diff --git a/.gitignore b/.gitignore index 0c6f4cb79dd0..eb9aedb4e2f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .DS_Store +.tags* +/.idea/ /build/ /dist/ /external_binaries/ diff --git a/.travis.yml b/.travis.yml index 4d4cd5dde9f0..8b4343138994 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ os: - osx env: - TARGET_ARCH=x64 +osx_image: xcode7 matrix: include: diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000000..444ce0b4c821 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,24 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery +- Personal attacks +- Trolling or insulting/derogatory comments +- Public or private harassment +- Publishing other's private information, such as physical or electronic addresses, without explicit permission +- Other unethical or unprofessional conduct + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at [atom@github.com](mailto:atom@github.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. + +This Code of Conduct is adapted from the Contributor Covenant, version 1.3.0, available from http://contributor-covenant.org/version/1/3/0/ diff --git a/CONTRIBUTING-ko.md b/CONTRIBUTING-ko.md new file mode 100644 index 000000000000..52887c8d38a7 --- /dev/null +++ b/CONTRIBUTING-ko.md @@ -0,0 +1,80 @@ +# Electron에 기여하기 + +:+1::tada: 먼저, 이 프로젝트에 기여해주셔서 감사합니다! :tada::+1: + +이 프로젝트는 기여자 규약 [행동강령](CODE_OF_CONDUCT.md)을 준수합니다. 따라서 이 +프로젝트의 개발에 참여하려면 이 규약을 지켜야 합니다. 받아들일 수 없는 행위를 발견했을 +경우 atom@github.com로 보고 하십시오. + +다음 항목들은 Electron에 기여하는 가이드라인을 제시합니다. +참고로 이 항목들은 그저 가이드라인에 불과하며 규칙이 아닙니다. 따라서 스스로의 적절한 +판단에 따라 이 문서의 변경을 제안할 수 있으며 변경시 pull request를 넣으면 됩니다. + +## 이슈 제출 + +* [여기](https://github.com/atom/electron/issues/new)에서 새로운 이슈를 만들 수 +있습니다. 하지만 이슈를 작성하기 전에 아래의 항목들을 숙지하고 가능한한 이슈 보고에 +대해 최대한 많은 정보와 자세한 설명을 포함해야 합니다. 가능하다면 다음 항목을 포함해야 +합니다: + * 사용하고 있는 Electron의 버전 + * 현재 사용중인 운영체제 + * 가능하다면 무엇을 하려고 했고, 어떤 결과를 예측했으며, 어떤 것이 예측된대로 + 작동하지 않았는지에 대해 서술해야 합니다. +* 추가로 다음 사항을 준수하면 이슈를 해결하는데 큰 도움이 됩니다: + * 스크린샷 또는 GIF 애니메이션 이미지들 + * 터미널에 출력된 에러의 내용 또는 개발자 도구, 알림창에 뜬 내용 + * [Cursory search](https://github.com/atom/electron/issues?utf8=✓&q=is%3Aissue+)를 + 통해 이미 비슷한 내용의 이슈가 등록되어있는지 확인 + +## Pull Request 하기 + +* 가능한한 스크린샷과 GIF 애니메이션 이미지를 pull request에 추가 +* CoffeeScript, JavaScript, C++과 Python등 +[참조문서에 정의된 코딩스타일](/docs-translations/ko-KR/development/coding-style.md)을 +준수 +* [문서 스타일 가이드](/docs-translations/ko-KR/styleguide.md)에 따라 문서를 +[Markdown](https://daringfireball.net/projects/markdown) 형식으로 작성. +* 짧은, 현재 시제 커밋 메시지 사용. [커밋 메시지 스타일 가이드](#Git-커밋-메시지)를 +참고하세요 + +## 스타일 가이드 + +### 공통 코드 + +* 파일 마지막에 공백 라인(newline) 추가 +* 다음 순서에 맞춰서 require 코드 작성: + * Node 빌트인 모듈 (`path` 같은) + * Electron 모듈 (`ipc`, `app` 같은) + * 로컬 모듈 (상대 경로상에 있는) +* 다음 순서에 맞춰서 클래스 속성 지정: + * 클래스 메서드와 속성 (메서드는 `@`로 시작) + * 인스턴스 메서드와 속성 +* 플랫폼 종속적인 코드 자제: + * 파일 이름 결합시 `path.join()`을 사용. + * 임시 디렉터리가 필요할 땐 `/tmp` 대신 `os.tmpdir()`을 통해 접근. +* 명시적인 함수 종료가 필요할 땐 `return` 만 사용. + * `return null`, `return undefined`, `null`, 또는 `undefined` 사용 X + +### Git 커밋 메시지 + +* 현재 시제 사용 ("Added feature" 대신 "Add feature" 사용) +* 필수적 분위기(imperative mood) 사용 ("Moves cursor to..." 대신 "Move cursor to..." 사용) +* 첫 줄은 72자에 맞추거나 그 보다 적게 제한 +* 자유롭게 필요에 따라 이슈나 PR링크를 참조 +* 단순한 문서 변경일 경우 `[ci skip]`을 커밋 메시지에 추가 +* 커밋 메시지의 도입부에 의미있는 이모티콘 사용: + * :art: `:art:` 코드의 포맷이나 구조를 개선(추가)했을 때 + * :racehorse: `:racehorse:` 성능을 개선했을 때 + * :non-potable_water: `:non-potable_water:` 메모리 누수를 연결했을 때 + * :memo: `:memo:` 문서를 작성했을 때 + * :penguin: `:penguin:` Linux에 대한 패치를 했을 때 + * :apple: `:apple:` Mac OS에 대한 패치를 했을 때 + * :checkered_flag: `:checkered_flag:` Windows에 대한 패치를 했을 때 + * :bug: `:bug:` 버그를 고쳤을 때 + * :fire: `:fire:` 코드 또는 파일을 삭제했을 때 + * :green_heart: `:green_heart:` CI 빌드를 고쳤을 때 + * :white_check_mark: `:white_check_mark:` 테스트를 추가했을 때 + * :lock: `:lock:` 보안 문제를 해결했을 때 + * :arrow_up: `:arrow_up:` 종속성 라이브러리를 업데이트 했을 때 + * :arrow_down: `:arrow_down:` 종속성 라이브러리를 다운그레이드 했을 때 + * :shirt: `:shirt:` linter(코드 검사기)의 경고를 제거했을 때 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6ca3ea5d2f32..67c1323205e5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,8 +2,9 @@ :+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. +This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md). +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 @@ -57,6 +58,7 @@ possible with your report. If you can, please include: * 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 +* When only changing documentation, include `[ci skip]` in the commit description * Consider starting the commit message with an applicable emoji: * :art: `:art:` when improving the format/structure of the code * :racehorse: `:racehorse:` when improving performance diff --git a/README-ko.md b/README-ko.md index 1481e7ce3dc5..2bd24dfe5800 100644 --- a/README-ko.md +++ b/README-ko.md @@ -8,18 +8,26 @@ :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 프레임워크는 JavaScript, HTML 그리고 CSS를 사용하여 +Cross-Platform 데스크톱 어플리케이션을 개발할 수 있도록 해주는 프레임워크입니다. +[Node.js](https://nodejs.org/)와 [Chromium](http://www.chromium.org)을 기반으로 +만들어졌으며 [Atom Editor](https://github.com/atom/atom)에 사용되고 있습니다. -Electron에 대한 중요한 알림을 받고 싶다면 Twitter에서 [@ElectronJS](https://twitter.com/electronjs)를 팔로우 하세요. +Electron에 대한 중요한 알림을 받고 싶다면 Twitter에서 +[@ElectronJS](https://twitter.com/electronjs)를 팔로우 하세요. -이 프로젝트는 기여자 규약 1.2를 준수합니다. 이 프로젝트에 참여할 때 코드를 유지해야 합니다. 받아들일 수 없는 행동은 atom@github.com로 보고 하십시오. +이 프로젝트는 기여자 규약 [행동강령](CODE_OF_CONDUCT.md)을 준수합니다. 따라서 이 +프로젝트의 개발에 참여하려면 이 규약을 지켜야 합니다. 받아들일 수 없는 행위를 발견했을 +경우 atom@github.com로 보고 하십시오. ## 다운로드 -Linux, Windows, Mac용으로 미리 빌드된 Electron 바이너리와 디버그 심볼이 준비되어 있습니다. [releases](https://github.com/atom/electron/releases) 페이지에서 받아 볼 수 있습니다. +Linux, Windows, OS X 용으로 미리 빌드된 Electron 바이너리와 디버그 심볼이 준비되어 +있습니다. [releases](https://github.com/atom/electron/releases) 페이지에서 받아 볼 +수 있습니다. -또한 [`npm`](https://docs.npmjs.com/)을 통해 미리 빌드된 Electron 바이너리를 받을 수도 있습니다: +또한 [`npm`](https://docs.npmjs.com/)을 통해 미리 빌드된 Electron 바이너리를 설치할 +수도 있습니다: ```sh # $PATH에 `electron` 커맨드를 등록하고 전역에 설치합니다. @@ -35,8 +43,9 @@ npm install electron-prebuilt --save-dev ## 참조 문서 -[Docs](https://github.com/atom/electron/tree/master/docs/README.md)에 개발 가이드와 API 레퍼런스가 있습니다. -Electron을 빌드 하는 방법과 프로젝트에 기여하는 방법도 문서에 포함되어 있으니 참고하시기 바랍니다. +[Docs](https://github.com/atom/electron/tree/master/docs/README.md)에 개발 지침과 +API 레퍼런스가 있습니다. Electron을 빌드 하는 방법과 프로젝트에 기여하는법 또한 문서에 +포함되어 있으니 참고하시기 바랍니다. ## 참조 문서 (번역) @@ -47,12 +56,19 @@ Electron을 빌드 하는 방법과 프로젝트에 기여하는 방법도 문 - [중국어 간체](https://github.com/atom/electron/tree/master/docs-translations/zh-CN) - [중국어 번체](https://github.com/atom/electron/tree/master/docs-translations/zh-TW) +## 시작하기 + +[`atom/electron-quick-start`](https://github.com/atom/electron-quick-start) +저장소를 클론하여 Electron을 간단히 접해볼 수 있습니다. + ## 커뮤니티 -다음 링크를 통해 커뮤니티에 질문을 올리거나 토론을 나누실 수 있습니다: +다음 링크를 통해 커뮤니티에 질문을 올리거나 토론을 나눌 수 있습니다: -- Atom 포럼의 [`electron`](http://discuss.atom.io/category/electron) 카테고리 -- Freenode 채팅의 `#atom-shell` 채널 +- Atom 포럼의 [`electron`](http://discuss.atom.io/c/electron) 카테고리 +- Freenode 채팅의 `#atom-shell` 채널 - Slack의 [`Atom`](http://atom-slack.herokuapp.com/) 채널 -[awesome-electron](https://github.com/sindresorhus/awesome-electron) 프로젝트엔 커뮤니티가 운영중인 유용한 예제 어플리케이션과 도구, 리소스가 있으니 한번 참고해 보시기 바랍니다. +[awesome-electron](https://github.com/sindresorhus/awesome-electron) 프로젝트에 +커뮤니티가 운영중인 유용한 예제 어플리케이션과 도구, 리소스가 있으니 한번 참고해 보시기 +바랍니다. diff --git a/README.md b/README.md index 0bf29fbc3e31..bb814b245139 100644 --- a/README.md +++ b/README.md @@ -7,20 +7,20 @@ :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 +using JavaScript, HTML and CSS. It is based on [Node.js](https://nodejs.org/) and [Chromium](http://www.chromium.org) and is used in the [Atom 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. +This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md). +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 +Prebuilt binaries and debug symbols of Electron for Linux, Windows and OS X can be found on the [releases](https://github.com/atom/electron/releases) page. You can also use [`npm`](https://docs.npmjs.com/) to install prebuilt electron @@ -52,15 +52,22 @@ contains documents describing how to build and contribute to Electron. - [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) +- [Russian](https://github.com/atom/electron/tree/master/docs-translations/ru-RU) + +## Quick Start + +Clone and run the [`atom/electron-quick-start`](https://github.com/atom/electron-quick-start) +repository to see a minimal Electron app in action. ## Community -You can ask questions and interact with the community in the following +You can ask questions and interact with the community in the following locations: -- [`electron`](http://discuss.atom.io/category/electron) category on the Atom +- [`electron`](http://discuss.atom.io/c/electron) category on the Atom forums - `#atom-shell` channel on Freenode - [`Atom`](http://atom-slack.herokuapp.com/) channel on Slack +- [`electron-br`](https://electron-br.slack.com) *(Brazilian Portuguese)* -Check out [awesome-electron](https://github.com/sindresorhus/awesome-electron) +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 b2e55e9be308..edfb8009c6d1 100644 --- a/atom.gyp +++ b/atom.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '0.33.1', + 'version%': '0.36.0', }, 'includes': [ 'filenames.gypi', @@ -64,9 +64,6 @@ 'files': [ '<(PRODUCT_DIR)/<(product_name) Helper.app', '<(PRODUCT_DIR)/<(product_name) Framework.framework', - 'external_binaries/Squirrel.framework', - 'external_binaries/ReactiveCocoa.framework', - 'external_binaries/Mantle.framework', ], }, { @@ -109,7 +106,21 @@ '<@(locale_dirs)', ], }, - ] + ], + 'conditions': [ + ['mas_build==0', { + 'copies': [ + { + 'destination': '<(PRODUCT_DIR)/<(product_name).app/Contents/Frameworks', + 'files': [ + 'external_binaries/Squirrel.framework', + 'external_binaries/ReactiveCocoa.framework', + 'external_binaries/Mantle.framework', + ], + }, + ], + }], + ], }, { # OS=="mac" 'dependencies': [ 'make_locale_paks', @@ -290,12 +301,28 @@ 'vendor/breakpad/breakpad.gyp:breakpad_sender', ], }], # OS=="win" - ['OS=="mac"', { + ['OS=="mac" and mas_build==0', { 'dependencies': [ 'vendor/crashpad/client/client.gyp:crashpad_client', 'vendor/crashpad/handler/handler.gyp:crashpad_handler', ], - }], # OS=="mac" + 'link_settings': { + # Do not link with QTKit for mas build. + 'libraries': [ + '$(SDKROOT)/System/Library/Frameworks/QTKit.framework', + ], + }, + }], # OS=="mac" and mas_build==0 + ['OS=="mac" and mas_build==1', { + 'defines': [ + 'MAS_BUILD', + ], + 'sources!': [ + 'atom/browser/auto_updater_mac.mm', + 'atom/common/crash_reporter/crash_reporter_mac.h', + 'atom/common/crash_reporter/crash_reporter_mac.mm', + ], + }], # OS=="mac" and mas_build==1 ['OS=="linux"', { 'link_settings': { 'ldflags': [ @@ -398,9 +425,6 @@ 'libraries': [ '$(SDKROOT)/System/Library/Frameworks/Carbon.framework', '$(SDKROOT)/System/Library/Frameworks/QuartzCore.framework', - 'external_binaries/Squirrel.framework', - 'external_binaries/ReactiveCocoa.framework', - 'external_binaries/Mantle.framework', ], }, 'mac_bundle': 1, @@ -444,12 +468,6 @@ '<@(copied_libraries)', ], }, - { - 'destination': '<(PRODUCT_DIR)/<(product_name) Framework.framework/Versions/A/Resources', - 'files': [ - '<(PRODUCT_DIR)/crashpad_handler', - ], - }, ], 'postbuilds': [ { @@ -481,6 +499,25 @@ ], }, ], + 'conditions': [ + ['mas_build==0', { + 'link_settings': { + 'libraries': [ + 'external_binaries/Squirrel.framework', + 'external_binaries/ReactiveCocoa.framework', + 'external_binaries/Mantle.framework', + ], + }, + 'copies': [ + { + 'destination': '<(PRODUCT_DIR)/<(product_name) Framework.framework/Versions/A/Resources', + 'files': [ + '<(PRODUCT_DIR)/crashpad_handler', + ], + }, + ], + }], + ], }, # target framework { 'target_name': '<(project_name)_helper', diff --git a/atom/app/atom_content_client.cc b/atom/app/atom_content_client.cc index e760c01453d4..9f161ac569a6 100644 --- a/atom/app/atom_content_client.cc +++ b/atom/app/atom_content_client.cc @@ -31,8 +31,8 @@ content::PepperPluginInfo CreatePepperFlashInfo(const base::FilePath& path, plugin.path = path; plugin.permissions = ppapi::PERMISSION_ALL_BITS; - std::vector flash_version_numbers; - base::SplitString(version, '.', &flash_version_numbers); + std::vector flash_version_numbers = base::SplitString( + version, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); if (flash_version_numbers.size() < 1) flash_version_numbers.push_back("11"); // |SplitString()| puts in an empty string given an empty string. :( @@ -47,7 +47,7 @@ content::PepperPluginInfo CreatePepperFlashInfo(const base::FilePath& path, // 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, '.'); + plugin.version = base::JoinString(flash_version_numbers, "."); content::WebPluginMimeType swf_mime_type( content::kFlashPluginSwfMimeType, content::kFlashPluginSwfExtension, @@ -81,25 +81,24 @@ std::string AtomContentClient::GetUserAgent() const { } void AtomContentClient::AddAdditionalSchemes( - std::vector* standard_schemes, + 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()); + std::vector schemes = base::SplitString( + custom_schemes, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + for (const std::string& scheme : schemes) + standard_schemes->push_back({scheme.c_str(), url::SCHEME_WITHOUT_PORT}); } - standard_schemes->push_back("chrome-extension"); + standard_schemes->push_back({"chrome-extension", url::SCHEME_WITHOUT_PORT}); } void AtomContentClient::AddPepperPlugins( std::vector* plugins) { auto command_line = base::CommandLine::ForCurrentProcess(); - auto flash_path = command_line->GetSwitchValueNative( + auto flash_path = command_line->GetSwitchValuePath( switches::kPpapiFlashPath); if (flash_path.empty()) return; @@ -108,7 +107,7 @@ void AtomContentClient::AddPepperPlugins( switches::kPpapiFlashVersion); plugins->push_back( - CreatePepperFlashInfo(base::FilePath(flash_path), flash_version)); + CreatePepperFlashInfo(flash_path, flash_version)); } } // namespace atom diff --git a/atom/app/atom_content_client.h b/atom/app/atom_content_client.h index a6b2f73e7faa..2716b1eea400 100644 --- a/atom/app/atom_content_client.h +++ b/atom/app/atom_content_client.h @@ -22,7 +22,7 @@ class AtomContentClient : public brightray::ContentClient { std::string GetProduct() const override; std::string GetUserAgent() const override; void AddAdditionalSchemes( - std::vector* standard_schemes, + std::vector* standard_schemes, std::vector* savable_schemes) override; void AddPepperPlugins( std::vector* plugins) override; diff --git a/atom/app/atom_library_main.mm b/atom/app/atom_library_main.mm index 885500beb5d3..7ee752293468 100644 --- a/atom/app/atom_library_main.mm +++ b/atom/app/atom_library_main.mm @@ -10,6 +10,7 @@ #include "base/at_exit.h" #include "base/i18n/icu_util.h" #include "base/mac/bundle_locations.h" +#include "base/mac/scoped_nsautorelease_pool.h" #include "brightray/common/mac/main_application_bundle.h" #include "content/public/app/content_main.h" @@ -25,6 +26,7 @@ int AtomMain(int argc, const char* argv[]) { int AtomInitializeICUandStartNode(int argc, char *argv[]) { base::AtExitManager atexit_manager; + base::mac::ScopedNSAutoreleasePool pool; base::mac::SetOverrideFrameworkBundlePath( brightray::MainApplicationBundlePath() .Append("Contents") diff --git a/atom/app/atom_main.cc b/atom/app/atom_main.cc index 26dcb9421266..5b5df448dfa8 100644 --- a/atom/app/atom_main.cc +++ b/atom/app/atom_main.cc @@ -5,7 +5,6 @@ #include "atom/app/atom_main.h" #include -#include #if defined(OS_WIN) #include @@ -36,10 +35,33 @@ #include "base/at_exit.h" #include "base/i18n/icu_util.h" -#if defined(OS_WIN) - namespace { +const char* kRunAsNode = "ELECTRON_RUN_AS_NODE"; +const char* kOldRunAsNode = "ATOM_SHELL_INTERNAL_RUN_AS_NODE"; + +bool IsEnvSet(const char* name) { +#if defined(OS_WIN) + size_t required_size; + getenv_s(&required_size, nullptr, 0, name); + return required_size != 0; +#else + char* indicator = getenv(name); + return indicator && indicator[0] != '\0'; +#endif +} + +bool IsRunAsNode() { + return IsEnvSet(kRunAsNode) || IsEnvSet(kOldRunAsNode); +} + +#if defined(OS_WIN) +bool IsCygwin() { + std::string os; + scoped_ptr env(base::Environment::Create()); + return env->GetVar("OS", &os) && os == "cygwin"; +} + // Win8.1 supports monitor-specific DPI scaling. bool SetProcessDpiAwarenessWrapper(PROCESS_DPI_AWARENESS value) { typedef HRESULT(WINAPI *SetProcessDpiAwarenessPtr)(PROCESS_DPI_AWARENESS); @@ -77,24 +99,22 @@ void EnableHighDPISupport() { SetProcessDPIAwareWrapper(); } } +#endif } // namespace +#if defined(OS_WIN) int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { int argc = 0; wchar_t** wargv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); - scoped_ptr env(base::Environment::Create()); - // Make output work in console if we are not in cygiwn. - std::string os; - if (env->GetVar("OS", &os) && os != "cygwin") { + if (!IsCygwin() && !IsEnvSet("ELECTRON_NO_ATTACH_CONSOLE")) { AttachConsole(ATTACH_PARENT_PROCESS); FILE* dontcare; freopen_s(&dontcare, "CON", "w", stdout); freopen_s(&dontcare, "CON", "w", stderr); - freopen_s(&dontcare, "CON", "r", stdin); } // Convert argv to to UTF8 @@ -131,16 +151,12 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { } } - std::string node_indicator, crash_service_indicator; - if (env->GetVar("ATOM_SHELL_INTERNAL_RUN_AS_NODE", &node_indicator) && - node_indicator == "1") { + if (IsRunAsNode()) { // 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", - &crash_service_indicator) && - crash_service_indicator == "1") { + } else if (IsEnvSet("ATOM_SHELL_INTERNAL_CRASH_SERVICE")) { return crash_service::Main(cmd); } @@ -164,8 +180,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { #elif defined(OS_LINUX) // defined(OS_WIN) 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) { + if (IsRunAsNode()) { base::i18n::InitializeICU(); base::AtExitManager atexit_manager; return atom::NodeMain(argc, const_cast(argv)); @@ -182,8 +197,7 @@ int main(int argc, const char* argv[]) { #else // defined(OS_LINUX) 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) { + if (IsRunAsNode()) { return AtomInitializeICUandStartNode(argc, const_cast(argv)); } diff --git a/atom/app/atom_main_delegate.cc b/atom/app/atom_main_delegate.cc index 3c7d6b2e7034..80283131069d 100644 --- a/atom/app/atom_main_delegate.cc +++ b/atom/app/atom_main_delegate.cc @@ -5,6 +5,7 @@ #include "atom/app/atom_main_delegate.h" #include +#include #include "atom/app/atom_content_client.h" #include "atom/browser/atom_browser_client.h" @@ -15,11 +16,21 @@ #include "base/debug/stack_trace.h" #include "base/environment.h" #include "base/logging.h" +#include "chrome/common/chrome_paths.h" #include "content/public/common/content_switches.h" #include "ui/base/resource/resource_bundle.h" namespace atom { +namespace { + +bool IsBrowserProcess(base::CommandLine* cmd) { + std::string process_type = cmd->GetSwitchValueASCII(switches::kProcessType); + return process_type.empty(); +} + +} // namespace + AtomMainDelegate::AtomMainDelegate() { } @@ -27,10 +38,16 @@ AtomMainDelegate::~AtomMainDelegate() { } bool AtomMainDelegate::BasicStartupComplete(int* exit_code) { - // Disable logging out to debug.log on Windows + auto command_line = base::CommandLine::ForCurrentProcess(); + logging::LoggingSettings settings; #if defined(OS_WIN) + // On Windows the terminal returns immediately, so we add a new line to + // prevent output in the same line as the prompt. + if (IsBrowserProcess(command_line)) + std::wcout << std::endl; #if defined(DEBUG) + // Print logging to debug.log on Windows settings.logging_dest = logging::LOG_TO_ALL; settings.log_file = L"debug.log"; settings.lock_log = logging::LOCK_LOG_FILE; @@ -41,15 +58,29 @@ bool AtomMainDelegate::BasicStartupComplete(int* exit_code) { #else // defined(OS_WIN) settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; #endif // !defined(OS_WIN) + + // Only enable logging when --enable-logging is specified. + scoped_ptr env(base::Environment::Create()); + if (!command_line->HasSwitch(switches::kEnableLogging) && + !env->HasVar("ELECTRON_ENABLE_LOGGING")) { + settings.logging_dest = logging::LOG_NONE; + logging::SetMinLogLevel(logging::LOG_NUM_SEVERITIES); + } + logging::InitLogging(settings); // Logging with pid and timestamp. logging::SetLogItems(true, false, true, false); -#if defined(DEBUG) && defined(OS_LINUX) // Enable convient stack printing. - base::debug::EnableInProcessStackDumping(); + bool enable_stack_dumping = env->HasVar("ELECTRON_ENABLE_STACK_DUMPING"); +#if defined(DEBUG) && defined(OS_LINUX) + enable_stack_dumping = true; #endif + if (enable_stack_dumping) + base::debug::EnableInProcessStackDumping(); + + chrome::RegisterPathProvider(); return brightray::MainDelegate::BasicStartupComplete(exit_code); } @@ -71,16 +102,9 @@ void AtomMainDelegate::PreSandboxStartup() { } // Only append arguments for browser process. - if (!process_type.empty()) + if (!IsBrowserProcess(command_line)) return; -#if defined(OS_WIN) - // Disable the LegacyRenderWidgetHostHWND, it made frameless windows unable - // to move and resize. We may consider enabling it again after upgraded to - // Chrome 38, which should have fixed the problem. - command_line->AppendSwitch(switches::kDisableLegacyIntermediateWindow); -#endif - // Disable renderer sandbox for most of node's functions. command_line->AppendSwitch(switches::kNoSandbox); diff --git a/atom/app/node_main.cc b/atom/app/node_main.cc index 7efb1b40eece..b946ae28ff94 100644 --- a/atom/app/node_main.cc +++ b/atom/app/node_main.cc @@ -7,8 +7,8 @@ #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 "atom/common/node_includes.h" #include "base/thread_task_runner_handle.h" #include "gin/array_buffer.h" #include "gin/public/isolate_holder.h" @@ -19,25 +19,22 @@ 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; - node::Init(&argc, const_cast(argv), &exec_argc, &exec_argv); - int exit_code = 1; { // Feed gin::PerIsolateData with a task runner. + argv = uv_setup_args(argc, argv); 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; + + int exec_argc; + const char** exec_argv; + node::Init(&argc, const_cast(argv), &exec_argc, &exec_argv); + node::Environment* env = node::CreateEnvironment( gin_env.isolate(), loop, gin_env.context(), argc, argv, exec_argc, exec_argv); diff --git a/atom/app/uv_task_runner.cc b/atom/app/uv_task_runner.cc index 23463e44e2ef..f49ba259bac8 100644 --- a/atom/app/uv_task_runner.cc +++ b/atom/app/uv_task_runner.cc @@ -48,8 +48,13 @@ void UvTaskRunner::OnTimeout(uv_timer_t* timer) { self->tasks_[timer].Run(); self->tasks_.erase(timer); - uv_unref(reinterpret_cast(timer)); - delete timer; + uv_timer_stop(timer); + uv_close(reinterpret_cast(timer), UvTaskRunner::OnClose); +} + +// static +void UvTaskRunner::OnClose(uv_handle_t* handle) { + delete reinterpret_cast(handle); } } // namespace atom diff --git a/atom/app/uv_task_runner.h b/atom/app/uv_task_runner.h index e4eac0155354..c7302766c2d1 100644 --- a/atom/app/uv_task_runner.h +++ b/atom/app/uv_task_runner.h @@ -31,6 +31,7 @@ class UvTaskRunner : public base::SingleThreadTaskRunner { private: static void OnTimeout(uv_timer_t* timer); + static void OnClose(uv_handle_t* handle); uv_loop_t* loop_; diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 2e7596971f48..697d6eca6aab 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -7,26 +7,29 @@ #include #include -#if defined(OS_WIN) -#include -#endif - #include "atom/browser/api/atom_api_menu.h" #include "atom/browser/api/atom_api_session.h" +#include "atom/browser/api/atom_api_web_contents.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/browser/login_handler.h" #include "atom/common/native_mate_converters/callback.h" +#include "atom/common/native_mate_converters/net_converter.h" #include "atom/common/native_mate_converters/file_path_converter.h" +#include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/node_includes.h" +#include "atom/common/options_switches.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 "chrome/common/chrome_paths.h" #include "content/public/browser/client_certificate_delegate.h" #include "content/public/browser/gpu_data_manager.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/common/content_switches.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" #include "net/ssl/ssl_cert_request_info.h" @@ -61,21 +64,6 @@ 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 @@ -99,22 +87,47 @@ int GetPathConstant(const std::string& name) { return base::DIR_HOME; else if (name == "temp") return base::DIR_TEMP; - else if (name == "userDesktop") + else if (name == "userDesktop" || name == "desktop") return base::DIR_USER_DESKTOP; else if (name == "exe") return base::FILE_EXE; else if (name == "module") return base::FILE_MODULE; + else if (name == "documents") + return chrome::DIR_USER_DOCUMENTS; + else if (name == "downloads") + return chrome::DIR_DEFAULT_DOWNLOADS; + else if (name == "music") + return chrome::DIR_USER_MUSIC; + else if (name == "pictures") + return chrome::DIR_USER_PICTURES; + else if (name == "videos") + return chrome::DIR_USER_VIDEOS; else return -1; } +bool NotificationCallbackWrapper( + const ProcessSingleton::NotificationCallback& callback, + const base::CommandLine::StringVector& cmd, + const base::FilePath& cwd) { + // Make sure the callback is called after app gets ready. + if (Browser::Get()->is_ready()) { + callback.Run(cmd, cwd); + } else { + scoped_refptr task_runner( + base::ThreadTaskRunnerHandle::Get()); + task_runner->PostTask( + FROM_HERE, base::Bind(base::IgnoreResult(callback), cmd, cwd)); + } + // ProcessSingleton needs to know whether current process is quiting. + return !Browser::Get()->is_shutting_down(); +} + 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(); @@ -128,18 +141,29 @@ void OnClientCertificateSelected( net::X509Certificate::CreateCertificateListFromBytes( encoded_data.data(), encoded_data.size(), net::X509Certificate::FORMAT_AUTO); - delegate->ContinueWithCertificate(certs[0].get()); } +void PassLoginInformation(scoped_refptr login_handler, + mate::Arguments* args) { + base::string16 username, password; + if (args->GetNext(&username) && args->GetNext(&password)) + login_handler->Login(username, password); + else + login_handler->CancelAuth(); +} + } // namespace App::App() { + static_cast(AtomBrowserClient::Get())->set_delegate(this); Browser::Get()->AddObserver(this); content::GpuDataManager::GetInstance()->AddObserver(this); } App::~App() { + static_cast(AtomBrowserClient::Get())->set_delegate( + nullptr); Browser::Get()->RemoveObserver(this); content::GpuDataManager::GetInstance()->RemoveObserver(this); } @@ -158,6 +182,11 @@ void App::OnWindowAllClosed() { void App::OnQuit() { Emit("quit"); + + if (process_singleton_.get()) { + process_singleton_->Cleanup(); + process_singleton_.reset(); + } } void App::OnOpenFile(bool* prevent_default, const std::string& file_path) { @@ -177,26 +206,62 @@ 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( +void App::OnLogin(LoginHandler* login_handler) { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + bool prevent_default = Emit( + "login", + WebContents::CreateFrom(isolate(), login_handler->GetWebContents()), + login_handler->request(), + login_handler->auth_info(), + base::Bind(&PassLoginInformation, make_scoped_refptr(login_handler))); + + // Default behavior is to always cancel the auth. + if (!prevent_default) + login_handler->CancelAuth(); +} + +void App::AllowCertificateError( + int pid, + int fid, + int cert_error, + const net::SSLInfo& ssl_info, + const GURL& request_url, + content::ResourceType resource_type, + bool overridable, + bool strict_enforcement, + bool expired_previous_decision, + const base::Callback& callback, + content::CertificateRequestResultType* request) { + auto rfh = content::RenderFrameHost::FromID(pid, fid); + auto web_contents = content::WebContents::FromRenderFrameHost(rfh); + + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + bool prevent_default = Emit("certificate-error", + WebContents::CreateFrom(isolate(), web_contents), + request_url, + net::ErrorToString(cert_error), + ssl_info.cert, + callback); + + // Deny the certificate by default. + if (!prevent_default) + *request = content::CERTIFICATE_REQUEST_RESULT_TYPE_DENY; +} + +void App::SelectClientCertificate( 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), + Emit("select-client-certificate", + WebContents::CreateFrom(isolate(), web_contents), cert_request_info->host_and_port.ToString(), cert_request_info->client_certs, base::Bind(&OnClientCertificateSelected, @@ -242,22 +307,36 @@ void App::SetDesktopName(const std::string& desktop_name) { #endif } -void App::SetAppUserModelId(const std::string& app_id) { -#if defined(OS_WIN) - base::string16 app_id_utf16 = base::UTF8ToUTF16(app_id); - SetCurrentProcessExplicitAppUserModelID(app_id_utf16.c_str()); -#endif +void App::AllowNTLMCredentialsForAllDomains(bool should_allow) { + auto browser_context = static_cast( + AtomBrowserMainParts::Get()->browser_context()); + browser_context->AllowNTLMCredentialsForAllDomains(should_allow); } 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_); +bool App::MakeSingleInstance( + const ProcessSingleton::NotificationCallback& callback) { + if (process_singleton_.get()) + return false; + + base::FilePath user_dir; + PathService::Get(brightray::DIR_USER_DATA, &user_dir); + process_singleton_.reset(new ProcessSingleton( + user_dir, base::Bind(NotificationCallbackWrapper, callback))); + + switch (process_singleton_->NotifyOtherProcessOrCreate()) { + case ProcessSingleton::NotifyResult::LOCK_ERROR: + case ProcessSingleton::NotifyResult::PROFILE_IN_USE: + case ProcessSingleton::NotifyResult::PROCESS_NOTIFIED: + process_singleton_.reset(); + return true; + case ProcessSingleton::NotifyResult::PROCESS_NONE: + default: // Shouldn't be needed, but VS warns if it is not there. + return false; + } } mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder( @@ -265,6 +344,7 @@ mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder( auto browser = base::Unretained(Browser::Get()); return mate::ObjectTemplateBuilder(isolate) .SetMethod("quit", base::Bind(&Browser::Quit, browser)) + .SetMethod("exit", base::Bind(&Browser::Exit, browser)) .SetMethod("focus", base::Bind(&Browser::Focus, browser)) .SetMethod("getVersion", base::Bind(&Browser::GetVersion, browser)) .SetMethod("setVersion", base::Bind(&Browser::SetVersion, browser)) @@ -275,6 +355,8 @@ mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder( base::Bind(&Browser::AddRecentDocument, browser)) .SetMethod("clearRecentDocuments", base::Bind(&Browser::ClearRecentDocuments, browser)) + .SetMethod("setAppUserModelId", + base::Bind(&Browser::SetAppUserModelID, browser)) #if defined(OS_WIN) .SetMethod("setUserTasks", base::Bind(&Browser::SetUserTasks, browser)) @@ -282,9 +364,10 @@ mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder( .SetMethod("setPath", &App::SetPath) .SetMethod("getPath", &App::GetPath) .SetMethod("setDesktopName", &App::SetDesktopName) - .SetMethod("setAppUserModelId", &App::SetAppUserModelId) + .SetMethod("allowNTLMCredentialsForAllDomains", + &App::AllowNTLMCredentialsForAllDomains) .SetMethod("getLocale", &App::GetLocale) - .SetProperty("defaultSession", &App::DefaultSession); + .SetMethod("makeSingleInstance", &App::MakeSingleInstance); } // static @@ -301,6 +384,16 @@ namespace { void AppendSwitch(const std::string& switch_string, mate::Arguments* args) { auto command_line = base::CommandLine::ForCurrentProcess(); + + if (switch_string == atom::switches::kPpapiFlashPath || + switch_string == atom::switches::kClientCertificate || + switch_string == switches::kLogNetLog) { + base::FilePath path; + args->GetNext(&path); + command_line->AppendSwitchPath(switch_string, path); + return; + } + std::string value; if (args->GetNext(&value)) command_line->AppendSwitchASCII(switch_string, value); diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 4896a5f066a1..a6f99d65e0af 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -8,7 +8,10 @@ #include #include "atom/browser/api/event_emitter.h" +#include "atom/browser/atom_browser_client.h" #include "atom/browser/browser_observer.h" +#include "atom/common/native_mate_converters/callback.h" +#include "chrome/browser/process_singleton.h" #include "content/public/browser/gpu_data_manager_observer.h" #include "native_mate/handle.h" @@ -24,7 +27,8 @@ namespace atom { namespace api { -class App : public mate::EventEmitter, +class App : public AtomBrowserClient::Delegate, + public mate::EventEmitter, public BrowserObserver, public content::GpuDataManagerObserver { public: @@ -44,7 +48,22 @@ class App : public mate::EventEmitter, void OnActivate(bool has_visible_windows) override; void OnWillFinishLaunching() override; void OnFinishLaunching() override; - void OnSelectCertificate( + void OnLogin(LoginHandler* login_handler) override; + + // content::ContentBrowserClient: + void AllowCertificateError( + int render_process_id, + int render_frame_id, + int cert_error, + const net::SSLInfo& ssl_info, + const GURL& request_url, + content::ResourceType resource_type, + bool overridable, + bool strict_enforcement, + bool expired_previous_decision, + const base::Callback& callback, + content::CertificateRequestResultType* request) override; + void SelectClientCertificate( content::WebContents* web_contents, net::SSLCertRequestInfo* cert_request_info, scoped_ptr delegate) override; @@ -64,11 +83,12 @@ class App : public mate::EventEmitter, const base::FilePath& path); void SetDesktopName(const std::string& desktop_name); - void SetAppUserModelId(const std::string& app_id); + void AllowNTLMCredentialsForAllDomains(bool should_allow); + bool MakeSingleInstance( + const ProcessSingleton::NotificationCallback& callback); std::string GetLocale(); - v8::Local DefaultSession(v8::Isolate* isolate); - v8::Global default_session_; + scoped_ptr process_singleton_; 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 250aa3e45f64..1a02a54d4533 100644 --- a/atom/browser/api/atom_api_auto_updater.cc +++ b/atom/browser/api/atom_api_auto_updater.cc @@ -5,12 +5,31 @@ #include "atom/browser/api/atom_api_auto_updater.h" #include "base/time/time.h" -#include "atom/browser/auto_updater.h" #include "atom/browser/browser.h" +#include "atom/browser/native_window.h" +#include "atom/browser/window_list.h" +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/node_includes.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" +namespace mate { + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const base::Time& val) { + v8::MaybeLocal date = v8::Date::New( + isolate->GetCurrentContext(), val.ToJsTime()); + if (date.IsEmpty()) + return v8::Null(isolate); + else + return date.ToLocalChecked(); + } +}; + +} // namespace mate + namespace atom { namespace api { @@ -20,11 +39,18 @@ AutoUpdater::AutoUpdater() { } AutoUpdater::~AutoUpdater() { - auto_updater::AutoUpdater::SetDelegate(NULL); + auto_updater::AutoUpdater::SetDelegate(nullptr); } -void AutoUpdater::OnError(const std::string& error) { - Emit("error", error); +void AutoUpdater::OnError(const std::string& message) { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + auto error = v8::Exception::Error(mate::StringToV8(isolate(), message)); + EmitCustomEvent( + "error", + error->ToObject(isolate()->GetCurrentContext()).ToLocalChecked(), + // Message is also emitted to keep compatibility with old code. + message); } void AutoUpdater::OnCheckingForUpdate() { @@ -42,26 +68,36 @@ void AutoUpdater::OnUpdateNotAvailable() { void AutoUpdater::OnUpdateDownloaded(const std::string& release_notes, const std::string& release_name, const base::Time& release_date, - const std::string& update_url, - const base::Closure& quit_and_install) { - quit_and_install_ = quit_and_install; - Emit("update-downloaded-raw", release_notes, release_name, - release_date.ToJsTime(), update_url); + const std::string& url) { + Emit("update-downloaded", release_notes, release_name, release_date, url, + // Keep compatibility with old APIs. + base::Bind(&AutoUpdater::QuitAndInstall, base::Unretained(this))); +} + +void AutoUpdater::OnWindowAllClosed() { + QuitAndInstall(); } mate::ObjectTemplateBuilder AutoUpdater::GetObjectTemplateBuilder( v8::Isolate* isolate) { return mate::ObjectTemplateBuilder(isolate) - .SetMethod("setFeedUrl", &auto_updater::AutoUpdater::SetFeedURL) + .SetMethod("setFeedURL", &auto_updater::AutoUpdater::SetFeedURL) .SetMethod("checkForUpdates", &auto_updater::AutoUpdater::CheckForUpdates) - .SetMethod("_quitAndInstall", &AutoUpdater::QuitAndInstall); + .SetMethod("quitAndInstall", &AutoUpdater::QuitAndInstall); } void AutoUpdater::QuitAndInstall() { - if (quit_and_install_.is_null()) - Browser::Get()->Shutdown(); - else - quit_and_install_.Run(); + // If we don't have any window then quitAndInstall immediately. + WindowList* window_list = WindowList::GetInstance(); + if (window_list->size() == 0) { + auto_updater::AutoUpdater::QuitAndInstall(); + return; + } + + // Otherwise do the restart after all windows have been closed. + window_list->AddObserver(this); + for (NativeWindow* window : *window_list) + window->Close(); } // static diff --git a/atom/browser/api/atom_api_auto_updater.h b/atom/browser/api/atom_api_auto_updater.h index 50c3989703a1..95b91041e9e3 100644 --- a/atom/browser/api/atom_api_auto_updater.h +++ b/atom/browser/api/atom_api_auto_updater.h @@ -7,9 +7,9 @@ #include -#include "base/callback.h" #include "atom/browser/api/event_emitter.h" -#include "atom/browser/auto_updater_delegate.h" +#include "atom/browser/auto_updater.h" +#include "atom/browser/window_list_observer.h" #include "native_mate/handle.h" namespace atom { @@ -17,7 +17,8 @@ namespace atom { namespace api { class AutoUpdater : public mate::EventEmitter, - public auto_updater::AutoUpdaterDelegate { + public auto_updater::Delegate, + public WindowListObserver { public: static mate::Handle Create(v8::Isolate* isolate); @@ -25,17 +26,18 @@ class AutoUpdater : public mate::EventEmitter, AutoUpdater(); virtual ~AutoUpdater(); - // AutoUpdaterDelegate implementations. + // Delegate implementations. void OnError(const std::string& error) override; void OnCheckingForUpdate() override; void OnUpdateAvailable() override; void OnUpdateNotAvailable() override; - void OnUpdateDownloaded( - const std::string& release_notes, - const std::string& release_name, - const base::Time& release_date, - const std::string& update_url, - const base::Closure& quit_and_install) override; + void OnUpdateDownloaded(const std::string& release_notes, + const std::string& release_name, + const base::Time& release_date, + const std::string& update_url) override; + + // WindowListObserver: + void OnWindowAllClosed() override; // mate::Wrappable implementations: mate::ObjectTemplateBuilder GetObjectTemplateBuilder( @@ -44,8 +46,6 @@ class AutoUpdater : public mate::EventEmitter, private: void QuitAndInstall(); - base::Closure quit_and_install_; - DISALLOW_COPY_AND_ASSIGN(AutoUpdater); }; diff --git a/atom/browser/api/atom_api_cookies.cc b/atom/browser/api/atom_api_cookies.cc index 4f989eff7275..3b1bd499be4c 100644 --- a/atom/browser/api/atom_api_cookies.cc +++ b/atom/browser/api/atom_api_cookies.cc @@ -204,7 +204,7 @@ void Cookies::GetCookiesOnIOThread(scoped_ptr filter, Passed(&filter), callback))) { BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(&RunGetCookiesCallbackOnUIThread, isolate(), - "Url is not valid", net::CookieList(), callback)); + "URL is not valid", net::CookieList(), callback)); } } @@ -229,7 +229,7 @@ void Cookies::Remove(const mate::Dictionary& details, error_message = "Details(url, name) of removing cookie are required."; } if (error_message.empty() && !url.is_valid()) { - error_message = "Url is not valid."; + error_message = "URL is not valid."; } if (!error_message.empty()) { RunRemoveCookiesCallbackOnUIThread(isolate(), error_message, callback); @@ -261,7 +261,7 @@ void Cookies::Set(const base::DictionaryValue& options, GURL gurl(url); if (error_message.empty() && !gurl.is_valid()) { - error_message = "Url is not valid."; + error_message = "URL is not valid."; } if (!error_message.empty()) { @@ -322,14 +322,6 @@ void Cookies::OnSetCookies(const CookiesCallback& callback, 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(); } @@ -341,6 +333,15 @@ mate::Handle Cookies::Create( return mate::CreateHandle(isolate, new Cookies(browser_context)); } +// static +void Cookies::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .SetMethod("get", &Cookies::Get) + .SetMethod("remove", &Cookies::Remove) + .SetMethod("set", &Cookies::Set); +} + } // namespace api } // namespace atom diff --git a/atom/browser/api/atom_api_cookies.h b/atom/browser/api/atom_api_cookies.h index 0c309b3f18ee..5afa1bd23ca6 100644 --- a/atom/browser/api/atom_api_cookies.h +++ b/atom/browser/api/atom_api_cookies.h @@ -7,8 +7,8 @@ #include +#include "atom/browser/api/trackable_object.h" #include "base/callback.h" -#include "native_mate/wrappable.h" #include "native_mate/handle.h" #include "net/cookies/canonical_cookie.h" @@ -33,7 +33,7 @@ namespace atom { namespace api { -class Cookies : public mate::Wrappable { +class Cookies : public mate::TrackableObject { public: // node.js style callback function(error, result) typedef base::Callback, v8::Local)> @@ -42,6 +42,10 @@ class Cookies : public mate::Wrappable { static mate::Handle Create(v8::Isolate* isolate, content::BrowserContext* browser_context); + // mate::TrackableObject: + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + protected: explicit Cookies(content::BrowserContext* browser_context); ~Cookies(); @@ -70,10 +74,6 @@ class Cookies : public mate::Wrappable { 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(); diff --git a/atom/browser/api/atom_api_download_item.cc b/atom/browser/api/atom_api_download_item.cc index 6b3dfbf3f7db..f186821e7d31 100644 --- a/atom/browser/api/atom_api_download_item.cc +++ b/atom/browser/api/atom_api_download_item.cc @@ -6,6 +6,7 @@ #include +#include "atom/browser/atom_browser_main_parts.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" @@ -30,7 +31,7 @@ struct Converter { download_state = "cancelled"; break; case content::DownloadItem::INTERRUPTED: - download_state = "interrputed"; + download_state = "interrupted"; break; default: break; @@ -69,29 +70,20 @@ DownloadItem::DownloadItem(content::DownloadItem* download_item) : } DownloadItem::~DownloadItem() { - Destroy(); -} - -void DownloadItem::Destroy() { - if (download_item_) { - download_item_->RemoveObserver(this); - auto iter = g_download_item_objects.find(download_item_->GetId()); - if (iter != g_download_item_objects.end()) - g_download_item_objects.erase(iter); - download_item_ = nullptr; - } -} - -bool DownloadItem::IsDestroyed() const { - return download_item_ == nullptr; + if (download_item_) + OnDownloadDestroyed(download_item_); } void DownloadItem::OnDownloadUpdated(content::DownloadItem* item) { download_item_->IsDone() ? Emit("done", item->GetState()) : Emit("updated"); } -void DownloadItem::OnDownloadDestroyed(content::DownloadItem* download) { - Destroy(); +void DownloadItem::OnDownloadDestroyed(content::DownloadItem* download_item) { + download_item_->RemoveObserver(this); + auto iter = g_download_item_objects.find(download_item_->GetId()); + if (iter != g_download_item_objects.end()) + g_download_item_objects.erase(iter); + download_item_ = nullptr; } int64 DownloadItem::GetReceivedBytes() { @@ -102,7 +94,7 @@ int64 DownloadItem::GetTotalBytes() { return download_item_->GetTotalBytes(); } -const GURL& DownloadItem::GetUrl() { +const GURL& DownloadItem::GetURL() { return download_item_->GetURL(); } @@ -115,7 +107,7 @@ bool DownloadItem::HasUserGesture() { } std::string DownloadItem::GetFilename() { - return base::UTF16ToUTF8(net::GenerateFileName(GetUrl(), + return base::UTF16ToUTF8(net::GenerateFileName(GetURL(), GetContentDisposition(), std::string(), download_item_->GetSuggestedFilename(), @@ -143,15 +135,17 @@ void DownloadItem::Cancel() { download_item_->Cancel(true); } -mate::ObjectTemplateBuilder DownloadItem::GetObjectTemplateBuilder( - v8::Isolate* isolate) { - return mate::ObjectTemplateBuilder(isolate) +// static +void DownloadItem::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .MakeDestroyable() .SetMethod("pause", &DownloadItem::Pause) .SetMethod("resume", &DownloadItem::Resume) .SetMethod("cancel", &DownloadItem::Cancel) .SetMethod("getReceivedBytes", &DownloadItem::GetReceivedBytes) .SetMethod("getTotalBytes", &DownloadItem::GetTotalBytes) - .SetMethod("getUrl", &DownloadItem::GetUrl) + .SetMethod("getURL", &DownloadItem::GetURL) .SetMethod("getMimeType", &DownloadItem::GetMimeType) .SetMethod("hasUserGesture", &DownloadItem::HasUserGesture) .SetMethod("getFilename", &DownloadItem::GetFilename) @@ -159,14 +153,6 @@ mate::ObjectTemplateBuilder DownloadItem::GetObjectTemplateBuilder( .SetMethod("setSavePath", &DownloadItem::SetSavePath); } -void SetWrapDownloadItem(const WrapDownloadItemCallback& callback) { - g_wrap_download_item = callback; -} - -void ClearWrapDownloadItem() { - g_wrap_download_item.Reset(); -} - // static mate::Handle DownloadItem::Create( v8::Isolate* isolate, content::DownloadItem* item) { @@ -182,6 +168,18 @@ void* DownloadItem::UserDataKey() { return &kDownloadItemSavePathKey; } +void ClearWrapDownloadItem() { + g_wrap_download_item.Reset(); +} + +void SetWrapDownloadItem(const WrapDownloadItemCallback& callback) { + g_wrap_download_item = callback; + + // Cleanup the wrapper on exit. + atom::AtomBrowserMainParts::Get()->RegisterDestructionCallback( + base::Bind(ClearWrapDownloadItem)); +} + } // namespace api } // namespace atom @@ -193,7 +191,6 @@ void Initialize(v8::Local exports, v8::Local unused, v8::Isolate* isolate = context->GetIsolate(); mate::Dictionary dict(isolate, exports); dict.SetMethod("_setWrapDownloadItem", &atom::api::SetWrapDownloadItem); - dict.SetMethod("_clearWrapDownloadItem", &atom::api::ClearWrapDownloadItem); } } // namespace diff --git a/atom/browser/api/atom_api_download_item.h b/atom/browser/api/atom_api_download_item.h index 14074a4bed0d..471913c2266a 100644 --- a/atom/browser/api/atom_api_download_item.h +++ b/atom/browser/api/atom_api_download_item.h @@ -17,7 +17,7 @@ namespace atom { namespace api { -class DownloadItem : public mate::EventEmitter, +class DownloadItem : public mate::TrackableObject, public content::DownloadItem::Observer { public: class SavePathData : public base::SupportsUserData::Data { @@ -32,6 +32,10 @@ class DownloadItem : public mate::EventEmitter, content::DownloadItem* item); static void* UserDataKey(); + // mate::TrackableObject: + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + protected: explicit DownloadItem(content::DownloadItem* download_item); ~DownloadItem(); @@ -49,17 +53,10 @@ class DownloadItem : public mate::EventEmitter, bool HasUserGesture(); std::string GetFilename(); std::string GetContentDisposition(); - const GURL& GetUrl(); + const GURL& GetURL(); void SetSavePath(const base::FilePath& path); private: - // mate::Wrappable: - mate::ObjectTemplateBuilder GetObjectTemplateBuilder( - v8::Isolate* isolate) override; - bool IsDestroyed() const override; - - void Destroy(); - content::DownloadItem* download_item_; DISALLOW_COPY_AND_ASSIGN(DownloadItem); diff --git a/atom/browser/api/atom_api_global_shortcut.h b/atom/browser/api/atom_api_global_shortcut.h index 15cd3d4e0edb..d7057b000320 100644 --- a/atom/browser/api/atom_api_global_shortcut.h +++ b/atom/browser/api/atom_api_global_shortcut.h @@ -8,9 +8,9 @@ #include #include +#include "atom/browser/api/trackable_object.h" #include "base/callback.h" #include "chrome/browser/extensions/global_shortcut_listener.h" -#include "native_mate/wrappable.h" #include "native_mate/handle.h" #include "ui/base/accelerators/accelerator.h" @@ -19,13 +19,13 @@ namespace atom { namespace api { class GlobalShortcut : public extensions::GlobalShortcutListener::Observer, - public mate::Wrappable { + public mate::TrackableObject { public: static mate::Handle Create(v8::Isolate* isolate); protected: GlobalShortcut(); - virtual ~GlobalShortcut(); + ~GlobalShortcut() override; // mate::Wrappable implementations: mate::ObjectTemplateBuilder GetObjectTemplateBuilder( diff --git a/atom/browser/api/atom_api_menu.cc b/atom/browser/api/atom_api_menu.cc index 9bd724a9612e..96cba3fe9914 100644 --- a/atom/browser/api/atom_api_menu.cc +++ b/atom/browser/api/atom_api_menu.cc @@ -151,6 +151,7 @@ bool Menu::IsVisibleAt(int index) const { void Menu::BuildPrototype(v8::Isolate* isolate, v8::Local prototype) { mate::ObjectTemplateBuilder(isolate, prototype) + .MakeDestroyable() .SetMethod("insertItem", &Menu::InsertItemAt) .SetMethod("insertCheckItem", &Menu::InsertCheckItemAt) .SetMethod("insertRadioItem", &Menu::InsertRadioItemAt) diff --git a/atom/browser/api/atom_api_menu.h b/atom/browser/api/atom_api_menu.h index 0d93c0d46be6..17bb9073a719 100644 --- a/atom/browser/api/atom_api_menu.h +++ b/atom/browser/api/atom_api_menu.h @@ -8,16 +8,16 @@ #include #include "atom/browser/api/atom_api_window.h" +#include "atom/browser/api/trackable_object.h" #include "atom/browser/ui/atom_menu_model.h" #include "base/callback.h" #include "base/memory/scoped_ptr.h" -#include "native_mate/wrappable.h" namespace atom { namespace api { -class Menu : public mate::Wrappable, +class Menu : public mate::TrackableObject, public AtomMenuModel::Delegate { public: static mate::Wrappable* Create(); @@ -37,7 +37,7 @@ class Menu : public mate::Wrappable, protected: Menu(); - virtual ~Menu(); + ~Menu() override; // mate::Wrappable: void AfterInit(v8::Isolate* isolate) override; diff --git a/atom/browser/api/atom_api_power_monitor.h b/atom/browser/api/atom_api_power_monitor.h index dcf0906b437f..8fb52eeec95e 100644 --- a/atom/browser/api/atom_api_power_monitor.h +++ b/atom/browser/api/atom_api_power_monitor.h @@ -5,7 +5,7 @@ #ifndef ATOM_BROWSER_API_ATOM_API_POWER_MONITOR_H_ #define ATOM_BROWSER_API_ATOM_API_POWER_MONITOR_H_ -#include "atom/browser/api/event_emitter.h" +#include "atom/browser/api/trackable_object.h" #include "base/compiler_specific.h" #include "base/power_monitor/power_observer.h" #include "native_mate/handle.h" @@ -14,14 +14,14 @@ namespace atom { namespace api { -class PowerMonitor : public mate::EventEmitter, +class PowerMonitor : public mate::TrackableObject, public base::PowerObserver { public: static v8::Local Create(v8::Isolate* isolate); protected: PowerMonitor(); - virtual ~PowerMonitor(); + ~PowerMonitor() override; // base::PowerObserver implementations: void OnPowerStateChange(bool on_battery_power) override; diff --git a/atom/browser/api/atom_api_power_save_blocker.h b/atom/browser/api/atom_api_power_save_blocker.h index 9861f2b0f7cd..a698d746ceb0 100644 --- a/atom/browser/api/atom_api_power_save_blocker.h +++ b/atom/browser/api/atom_api_power_save_blocker.h @@ -7,10 +7,10 @@ #include +#include "atom/browser/api/trackable_object.h" #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; @@ -20,13 +20,13 @@ namespace atom { namespace api { -class PowerSaveBlocker : public mate::Wrappable { +class PowerSaveBlocker : public mate::TrackableObject { public: static mate::Handle Create(v8::Isolate* isolate); protected: PowerSaveBlocker(); - virtual ~PowerSaveBlocker(); + ~PowerSaveBlocker() override; // mate::Wrappable implementations: mate::ObjectTemplateBuilder GetObjectTemplateBuilder( @@ -48,7 +48,6 @@ class PowerSaveBlocker : public mate::Wrappable { std::map; PowerSaveBlockerTypeMap power_save_blocker_types_; - DISALLOW_COPY_AND_ASSIGN(PowerSaveBlocker); }; diff --git a/atom/browser/api/atom_api_protocol.cc b/atom/browser/api/atom_api_protocol.cc index 661ab1b5cbdd..b1ad7981377a 100644 --- a/atom/browser/api/atom_api_protocol.cc +++ b/atom/browser/api/atom_api_protocol.cc @@ -12,27 +12,12 @@ #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/native_mate_converters/net_converter.h" #include "atom/common/node_includes.h" #include "native_mate/dictionary.h" using content::BrowserThread; -namespace mate { - -template<> -struct Converter { - static v8::Local ToV8(v8::Isolate* isolate, - const net::URLRequest* val) { - return mate::ObjectTemplateBuilder(isolate) - .SetValue("method", val->method()) - .SetValue("url", val->url().spec()) - .SetValue("referrer", val->referrer()) - .Build()->NewInstance(); - } -}; - -} // namespace mate - namespace atom { namespace api { @@ -52,7 +37,7 @@ mate::ObjectTemplateBuilder Protocol::GetObjectTemplateBuilder( .SetMethod("registerBufferProtocol", &Protocol::RegisterProtocol) .SetMethod("registerFileProtocol", - &Protocol::RegisterProtocol) + &Protocol::RegisterProtocol) .SetMethod("registerHttpProtocol", &Protocol::RegisterProtocol) .SetMethod("unregisterProtocol", &Protocol::UnregisterProtocol) @@ -62,7 +47,7 @@ mate::ObjectTemplateBuilder Protocol::GetObjectTemplateBuilder( .SetMethod("interceptBufferProtocol", &Protocol::InterceptProtocol) .SetMethod("interceptFileProtocol", - &Protocol::InterceptProtocol) + &Protocol::InterceptProtocol) .SetMethod("interceptHttpProtocol", &Protocol::InterceptProtocol) .SetMethod("uninterceptProtocol", &Protocol::UninterceptProtocol); diff --git a/atom/browser/api/atom_api_screen.cc b/atom/browser/api/atom_api_screen.cc index b73bda9ced84..407a71f0cc41 100644 --- a/atom/browser/api/atom_api_screen.cc +++ b/atom/browser/api/atom_api_screen.cc @@ -41,7 +41,7 @@ std::vector MetricsToArray(uint32_t metrics) { if (metrics & gfx::DisplayObserver::DISPLAY_METRIC_DEVICE_SCALE_FACTOR) array.push_back("scaleFactor"); if (metrics & gfx::DisplayObserver::DISPLAY_METRIC_ROTATION) - array.push_back("rotaion"); + array.push_back("rotation"); return array; } diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index 7d5f75cac09c..9cec7378b8e0 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -9,16 +9,22 @@ #include "atom/browser/api/atom_api_cookies.h" #include "atom/browser/api/atom_api_download_item.h" -#include "atom/browser/atom_browser_context.h" #include "atom/browser/api/atom_api_web_contents.h" +#include "atom/browser/api/save_page_handler.h" +#include "atom/browser/atom_browser_context.h" +#include "atom/browser/atom_browser_main_parts.h" +#include "atom/browser/net/atom_cert_verifier.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/native_mate_converters/net_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 "brightray/browser/net/devtools_network_conditions.h" +#include "brightray/browser/net/devtools_network_controller.h" #include "chrome/common/pref_names.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/storage_partition.h" @@ -45,7 +51,7 @@ struct ClearStorageDataOptions { uint32 GetStorageMask(const std::vector& storage_types) { uint32 storage_mask = 0; for (const auto& it : storage_types) { - auto type = base::StringToLowerASCII(it); + auto type = base::ToLowerASCII(it); if (type == "appcache") storage_mask |= StoragePartition::REMOVE_DATA_MASK_APPCACHE; else if (type == "cookies") @@ -69,7 +75,7 @@ uint32 GetStorageMask(const std::vector& storage_types) { uint32 GetQuotaMask(const std::vector& quota_types) { uint32 quota_mask = 0; for (const auto& it : quota_types) { - auto type = base::StringToLowerASCII(it); + auto type = base::ToLowerASCII(it); if (type == "temporary") quota_mask |= StoragePartition::QUOTA_MANAGED_STORAGE_MASK_TEMPORARY; else if (type == "persistent") @@ -102,6 +108,24 @@ struct Converter { } }; +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + net::ProxyConfig* out) { + std::string proxy; + if (!ConvertFromV8(isolate, val, &proxy)) + return false; + auto pac_url = GURL(proxy); + if (pac_url.is_valid()) { + out->set_pac_url(pac_url); + } else { + out->proxy_rules().ParseFromString(proxy); + } + return true; + } +}; + } // namespace mate namespace atom { @@ -206,12 +230,13 @@ void ClearHttpCacheInIO( } void SetProxyInIO(net::URLRequestContextGetter* getter, - const std::string& proxy, + const net::ProxyConfig& config, 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)); + proxy_service->ResetConfigService(make_scoped_ptr( + new net::ProxyConfigServiceFixed(config))); + // Refetches and applies the new pac script if provided. + proxy_service->ForceReloadProxyConfig(); RunCallbackInUI(callback); } @@ -229,12 +254,13 @@ Session::Session(AtomBrowserContext* browser_context) Session::~Session() { content::BrowserContext::GetDownloadManager(browser_context())-> RemoveObserver(this); - Destroy(); } void Session::OnDownloadCreated(content::DownloadManager* manager, content::DownloadItem* item) { auto web_contents = item->GetWebContents(); + if (SavePageHandler::IsSavePageTypes(item->GetMimeType())) + return; bool prevent_default = Emit( "will-download", DownloadItem::Create(isolate(), item), @@ -245,14 +271,6 @@ void Session::OnDownloadCreated(content::DownloadManager* manager, } } -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); } @@ -282,11 +300,11 @@ void Session::ClearStorageData(mate::Arguments* args) { base::Time(), base::Time::Max(), callback); } -void Session::SetProxy(const std::string& proxy, +void Session::SetProxy(const net::ProxyConfig& config, const base::Closure& callback) { auto getter = browser_context_->GetRequestContext(); BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::Bind(&SetProxyInIO, base::Unretained(getter), proxy, callback)); + base::Bind(&SetProxyInIO, base::Unretained(getter), config, callback)); } void Session::SetDownloadPath(const base::FilePath& path) { @@ -294,6 +312,54 @@ void Session::SetDownloadPath(const base::FilePath& path) { prefs::kDownloadDefaultDirectory, path); } +void Session::EnableNetworkEmulation(const mate::Dictionary& options) { + scoped_ptr conditions; + bool offline = false; + double latency, download_throughput, upload_throughput; + if (options.Get("offline", &offline) && offline) { + conditions.reset(new brightray::DevToolsNetworkConditions(offline)); + } else { + options.Get("latency", &latency); + options.Get("downloadThroughput", &download_throughput); + options.Get("uploadThroughput", &upload_throughput); + conditions.reset( + new brightray::DevToolsNetworkConditions(false, + latency, + download_throughput, + upload_throughput)); + } + auto controller = browser_context_->GetDevToolsNetworkController(); + + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&brightray::DevToolsNetworkController::SetNetworkState, + base::Unretained(controller), + std::string(), + base::Passed(&conditions))); +} + +void Session::DisableNetworkEmulation() { + scoped_ptr conditions( + new brightray::DevToolsNetworkConditions(false)); + auto controller = browser_context_->GetDevToolsNetworkController(); + + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&brightray::DevToolsNetworkController::SetNetworkState, + base::Unretained(controller), + std::string(), + base::Passed(&conditions))); +} + +void Session::SetCertVerifyProc(v8::Local val, + mate::Arguments* args) { + AtomCertVerifier::VerifyProc proc; + if (!(val->IsNull() || mate::ConvertFromV8(args->isolate(), val, &proc))) { + args->ThrowError("Must pass null or function"); + return; + } + + browser_context_->cert_verifier()->SetVerifyProc(proc); +} + v8::Local Session::Cookies(v8::Isolate* isolate) { if (cookies_.IsEmpty()) { auto handle = atom::api::Cookies::Create(isolate, browser_context()); @@ -302,17 +368,6 @@ v8::Local Session::Cookies(v8::Isolate* isolate) { 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) { @@ -333,14 +388,34 @@ mate::Handle Session::FromPartition( static_cast(browser_context.get())); } -void SetWrapSession(const WrapSessionCallback& callback) { - g_wrap_session = callback; +// static +void Session::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .MakeDestroyable() + .SetMethod("resolveProxy", &Session::ResolveProxy) + .SetMethod("clearCache", &Session::ClearCache) + .SetMethod("clearStorageData", &Session::ClearStorageData) + .SetMethod("setProxy", &Session::SetProxy) + .SetMethod("setDownloadPath", &Session::SetDownloadPath) + .SetMethod("enableNetworkEmulation", &Session::EnableNetworkEmulation) + .SetMethod("disableNetworkEmulation", &Session::DisableNetworkEmulation) + .SetMethod("setCertificateVerifyProc", &Session::SetCertVerifyProc) + .SetProperty("cookies", &Session::Cookies); } void ClearWrapSession() { g_wrap_session.Reset(); } +void SetWrapSession(const WrapSessionCallback& callback) { + g_wrap_session = callback; + + // Cleanup the wrapper on exit. + atom::AtomBrowserMainParts::Get()->RegisterDestructionCallback( + base::Bind(ClearWrapSession)); +} + } // namespace api } // namespace atom @@ -353,7 +428,6 @@ void Initialize(v8::Local exports, v8::Local unused, 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 diff --git a/atom/browser/api/atom_api_session.h b/atom/browser/api/atom_api_session.h index 14406e57af5b..e800a992d4b7 100644 --- a/atom/browser/api/atom_api_session.h +++ b/atom/browser/api/atom_api_session.h @@ -20,6 +20,11 @@ class FilePath; namespace mate { class Arguments; +class Dictionary; +} + +namespace net { +class ProxyConfig; } namespace atom { @@ -43,6 +48,10 @@ class Session: public mate::TrackableObject, AtomBrowserContext* browser_context() const { return browser_context_.get(); } + // mate::TrackableObject: + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + protected: explicit Session(AtomBrowserContext* browser_context); ~Session(); @@ -51,20 +60,15 @@ class Session: public mate::TrackableObject, 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 SetProxy(const net::ProxyConfig& config, const base::Closure& callback); void SetDownloadPath(const base::FilePath& path); + void EnableNetworkEmulation(const mate::Dictionary& options); + void DisableNetworkEmulation(); + void SetCertVerifyProc(v8::Local proc, mate::Arguments* args); v8::Local Cookies(v8::Isolate* isolate); // Cached object for cookies API. diff --git a/atom/browser/api/atom_api_tray.cc b/atom/browser/api/atom_api_tray.cc index 0f5829e19c14..5f351f485049 100644 --- a/atom/browser/api/atom_api_tray.cc +++ b/atom/browser/api/atom_api_tray.cc @@ -44,21 +44,21 @@ mate::Wrappable* Tray::New(v8::Isolate* isolate, const gfx::Image& image) { void Tray::OnClicked(const gfx::Rect& bounds, int modifiers) { v8::Locker locker(isolate()); v8::HandleScope handle_scope(isolate()); - EmitCustomEvent("clicked", + EmitCustomEvent("click", ModifiersToObject(isolate(), modifiers), bounds); } void Tray::OnDoubleClicked(const gfx::Rect& bounds, int modifiers) { v8::Locker locker(isolate()); v8::HandleScope handle_scope(isolate()); - EmitCustomEvent("double-clicked", + EmitCustomEvent("double-click", 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", + EmitCustomEvent("right-click", ModifiersToObject(isolate(), modifiers), bounds); } @@ -67,23 +67,31 @@ void Tray::OnBalloonShow() { } void Tray::OnBalloonClicked() { - Emit("balloon-clicked"); + Emit("balloon-click"); } void Tray::OnBalloonClosed() { Emit("balloon-closed"); } +void Tray::OnDrop() { + Emit("drop"); +} + void Tray::OnDropFiles(const std::vector& files) { Emit("drop-files", files); } -bool Tray::IsDestroyed() const { - return !tray_icon_; +void Tray::OnDragEntered() { + Emit("drag-enter"); } -void Tray::Destroy() { - tray_icon_.reset(); +void Tray::OnDragExited() { + Emit("drag-leave"); +} + +void Tray::OnDragEnded() { + Emit("drag-end"); } void Tray::SetImage(mate::Arguments* args, const gfx::Image& image) { @@ -121,9 +129,11 @@ void Tray::DisplayBalloon(mate::Arguments* args, } void Tray::PopUpContextMenu(mate::Arguments* args) { + mate::Handle menu; + args->GetNext(&menu); gfx::Point pos; args->GetNext(&pos); - tray_icon_->PopUpContextMenu(pos); + tray_icon_->PopUpContextMenu(pos, menu.IsEmpty() ? nullptr : menu->model()); } void Tray::SetContextMenu(mate::Arguments* args, Menu* menu) { @@ -144,7 +154,7 @@ v8::Local Tray::ModifiersToObject(v8::Isolate* isolate, void Tray::BuildPrototype(v8::Isolate* isolate, v8::Local prototype) { mate::ObjectTemplateBuilder(isolate, prototype) - .SetMethod("destroy", &Tray::Destroy, true) + .MakeDestroyable() .SetMethod("setImage", &Tray::SetImage) .SetMethod("setPressedImage", &Tray::SetPressedImage) .SetMethod("setToolTip", &Tray::SetToolTip) diff --git a/atom/browser/api/atom_api_tray.h b/atom/browser/api/atom_api_tray.h index dc9302597cf3..0e0d153ad0d3 100644 --- a/atom/browser/api/atom_api_tray.h +++ b/atom/browser/api/atom_api_tray.h @@ -8,7 +8,7 @@ #include #include -#include "atom/browser/api/event_emitter.h" +#include "atom/browser/api/trackable_object.h" #include "atom/browser/ui/tray_icon_observer.h" #include "base/memory/scoped_ptr.h" @@ -29,7 +29,7 @@ namespace api { class Menu; -class Tray : public mate::EventEmitter, +class Tray : public mate::TrackableObject, public TrayIconObserver { public: static mate::Wrappable* New(v8::Isolate* isolate, const gfx::Image& image); @@ -39,7 +39,7 @@ class Tray : public mate::EventEmitter, protected: explicit Tray(const gfx::Image& image); - virtual ~Tray(); + ~Tray() override; // TrayIconObserver: void OnClicked(const gfx::Rect& bounds, int modifiers) override; @@ -48,12 +48,12 @@ class Tray : public mate::EventEmitter, void OnBalloonShow() override; void OnBalloonClicked() override; void OnBalloonClosed() override; + void OnDrop() override; void OnDropFiles(const std::vector& files) override; + void OnDragEntered() override; + void OnDragExited() override; + void OnDragEnded() override; - // mate::Wrappable: - bool IsDestroyed() const override; - - void Destroy(); void SetImage(mate::Arguments* args, const gfx::Image& image); void SetPressedImage(mate::Arguments* args, const gfx::Image& image); void SetToolTip(mate::Arguments* args, const std::string& tool_tip); diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index bdeb4ed4f873..a70b6cf4e0b2 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -7,6 +7,7 @@ #include #include "atom/browser/api/atom_api_session.h" +#include "atom/browser/api/atom_api_window.h" #include "atom/browser/atom_browser_client.h" #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" @@ -17,6 +18,7 @@ #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/content_converter.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" @@ -26,9 +28,11 @@ #include "base/strings/string_util.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/printing/print_preview_message_handler.h" #include "content/common/view_messages.h" +#include "content/public/browser/browser_plugin_guest_manager.h" #include "content/public/browser/favicon_status.h" #include "content/public/browser/native_web_keyboard_event.h" #include "content/public/browser/navigation_details.h" @@ -43,12 +47,14 @@ #include "content/public/browser/storage_partition.h" #include "content/public/browser/site_instance.h" #include "content/public/browser/web_contents.h" +#include "content/public/common/context_menu_params.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 "ui/base/l10n/l10n_util.h" #include "atom/common/node_includes.h" @@ -60,9 +66,21 @@ struct PrintSettings { }; void SetUserAgentInIO(scoped_refptr getter, + std::string accept_lang, std::string user_agent) { getter->GetURLRequestContext()->set_http_user_agent_settings( - new net::StaticHttpUserAgentSettings("en-us,en", user_agent)); + new net::StaticHttpUserAgentSettings( + net::HttpUtil::GenerateAcceptLanguageHeader(accept_lang), + user_agent)); +} + +bool NotifyZoomLevelChanged( + double level, content::WebContents* guest_web_contents) { + guest_web_contents->SendToAllFrames( + new AtomViewMsg_SetZoomLevel(MSG_ROUTING_NONE, level)); + + // Return false to iterate over all guests. + return false; } } // namespace @@ -130,8 +148,7 @@ struct Converter { std::string key; std::string value; while (headers->EnumerateHeaderLines(&iter, &key, &value)) { - key = base::StringToLowerASCII(key); - value = base::StringToLowerASCII(value); + key = base::ToLowerASCII(key); if (response_headers.HasKey(key)) { base::ListValue* values = nullptr; if (response_headers.GetList(key, &values)) @@ -147,6 +164,27 @@ struct Converter { } }; +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Local val, + content::SavePageType* out) { + std::string save_type; + if (!ConvertFromV8(isolate, val, &save_type)) + return false; + save_type = base::ToLowerASCII(save_type); + if (save_type == "htmlonly") { + *out = content::SAVE_PAGE_TYPE_AS_ONLY_HTML; + } else if (save_type == "htmlcomplete") { + *out = content::SAVE_PAGE_TYPE_AS_COMPLETE_HTML; + } else if (save_type == "mhtml") { + *out = content::SAVE_PAGE_TYPE_AS_MHTML; + } else { + return false; + } + return true; + } +}; + } // namespace mate @@ -156,8 +194,6 @@ namespace api { namespace { -v8::Persistent template_; - // The wrapWebContents function which is implemented in JavaScript using WrapWebContentsCallback = base::Callback)>; WrapWebContentsCallback g_wrap_web_contents; @@ -228,10 +264,10 @@ WebContents::WebContents(v8::Isolate* isolate, AttachAsUserData(web_contents); InitWithWebContents(web_contents); + managed_web_contents()->GetView()->SetDelegate(this); + // Save the preferences in C++. - base::DictionaryValue web_preferences; - mate::ConvertFromV8(isolate, options.GetHandle(), &web_preferences); - new WebContentsPreferences(web_contents, &web_preferences); + new WebContentsPreferences(web_contents, options); web_contents->SetUserAgentOverride(GetBrowserContext()->GetUserAgent()); @@ -252,7 +288,15 @@ WebContents::WebContents(v8::Isolate* isolate, } WebContents::~WebContents() { - Destroy(); + if (type_ == WEB_VIEW && managed_web_contents()) { + // When force destroying the "destroyed" event is not emitted. + WebContentsDestroyed(); + + guest_delegate_->Destroy(); + + Observe(nullptr); + DestroyWebContents(); + } } bool WebContents::AddMessageToConsole(content::WebContents* source, @@ -367,6 +411,15 @@ void WebContents::RendererResponsive(content::WebContents* source) { owner_window()->RendererResponsive(source); } +bool WebContents::HandleContextMenu(const content::ContextMenuParams& params) { + if (!params.custom_context.is_pepper_menu) + return false; + + Emit("pepper-context-menu", std::make_pair(params, web_contents())); + web_contents()->NotifyContextMenuClosed(params.custom_context); + return true; +} + 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. @@ -413,14 +466,13 @@ void WebContents::DidFinishLoad(content::RenderFrameHost* render_frame_host, Emit("did-finish-load"); } -// this error occurs when host could not be found void WebContents::DidFailProvisionalLoad( content::RenderFrameHost* render_frame_host, - const GURL& validated_url, + const GURL& url, int error_code, const base::string16& error_description, bool was_ignored_by_handler) { - Emit("did-fail-load", error_code, error_description, validated_url); + Emit("did-fail-provisional-load", error_code, error_description, url); } void WebContents::DidFailLoad(content::RenderFrameHost* render_frame_host, @@ -473,9 +525,10 @@ void WebContents::DidNavigateMainFrame( void WebContents::TitleWasSet(content::NavigationEntry* entry, bool explicit_set) { - // Back/Forward navigation may have pruned entries. if (entry) - Emit("page-title-set", entry->GetTitle(), explicit_set); + Emit("-page-title-updated", entry->GetTitle(), explicit_set); + else + Emit("-page-title-updated", "", explicit_set); } void WebContents::DidUpdateFaviconURL( @@ -491,12 +544,40 @@ void WebContents::DidUpdateFaviconURL( Emit("page-favicon-updated", unique_urls); } +void WebContents::DevToolsFocused() { + Emit("devtools-focused"); +} + +void WebContents::DevToolsOpened() { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + auto handle = WebContents::CreateFrom( + isolate(), managed_web_contents()->GetDevToolsWebContents()); + devtools_web_contents_.Reset(isolate(), handle.ToV8()); + + // Inherit owner window in devtools. + if (owner_window()) + handle->SetOwnerWindow(managed_web_contents()->GetDevToolsWebContents(), + owner_window()); + + Emit("devtools-opened"); +} + +void WebContents::DevToolsClosed() { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + devtools_web_contents_.Reset(); + + Emit("devtools-closed"); +} + bool WebContents::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(WebContents, message) IPC_MESSAGE_HANDLER(AtomViewHostMsg_Message, OnRendererMessage) IPC_MESSAGE_HANDLER_DELAY_REPLY(AtomViewHostMsg_Message_Sync, OnRendererMessageSync) + IPC_MESSAGE_HANDLER(AtomViewHostMsg_ZoomLevelChanged, OnZoomLevelChanged) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() @@ -516,23 +597,6 @@ void WebContents::NavigationEntryCommitted( details.is_in_page, details.did_replace_entry); } -void WebContents::Destroy() { - session_.Reset(); - if (type_ == WEB_VIEW && managed_web_contents()) { - // When force destroying the "destroyed" event is not emitted. - WebContentsDestroyed(); - - guest_delegate_->Destroy(); - - Observe(nullptr); - DestroyWebContents(); - } -} - -bool WebContents::IsAlive() const { - return web_contents() != NULL; -} - int WebContents::GetID() const { return web_contents()->GetRenderProcessHost()->GetID(); } @@ -553,12 +617,25 @@ void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) { if (options.Get("userAgent", &user_agent)) SetUserAgent(user_agent); + std::string extra_headers; + if (options.Get("extraHeaders", &extra_headers)) + params.extra_headers = extra_headers; + 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); } +void WebContents::DownloadURL(const GURL& url) { + auto browser_context = web_contents()->GetBrowserContext(); + auto download_manager = + content::BrowserContext::GetDownloadManager(browser_context); + + download_manager->DownloadUrl( + content::DownloadUrlParameters::FromWebContents(web_contents(), url)); +} + GURL WebContents::GetURL() const { return web_contents()->GetURL(); } @@ -579,10 +656,6 @@ void WebContents::Stop() { web_contents()->Stop(); } -void WebContents::ReloadIgnoringCache() { - web_contents()->GetController().ReloadIgnoringCache(false); -} - void WebContents::GoBack() { atom::AtomBrowserClient::SuppressRendererProcessRestartForOnce(); web_contents()->GetController().GoBack(); @@ -606,8 +679,10 @@ void WebContents::SetUserAgent(const std::string& user_agent) { web_contents()->SetUserAgentOverride(user_agent); scoped_refptr getter = web_contents()->GetBrowserContext()->GetRequestContext(); + + auto accept_lang = l10n_util::GetApplicationLocale(""); getter->GetNetworkTaskRunner()->PostTask(FROM_HERE, - base::Bind(&SetUserAgentInIO, getter, user_agent)); + base::Bind(&SetUserAgentInIO, getter, accept_lang, user_agent)); } std::string WebContents::GetUserAgent() { @@ -618,6 +693,13 @@ void WebContents::InsertCSS(const std::string& css) { web_contents()->InsertCSS(css); } +bool WebContents::SavePage(const base::FilePath& full_file_path, + const content::SavePageType& save_type, + const SavePageHandler::SavePageCallback& callback) { + auto handler = new SavePageHandler(web_contents(), callback); + return handler->Handle(full_file_path, save_type); +} + void WebContents::ExecuteJavaScript(const base::string16& code, bool has_user_gesture) { Send(new AtomViewMsg_ExecuteJavaScript(routing_id(), code, has_user_gesture)); @@ -698,10 +780,6 @@ void WebContents::InspectServiceWorker() { } } -v8::Local WebContents::Session(v8::Isolate* isolate) { - return v8::Local::New(isolate, session_); -} - void WebContents::HasServiceWorker( const base::Callback& callback) { auto context = GetServiceWorkerContext(web_contents()); @@ -893,79 +971,91 @@ v8::Local WebContents::GetWebPreferences(v8::Isolate* isolate) { return mate::ConvertToV8(isolate, *web_preferences->web_preferences()); } -mate::ObjectTemplateBuilder WebContents::GetObjectTemplateBuilder( - v8::Isolate* isolate) { - if (template_.IsEmpty()) - template_.Reset(isolate, mate::ObjectTemplateBuilder(isolate) - .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) - .SetMethod("_stop", &WebContents::Stop) - .SetMethod("_reloadIgnoringCache", &WebContents::ReloadIgnoringCache) - .SetMethod("_goBack", &WebContents::GoBack) - .SetMethod("_goForward", &WebContents::GoForward) - .SetMethod("_goToOffset", &WebContents::GoToOffset) - .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("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::IsGuest) - .SetMethod("getWebPreferences", &WebContents::GetWebPreferences) - .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_)); +v8::Local WebContents::GetOwnerBrowserWindow() { + if (owner_window()) + return Window::From(isolate(), owner_window()); + else + return v8::Null(isolate()); } -bool WebContents::IsDestroyed() const { - return !IsAlive(); +v8::Local WebContents::Session(v8::Isolate* isolate) { + return v8::Local::New(isolate, session_); +} + +v8::Local WebContents::DevToolsWebContents(v8::Isolate* isolate) { + if (devtools_web_contents_.IsEmpty()) + return v8::Null(isolate); + else + return v8::Local::New(isolate, devtools_web_contents_); +} + +// static +void WebContents::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .MakeDestroyable() + .SetMethod("getId", &WebContents::GetID) + .SetMethod("equal", &WebContents::Equal) + .SetMethod("_loadURL", &WebContents::LoadURL) + .SetMethod("downloadURL", &WebContents::DownloadURL) + .SetMethod("_getURL", &WebContents::GetURL) + .SetMethod("getTitle", &WebContents::GetTitle) + .SetMethod("isLoading", &WebContents::IsLoading) + .SetMethod("isWaitingForResponse", &WebContents::IsWaitingForResponse) + .SetMethod("_stop", &WebContents::Stop) + .SetMethod("_goBack", &WebContents::GoBack) + .SetMethod("_goForward", &WebContents::GoForward) + .SetMethod("_goToOffset", &WebContents::GoToOffset) + .SetMethod("isCrashed", &WebContents::IsCrashed) + .SetMethod("setUserAgent", &WebContents::SetUserAgent) + .SetMethod("getUserAgent", &WebContents::GetUserAgent) + .SetMethod("insertCSS", &WebContents::InsertCSS) + .SetMethod("savePage", &WebContents::SavePage) + .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("focus", &WebContents::Focus) + .SetMethod("tabTraverse", &WebContents::TabTraverse) + .SetMethod("_send", &WebContents::SendIPCMessage) + .SetMethod("sendInputEvent", &WebContents::SendInputEvent) + .SetMethod("beginFrameSubscription", + &WebContents::BeginFrameSubscription) + .SetMethod("endFrameSubscription", &WebContents::EndFrameSubscription) + .SetMethod("setSize", &WebContents::SetSize) + .SetMethod("setAllowTransparency", &WebContents::SetAllowTransparency) + .SetMethod("isGuest", &WebContents::IsGuest) + .SetMethod("getWebPreferences", &WebContents::GetWebPreferences) + .SetMethod("getOwnerBrowserWindow", &WebContents::GetOwnerBrowserWindow) + .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) + .SetProperty("devToolsWebContents", &WebContents::DevToolsWebContents); } AtomBrowserContext* WebContents::GetBrowserContext() const { @@ -985,6 +1075,15 @@ void WebContents::OnRendererMessageSync(const base::string16& channel, EmitWithSender(base::UTF16ToUTF8(channel), web_contents(), message, args); } +void WebContents::OnZoomLevelChanged(double level) { + auto manager = web_contents()->GetBrowserContext()->GetGuestManager(); + if (!manager) + return; + manager->ForEachGuest(web_contents(), + base::Bind(&NotifyZoomLevelChanged, + level)); +} + // static mate::Handle WebContents::CreateFrom( v8::Isolate* isolate, content::WebContents* web_contents) { @@ -1007,14 +1106,18 @@ mate::Handle WebContents::Create( return handle; } -void SetWrapWebContents(const WrapWebContentsCallback& callback) { - g_wrap_web_contents = callback; -} - void ClearWrapWebContents() { g_wrap_web_contents.Reset(); } +void SetWrapWebContents(const WrapWebContentsCallback& callback) { + g_wrap_web_contents = callback; + + // Cleanup the wrapper on exit. + atom::AtomBrowserMainParts::Get()->RegisterDestructionCallback( + base::Bind(ClearWrapWebContents)); +} + } // namespace api } // namespace atom @@ -1028,7 +1131,6 @@ void Initialize(v8::Local exports, v8::Local unused, 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 91750ac6136c..fb8892f105b9 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -9,6 +9,7 @@ #include #include "atom/browser/api/frame_subscriber.h" +#include "atom/browser/api/save_page_handler.h" #include "atom/browser/api/trackable_object.h" #include "atom/browser/common_web_contents_delegate.h" #include "content/public/browser/web_contents_observer.h" @@ -53,13 +54,10 @@ class WebContents : public mate::TrackableObject, static mate::Handle Create( v8::Isolate* isolate, const mate::Dictionary& options); - // 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); + void DownloadURL(const GURL& url); GURL GetURL() const; base::string16 GetTitle() const; bool IsLoading() const; @@ -73,6 +71,9 @@ class WebContents : public mate::TrackableObject, void SetUserAgent(const std::string& user_agent); std::string GetUserAgent(); void InsertCSS(const std::string& css); + bool SavePage(const base::FilePath& full_file_path, + const content::SavePageType& save_type, + const SavePageHandler::SavePageCallback& callback); void ExecuteJavaScript(const base::string16& code, bool has_user_gesture); void OpenDevTools(mate::Arguments* args); @@ -83,7 +84,6 @@ class WebContents : public mate::TrackableObject, 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); @@ -135,16 +135,22 @@ class WebContents : public mate::TrackableObject, // Returns the web preferences of current WebContents. v8::Local GetWebPreferences(v8::Isolate* isolate); + // Returns the owner window. + v8::Local GetOwnerBrowserWindow(); + + // Properties. + v8::Local Session(v8::Isolate* isolate); + v8::Local DevToolsWebContents(v8::Isolate* isolate); + + // mate::TrackableObject: + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + protected: explicit WebContents(content::WebContents* web_contents); 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, int32 level, @@ -179,6 +185,7 @@ class WebContents : public mate::TrackableObject, void ExitFullscreenModeForTab(content::WebContents* source) override; void RendererUnresponsive(content::WebContents* source) override; void RendererResponsive(content::WebContents* source) override; + bool HandleContextMenu(const content::ContextMenuParams& params) override; // content::WebContentsObserver: void BeforeUnloadFired(const base::TimeTicks& proceed_time) override; @@ -218,6 +225,11 @@ class WebContents : public mate::TrackableObject, void PluginCrashed(const base::FilePath& plugin_path, base::ProcessId plugin_pid) override; + // brightray::InspectableWebContentsViewDelegate: + void DevToolsFocused() override; + void DevToolsOpened() override; + void DevToolsClosed() override; + private: enum Type { BROWSER_WINDOW, // Used by BrowserWindow. @@ -236,7 +248,12 @@ class WebContents : public mate::TrackableObject, const base::ListValue& args, IPC::Message* message); + // Called when guests need to be notified of + // embedders' zoom level change. + void OnZoomLevelChanged(double level); + v8::Global session_; + v8::Global devtools_web_contents_; scoped_ptr guest_delegate_; diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 4d866d18503c..84e5c53ebe65 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "atom/browser/api/atom_api_window.h" +#include "atom/common/native_mate_converters/value_converter.h" #include "atom/browser/api/atom_api_menu.h" #include "atom/browser/api/atom_api_web_contents.h" @@ -60,22 +61,82 @@ void OnCapturePageDone( callback.Run(gfx::Image::CreateFrom1xBitmap(bitmap)); } +// Converts min-width to minWidth, returns false if no conversion is needed. +bool TranslateOldKey(const std::string& key, std::string* new_key) { + if (key.find('-') == std::string::npos) + return false; + new_key->reserve(key.size()); + bool next_upper_case = false; + for (char c : key) { + if (c == '-') { + next_upper_case = true; + } else if (next_upper_case) { + new_key->push_back(base::ToUpperASCII(c)); + next_upper_case = false; + } else { + new_key->push_back(c); + } + } + return true; +} + +// Converts min-width to minWidth recursively in the dictionary. +void TranslateOldOptions(v8::Isolate* isolate, v8::Local options) { + auto context = isolate->GetCurrentContext(); + auto maybe_keys = options->GetOwnPropertyNames(context); + if (maybe_keys.IsEmpty()) + return; + std::vector keys; + if (!mate::ConvertFromV8(isolate, maybe_keys.ToLocalChecked(), &keys)) + return; + mate::Dictionary dict(isolate, options); + for (const auto& key : keys) { + v8::Local value; + if (!dict.Get(key, &value)) // Shouldn't happen, but guard it anyway. + continue; + // Go recursively. + v8::Local sub_options; + if (mate::ConvertFromV8(isolate, value, &sub_options)) + TranslateOldOptions(isolate, sub_options); + // Translate key. + std::string new_key; + if (TranslateOldKey(key, &new_key)) { + dict.Set(new_key, value); + dict.Delete(key); + } + } +} + +#if defined(OS_WIN) +// Converts binary data to Buffer. +v8::Local ToBuffer(v8::Isolate* isolate, void* val, int size) { + auto buffer = node::Buffer::New(isolate, static_cast(val), size); + if (buffer.IsEmpty()) + return v8::Null(isolate); + else + return buffer.ToLocalChecked(); +} +#endif + } // namespace Window::Window(v8::Isolate* isolate, const mate::Dictionary& options) { - // Use options['web-preferences'] to create WebContents. + // Be compatible with old style field names like min-width. + TranslateOldOptions(isolate, options.GetHandle()); + + // Use options.webPreferences to create WebContents. mate::Dictionary web_preferences = mate::Dictionary::CreateEmpty(isolate); - options.Get(switches::kWebPreferences, &web_preferences); + options.Get(options::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); + if (options.Get(options::kNodeIntegration, &value)) + web_preferences.Set(options::kNodeIntegration, value); + if (options.Get(options::kPreloadScript, &value)) + web_preferences.Set(options::kPreloadScript, value); + if (options.Get(options::kZoomFactor, &value)) + web_preferences.Set(options::kZoomFactor, value); // Creates the WebContents used by BrowserWindow. auto web_contents = WebContents::Create(isolate, web_preferences); @@ -92,16 +153,16 @@ Window::Window(v8::Isolate* isolate, const mate::Dictionary& options) { web_contents->SetOwnerWindow(window_.get()); window_->InitFromOptions(options); window_->AddObserver(this); + AttachAsUserData(window_.get()); } Window::~Window() { - if (window_) - Destroy(); -} + if (!window_->IsClosed()) + window_->CloseContents(nullptr); -void Window::OnPageTitleUpdated(bool* prevent_default, - const std::string& title) { - *prevent_default = Emit("page-title-updated", title); + // Destroy the native window in next tick because the native code might be + // iterating all windows. + base::MessageLoop::current()->DeleteSoon(FROM_HERE, window_.release()); } void Window::WillCloseWindow(bool* prevent_default) { @@ -109,19 +170,19 @@ void Window::WillCloseWindow(bool* prevent_default) { } void Window::OnWindowClosed() { - if (api_web_contents_) { - api_web_contents_->DestroyWebContents(); - api_web_contents_ = nullptr; - web_contents_.Reset(); - } + api_web_contents_->DestroyWebContents(); RemoveFromWeakMap(); window_->RemoveObserver(this); + // We can not call Destroy here because we need to call Emit first, but we + // also do not want any method to be used, so just mark as destroyed here. + MarkDestroyed(); + Emit("closed"); - // Clean up the resources after window has been closed. - base::MessageLoop::current()->DeleteSoon(FROM_HERE, window_.release()); + // Destroy the native class when window is closed. + base::MessageLoop::current()->PostTask(FROM_HERE, GetDestroyClosure()); } void Window::OnWindowBlur() { @@ -184,60 +245,45 @@ void Window::OnRendererResponsive() { Emit("responsive"); } -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); } +#if defined(OS_WIN) +void Window::OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) { + if (IsWindowMessageHooked(message)) { + messages_callback_map_[message].Run( + ToBuffer(isolate(), static_cast(&w_param), sizeof(WPARAM)), + ToBuffer(isolate(), static_cast(&l_param), sizeof(LPARAM))); + } +} +#endif + // static -mate::Wrappable* Window::New(v8::Isolate* isolate, - const mate::Dictionary& options) { +mate::Wrappable* Window::New(v8::Isolate* isolate, mate::Arguments* args) { if (!Browser::Get()->is_ready()) { isolate->ThrowException(v8::Exception::Error(mate::StringToV8( isolate, "Cannot create BrowserWindow before app is ready"))); return nullptr; } + + if (args->Length() > 1) { + args->ThrowError(); + return nullptr; + } + + mate::Dictionary options; + if (!(args->Length() == 1 && args->GetNext(&options))) { + options = mate::Dictionary::CreateEmpty(isolate); + } + return new Window(isolate, options); } -bool Window::IsDestroyed() const { - return !window_ || window_->IsClosed(); -} - -void Window::Destroy() { - if (window_) - window_->CloseContents(nullptr); -} - void Window::Close() { window_->Close(); } -bool Window::IsClosed() { - return window_->IsClosed(); -} - void Window::Focus() { window_->Focus(true); } @@ -406,6 +452,10 @@ bool Window::IsKiosk() { return window_->IsKiosk(); } +void Window::SetBackgroundColor(const std::string& color_name) { + window_->SetBackgroundColor(color_name); +} + void Window::FocusOnWebView() { window_->FocusOnWebView(); } @@ -509,6 +559,29 @@ bool Window::IsMenuBarVisible() { return window_->IsMenuBarVisible(); } +#if defined(OS_WIN) +bool Window::HookWindowMessage(UINT message, + const MessageCallback& callback) { + messages_callback_map_[message] = callback; + return true; +} + +void Window::UnhookWindowMessage(UINT message) { + if (!ContainsKey(messages_callback_map_, message)) + return; + + messages_callback_map_.erase(message); +} + +bool Window::IsWindowMessageHooked(UINT message) { + return ContainsKey(messages_callback_map_, message); +} + +void Window::UnhookAllWindowMessages() { + messages_callback_map_.clear(); +} +#endif + #if defined(OS_MACOSX) void Window::ShowDefinitionForSelection() { window_->ShowDefinitionForSelection(); @@ -540,20 +613,12 @@ v8::Local Window::WebContents(v8::Isolate* isolate) { 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::Local prototype) { mate::ObjectTemplateBuilder(isolate, prototype) - .SetMethod("destroy", &Window::Destroy, true) + .MakeDestroyable() .SetMethod("close", &Window::Close) - .SetMethod("isClosed", &Window::IsClosed) .SetMethod("focus", &Window::Focus) .SetMethod("isFocused", &Window::IsFocused) .SetMethod("show", &Window::Show) @@ -592,6 +657,7 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("setSkipTaskbar", &Window::SetSkipTaskbar) .SetMethod("setKiosk", &Window::SetKiosk) .SetMethod("isKiosk", &Window::IsKiosk) + .SetMethod("setBackgroundColor", &Window::SetBackgroundColor) .SetMethod("setRepresentedFilename", &Window::SetRepresentedFilename) .SetMethod("getRepresentedFilename", &Window::GetRepresentedFilename) .SetMethod("setDocumentEdited", &Window::SetDocumentEdited) @@ -613,13 +679,28 @@ void Window::BuildPrototype(v8::Isolate* isolate, &Window::SetVisibleOnAllWorkspaces) .SetMethod("isVisibleOnAllWorkspaces", &Window::IsVisibleOnAllWorkspaces) +#if defined(OS_WIN) + .SetMethod("hookWindowMessage", &Window::HookWindowMessage) + .SetMethod("isWindowMessageHooked", &Window::IsWindowMessageHooked) + .SetMethod("unhookWindowMessage", &Window::UnhookWindowMessage) + .SetMethod("unhookAllWindowMessages", &Window::UnhookAllWindowMessages) +#endif #if defined(OS_MACOSX) .SetMethod("showDefinitionForSelection", &Window::ShowDefinitionForSelection) #endif - .SetProperty("id", &Window::ID, true) - .SetProperty("webContents", &Window::WebContents, true) - .SetProperty("devToolsWebContents", &Window::DevToolsWebContents, true); + .SetProperty("id", &Window::ID) + .SetProperty("webContents", &Window::WebContents); +} + +// static +v8::Local Window::From(v8::Isolate* isolate, + NativeWindow* native_window) { + auto existing = TrackableObject::FromWrappedClass(isolate, native_window); + if (existing) + return existing->GetWrapper(isolate); + else + return v8::Null(isolate); } } // namespace api diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index d60bf0d87ea0..6d5ce22f4316 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -5,6 +5,7 @@ #ifndef ATOM_BROWSER_API_ATOM_API_WINDOW_H_ #define ATOM_BROWSER_API_ATOM_API_WINDOW_H_ +#include #include #include @@ -37,12 +38,15 @@ class WebContents; class Window : public mate::TrackableObject, public NativeWindowObserver { public: - static mate::Wrappable* New(v8::Isolate* isolate, - const mate::Dictionary& options); + static mate::Wrappable* New(v8::Isolate* isolate, mate::Arguments* args); static void BuildPrototype(v8::Isolate* isolate, v8::Local prototype); + // Returns the BrowserWindow object from |native_window|. + static v8::Local From(v8::Isolate* isolate, + NativeWindow* native_window); + NativeWindow* window() const { return window_.get(); } protected: @@ -50,8 +54,6 @@ class Window : public mate::TrackableObject, virtual ~Window(); // NativeWindowObserver: - void OnPageTitleUpdated(bool* prevent_default, - const std::string& title) override; void WillCloseWindow(bool* prevent_default) override; void OnWindowClosed() override; void OnWindowBlur() override; @@ -69,21 +71,15 @@ class Window : public mate::TrackableObject, 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; + #if defined(OS_WIN) + void OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) override; + #endif private: - // mate::TrackableObject: - void Destroy() override; - // APIs for NativeWindow. void Close(); - bool IsClosed(); void Focus(); bool IsFocused(); void Show(); @@ -121,6 +117,7 @@ class Window : public mate::TrackableObject, void SetSkipTaskbar(bool skip); void SetKiosk(bool kiosk); bool IsKiosk(); + void SetBackgroundColor(const std::string& color_name); void FocusOnWebView(); void BlurWebView(); bool IsWebViewFocused(); @@ -141,6 +138,16 @@ class Window : public mate::TrackableObject, bool IsMenuBarVisible(); void SetAspectRatio(double aspect_ratio, mate::Arguments* args); +#if defined(OS_WIN) + typedef base::Callback, + v8::Local)> MessageCallback; + + bool HookWindowMessage(UINT message, const MessageCallback& callback); + bool IsWindowMessageHooked(UINT message); + void UnhookWindowMessage(UINT message); + void UnhookAllWindowMessages(); +#endif + #if defined(OS_MACOSX) void ShowDefinitionForSelection(); #endif @@ -150,10 +157,13 @@ class Window : public mate::TrackableObject, int32_t ID() const; v8::Local WebContents(v8::Isolate* isolate); - v8::Local DevToolsWebContents(v8::Isolate* isolate); + +#if defined(OS_WIN) + typedef std::map MessageCallbackMap; + MessageCallbackMap messages_callback_map_; +#endif v8::Global web_contents_; - v8::Global devtools_web_contents_; v8::Global menu_; api::WebContents* api_web_contents_; diff --git a/atom/browser/api/frame_subscriber.cc b/atom/browser/api/frame_subscriber.cc index 526769f9cd6c..5b7241486b72 100644 --- a/atom/browser/api/frame_subscriber.cc +++ b/atom/browser/api/frame_subscriber.cc @@ -24,12 +24,11 @@ bool FrameSubscriber::ShouldCaptureFrame( base::TimeTicks present_time, scoped_refptr* storage, DeliverFrameCallback* callback) { - *storage = media::VideoFrame::CreateFrame(media::VideoFrame::YV12, size_, - gfx::Rect(size_), size_, - base::TimeDelta()); + *storage = media::VideoFrame::CreateFrame( + media::PIXEL_FORMAT_YV12, + size_, gfx::Rect(size_), size_, base::TimeDelta()); *callback = base::Bind(&FrameSubscriber::OnFrameDelivered, - base::Unretained(this), - *storage); + base::Unretained(this), *storage); return true; } @@ -38,6 +37,9 @@ void FrameSubscriber::OnFrameDelivered( if (!result) return; + v8::Locker locker(isolate_); + v8::HandleScope handle_scope(isolate_); + gfx::Rect rect = frame->visible_rect(); size_t rgb_arr_size = rect.width() * rect.height() * 4; v8::MaybeLocal buffer = node::Buffer::New(isolate_, rgb_arr_size); @@ -56,8 +58,6 @@ void FrameSubscriber::OnFrameDelivered( rect.width() * 4, media::YV12); - v8::Locker locker(isolate_); - v8::HandleScope handle_scope(isolate_); callback_.Run(buffer.ToLocalChecked()); } diff --git a/atom/browser/api/lib/app.coffee b/atom/browser/api/lib/app.coffee index 18c80dc2b1f4..d0ec41c4d23c 100644 --- a/atom/browser/api/lib/app.coffee +++ b/atom/browser/api/lib/app.coffee @@ -1,30 +1,17 @@ -EventEmitter = require('events').EventEmitter +{deprecate, session, Menu} = require 'electron' +{EventEmitter} = require 'events' bindings = process.atomBinding 'app' -sessionBindings = process.atomBinding 'session' downloadItemBindings = process.atomBinding 'download_item' app = bindings.app app.__proto__ = EventEmitter.prototype -wrapSession = (session) -> - # session is an Event Emitter. - session.__proto__ = EventEmitter.prototype - -wrapDownloadItem = (download_item) -> - # download_item is an Event Emitter. - download_item.__proto__ = EventEmitter.prototype - # Be compatible with old APIs. - download_item.url = download_item.getUrl() - download_item.filename = download_item.getFilename() - download_item.mimeType = download_item.getMimeType() - download_item.hasUserGesture = download_item.hasUserGesture() - app.setApplicationMenu = (menu) -> - require('menu').setApplicationMenu menu + Menu.setApplicationMenu menu app.getApplicationMenu = -> - require('menu').getApplicationMenu() + Menu.getApplicationMenu() app.commandLine = appendSwitch: bindings.appendSwitch, @@ -47,22 +34,39 @@ app.setAppPath = (path) -> app.getAppPath = -> appPath -# Be compatible with old API. -app.once 'ready', -> @emit 'finish-launching' -app.terminate = app.quit -app.exit = process.exit -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 +# Routes the events to webContents. +for name in ['login', 'certificate-error', 'select-client-certificate'] + do (name) -> + app.on name, (event, webContents, args...) -> + webContents.emit name, event, args... -# Session wrapper. -sessionBindings._setWrapSession wrapSession -process.once 'exit', sessionBindings._clearWrapSession +# Deprecated. +app.getHomeDir = deprecate 'app.getHomeDir', 'app.getPath', -> + @getPath 'home' +app.getDataPath = deprecate 'app.getDataPath', 'app.getPath', -> + @getPath 'userData' +app.setDataPath = deprecate 'app.setDataPath', 'app.setPath', (path) -> + @setPath 'userData', path +app.resolveProxy = deprecate 'app.resolveProxy', 'session.defaultSession.resolveProxy', (url, callback) -> + session.defaultSession.resolveProxy url, callback +deprecate.rename app, 'terminate', 'quit' +deprecate.event app, 'finish-launching', 'ready', -> + setImmediate => # give default app a chance to setup default menu. + @emit 'finish-launching' +deprecate.event app, 'activate-with-no-open-windows', 'activate', (event, hasVisibleWindows) -> + @emit 'activate-with-no-open-windows', event if not hasVisibleWindows +deprecate.event app, 'select-certificate', 'select-client-certificate' +# Wrappers for native classes. +wrapDownloadItem = (downloadItem) -> + # downloadItem is an EventEmitter. + downloadItem.__proto__ = EventEmitter.prototype + # Deprecated. + deprecate.property downloadItem, 'url', 'getURL' + deprecate.property downloadItem, 'filename', 'getFilename' + deprecate.property downloadItem, 'mimeType', 'getMimeType' + deprecate.rename downloadItem, 'getUrl', 'getURL' downloadItemBindings._setWrapDownloadItem wrapDownloadItem -process.once 'exit', downloadItemBindings._clearWrapDownloadItem # Only one App object pemitted. module.exports = app diff --git a/atom/browser/api/lib/atom-delegate.coffee b/atom/browser/api/lib/atom-delegate.coffee deleted file mode 100644 index 2e1e6334470b..000000000000 --- a/atom/browser/api/lib/atom-delegate.coffee +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = - browserMainParts: - preMainMessageLoopRun: -> - -setImmediate -> - module.exports.browserMainParts.preMainMessageLoopRun() diff --git a/atom/browser/api/lib/auto-updater.coffee b/atom/browser/api/lib/auto-updater.coffee index 8b6f7ffd0d30..28df59fbc3db 100644 --- a/atom/browser/api/lib/auto-updater.coffee +++ b/atom/browser/api/lib/auto-updater.coffee @@ -1,24 +1,12 @@ -autoUpdater = process.atomBinding('auto_updater').autoUpdater -EventEmitter = require('events').EventEmitter +{deprecate} = require 'electron' -autoUpdater.__proto__ = EventEmitter.prototype +autoUpdater = + if process.platform is 'win32' + require './auto-updater/auto-updater-win' + else + require './auto-updater/auto-updater-native' -autoUpdater.on 'update-downloaded-raw', (args...) -> - args[3] = new Date(args[3]) # releaseDate - @emit 'update-downloaded', args..., => @quitAndInstall() - -autoUpdater.quitAndInstall = -> - # If we don't have any window then quitAndInstall immediately. - BrowserWindow = require 'browser-window' - windows = BrowserWindow.getAllWindows() - if windows.length is 0 - @_quitAndInstall() - return - - # Do the restart after all windows have been closed. - app = require 'app' - app.removeAllListeners 'window-all-closed' - app.once 'window-all-closed', @_quitAndInstall.bind(this) - win.close() for win in windows +# Deprecated. +deprecate.rename autoUpdater, 'setFeedUrl', 'setFeedURL' module.exports = autoUpdater diff --git a/atom/browser/api/lib/auto-updater/auto-updater-native.coffee b/atom/browser/api/lib/auto-updater/auto-updater-native.coffee new file mode 100644 index 000000000000..187be64f5ade --- /dev/null +++ b/atom/browser/api/lib/auto-updater/auto-updater-native.coffee @@ -0,0 +1,6 @@ +{EventEmitter} = require 'events' +{autoUpdater} = process.atomBinding 'auto_updater' + +autoUpdater.__proto__ = EventEmitter.prototype + +module.exports = autoUpdater diff --git a/atom/browser/api/lib/auto-updater/auto-updater-win.coffee b/atom/browser/api/lib/auto-updater/auto-updater-win.coffee new file mode 100644 index 000000000000..e7cb194ffcf4 --- /dev/null +++ b/atom/browser/api/lib/auto-updater/auto-updater-win.coffee @@ -0,0 +1,42 @@ +{app} = require 'electron' +{EventEmitter} = require 'events' +url = require 'url' + +squirrelUpdate = require './squirrel-update-win' + +class AutoUpdater extends EventEmitter + quitAndInstall: -> + squirrelUpdate.processStart() + app.quit() + + setFeedURL: (updateURL) -> + @updateURL = updateURL + + checkForUpdates: -> + return @emitError 'Update URL is not set' unless @updateURL + return @emitError 'Can not find Squirrel' unless squirrelUpdate.supported() + + @emit 'checking-for-update' + + squirrelUpdate.download @updateURL, (error, update) => + return @emitError error if error? + return @emit 'update-not-available' unless update? + + @emit 'update-available' + + squirrelUpdate.update @updateURL, (error) => + return @emitError error if error? + + {releaseNotes, version} = update + # Following information is not available on Windows, so fake them. + date = new Date + url = @updateURL + + @emit 'update-downloaded', {}, releaseNotes, version, date, url, => @quitAndInstall() + + # Private: Emit both error object and message, this is to keep compatibility + # with Old APIs. + emitError: (message) -> + @emit 'error', new Error(message), message + +module.exports = new AutoUpdater diff --git a/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee b/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee new file mode 100644 index 000000000000..ee914c4fa827 --- /dev/null +++ b/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee @@ -0,0 +1,67 @@ +fs = require 'fs' +path = require 'path' +{spawn} = require 'child_process' + +appFolder = path.dirname process.execPath # i.e. my-app/app-0.1.13/ +updateExe = path.resolve appFolder, '..', 'Update.exe' # i.e. my-app/Update.exe +exeName = path.basename process.execPath + +# Spawn a command and invoke the callback when it completes with an error +# and the output from standard out. +spawnUpdate = (args, detached, callback) -> + try + spawnedProcess = spawn updateExe, args, {detached} + catch error + # Shouldn't happen, but still guard it. + process.nextTick -> callback error + return + + stdout = '' + stderr = '' + spawnedProcess.stdout.on 'data', (data) -> stdout += data + spawnedProcess.stderr.on 'data', (data) -> stderr += data + + errorEmitted = false + spawnedProcess.on 'error', (error) -> + errorEmitted = true + callback error + spawnedProcess.on 'exit', (code, signal) -> + # We may have already emitted an error. + return if errorEmitted + + # Process terminated with error. + if code isnt 0 + return callback "Command failed: #{signal ? code}\n#{stderr}" + + # Success. + callback null, stdout + +# Start an instance of the installed app. +exports.processStart = (callback) -> + spawnUpdate ['--processStart', exeName], true, -> + +# Download the releases specified by the URL and write new results to stdout. +exports.download = (updateURL, callback) -> + spawnUpdate ['--download', updateURL], false, (error, stdout) -> + return callback(error) if error? + + try + # Last line of output is the JSON details about the releases + json = stdout.trim().split('\n').pop() + update = JSON.parse(json)?.releasesToApply?.pop?() + catch + return callback "Invalid result:\n#{stdout}" + + callback null, update + +# Update the application to the latest remote version specified by URL. +exports.update = (updateURL, callback) -> + spawnUpdate ['--update', updateURL], false, callback + +# Is the Update.exe installed with the current application? +exports.supported = -> + try + fs.accessSync updateExe, fs.R_OK + return true + catch + return false diff --git a/atom/browser/api/lib/browser-window.coffee b/atom/browser/api/lib/browser-window.coffee index 2a92cfc55f88..07b4191d7c7b 100644 --- a/atom/browser/api/lib/browser-window.coffee +++ b/atom/browser/api/lib/browser-window.coffee @@ -1,11 +1,12 @@ -EventEmitter = require('events').EventEmitter -app = require 'app' -ipc = require 'ipc' +{ipcMain, deprecate} = require 'electron' +{EventEmitter} = require 'events' -BrowserWindow = process.atomBinding('window').BrowserWindow +{BrowserWindow} = process.atomBinding 'window' BrowserWindow::__proto__ = EventEmitter.prototype BrowserWindow::_init = -> + {app} = require 'electron' # avoid recursive require. + # Simulate the application menu on platforms other than OS X. if process.platform isnt 'darwin' menu = app.getApplicationMenu() @@ -14,7 +15,7 @@ BrowserWindow::_init = -> # Make new windows requested by links behave like "window.open" @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 + ipcMain.emit 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', event, url, frameName, options # window.resizeTo(...) # window.moveTo(...) @@ -30,6 +31,11 @@ BrowserWindow::_init = -> @webContents.on 'crashed', => @emit 'crashed' + # Change window title to page title. + @webContents.on 'page-title-updated', (event, title, explicitSet) => + @emit 'page-title-updated', event, title + @setTitle title unless event.defaultPrevented + # 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, @@ -48,6 +54,15 @@ BrowserWindow::_init = -> # Notify the creation of the window. app.emit 'browser-window-created', {}, this + # Be compatible with old APIs. + @webContents.on 'devtools-focused', => @emit 'devtools-focused' + @webContents.on 'devtools-opened', => @emit 'devtools-opened' + @webContents.on 'devtools-closed', => @emit 'devtools-closed' + Object.defineProperty this, 'devToolsWebContents', + enumerable: true, + configurable: false, + get: -> @webContents.devToolsWebContents + BrowserWindow.getFocusedWindow = -> windows = BrowserWindow.getAllWindows() return window for window in windows when window.isFocused() @@ -61,33 +76,37 @@ BrowserWindow.fromDevToolsWebContents = (webContents) -> 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::loadURL = -> @webContents.loadURL.apply @webContents, arguments +BrowserWindow::getURL = -> @webContents.getURL() BrowserWindow::reload = -> @webContents.reload.apply @webContents, arguments -BrowserWindow::reloadIgnoringCache = -> @webContents.reloadIgnoringCache.apply @webContents, arguments -BrowserWindow::getPageTitle = -> @webContents.getTitle() -BrowserWindow::isLoading = -> @webContents.isLoading() -BrowserWindow::isWaitingForResponse = -> @webContents.isWaitingForResponse() -BrowserWindow::stop = -> @webContents.stop() -BrowserWindow::isCrashed = -> @webContents.isCrashed() -BrowserWindow::executeJavaScriptInDevTools = (code) -> @devToolsWebContents?.executeJavaScript code +BrowserWindow::send = -> @webContents.send.apply @webContents, arguments 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 + +# Deprecated. +deprecate.member BrowserWindow, 'undo', 'webContents' +deprecate.member BrowserWindow, 'redo', 'webContents' +deprecate.member BrowserWindow, 'cut', 'webContents' +deprecate.member BrowserWindow, 'copy', 'webContents' +deprecate.member BrowserWindow, 'paste', 'webContents' +deprecate.member BrowserWindow, 'selectAll', 'webContents' +deprecate.member BrowserWindow, 'reloadIgnoringCache', 'webContents' +deprecate.member BrowserWindow, 'isLoading', 'webContents' +deprecate.member BrowserWindow, 'isWaitingForResponse', 'webContents' +deprecate.member BrowserWindow, 'stop', 'webContents' +deprecate.member BrowserWindow, 'isCrashed', 'webContents' +deprecate.member BrowserWindow, 'print', 'webContents' +deprecate.member BrowserWindow, 'printToPDF', 'webContents' +deprecate.rename BrowserWindow, 'restart', 'reload' +deprecate.rename BrowserWindow, 'loadUrl', 'loadURL' +deprecate.rename BrowserWindow, 'getUrl', 'getURL' +BrowserWindow::executeJavaScriptInDevTools = deprecate 'executeJavaScriptInDevTools', 'devToolsWebContents.executeJavaScript', (code) -> + @devToolsWebContents?.executeJavaScript code +BrowserWindow::getPageTitle = deprecate 'getPageTitle', 'webContents.getTitle', -> + @webContents?.getTitle() module.exports = BrowserWindow diff --git a/atom/browser/api/lib/dialog.coffee b/atom/browser/api/lib/dialog.coffee index 0843af04282e..f10ce58c17f5 100644 --- a/atom/browser/api/lib/dialog.coffee +++ b/atom/browser/api/lib/dialog.coffee @@ -1,7 +1,7 @@ +{app, BrowserWindow} = require 'electron' + binding = process.atomBinding 'dialog' v8Util = process.atomBinding 'v8_util' -app = require 'app' -BrowserWindow = require 'browser-window' fileDialogProperties = openFile: 1 << 0 diff --git a/atom/browser/api/lib/exports/electron.coffee b/atom/browser/api/lib/exports/electron.coffee new file mode 100644 index 000000000000..3f7d9b1a13fe --- /dev/null +++ b/atom/browser/api/lib/exports/electron.coffee @@ -0,0 +1,55 @@ +# Import common modules. +module.exports = require '../../../../common/api/lib/exports/electron' + +Object.defineProperties module.exports, + # Browser side modules, please sort with alphabet order. + app: + enumerable: true + get: -> require '../app' + autoUpdater: + enumerable: true + get: -> require '../auto-updater' + BrowserWindow: + enumerable: true + get: -> require '../browser-window' + contentTracing: + enumerable: true + get: -> require '../content-tracing' + dialog: + enumerable: true + get: -> require '../dialog' + ipcMain: + enumerable: true + get: -> require '../ipc-main' + globalShortcut: + enumerable: true + get: -> require '../global-shortcut' + Menu: + enumerable: true + get: -> require '../menu' + MenuItem: + enumerable: true + get: -> require '../menu-item' + powerMonitor: + enumerable: true + get: -> require '../power-monitor' + powerSaveBlocker: + enumerable: true + get: -> require '../power-save-blocker' + protocol: + enumerable: true + get: -> require '../protocol' + screen: + enumerable: true + get: -> require '../screen' + session: + enumerable: true + get: -> require '../session' + Tray: + enumerable: true + get: -> require '../tray' + # The internal modules, invisible unless you know their names. + NavigationController: + get: -> require '../navigation-controller' + webContents: + get: -> require '../web-contents' diff --git a/atom/browser/api/lib/global-shortcut.coffee b/atom/browser/api/lib/global-shortcut.coffee index 8b24d2725366..56c3e128767e 100644 --- a/atom/browser/api/lib/global-shortcut.coffee +++ b/atom/browser/api/lib/global-shortcut.coffee @@ -1,5 +1,3 @@ -bindings = process.atomBinding 'global_shortcut' - -globalShortcut = bindings.globalShortcut +{globalShortcut} = process.atomBinding 'global_shortcut' module.exports = globalShortcut diff --git a/atom/browser/api/lib/ipc-main.coffee b/atom/browser/api/lib/ipc-main.coffee new file mode 100644 index 000000000000..8021544479d2 --- /dev/null +++ b/atom/browser/api/lib/ipc-main.coffee @@ -0,0 +1,3 @@ +{EventEmitter} = require 'events' + +module.exports = new EventEmitter diff --git a/atom/browser/api/lib/ipc.coffee b/atom/browser/api/lib/ipc.coffee index 71cf1d17e491..018cb6bb0437 100644 --- a/atom/browser/api/lib/ipc.coffee +++ b/atom/browser/api/lib/ipc.coffee @@ -1,3 +1,6 @@ -EventEmitter = require('events').EventEmitter +{deprecate, ipcMain} = require 'electron' -module.exports = new EventEmitter +# This module is deprecated, we mirror everything from ipcMain. +deprecate.warn 'ipc module', 'require("electron").ipcMain' + +module.exports = ipcMain diff --git a/atom/browser/api/lib/menu-item.coffee b/atom/browser/api/lib/menu-item.coffee index cfefeec4edbb..57beb6ffdae3 100644 --- a/atom/browser/api/lib/menu-item.coffee +++ b/atom/browser/api/lib/menu-item.coffee @@ -1,4 +1,3 @@ -BrowserWindow = require 'browser-window' v8Util = process.atomBinding 'v8_util' nextCommandId = 0 @@ -14,11 +13,16 @@ rolesMap = minimize: 'minimize' close: 'close' +# Maps methods that should be called directly on the BrowserWindow instance +methodInBrowserWindow = + minimize: true + close: true + class MenuItem @types = ['normal', 'separator', 'submenu', 'checkbox', 'radio'] constructor: (options) -> - Menu = require 'menu' + {Menu} = require 'electron' {click, @selector, @type, @role, @label, @sublabel, @accelerator, @icon, @enabled, @visible, @checked, @submenu} = options @@ -43,8 +47,12 @@ class MenuItem # Manually flip the checked flags when clicked. @checked = !@checked if @type in ['checkbox', 'radio'] - if @role and rolesMap[@role] and process.platform isnt 'darwin' - focusedWindow?[rolesMap[@role]]() + if @role and rolesMap[@role] and process.platform isnt 'darwin' and focusedWindow? + methodName = rolesMap[@role] + if methodInBrowserWindow[methodName] + focusedWindow[methodName]() + else + focusedWindow.webContents?[methodName]() else if typeof click is 'function' click this, focusedWindow else if typeof @selector is 'string' diff --git a/atom/browser/api/lib/menu.coffee b/atom/browser/api/lib/menu.coffee index 3595ba1fe7db..26e2dc233506 100644 --- a/atom/browser/api/lib/menu.coffee +++ b/atom/browser/api/lib/menu.coffee @@ -1,8 +1,7 @@ -BrowserWindow = require 'browser-window' -EventEmitter = require('events').EventEmitter -MenuItem = require 'menu-item' -v8Util = process.atomBinding 'v8_util' +{BrowserWindow, MenuItem} = require 'electron' +{EventEmitter} = require 'events' +v8Util = process.atomBinding 'v8_util' bindings = process.atomBinding 'menu' # Automatically generated radio menu item's group id. @@ -79,7 +78,11 @@ Menu::_init = -> v8Util.setHiddenValue group[0], 'checked', true unless checked Menu::popup = (window, x, y) -> - throw new TypeError('Invalid window') unless window?.constructor is BrowserWindow + unless window?.constructor is BrowserWindow + # Shift. + y = x + x = window + window = BrowserWindow.getFocusedWindow() if x? and y? @_popupAt(window, x, y) else diff --git a/atom/browser/api/lib/navigation-controller.coffee b/atom/browser/api/lib/navigation-controller.coffee index f78d92c341d6..d0c539a99db5 100644 --- a/atom/browser/api/lib/navigation-controller.coffee +++ b/atom/browser/api/lib/navigation-controller.coffee @@ -1,15 +1,15 @@ -ipc = require 'ipc' +{ipcMain} = require 'electron' # The history operation in renderer is redirected to browser. -ipc.on 'ATOM_SHELL_NAVIGATION_CONTROLLER', (event, method, args...) -> +ipcMain.on 'ATOM_SHELL_NAVIGATION_CONTROLLER', (event, method, args...) -> event.sender[method] args... -ipc.on 'ATOM_SHELL_SYNC_NAVIGATION_CONTROLLER', (event, method, args...) -> +ipcMain.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. +# control on user land, and only rely on WebContents.loadURL for navigation. # This helps us avoid Chromium's various optimizations so we can ensure renderer # process is restarted everytime. class NavigationController @@ -17,9 +17,9 @@ class NavigationController @clearHistory() # webContents may have already navigated to a page. - if @webContents._getUrl() + if @webContents._getURL() @currentIndex++ - @history.push @webContents._getUrl() + @history.push @webContents._getURL() @webContents.on 'navigation-entry-commited', (event, url, inPage, replaceEntry) => if @inPageIndex > -1 and not inPage @@ -42,12 +42,12 @@ class NavigationController @currentIndex++ @history.push url - loadUrl: (url, options={}) -> + loadURL: (url, options={}) -> @pendingIndex = -1 - @webContents._loadUrl url, options + @webContents._loadURL url, options @webContents.emit 'load-url', url, options - getUrl: -> + getURL: -> if @currentIndex is -1 '' else @@ -59,11 +59,11 @@ class NavigationController reload: -> @pendingIndex = @currentIndex - @webContents._loadUrl @getUrl(), {} + @webContents._loadURL @getURL(), {} reloadIgnoringCache: -> - @webContents._reloadIgnoringCache() # Rely on WebContents to clear cache. - @reload() + @pendingIndex = @currentIndex + @webContents._loadURL @getURL(), {extraHeaders: "pragma: no-cache\n"} canGoBack: -> @getActiveIndex() > 0 @@ -89,7 +89,7 @@ class NavigationController if @inPageIndex > -1 and @pendingIndex >= @inPageIndex @webContents._goBack() else - @webContents._loadUrl @history[@pendingIndex], {} + @webContents._loadURL @history[@pendingIndex], {} goForward: -> return unless @canGoForward() @@ -97,12 +97,12 @@ class NavigationController if @inPageIndex > -1 and @pendingIndex >= @inPageIndex @webContents._goForward() else - @webContents._loadUrl @history[@pendingIndex], {} + @webContents._loadURL @history[@pendingIndex], {} goToIndex: (index) -> return unless @canGoToIndex index @pendingIndex = index - @webContents._loadUrl @history[@pendingIndex], {} + @webContents._loadURL @history[@pendingIndex], {} goToOffset: (offset) -> return unless @canGoToOffset offset diff --git a/atom/browser/api/lib/power-monitor.coffee b/atom/browser/api/lib/power-monitor.coffee index f13e60eb9da8..54bf9391827c 100644 --- a/atom/browser/api/lib/power-monitor.coffee +++ b/atom/browser/api/lib/power-monitor.coffee @@ -1,5 +1,6 @@ -powerMonitor = process.atomBinding('power_monitor').powerMonitor -EventEmitter = require('events').EventEmitter +{EventEmitter} = require 'events' + +{powerMonitor} = process.atomBinding 'power_monitor' powerMonitor.__proto__ = EventEmitter.prototype diff --git a/atom/browser/api/lib/power-save-blocker.coffee b/atom/browser/api/lib/power-save-blocker.coffee index 7f428bc40f19..58392bc9aa8b 100644 --- a/atom/browser/api/lib/power-save-blocker.coffee +++ b/atom/browser/api/lib/power-save-blocker.coffee @@ -1,3 +1,3 @@ -bindings = process.atomBinding 'power_save_blocker' +{powerSaveBlocker} = process.atomBinding 'power_save_blocker' -module.exports = bindings.powerSaveBlocker +module.exports = powerSaveBlocker diff --git a/atom/browser/api/lib/protocol.coffee b/atom/browser/api/lib/protocol.coffee index 13f2a6d10270..a1dbc7c17d75 100644 --- a/atom/browser/api/lib/protocol.coffee +++ b/atom/browser/api/lib/protocol.coffee @@ -1,7 +1,8 @@ -app = require 'app' +{app} = require 'electron' + throw new Error('Can not initialize protocol module before app is ready') unless app.isReady() -protocol = process.atomBinding('protocol').protocol +{protocol} = process.atomBinding 'protocol' # Warn about removed APIs. logAndThrow = (callback, message) -> diff --git a/atom/browser/api/lib/screen.coffee b/atom/browser/api/lib/screen.coffee index 6ef5a5f66338..87c42f091df2 100644 --- a/atom/browser/api/lib/screen.coffee +++ b/atom/browser/api/lib/screen.coffee @@ -1,6 +1,6 @@ -EventEmitter = require('events').EventEmitter +{EventEmitter} = require 'events' +{screen} = process.atomBinding 'screen' -screen = process.atomBinding('screen').screen screen.__proto__ = EventEmitter.prototype module.exports = screen diff --git a/atom/browser/api/lib/session.coffee b/atom/browser/api/lib/session.coffee new file mode 100644 index 000000000000..6abfe7925e69 --- /dev/null +++ b/atom/browser/api/lib/session.coffee @@ -0,0 +1,23 @@ +{EventEmitter} = require 'events' + +bindings = process.atomBinding 'session' + +PERSIST_PERFIX = 'persist:' + +# Returns the Session from |partition| string. +exports.fromPartition = (partition='') -> + if partition.startsWith PERSIST_PERFIX + bindings.fromPartition partition.substr(PERSIST_PERFIX.length), false + else + bindings.fromPartition partition, true + +# Returns the default session. +Object.defineProperty exports, 'defaultSession', + enumerable: true + get: -> exports.fromPartition '' + +wrapSession = (session) -> + # session is an EventEmitter. + session.__proto__ = EventEmitter.prototype + +bindings._setWrapSession wrapSession diff --git a/atom/browser/api/lib/tray.coffee b/atom/browser/api/lib/tray.coffee index 1c225ddd403c..db26ab5b7ed1 100644 --- a/atom/browser/api/lib/tray.coffee +++ b/atom/browser/api/lib/tray.coffee @@ -1,14 +1,19 @@ -EventEmitter = require('events').EventEmitter -bindings = process.atomBinding 'tray' +{deprecate} = require 'electron' +{EventEmitter} = require 'events' -Tray = bindings.Tray +{Tray} = process.atomBinding 'tray' Tray::__proto__ = EventEmitter.prototype +Tray::_init = -> + # Deprecated. + deprecate.rename this, 'popContextMenu', 'popUpContextMenu' + deprecate.event this, 'clicked', 'click' + deprecate.event this, 'double-clicked', 'double-click' + deprecate.event this, 'right-clicked', 'right-click' + deprecate.event this, 'balloon-clicked', 'balloon-click' + 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 3a2abfb5155f..dacbc919d665 100644 --- a/atom/browser/api/lib/web-contents.coffee +++ b/atom/browser/api/lib/web-contents.coffee @@ -1,7 +1,7 @@ -EventEmitter = require('events').EventEmitter -NavigationController = require './navigation-controller' +{EventEmitter} = require 'events' +{deprecate, ipcMain, session, NavigationController, Menu} = require 'electron' + binding = process.atomBinding 'web_contents' -ipc = require 'ipc' nextId = 0 getNextId = -> ++nextId @@ -45,7 +45,7 @@ wrapWebContents = (webContents) -> # Make sure webContents.executeJavaScript would run the code only when the # web contents has been loaded. webContents.executeJavaScript = (code, hasUserGesture=false) -> - if @getUrl() and not @isLoading() + if @getURL() and not @isLoading() @_executeJavaScript code, hasUserGesture else webContents.once 'did-finish-load', @_executeJavaScript.bind(this, code, hasUserGesture) @@ -59,11 +59,32 @@ wrapWebContents = (webContents) -> # Dispatch IPC messages to the ipc module. webContents.on 'ipc-message', (event, packed) -> [channel, args...] = packed - ipc.emit channel, event, args... + ipcMain.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) - ipc.emit channel, event, args... + ipcMain.emit channel, event, args... + + # Handle context menu action request from pepper plugin. + webContents.on 'pepper-context-menu', (event, params) -> + menu = Menu.buildFromTemplate params.menu + menu.popup params.x, params.y + + # This error occurs when host could not be found. + webContents.on 'did-fail-provisional-load', (args...) -> + # Calling loadURL during this event might cause crash, so delay the event + # until next tick. + setImmediate => @emit 'did-fail-load', args... + + # Delays the page-title-updated event to next tick. + webContents.on '-page-title-updated', (args...) -> + setImmediate => @emit 'page-title-updated', args... + + # Deprecated. + deprecate.rename webContents, 'loadUrl', 'loadURL' + deprecate.rename webContents, 'getUrl', 'getURL' + deprecate.event webContents, 'page-title-set', 'page-title-updated', (args...) -> + @emit 'page-title-set', args... webContents.printToPDF = (options, callback) -> printingSetting = @@ -106,7 +127,6 @@ wrapWebContents = (webContents) -> @_printToPDF printingSetting, callback binding._setWrapWebContents wrapWebContents -process.once 'exit', binding._clearWrapWebContents module.exports.create = (options={}) -> binding.create(options) diff --git a/atom/browser/api/save_page_handler.cc b/atom/browser/api/save_page_handler.cc new file mode 100644 index 000000000000..1e5bc094cf63 --- /dev/null +++ b/atom/browser/api/save_page_handler.cc @@ -0,0 +1,83 @@ +// 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/save_page_handler.h" + +#include + +#include "atom/browser/atom_browser_context.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "content/public/browser/web_contents.h" + +namespace atom { + +namespace api { + +SavePageHandler::SavePageHandler(content::WebContents* web_contents, + const SavePageCallback& callback) + : web_contents_(web_contents), + callback_(callback) { +} + +SavePageHandler::~SavePageHandler() { +} + +void SavePageHandler::OnDownloadCreated(content::DownloadManager* manager, + content::DownloadItem* item) { + // OnDownloadCreated is invoked during WebContents::SavePage, so the |item| + // here is the one stated by WebContents::SavePage. + item->AddObserver(this); +} + +bool SavePageHandler::Handle(const base::FilePath& full_path, + const content::SavePageType& save_type) { + auto download_manager = content::BrowserContext::GetDownloadManager( + web_contents_->GetBrowserContext()); + download_manager->AddObserver(this); + // Chromium will create a 'foo_files' directory under the directory of saving + // page 'foo.html' for holding other resource files of 'foo.html'. + base::FilePath saved_main_directory_path = full_path.DirName().Append( + full_path.RemoveExtension().BaseName().value() + + FILE_PATH_LITERAL("_files")); + bool result = web_contents_->SavePage(full_path, + saved_main_directory_path, + save_type); + download_manager->RemoveObserver(this); + // If initialization fails which means fail to create |DownloadItem|, we need + // to delete the |SavePageHandler| instance to avoid memory-leak. + if (!result) + delete this; + return result; +} + +void SavePageHandler::OnDownloadUpdated(content::DownloadItem* item) { + if (item->IsDone()) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + if (item->GetState() == content::DownloadItem::COMPLETE) { + callback_.Run(v8::Null(isolate)); + } else { + v8::Local error_message = v8::String::NewFromUtf8( + isolate, "Fail to save page"); + callback_.Run(v8::Exception::Error(error_message)); + } + Destroy(item); + } +} + +void SavePageHandler::Destroy(content::DownloadItem* item) { + item->RemoveObserver(this); + delete this; +} + +// static +bool SavePageHandler::IsSavePageTypes(const std::string& type) { + return type == "multipart/related" || type == "text/html"; +} + +} // namespace api + +} // namespace atom diff --git a/atom/browser/api/save_page_handler.h b/atom/browser/api/save_page_handler.h new file mode 100644 index 000000000000..dd1692a8badc --- /dev/null +++ b/atom/browser/api/save_page_handler.h @@ -0,0 +1,60 @@ +// 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_SAVE_PAGE_HANDLER_H_ +#define ATOM_BROWSER_API_SAVE_PAGE_HANDLER_H_ + +#include + +#include "content/public/browser/download_item.h" +#include "content/public/browser/download_manager.h" +#include "content/public/browser/save_page_type.h" +#include "v8/include/v8.h" + +namespace base { +class FilePath; +} + +namespace content { +class WebContents; +} + +namespace atom { + +namespace api { + +// A self-destroyed class for handling save page request. +class SavePageHandler : public content::DownloadManager::Observer, + public content::DownloadItem::Observer { + public: + using SavePageCallback = base::Callback)>; + + SavePageHandler(content::WebContents* web_contents, + const SavePageCallback& callback); + ~SavePageHandler(); + + bool Handle(const base::FilePath& full_path, + const content::SavePageType& save_type); + + static bool IsSavePageTypes(const std::string& type); + + private: + void Destroy(content::DownloadItem* item); + + // content::DownloadManager::Observer: + void OnDownloadCreated(content::DownloadManager* manager, + content::DownloadItem* item) override; + + // content::DownloadItem::Observer: + void OnDownloadUpdated(content::DownloadItem* item) override; + + content::WebContents* web_contents_; // weak + SavePageCallback callback_; +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_SAVE_PAGE_HANDLER_H_ diff --git a/atom/browser/api/trackable_object.cc b/atom/browser/api/trackable_object.cc index 50bfa59e6a7a..77a936cde02c 100644 --- a/atom/browser/api/trackable_object.cc +++ b/atom/browser/api/trackable_object.cc @@ -30,11 +30,11 @@ class IDUserData : public base::SupportsUserData::Data { TrackableObjectBase::TrackableObjectBase() : weak_map_id_(0), wrapped_(nullptr), weak_factory_(this) { - RegisterDestructionCallback( - base::Bind(&TrackableObjectBase::Destroy, weak_factory_.GetWeakPtr())); + cleanup_ = RegisterDestructionCallback(GetDestroyClosure()); } TrackableObjectBase::~TrackableObjectBase() { + cleanup_.Run(); } void TrackableObjectBase::AfterInit(v8::Isolate* isolate) { @@ -42,6 +42,18 @@ void TrackableObjectBase::AfterInit(v8::Isolate* isolate) { AttachAsUserData(wrapped_); } +void TrackableObjectBase::MarkDestroyed() { + GetWrapper(isolate())->SetAlignedPointerInInternalField(0, nullptr); +} + +base::Closure TrackableObjectBase::GetDestroyClosure() { + return base::Bind(&TrackableObjectBase::Destroy, weak_factory_.GetWeakPtr()); +} + +void TrackableObjectBase::Destroy() { + delete this; +} + void TrackableObjectBase::AttachAsUserData(base::SupportsUserData* wrapped) { if (weak_map_id_ != 0) { wrapped->SetUserData(kTrackedObjectKey, new IDUserData(weak_map_id_)); @@ -63,9 +75,9 @@ int32_t TrackableObjectBase::GetIDFromWrappedClass(base::SupportsUserData* w) { } // static -void TrackableObjectBase::RegisterDestructionCallback( - const base::Closure& closure) { - atom::AtomBrowserMainParts::Get()->RegisterDestructionCallback(closure); +base::Closure TrackableObjectBase::RegisterDestructionCallback( + const base::Closure& c) { + return atom::AtomBrowserMainParts::Get()->RegisterDestructionCallback(c); } } // namespace mate diff --git a/atom/browser/api/trackable_object.h b/atom/browser/api/trackable_object.h index 8ff7d1c04041..7c4ed03fe052 100644 --- a/atom/browser/api/trackable_object.h +++ b/atom/browser/api/trackable_object.h @@ -12,6 +12,7 @@ #include "base/bind.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" +#include "native_mate/object_template_builder.h" namespace base { class SupportsUserData; @@ -30,26 +31,32 @@ class TrackableObjectBase : public mate::EventEmitter { // 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; + // Mark the JS object as destroyed. + void MarkDestroyed(); + + // Returns a closure that can destroy the native class. + base::Closure GetDestroyClosure(); + // 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); + static base::Closure RegisterDestructionCallback(const base::Closure& c); int32_t weak_map_id_; base::SupportsUserData* wrapped_; private: + void Destroy(); + + base::Closure cleanup_; base::WeakPtrFactory weak_factory_; DISALLOW_COPY_AND_ASSIGN(TrackableObjectBase); @@ -91,11 +98,6 @@ class TrackableObject : public TrackableObjectBase { 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())) @@ -103,28 +105,49 @@ class TrackableObject : public TrackableObjectBase { } protected: + TrackableObject() {} ~TrackableObject() override { RemoveFromWeakMap(); } void AfterInit(v8::Isolate* isolate) override { - if (!weak_map_) + if (!weak_map_) { weak_map_.reset(new atom::IDWeakMap); + RegisterDestructionCallback( + base::Bind(&TrackableObject::ReleaseAllWeakReferences)); + } weak_map_id_ = weak_map_->Add(isolate, GetWrapper(isolate)); TrackableObjectBase::AfterInit(isolate); } private: + // mate::Wrappable: + mate::ObjectTemplateBuilder GetObjectTemplateBuilder( + v8::Isolate* isolate) override { + if (template_.IsEmpty()) { + auto templ = v8::ObjectTemplate::New(isolate); + T::BuildPrototype(isolate, templ); + template_.Reset(isolate, templ); + } + + return ObjectTemplateBuilder( + isolate, v8::Local::New(isolate, template_)); + } + // Releases all weak references in weak map, called when app is terminating. static void ReleaseAllWeakReferences() { weak_map_.reset(); } + static v8::Persistent template_; static scoped_ptr weak_map_; DISALLOW_COPY_AND_ASSIGN(TrackableObject); }; +template +v8::Persistent TrackableObject::template_; + template scoped_ptr TrackableObject::weak_map_; diff --git a/atom/browser/atom_access_token_store.cc b/atom/browser/atom_access_token_store.cc index 3d254f060188..adf2f5061cb0 100644 --- a/atom/browser/atom_access_token_store.cc +++ b/atom/browser/atom_access_token_store.cc @@ -18,7 +18,7 @@ namespace { // Notice that we just combined the api key with the url together here, because // if we use the standard {url: key} format Chromium would override our key with // the predefined one in common.gypi of libchromiumcontent, which is empty. -const char* kGeolocationProviderUrl = +const char* kGeolocationProviderURL = "https://www.googleapis.com/geolocation/v1/geolocate?key=" GOOGLEAPIS_API_KEY; @@ -35,11 +35,11 @@ void AtomAccessTokenStore::LoadAccessTokens( const LoadAccessTokensCallbackType& callback) { AccessTokenSet access_token_set; - // Equivelent to access_token_set[kGeolocationProviderUrl]. + // Equivelent to access_token_set[kGeolocationProviderURL]. // Somehow base::string16 is causing compilation errors when used in a pair // of std::map on Linux, this can work around it. std::pair token_pair; - token_pair.first = GURL(kGeolocationProviderUrl); + token_pair.first = GURL(kGeolocationProviderURL); access_token_set.insert(token_pair); auto browser_context = AtomBrowserMainParts::Get()->browser_context(); diff --git a/atom/browser/atom_browser_client.cc b/atom/browser/atom_browser_client.cc index e45caceab01e..b9b186d187d0 100644 --- a/atom/browser/atom_browser_client.cc +++ b/atom/browser/atom_browser_client.cc @@ -12,8 +12,8 @@ #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/atom_quota_permission_context.h" +#include "atom/browser/atom_resource_dispatcher_host_delegate.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_contents_preferences.h" #include "atom/browser/window_list.h" @@ -30,6 +30,7 @@ #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" @@ -83,10 +84,10 @@ void AtomBrowserClient::SuppressRendererProcessRestartForOnce() { void AtomBrowserClient::SetCustomSchemes( const std::vector& schemes) { - g_custom_schemes = JoinString(schemes, ','); + g_custom_schemes = base::JoinString(schemes, ","); } -AtomBrowserClient::AtomBrowserClient() { +AtomBrowserClient::AtomBrowserClient() : delegate_(nullptr) { } AtomBrowserClient::~AtomBrowserClient() { @@ -115,7 +116,6 @@ void AtomBrowserClient::OverrideWebkitPrefs( prefs->javascript_can_open_windows_automatically = true; prefs->plugins_enabled = true; prefs->dom_paste_enabled = true; - prefs->java_enabled = false; prefs->allow_scripts_to_close_windows = true; prefs->javascript_can_access_clipboard = true; prefs->local_storage_enabled = true; @@ -206,6 +206,26 @@ content::QuotaPermissionContext* return new AtomQuotaPermissionContext; } +void AtomBrowserClient::AllowCertificateError( + int render_process_id, + int render_frame_id, + int cert_error, + const net::SSLInfo& ssl_info, + const GURL& request_url, + content::ResourceType resource_type, + bool overridable, + bool strict_enforcement, + bool expired_previous_decision, + const base::Callback& callback, + content::CertificateRequestResultType* request) { + if (delegate_) { + delegate_->AllowCertificateError( + render_process_id, render_frame_id, cert_error, ssl_info, request_url, + resource_type, overridable, strict_enforcement, + expired_previous_decision, callback, request); + } +} + void AtomBrowserClient::SelectClientCertificate( content::WebContents* web_contents, net::SSLCertRequestInfo* cert_request_info, @@ -220,10 +240,17 @@ void AtomBrowserClient::SelectClientCertificate( return; } - if (!cert_request_info->client_certs.empty()) - Browser::Get()->ClientCertificateSelector(web_contents, - cert_request_info, - delegate.Pass()); + if (!cert_request_info->client_certs.empty() && delegate_) { + delegate_->SelectClientCertificate( + web_contents, cert_request_info, delegate.Pass()); + } +} + +void AtomBrowserClient::ResourceDispatcherHostCreated() { + resource_dispatcher_host_delegate_.reset( + new AtomResourceDispatcherHostDelegate); + content::ResourceDispatcherHost::Get()->SetDelegate( + resource_dispatcher_host_delegate_.get()); } brightray::BrowserMainParts* AtomBrowserClient::OverrideCreateBrowserMainParts( diff --git a/atom/browser/atom_browser_client.h b/atom/browser/atom_browser_client.h index a0217efede9f..75e17494593b 100644 --- a/atom/browser/atom_browser_client.h +++ b/atom/browser/atom_browser_client.h @@ -23,12 +23,17 @@ class SSLCertRequestInfo; namespace atom { +class AtomResourceDispatcherHostDelegate; + class AtomBrowserClient : public brightray::BrowserClient, public content::RenderProcessHostObserver { public: AtomBrowserClient(); virtual ~AtomBrowserClient(); + using Delegate = content::ContentBrowserClient; + void set_delegate(Delegate* delegate) { delegate_ = delegate; } + // Don't force renderer process to restart for once. static void SuppressRendererProcessRestartForOnce(); // Custom schemes to be registered to standard. @@ -52,10 +57,23 @@ class AtomBrowserClient : public brightray::BrowserClient, int child_process_id) override; void DidCreatePpapiPlugin(content::BrowserPpapiHost* browser_host) override; content::QuotaPermissionContext* CreateQuotaPermissionContext() override; + void AllowCertificateError( + int render_process_id, + int render_frame_id, + int cert_error, + const net::SSLInfo& ssl_info, + const GURL& request_url, + content::ResourceType resource_type, + bool overridable, + bool strict_enforcement, + bool expired_previous_decision, + const base::Callback& callback, + content::CertificateRequestResultType* request) override; void SelectClientCertificate( content::WebContents* web_contents, net::SSLCertRequestInfo* cert_request_info, scoped_ptr delegate) override; + void ResourceDispatcherHostCreated() override; // brightray::BrowserClient: brightray::BrowserMainParts* OverrideCreateBrowserMainParts( @@ -68,6 +86,11 @@ class AtomBrowserClient : public brightray::BrowserClient, // pending_render_process => current_render_process. std::map pending_processes_; + scoped_ptr + resource_dispatcher_host_delegate_; + + Delegate* delegate_; + DISALLOW_COPY_AND_ASSIGN(AtomBrowserClient); }; diff --git a/atom/browser/atom_browser_context.cc b/atom/browser/atom_browser_context.cc index 6823fbaee90c..ec123825822e 100644 --- a/atom/browser/atom_browser_context.cc +++ b/atom/browser/atom_browser_context.cc @@ -6,8 +6,9 @@ #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/atom_download_manager_delegate.h" -#include "atom/browser/atom_ssl_config_service.h" #include "atom/browser/browser.h" +#include "atom/browser/net/atom_cert_verifier.h" +#include "atom/browser/net/atom_ssl_config_service.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" @@ -60,7 +61,9 @@ std::string RemoveWhitespace(const std::string& str) { AtomBrowserContext::AtomBrowserContext(const std::string& partition, bool in_memory) : brightray::BrowserContext(partition, in_memory), - job_factory_(new AtomURLRequestJobFactory) { + cert_verifier_(nullptr), + job_factory_(new AtomURLRequestJobFactory), + allow_ntlm_everywhere_(false) { } AtomBrowserContext::~AtomBrowserContext() { @@ -83,7 +86,8 @@ std::string AtomBrowserContext::GetUserAgent() { return content::BuildUserAgentFromProduct(user_agent); } -net::URLRequestJobFactory* AtomBrowserContext::CreateURLRequestJobFactory( +scoped_ptr +AtomBrowserContext::CreateURLRequestJobFactory( content::ProtocolHandlerMap* handlers, content::URLRequestInterceptorScopedVector* interceptors) { scoped_ptr job_factory(job_factory_); @@ -128,7 +132,7 @@ net::URLRequestJobFactory* AtomBrowserContext::CreateURLRequestJobFactory( top_job_factory.Pass(), make_scoped_ptr(*it))); interceptors->weak_clear(); - return top_job_factory.release(); + return top_job_factory.Pass(); } net::HttpCache::BackendFactory* @@ -157,6 +161,12 @@ content::BrowserPluginGuestManager* AtomBrowserContext::GetGuestManager() { return guest_manager_.get(); } +scoped_ptr AtomBrowserContext::CreateCertVerifier() { + DCHECK(!cert_verifier_); + cert_verifier_ = new AtomCertVerifier; + return make_scoped_ptr(cert_verifier_); +} + net::SSLConfigService* AtomBrowserContext::CreateSSLConfigService() { return new AtomSSLConfigService; } @@ -168,6 +178,16 @@ void AtomBrowserContext::RegisterPrefs(PrefRegistrySimple* pref_registry) { base::FilePath()); } +bool AtomBrowserContext::AllowNTLMCredentialsForDomain(const GURL& origin) { + if (allow_ntlm_everywhere_) + return true; + return Delegate::AllowNTLMCredentialsForDomain(origin); +} + +void AtomBrowserContext::AllowNTLMCredentialsForAllDomains(bool should_allow) { + allow_ntlm_everywhere_ = should_allow; +} + } // namespace atom namespace brightray { diff --git a/atom/browser/atom_browser_context.h b/atom/browser/atom_browser_context.h index 839359c1ef50..564c9955d917 100644 --- a/atom/browser/atom_browser_context.h +++ b/atom/browser/atom_browser_context.h @@ -12,6 +12,7 @@ namespace atom { class AtomDownloadManagerDelegate; +class AtomCertVerifier; class AtomURLRequestJobFactory; class WebViewManager; @@ -22,12 +23,14 @@ class AtomBrowserContext : public brightray::BrowserContext { // brightray::URLRequestContextGetter::Delegate: std::string GetUserAgent() override; - net::URLRequestJobFactory* CreateURLRequestJobFactory( + scoped_ptr CreateURLRequestJobFactory( content::ProtocolHandlerMap* handlers, content::URLRequestInterceptorScopedVector* interceptors) override; net::HttpCache::BackendFactory* CreateHttpCacheBackendFactory( const base::FilePath& base_path) override; + scoped_ptr CreateCertVerifier() override; net::SSLConfigService* CreateSSLConfigService() override; + bool AllowNTLMCredentialsForDomain(const GURL& auth_origin) override; // content::BrowserContext: content::DownloadManagerDelegate* GetDownloadManagerDelegate() override; @@ -36,6 +39,10 @@ class AtomBrowserContext : public brightray::BrowserContext { // brightray::BrowserContext: void RegisterPrefs(PrefRegistrySimple* pref_registry) override; + void AllowNTLMCredentialsForAllDomains(bool should_allow); + + AtomCertVerifier* cert_verifier() const { return cert_verifier_; } + AtomURLRequestJobFactory* job_factory() const { return job_factory_; } private: @@ -43,8 +50,11 @@ class AtomBrowserContext : public brightray::BrowserContext { scoped_ptr guest_manager_; // Managed by brightray::BrowserContext. + AtomCertVerifier* cert_verifier_; AtomURLRequestJobFactory* job_factory_; + bool allow_ntlm_everywhere_; + DISALLOW_COPY_AND_ASSIGN(AtomBrowserContext); }; diff --git a/atom/browser/atom_browser_main_parts.cc b/atom/browser/atom_browser_main_parts.cc index a1a1192b2768..fd72f5e4ae9a 100644 --- a/atom/browser/atom_browser_main_parts.cc +++ b/atom/browser/atom_browser_main_parts.cc @@ -25,11 +25,17 @@ namespace atom { +template +void Erase(T* container, typename T::iterator iter) { + container->erase(iter); +} + // static AtomBrowserMainParts* AtomBrowserMainParts::self_ = NULL; AtomBrowserMainParts::AtomBrowserMainParts() : fake_browser_process_(new BrowserProcess), + exit_code_(nullptr), browser_(new Browser), node_bindings_(NodeBindings::Create(true)), atom_bindings_(new AtomBindings), @@ -47,29 +53,39 @@ AtomBrowserMainParts* AtomBrowserMainParts::Get() { return self_; } -void AtomBrowserMainParts::RegisterDestructionCallback( +bool AtomBrowserMainParts::SetExitCode(int code) { + if (!exit_code_) + return false; + + *exit_code_ = code; + return true; +} + +base::Closure AtomBrowserMainParts::RegisterDestructionCallback( const base::Closure& callback) { - destruction_callbacks_.push_back(callback); + auto iter = destructors_.insert(destructors_.end(), callback); + return base::Bind(&Erase>, &destructors_, iter); +} + +void AtomBrowserMainParts::PreEarlyInitialization() { + brightray::BrowserMainParts::PreEarlyInitialization(); +#if defined(OS_POSIX) + HandleSIGCHLD(); +#endif } void AtomBrowserMainParts::PostEarlyInitialization() { brightray::BrowserMainParts::PostEarlyInitialization(); -#if defined(USE_X11) - SetDPIFromGSettings(); -#endif + // 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_); - { - // 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); - } + // 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(); @@ -104,6 +120,8 @@ void AtomBrowserMainParts::PreMainMessageLoopRun() { 1000)); brightray::BrowserMainParts::PreMainMessageLoopRun(); + bridge_task_runner_->MessageLoopIsReady(); + bridge_task_runner_ = nullptr; #if defined(USE_X11) libgtk2ui::GtkInitFromCommandLine(*base::CommandLine::ForCurrentProcess()); @@ -116,14 +134,43 @@ void AtomBrowserMainParts::PreMainMessageLoopRun() { #endif } +bool AtomBrowserMainParts::MainMessageLoopRun(int* result_code) { + exit_code_ = result_code; + return brightray::BrowserMainParts::MainMessageLoopRun(result_code); +} + +void AtomBrowserMainParts::PostMainMessageLoopStart() { + brightray::BrowserMainParts::PostMainMessageLoopStart(); +#if defined(OS_POSIX) + HandleShutdownSignals(); +#endif +} + void AtomBrowserMainParts::PostMainMessageLoopRun() { brightray::BrowserMainParts::PostMainMessageLoopRun(); +#if defined(OS_MACOSX) + FreeAppDelegate(); +#endif + // 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_) + // We don't use ranged for loop because iterators are getting invalided when + // the callback runs. + for (auto iter = destructors_.begin(); iter != destructors_.end();) { + base::Closure& callback = *iter; + ++iter; callback.Run(); + } + + // Destroy JavaScript environment immediately after running destruction + // callbacks. + gc_timer_.Stop(); + node_debugger_.reset(); + atom_bindings_.reset(); + node_bindings_.reset(); + js_env_.reset(); } } // namespace atom diff --git a/atom/browser/atom_browser_main_parts.h b/atom/browser/atom_browser_main_parts.h index bcebc86f16ca..fbc59f7f811d 100644 --- a/atom/browser/atom_browser_main_parts.h +++ b/atom/browser/atom_browser_main_parts.h @@ -31,25 +31,37 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts { static AtomBrowserMainParts* Get(); + // Sets the exit code, will fail if the the message loop is not ready. + bool SetExitCode(int code); + // Register a callback that should be destroyed before JavaScript environment // gets destroyed. - void RegisterDestructionCallback(const base::Closure& callback); + // Returns a closure that can be used to remove |callback| from the list. + base::Closure RegisterDestructionCallback(const base::Closure& callback); Browser* browser() { return browser_.get(); } protected: // content::BrowserMainParts: + void PreEarlyInitialization() override; void PostEarlyInitialization() override; void PreMainMessageLoopRun() override; + bool MainMessageLoopRun(int* result_code) override; + void PostMainMessageLoopStart() override; void PostMainMessageLoopRun() override; #if defined(OS_MACOSX) void PreMainMessageLoopStart() override; - void PostDestroyThreads() override; #endif private: -#if defined(USE_X11) - void SetDPIFromGSettings(); +#if defined(OS_POSIX) + // Set signal handlers. + void HandleSIGCHLD(); + void HandleShutdownSignals(); +#endif + +#if defined(OS_MACOSX) + void FreeAppDelegate(); #endif // A fake BrowserProcess object that used to feed the source code from chrome. @@ -59,6 +71,9 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts { // with a task runner that will post all work to main loop. scoped_refptr bridge_task_runner_; + // Pointer to exit code. + int* exit_code_; + scoped_ptr browser_; scoped_ptr js_env_; scoped_ptr node_bindings_; @@ -68,7 +83,7 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts { base::Timer gc_timer_; // List of callbacks should be executed before destroying JS env. - std::list destruction_callbacks_; + std::list destructors_; static AtomBrowserMainParts* self_; diff --git a/atom/browser/atom_browser_main_parts_linux.cc b/atom/browser/atom_browser_main_parts_linux.cc deleted file mode 100644 index 278e49ac1f1e..000000000000 --- a/atom/browser/atom_browser_main_parts_linux.cc +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) 2014 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "atom/browser/atom_browser_main_parts.h" - -#include - -#include "base/command_line.h" -#include "base/strings/string_number_conversions.h" -#include "ui/gfx/switches.h" - -namespace atom { - -namespace { - -const char* kInterfaceSchema = "org.gnome.desktop.interface"; -const char* kScaleFactor = "scaling-factor"; - -bool SchemaExists(const char* schema_name) { - const gchar* const* schemas = g_settings_list_schemas(); - while (*schemas) { - if (strcmp(schema_name, static_cast(*schemas)) == 0) - return true; - schemas++; - } - return false; -} - -bool KeyExists(GSettings* client, const char* key) { - gchar** keys = g_settings_list_keys(client); - if (!keys) - return false; - - gchar** iter = keys; - while (*iter) { - if (strcmp(*iter, key) == 0) - break; - iter++; - } - - bool exists = *iter != NULL; - g_strfreev(keys); - return exists; -} - -void GetDPIFromGSettings(guint* scale_factor) { - GSettings* client = nullptr; - if (!SchemaExists(kInterfaceSchema) || - !(client = g_settings_new(kInterfaceSchema))) { - VLOG(1) << "Cannot create gsettings client."; - return; - } - - if (KeyExists(client, kScaleFactor)) - *scale_factor = g_settings_get_uint(client, kScaleFactor); - - g_object_unref(client); -} - -} // namespace - -void AtomBrowserMainParts::SetDPIFromGSettings() { - guint scale_factor = 1; - GetDPIFromGSettings(&scale_factor); - if (scale_factor == 0) - scale_factor = 1; - - base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( - switches::kForceDeviceScaleFactor, base::UintToString(scale_factor)); -} - -} // namespace atom diff --git a/atom/browser/atom_browser_main_parts_mac.mm b/atom/browser/atom_browser_main_parts_mac.mm index 1de07dfc0e44..42e3100f490e 100644 --- a/atom/browser/atom_browser_main_parts_mac.mm +++ b/atom/browser/atom_browser_main_parts_mac.mm @@ -34,7 +34,7 @@ void AtomBrowserMainParts::PreMainMessageLoopStart() { setObject:@"NO" forKey:@"NSTreatUnknownArgumentsAsOpen"]; } -void AtomBrowserMainParts::PostDestroyThreads() { +void AtomBrowserMainParts::FreeAppDelegate() { [[NSApp delegate] release]; [NSApp setDelegate:nil]; } diff --git a/atom/browser/atom_browser_main_parts_posix.cc b/atom/browser/atom_browser_main_parts_posix.cc new file mode 100644 index 000000000000..2a0dddc47483 --- /dev/null +++ b/atom/browser/atom_browser_main_parts_posix.cc @@ -0,0 +1,225 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +// Most code came from: chrome/browser/chrome_browser_main_posix.cc. + +#include "atom/browser/atom_browser_main_parts.h" + +#include +#include +#include +#include +#include +#include + +#include "atom/browser/browser.h" +#include "base/posix/eintr_wrapper.h" +#include "content/public/browser/browser_thread.h" + +using content::BrowserThread; + +namespace atom { + +namespace { + +// See comment in |PreEarlyInitialization()|, where sigaction is called. +void SIGCHLDHandler(int signal) { +} + +// The OSX fork() implementation can crash in the child process before +// fork() returns. In that case, the shutdown pipe will still be +// shared with the parent process. To prevent child crashes from +// causing parent shutdowns, |g_pipe_pid| is the pid for the process +// which registered |g_shutdown_pipe_write_fd|. +// See . +pid_t g_pipe_pid = -1; +int g_shutdown_pipe_write_fd = -1; +int g_shutdown_pipe_read_fd = -1; + +// Common code between SIG{HUP, INT, TERM}Handler. +void GracefulShutdownHandler(int signal) { + // Reinstall the default handler. We had one shot at graceful shutdown. + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = SIG_DFL; + RAW_CHECK(sigaction(signal, &action, NULL) == 0); + + RAW_CHECK(g_pipe_pid == getpid()); + RAW_CHECK(g_shutdown_pipe_write_fd != -1); + RAW_CHECK(g_shutdown_pipe_read_fd != -1); + size_t bytes_written = 0; + do { + int rv = HANDLE_EINTR( + write(g_shutdown_pipe_write_fd, + reinterpret_cast(&signal) + bytes_written, + sizeof(signal) - bytes_written)); + RAW_CHECK(rv >= 0); + bytes_written += rv; + } while (bytes_written < sizeof(signal)); +} + +// See comment in |PostMainMessageLoopStart()|, where sigaction is called. +void SIGHUPHandler(int signal) { + RAW_CHECK(signal == SIGHUP); + GracefulShutdownHandler(signal); +} + +// See comment in |PostMainMessageLoopStart()|, where sigaction is called. +void SIGINTHandler(int signal) { + RAW_CHECK(signal == SIGINT); + GracefulShutdownHandler(signal); +} + +// See comment in |PostMainMessageLoopStart()|, where sigaction is called. +void SIGTERMHandler(int signal) { + RAW_CHECK(signal == SIGTERM); + GracefulShutdownHandler(signal); +} + +class ShutdownDetector : public base::PlatformThread::Delegate { + public: + explicit ShutdownDetector(int shutdown_fd); + + void ThreadMain() override; + + private: + const int shutdown_fd_; + + DISALLOW_COPY_AND_ASSIGN(ShutdownDetector); +}; + +ShutdownDetector::ShutdownDetector(int shutdown_fd) + : shutdown_fd_(shutdown_fd) { + CHECK_NE(shutdown_fd_, -1); +} + +// These functions are used to help us diagnose crash dumps that happen +// during the shutdown process. +NOINLINE void ShutdownFDReadError() { + // Ensure function isn't optimized away. + asm(""); + sleep(UINT_MAX); +} + +NOINLINE void ShutdownFDClosedError() { + // Ensure function isn't optimized away. + asm(""); + sleep(UINT_MAX); +} + +NOINLINE void ExitPosted() { + // Ensure function isn't optimized away. + asm(""); + sleep(UINT_MAX); +} + +void ShutdownDetector::ThreadMain() { + base::PlatformThread::SetName("CrShutdownDetector"); + + int signal; + size_t bytes_read = 0; + ssize_t ret; + do { + ret = HANDLE_EINTR( + read(shutdown_fd_, + reinterpret_cast(&signal) + bytes_read, + sizeof(signal) - bytes_read)); + if (ret < 0) { + NOTREACHED() << "Unexpected error: " << strerror(errno); + ShutdownFDReadError(); + break; + } else if (ret == 0) { + NOTREACHED() << "Unexpected closure of shutdown pipe."; + ShutdownFDClosedError(); + break; + } + bytes_read += ret; + } while (bytes_read < sizeof(signal)); + VLOG(1) << "Handling shutdown for signal " << signal << "."; + base::Closure task = + base::Bind(&Browser::Quit, base::Unretained(Browser::Get())); + + if (!BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, task)) { + // Without a UI thread to post the exit task to, there aren't many + // options. Raise the signal again. The default handler will pick it up + // and cause an ungraceful exit. + RAW_LOG(WARNING, "No UI thread, exiting ungracefully."); + kill(getpid(), signal); + + // The signal may be handled on another thread. Give that a chance to + // happen. + sleep(3); + + // We really should be dead by now. For whatever reason, we're not. Exit + // immediately, with the exit status set to the signal number with bit 8 + // set. On the systems that we care about, this exit status is what is + // normally used to indicate an exit by this signal's default handler. + // This mechanism isn't a de jure standard, but even in the worst case, it + // should at least result in an immediate exit. + RAW_LOG(WARNING, "Still here, exiting really ungracefully."); + _exit(signal | (1 << 7)); + } + ExitPosted(); +} + +} // namespace + +void AtomBrowserMainParts::HandleSIGCHLD() { + // We need to accept SIGCHLD, even though our handler is a no-op because + // otherwise we cannot wait on children. (According to POSIX 2001.) + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = SIGCHLDHandler; + CHECK_EQ(sigaction(SIGCHLD, &action, NULL), 0); +} + +void AtomBrowserMainParts::HandleShutdownSignals() { + int pipefd[2]; + int ret = pipe(pipefd); + if (ret < 0) { + PLOG(DFATAL) << "Failed to create pipe"; + } else { + g_pipe_pid = getpid(); + g_shutdown_pipe_read_fd = pipefd[0]; + g_shutdown_pipe_write_fd = pipefd[1]; +#if !defined(ADDRESS_SANITIZER) && !defined(KEEP_SHADOW_STACKS) + const size_t kShutdownDetectorThreadStackSize = PTHREAD_STACK_MIN * 2; +#else + // ASan instrumentation and -finstrument-functions (used for keeping the + // shadow stacks) bloat the stack frames, so we need to increase the stack + // size to avoid hitting the guard page. + const size_t kShutdownDetectorThreadStackSize = PTHREAD_STACK_MIN * 4; +#endif + // TODO(viettrungluu,willchan): crbug.com/29675 - This currently leaks, so + // if you change this, you'll probably need to change the suppression. + if (!base::PlatformThread::CreateNonJoinable( + kShutdownDetectorThreadStackSize, + new ShutdownDetector(g_shutdown_pipe_read_fd))) { + LOG(DFATAL) << "Failed to create shutdown detector task."; + } + } + // Setup signal handlers for shutdown AFTER shutdown pipe is setup because + // it may be called right away after handler is set. + + // If adding to this list of signal handlers, note the new signal probably + // needs to be reset in child processes. See + // base/process_util_posix.cc:LaunchProcess. + + // We need to handle SIGTERM, because that is how many POSIX-based distros ask + // processes to quit gracefully at shutdown time. + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = SIGTERMHandler; + CHECK_EQ(sigaction(SIGTERM, &action, NULL), 0); + // Also handle SIGINT - when the user terminates the browser via Ctrl+C. If + // the browser process is being debugged, GDB will catch the SIGINT first. + action.sa_handler = SIGINTHandler; + CHECK_EQ(sigaction(SIGINT, &action, NULL), 0); + // And SIGHUP, for when the terminal disappears. On shutdown, many Linux + // distros send SIGHUP, SIGTERM, and then SIGKILL. + action.sa_handler = SIGHUPHandler; + CHECK_EQ(sigaction(SIGHUP, &action, NULL), 0); +} + +} // namespace atom diff --git a/atom/browser/atom_resource_dispatcher_host_delegate.cc b/atom/browser/atom_resource_dispatcher_host_delegate.cc new file mode 100644 index 000000000000..aaba1f31045b --- /dev/null +++ b/atom/browser/atom_resource_dispatcher_host_delegate.cc @@ -0,0 +1,40 @@ +// 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_resource_dispatcher_host_delegate.h" + +#include "atom/browser/login_handler.h" +#include "atom/common/platform_util.h" +#include "content/public/browser/browser_thread.h" +#include "net/base/escape.h" +#include "url/gurl.h" + +using content::BrowserThread; + +namespace atom { + +AtomResourceDispatcherHostDelegate::AtomResourceDispatcherHostDelegate() { +} + +bool AtomResourceDispatcherHostDelegate::HandleExternalProtocol( + const GURL& url, + int render_process_id, + int render_view_id, + bool is_main_frame, + ui::PageTransition transition, + bool has_user_gesture) { + GURL escaped_url(net::EscapeExternalHandlerValue(url.spec())); + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(base::IgnoreResult(platform_util::OpenExternal), escaped_url)); + return true; +} + +content::ResourceDispatcherHostLoginDelegate* +AtomResourceDispatcherHostDelegate::CreateLoginDelegate( + net::AuthChallengeInfo* auth_info, + net::URLRequest* request) { + return new LoginHandler(auth_info, request); +} + +} // namespace atom diff --git a/atom/browser/atom_resource_dispatcher_host_delegate.h b/atom/browser/atom_resource_dispatcher_host_delegate.h new file mode 100644 index 000000000000..a90b366bc75b --- /dev/null +++ b/atom/browser/atom_resource_dispatcher_host_delegate.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_RESOURCE_DISPATCHER_HOST_DELEGATE_H_ +#define ATOM_BROWSER_ATOM_RESOURCE_DISPATCHER_HOST_DELEGATE_H_ + +#include "content/public/browser/resource_dispatcher_host_delegate.h" + +namespace atom { + +class AtomResourceDispatcherHostDelegate + : public content::ResourceDispatcherHostDelegate { + public: + AtomResourceDispatcherHostDelegate(); + + // content::ResourceDispatcherHostDelegate: + bool HandleExternalProtocol(const GURL& url, + int render_process_id, + int render_view_id, + bool is_main_frame, + ui::PageTransition transition, + bool has_user_gesture) override; + content::ResourceDispatcherHostLoginDelegate* CreateLoginDelegate( + net::AuthChallengeInfo* auth_info, + net::URLRequest* request) override; +}; + +} // namespace atom + +#endif // ATOM_BROWSER_ATOM_RESOURCE_DISPATCHER_HOST_DELEGATE_H_ diff --git a/atom/browser/auto_updater.cc b/atom/browser/auto_updater.cc index fd3d412f9bb0..7dbfc5a6feba 100644 --- a/atom/browser/auto_updater.cc +++ b/atom/browser/auto_updater.cc @@ -6,14 +6,25 @@ namespace auto_updater { -AutoUpdaterDelegate* AutoUpdater::delegate_ = NULL; +Delegate* AutoUpdater::delegate_ = nullptr; -AutoUpdaterDelegate* AutoUpdater::GetDelegate() { +Delegate* AutoUpdater::GetDelegate() { return delegate_; } -void AutoUpdater::SetDelegate(AutoUpdaterDelegate* delegate) { +void AutoUpdater::SetDelegate(Delegate* delegate) { delegate_ = delegate; } +#if !defined(OS_MACOSX) || defined(MAS_BUILD) +void AutoUpdater::SetFeedURL(const std::string& url) { +} + +void AutoUpdater::CheckForUpdates() { +} + +void AutoUpdater::QuitAndInstall() { +} +#endif + } // namespace auto_updater diff --git a/atom/browser/auto_updater.h b/atom/browser/auto_updater.h index e31aa0978a1c..9e479d4220de 100644 --- a/atom/browser/auto_updater.h +++ b/atom/browser/auto_updater.h @@ -9,21 +9,48 @@ #include "base/basictypes.h" +namespace base { +class Time; +} + namespace auto_updater { -class AutoUpdaterDelegate; +class Delegate { + public: + // An error happened. + virtual void OnError(const std::string& error) {} + + // Checking to see if there is an update + virtual void OnCheckingForUpdate() {} + + // There is an update available and it is being downloaded + virtual void OnUpdateAvailable() {} + + // There is no available update. + virtual void OnUpdateNotAvailable() {} + + // There is a new update which has been downloaded. + virtual void OnUpdateDownloaded(const std::string& release_notes, + const std::string& release_name, + const base::Time& release_date, + const std::string& update_url) {} + + protected: + virtual ~Delegate() {} +}; class AutoUpdater { public: // Gets/Sets the delegate. - static AutoUpdaterDelegate* GetDelegate(); - static void SetDelegate(AutoUpdaterDelegate* delegate); + static Delegate* GetDelegate(); + static void SetDelegate(Delegate* delegate); static void SetFeedURL(const std::string& url); static void CheckForUpdates(); + static void QuitAndInstall(); private: - static AutoUpdaterDelegate* delegate_; + static Delegate* delegate_; DISALLOW_IMPLICIT_CONSTRUCTORS(AutoUpdater); }; diff --git a/atom/browser/auto_updater_delegate.h b/atom/browser/auto_updater_delegate.h deleted file mode 100644 index 804bc8503a1e..000000000000 --- a/atom/browser/auto_updater_delegate.h +++ /dev/null @@ -1,45 +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_AUTO_UPDATER_DELEGATE_H_ -#define ATOM_BROWSER_AUTO_UPDATER_DELEGATE_H_ - -#include - -#include "base/callback_forward.h" - -namespace base { -class Time; -} - -namespace auto_updater { - -class AutoUpdaterDelegate { - public: - // An error happened. - virtual void OnError(const std::string& error) {} - - // Checking to see if there is an update - virtual void OnCheckingForUpdate() {} - - // There is an update available and it is being downloaded - virtual void OnUpdateAvailable() {} - - // There is no available update. - virtual void OnUpdateNotAvailable() {} - - // There is a new update which has been downloaded. - virtual void OnUpdateDownloaded(const std::string& release_notes, - const std::string& release_name, - const base::Time& release_date, - const std::string& update_url, - const base::Closure& quit_and_install) {} - - protected: - virtual ~AutoUpdaterDelegate() {} -}; - -} // namespace auto_updater - -#endif // ATOM_BROWSER_AUTO_UPDATER_DELEGATE_H_ diff --git a/atom/browser/auto_updater_linux.cc b/atom/browser/auto_updater_linux.cc deleted file mode 100644 index 00c95d0d45d7..000000000000 --- a/atom/browser/auto_updater_linux.cc +++ /dev/null @@ -1,17 +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/auto_updater.h" - -namespace auto_updater { - -// static -void AutoUpdater::SetFeedURL(const std::string& url) { -} - -// static -void AutoUpdater::CheckForUpdates() { -} - -} // namespace auto_updater diff --git a/atom/browser/auto_updater_mac.mm b/atom/browser/auto_updater_mac.mm index 42ee8413632e..a55cdd281265 100644 --- a/atom/browser/auto_updater_mac.mm +++ b/atom/browser/auto_updater_mac.mm @@ -12,9 +12,6 @@ #include "base/bind.h" #include "base/time/time.h" #include "base/strings/sys_string_conversions.h" -#include "atom/browser/auto_updater_delegate.h" - -#include namespace auto_updater { @@ -23,20 +20,12 @@ namespace { // The gloal SQRLUpdater object. SQRLUpdater* g_updater = nil; -void RelaunchToInstallUpdate() { - [[g_updater relaunchToInstallUpdate] subscribeError:^(NSError* error) { - AutoUpdaterDelegate* delegate = AutoUpdater::GetDelegate(); - if (delegate) - delegate->OnError(base::SysNSStringToUTF8(error.localizedDescription)); - }]; -} - } // namespace // static void AutoUpdater::SetFeedURL(const std::string& feed) { if (g_updater == nil) { - AutoUpdaterDelegate* delegate = GetDelegate(); + Delegate* delegate = GetDelegate(); if (!delegate) return; @@ -67,7 +56,7 @@ void AutoUpdater::SetFeedURL(const std::string& feed) { // static void AutoUpdater::CheckForUpdates() { - AutoUpdaterDelegate* delegate = GetDelegate(); + Delegate* delegate = GetDelegate(); if (!delegate) return; @@ -86,8 +75,7 @@ void AutoUpdater::CheckForUpdates() { base::SysNSStringToUTF8(update.releaseNotes), base::SysNSStringToUTF8(update.releaseName), base::Time::FromDoubleT(update.releaseDate.timeIntervalSince1970), - base::SysNSStringToUTF8(update.updateURL.absoluteString), - base::Bind(RelaunchToInstallUpdate)); + base::SysNSStringToUTF8(update.updateURL.absoluteString)); } else { // When the completed event is sent with no update, then we know there // is no update available. @@ -100,4 +88,12 @@ void AutoUpdater::CheckForUpdates() { }]; } +void AutoUpdater::QuitAndInstall() { + [[g_updater relaunchToInstallUpdate] subscribeError:^(NSError* error) { + Delegate* delegate = AutoUpdater::GetDelegate(); + if (delegate) + delegate->OnError(base::SysNSStringToUTF8(error.localizedDescription)); + }]; +} + } // namespace auto_updater diff --git a/atom/browser/auto_updater_win.cc b/atom/browser/auto_updater_win.cc deleted file mode 100644 index 00c95d0d45d7..000000000000 --- a/atom/browser/auto_updater_win.cc +++ /dev/null @@ -1,17 +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/auto_updater.h" - -namespace auto_updater { - -// static -void AutoUpdater::SetFeedURL(const std::string& url) { -} - -// static -void AutoUpdater::CheckForUpdates() { -} - -} // namespace auto_updater diff --git a/atom/browser/bridge_task_runner.cc b/atom/browser/bridge_task_runner.cc index 24572f3990d8..36c8d17359e8 100644 --- a/atom/browser/bridge_task_runner.cc +++ b/atom/browser/bridge_task_runner.cc @@ -8,13 +8,28 @@ namespace atom { +void BridgeTaskRunner::MessageLoopIsReady() { + auto message_loop = base::MessageLoop::current(); + CHECK(message_loop); + for (const TaskPair& task : tasks_) { + message_loop->task_runner()->PostDelayedTask( + base::get<0>(task), base::get<1>(task), base::get<2>(task)); + } + for (const TaskPair& task : non_nestable_tasks_) { + message_loop->task_runner()->PostNonNestableDelayedTask( + base::get<0>(task), base::get<1>(task), base::get<2>(task)); + } +} + 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; + if (!message_loop) { + tasks_.push_back(base::MakeTuple(from_here, task, delay)); + return true; + } return message_loop->task_runner()->PostDelayedTask(from_here, task, delay); } @@ -22,7 +37,7 @@ bool BridgeTaskRunner::PostDelayedTask( bool BridgeTaskRunner::RunsTasksOnCurrentThread() const { auto message_loop = base::MessageLoop::current(); if (!message_loop) - return false; + return true; return message_loop->task_runner()->RunsTasksOnCurrentThread(); } @@ -32,8 +47,10 @@ bool BridgeTaskRunner::PostNonNestableDelayedTask( const base::Closure& task, base::TimeDelta delay) { auto message_loop = base::MessageLoop::current(); - if (!message_loop) - return false; + if (!message_loop) { + non_nestable_tasks_.push_back(base::MakeTuple(from_here, task, delay)); + return true; + } return message_loop->task_runner()->PostNonNestableDelayedTask( from_here, task, delay); diff --git a/atom/browser/bridge_task_runner.h b/atom/browser/bridge_task_runner.h index fb42aa3852f9..b69b33b29516 100644 --- a/atom/browser/bridge_task_runner.h +++ b/atom/browser/bridge_task_runner.h @@ -5,17 +5,23 @@ #ifndef ATOM_BROWSER_BRIDGE_TASK_RUNNER_H_ #define ATOM_BROWSER_BRIDGE_TASK_RUNNER_H_ +#include + #include "base/single_thread_task_runner.h" +#include "base/tuple.h" namespace atom { // Post all tasks to the current message loop's task runner if available, -// otherwise fail silently. +// otherwise delay the work until message loop is ready. class BridgeTaskRunner : public base::SingleThreadTaskRunner { public: BridgeTaskRunner() {} ~BridgeTaskRunner() override {} + // Called when message loop is ready. + void MessageLoopIsReady(); + // base::SingleThreadTaskRunner: bool PostDelayedTask(const tracked_objects::Location& from_here, const base::Closure& task, @@ -27,6 +33,11 @@ class BridgeTaskRunner : public base::SingleThreadTaskRunner { base::TimeDelta delay) override; private: + using TaskPair = base::Tuple< + tracked_objects::Location, base::Closure, base::TimeDelta>; + std::vector tasks_; + std::vector non_nestable_tasks_; + DISALLOW_COPY_AND_ASSIGN(BridgeTaskRunner); }; diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 9d2a9fc1effb..c77f359760c9 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -7,16 +7,16 @@ #include #include "atom/browser/atom_browser_main_parts.h" +#include "atom/browser/native_window.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 { Browser::Browser() : is_quiting_(false), - is_ready_(false) { + is_ready_(false), + is_shutdown_(false) { WindowList::AddObserver(this); } @@ -30,6 +30,9 @@ Browser* Browser::Get() { } void Browser::Quit() { + if (is_quiting_) + return; + is_quiting_ = HandleBeforeQuit(); if (!is_quiting_) return; @@ -41,12 +44,43 @@ void Browser::Quit() { window_list->CloseAllWindows(); } +void Browser::Exit(int code) { + if (!AtomBrowserMainParts::Get()->SetExitCode(code)) { + // Message loop is not ready, quit directly. + exit(code); + } else { + // Prepare to quit when all windows have been closed.. + is_quiting_ = true; + + // Must destroy windows before quitting, otherwise bad things can happen. + atom::WindowList* window_list = atom::WindowList::GetInstance(); + if (window_list->size() == 0) { + NotifyAndShutdown(); + } else { + // Unlike Quit(), we do not ask to close window, but destroy the window + // without asking. + for (NativeWindow* window : *window_list) + window->CloseContents(nullptr); // e.g. Destroy() + } + } +} + void Browser::Shutdown() { + if (is_shutdown_) + return; + + is_shutdown_ = true; + is_quiting_ = true; + FOR_EACH_OBSERVER(BrowserObserver, observers_, OnQuit()); - is_quiting_ = true; - base::MessageLoop::current()->PostTask( - FROM_HERE, base::MessageLoop::QuitWhenIdleClosure()); + if (base::MessageLoop::current()) { + base::MessageLoop::current()->PostTask( + FROM_HERE, base::MessageLoop::QuitWhenIdleClosure()); + } else { + // There is no message loop available so we are in early stage. + exit(0); + } } std::string Browser::GetVersion() const { @@ -75,10 +109,6 @@ std::string Browser::GetName() const { void Browser::SetName(const std::string& name) { name_override_ = name; - -#if defined(OS_WIN) - SetAppUserModelID(name); -#endif } bool Browser::OpenFile(const std::string& file_path) { @@ -109,18 +139,14 @@ 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::RequestLogin(LoginHandler* login_handler) { + FOR_EACH_OBSERVER(BrowserObserver, observers_, OnLogin(login_handler)); } void Browser::NotifyAndShutdown() { + if (is_shutdown_) + return; + 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 d135556b8760..e46624b158df 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -11,12 +11,12 @@ #include "base/basictypes.h" #include "base/compiler_specific.h" #include "base/observer_list.h" +#include "base/strings/string16.h" #include "atom/browser/browser_observer.h" #include "atom/browser/window_list_observer.h" #if defined(OS_WIN) #include "base/files/file_path.h" -#include "base/strings/string16.h" #endif namespace base { @@ -29,6 +29,8 @@ class MenuModel; namespace atom { +class LoginHandler; + // This class is used for control application-wide operations. class Browser : public WindowListObserver { public: @@ -40,6 +42,9 @@ class Browser : public WindowListObserver { // Try to close all windows and quit the application. void Quit(); + // Exit the application immediately and set exit code. + void Exit(int code); + // Cleanup everything and shutdown the application gracefully. void Shutdown(); @@ -64,6 +69,9 @@ class Browser : public WindowListObserver { // Clear the recent documents list. void ClearRecentDocuments(); + // Set the application user model ID. + void SetAppUserModelID(const base::string16& name); + #if defined(OS_MACOSX) // Bounce the dock icon. enum BounceType { @@ -98,8 +106,10 @@ class Browser : public WindowListObserver { // Add a custom task to jump list. void SetUserTasks(const std::vector& tasks); - // Set the application user model ID, called when "SetName" is called. - void SetAppUserModelID(const std::string& name); + // Returns the application user model ID, if there isn't one, then create + // one from app's name. + // The returned string managed by Browser, and should not be modified. + PCWSTR GetAppUserModelID(); #endif // Tell the application to open a file. @@ -116,11 +126,8 @@ class Browser : public WindowListObserver { void WillFinishLaunching(); void DidFinishLaunching(); - // Called when client certificate is required. - void ClientCertificateSelector( - content::WebContents* web_contents, - net::SSLCertRequestInfo* cert_request_info, - scoped_ptr delegate); + // Request basic auth login. + void RequestLogin(LoginHandler* login_handler); void AddObserver(BrowserObserver* obs) { observers_.AddObserver(obs); @@ -130,6 +137,7 @@ class Browser : public WindowListObserver { observers_.RemoveObserver(obs); } + bool is_shutting_down() const { return is_shutdown_; } bool is_quiting() const { return is_quiting_; } bool is_ready() const { return is_ready_; } @@ -159,6 +167,9 @@ class Browser : public WindowListObserver { // Whether "ready" event has been emitted. bool is_ready_; + // The browse is being shutdown. + bool is_shutdown_; + std::string version_override_; std::string name_override_; diff --git a/atom/browser/browser_linux.cc b/atom/browser/browser_linux.cc index ea8fb7c10c5d..25cb9a0a2385 100644 --- a/atom/browser/browser_linux.cc +++ b/atom/browser/browser_linux.cc @@ -31,6 +31,9 @@ void Browser::AddRecentDocument(const base::FilePath& path) { void Browser::ClearRecentDocuments() { } +void Browser::SetAppUserModelID(const base::string16& name) { +} + std::string Browser::GetExecutableFileVersion() const { return brightray::GetApplicationVersion(); } diff --git a/atom/browser/browser_mac.mm b/atom/browser/browser_mac.mm index 2353aa6c42c3..6589057c2c6c 100644 --- a/atom/browser/browser_mac.mm +++ b/atom/browser/browser_mac.mm @@ -26,6 +26,9 @@ void Browser::AddRecentDocument(const base::FilePath& path) { void Browser::ClearRecentDocuments() { } +void Browser::SetAppUserModelID(const base::string16& name) { +} + std::string Browser::GetExecutableFileVersion() const { return brightray::GetApplicationVersion(); } diff --git a/atom/browser/browser_observer.h b/atom/browser/browser_observer.h index 45e86e620f84..f6d76bc13fb3 100644 --- a/atom/browser/browser_observer.h +++ b/atom/browser/browser_observer.h @@ -7,19 +7,10 @@ #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 LoginHandler; + class BrowserObserver { public: // The browser is about to close all windows. @@ -51,11 +42,8 @@ class BrowserObserver { 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) {} + // The browser requests HTTP login. + virtual void OnLogin(LoginHandler* login_handler) {} protected: virtual ~BrowserObserver() {} diff --git a/atom/browser/browser_win.cc b/atom/browser/browser_win.cc index b861af945421..fdf4bd8c3bbe 100644 --- a/atom/browser/browser_win.cc +++ b/atom/browser/browser_win.cc @@ -15,6 +15,7 @@ #include "base/files/file_path.h" #include "base/memory/scoped_ptr.h" #include "base/path_service.h" +#include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/win/win_util.h" @@ -25,6 +26,8 @@ namespace atom { namespace { +const wchar_t kAppUserModelIDFormat[] = L"electron.app.$1"; + BOOL CALLBACK WindowsEnumerationHandler(HWND hwnd, LPARAM param) { DWORD target_process_id = *reinterpret_cast(param); DWORD process_id = 0; @@ -56,7 +59,7 @@ void Browser::AddRecentDocument(const base::FilePath& path) { if (SUCCEEDED(hr)) { SHARDAPPIDINFO info; info.psi = item; - info.pszAppID = app_user_model_id_.c_str(); + info.pszAppID = GetAppUserModelID(); SHAddToRecentDocs(SHARD_APPIDINFO, &info); } } @@ -66,16 +69,21 @@ void Browser::ClearRecentDocuments() { if (FAILED(destinations.CoCreateInstance(CLSID_ApplicationDestinations, NULL, CLSCTX_INPROC_SERVER))) return; - if (FAILED(destinations->SetAppID(app_user_model_id_.c_str()))) + if (FAILED(destinations->SetAppID(GetAppUserModelID()))) return; destinations->RemoveAllDestinations(); } +void Browser::SetAppUserModelID(const base::string16& name) { + app_user_model_id_ = name; + SetCurrentProcessExplicitAppUserModelID(app_user_model_id_.c_str()); +} + void Browser::SetUserTasks(const std::vector& tasks) { CComPtr destinations; if (FAILED(destinations.CoCreateInstance(CLSID_DestinationList))) return; - if (FAILED(destinations->SetAppID(app_user_model_id_.c_str()))) + if (FAILED(destinations->SetAppID(GetAppUserModelID()))) return; // Start a transaction that updates the JumpList of this application. @@ -117,10 +125,13 @@ void Browser::SetUserTasks(const std::vector& tasks) { destinations->CommitList(); } -void Browser::SetAppUserModelID(const std::string& name) { - app_user_model_id_ = base::string16(L"electron.app."); - app_user_model_id_ += base::UTF8ToUTF16(name); - SetCurrentProcessExplicitAppUserModelID(app_user_model_id_.c_str()); +PCWSTR Browser::GetAppUserModelID() { + if (app_user_model_id_.empty()) { + SetAppUserModelID(base::ReplaceStringPlaceholders( + kAppUserModelIDFormat, base::UTF8ToUTF16(GetName()), nullptr)); + } + + return app_user_model_id_.c_str(); } std::string Browser::GetExecutableFileVersion() const { diff --git a/atom/browser/common_web_contents_delegate.cc b/atom/browser/common_web_contents_delegate.cc index 3cef7c6d68cc..72a664f8cd00 100644 --- a/atom/browser/common_web_contents_delegate.cc +++ b/atom/browser/common_web_contents_delegate.cc @@ -21,6 +21,14 @@ #include "content/public/browser/render_view_host.h" #include "storage/browser/fileapi/isolated_context.h" +#if defined(TOOLKIT_VIEWS) +#include "atom/browser/native_window_views.h" +#endif + +#if defined(USE_X11) +#include "atom/browser/browser.h" +#endif + using content::BrowserThread; namespace atom { @@ -128,7 +136,11 @@ void CommonWebContentsDelegate::InitWithWebContents( } void CommonWebContentsDelegate::SetOwnerWindow(NativeWindow* owner_window) { - content::WebContents* web_contents = GetWebContents(); + SetOwnerWindow(GetWebContents(), owner_window); +} + +void CommonWebContentsDelegate::SetOwnerWindow( + content::WebContents* web_contents, NativeWindow* owner_window) { owner_window_ = owner_window->GetWeakPtr(); NativeWindowRelay* relay = new NativeWindowRelay(owner_window_); web_contents->SetUserData(relay->key, relay); @@ -355,6 +367,23 @@ void CommonWebContentsDelegate::OnDevToolsAppendToFile( "DevToolsAPI.appendedToURL", &url_value, nullptr, nullptr); } +#if defined(TOOLKIT_VIEWS) +gfx::ImageSkia CommonWebContentsDelegate::GetDevToolsWindowIcon() { + if (!owner_window()) + return gfx::ImageSkia(); + return static_cast(static_cast( + owner_window()))->GetWindowAppIcon(); +} +#endif + +#if defined(USE_X11) +void CommonWebContentsDelegate::GetDevToolsWindowWMClass( + std::string* name, std::string* class_name) { + *class_name = Browser::Get()->GetName(); + *name = base::ToLowerASCII(*class_name); +} +#endif + void CommonWebContentsDelegate::SetHtmlApiFullscreen(bool enter_fullscreen) { // Window is already in fullscreen mode, save the state. if (enter_fullscreen && owner_window_->IsFullscreen()) { diff --git a/atom/browser/common_web_contents_delegate.h b/atom/browser/common_web_contents_delegate.h index 495b5501a0d1..ee18f36660ee 100644 --- a/atom/browser/common_web_contents_delegate.h +++ b/atom/browser/common_web_contents_delegate.h @@ -12,6 +12,7 @@ #include "brightray/browser/default_web_contents_delegate.h" #include "brightray/browser/inspectable_web_contents_impl.h" #include "brightray/browser/inspectable_web_contents_delegate.h" +#include "brightray/browser/inspectable_web_contents_view_delegate.h" namespace atom { @@ -21,7 +22,8 @@ class WebDialogHelper; class CommonWebContentsDelegate : public brightray::DefaultWebContentsDelegate, - public brightray::InspectableWebContentsDelegate { + public brightray::InspectableWebContentsDelegate, + public brightray::InspectableWebContentsViewDelegate { public: CommonWebContentsDelegate(); virtual ~CommonWebContentsDelegate(); @@ -32,6 +34,8 @@ class CommonWebContentsDelegate // Set the window as owner window. void SetOwnerWindow(NativeWindow* owner_window); + void SetOwnerWindow(content::WebContents* web_contents, + NativeWindow* owner_window); // Destroy the managed InspectableWebContents object. void DestroyWebContents(); @@ -86,6 +90,15 @@ class CommonWebContentsDelegate void DevToolsRemoveFileSystem( const base::FilePath& file_system_path) override; + // brightray::InspectableWebContentsViewDelegate: +#if defined(TOOLKIT_VIEWS) + gfx::ImageSkia GetDevToolsWindowIcon() override; +#endif +#if defined(USE_X11) + void GetDevToolsWindowWMClass( + std::string* name, std::string* class_name) override; +#endif + private: // Callback for when DevToolsSaveToFile has completed. void OnDevToolsSaveToFile(const std::string& url); diff --git a/atom/browser/default_app/default_app.js b/atom/browser/default_app/default_app.js index 2378902b44ec..2ec765d0d691 100644 --- a/atom/browser/default_app/default_app.js +++ b/atom/browser/default_app/default_app.js @@ -1,5 +1,6 @@ -var app = require('app'); -var BrowserWindow = require('browser-window'); +const electron = require('electron'); +const app = electron.app; +const BrowserWindow = electron.BrowserWindow; var mainWindow = null; @@ -12,9 +13,9 @@ app.on('ready', function() { mainWindow = new BrowserWindow({ width: 800, height: 600, - 'auto-hide-menu-bar': true, - 'use-content-size': true, + autoHideMenuBar: true, + useContentSize: true, }); - mainWindow.loadUrl('file://' + __dirname + '/index.html'); + mainWindow.loadURL('file://' + __dirname + '/index.html'); mainWindow.focus(); }); diff --git a/atom/browser/default_app/index.html b/atom/browser/default_app/index.html index 96e45806d5dd..ec16a38bc423 100644 --- a/atom/browser/default_app/index.html +++ b/atom/browser/default_app/index.html @@ -57,13 +57,17 @@ -

Welcome to Electron

+

+ +

To run your app with Electron, execute the following command under your @@ -83,8 +91,18 @@

The path-to-your-app should be the path to your own Electron - app, you can read the quick start - guide in Electron's docs + app, you can read the + + guide in Electron's + on how to write one.

diff --git a/atom/browser/default_app/main.js b/atom/browser/default_app/main.js index 4d854dc35cbc..c325aed02037 100644 --- a/atom/browser/default_app/main.js +++ b/atom/browser/default_app/main.js @@ -1,9 +1,11 @@ -var app = require('app'); -var dialog = require('dialog'); +const electron = require('electron'); +const app = electron.app; +const dialog = electron.dialog; +const shell = electron.shell; +const Menu = electron.Menu; + var fs = require('fs'); var path = require('path'); -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() { @@ -13,16 +15,22 @@ app.on('window-all-closed', function() { // Parse command line options. var argv = process.argv.slice(1); -var option = { file: null, help: null, version: null, webdriver: null }; -for (var i in argv) { +var option = { file: null, help: null, version: null, webdriver: null, modules: [] }; +for (var i = 0; i < argv.length; i++) { if (argv[i] == '--version' || argv[i] == '-v') { option.version = true; break; + } else if (argv[i].match(/^--app=/)) { + option.file = argv[i].split('=')[1]; + break; } else if (argv[i] == '--help' || argv[i] == '-h') { option.help = true; break; } else if (argv[i] == '--test-type=webdriver') { option.webdriver = true; + } else if (argv[i] == '--require' || argv[i] == '-r') { + option.modules.push(argv[++i]); + continue; } else if (argv[i][0] == '-') { continue; } else { @@ -136,19 +144,23 @@ app.once('ready', function() { submenu: [ { label: 'Learn More', - click: function() { require('shell').openExternal('http://electron.atom.io') } + click: function() { shell.openExternal('http://electron.atom.io') } }, { label: 'Documentation', - click: function() { require('shell').openExternal('https://github.com/atom/electron/tree/master/docs#readme') } + click: function() { + shell.openExternal( + `https://github.com/atom/electron/tree/v${process.versions.electron}/docs#readme` + ) + } }, { label: 'Community Discussions', - click: function() { require('shell').openExternal('https://discuss.atom.io/c/electron') } + click: function() { shell.openExternal('https://discuss.atom.io/c/electron') } }, { label: 'Search Issues', - click: function() { require('shell').openExternal('https://github.com/atom/electron/issues') } + click: function() { shell.openExternal('https://github.com/atom/electron/issues') } } ] }, @@ -181,11 +193,11 @@ app.once('ready', function() { { label: 'Hide Others', accelerator: 'Command+Shift+H', - role: 'hideothers:' + role: 'hideothers' }, { label: 'Show All', - role: 'unhide:' + role: 'unhide' }, { type: 'separator' @@ -212,6 +224,10 @@ app.once('ready', function() { Menu.setApplicationMenu(menu); }); +if (option.modules.length > 0) { + require('module')._preloadModules(option.modules); +} + // Start the specified app if there is one specified in command line, otherwise // start the default app. if (option.file && !option.webdriver) { @@ -237,7 +253,11 @@ 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\n\n' + e.toString()); + dialog.showErrorBox( + 'Error opening app', + 'The app provided is not a valid Electron app, please read the docs on how to write one:\n' + + `https://github.com/atom/electron/tree/v${process.versions.electron}/docs\n\n${e.toString()}` + ); process.exit(1); } else { console.error('App threw an error when running', e); @@ -253,10 +273,11 @@ if (option.file && !option.webdriver) { helpMessage += "A path to an Electron application may be specified. The path must be to \n"; helpMessage += "an index.js file or to a folder containing a package.json or index.js file.\n\n"; helpMessage += "Options:\n"; + helpMessage += " -r, --require Module to preload (option can be repeated)\n"; helpMessage += " -h, --help Print this usage message.\n"; helpMessage += " -v, --version Print the version."; console.log(helpMessage); process.exit(0); } else { - require('./default_app.js'); + require('./default_app'); } diff --git a/atom/browser/javascript_environment.cc b/atom/browser/javascript_environment.cc index 3788fcad8b68..dc27cedee56f 100644 --- a/atom/browser/javascript_environment.cc +++ b/atom/browser/javascript_environment.cc @@ -4,6 +4,10 @@ #include "atom/browser/javascript_environment.h" +#include + +#include "base/command_line.h" +#include "content/public/common/content_switches.h" #include "gin/array_buffer.h" #include "gin/v8_initializer.h" @@ -20,7 +24,18 @@ JavascriptEnvironment::JavascriptEnvironment() } bool JavascriptEnvironment::Initialize() { - gin::V8Initializer::LoadV8Snapshot(); + auto cmd = base::CommandLine::ForCurrentProcess(); + if (cmd->HasSwitch("debug-brk")) { + // Need to be called before v8::Initialize(). + const char expose_debug_as[] = "--expose_debug_as=v8debug"; + v8::V8::SetFlagsFromString(expose_debug_as, sizeof(expose_debug_as) - 1); + } + + // --js-flags. + std::string js_flags = cmd->GetSwitchValueASCII(switches::kJavaScriptFlags); + if (!js_flags.empty()) + v8::V8::SetFlagsFromString(js_flags.c_str(), js_flags.size()); + gin::IsolateHolder::Initialize(gin::IsolateHolder::kNonStrictMode, gin::ArrayBufferAllocator::SharedInstance()); return true; diff --git a/atom/browser/lib/chrome-extension.coffee b/atom/browser/lib/chrome-extension.coffee index 15f7bfd54c56..931b8168961e 100644 --- a/atom/browser/lib/chrome-extension.coffee +++ b/atom/browser/lib/chrome-extension.coffee @@ -1,4 +1,4 @@ -app = require 'app' +electron = require 'electron' fs = require 'fs' path = require 'path' url = require 'url' @@ -40,6 +40,7 @@ loadedExtensions = null loadedExtensionsPath = null # Persistent loaded extensions. +{app} = electron app.on 'will-quit', -> try loadedExtensions = Object.keys(extensionInfoMap).map (key) -> extensionInfoMap[key].srcDirectory @@ -51,11 +52,10 @@ app.on 'will-quit', -> # We can not use protocol or BrowserWindow until app is ready. app.once 'ready', -> - protocol = require 'protocol' - BrowserWindow = require 'browser-window' + {protocol, BrowserWindow} = electron # Load persistented extensions. - loadedExtensionsPath = path.join app.getDataPath(), 'DevTools Extensions' + loadedExtensionsPath = path.join app.getPath('userData'), 'DevTools Extensions' try loadedExtensions = JSON.parse fs.readFileSync(loadedExtensionsPath) diff --git a/atom/browser/lib/guest-view-manager.coffee b/atom/browser/lib/guest-view-manager.coffee index 455e969812f7..d4bf55158fed 100644 --- a/atom/browser/lib/guest-view-manager.coffee +++ b/atom/browser/lib/guest-view-manager.coffee @@ -1,5 +1,5 @@ -ipc = require 'ipc' -webContents = require 'web-contents' +{ipcMain, webContents} = require 'electron' + webViewManager = null # Doesn't exist in early initialization. supportedWebViewEvents = [ @@ -19,7 +19,7 @@ supportedWebViewEvents = [ 'gpu-crashed' 'plugin-crashed' 'destroyed' - 'page-title-set' + 'page-title-updated' 'page-favicon-updated' 'enter-html-full-screen' 'leave-html-full-screen' @@ -79,7 +79,7 @@ createGuest = (embedder, params) -> opts = {} opts.httpReferrer = params.httpreferrer if params.httpreferrer opts.userAgent = params.useragent if params.useragent - @loadUrl params.src, opts + @loadURL params.src, opts if params.allowtransparency? @setAllowTransparency params.allowtransparency @@ -118,11 +118,11 @@ attachGuest = (embedder, elementInstanceId, guestInstanceId, params) -> destroyGuest embedder, oldGuestInstanceId 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 + guestInstanceId: guestInstanceId + nodeIntegration: params.nodeintegration ? false + plugins: params.plugins + webSecurity: !params.disablewebsecurity + webPreferences.preloadURL = params.preload if params.preload webViewManager.addGuest guestInstanceId, elementInstanceId, embedder, guest, webPreferences guest.attachParams = params @@ -140,19 +140,19 @@ destroyGuest = (embedder, id) -> delete reverseEmbedderElementsMap[id] delete embedderElementsMap[key] -ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', (event, params, requestId) -> +ipcMain.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) -> +ipcMain.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_ATTACH_GUEST', (event, elementInstanceId, guestInstanceId, params) -> attachGuest event.sender, elementInstanceId, guestInstanceId, params -ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', (event, id) -> +ipcMain.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', (event, id) -> destroyGuest event.sender, id -ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_SIZE', (event, id, params) -> +ipcMain.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) -> +ipcMain.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_ALLOW_TRANSPARENCY', (event, id, allowtransparency) -> guestInstances[id]?.guest.setAllowTransparency allowtransparency # Returns WebContents from its guest id. diff --git a/atom/browser/lib/guest-window-manager.coffee b/atom/browser/lib/guest-window-manager.coffee index 9a5c0ca349ae..53bbb735b044 100644 --- a/atom/browser/lib/guest-window-manager.coffee +++ b/atom/browser/lib/guest-window-manager.coffee @@ -1,29 +1,37 @@ -ipc = require 'ipc' +{ipcMain, BrowserWindow} = require 'electron' v8Util = process.atomBinding 'v8_util' -BrowserWindow = require 'browser-window' frameToGuest = {} +# Copy attribute of |parent| to |child| if it is not defined in |child|. +mergeOptions = (child, parent) -> + for own key, value of parent when key not of child + if typeof value is 'object' + child[key] = mergeOptions {}, value + else + child[key] = value + child + # Merge |options| with the |embedder|'s window's options. mergeBrowserWindowOptions = (embedder, options) -> if embedder.browserWindowOptions? # Inherit the original options if it is a BrowserWindow. - options.__proto__ = embedder.browserWindowOptions + mergeOptions options, embedder.browserWindowOptions else # Or only inherit web-preferences if it is a webview. - options['web-preferences'] ?= {} - options['web-preferences'].__proto__ = embedder.getWebPreferences() + options.webPreferences ?= {} + mergeOptions options.webPreferences, embedder.getWebPreferences() options # Create a new guest created by |embedder| with |options|. createGuest = (embedder, url, frameName, options) -> guest = frameToGuest[frameName] if frameName and guest? - guest.loadUrl url + guest.loadURL url return guest.id guest = new BrowserWindow(options) - guest.loadUrl url + guest.loadURL url # Remember the embedder, will be used by window.opener methods. v8Util.setHiddenValue guest.webContents, 'embedder', embedder @@ -31,11 +39,12 @@ createGuest = (embedder, url, frameName, options) -> # When |embedder| is destroyed we should also destroy attached guest, and if # guest is closed by user then we should prevent |embedder| from double # closing guest. + guestId = guest.id closedByEmbedder = -> guest.removeListener 'closed', closedByUser guest.destroy() closedByUser = -> - embedder.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED', guest.id + embedder.send "ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_#{guestId}" embedder.removeListener 'render-view-deleted', closedByEmbedder embedder.once 'render-view-deleted', closedByEmbedder guest.once 'closed', closedByUser @@ -49,7 +58,7 @@ createGuest = (embedder, url, frameName, options) -> guest.id # Routed window.open messages. -ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, args...) -> +ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, args...) -> [url, frameName, options] = args options = mergeBrowserWindowOptions event.sender, options event.sender.emit 'new-window', event, url, frameName, 'new-window', options @@ -58,26 +67,26 @@ ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, args...) -> else event.returnValue = createGuest event.sender, url, frameName, options -ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', (event, guestId) -> +ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', (event, guestId) -> BrowserWindow.fromId(guestId)?.destroy() -ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', (event, guestId, method, args...) -> +ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', (event, guestId, method, args...) -> BrowserWindow.fromId(guestId)?[method] args... -ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestId, message, targetOrigin) -> +ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestId, message, targetOrigin) -> guestContents = BrowserWindow.fromId(guestId)?.webContents - if guestContents?.getUrl().indexOf(targetOrigin) is 0 or targetOrigin is '*' - guestContents.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', message, targetOrigin + if guestContents?.getURL().indexOf(targetOrigin) is 0 or targetOrigin is '*' + guestContents.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', guestId, message, targetOrigin -ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', (event, guestId, message, targetOrigin, sourceOrigin) -> +ipcMain.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 '*' + if embedder?.getURL().indexOf(targetOrigin) is 0 or targetOrigin is '*' embedder.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', guestId, message, sourceOrigin -ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestId, method, args...) -> +ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestId, method, args...) -> BrowserWindow.fromId(guestId)?.webContents?[method] args... -ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_GET_GUEST_ID', (event) -> +ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_GET_GUEST_ID', (event) -> embedder = v8Util.getHiddenValue event.sender, 'embedder' if embedder? guest = BrowserWindow.fromWebContents event.sender diff --git a/atom/browser/lib/init.coffee b/atom/browser/lib/init.coffee index 6caaaf4bf056..85faf0f038ad 100644 --- a/atom/browser/lib/init.coffee +++ b/atom/browser/lib/init.coffee @@ -7,21 +7,31 @@ Module = require 'module' # we need to restore it here. process.argv.splice 1, 1 -# Add browser/api/lib to module search paths, which contains javascript part of -# Electron's built-in libraries. -globalPaths = Module.globalPaths -globalPaths.push path.resolve(__dirname, '..', 'api', 'lib') +# Clear search paths. +require path.resolve(__dirname, '..', '..', 'common', 'lib', 'reset-search-paths') # Import common settings. require path.resolve(__dirname, '..', '..', 'common', 'lib', 'init') +globalPaths = Module.globalPaths +unless process.env.ELECTRON_HIDE_INTERNAL_MODULES + globalPaths.push path.resolve(__dirname, '..', 'api', 'lib') + +# Expose public APIs. +globalPaths.push path.resolve(__dirname, '..', 'api', 'lib', 'exports') + if process.platform is 'win32' # Redirect node's console to use our own implementations, since node can not # handle console output when running as GUI program. - print = (args...) -> - process.log util.format(args...) - console.log = console.error = console.warn = print - process.stdout.write = process.stderr.write = print + consoleLog = (args...) -> + process.log util.format(args...) + "\n" + streamWrite = (chunk, encoding, callback) -> + chunk = chunk.toString(encoding) if Buffer.isBuffer chunk + process.log chunk + callback() if callback + true + console.log = console.error = console.warn = consoleLog + process.stdout.write = process.stderr.write = streamWrite # Always returns EOF for stdin stream. Readable = require('stream').Readable @@ -36,15 +46,19 @@ process.on 'uncaughtException', (error) -> return # Show error in GUI. + {dialog} = require 'electron' stack = error.stack ? "#{error.name}: #{error.message}" message = "Uncaught Exception:\n#{stack}" - require('dialog').showErrorBox 'A JavaScript error occurred in the main process', message + dialog.showErrorBox 'A JavaScript error occurred in the main process', message # Emit 'exit' event on quit. -app = require 'app' +{app} = require 'electron' app.on 'quit', -> process.emit 'exit' +# Map process.exit to app.exit, which quits gracefully. +process.exit = app.exit + # Load the RPC server. require './rpc-server' @@ -64,7 +78,9 @@ for packagePath in searchPaths catch e continue -throw new Error("Unable to find a valid app") unless packageJson? +unless packageJson? + process.nextTick -> process.exit 1 + throw new Error("Unable to find a valid app") # Set application's version. app.setVersion packageJson.version if packageJson.version? diff --git a/atom/browser/lib/objects-registry.coffee b/atom/browser/lib/objects-registry.coffee index ccfe2dbe0ad2..53b00ed7f82b 100644 --- a/atom/browser/lib/objects-registry.coffee +++ b/atom/browser/lib/objects-registry.coffee @@ -1,4 +1,4 @@ -EventEmitter = require('events').EventEmitter +{EventEmitter} = require 'events' v8Util = process.atomBinding 'v8_util' class ObjectsRegistry extends EventEmitter @@ -34,6 +34,7 @@ class ObjectsRegistry extends EventEmitter @dereference id, 1 # Also reduce the count in owner. pointer = @owners[webContentsId] + return unless pointer? --pointer[id] delete pointer[id] if pointer[id] is 0 @@ -57,6 +58,7 @@ class ObjectsRegistry extends EventEmitter # Private: Dereference the object from store. dereference: (id, count) -> pointer = @storage[id] + return unless pointer? pointer.count -= count if pointer.count is 0 v8Util.deleteHiddenValue pointer.object, 'atomId' diff --git a/atom/browser/lib/rpc-server.coffee b/atom/browser/lib/rpc-server.coffee index 0a28d350e8a9..e388a0fd5754 100644 --- a/atom/browser/lib/rpc-server.coffee +++ b/atom/browser/lib/rpc-server.coffee @@ -1,7 +1,11 @@ -ipc = require 'ipc' path = require 'path' -objectsRegistry = require './objects-registry.js' + +electron = require 'electron' +{ipcMain} = electron +objectsRegistry = require './objects-registry' + v8Util = process.atomBinding 'v8_util' +{IDWeakMap} = process.atomBinding 'id_weak_map' # Convert a real value into meta data. valueToMeta = (sender, value, optimizeSimpleObject=false) -> @@ -10,7 +14,9 @@ valueToMeta = (sender, value, optimizeSimpleObject=false) -> 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' + meta.type = 'error' if value instanceof Error + meta.type = 'date' if value instanceof Date + meta.type = 'promise' if value?.constructor.name is 'Promise' # Treat simple objects as value. if optimizeSimpleObject and meta.type is 'object' and v8Util.getHiddenValue value, 'simple' @@ -30,21 +36,28 @@ valueToMeta = (sender, value, optimizeSimpleObject=false) -> # it. meta.id = objectsRegistry.add sender.getId(), value - meta.members = [] - meta.members.push {name: prop, type: typeof field} for prop, field of value + meta.members = ({name, type: typeof field} for name, 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)) + meta.then = valueToMeta sender, value.then.bind(value) + else if meta.type is 'error' + meta.members = plainObjectToMeta value + else if meta.type is 'date' + meta.value = value.getTime() else meta.type = 'value' meta.value = value meta +# Convert object to meta by value. +plainObjectToMeta = (obj) -> + Object.getOwnPropertyNames(obj).map (name) -> {name, value: obj[name]} + # Convert Error into meta data. -errorToMeta = (error) -> - type: 'error', message: error.message, stack: (error.stack || error) +exceptionToMeta = (error) -> + type: 'exception', message: error.message, stack: (error.stack || error) # Convert array of meta data from renderer into array of real values. unwrapArgs = (sender, args) -> @@ -64,16 +77,27 @@ unwrapArgs = (sender, args) -> returnValue = metaToValue meta.value -> returnValue when 'function' + # Cache the callbacks in renderer. + unless sender.callbacks + sender.callbacks = new IDWeakMap + sender.on 'render-view-deleted', -> + sender.callbacks.clear() + return sender.callbacks.get meta.id if sender.callbacks.has meta.id + rendererReleased = false objectsRegistry.once "clear-#{sender.getId()}", -> rendererReleased = true ret = -> - throw new Error('Calling a callback of released renderer view') if rendererReleased + if rendererReleased + throw new Error("Attempting to call a function in a renderer window + that has been closed or released. Function provided here: #{meta.location}.") sender.send 'ATOM_RENDERER_CALLBACK', meta.id, valueToMeta(sender, arguments) v8Util.setDestructor ret, -> return if rendererReleased + sender.callbacks.remove meta.id sender.send 'ATOM_RENDERER_RELEASE_CALLBACK', meta.id + sender.callbacks.set meta.id, ret ret else throw new TypeError("Unknown type: #{meta.type}") @@ -82,47 +106,58 @@ unwrapArgs = (sender, args) -> # Call a function and send reply asynchronously if it's a an asynchronous # style function and the caller didn't pass a callback. callFunction = (event, func, caller, args) -> - if v8Util.getHiddenValue(func, 'asynchronous') and typeof args[args.length - 1] isnt 'function' - args.push (ret) -> + funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous') + funcPassedCallback = typeof args[args.length - 1] is 'function' + + try + if funcMarkedAsync and not funcPassedCallback + args.push (ret) -> + event.returnValue = valueToMeta event.sender, ret, true + func.apply caller, args + else + ret = func.apply caller, args event.returnValue = valueToMeta event.sender, ret, true - func.apply caller, args - else - ret = func.apply caller, args - event.returnValue = valueToMeta event.sender, ret, true + catch e + # Catch functions thrown further down in function invocation and wrap + # them with the function name so it's easier to trace things like + # `Error processing argument -1.` + funcName = func.name ? "anonymous" + throw new Error("Could not call remote function `#{funcName}`. + Check that the function signature is correct. + Underlying error: #{e.message}") # Send by BrowserWindow when its render view is deleted. process.on 'ATOM_BROWSER_RELEASE_RENDER_VIEW', (id) -> objectsRegistry.clear id -ipc.on 'ATOM_BROWSER_REQUIRE', (event, module) -> +ipcMain.on 'ATOM_BROWSER_REQUIRE', (event, module) -> try event.returnValue = valueToMeta event.sender, process.mainModule.require(module) catch e - event.returnValue = errorToMeta e + event.returnValue = exceptionToMeta e -ipc.on 'ATOM_BROWSER_GLOBAL', (event, name) -> +ipcMain.on 'ATOM_BROWSER_GET_BUILTIN', (event, module) -> + try + event.returnValue = valueToMeta event.sender, electron[module] + catch e + event.returnValue = exceptionToMeta e + +ipcMain.on 'ATOM_BROWSER_GLOBAL', (event, name) -> try event.returnValue = valueToMeta event.sender, global[name] catch e - event.returnValue = errorToMeta e + event.returnValue = exceptionToMeta e -ipc.on 'ATOM_BROWSER_CURRENT_WINDOW', (event, guestInstanceId) -> +ipcMain.on 'ATOM_BROWSER_CURRENT_WINDOW', (event) -> try - BrowserWindow = require 'browser-window' - if guestInstanceId? - guestViewManager = require './guest-view-manager' - window = BrowserWindow.fromWebContents guestViewManager.getEmbedder(guestInstanceId) - else - window = BrowserWindow.fromWebContents event.sender - window = BrowserWindow.fromDevToolsWebContents event.sender unless window? - event.returnValue = valueToMeta event.sender, window + event.returnValue = valueToMeta event.sender, event.sender.getOwnerBrowserWindow() catch e - event.returnValue = errorToMeta e + event.returnValue = exceptionToMeta e -ipc.on 'ATOM_BROWSER_CURRENT_WEB_CONTENTS', (event) -> +ipcMain.on 'ATOM_BROWSER_CURRENT_WEB_CONTENTS', (event) -> event.returnValue = valueToMeta event.sender, event.sender -ipc.on 'ATOM_BROWSER_CONSTRUCTOR', (event, id, args) -> +ipcMain.on 'ATOM_BROWSER_CONSTRUCTOR', (event, id, args) -> try args = unwrapArgs event.sender, args constructor = objectsRegistry.get id @@ -131,17 +166,17 @@ ipc.on 'ATOM_BROWSER_CONSTRUCTOR', (event, id, args) -> obj = new (Function::bind.apply(constructor, [null].concat(args))) event.returnValue = valueToMeta event.sender, obj catch e - event.returnValue = errorToMeta e + event.returnValue = exceptionToMeta e -ipc.on 'ATOM_BROWSER_FUNCTION_CALL', (event, id, args) -> +ipcMain.on 'ATOM_BROWSER_FUNCTION_CALL', (event, id, args) -> try args = unwrapArgs event.sender, args func = objectsRegistry.get id callFunction event, func, global, args catch e - event.returnValue = errorToMeta e + event.returnValue = exceptionToMeta e -ipc.on 'ATOM_BROWSER_MEMBER_CONSTRUCTOR', (event, id, method, args) -> +ipcMain.on 'ATOM_BROWSER_MEMBER_CONSTRUCTOR', (event, id, method, args) -> try args = unwrapArgs event.sender, args constructor = objectsRegistry.get(id)[method] @@ -149,37 +184,48 @@ ipc.on 'ATOM_BROWSER_MEMBER_CONSTRUCTOR', (event, id, method, args) -> obj = new (Function::bind.apply(constructor, [null].concat(args))) event.returnValue = valueToMeta event.sender, obj catch e - event.returnValue = errorToMeta e + event.returnValue = exceptionToMeta e -ipc.on 'ATOM_BROWSER_MEMBER_CALL', (event, id, method, args) -> +ipcMain.on 'ATOM_BROWSER_MEMBER_CALL', (event, id, method, args) -> try args = unwrapArgs event.sender, args obj = objectsRegistry.get id callFunction event, obj[method], obj, args catch e - event.returnValue = errorToMeta e + event.returnValue = exceptionToMeta e -ipc.on 'ATOM_BROWSER_MEMBER_SET', (event, id, name, value) -> +ipcMain.on 'ATOM_BROWSER_MEMBER_SET', (event, id, name, value) -> try obj = objectsRegistry.get id obj[name] = value event.returnValue = null catch e - event.returnValue = errorToMeta e + event.returnValue = exceptionToMeta e -ipc.on 'ATOM_BROWSER_MEMBER_GET', (event, id, name) -> +ipcMain.on 'ATOM_BROWSER_MEMBER_GET', (event, id, name) -> try obj = objectsRegistry.get id event.returnValue = valueToMeta event.sender, obj[name] catch e - event.returnValue = errorToMeta e + event.returnValue = exceptionToMeta e -ipc.on 'ATOM_BROWSER_DEREFERENCE', (event, id) -> +ipcMain.on 'ATOM_BROWSER_DEREFERENCE', (event, id) -> objectsRegistry.remove event.sender.getId(), id -ipc.on 'ATOM_BROWSER_GUEST_WEB_CONTENTS', (event, guestInstanceId) -> +ipcMain.on 'ATOM_BROWSER_GUEST_WEB_CONTENTS', (event, guestInstanceId) -> try guestViewManager = require './guest-view-manager' event.returnValue = valueToMeta event.sender, guestViewManager.getGuest(guestInstanceId) catch e - event.returnValue = errorToMeta e + event.returnValue = exceptionToMeta e + +ipcMain.on 'ATOM_BROWSER_LIST_MODULES', (event) -> + event.returnValue = (name for name of electron) + +ipcMain.on 'ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', (event, guestInstanceId, method, args...) -> + try + guestViewManager = require './guest-view-manager' + guest = guestViewManager.getGuest(guestInstanceId) + guest[method].apply(guest, args) + catch e + event.returnValue = exceptionToMeta e diff --git a/atom/browser/login_handler.cc b/atom/browser/login_handler.cc new file mode 100644 index 000000000000..7a1a77cc2b13 --- /dev/null +++ b/atom/browser/login_handler.cc @@ -0,0 +1,109 @@ +// 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/login_handler.h" + +#include "atom/browser/browser.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/resource_dispatcher_host.h" +#include "content/public/browser/resource_request_info.h" +#include "content/public/browser/web_contents.h" +#include "net/base/auth.h" +#include "net/url_request/url_request.h" + +using content::BrowserThread; + +namespace atom { + +namespace { + +// Helper to remove the ref from an net::URLRequest to the LoginHandler. +// Should only be called from the IO thread, since it accesses an +// net::URLRequest. +void ResetLoginHandlerForRequest(net::URLRequest* request) { + content::ResourceDispatcherHost::Get()->ClearLoginDelegateForRequest(request); +} + +} // namespace + +LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info, + net::URLRequest* request) + : handled_auth_(false), + auth_info_(auth_info), + request_(request), + render_process_host_id_(0), + render_frame_id_(0) { + content::ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderFrame( + &render_process_host_id_, &render_frame_id_); + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(&Browser::RequestLogin, + base::Unretained(Browser::Get()), + make_scoped_refptr(this))); +} + +LoginHandler::~LoginHandler() { +} + +content::WebContents* LoginHandler::GetWebContents() const { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + content::RenderFrameHost* rfh = content::RenderFrameHost::FromID( + render_process_host_id_, render_frame_id_); + return content::WebContents::FromRenderFrameHost(rfh); +} + +void LoginHandler::Login(const base::string16& username, + const base::string16& password) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (TestAndSetAuthHandled()) + return; + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&LoginHandler::DoLogin, this, username, password)); +} + +void LoginHandler::CancelAuth() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (TestAndSetAuthHandled()) + return; + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&LoginHandler::DoCancelAuth, this)); +} + +void LoginHandler::OnRequestCancelled() { + TestAndSetAuthHandled(); + request_ = nullptr; +} + +// Marks authentication as handled and returns the previous handled state. +bool LoginHandler::TestAndSetAuthHandled() { + base::AutoLock lock(handled_auth_lock_); + bool was_handled = handled_auth_; + handled_auth_ = true; + return was_handled; +} + +void LoginHandler::DoCancelAuth() { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + if (request_) { + request_->CancelAuth(); + // Verify that CancelAuth doesn't destroy the request via our delegate. + DCHECK(request_ != nullptr); + ResetLoginHandlerForRequest(request_); + } +} + +void LoginHandler::DoLogin(const base::string16& username, + const base::string16& password) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + if (request_) { + request_->SetAuth(net::AuthCredentials(username, password)); + ResetLoginHandlerForRequest(request_); + } +} + +} // namespace atom diff --git a/atom/browser/login_handler.h b/atom/browser/login_handler.h new file mode 100644 index 000000000000..52ec1abf5b1a --- /dev/null +++ b/atom/browser/login_handler.h @@ -0,0 +1,76 @@ +// 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_LOGIN_HANDLER_H_ +#define ATOM_BROWSER_LOGIN_HANDLER_H_ + +#include "base/strings/string16.h" +#include "base/synchronization/lock.h" +#include "content/public/browser/resource_dispatcher_host_login_delegate.h" + +namespace content { +class WebContents; +} + +namespace net { +class AuthChallengeInfo; +class URLRequest; +} + +namespace atom { + +// Handles the HTTP basic auth, must be created on IO thread. +class LoginHandler : public content::ResourceDispatcherHostLoginDelegate { + public: + LoginHandler(net::AuthChallengeInfo* auth_info, net::URLRequest* request); + + // Returns the WebContents associated with the request, must be called on UI + // thread. + content::WebContents* GetWebContents() const; + + // The auth is cancelled, must be called on UI thread. + void CancelAuth(); + + // Login with |username| and |password|, must be called on UI thread. + void Login(const base::string16& username, const base::string16& password); + + const net::AuthChallengeInfo* auth_info() const { return auth_info_.get(); } + const net::URLRequest* request() const { return request_; } + + protected: + ~LoginHandler() override; + + // content::ResourceDispatcherHostLoginDelegate: + void OnRequestCancelled() override; + + private: + // Must be called on IO thread. + void DoCancelAuth(); + void DoLogin(const base::string16& username, const base::string16& password); + + // Marks authentication as handled and returns the previous handled + // state. + bool TestAndSetAuthHandled(); + + // True if we've handled auth (Login or CancelAuth has been called). + bool handled_auth_; + mutable base::Lock handled_auth_lock_; + + // Who/where/what asked for the authentication. + scoped_refptr auth_info_; + + // The request that wants login data. + // This should only be accessed on the IO loop. + net::URLRequest* request_; + + // Cached from the net::URLRequest, in case it goes NULL on us. + int render_process_host_id_; + int render_frame_id_; + + DISALLOW_COPY_AND_ASSIGN(LoginHandler); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_LOGIN_HANDLER_H_ diff --git a/atom/browser/mac/atom_application.mm b/atom/browser/mac/atom_application.mm index 9eaabc410bda..cc9c6accc83d 100644 --- a/atom/browser/mac/atom_application.mm +++ b/atom/browser/mac/atom_application.mm @@ -43,11 +43,20 @@ atom::Browser::Get()->OpenURL(base::SysNSStringToUTF8(url)); } +- (bool)voiceOverEnabled { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + [defaults addSuiteNamed:@"com.apple.universalaccess"]; + [defaults synchronize]; + + return [defaults boolForKey:@"voiceOverOnOffKey"]; +} + - (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute { // Undocumented attribute that VoiceOver happens to set while running. // Chromium uses this too, even though it's not exactly right. if ([attribute isEqualToString:@"AXEnhancedUserInterface"]) { - [self updateAccessibilityEnabled:[value boolValue]]; + bool enableAccessibility = ([self voiceOverEnabled] && [value boolValue]); + [self updateAccessibilityEnabled:enableAccessibility]; } return [super accessibilitySetValue:value forAttribute:attribute]; } diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index e06633d7b7ca..666da2d6b71d 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -43,31 +43,12 @@ DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::NativeWindowRelay); namespace atom { -namespace { - -// 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( brightray::InspectableWebContents* inspectable_web_contents, const mate::Dictionary& options) : content::WebContentsObserver(inspectable_web_contents->GetWebContents()), has_frame_(true), + force_using_draggable_region_(false), transparent_(false), enable_larger_than_screen_(false), is_closed_(false), @@ -75,18 +56,16 @@ NativeWindow::NativeWindow( 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(options::kFrame, &has_frame_); + options.Get(options::kTransparent, &transparent_); + options.Get(options::kEnableLargerThanScreen, &enable_larger_than_screen_); // Tell the content module to initialize renderer widget with transparent // mode. ui::GpuSwitchingManager::SetTransparent(transparent_); // Read icon before window is created. - options.Get(switches::kIcon, &icon_); + options.Get(options::kIcon, &icon_); WindowList::AddWindow(this); } @@ -112,60 +91,67 @@ void NativeWindow::InitFromOptions(const mate::Dictionary& options) { // Setup window from options. int x = -1, y = -1; bool center; - if (options.Get(switches::kX, &x) && options.Get(switches::kY, &y)) { - int width = -1, height = -1; - options.Get(switches::kWidth, &width); - options.Get(switches::kHeight, &height); - SetBounds(gfx::Rect(x, y, width, height)); - } else if (options.Get(switches::kCenter, ¢er) && center) { + if (options.Get(options::kX, &x) && options.Get(options::kY, &y)) { + SetPosition(gfx::Point(x, y)); + } else if (options.Get(options::kCenter, ¢er) && center) { Center(); } + // On Linux and Window we may already have maximum size defined. + extensions::SizeConstraints size_constraints(GetContentSizeConstraints()); int min_height = 0, min_width = 0; - if (options.Get(switches::kMinHeight, &min_height) | - options.Get(switches::kMinWidth, &min_width)) { - SetMinimumSize(gfx::Size(min_width, min_height)); + if (options.Get(options::kMinHeight, &min_height) | + options.Get(options::kMinWidth, &min_width)) { + size_constraints.set_minimum_size(gfx::Size(min_width, min_height)); } int max_height = INT_MAX, max_width = INT_MAX; - if (options.Get(switches::kMaxHeight, &max_height) | - options.Get(switches::kMaxWidth, &max_width)) { - SetMaximumSize(gfx::Size(max_width, max_height)); + if (options.Get(options::kMaxHeight, &max_height) | + options.Get(options::kMaxWidth, &max_width)) { + size_constraints.set_maximum_size(gfx::Size(max_width, max_height)); } + bool use_content_size = false; + options.Get(options::kUseContentSize, &use_content_size); + if (use_content_size) { + SetContentSizeConstraints(size_constraints); + } else { + SetSizeConstraints(size_constraints); + } +#if defined(OS_WIN) || defined(USE_X11) bool resizable; - if (options.Get(switches::kResizable, &resizable)) { + if (options.Get(options::kResizable, &resizable)) { SetResizable(resizable); } +#endif bool top; - if (options.Get(switches::kAlwaysOnTop, &top) && top) { + if (options.Get(options::kAlwaysOnTop, &top) && top) { SetAlwaysOnTop(true); } #if defined(OS_MACOSX) || defined(OS_WIN) bool fullscreen; - if (options.Get(switches::kFullscreen, &fullscreen) && fullscreen) { + if (options.Get(options::kFullscreen, &fullscreen) && fullscreen) { SetFullScreen(true); } #endif bool skip; - if (options.Get(switches::kSkipTaskbar, &skip) && skip) { + if (options.Get(options::kSkipTaskbar, &skip) && skip) { SetSkipTaskbar(skip); } bool kiosk; - if (options.Get(switches::kKiosk, &kiosk) && kiosk) { + if (options.Get(options::kKiosk, &kiosk) && kiosk) { SetKiosk(kiosk); } + std::string color; + if (options.Get(options::kBackgroundColor, &color)) { + SetBackgroundColor(color); + } std::string title("Electron"); - options.Get(switches::kTitle, &title); + options.Get(options::kTitle, &title); SetTitle(title); // Then show it. bool show = true; - options.Get(switches::kShow, &show); - if (show) { + options.Get(options::kShow, &show); + if (show) Show(); - } else { - // When RenderView is created it sets to visible, this is to prevent - // breaking the visibility API. - web_contents()->WasHidden(); - } } void NativeWindow::SetSize(const gfx::Size& size) { @@ -184,6 +170,67 @@ gfx::Point NativeWindow::GetPosition() { return GetBounds().origin(); } +void NativeWindow::SetContentSize(const gfx::Size& size) { + SetSize(ContentSizeToWindowSize(size)); +} + +gfx::Size NativeWindow::GetContentSize() { + return WindowSizeToContentSize(GetSize()); +} + +void NativeWindow::SetSizeConstraints( + const extensions::SizeConstraints& window_constraints) { + extensions::SizeConstraints content_constraints; + if (window_constraints.HasMaximumSize()) + content_constraints.set_maximum_size( + WindowSizeToContentSize(window_constraints.GetMaximumSize())); + if (window_constraints.HasMinimumSize()) + content_constraints.set_minimum_size( + WindowSizeToContentSize(window_constraints.GetMinimumSize())); + SetContentSizeConstraints(content_constraints); +} + +extensions::SizeConstraints NativeWindow::GetSizeConstraints() { + extensions::SizeConstraints content_constraints = GetContentSizeConstraints(); + extensions::SizeConstraints window_constraints; + if (content_constraints.HasMaximumSize()) + window_constraints.set_maximum_size( + ContentSizeToWindowSize(content_constraints.GetMaximumSize())); + if (content_constraints.HasMinimumSize()) + window_constraints.set_minimum_size( + ContentSizeToWindowSize(content_constraints.GetMinimumSize())); + return window_constraints; +} + +void NativeWindow::SetContentSizeConstraints( + const extensions::SizeConstraints& size_constraints) { + size_constraints_ = size_constraints; +} + +extensions::SizeConstraints NativeWindow::GetContentSizeConstraints() { + return size_constraints_; +} + +void NativeWindow::SetMinimumSize(const gfx::Size& size) { + extensions::SizeConstraints size_constraints; + size_constraints.set_minimum_size(size); + SetSizeConstraints(size_constraints); +} + +gfx::Size NativeWindow::GetMinimumSize() { + return GetSizeConstraints().GetMinimumSize(); +} + +void NativeWindow::SetMaximumSize(const gfx::Size& size) { + extensions::SizeConstraints size_constraints; + size_constraints.set_maximum_size(size); + SetSizeConstraints(size_constraints); +} + +gfx::Size NativeWindow::GetMaximumSize() { + return GetSizeConstraints().GetMaximumSize(); +} + void NativeWindow::SetRepresentedFilename(const std::string& filename) { } @@ -244,10 +291,10 @@ void NativeWindow::CapturePage(const gfx::Rect& rect, const float scale = screen->GetDisplayNearestWindow(native_view).device_scale_factor(); if (scale > 1.0f) - bitmap_size = gfx::ToCeiledSize(gfx::ScaleSize(view_size, scale)); + bitmap_size = gfx::ScaleToCeiledSize(view_size, scale); host->CopyFromBackingStore( - rect.IsEmpty() ? gfx::Rect(view_size) : rect, + gfx::Rect(view_size), bitmap_size, base::Bind(&NativeWindow::OnCapturePageDone, weak_factory_.GetWeakPtr(), @@ -417,16 +464,26 @@ void NativeWindow::NotifyWindowExecuteWindowsCommand( OnExecuteWindowsCommand(command)); } -void NativeWindow::DevToolsFocused() { - FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnDevToolsFocus()); +#if defined(OS_WIN) +void NativeWindow::NotifyWindowMessage( + UINT message, WPARAM w_param, LPARAM l_param) { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, + OnWindowMessage(message, w_param, l_param)); } +#endif -void NativeWindow::DevToolsOpened() { - FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnDevToolsOpened()); -} - -void NativeWindow::DevToolsClosed() { - FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnDevToolsClosed()); +scoped_ptr NativeWindow::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(); } void NativeWindow::RenderViewCreated( @@ -448,17 +505,6 @@ void NativeWindow::BeforeUnloadDialogCancelled() { window_unresposive_closure_.Cancel(); } -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) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(NativeWindow, message) @@ -473,7 +519,7 @@ bool NativeWindow::OnMessageReceived(const IPC::Message& message) { void NativeWindow::UpdateDraggableRegions( const std::vector& regions) { // Draggable region is not supported for non-frameless window. - if (has_frame_) + if (has_frame_ && !force_using_draggable_region_) return; draggable_region_ = DraggableRegionsToSkRegion(regions); } diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index e9a2b9433d13..0c918d92df99 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -15,10 +15,11 @@ #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/observer_list.h" -#include "brightray/browser/inspectable_web_contents_view_delegate.h" +#include "base/supports_user_data.h" #include "content/public/browser/readback_types.h" #include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_user_data.h" +#include "extensions/browser/app_window/size_constraints.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_skia.h" @@ -50,8 +51,8 @@ namespace atom { struct DraggableRegion; -class NativeWindow : public content::WebContentsObserver, - public brightray::InspectableWebContentsViewDelegate { +class NativeWindow : public base::SupportsUserData, + public content::WebContentsObserver { public: using CapturePageCallback = base::Callback; @@ -110,12 +111,18 @@ class NativeWindow : public content::WebContentsObserver, virtual gfx::Size GetSize(); virtual void SetPosition(const gfx::Point& position); virtual gfx::Point GetPosition(); - virtual void SetContentSize(const gfx::Size& size) = 0; - virtual gfx::Size GetContentSize() = 0; - virtual void SetMinimumSize(const gfx::Size& size) = 0; - virtual gfx::Size GetMinimumSize() = 0; - virtual void SetMaximumSize(const gfx::Size& size) = 0; - virtual gfx::Size GetMaximumSize() = 0; + virtual void SetContentSize(const gfx::Size& size); + virtual gfx::Size GetContentSize(); + virtual void SetSizeConstraints( + const extensions::SizeConstraints& size_constraints); + virtual extensions::SizeConstraints GetSizeConstraints(); + virtual void SetContentSizeConstraints( + const extensions::SizeConstraints& size_constraints); + virtual extensions::SizeConstraints GetContentSizeConstraints(); + virtual void SetMinimumSize(const gfx::Size& size); + virtual gfx::Size GetMinimumSize(); + virtual void SetMaximumSize(const gfx::Size& size); + virtual gfx::Size GetMaximumSize(); virtual void SetResizable(bool resizable) = 0; virtual bool IsResizable() = 0; virtual void SetAlwaysOnTop(bool top) = 0; @@ -127,6 +134,7 @@ class NativeWindow : public content::WebContentsObserver, virtual void SetSkipTaskbar(bool skip) = 0; virtual void SetKiosk(bool kiosk) = 0; virtual bool IsKiosk() = 0; + virtual void SetBackgroundColor(const std::string& color_name) = 0; virtual void SetRepresentedFilename(const std::string& filename); virtual std::string GetRepresentedFilename(); virtual void SetDocumentEdited(bool edited); @@ -202,6 +210,10 @@ class NativeWindow : public content::WebContentsObserver, void NotifyWindowLeaveHtmlFullScreen(); void NotifyWindowExecuteWindowsCommand(const std::string& command); + #if defined(OS_WIN) + void NotifyWindowMessage(UINT message, WPARAM w_param, LPARAM l_param); + #endif + void AddObserver(NativeWindowObserver* obs) { observers_.AddObserver(obs); } @@ -219,6 +231,13 @@ class NativeWindow : public content::WebContentsObserver, bool enable_larger_than_screen() const { return enable_larger_than_screen_; } gfx::ImageSkia icon() const { return icon_; } + bool force_using_draggable_region() const { + return force_using_draggable_region_; + } + void set_force_using_draggable_region(bool force) { + force_using_draggable_region_ = true; + } + void set_has_dialog_attached(bool has_dialog_attached) { has_dialog_attached_ = has_dialog_attached; } @@ -227,22 +246,25 @@ class NativeWindow : public content::WebContentsObserver, NativeWindow(brightray::InspectableWebContents* inspectable_web_contents, const mate::Dictionary& options); - // brightray::InspectableWebContentsViewDelegate: - void DevToolsFocused() override; - void DevToolsOpened() override; - void DevToolsClosed() override; + // 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); + + // Converts between content size to window size. + virtual gfx::Size ContentSizeToWindowSize(const gfx::Size& size) = 0; + virtual gfx::Size WindowSizeToContentSize(const gfx::Size& size) = 0; + + // Called when the window needs to update its draggable region. + virtual void UpdateDraggableRegions( + const std::vector& regions); // content::WebContentsObserver: void RenderViewCreated(content::RenderViewHost* render_view_host) override; void BeforeUnloadDialogCancelled() override; - void TitleWasSet(content::NavigationEntry* entry, bool explicit_set) override; bool OnMessageReceived(const IPC::Message& message) override; private: - // Called when the window needs to update its draggable region. - void UpdateDraggableRegions( - const std::vector& regions); - // Schedule a notification unresponsive event. void ScheduleUnresponsiveEvent(int ms); @@ -257,6 +279,9 @@ class NativeWindow : public content::WebContentsObserver, // Whether window has standard frame. bool has_frame_; + // Force the window to be aware of draggable regions. + bool force_using_draggable_region_; + // Whether window is transparent. bool transparent_; @@ -264,6 +289,9 @@ class NativeWindow : public content::WebContentsObserver, // has to been explicitly provided. scoped_ptr draggable_region_; // used in custom drag. + // Minimum and maximum size, stored as content size. + extensions::SizeConstraints size_constraints_; + // Whether window can be resized larger than screen. bool enable_larger_than_screen_; diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 20ad60531498..08f9198e4ff1 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -44,12 +44,8 @@ class NativeWindowMac : public NativeWindow { bool IsFullscreen() const override; void SetBounds(const gfx::Rect& bounds) override; gfx::Rect GetBounds() override; - void SetContentSize(const gfx::Size& size) override; - gfx::Size GetContentSize() override; - void SetMinimumSize(const gfx::Size& size) override; - gfx::Size GetMinimumSize() override; - void SetMaximumSize(const gfx::Size& size) override; - gfx::Size GetMaximumSize() override; + void SetContentSizeConstraints( + const extensions::SizeConstraints& size_constraints) override; void SetResizable(bool resizable) override; bool IsResizable() override; void SetAlwaysOnTop(bool top) override; @@ -61,6 +57,7 @@ class NativeWindowMac : public NativeWindow { void SetSkipTaskbar(bool skip) override; void SetKiosk(bool kiosk) override; bool IsKiosk() override; + void SetBackgroundColor(const std::string& color_name) override; void SetRepresentedFilename(const std::string& filename) override; std::string GetRepresentedFilename() override; void SetDocumentEdited(bool edited) override; @@ -75,12 +72,10 @@ class NativeWindowMac : public NativeWindow { void SetVisibleOnAllWorkspaces(bool visible) override; bool IsVisibleOnAllWorkspaces() override; - // Returns true if |point| in local Cocoa coordinate system falls within - // the draggable region. - bool IsWithinDraggableRegion(NSPoint point) const; - - // Called to handle a mouse event. - void HandleMouseEvent(NSEvent* event); + // Refresh the DraggableRegion views. + void UpdateDraggableRegionViews() { + UpdateDraggableRegionViews(draggable_regions_); + } protected: // NativeWindow: @@ -88,13 +83,24 @@ class NativeWindowMac : public NativeWindow { content::WebContents*, const content::NativeWebKeyboardEvent&) override; + // Return a vector of non-draggable regions that fill a window of size + // |width| by |height|, but leave gaps where the window should be draggable. + std::vector CalculateNonDraggableRegions( + const std::vector& regions, int width, int height); + private: + // NativeWindow: + gfx::Size ContentSizeToWindowSize(const gfx::Size& size) override; + gfx::Size WindowSizeToContentSize(const gfx::Size& size) override; + void UpdateDraggableRegions( + const std::vector& regions) override; + void InstallView(); void UninstallView(); // Install the drag view, which will cover the whole window and decides // whehter we can drag. - void InstallDraggableRegionView(); + void UpdateDraggableRegionViews(const std::vector& regions); base::scoped_nsobject window_; base::scoped_nsobject window_delegate_; @@ -102,6 +108,8 @@ class NativeWindowMac : public NativeWindow { // The view that will fill the whole frameless window. base::scoped_nsobject content_view_; + std::vector draggable_regions_; + bool is_kiosk_; NSInteger attention_request_id_; // identifier from requestUserAttention @@ -109,10 +117,6 @@ class NativeWindowMac : public NativeWindow { // The presentation options before entering kiosk mode. NSApplicationPresentationOptions kiosk_options_; - // 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_; - DISALLOW_COPY_AND_ASSIGN(NativeWindowMac); }; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 6a99f1cf4227..ef33a119e107 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -6,7 +6,6 @@ #include -#import "atom/browser/ui/cocoa/event_processing_window.h" #include "atom/common/draggable_region.h" #include "atom/common/options_switches.h" #include "base/mac/mac_util.h" @@ -19,6 +18,7 @@ #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host_view.h" #include "native_mate/dictionary.h" +#include "ui/gfx/skia_util.h" namespace { @@ -146,6 +146,7 @@ bool ScopedDisableResize::disable_resize_ = false; } - (void)windowDidResize:(NSNotification*)notification { + shell_->UpdateDraggableRegionViews(); shell_->NotifyWindowResize(); } @@ -190,6 +191,11 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)windowWillClose:(NSNotification*)notification { shell_->NotifyWindowClosed(); + + // Clears the delegate when window is going to be closed, since EL Capitan it + // is possible that the methods of delegate would get called after the window + // has been closed. + [shell_->GetNativeWindow() setDelegate:nil]; } - (BOOL)windowShouldClose:(id)window { @@ -202,13 +208,15 @@ bool ScopedDisableResize::disable_resize_ = false; @end -@interface AtomNSWindow : EventProcessingWindow { +@interface AtomNSWindow : NSWindow { @private atom::NativeWindowMac* shell_; bool enable_larger_than_screen_; } @property BOOL acceptsFirstMouse; @property BOOL disableAutoHideCursor; +@property BOOL disableKeyOrMainWindow; + - (void)setShell:(atom::NativeWindowMac*)shell; - (void)setEnableLargerThanScreen:(bool)enable; @end @@ -223,6 +231,8 @@ bool ScopedDisableResize::disable_resize_ = false; enable_larger_than_screen_ = enable; } +// NSWindow overrides. + - (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen*)screen { // Resizing is disabled. if (ScopedDisableResize::IsResizeDisabled()) @@ -255,45 +265,33 @@ bool ScopedDisableResize::disable_resize_ = false; return [children filteredArrayUsingPredicate:predicate]; } +- (BOOL)canBecomeMainWindow { + return !self.disableKeyOrMainWindow; +} + +- (BOOL)canBecomeKeyWindow { + return !self.disableKeyOrMainWindow; +} + @end -@interface ControlRegionView : NSView { - @private - atom::NativeWindowMac* shellWindow_; // Weak; owns self. -} +@interface ControlRegionView : NSView @end @implementation ControlRegionView -- (id)initWithShellWindow:(atom::NativeWindowMac*)shellWindow { - if ((self = [super init])) - shellWindow_ = shellWindow; - return self; -} - - (BOOL)mouseDownCanMoveWindow { return NO; } - (NSView*)hitTest:(NSPoint)aPoint { - if (!shellWindow_->IsWithinDraggableRegion(aPoint)) { - return nil; - } - return self; + return nil; } -- (void)mouseDown:(NSEvent*)event { - shellWindow_->HandleMouseEvent(event); -} - -- (void)mouseDragged:(NSEvent*)event { - shellWindow_->HandleMouseEvent(event); -} - -- (BOOL)acceptsFirstMouse:(NSEvent*)event { - return YES; -} +@end +@interface NSView (WebContentsView) +- (void)setMouseDownCanMoveWindow:(BOOL)can_move; @end @interface AtomProgressBar : NSProgressIndicator @@ -338,8 +336,8 @@ NativeWindowMac::NativeWindowMac( is_kiosk_(false), attention_request_id_(0) { int width = 800, height = 600; - options.Get(switches::kWidth, &width); - options.Get(switches::kHeight, &height); + options.Get(options::kWidth, &width); + options.Get(options::kHeight, &height); NSRect main_screen_rect = [[[NSScreen screens] objectAtIndex:0] frame]; NSRect cocoa_bounds = NSMakeRect( @@ -348,24 +346,36 @@ NativeWindowMac::NativeWindowMac( width, height); + bool resizable = true; + options.Get(options::kResizable, &resizable); + + // New title bar styles are available in Yosemite or newer + std::string titleBarStyle; + if (base::mac::IsOSYosemiteOrLater()) + options.Get(options::kTitleBarStyle, &titleBarStyle); + + std::string windowType; + options.Get(options::kType, &windowType); + bool useStandardWindow = true; - options.Get(switches::kStandardWindow, &useStandardWindow); + // eventually deprecate separate "standardWindow" option in favor of + // standard / textured window types + options.Get(options::kStandardWindow, &useStandardWindow); + if (windowType == "textured") { + useStandardWindow = false; + } NSUInteger styleMask = NSTitledWindowMask | NSClosableWindowMask | - NSMiniaturizableWindowMask | NSResizableWindowMask; + NSMiniaturizableWindowMask; 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; - } + if (resizable) { + styleMask |= NSResizableWindowMask; + } + if ((titleBarStyle == "hidden") || (titleBarStyle == "hidden-inset")) { + styleMask |= NSFullSizeContentViewWindowMask; + styleMask |= NSUnifiedTitleAndToolbarWindowMask; } window_.reset([[AtomNSWindow alloc] @@ -386,6 +396,15 @@ NativeWindowMac::NativeWindowMac( [window_ setBackgroundColor:[NSColor clearColor]]; } + if (windowType == "desktop") { + [window_ setLevel:kCGDesktopWindowLevel - 1]; + [window_ setDisableKeyOrMainWindow:YES]; + [window_ setCollectionBehavior: + (NSWindowCollectionBehaviorCanJoinAllSpaces | + NSWindowCollectionBehaviorStationary | + NSWindowCollectionBehaviorIgnoresCycle)]; + } + // Remove non-transparent corners, see http://git.io/vfonD. if (!has_frame()) [window_ setOpaque:NO]; @@ -393,39 +412,39 @@ NativeWindowMac::NativeWindowMac( // 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]; - } + // Hide the title bar. + if ((titleBarStyle == "hidden") || (titleBarStyle == "hidden-inset")) { + [window_ setTitlebarAppearsTransparent:YES]; + [window_ setTitleVisibility:NSWindowTitleHidden]; + if (titleBarStyle == "hidden-inset") { + base::scoped_nsobject toolbar( + [[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]); + [toolbar setShowsBaselineSeparator:NO]; + [window_ setToolbar:toolbar]; } + // We should be aware of draggable regions when using hidden titlebar. + set_force_using_draggable_region(true); } // On OS X the initial window size doesn't include window frame. bool use_content_size = false; - options.Get(switches::kUseContentSize, &use_content_size); + options.Get(options::kUseContentSize, &use_content_size); if (!has_frame() || !use_content_size) SetSize(gfx::Size(width, height)); // Enable the NSView to accept first mouse event. bool acceptsFirstMouse = false; - options.Get(switches::kAcceptFirstMouse, &acceptsFirstMouse); + options.Get(options::kAcceptFirstMouse, &acceptsFirstMouse); [window_ setAcceptsFirstMouse:acceptsFirstMouse]; // Disable auto-hiding cursor. bool disableAutoHideCursor = false; - options.Get(switches::kDisableAutoHideCursor, &disableAutoHideCursor); + options.Get(options::kDisableAutoHideCursor, &disableAutoHideCursor); [window_ setDisableAutoHideCursor:disableAutoHideCursor]; // Disable fullscreen button when 'fullscreen' is specified to false. bool fullscreen; - if (!(options.Get(switches::kFullscreen, &fullscreen) && + if (!(options.Get(options::kFullscreen, &fullscreen) && !fullscreen)) { NSUInteger collectionBehavior = [window_ collectionBehavior]; collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary; @@ -467,8 +486,6 @@ bool NativeWindowMac::IsFocused() { } void NativeWindowMac::Show() { - web_contents()->WasShown(); - // This method is supposed to put focus on window, however if the app does not // have focus then "makeKeyAndOrderFront" will only show the window. [NSApp activateIgnoringOtherApps:YES]; @@ -477,13 +494,11 @@ void NativeWindowMac::Show() { } void NativeWindowMac::ShowInactive() { - web_contents()->WasShown(); [window_ orderFrontRegardless]; } void NativeWindowMac::Hide() { [window_ orderOut:nil]; - web_contents()->WasHidden(); } bool NativeWindowMac::IsVisible() { @@ -550,56 +565,30 @@ gfx::Rect NativeWindowMac::GetBounds() { return bounds; } -void NativeWindowMac::SetContentSize(const gfx::Size& size) { - if (!has_frame()) { - SetSize(size); - return; +void NativeWindowMac::SetContentSizeConstraints( + const extensions::SizeConstraints& size_constraints) { + auto convertSize = [this](const gfx::Size& size) { + // Our frameless window still has titlebar attached, so setting contentSize + // will result in actual content size being larger. + if (!has_frame()) { + NSRect frame = NSMakeRect(0, 0, size.width(), size.height()); + NSRect content = [window_ contentRectForFrameRect:frame]; + return content.size; + } else { + return NSMakeSize(size.width(), size.height()); + } + }; + + NSView* content = [window_ contentView]; + if (size_constraints.HasMinimumSize()) { + NSSize min_size = convertSize(size_constraints.GetMinimumSize()); + [window_ setContentMinSize:[content convertSize:min_size toView:nil]]; } - - NSRect frame_nsrect = [window_ frame]; - NSSize frame = frame_nsrect.size; - NSSize content = [window_ contentRectForFrameRect:frame_nsrect].size; - - int width = size.width() + frame.width - content.width; - int height = size.height() + frame.height - content.height; - frame_nsrect.origin.y -= height - frame_nsrect.size.height; - frame_nsrect.size.width = width; - frame_nsrect.size.height = height; - [window_ setFrame:frame_nsrect display:YES]; -} - -gfx::Size NativeWindowMac::GetContentSize() { - if (!has_frame()) - return GetSize(); - - NSRect bounds = [[window_ contentView] bounds]; - return gfx::Size(bounds.size.width, bounds.size.height); -} - -void NativeWindowMac::SetMinimumSize(const gfx::Size& size) { - NSSize min_size = NSMakeSize(size.width(), size.height()); - NSView* content = [window_ contentView]; - [window_ setContentMinSize:[content convertSize:min_size toView:nil]]; -} - -gfx::Size NativeWindowMac::GetMinimumSize() { - NSView* content = [window_ contentView]; - NSSize min_size = [content convertSize:[window_ contentMinSize] - fromView:nil]; - return gfx::Size(min_size.width, min_size.height); -} - -void NativeWindowMac::SetMaximumSize(const gfx::Size& size) { - NSSize max_size = NSMakeSize(size.width(), size.height()); - NSView* content = [window_ contentView]; - [window_ setContentMaxSize:[content convertSize:max_size toView:nil]]; -} - -gfx::Size NativeWindowMac::GetMaximumSize() { - NSView* content = [window_ contentView]; - NSSize max_size = [content convertSize:[window_ contentMaxSize] - fromView:nil]; - return gfx::Size(max_size.width, max_size.height); + if (size_constraints.HasMaximumSize()) { + NSSize max_size = convertSize(size_constraints.GetMaximumSize()); + [window_ setContentMaxSize:[content convertSize:max_size toView:nil]]; + } + NativeWindow::SetContentSizeConstraints(size_constraints); } void NativeWindowMac::SetResizable(bool resizable) { @@ -680,6 +669,9 @@ bool NativeWindowMac::IsKiosk() { return is_kiosk_; } +void NativeWindowMac::SetBackgroundColor(const std::string& color_name) { +} + void NativeWindowMac::SetRepresentedFilename(const std::string& filename) { [window_ setRepresentedFilename:base::SysUTF8ToNSString(filename)]; } @@ -768,36 +760,6 @@ bool NativeWindowMac::IsVisibleOnAllWorkspaces() { return collectionBehavior & NSWindowCollectionBehaviorCanJoinAllSpaces; } -bool NativeWindowMac::IsWithinDraggableRegion(NSPoint point) const { - if (!draggable_region()) - return false; - if (!web_contents()) - return false; - 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); -} - -void NativeWindowMac::HandleMouseEvent(NSEvent* event) { - NSPoint eventLoc = [event locationInWindow]; - NSRect mouseRect = [window_ convertRectToScreen:NSMakeRect(eventLoc.x, eventLoc.y, 0, 0)]; - NSPoint current_mouse_location = mouseRect.origin; - - if ([event type] == NSLeftMouseDown) { - NSPoint frame_origin = [window_ frame].origin; - last_mouse_offset_ = NSMakePoint( - frame_origin.x - current_mouse_location.x, - frame_origin.y - current_mouse_location.y); - } else if ([event type] == NSLeftMouseDragged) { - [window_ setFrameOrigin:NSMakePoint( - current_mouse_location.x + last_mouse_offset_.x, - current_mouse_location.y + last_mouse_offset_.y)]; - } -} - void NativeWindowMac::HandleKeyboardEvent( content::WebContents*, const content::NativeWebKeyboardEvent& event) { @@ -805,23 +767,59 @@ void NativeWindowMac::HandleKeyboardEvent( event.type == content::NativeWebKeyboardEvent::Char) return; - if (event.os_event.window == window_.get()) { - EventProcessingWindow* event_window = - static_cast(window_); - DCHECK([event_window isKindOfClass:[EventProcessingWindow class]]); - [event_window redispatchKeyEvent:event.os_event]; - } else { + BOOL handled = [[NSApp mainMenu] performKeyEquivalent:event.os_event]; + if (!handled && event.os_event.window != window_.get()) { // The event comes from detached devtools view, and it has already been - // handled by the devtools itself, we now send it to application menu to - // make menu acclerators work. - BOOL handled = [[NSApp mainMenu] performKeyEquivalent:event.os_event]; - // Handle the cmd+~ shortcut. if (!handled && (event.os_event.modifierFlags & NSCommandKeyMask) && - (event.os_event.keyCode == 50 /* ~ key */)) + (event.os_event.keyCode == 50 /* ~ key */)) { + // Handle the cmd+~ shortcut. Focus(true); + } } } +std::vector NativeWindowMac::CalculateNonDraggableRegions( + const std::vector& regions, int width, int height) { + std::vector result; + if (regions.empty()) { + result.push_back(gfx::Rect(0, 0, width, height)); + } else { + scoped_ptr draggable(DraggableRegionsToSkRegion(regions)); + scoped_ptr non_draggable(new SkRegion); + non_draggable->op(0, 0, width, height, SkRegion::kUnion_Op); + non_draggable->op(*draggable, SkRegion::kDifference_Op); + for (SkRegion::Iterator it(*non_draggable); !it.done(); it.next()) { + result.push_back(gfx::SkIRectToRect(it.rect())); + } + } + return result; +} + +gfx::Size NativeWindowMac::ContentSizeToWindowSize(const gfx::Size& size) { + if (!has_frame()) + return size; + + NSRect content = NSMakeRect(0, 0, size.width(), size.height()); + NSRect frame = [window_ frameRectForContentRect:content]; + return gfx::Size(frame.size); +} + +gfx::Size NativeWindowMac::WindowSizeToContentSize(const gfx::Size& size) { + if (!has_frame()) + return size; + + NSRect frame = NSMakeRect(0, 0, size.width(), size.height()); + NSRect content = [window_ contentRectForFrameRect:frame]; + return gfx::Size(content.size); +} + +void NativeWindowMac::UpdateDraggableRegions( + const std::vector& regions) { + NativeWindow::UpdateDraggableRegions(regions); + draggable_regions_ = regions; + UpdateDraggableRegionViews(regions); +} + void NativeWindowMac::InstallView() { // Make sure the bottom corner is rounded: http://crbug.com/396264. [[window_ contentView] setWantsLayer:YES]; @@ -844,8 +842,6 @@ void NativeWindowMac::InstallView() { [view setFrame:[content_view_ bounds]]; [content_view_ addSubview:view]; - InstallDraggableRegionView(); - [[window_ standardWindowButton:NSWindowZoomButton] setHidden:YES]; [[window_ standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES]; [[window_ standardWindowButton:NSWindowCloseButton] setHidden:YES]; @@ -862,14 +858,55 @@ void NativeWindowMac::UninstallView() { [view removeFromSuperview]; } -void NativeWindowMac::InstallDraggableRegionView() { +void NativeWindowMac::UpdateDraggableRegionViews( + const std::vector& regions) { + if (has_frame() && !force_using_draggable_region()) + return; + + // All ControlRegionViews should be added as children of the WebContentsView, + // because WebContentsView will be removed and re-added when entering and + // leaving fullscreen mode. NSView* webView = web_contents()->GetNativeView(); - base::scoped_nsobject controlRegion( - [[ControlRegionView alloc] initWithShellWindow:this]); - [controlRegion setFrame:NSMakeRect(0, 0, - NSWidth([webView bounds]), - NSHeight([webView bounds]))]; - [webView addSubview:controlRegion]; + NSInteger webViewWidth = NSWidth([webView bounds]); + NSInteger webViewHeight = NSHeight([webView bounds]); + + [webView setMouseDownCanMoveWindow:YES]; + + // Remove all ControlRegionViews that are added last time. + // Note that [webView subviews] returns the view's mutable internal array and + // it should be copied to avoid mutating the original array while enumerating + // it. + base::scoped_nsobject subviews([[webView subviews] copy]); + for (NSView* subview in subviews.get()) + if ([subview isKindOfClass:[ControlRegionView class]]) + [subview removeFromSuperview]; + + // Draggable regions is implemented by having the whole web view draggable + // (mouseDownCanMoveWindow) and overlaying regions that are not draggable. + std::vector system_drag_exclude_areas = + CalculateNonDraggableRegions(regions, webViewWidth, webViewHeight); + + // Create and add a ControlRegionView for each region that needs to be + // excluded from the dragging. + for (std::vector::const_iterator iter = + system_drag_exclude_areas.begin(); + iter != system_drag_exclude_areas.end(); + ++iter) { + base::scoped_nsobject controlRegion( + [[ControlRegionView alloc] initWithFrame:NSZeroRect]); + [controlRegion setFrame:NSMakeRect(iter->x(), + webViewHeight - iter->bottom(), + iter->width(), + iter->height())]; + [webView addSubview:controlRegion]; + } + + // AppKit will not update its cache of mouseDownCanMoveWindow unless something + // changes. Previously we tried adding an NSView and removing it, but for some + // reason it required reposting the mouse-down event, and didn't always work. + // Calling the below seems to be an effective solution. + [window_ setMovableByWindowBackground:NO]; + [window_ setMovableByWindowBackground:YES]; } // static diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index 5b0a0c56b3de..ce2d41c6fad6 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -11,16 +11,16 @@ #include "ui/base/window_open_disposition.h" #include "url/gurl.h" +#if defined(OS_WIN) +#include +#endif + namespace atom { class NativeWindowObserver { public: virtual ~NativeWindowObserver() {} - // Called when the web page of the window has updated it's document title. - virtual void OnPageTitleUpdated(bool* prevent_default, - const std::string& title) {} - // Called when the web page in window wants to create a popup window. virtual void WillCreatePopupWindow(const base::string16& frame_name, const GURL& target_url, @@ -55,10 +55,10 @@ class NativeWindowObserver { virtual void OnWindowEnterHtmlFullScreen() {} virtual void OnWindowLeaveHtmlFullScreen() {} - // Redirect devtools events. - virtual void OnDevToolsFocus() {} - virtual void OnDevToolsOpened() {} - virtual void OnDevToolsClosed() {} + // Called when window message received + #if defined(OS_WIN) + virtual void OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) {} + #endif // Called when renderer is hung. virtual void OnRendererUnresponsive() {} diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 70707219f3f2..16faee58c5a4 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -42,6 +42,7 @@ #elif defined(OS_WIN) #include "atom/browser/ui/views/win_frame_view.h" #include "atom/browser/ui/win/atom_desktop_window_tree_host_win.h" +#include "skia/ext/skia_utils_win.h" #include "ui/base/win/shell.h" #include "ui/gfx/win/dpi.h" #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" @@ -77,69 +78,28 @@ 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"; +SkColor ParseHexColor(const std::string& name) { + SkColor result = 0xFF000000; + unsigned value = 0; + auto color = name.substr(1); + unsigned length = color.size(); + if (length != 3 && length != 6) + return result; + for (unsigned i = 0; i < length; ++i) { + if (!base::IsHexDigit(color[i])) + return result; + value <<= 4; + value |= (color[i] < 'A' ? color[i] - '0' : (color[i] - 'A' + 10) & 0xF); } + if (length == 6) { + result |= value; + return result; + } + result |= (value & 0xF00) << 12 | (value & 0xF00) << 8 + | (value & 0xF0) << 8 | (value & 0xF0) << 4 + | (value & 0xF) << 4 | (value & 0xF); + return result; } -#endif class NativeWindowClientView : public views::ClientView { public: @@ -169,19 +129,16 @@ NativeWindowViews::NativeWindowViews( menu_bar_autohide_(false), menu_bar_visible_(false), menu_bar_alt_pressed_(false), -#if defined(OS_WIN) - is_minimized_(false), -#endif keyboard_event_handler_(new views::UnhandledKeyboardEventHandler), use_content_size_(false), resizable_(true) { - options.Get(switches::kTitle, &title_); - options.Get(switches::kAutoHideMenuBar, &menu_bar_autohide_); + options.Get(options::kTitle, &title_); + options.Get(options::kAutoHideMenuBar, &menu_bar_autohide_); #if defined(OS_WIN) // On Windows we rely on the CanResize() to indicate whether window can be // resized, and it should be set before window is created. - options.Get(switches::kResizable, &resizable_); + options.Get(options::kResizable, &resizable_); #endif if (enable_larger_than_screen()) @@ -189,11 +146,12 @@ NativeWindowViews::NativeWindowViews( // will not allow us to resize the window larger than scree. // Setting directly to INT_MAX somehow doesn't work, so we just devide // by 10, which should still be large enough. - maximum_size_.SetSize(INT_MAX / 10, INT_MAX / 10); + SetContentSizeConstraints(extensions::SizeConstraints( + gfx::Size(), gfx::Size(INT_MAX / 10, INT_MAX / 10))); int width = 800, height = 600; - options.Get(switches::kWidth, &width); - options.Get(switches::kHeight, &height); + options.Get(options::kWidth, &width); + options.Get(options::kHeight, &height); gfx::Rect bounds(0, 0, width, height); widget_size_ = bounds.size(); @@ -222,19 +180,22 @@ NativeWindowViews::NativeWindowViews( // Set WM_WINDOW_ROLE. params.wm_role_name = "browser-window"; // Set WM_CLASS. - params.wm_class_name = base::StringToLowerASCII(name); + params.wm_class_name = base::ToLowerASCII(name); params.wm_class_class = name; #endif window_->Init(params); + bool fullscreen = false; + options.Get(options::kFullscreen, &fullscreen); + #if defined(USE_X11) // Start monitoring window states. window_state_watcher_.reset(new WindowStateWatcher(this)); // Set _GTK_THEME_VARIANT to dark if we have "dark-theme" option set. bool use_dark_theme = false; - if (options.Get(switches::kDarkTheme, &use_dark_theme) && use_dark_theme) { + if (options.Get(options::kDarkTheme, &use_dark_theme) && use_dark_theme) { XDisplay* xdisplay = gfx::GetXDisplay(); XChangeProperty(xdisplay, GetAcceleratedWidget(), XInternAtom(xdisplay, "_GTK_THEME_VARIANT", False), @@ -248,13 +209,12 @@ NativeWindowViews::NativeWindowViews( // 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) { + if (options.Get(options::kSkipTaskbar, &skip_taskbar) && skip_taskbar) { state_atom_list.push_back(GetAtom("_NET_WM_STATE_SKIP_TASKBAR")); } // Before the window is mapped, there is no SHOW_FULLSCREEN_STATE. - bool fullscreen = false; - if (options.Get(switches::kFullscreen, & fullscreen) && fullscreen) { + if (fullscreen) { state_atom_list.push_back(GetAtom("_NET_WM_STATE_FULLSCREEN")); } @@ -263,21 +223,24 @@ NativeWindowViews::NativeWindowViews( // Set the _NET_WM_WINDOW_TYPE. std::string window_type; - if (options.Get(switches::kType, &window_type)) + if (options.Get(options::kType, &window_type)) SetWindowType(GetAcceleratedWidget(), window_type); #endif // Add web view. SetLayoutManager(new MenuLayout(this, kMenuBarHeight)); - set_background(views::Background::CreateStandardPanelBackground()); + AddChildView(web_view_); - if (has_frame() && - options.Get(switches::kUseContentSize, &use_content_size_) && - use_content_size_) - bounds = ContentBoundsToWindowBounds(bounds); - #if defined(OS_WIN) + // Save initial window state. + if (fullscreen) + last_window_state_ = ui::SHOW_STATE_FULLSCREEN; + else + last_window_state_ = ui::SHOW_STATE_NORMAL; + + last_normal_size_ = gfx::Size(widget_size_); + if (!has_frame()) { // Set Window style so that we get a minimize and maximize animation when // frameless. @@ -309,8 +272,14 @@ NativeWindowViews::NativeWindowViews( if (transparent() && !has_frame()) wm::SetShadowType(GetNativeWindow(), wm::SHADOW_TYPE_NONE); + gfx::Size size = bounds.size(); + if (has_frame() && + options.Get(options::kUseContentSize, &use_content_size_) && + use_content_size_) + size = ContentSizeToWindowSize(size); + window_->UpdateWindowIcon(); - window_->CenterWindow(bounds.size()); + window_->CenterWindow(size); Layout(); } @@ -338,18 +307,15 @@ bool NativeWindowViews::IsFocused() { } void NativeWindowViews::Show() { - web_contents()->WasShown(); window_->native_widget_private()->ShowWithWindowState(GetRestoredState()); } void NativeWindowViews::ShowInactive() { - web_contents()->WasShown(); window_->ShowInactive(); } void NativeWindowViews::Hide() { window_->Hide(); - web_contents()->WasHidden(); } bool NativeWindowViews::IsVisible() { @@ -391,11 +357,16 @@ 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) + if (fullscreen) { + last_window_state_ = ui::SHOW_STATE_FULLSCREEN; NotifyWindowEnterFullScreen(); - else + } else { + last_window_state_ = ui::SHOW_STATE_NORMAL; NotifyWindowLeaveFullScreen(); + } + // We set the new value after notifying, so we can handle the size event + // correctly. + window_->SetFullscreen(fullscreen); #else if (IsVisible()) window_->SetFullscreen(fullscreen); @@ -431,42 +402,26 @@ gfx::Rect NativeWindowViews::GetBounds() { return window_->GetWindowBoundsInScreen(); } -void NativeWindowViews::SetContentSize(const gfx::Size& size) { - if (!has_frame()) { - NativeWindow::SetSize(size); - return; - } - - gfx::Rect bounds = window_->GetWindowBoundsInScreen(); - bounds.set_size(size); - SetBounds(ContentBoundsToWindowBounds(bounds)); -} - gfx::Size NativeWindowViews::GetContentSize() { - if (!has_frame()) - return GetSize(); +#if defined(OS_WIN) + if (IsMinimized()) + return NativeWindow::GetContentSize(); +#endif - gfx::Size content_size = - window_->non_client_view()->frame_view()->GetBoundsForClientView().size(); - if (menu_bar_ && menu_bar_visible_) - content_size.set_height(content_size.height() - kMenuBarHeight); - return content_size; + return web_view_->size(); } -void NativeWindowViews::SetMinimumSize(const gfx::Size& size) { - minimum_size_ = size; -} - -gfx::Size NativeWindowViews::GetMinimumSize() { - return minimum_size_; -} - -void NativeWindowViews::SetMaximumSize(const gfx::Size& size) { - maximum_size_ = size; -} - -gfx::Size NativeWindowViews::GetMaximumSize() { - return maximum_size_; +void NativeWindowViews::SetContentSizeConstraints( + const extensions::SizeConstraints& size_constraints) { + NativeWindow::SetContentSizeConstraints(size_constraints); + // widget_delegate() is only available after Init() is called, we make use of + // this to determine whether native widget has initialized. + if (window_ && window_->widget_delegate()) + window_->OnSizeConstraintsChanged(); +#if defined(USE_X11) + if (resizable_) + old_size_constraints_ = size_constraints; +#endif } void NativeWindowViews::SetResizable(bool resizable) { @@ -485,11 +440,13 @@ void NativeWindowViews::SetResizable(bool resizable) { // On Linux there is no "resizable" property of a window, we have to set // both the minimum and maximum size to the window size to achieve it. if (resizable) { - SetMaximumSize(gfx::Size()); - SetMinimumSize(gfx::Size()); + SetContentSizeConstraints(old_size_constraints_); } else { - SetMaximumSize(GetSize()); - SetMinimumSize(GetSize()); + old_size_constraints_ = GetContentSizeConstraints(); + resizable_ = false; + gfx::Size content_size = GetContentSize(); + SetContentSizeConstraints( + extensions::SizeConstraints(content_size, content_size)); } } #endif @@ -563,6 +520,21 @@ bool NativeWindowViews::IsKiosk() { return IsFullscreen(); } +void NativeWindowViews::SetBackgroundColor(const std::string& color_name) { + // web views' background color. + SkColor background_color = ParseHexColor(color_name); + set_background(views::Background::CreateSolidBackground(background_color)); + +#if defined(OS_WIN) + // Set the background color of native window. + HBRUSH brush = CreateSolidBrush(skia::SkColorToCOLORREF(background_color)); + ULONG_PTR previous_brush = SetClassLongPtr( + GetAcceleratedWidget(), GCLP_HBRBACKGROUND, (LONG)brush); + if (previous_brush) + DeleteObject((HBRUSH)previous_brush); +#endif +} + void NativeWindowViews::SetMenu(ui::MenuModel* menu_model) { if (menu_model == nullptr) { // Remove accelerators @@ -601,8 +573,24 @@ void NativeWindowViews::SetMenu(ui::MenuModel* menu_model) { if (!menu_bar_autohide_) { SetMenuBarVisibility(true); - if (use_content_size_) + if (use_content_size_) { + // Enlarge the size constraints for the menu. + extensions::SizeConstraints constraints = GetContentSizeConstraints(); + if (constraints.HasMinimumSize()) { + gfx::Size min_size = constraints.GetMinimumSize(); + min_size.set_height(min_size.height() + kMenuBarHeight); + constraints.set_minimum_size(min_size); + } + if (constraints.HasMaximumSize()) { + gfx::Size max_size = constraints.GetMaximumSize(); + max_size.set_height(max_size.height() + kMenuBarHeight); + constraints.set_maximum_size(max_size); + } + SetContentSizeConstraints(constraints); + + // Resize the window to make sure content size is not changed. SetContentSize(content_size); + } } } @@ -805,53 +793,47 @@ void NativeWindowViews::OnWidgetMove() { NotifyWindowMove(); } +gfx::Size NativeWindowViews::ContentSizeToWindowSize(const gfx::Size& size) { + if (!has_frame()) + return size; + + gfx::Size window_size(size); #if defined(OS_WIN) -bool NativeWindowViews::ExecuteWindowsCommand(int command_id) { - // Windows uses the 4 lower order bits of |command_id| for type-specific - // information so we must exclude this when comparing. - static const int sc_mask = 0xFFF0; - if ((command_id & sc_mask) == SC_MINIMIZE) { - NotifyWindowMinimize(); - is_minimized_ = true; - } else if ((command_id & sc_mask) == SC_RESTORE) { - if (is_minimized_) - NotifyWindowRestore(); - else - NotifyWindowUnmaximize(); - is_minimized_ = false; - } else if ((command_id & sc_mask) == SC_MAXIMIZE) { - NotifyWindowMaximize(); - } else { - std::string command = AppCommandToString(command_id); - NotifyWindowExecuteWindowsCommand(command); - } - return false; -} + gfx::Rect dpi_bounds = + gfx::Rect(gfx::Point(), gfx::win::DIPToScreenSize(size)); + gfx::Rect window_bounds = gfx::win::ScreenToDIPRect( + window_->non_client_view()->GetWindowBoundsForClientBounds(dpi_bounds)); + window_size = window_bounds.size(); #endif -gfx::ImageSkia NativeWindowViews::GetDevToolsWindowIcon() { - return GetWindowAppIcon(); + if (menu_bar_ && menu_bar_visible_) + window_size.set_height(window_size.height() + kMenuBarHeight); + return window_size; } -#if defined(USE_X11) -void NativeWindowViews::GetDevToolsWindowWMClass( - std::string* name, std::string* class_name) { - *class_name = Browser::Get()->GetName(); - *name = base::StringToLowerASCII(*class_name); -} -#endif +gfx::Size NativeWindowViews::WindowSizeToContentSize(const gfx::Size& size) { + if (!has_frame()) + return size; + gfx::Size content_size(size); #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; -} + content_size = gfx::win::DIPToScreenSize(content_size); + RECT rect; + SetRectEmpty(&rect); + HWND hwnd = GetAcceleratedWidget(); + DWORD style = ::GetWindowLong(hwnd, GWL_STYLE); + DWORD ex_style = ::GetWindowLong(hwnd, GWL_EXSTYLE); + AdjustWindowRectEx(&rect, style, FALSE, ex_style); + content_size.set_width(content_size.width() - (rect.right - rect.left)); + content_size.set_height(content_size.height() - (rect.bottom - rect.top)); + content_size = gfx::win::ScreenToDIPSize(content_size); #endif + if (menu_bar_ && menu_bar_visible_) + content_size.set_height(content_size.height() - kMenuBarHeight); + return content_size; +} + void NativeWindowViews::HandleKeyboardEvent( content::WebContents*, const content::NativeWebKeyboardEvent& event) { @@ -883,9 +865,6 @@ void NativeWindowViews::HandleKeyboardEvent( // When a single Alt is pressed: menu_bar_alt_pressed_ = true; } else if (event.type == blink::WebInputEvent::KeyUp && IsAltKey(event) && -#if defined(USE_X11) - event.modifiers == 0 && -#endif menu_bar_alt_pressed_) { // When a single Alt is released right after a Alt is pressed: menu_bar_alt_pressed_ = false; @@ -896,6 +875,14 @@ void NativeWindowViews::HandleKeyboardEvent( } } +gfx::Size NativeWindowViews::GetMinimumSize() { + return NativeWindow::GetMinimumSize(); +} + +gfx::Size NativeWindowViews::GetMaximumSize() { + return NativeWindow::GetMaximumSize(); +} + bool NativeWindowViews::AcceleratorPressed(const ui::Accelerator& accelerator) { return accelerator_util::TriggerAcceleratorTableCommand( &accelerator_table_, accelerator); @@ -918,26 +905,6 @@ 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( - window_->non_client_view()->GetWindowBoundsForClientBounds(dpi_bounds)); -#else - 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); - return window_bounds; -} - ui::WindowShowState NativeWindowViews::GetRestoredState() { if (IsMaximized()) return ui::SHOW_STATE_MAXIMIZED; diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index 355f5bd38ef3..6c47c74331d0 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -63,12 +63,9 @@ class NativeWindowViews : public NativeWindow, bool IsFullscreen() const override; void SetBounds(const gfx::Rect& bounds) override; gfx::Rect GetBounds() override; - void SetContentSize(const gfx::Size& size) override; gfx::Size GetContentSize() override; - void SetMinimumSize(const gfx::Size& size) override; - gfx::Size GetMinimumSize() override; - void SetMaximumSize(const gfx::Size& size) override; - gfx::Size GetMaximumSize() override; + void SetContentSizeConstraints( + const extensions::SizeConstraints& size_constraints) override; void SetResizable(bool resizable) override; bool IsResizable() override; void SetAlwaysOnTop(bool top) override; @@ -80,6 +77,7 @@ class NativeWindowViews : public NativeWindow, void SetSkipTaskbar(bool skip) override; void SetKiosk(bool kiosk) override; bool IsKiosk() override; + void SetBackgroundColor(const std::string& color_name) override; void SetMenu(ui::MenuModel* menu_model) override; gfx::NativeWindow GetNativeWindow() override; void SetOverlayIcon(const gfx::Image& overlay, @@ -131,34 +129,29 @@ class NativeWindowViews : public NativeWindow, bool ExecuteWindowsCommand(int command_id) override; #endif - // brightray::InspectableWebContentsViewDelegate: - gfx::ImageSkia GetDevToolsWindowIcon() override; -#if defined(USE_X11) - void GetDevToolsWindowWMClass( - std::string* name, std::string* class_name) override; -#endif - #if defined(OS_WIN) // MessageHandlerDelegate: bool PreHandleMSG( UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result) override; + + void HandleSizeEvent(WPARAM w_param, LPARAM l_param); #endif // NativeWindow: + gfx::Size ContentSizeToWindowSize(const gfx::Size& size) override; + gfx::Size WindowSizeToContentSize(const gfx::Size& size) override; void HandleKeyboardEvent( content::WebContents*, const content::NativeWebKeyboardEvent& event) override; // views::View: + gfx::Size GetMinimumSize() override; + gfx::Size GetMaximumSize() override; bool AcceleratorPressed(const ui::Accelerator& accelerator) override; // Register accelerators supported by the menu model. void RegisterAccelerators(ui::MenuModel* menu_model); - // Converts between client area and window area, since we include the menu bar - // in client area we need to substract/add menu bar's height in convertions. - gfx::Rect ContentBoundsToWindowBounds(const gfx::Rect& content_bounds); - // Returns the restore state for the window. ui::WindowShowState GetRestoredState(); @@ -175,12 +168,23 @@ class NativeWindowViews : public NativeWindow, // Handles window state events. scoped_ptr window_state_watcher_; + + // The "resizable" flag on Linux is implemented by setting size constraints, + // we need to make sure size constraints are restored when window becomes + // resizable again. + extensions::SizeConstraints old_size_constraints_; #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_; + + ui::WindowShowState last_window_state_; + + // There's an issue with restore on Windows, that sometimes causes the Window + // to receive the wrong size (#2498). To circumvent that, we keep tabs on the + // size of the window while in the normal state (not maximized, minimized or + // fullscreen), so we restore it correctly. + gfx::Size last_normal_size_; + // In charge of running taskbar related APIs. TaskbarHost taskbar_host_; #endif @@ -194,8 +198,6 @@ class NativeWindowViews : public NativeWindow, bool use_content_size_; bool resizable_; std::string title_; - gfx::Size minimum_size_; - gfx::Size maximum_size_; gfx::Size widget_size_; DISALLOW_COPY_AND_ASSIGN(NativeWindowViews); diff --git a/atom/browser/native_window_views_win.cc b/atom/browser/native_window_views_win.cc new file mode 100644 index 000000000000..02ebd2ef32fc --- /dev/null +++ b/atom/browser/native_window_views_win.cc @@ -0,0 +1,162 @@ +// 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/native_window_views.h" +#include "content/public/browser/browser_accessibility_state.h" + +namespace atom { + +namespace { + +// 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"; + } +} + +} // namespace + +bool NativeWindowViews::ExecuteWindowsCommand(int command_id) { + std::string command = AppCommandToString(command_id); + NotifyWindowExecuteWindowsCommand(command); + return false; +} + +bool NativeWindowViews::PreHandleMSG( + UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result) { + NotifyWindowMessage(message, w_param, l_param); + + switch (message) { + // Screen readers send WM_GETOBJECT in order to get the accessibility + // object, so take this opportunity to push Chromium into accessible + // mode if it isn't already, always say we didn't handle the message + // because we still want Chromium to handle returning the actual + // accessibility object. + case WM_GETOBJECT: { + const DWORD obj_id = static_cast(l_param); + if (obj_id == OBJID_CLIENT) { + const auto axState = content::BrowserAccessibilityState::GetInstance(); + if (axState && !axState->IsAccessibleBrowser()) + axState->OnScreenReaderDetected(); + } + return false; + } + case WM_COMMAND: + // Handle thumbar button click message. + if (HIWORD(w_param) == THBN_CLICKED) + return taskbar_host_.HandleThumbarButtonEvent(LOWORD(w_param)); + return false; + case WM_SIZE: + // Handle window state change. + HandleSizeEvent(w_param, l_param); + return false; + default: + return false; + } +} + +void NativeWindowViews::HandleSizeEvent(WPARAM w_param, LPARAM l_param) { + // Here we handle the WM_SIZE event in order to figure out what is the current + // window state and notify the user accordingly. + switch (w_param) { + case SIZE_MAXIMIZED: + last_window_state_ = ui::SHOW_STATE_MAXIMIZED; + NotifyWindowMaximize(); + break; + case SIZE_MINIMIZED: + last_window_state_ = ui::SHOW_STATE_MINIMIZED; + NotifyWindowMinimize(); + break; + case SIZE_RESTORED: + if (last_window_state_ == ui::SHOW_STATE_NORMAL) { + // Window was resized so we save it's new size. + last_normal_size_ = GetSize(); + } else { + switch (last_window_state_) { + case ui::SHOW_STATE_MAXIMIZED: + last_window_state_ = ui::SHOW_STATE_NORMAL; + + // When the window is restored we resize it to the previous known + // normal size. + NativeWindow::SetSize(last_normal_size_); + + NotifyWindowUnmaximize(); + break; + case ui::SHOW_STATE_MINIMIZED: + if (IsFullscreen()) { + last_window_state_ = ui::SHOW_STATE_FULLSCREEN; + NotifyWindowEnterFullScreen(); + } else { + last_window_state_ = ui::SHOW_STATE_NORMAL; + + // When the window is restored we resize it to the previous known + // normal size. + NativeWindow::SetSize(last_normal_size_); + + NotifyWindowRestore(); + } + break; + } + } + break; + } +} + +} // namespace atom diff --git a/atom/browser/net/atom_cert_verifier.cc b/atom/browser/net/atom_cert_verifier.cc new file mode 100644 index 000000000000..3633d805fb5b --- /dev/null +++ b/atom/browser/net/atom_cert_verifier.cc @@ -0,0 +1,77 @@ +// 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/atom_cert_verifier.h" + +#include "atom/browser/browser.h" +#include "atom/common/native_mate_converters/net_converter.h" +#include "content/public/browser/browser_thread.h" +#include "net/base/net_errors.h" +#include "net/cert/crl_set.h" +#include "net/cert/x509_certificate.h" + +using content::BrowserThread; + +namespace atom { + +namespace { + +void OnResult( + net::CertVerifyResult* verify_result, + const net::CompletionCallback& callback, + bool result) { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(callback, result ? net::OK : net::ERR_FAILED)); +} + +} // namespace + +AtomCertVerifier::AtomCertVerifier() + : default_cert_verifier_(net::CertVerifier::CreateDefault()) { +} + +AtomCertVerifier::~AtomCertVerifier() { +} + +void AtomCertVerifier::SetVerifyProc(const VerifyProc& proc) { + base::AutoLock auto_lock(lock_); + verify_proc_ = proc; +} + +int AtomCertVerifier::Verify( + net::X509Certificate* cert, + const std::string& hostname, + const std::string& ocsp_response, + int flags, + net::CRLSet* crl_set, + net::CertVerifyResult* verify_result, + const net::CompletionCallback& callback, + scoped_ptr* out_req, + const net::BoundNetLog& net_log) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + VerifyProc proc; + { + base::AutoLock auto_lock(lock_); + proc = verify_proc_; + } + + if (proc.is_null()) + return default_cert_verifier_->Verify( + cert, hostname, ocsp_response, flags, crl_set, verify_result, callback, + out_req, net_log); + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(proc, hostname, make_scoped_refptr(cert), + base::Bind(OnResult, verify_result, callback))); + return net::ERR_IO_PENDING; +} + +bool AtomCertVerifier::SupportsOCSPStapling() { + return true; +} + +} // namespace atom diff --git a/atom/browser/net/atom_cert_verifier.h b/atom/browser/net/atom_cert_verifier.h new file mode 100644 index 000000000000..796ae2849bda --- /dev/null +++ b/atom/browser/net/atom_cert_verifier.h @@ -0,0 +1,51 @@ +// 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_ATOM_CERT_VERIFIER_H_ +#define ATOM_BROWSER_NET_ATOM_CERT_VERIFIER_H_ + +#include + +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "net/cert/cert_verifier.h" + +namespace atom { + +class AtomCertVerifier : public net::CertVerifier { + public: + AtomCertVerifier(); + virtual ~AtomCertVerifier(); + + using VerifyProc = + base::Callback, + const base::Callback&)>; + + void SetVerifyProc(const VerifyProc& proc); + + protected: + // net::CertVerifier: + int Verify(net::X509Certificate* cert, + const std::string& hostname, + const std::string& ocsp_response, + int flags, + net::CRLSet* crl_set, + net::CertVerifyResult* verify_result, + const net::CompletionCallback& callback, + scoped_ptr* out_req, + const net::BoundNetLog& net_log) override; + bool SupportsOCSPStapling() override; + + private: + base::Lock lock_; + VerifyProc verify_proc_; + scoped_ptr default_cert_verifier_; + + DISALLOW_COPY_AND_ASSIGN(AtomCertVerifier); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_NET_ATOM_CERT_VERIFIER_H_ diff --git a/atom/browser/atom_ssl_config_service.cc b/atom/browser/net/atom_ssl_config_service.cc similarity index 55% rename from atom/browser/atom_ssl_config_service.cc rename to atom/browser/net/atom_ssl_config_service.cc index f19dbacf7dd3..2b3143e93cb4 100644 --- a/atom/browser/atom_ssl_config_service.cc +++ b/atom/browser/net/atom_ssl_config_service.cc @@ -2,14 +2,17 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#include "atom/browser/atom_ssl_config_service.h" +#include "atom/browser/net/atom_ssl_config_service.h" #include +#include #include "base/command_line.h" +#include "base/strings/string_split.h" #include "atom/common/options_switches.h" #include "content/public/browser/browser_thread.h" #include "net/socket/ssl_client_socket.h" +#include "net/ssl/ssl_cipher_suite_names.h" namespace atom { @@ -26,6 +29,23 @@ uint16 GetSSLProtocolVersion(const std::string& version_string) { return version; } +std::vector ParseCipherSuites( + const std::vector& cipher_strings) { + std::vector cipher_suites; + cipher_suites.reserve(cipher_strings.size()); + + for (auto& cipher_string : cipher_strings) { + uint16 cipher_suite = 0; + if (!net::ParseSSLCipherString(cipher_string, &cipher_suite)) { + LOG(ERROR) << "Ignoring unrecognised cipher suite : " + << cipher_string; + continue; + } + cipher_suites.push_back(cipher_suite); + } + return cipher_suites; +} + } // namespace AtomSSLConfigService::AtomSSLConfigService() { @@ -35,6 +55,13 @@ AtomSSLConfigService::AtomSSLConfigService() { cmd_line->GetSwitchValueASCII(switches::kSSLVersionFallbackMin); config_.version_fallback_min = GetSSLProtocolVersion(version_string); } + + if (cmd_line->HasSwitch(switches::kCipherSuiteBlacklist)) { + auto cipher_strings = base::SplitString( + cmd_line->GetSwitchValueASCII(switches::kCipherSuiteBlacklist), + ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + config_.disabled_cipher_suites = ParseCipherSuites(cipher_strings); + } } AtomSSLConfigService::~AtomSSLConfigService() { diff --git a/atom/browser/atom_ssl_config_service.h b/atom/browser/net/atom_ssl_config_service.h similarity index 76% rename from atom/browser/atom_ssl_config_service.h rename to atom/browser/net/atom_ssl_config_service.h index 3fa91c62c438..2e3ac4f6c597 100644 --- a/atom/browser/atom_ssl_config_service.h +++ b/atom/browser/net/atom_ssl_config_service.h @@ -2,8 +2,8 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#ifndef ATOM_BROWSER_ATOM_SSL_CONFIG_SERVICE_H_ -#define ATOM_BROWSER_ATOM_SSL_CONFIG_SERVICE_H_ +#ifndef ATOM_BROWSER_NET_ATOM_SSL_CONFIG_SERVICE_H_ +#define ATOM_BROWSER_NET_ATOM_SSL_CONFIG_SERVICE_H_ #include "net/ssl/ssl_config_service.h" @@ -25,4 +25,4 @@ class AtomSSLConfigService : public net::SSLConfigService { } // namespace atom -#endif // ATOM_BROWSER_ATOM_SSL_CONFIG_SERVICE_H_ +#endif // ATOM_BROWSER_NET_ATOM_SSL_CONFIG_SERVICE_H_ diff --git a/atom/browser/net/js_asker.cc b/atom/browser/net/js_asker.cc index d838ae39638f..8f0d1d2b9577 100644 --- a/atom/browser/net/js_asker.cc +++ b/atom/browser/net/js_asker.cc @@ -8,7 +8,6 @@ #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 { @@ -16,34 +15,14 @@ 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)); - +void HandlerCallback(const ResponseCallback& callback, mate::Arguments* args) { // 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)); + base::Bind(callback, false, nullptr)); return; } @@ -53,42 +32,7 @@ void HandlerCallback(v8::Isolate* isolate, 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)); + base::Bind(callback, true, base::Passed(&options))); } } // namespace @@ -102,16 +46,9 @@ void AskForOptions(v8::Isolate* 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()); + handler.Run(request, + mate::ConvertToV8(isolate, + base::Bind(&HandlerCallback, callback))); } bool IsErrorOptions(base::Value* value, int* error) { diff --git a/atom/browser/net/url_request_async_asar_job.cc b/atom/browser/net/url_request_async_asar_job.cc index ba0189e5f6e0..3578f3b7971c 100644 --- a/atom/browser/net/url_request_async_asar_job.cc +++ b/atom/browser/net/url_request_async_asar_job.cc @@ -4,15 +4,19 @@ #include "atom/browser/net/url_request_async_asar_job.h" +#include + +#include "atom/common/atom_constants.h" + namespace atom { -UrlRequestAsyncAsarJob::UrlRequestAsyncAsarJob( +URLRequestAsyncAsarJob::URLRequestAsyncAsarJob( net::URLRequest* request, net::NetworkDelegate* network_delegate) : JsAsker(request, network_delegate) { } -void UrlRequestAsyncAsarJob::StartAsync(scoped_ptr options) { +void URLRequestAsyncAsarJob::StartAsync(scoped_ptr options) { base::FilePath::StringType file_path; if (options->IsType(base::Value::TYPE_DICTIONARY)) { static_cast(options.get())->GetString( @@ -34,4 +38,12 @@ void UrlRequestAsyncAsarJob::StartAsync(scoped_ptr options) { } } +void URLRequestAsyncAsarJob::GetResponseInfo(net::HttpResponseInfo* info) { + std::string status("HTTP/1.1 200 OK"); + net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status); + + headers->AddHeader(kCORSHeader); + info->headers = headers; +} + } // namespace atom diff --git a/atom/browser/net/url_request_async_asar_job.h b/atom/browser/net/url_request_async_asar_job.h index 748b96d84d28..d65142f0bdbb 100644 --- a/atom/browser/net/url_request_async_asar_job.h +++ b/atom/browser/net/url_request_async_asar_job.h @@ -11,15 +11,18 @@ namespace atom { // Like URLRequestAsarJob, but asks the JavaScript handler for file path. -class UrlRequestAsyncAsarJob : public JsAsker { +class URLRequestAsyncAsarJob : public JsAsker { public: - UrlRequestAsyncAsarJob(net::URLRequest*, net::NetworkDelegate*); + URLRequestAsyncAsarJob(net::URLRequest*, net::NetworkDelegate*); // JsAsker: void StartAsync(scoped_ptr options) override; + // URLRequestJob: + void GetResponseInfo(net::HttpResponseInfo* info) override; + private: - DISALLOW_COPY_AND_ASSIGN(UrlRequestAsyncAsarJob); + DISALLOW_COPY_AND_ASSIGN(URLRequestAsyncAsarJob); }; } // namespace atom diff --git a/atom/browser/net/url_request_buffer_job.cc b/atom/browser/net/url_request_buffer_job.cc index affc3dd37d25..aa273bf81693 100644 --- a/atom/browser/net/url_request_buffer_job.cc +++ b/atom/browser/net/url_request_buffer_job.cc @@ -6,6 +6,7 @@ #include +#include "atom/common/atom_constants.h" #include "base/strings/string_number_conversions.h" #include "net/base/net_errors.h" @@ -50,6 +51,8 @@ void URLRequestBufferJob::GetResponseInfo(net::HttpResponseInfo* info) { status.append("\0\0", 2); net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status); + headers->AddHeader(kCORSHeader); + if (!mime_type_.empty()) { std::string content_type_header(net::HttpRequestHeaders::kContentType); content_type_header.append(": "); diff --git a/atom/browser/net/url_request_fetch_job.cc b/atom/browser/net/url_request_fetch_job.cc index a8a16e286b30..f04ecd4060f0 100644 --- a/atom/browser/net/url_request_fetch_job.cc +++ b/atom/browser/net/url_request_fetch_job.cc @@ -14,6 +14,7 @@ #include "net/http/http_response_headers.h" #include "net/url_request/url_fetcher.h" #include "net/url_request/url_fetcher_response_writer.h" +#include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_builder.h" #include "net/url_request/url_request_status.h" @@ -23,7 +24,7 @@ namespace { // Convert string to RequestType. net::URLFetcher::RequestType GetRequestType(const std::string& raw) { - std::string method = base::StringToUpperASCII(raw); + std::string method = base::ToUpperASCII(raw); if (method.empty() || method == "GET") return net::URLFetcher::GET; else if (method == "POST") @@ -138,8 +139,9 @@ net::URLRequestContextGetter* URLRequestFetchJob::CreateRequestContext() { 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); + request_context_ = builder.Build(); + url_request_context_getter_ = new net::TrivialURLRequestContextGetter( + request_context_.get(), task_runner); } return url_request_context_getter_.get(); } @@ -181,6 +183,11 @@ void URLRequestFetchJob::Kill() { bool URLRequestFetchJob::ReadRawData(net::IOBuffer* dest, int dest_size, int* bytes_read) { + if (GetResponseCode() == 204) { + *bytes_read = 0; + request()->set_received_response_content_length(prefilter_bytes_read()); + return true; + } pending_buffer_ = dest; pending_buffer_size_ = dest_size; SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); @@ -207,6 +214,14 @@ int URLRequestFetchJob::GetResponseCode() const { } void URLRequestFetchJob::OnURLFetchComplete(const net::URLFetcher* source) { + if (!response_info_) { + // Since we notify header completion only after first write there will be + // no response object constructed for http respones with no content 204. + // We notify header completion here. + HeadersCompleted(); + return; + } + pending_buffer_ = nullptr; pending_buffer_size_ = 0; NotifyDone(fetcher_->GetStatus()); diff --git a/atom/browser/net/url_request_fetch_job.h b/atom/browser/net/url_request_fetch_job.h index 189cebf01b18..399f78ae3963 100644 --- a/atom/browser/net/url_request_fetch_job.h +++ b/atom/browser/net/url_request_fetch_job.h @@ -45,6 +45,7 @@ class URLRequestFetchJob : public JsAsker, // Create a independent request context. net::URLRequestContextGetter* CreateRequestContext(); + scoped_ptr request_context_; scoped_refptr url_request_context_getter_; scoped_ptr fetcher_; scoped_refptr pending_buffer_; diff --git a/atom/browser/net/url_request_string_job.cc b/atom/browser/net/url_request_string_job.cc index 4a631b813eff..606781142da0 100644 --- a/atom/browser/net/url_request_string_job.cc +++ b/atom/browser/net/url_request_string_job.cc @@ -6,6 +6,7 @@ #include +#include "atom/common/atom_constants.h" #include "net/base/net_errors.h" namespace atom { @@ -32,6 +33,8 @@ void URLRequestStringJob::GetResponseInfo(net::HttpResponseInfo* info) { std::string status("HTTP/1.1 200 OK"); net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status); + headers->AddHeader(kCORSHeader); + if (!mime_type_.empty()) { std::string content_type_header(net::HttpRequestHeaders::kContentType); content_type_header.append(": "); diff --git a/atom/browser/node_debugger.cc b/atom/browser/node_debugger.cc index da5602a88137..2cfcdb222aea 100644 --- a/atom/browser/node_debugger.cc +++ b/atom/browser/node_debugger.cc @@ -35,17 +35,14 @@ NodeDebugger::NodeDebugger(v8::Isolate* isolate) 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")) { + } else if (cmd->HasSwitch("debug-brk")) { use_debug_agent = true; - wait_for_connection = true; port_str = cmd->GetSwitchValueASCII("debug-brk"); } @@ -56,9 +53,6 @@ NodeDebugger::NodeDebugger(v8::Isolate* isolate) 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. diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index 6260173b8258..179f26db03f3 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,7 +17,11 @@ CFBundleIconFile atom.icns CFBundleVersion - 0.33.1 + 0.35.4 + CFBundleShortVersionString + 0.35.4 + LSApplicationCategoryType + public.app-category.developer-tools LSMinimumSystemVersion 10.8.0 NSMainNibFile diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index 0583c2df3be9..05fb3f336ff6 100644 --- a/atom/browser/resources/win/atom.rc +++ b/atom/browser/resources/win/atom.rc @@ -56,8 +56,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,33,1,0 - PRODUCTVERSION 0,33,1,0 + FILEVERSION 0,35,4,0 + PRODUCTVERSION 0,35,4,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "0.33.1" + VALUE "FileVersion", "0.35.4" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "0.33.1" + VALUE "ProductVersion", "0.35.4" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/browser/ui/accelerator_util.cc b/atom/browser/ui/accelerator_util.cc index e25e14b7968c..a0b90e0c7e58 100644 --- a/atom/browser/ui/accelerator_util.cc +++ b/atom/browser/ui/accelerator_util.cc @@ -24,10 +24,10 @@ bool StringToAccelerator(const std::string& description, LOG(ERROR) << "The accelerator string can only contain ASCII characters"; return false; } - std::string shortcut(base::StringToLowerASCII(description)); + std::string shortcut(base::ToLowerASCII(description)); - std::vector tokens; - base::SplitString(shortcut, '+', &tokens); + std::vector tokens = base::SplitString( + shortcut, "+", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); // Now, parse it into an accelerator. int modifiers = ui::EF_NONE; diff --git a/atom/browser/ui/cocoa/event_processing_window.h b/atom/browser/ui/cocoa/event_processing_window.h deleted file mode 100644 index 88242711f8b7..000000000000 --- a/atom/browser/ui/cocoa/event_processing_window.h +++ /dev/null @@ -1,30 +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_UI_COCOA_EVENT_PROCESSING_WINDOW_H_ -#define ATOM_BROWSER_UI_COCOA_EVENT_PROCESSING_WINDOW_H_ - -#import - -// Override NSWindow to access unhandled keyboard events (for command -// processing); subclassing NSWindow is the only method to do -// this. -@interface EventProcessingWindow : NSWindow { - @private - BOOL redispatchingEvent_; - BOOL eventHandled_; -} - -// Sends a key event to |NSApp sendEvent:|, but also makes sure that it's not -// short-circuited to the RWHV. This is used to send keyboard events to the menu -// and the cmd-` handler if a keyboard event comes back unhandled from the -// renderer. The event must be of type |NSKeyDown|, |NSKeyUp|, or -// |NSFlagsChanged|. -// Returns |YES| if |event| has been handled. -- (BOOL)redispatchKeyEvent:(NSEvent*)event; - -- (BOOL)performKeyEquivalent:(NSEvent*)theEvent; -@end - -#endif // ATOM_BROWSER_UI_COCOA_EVENT_PROCESSING_WINDOW_H_ diff --git a/atom/browser/ui/cocoa/event_processing_window.mm b/atom/browser/ui/cocoa/event_processing_window.mm deleted file mode 100644 index d47cdf37b508..000000000000 --- a/atom/browser/ui/cocoa/event_processing_window.mm +++ /dev/null @@ -1,106 +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. - -#import "atom/browser/ui/cocoa/event_processing_window.h" - -#include "base/logging.h" -#import "content/public/browser/render_widget_host_view_mac_base.h" - -@interface EventProcessingWindow () -// Duplicate the given key event, but changing the associated window. -- (NSEvent*)keyEventForWindow:(NSWindow*)window fromKeyEvent:(NSEvent*)event; -@end - -@implementation EventProcessingWindow - -- (BOOL)redispatchKeyEvent:(NSEvent*)event { - DCHECK(event); - NSEventType eventType = [event type]; - if (eventType != NSKeyDown && - eventType != NSKeyUp && - eventType != NSFlagsChanged) { - NOTREACHED(); - return YES; // Pretend it's been handled in an effort to limit damage. - } - - // Ordinarily, the event's window should be this window. However, when - // switching between normal and fullscreen mode, we switch out the window, and - // the event's window might be the previous window (or even an earlier one if - // the renderer is running slowly and several mode switches occur). In this - // rare case, we synthesize a new key event so that its associate window - // (number) is our own. - if ([event window] != self) - event = [self keyEventForWindow:self fromKeyEvent:event]; - - // Redispatch the event. - eventHandled_ = YES; - redispatchingEvent_ = YES; - [NSApp sendEvent:event]; - redispatchingEvent_ = NO; - - // If the event was not handled by [NSApp sendEvent:], the sendEvent: - // method below will be called, and because |redispatchingEvent_| is YES, - // |eventHandled_| will be set to NO. - return eventHandled_; -} - -- (void)sendEvent:(NSEvent*)event { - if (!redispatchingEvent_) - [super sendEvent:event]; - else - eventHandled_ = NO; -} - -- (NSEvent*)keyEventForWindow:(NSWindow*)window fromKeyEvent:(NSEvent*)event { - NSEventType eventType = [event type]; - - // Convert the event's location from the original window's coordinates into - // our own. - NSPoint eventLoc = [event locationInWindow]; - eventLoc = [self convertRectFromScreen: - [[event window] convertRectToScreen:NSMakeRect(eventLoc.x, eventLoc.y, 0, 0)]].origin; - - // Various things *only* apply to key down/up. - BOOL eventIsARepeat = NO; - NSString* eventCharacters = nil; - NSString* eventUnmodCharacters = nil; - if (eventType == NSKeyDown || eventType == NSKeyUp) { - eventIsARepeat = [event isARepeat]; - eventCharacters = [event characters]; - eventUnmodCharacters = [event charactersIgnoringModifiers]; - } - - // This synthesis may be slightly imperfect: we provide nil for the context, - // since I (viettrungluu) am sceptical that putting in the original context - // (if one is given) is valid. - return [NSEvent keyEventWithType:eventType - location:eventLoc - modifierFlags:[event modifierFlags] - timestamp:[event timestamp] - windowNumber:[window windowNumber] - context:nil - characters:eventCharacters - charactersIgnoringModifiers:eventUnmodCharacters - isARepeat:eventIsARepeat - keyCode:[event keyCode]]; -} - - -- (BOOL)performKeyEquivalent:(NSEvent*)event { - if (redispatchingEvent_) - return NO; - - // Give the web site a chance to handle the event. If it doesn't want to - // handle it, it will call us back with one of the |handle*| methods above. - NSResponder* r = [self firstResponder]; - if ([r conformsToProtocol:@protocol(RenderWidgetHostViewMacBase)]) - return [r performKeyEquivalent:event]; - - if ([super performKeyEquivalent:event]) - return YES; - - return NO; -} - -@end // EventProcessingWindow diff --git a/atom/browser/ui/file_dialog_gtk.cc b/atom/browser/ui/file_dialog_gtk.cc index 5885ffe3611c..ed79449655f0 100644 --- a/atom/browser/ui/file_dialog_gtk.cc +++ b/atom/browser/ui/file_dialog_gtk.cc @@ -22,7 +22,9 @@ gboolean FileFilterCaseInsensitive(const GtkFileFilterInfo* file_info, // Makes .* file extension matches all file types. if (*file_extension == ".*") return true; - return base::EndsWith(file_info->filename, *file_extension, false); + return base::EndsWith( + file_info->filename, + *file_extension, base::CompareCase::INSENSITIVE_ASCII); } // Deletes |data| when gtk_file_filter_add_custom() is done with it. diff --git a/atom/browser/ui/file_dialog_win.cc b/atom/browser/ui/file_dialog_win.cc index b169471c6e99..f39094f9dfc5 100644 --- a/atom/browser/ui/file_dialog_win.cc +++ b/atom/browser/ui/file_dialog_win.cc @@ -51,7 +51,7 @@ void ConvertFilters(const Filters& filters, std::vector extensions(filter.second); for (size_t j = 0; j < extensions.size(); ++j) extensions[j].insert(0, "*."); - buffer->push_back(base::UTF8ToWide(JoinString(extensions, ";"))); + buffer->push_back(base::UTF8ToWide(base::JoinString(extensions, ";"))); spec.pszSpec = buffer->back().c_str(); filterspec->push_back(spec); @@ -79,6 +79,26 @@ class FileDialog { if (!title.empty()) GetPtr()->SetTitle(base::UTF8ToUTF16(title).c_str()); + // By default, *.* will be added to the file name if file type is "*.*". In + // Electron, we disable it to make a better experience. + // + // From MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/ + // bb775970(v=vs.85).aspx + // + // If SetDefaultExtension is not called, the dialog will not update + // automatically when user choose a new file type in the file dialog. + // + // We set file extension to the first none-wildcard extension to make + // sure the dialog will update file extension automatically. + for (size_t i = 0; i < filterspec.size(); ++i) { + if (std::wstring(filterspec[i].pszSpec) != L"*.*") { + // SetFileTypeIndex is regarded as one-based index. + GetPtr()->SetFileTypeIndex(i+1); + GetPtr()->SetDefaultExtension(filterspec[i].pszSpec); + break; + } + } + SetDefaultFolder(default_path); } @@ -252,7 +272,10 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, bool matched = false; for (size_t i = 0; i < filter.second.size(); ++i) { - if (base::EndsWith(file_name, filter.second[i], false)) { + if (filter.second[i] == "*" || + base::EndsWith( + file_name, filter.second[i], + base::CompareCase::INSENSITIVE_ASCII)) { matched = true; break;; } diff --git a/atom/browser/ui/message_box_gtk.cc b/atom/browser/ui/message_box_gtk.cc index 41682190e60e..de8d994e5bca 100644 --- a/atom/browser/ui/message_box_gtk.cc +++ b/atom/browser/ui/message_box_gtk.cc @@ -92,7 +92,7 @@ class GtkMessageBox { } const char* TranslateToStock(int id, const std::string& text) { - std::string lower = base::StringToLowerASCII(text); + std::string lower = base::ToLowerASCII(text); if (lower == "cancel") return GTK_STOCK_CANCEL; else if (lower == "no") diff --git a/atom/browser/ui/message_box_win.cc b/atom/browser/ui/message_box_win.cc index 697a7ad410a2..656be9f10bb2 100644 --- a/atom/browser/ui/message_box_win.cc +++ b/atom/browser/ui/message_box_win.cc @@ -34,7 +34,7 @@ struct CommonButtonID { int id; }; CommonButtonID GetCommonID(const base::string16& button) { - base::string16 lower = base::StringToLowerASCII(button); + base::string16 lower = base::ToLowerASCII(button); if (lower == L"ok") return { TDCBF_OK_BUTTON, IDOK }; else if (lower == L"yes") diff --git a/atom/browser/ui/tray_icon.cc b/atom/browser/ui/tray_icon.cc index 12c6be2ea74e..60923c2ad0a6 100644 --- a/atom/browser/ui/tray_icon.cc +++ b/atom/browser/ui/tray_icon.cc @@ -26,7 +26,8 @@ void TrayIcon::DisplayBalloon(const gfx::Image& icon, const base::string16& contents) { } -void TrayIcon::PopUpContextMenu(const gfx::Point& pos) { +void TrayIcon::PopUpContextMenu(const gfx::Point& pos, + ui::SimpleMenuModel* menu_model) { } void TrayIcon::NotifyClicked(const gfx::Rect& bounds, int modifiers) { @@ -55,8 +56,24 @@ void TrayIcon::NotifyRightClicked(const gfx::Rect& bounds, int modifiers) { OnRightClicked(bounds, modifiers)); } -void TrayIcon::NotfiyDropFiles(const std::vector& files) { +void TrayIcon::NotifyDrop() { + FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnDrop()); +} + +void TrayIcon::NotifyDropFiles(const std::vector& files) { FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnDropFiles(files)); } +void TrayIcon::NotifyDragEntered() { + FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnDragEntered()); +} + +void TrayIcon::NotifyDragExited() { + FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnDragExited()); +} + +void TrayIcon::NotifyDragEnded() { + FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnDragEnded()); +} + } // namespace atom diff --git a/atom/browser/ui/tray_icon.h b/atom/browser/ui/tray_icon.h index af774ddbfb42..c80ff08d6a52 100644 --- a/atom/browser/ui/tray_icon.h +++ b/atom/browser/ui/tray_icon.h @@ -47,7 +47,9 @@ class TrayIcon { const base::string16& title, const base::string16& contents); - virtual void PopUpContextMenu(const gfx::Point& pos); + // Popups the menu. + virtual void PopUpContextMenu(const gfx::Point& pos, + ui::SimpleMenuModel* menu_model); // Set the context menu for this icon. virtual void SetContextMenu(ui::SimpleMenuModel* menu_model) = 0; @@ -61,7 +63,11 @@ class TrayIcon { void NotifyBalloonClosed(); void NotifyRightClicked(const gfx::Rect& bounds = gfx::Rect(), int modifiers = 0); - void NotfiyDropFiles(const std::vector& files); + void NotifyDrop(); + void NotifyDropFiles(const std::vector& files); + void NotifyDragEntered(); + void NotifyDragExited(); + void NotifyDragEnded(); protected: TrayIcon(); diff --git a/atom/browser/ui/tray_icon_cocoa.h b/atom/browser/ui/tray_icon_cocoa.h index 7781c93a1c03..59e2241aa480 100644 --- a/atom/browser/ui/tray_icon_cocoa.h +++ b/atom/browser/ui/tray_icon_cocoa.h @@ -29,7 +29,8 @@ 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 PopUpContextMenu(const gfx::Point& pos, + ui::SimpleMenuModel* menu_model) override; void SetContextMenu(ui::SimpleMenuModel* menu_model) override; protected: diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index ec6a6a3e1964..997ac6fd3134 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -23,6 +23,7 @@ const CGFloat kVerticalTitleMargin = 2; atom::TrayIconCocoa* trayIcon_; // weak AtomMenuController* menuController_; // weak BOOL isHighlightEnable_; + BOOL forceHighlight_; BOOL inMouseEventSequence_; base::scoped_nsobject image_; base::scoped_nsobject alternateImage_; @@ -39,16 +40,12 @@ const CGFloat kVerticalTitleMargin = 2; image_.reset([image copy]); trayIcon_ = icon; isHighlightEnable_ = YES; + forceHighlight_ = NO; + inMouseEventSequence_ = NO; - // Get the initial size. - NSStatusBar* statusBar = [NSStatusBar systemStatusBar]; - NSRect frame = NSMakeRect(0, 0, [self fullWidth], [statusBar thickness]); - - if ((self = [super initWithFrame:frame])) { + if ((self = [super initWithFrame: CGRectZero])) { // Setup the image view. - NSRect iconFrame = frame; - iconFrame.size.width = [self iconWidth]; - image_view_.reset([[NSImageView alloc] initWithFrame:iconFrame]); + image_view_.reset([[NSImageView alloc] initWithFrame: CGRectZero]); [image_view_ setImageScaling:NSImageScaleNone]; [image_view_ setImageAlignment:NSImageAlignCenter]; [self addSubview:image_view_]; @@ -56,17 +53,27 @@ const CGFloat kVerticalTitleMargin = 2; // 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]; + [self registerForDraggedTypes: @[NSFilenamesPboardType]]; // Create the status item. - statusItem_.reset([[[NSStatusBar systemStatusBar] - statusItemWithLength:NSWidth(frame)] retain]); + NSStatusItem * item = [[NSStatusBar systemStatusBar] + statusItemWithLength:NSVariableStatusItemLength]; + statusItem_.reset([item retain]); [statusItem_ setView:self]; + + // Finalize setup by sizing our views + [self updateDimensions]; } return self; } +- (void)updateDimensions { + NSStatusBar * bar = [NSStatusBar systemStatusBar]; + [image_view_ setFrame: NSMakeRect(0, 0, [self iconWidth], [bar thickness])]; + [self setFrame: NSMakeRect(0, 0, [self fullWidth], [bar thickness])]; + [self setNeedsDisplay:YES]; +} + - (void)removeItem { [[NSStatusBar systemStatusBar] removeStatusItem:statusItem_]; statusItem_.reset(); @@ -81,9 +88,7 @@ const CGFloat kVerticalTitleMargin = 2; // Draw background. BOOL highlight = [self shouldHighlight]; CGFloat thickness = [[statusItem_ statusBar] thickness]; - NSRect statusItemBounds = NSMakeRect(0, 0, [statusItem_ length], thickness); - [statusItem_ drawStatusBarBackgroundInRect:statusItemBounds - withHighlight:highlight]; + [statusItem_ drawStatusBarBackgroundInRect:self.bounds withHighlight:highlight]; // Make use of NSImageView to draw the image, which can correctly draw // template image under dark menu bar. @@ -157,10 +162,10 @@ const CGFloat kVerticalTitleMargin = 2; 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]; + return @{ + NSFontAttributeName: font, + NSForegroundColorAttributeName: foregroundColor + }; } - (NSDictionary*)titleAttributes { @@ -169,7 +174,7 @@ const CGFloat kVerticalTitleMargin = 2; - (void)setImage:(NSImage*)image { image_.reset([image copy]); - [self setNeedsDisplay:YES]; + [self updateDimensions]; } - (void)setAlternateImage:(NSImage*)image { @@ -181,12 +186,12 @@ const CGFloat kVerticalTitleMargin = 2; } - (void)setTitle:(NSString*)title { - if (title.length > 0) + if (title.length > 0) { title_.reset([title copy]); - else + } else { title_.reset(); - [statusItem_ setLength:[self fullWidth]]; - [self setNeedsDisplay:YES]; + } + [self updateDimensions]; } - (void)setMenuController:(AtomMenuController*)menu { @@ -236,7 +241,19 @@ const CGFloat kVerticalTitleMargin = 2; [self setNeedsDisplay:YES]; } -- (void)popUpContextMenu { +- (void)popUpContextMenu:(ui::SimpleMenuModel*)menu_model { + // Show a custom menu. + if (menu_model) { + base::scoped_nsobject menuController( + [[AtomMenuController alloc] initWithModel:menu_model]); + forceHighlight_ = YES; // Should highlight when showing menu. + [self setNeedsDisplay:YES]; + [statusItem_ popUpStatusItemMenu:[menuController menu]]; + forceHighlight_ = NO; + [self setNeedsDisplay:YES]; + return; + } + if (menuController_ && ![menuController_ isMenuOpen]) { // Redraw the dray icon to show highlight if it is enabled. [self setNeedsDisplay:YES]; @@ -254,9 +271,23 @@ const CGFloat kVerticalTitleMargin = 2; } - (NSDragOperation)draggingEntered:(id )sender { + trayIcon_->NotifyDragEntered(); return NSDragOperationCopy; } +- (void)draggingExited:(id )sender { + trayIcon_->NotifyDragExited(); +} + +- (void)draggingEnded:(id )sender { + trayIcon_->NotifyDragEnded(); +} + +- (BOOL)prepareForDragOperation:(id )sender { + trayIcon_->NotifyDrop(); + return YES; +} + - (BOOL)performDragOperation:(id )sender { NSPasteboard* pboard = [sender draggingPasteboard]; @@ -265,13 +296,15 @@ const CGFloat kVerticalTitleMargin = 2; NSArray* files = [pboard propertyListForType:NSFilenamesPboardType]; for (NSString* file in files) dropFiles.push_back(base::SysNSStringToUTF8(file)); - trayIcon_->NotfiyDropFiles(dropFiles); + trayIcon_->NotifyDropFiles(dropFiles); return YES; } return NO; } - (BOOL)shouldHighlight { + if (isHighlightEnable_ && forceHighlight_) + return true; BOOL isMenuOpen = menuController_ && [menuController_ isMenuOpen]; return isHighlightEnable_ && (inMouseEventSequence_ || isMenuOpen); } @@ -322,8 +355,9 @@ void TrayIconCocoa::SetHighlightMode(bool highlight) { [status_item_view_ setHighlight:highlight]; } -void TrayIconCocoa::PopUpContextMenu(const gfx::Point& pos) { - [status_item_view_ popUpContextMenu]; +void TrayIconCocoa::PopUpContextMenu(const gfx::Point& pos, + ui::SimpleMenuModel* menu_model) { + [status_item_view_ popUpContextMenu:menu_model]; } void TrayIconCocoa::SetContextMenu(ui::SimpleMenuModel* menu_model) { diff --git a/atom/browser/ui/tray_icon_observer.h b/atom/browser/ui/tray_icon_observer.h index fa8090d7d6c5..ed421ed85452 100644 --- a/atom/browser/ui/tray_icon_observer.h +++ b/atom/browser/ui/tray_icon_observer.h @@ -22,7 +22,11 @@ class TrayIconObserver { virtual void OnBalloonClicked() {} virtual void OnBalloonClosed() {} virtual void OnRightClicked(const gfx::Rect& bounds, int modifiers) {} + virtual void OnDrop() {} virtual void OnDropFiles(const std::vector& files) {} + virtual void OnDragEntered() {} + virtual void OnDragExited() {} + virtual void OnDragEnded() {} protected: virtual ~TrayIconObserver() {} diff --git a/atom/browser/ui/views/frameless_view.cc b/atom/browser/ui/views/frameless_view.cc index 03a31e082874..2ec4459f6b45 100644 --- a/atom/browser/ui/views/frameless_view.cc +++ b/atom/browser/ui/views/frameless_view.cc @@ -104,11 +104,11 @@ gfx::Size FramelessView::GetPreferredSize() const { } gfx::Size FramelessView::GetMinimumSize() const { - return window_->GetMinimumSize(); + return window_->GetContentSizeConstraints().GetMinimumSize(); } gfx::Size FramelessView::GetMaximumSize() const { - return window_->GetMaximumSize(); + return window_->GetContentSizeConstraints().GetMaximumSize(); } const char* FramelessView::GetClassName() const { diff --git a/atom/browser/ui/views/menu_bar.cc b/atom/browser/ui/views/menu_bar.cc index d3059a50a41b..b7712929d024 100644 --- a/atom/browser/ui/views/menu_bar.cc +++ b/atom/browser/ui/views/menu_bar.cc @@ -17,7 +17,6 @@ #if defined(OS_WIN) #include "ui/gfx/color_utils.h" #elif defined(USE_X11) -#include "chrome/browser/ui/libgtk2ui/owned_widget_gtk2.h" #include "chrome/browser/ui/libgtk2ui/skia_utils_gtk2.h" #endif @@ -33,15 +32,16 @@ const SkColor kDefaultColor = SkColorSetARGB(255, 233, 233, 233); #if defined(USE_X11) void GetMenuBarColor(SkColor* enabled, SkColor* disabled, SkColor* highlight, SkColor* hover, SkColor* background) { - libgtk2ui::OwnedWidgetGtk fake_menu_bar; - fake_menu_bar.Own(gtk_menu_bar_new()); + GtkWidget* menu_bar = gtk_menu_bar_new(); - GtkStyle* style = gtk_rc_get_style(fake_menu_bar.get()); + GtkStyle* style = gtk_rc_get_style(menu_bar); *enabled = libgtk2ui::GdkColorToSkColor(style->fg[GTK_STATE_NORMAL]); *disabled = libgtk2ui::GdkColorToSkColor(style->fg[GTK_STATE_INSENSITIVE]); *highlight = libgtk2ui::GdkColorToSkColor(style->fg[GTK_STATE_SELECTED]); *hover = libgtk2ui::GdkColorToSkColor(style->fg[GTK_STATE_PRELIGHT]); *background = libgtk2ui::GdkColorToSkColor(style->bg[GTK_STATE_NORMAL]); + + gtk_widget_destroy(menu_bar); } #endif diff --git a/atom/browser/ui/views/native_frame_view.cc b/atom/browser/ui/views/native_frame_view.cc index a434fb434961..134255f48458 100644 --- a/atom/browser/ui/views/native_frame_view.cc +++ b/atom/browser/ui/views/native_frame_view.cc @@ -4,7 +4,7 @@ #include "atom/browser/ui/views/native_frame_view.h" -#include "atom/browser/native_window_views.h" +#include "atom/browser/native_window.h" namespace atom { @@ -14,8 +14,7 @@ const char kViewClassName[] = "AtomNativeFrameView"; } // namespace -NativeFrameView::NativeFrameView(NativeWindowViews* window, - views::Widget* widget) +NativeFrameView::NativeFrameView(NativeWindow* window, views::Widget* widget) : views::NativeFrameView(widget), window_(window) { } diff --git a/atom/browser/ui/views/native_frame_view.h b/atom/browser/ui/views/native_frame_view.h index acbe9cddc8dc..670459f1cbd0 100644 --- a/atom/browser/ui/views/native_frame_view.h +++ b/atom/browser/ui/views/native_frame_view.h @@ -9,13 +9,13 @@ namespace atom { -class NativeWindowViews; +class NativeWindow; // 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); + NativeFrameView(NativeWindow* window, views::Widget* widget); protected: // views::View: @@ -24,7 +24,7 @@ class NativeFrameView : public views::NativeFrameView { const char* GetClassName() const override; private: - NativeWindowViews* window_; // weak ref. + NativeWindow* window_; // weak ref. DISALLOW_COPY_AND_ASSIGN(NativeFrameView); }; diff --git a/atom/browser/ui/views/win_frame_view.cc b/atom/browser/ui/views/win_frame_view.cc index db74661932b7..fca7cb23347c 100644 --- a/atom/browser/ui/views/win_frame_view.cc +++ b/atom/browser/ui/views/win_frame_view.cc @@ -5,7 +5,6 @@ #include "atom/browser/ui/views/win_frame_view.h" #include "atom/browser/native_window_views.h" -#include "ui/gfx/win/dpi.h" #include "ui/views/widget/widget.h" #include "ui/views/win/hwnd_util.h" @@ -39,16 +38,6 @@ int WinFrameView::NonClientHitTest(const gfx::Point& point) { return FramelessView::NonClientHitTest(point); } -gfx::Size WinFrameView::GetMinimumSize() const { - gfx::Size size = FramelessView::GetMinimumSize(); - return gfx::win::DIPToScreenSize(size); -} - -gfx::Size WinFrameView::GetMaximumSize() const { - gfx::Size size = FramelessView::GetMaximumSize(); - return gfx::win::DIPToScreenSize(size); -} - const char* WinFrameView::GetClassName() const { return kViewClassName; } diff --git a/atom/browser/ui/views/win_frame_view.h b/atom/browser/ui/views/win_frame_view.h index 825677bff310..b2c1ef3a15de 100644 --- a/atom/browser/ui/views/win_frame_view.h +++ b/atom/browser/ui/views/win_frame_view.h @@ -20,8 +20,6 @@ class WinFrameView : public FramelessView { int NonClientHitTest(const gfx::Point& point) override; // views::View: - gfx::Size GetMinimumSize() const override; - gfx::Size GetMaximumSize() const override; const char* GetClassName() const override; private: diff --git a/atom/browser/ui/win/notify_icon.cc b/atom/browser/ui/win/notify_icon.cc index c88d4c810ef8..1ac29f1360cb 100644 --- a/atom/browser/ui/win/notify_icon.cc +++ b/atom/browser/ui/win/notify_icon.cc @@ -13,6 +13,7 @@ #include "ui/gfx/image/image.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" +#include "ui/gfx/screen.h" #include "ui/views/controls/menu/menu_runner.h" namespace atom { @@ -45,8 +46,7 @@ NotifyIcon::~NotifyIcon() { Shell_NotifyIcon(NIM_DELETE, &icon_data); } -void NotifyIcon::HandleClickEvent(const gfx::Point& cursor_pos, - int modifiers, +void NotifyIcon::HandleClickEvent(int modifiers, bool left_mouse_click, bool double_button_click) { NOTIFYICONIDENTIFIER icon_id; @@ -66,7 +66,7 @@ void NotifyIcon::HandleClickEvent(const gfx::Point& cursor_pos, return; } else if (!double_button_click) { // single right click if (menu_model_) - PopUpContextMenu(cursor_pos); + PopUpContextMenu(gfx::Point(), menu_model_); else NotifyRightClicked(gfx::Rect(rect), modifiers); } @@ -113,7 +113,7 @@ void NotifyIcon::SetToolTip(const std::string& tool_tip) { NOTIFYICONDATA icon_data; InitIconData(&icon_data); icon_data.uFlags |= NIF_TIP; - wcscpy_s(icon_data.szTip, base::UTF8ToUTF16(tool_tip).c_str()); + wcsncpy_s(icon_data.szTip, base::UTF8ToUTF16(tool_tip).c_str(), _TRUNCATE); BOOL result = Shell_NotifyIcon(NIM_MODIFY, &icon_data); if (!result) LOG(WARNING) << "Unable to set tooltip for status tray icon"; @@ -126,8 +126,8 @@ void NotifyIcon::DisplayBalloon(const gfx::Image& icon, InitIconData(&icon_data); 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()); + wcsncpy_s(icon_data.szInfoTitle, title.c_str(), _TRUNCATE); + wcsncpy_s(icon_data.szInfo, contents.c_str(), _TRUNCATE); icon_data.uTimeout = 0; base::win::Version win_version = base::win::GetVersion(); @@ -142,24 +142,26 @@ void NotifyIcon::DisplayBalloon(const gfx::Image& icon, LOG(WARNING) << "Unable to create status tray balloon."; } -void NotifyIcon::PopUpContextMenu(const gfx::Point& pos) { +void NotifyIcon::PopUpContextMenu(const gfx::Point& pos, + ui::SimpleMenuModel* menu_model) { // Returns if context menu isn't set. - if (!menu_model_) + 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; + // Show menu at mouse's position by default. + gfx::Rect rect(pos, gfx::Size()); + if (pos.IsOrigin()) + rect.set_origin(gfx::Screen::GetNativeScreen()->GetCursorScreenPoint()); + views::MenuRunner menu_runner( - menu_model_, + 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)); + NULL, NULL, rect, views::MENU_ANCHOR_TOPLEFT, ui::MENU_SOURCE_MOUSE)); } void NotifyIcon::SetContextMenu(ui::SimpleMenuModel* menu_model) { diff --git a/atom/browser/ui/win/notify_icon.h b/atom/browser/ui/win/notify_icon.h index d368dec71327..23608c7c7ab6 100644 --- a/atom/browser/ui/win/notify_icon.h +++ b/atom/browser/ui/win/notify_icon.h @@ -33,8 +33,7 @@ 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, - int modifiers, + void HandleClickEvent(int modifiers, bool left_button_click, bool double_button_click); @@ -52,7 +51,8 @@ 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 PopUpContextMenu(const gfx::Point& pos, + ui::SimpleMenuModel* menu_model) override; void SetContextMenu(ui::SimpleMenuModel* menu_model) override; private: diff --git a/atom/browser/ui/win/notify_icon_host.cc b/atom/browser/ui/win/notify_icon_host.cc index 2c84837e714d..a0d4287ff614 100644 --- a/atom/browser/ui/win/notify_icon_host.cc +++ b/atom/browser/ui/win/notify_icon_host.cc @@ -15,7 +15,6 @@ #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" namespace atom { @@ -172,10 +171,7 @@ LRESULT CALLBACK NotifyIconHost::WndProc(HWND hwnd, 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, GetKeyboardModifers(), (lparam == WM_LBUTTONDOWN || lparam == WM_LBUTTONDBLCLK), (lparam == WM_LBUTTONDBLCLK || lparam == WM_RBUTTONDBLCLK)); diff --git a/atom/browser/ui/win/taskbar_host.cc b/atom/browser/ui/win/taskbar_host.cc index a8e6ff2926cd..0d250829110f 100644 --- a/atom/browser/ui/win/taskbar_host.cc +++ b/atom/browser/ui/win/taskbar_host.cc @@ -97,7 +97,8 @@ bool TaskbarHost::SetThumbarButtons( // Set tooltip. if (!button.tooltip.empty()) { thumb_button.dwMask |= THB_TOOLTIP; - wcscpy_s(thumb_button.szTip, base::UTF8ToUTF16(button.tooltip).c_str()); + wcsncpy_s(thumb_button.szTip, base::UTF8ToUTF16(button.tooltip).c_str(), + _TRUNCATE); } // Save callback. diff --git a/atom/browser/ui/x/x_window_utils.cc b/atom/browser/ui/x/x_window_utils.cc index e57122839cd8..db83753bb376 100644 --- a/atom/browser/ui/x/x_window_utils.cc +++ b/atom/browser/ui/x/x_window_utils.cc @@ -6,6 +6,7 @@ #include +#include "base/environment.h" #include "base/strings/string_util.h" #include "dbus/bus.h" #include "dbus/object_proxy.h" @@ -41,7 +42,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 + base::StringToUpperASCII(type)).c_str(), False); + xdisplay, (type_prefix + base::ToUpperASCII(type)).c_str(), False); XChangeProperty(xdisplay, xwindow, XInternAtom(xdisplay, "_NET_WM_WINDOW_TYPE", False), XA_ATOM, @@ -50,6 +51,10 @@ void SetWindowType(::Window xwindow, const std::string& type) { } bool ShouldUseGlobalMenuBar() { + scoped_ptr env(base::Environment::Create()); + if (env->HasVar("ELECTRON_FORCE_WINDOW_MENU_BAR")) + return false; + dbus::Bus::Options options; scoped_refptr bus(new dbus::Bus(options)); diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index 2856598c0bc0..3d86df96dd76 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -6,10 +6,12 @@ #include +#include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/options_switches.h" #include "base/command_line.h" #include "base/strings/string_number_conversions.h" #include "content/public/common/web_preferences.h" +#include "native_mate/dictionary.h" #include "net/base/filename_util.h" #if defined(OS_WIN) @@ -23,26 +25,37 @@ 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, +struct FeaturePair { + const char* name; + const char* cmd; +}; +FeaturePair kWebRuntimeFeatures[] = { + { options::kExperimentalFeatures, + switches::kExperimentalFeatures }, + { options::kExperimentalCanvasFeatures, + switches::kExperimentalCanvasFeatures }, + { options::kOverlayScrollbars, + switches::kOverlayScrollbars }, + { options::kSharedWorker, + switches::kSharedWorker }, + { options::kPageVisibility, + switches::kPageVisibility }, }; } // namespace WebContentsPreferences::WebContentsPreferences( content::WebContents* web_contents, - base::DictionaryValue* web_preferences) { - web_preferences_.Swap(web_preferences); - web_contents->SetUserData(UserDataKey(), this); + const mate::Dictionary& web_preferences) { + v8::Isolate* isolate = web_preferences.isolate(); + mate::Dictionary copied(isolate, web_preferences.GetHandle()->Clone()); + // Following fields should not be stored. + copied.Delete("embedder"); + copied.Delete("isGuest"); + copied.Delete("session"); - // The "isGuest" is not a preferences field. - web_preferences_.Remove("isGuest", nullptr); + mate::ConvertFromV8(isolate, copied.GetHandle(), &web_preferences_); + web_contents->SetUserData(UserDataKey(), this); } WebContentsPreferences::~WebContentsPreferences() { @@ -64,7 +77,7 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( bool b; #if defined(OS_WIN) // Check if DirectWrite is disabled. - if (web_preferences.GetBoolean(switches::kDirectWrite, &b) && !b) + if (web_preferences.GetBoolean(options::kDirectWrite, &b) && !b) command_line->AppendSwitch(::switches::kDisableDirectWrite); #endif @@ -75,17 +88,17 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( // 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"); + const auto& feature = kWebRuntimeFeatures[i]; + if (web_preferences.GetBoolean(feature.name, &b)) + command_line->AppendSwitchASCII(feature.cmd, b ? "true" : "false"); } // Check if we have node integration specified. bool node_integration = true; - web_preferences.GetBoolean(switches::kNodeIntegration, &node_integration); + web_preferences.GetBoolean(options::kNodeIntegration, &node_integration); // Be compatible with old API of "node-integration" option. std::string old_token; - if (web_preferences.GetString(switches::kNodeIntegration, &old_token) && + if (web_preferences.GetString(options::kNodeIntegration, &old_token) && old_token != "disable") node_integration = true; command_line->AppendSwitchASCII(switches::kNodeIntegration, @@ -93,12 +106,12 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( // The preload script. base::FilePath::StringType preload; - if (web_preferences.GetString(switches::kPreloadScript, &preload)) { + if (web_preferences.GetString(options::kPreloadScript, &preload)) { if (base::FilePath(preload).IsAbsolute()) command_line->AppendSwitchNative(switches::kPreloadScript, preload); else LOG(ERROR) << "preload script must have absolute path."; - } else if (web_preferences.GetString(switches::kPreloadUrl, &preload)) { + } else if (web_preferences.GetString(options::kPreloadURL, &preload)) { // Translate to file path if there is "preload-url" option. base::FilePath preload_path; if (net::FileURLToFilePath(GURL(preload), &preload_path)) @@ -109,15 +122,14 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( // The zoom factor. double zoom_factor = 1.0; - if (web_preferences.GetDouble(switches::kZoomFactor, &zoom_factor) && + if (web_preferences.GetDouble(options::kZoomFactor, &zoom_factor) && zoom_factor != 1.0) command_line->AppendSwitchASCII(switches::kZoomFactor, base::DoubleToString(zoom_factor)); // --guest-instance-id, which is used to identify guest WebContents. int guest_instance_id; - if (web_preferences.GetInteger(switches::kGuestInstanceID, - &guest_instance_id)) + if (web_preferences.GetInteger(options::kGuestInstanceID, &guest_instance_id)) command_line->AppendSwitchASCII(switches::kGuestInstanceID, base::IntToString(guest_instance_id)); } @@ -134,23 +146,20 @@ void WebContentsPreferences::OverrideWebkitPrefs( 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)) + if (self->web_preferences_.GetBoolean("textAreasAreResizable", &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)) { + if (self->web_preferences_.GetBoolean("webSecurity", &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)) + if (self->web_preferences_.GetBoolean("allowDisplayingInsecureContent", &b)) prefs->allow_displaying_insecure_content = b; - if (self->web_preferences_.GetBoolean("allow-running-insecure-content", &b)) + if (self->web_preferences_.GetBoolean("allowRunningInsecureContent", &b)) prefs->allow_running_insecure_content = b; } diff --git a/atom/browser/web_contents_preferences.h b/atom/browser/web_contents_preferences.h index 3e36df021478..8b04f9ee24e6 100644 --- a/atom/browser/web_contents_preferences.h +++ b/atom/browser/web_contents_preferences.h @@ -16,6 +16,10 @@ namespace content { struct WebPreferences; } +namespace mate { +class Dictionary; +} + namespace atom { // Stores and applies the preferences of WebContents. @@ -31,7 +35,7 @@ class WebContentsPreferences content::WebContents* web_contents, content::WebPreferences* prefs); WebContentsPreferences(content::WebContents* web_contents, - base::DictionaryValue* web_preferences); + const mate::Dictionary& web_preferences); ~WebContentsPreferences() override; // $.extend(|web_preferences_|, |new_web_preferences|). diff --git a/atom/common/api/api_messages.h b/atom/common/api/api_messages.h index b32df3cef39d..274e1f533eb3 100644 --- a/atom/common/api/api_messages.h +++ b/atom/common/api/api_messages.h @@ -30,6 +30,12 @@ IPC_SYNC_MESSAGE_ROUTED2_1(AtomViewHostMsg_Message_Sync, base::ListValue /* arguments */, base::string16 /* result (in JSON) */) +IPC_MESSAGE_ROUTED1(AtomViewHostMsg_ZoomLevelChanged, + double /* level */) + +IPC_MESSAGE_ROUTED1(AtomViewMsg_SetZoomLevel, + double /* level */) + IPC_MESSAGE_ROUTED2(AtomViewMsg_Message, base::string16 /* channel */, base::ListValue /* arguments */) diff --git a/atom/common/api/atom_api_asar.cc b/atom/common/api/atom_api_asar.cc index 4ea7d8c5c362..7aee71fc3294 100644 --- a/atom/common/api/atom_api_asar.cc +++ b/atom/common/api/atom_api_asar.cc @@ -18,6 +18,8 @@ namespace { +v8::Persistent template_; + class Archive : public mate::Wrappable { public: static v8::Local Create(v8::Isolate* isolate, @@ -101,15 +103,20 @@ class Archive : public mate::Wrappable { // mate::Wrappable: mate::ObjectTemplateBuilder GetObjectTemplateBuilder(v8::Isolate* isolate) { - return mate::ObjectTemplateBuilder(isolate) - .SetValue("path", archive_->path()) - .SetMethod("getFileInfo", &Archive::GetFileInfo) - .SetMethod("stat", &Archive::Stat) - .SetMethod("readdir", &Archive::Readdir) - .SetMethod("realpath", &Archive::Realpath) - .SetMethod("copyFileOut", &Archive::CopyFileOut) - .SetMethod("getFd", &Archive::GetFD) - .SetMethod("destroy", &Archive::Destroy); + if (template_.IsEmpty()) + template_.Reset(isolate, mate::ObjectTemplateBuilder(isolate) + .SetValue("path", archive_->path()) + .SetMethod("getFileInfo", &Archive::GetFileInfo) + .SetMethod("stat", &Archive::Stat) + .SetMethod("readdir", &Archive::Readdir) + .SetMethod("realpath", &Archive::Realpath) + .SetMethod("copyFileOut", &Archive::CopyFileOut) + .SetMethod("getFd", &Archive::GetFD) + .SetMethod("destroy", &Archive::Destroy) + .Build()); + + return mate::ObjectTemplateBuilder( + isolate, v8::Local::New(isolate, template_)); } private: diff --git a/atom/common/api/atom_api_id_weak_map.cc b/atom/common/api/atom_api_id_weak_map.cc new file mode 100644 index 000000000000..f32e33682dff --- /dev/null +++ b/atom/common/api/atom_api_id_weak_map.cc @@ -0,0 +1,79 @@ +// 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/atom_api_id_weak_map.h" + +#include "atom/common/node_includes.h" +#include "native_mate/constructor.h" +#include "native_mate/dictionary.h" + +namespace atom { + +namespace api { + +IDWeakMap::IDWeakMap() { +} + +IDWeakMap::~IDWeakMap() { +} + +void IDWeakMap::Set(v8::Isolate* isolate, + int32_t id, + v8::Local object) { + id_weak_map_.Set(isolate, id, object); +} + +v8::Local IDWeakMap::Get(v8::Isolate* isolate, int32_t id) { + return id_weak_map_.Get(isolate, id).ToLocalChecked(); +} + +bool IDWeakMap::Has(int32_t id) { + return id_weak_map_.Has(id); +} + +void IDWeakMap::Remove(int32_t id) { + id_weak_map_.Remove(id); +} + +void IDWeakMap::Clear() { + id_weak_map_.Clear(); +} + +// static +void IDWeakMap::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .SetMethod("set", &IDWeakMap::Set) + .SetMethod("get", &IDWeakMap::Get) + .SetMethod("has", &IDWeakMap::Has) + .SetMethod("remove", &IDWeakMap::Remove) + .SetMethod("clear", &IDWeakMap::Clear); +} + +// static +mate::Wrappable* IDWeakMap::Create(v8::Isolate* isolate) { + return new IDWeakMap; +} + +} // namespace api + +} // namespace atom + +namespace { + +using atom::api::IDWeakMap; + +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { + v8::Isolate* isolate = context->GetIsolate(); + v8::Local constructor = mate::CreateConstructor( + isolate, "IDWeakMap", base::Bind(&IDWeakMap::Create)); + mate::Dictionary id_weak_map(isolate, constructor); + mate::Dictionary dict(isolate, exports); + dict.Set("IDWeakMap", id_weak_map); +} + +} // 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 new file mode 100644 index 000000000000..0cf656f455bc --- /dev/null +++ b/atom/common/api/atom_api_id_weak_map.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_COMMON_API_ATOM_API_ID_WEAK_MAP_H_ +#define ATOM_COMMON_API_ATOM_API_ID_WEAK_MAP_H_ + +#include "atom/common/id_weak_map.h" +#include "native_mate/object_template_builder.h" +#include "native_mate/handle.h" + +namespace atom { + +namespace api { + +class IDWeakMap : public mate::Wrappable { + public: + static mate::Wrappable* Create(v8::Isolate* isolate); + + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + + protected: + IDWeakMap(); + ~IDWeakMap(); + + private: + // Api for IDWeakMap. + void Set(v8::Isolate* isolate, int32_t id, v8::Local object); + v8::Local Get(v8::Isolate* isolate, int32_t id); + bool Has(int32_t id); + void Remove(int32_t id); + void Clear(); + + atom::IDWeakMap id_weak_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 df6c14dab350..a810069e71b9 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -63,7 +63,8 @@ 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 (base::EndsWith(filename, kScaleFactorPairs[i].name, true)) + if (base::EndsWith(filename, kScaleFactorPairs[i].name, + base::CompareCase::INSENSITIVE_ASCII)) return kScaleFactorPairs[i].scale; } @@ -168,7 +169,8 @@ mate::ObjectTemplateBuilder NativeImage::GetObjectTemplateBuilder( template_.Reset(isolate, mate::ObjectTemplateBuilder(isolate) .SetMethod("toPng", &NativeImage::ToPNG) .SetMethod("toJpeg", &NativeImage::ToJPEG) - .SetMethod("toDataUrl", &NativeImage::ToDataURL) + .SetMethod("toDataURL", &NativeImage::ToDataURL) + .SetMethod("toDataUrl", &NativeImage::ToDataURL) // deprecated. .SetMethod("isEmpty", &NativeImage::IsEmpty) .SetMethod("getSize", &NativeImage::GetSize) .SetMethod("setTemplateImage", &NativeImage::SetTemplateImage) @@ -309,7 +311,7 @@ void Initialize(v8::Local exports, v8::Local unused, dict.SetMethod("createEmpty", &atom::api::NativeImage::CreateEmpty); dict.SetMethod("createFromPath", &atom::api::NativeImage::CreateFromPath); dict.SetMethod("createFromBuffer", &atom::api::NativeImage::CreateFromBuffer); - dict.SetMethod("createFromDataUrl", + dict.SetMethod("createFromDataURL", &atom::api::NativeImage::CreateFromDataURL); } diff --git a/atom/common/api/atom_bindings.cc b/atom/common/api/atom_bindings.cc index d6fb355e09d8..a000f6fc743a 100644 --- a/atom/common/api/atom_bindings.cc +++ b/atom/common/api/atom_bindings.cc @@ -6,6 +6,7 @@ #include #include +#include #include "atom/common/atom_version.h" #include "atom/common/chrome_version.h" @@ -40,7 +41,7 @@ void FatalErrorCallback(const char* location, const char* message) { } void Log(const base::string16& message) { - logging::LogMessage("CONSOLE", 0, 0).stream() << message; + std::cout << message << std::flush; } } // namespace @@ -68,8 +69,9 @@ void AtomBindings::BindTo(v8::Isolate* isolate, dict.SetMethod("activateUvLoop", base::Bind(&AtomBindings::ActivateUVLoop, base::Unretained(this))); - // Do not warn about deprecated APIs. - dict.Set("noDeprecation", true); +#if defined(MAS_BUILD) + dict.Set("mas", true); +#endif mate::Dictionary versions; if (dict.Get("versions", &versions)) { diff --git a/atom/common/api/event_emitter_caller.cc b/atom/common/api/event_emitter_caller.cc index 94eb9ce9e79a..4b44553d3740 100644 --- a/atom/common/api/event_emitter_caller.cc +++ b/atom/common/api/event_emitter_caller.cc @@ -19,7 +19,7 @@ v8::Local CallEmitWithArgs(v8::Isolate* isolate, // Perform microtask checkpoint after running JavaScript. scoped_ptr script_scope( Locker::IsBrowserProcess() ? - nullptr : new blink::WebScopedRunV8Script(isolate)); + nullptr : new blink::WebScopedRunV8Script); // Use node::MakeCallback to call the callback, and it will also run pending // tasks in Node.js. return node::MakeCallback( diff --git a/atom/common/api/lib/callbacks-registry.coffee b/atom/common/api/lib/callbacks-registry.coffee index 8f5eb62916c0..c546df34f9a8 100644 --- a/atom/common/api/lib/callbacks-registry.coffee +++ b/atom/common/api/lib/callbacks-registry.coffee @@ -1,27 +1,43 @@ -savedGlobal = global # the "global.global" might be deleted later +v8Util = process.atomBinding 'v8_util' module.exports = class CallbacksRegistry constructor: -> - @emptyFunc = -> throw new Error "Browser trying to call a non-exist callback - in renderer, this usually happens when renderer code forgot to release - a callback installed on objects in browser when renderer was going to be - unloaded or released." + @nextId = 0 @callbacks = {} add: (callback) -> - id = Math.random().toString() + # The callback is already added. + id = v8Util.getHiddenValue callback, 'callbackId' + return id if id? + + id = ++@nextId + + # Capture the location of the function and put it in the ID string, + # so that release errors can be tracked down easily. + regexp = /at (.*)/gi + stackString = (new Error).stack + + while (match = regexp.exec(stackString)) isnt null + [x, location] = match + continue if location.indexOf('(native)') isnt -1 + continue if location.indexOf('atom.asar') isnt -1 + [x, filenameAndLine] = /([^/^\)]*)\)?$/gi.exec(location) + break + @callbacks[id] = callback + v8Util.setHiddenValue callback, 'callbackId', id + v8Util.setHiddenValue callback, 'location', filenameAndLine id get: (id) -> @callbacks[id] ? -> call: (id, args...) -> - @get(id).call savedGlobal, args... + @get(id).call global, args... apply: (id, args...) -> - @get(id).apply savedGlobal, args... + @get(id).apply global, args... remove: (id) -> delete @callbacks[id] diff --git a/atom/common/api/lib/clipboard.coffee b/atom/common/api/lib/clipboard.coffee index 5c4bb10d4ae5..a3a6d555fe5c 100644 --- a/atom/common/api/lib/clipboard.coffee +++ b/atom/common/api/lib/clipboard.coffee @@ -1,5 +1,5 @@ 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' + module.exports = require('electron').remote.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 451fa1729428..bd98ae2a4277 100644 --- a/atom/common/api/lib/crash-reporter.coffee +++ b/atom/common/api/lib/crash-reporter.coffee @@ -1,22 +1,26 @@ -binding = process.atomBinding 'crash_reporter' fs = require 'fs' os = require 'os' path = require 'path' {spawn} = require 'child_process' +electron = require 'electron' +binding = process.atomBinding 'crash_reporter' + class CrashReporter start: (options={}) -> - {@productName, companyName, submitUrl, autoSubmit, ignoreSystemCrashHandler, extra} = options + {@productName, companyName, submitURL, autoSubmit, ignoreSystemCrashHandler, extra} = options - app = - if process.type is 'browser' - require 'app' - else - require('remote').require 'app' + # Deprecated. + {deprecate} = electron + if options.submitUrl + submitURL ?= options.submitUrl + deprecate.warn 'submitUrl', 'submitURL' + + {app} = if process.type is 'browser' then electron else electron.remote @productName ?= app.getName() companyName ?= 'GitHub, Inc' - submitUrl ?= 'http://54.249.141.255:1127/post' + submitURL ?= 'http://54.249.141.255:1127/post' autoSubmit ?= true ignoreSystemCrashHandler ?= false extra ?= {} @@ -25,11 +29,11 @@ class CrashReporter extra._companyName ?= companyName extra._version ?= app.getVersion() - start = => binding.start @productName, companyName, submitUrl, autoSubmit, ignoreSystemCrashHandler, extra + start = => binding.start @productName, companyName, submitURL, autoSubmit, ignoreSystemCrashHandler, extra if process.platform is 'win32' args = [ - "--reporter-url=#{submitUrl}" + "--reporter-url=#{submitURL}" "--application-name=#{@productName}" "--v=1" ] diff --git a/atom/common/api/lib/deprecate.coffee b/atom/common/api/lib/deprecate.coffee new file mode 100644 index 000000000000..1daf5e4714fd --- /dev/null +++ b/atom/common/api/lib/deprecate.coffee @@ -0,0 +1,65 @@ +# Deprecate a method. +deprecate = (oldName, newName, fn) -> + warned = false + -> + unless warned or process.noDeprecation + warned = true + deprecate.warn oldName, newName + fn.apply this, arguments + +# The method is renamed. +deprecate.rename = (object, oldName, newName) -> + warned = false + newMethod = -> + unless warned or process.noDeprecation + warned = true + deprecate.warn oldName, newName + this[newName].apply this, arguments + if typeof object is 'function' + object.prototype[oldName] = newMethod + else + object[oldName] = newMethod + +# Forward the method to member. +deprecate.member = (object, method, member) -> + warned = false + object.prototype[method] = -> + unless warned or process.noDeprecation + warned = true + deprecate.warn method, "#{member}.#{method}" + this[member][method].apply this[member], arguments + +# Deprecate a property. +deprecate.property = (object, property, method) -> + Object.defineProperty object, property, + get: -> + warned = false + unless warned or process.noDeprecation + warned = true + deprecate.warn "#{property} property", "#{method} method" + this[method]() + +# Deprecate an event. +deprecate.event = (emitter, oldName, newName, fn) -> + warned = false + emitter.on newName, (args...) -> + if @listenerCount(oldName) > 0 # there is listeners for old API. + unless warned or process.noDeprecation + warned = true + deprecate.warn "'#{oldName}' event", "'#{newName}' event" + if fn? + fn.apply this, arguments + else + @emit oldName, args... + +# Print deprecate warning. +deprecate.warn = (oldName, newName) -> + message = "#{oldName} is deprecated. Use #{newName} instead." + if process.throwDeprecation + throw new Error(message) + else if process.traceDeprecation + console.trace message + else + console.warn "(electron) #{message}" + +module.exports = deprecate diff --git a/atom/common/api/lib/exports/electron.coffee b/atom/common/api/lib/exports/electron.coffee new file mode 100644 index 000000000000..e98144306561 --- /dev/null +++ b/atom/common/api/lib/exports/electron.coffee @@ -0,0 +1,27 @@ +# Do not expose the internal modules to `require`. +exports.hideInternalModules = -> + {globalPaths} = require 'module' + if globalPaths.length is 3 + # Remove the "common/api/lib" and "browser-or-renderer/api/lib". + globalPaths.splice 0, 2 + +Object.defineProperties exports, + # Common modules, please sort with alphabet order. + clipboard: + # Must be enumerable, otherwise it woulde be invisible to remote module. + enumerable: true + get: -> require '../clipboard' + crashReporter: + enumerable: true + get: -> require '../crash-reporter' + nativeImage: + enumerable: true + get: -> require '../native-image' + shell: + enumerable: true + get: -> require '../shell' + # The internal modules, invisible unless you know their names. + CallbacksRegistry: + get: -> require '../callbacks-registry' + deprecate: + get: -> require '../deprecate' diff --git a/atom/common/api/lib/native-image.coffee b/atom/common/api/lib/native-image.coffee index c3cbb60ef030..2bdfd494f44b 100644 --- a/atom/common/api/lib/native-image.coffee +++ b/atom/common/api/lib/native-image.coffee @@ -1 +1,7 @@ -module.exports = process.atomBinding 'native_image' +{deprecate} = require 'electron' +nativeImage = process.atomBinding 'native_image' + +# Deprecated. +deprecate.rename nativeImage, 'createFromDataUrl', 'createFromDataURL' + +module.exports = nativeImage diff --git a/atom/common/api/lib/shell.coffee b/atom/common/api/lib/shell.coffee index 8e06826f7076..5fb935bacd21 100644 --- a/atom/common/api/lib/shell.coffee +++ b/atom/common/api/lib/shell.coffee @@ -1,4 +1 @@ module.exports = process.atomBinding 'shell' - -if process.platform is 'win32' and process.type is 'renderer' - module.exports.showItemInFolder = require('remote').process.atomBinding('shell').showItemInFolder diff --git a/atom/common/asar/archive.cc b/atom/common/asar/archive.cc index 969f958956ca..ebb80cc2c485 100644 --- a/atom/common/asar/archive.cc +++ b/atom/common/asar/archive.cc @@ -13,6 +13,7 @@ #include "atom/common/asar/scoped_temporary_file.h" #include "base/files/file.h" +#include "base/files/file_util.h" #include "base/logging.h" #include "base/pickle.h" #include "base/json/json_reader.h" @@ -96,7 +97,6 @@ bool FillFileInfoWithNode(Archive::FileInfo* info, return false; info->size = static_cast(size); - info->unpacked = false; if (node->GetBoolean("unpacked", &info->unpacked) && info->unpacked) return true; @@ -107,6 +107,8 @@ bool FillFileInfoWithNode(Archive::FileInfo* info, return false; info->offset += header_size; + node->GetBoolean("executable", &info->executable); + return true; } @@ -270,9 +272,17 @@ bool Archive::CopyFileOut(const base::FilePath& path, base::FilePath* out) { } scoped_ptr temp_file(new ScopedTemporaryFile); - if (!temp_file->InitFromFile(&file_, info.offset, info.size)) + base::FilePath::StringType ext = path.Extension(); + if (!temp_file->InitFromFile(&file_, ext, info.offset, info.size)) return false; +#if defined(OS_POSIX) + if (info.executable) { + // chmod a+x temp_file; + base::SetPosixFilePermissions(temp_file->path(), 0755); + } +#endif + *out = temp_file->path(); external_files_.set(path, temp_file.Pass()); return true; diff --git a/atom/common/asar/archive.h b/atom/common/asar/archive.h index f2ff2f76d676..de5b9de605a3 100644 --- a/atom/common/asar/archive.h +++ b/atom/common/asar/archive.h @@ -25,8 +25,9 @@ class ScopedTemporaryFile; class Archive { public: struct FileInfo { - FileInfo() : size(0), offset(0) {} + FileInfo() : unpacked(false), executable(false), size(0), offset(0) {} bool unpacked; + bool executable; uint32 size; uint64 offset; }; diff --git a/atom/common/asar/scoped_temporary_file.cc b/atom/common/asar/scoped_temporary_file.cc index 6fccc9434fdb..6dd12782d8ee 100644 --- a/atom/common/asar/scoped_temporary_file.cc +++ b/atom/common/asar/scoped_temporary_file.cc @@ -28,20 +28,34 @@ ScopedTemporaryFile::~ScopedTemporaryFile() { } } -bool ScopedTemporaryFile::Init() { +bool ScopedTemporaryFile::Init(const base::FilePath::StringType& ext) { if (!path_.empty()) return true; base::ThreadRestrictions::ScopedAllowIO allow_io; - return base::CreateTemporaryFile(&path_); + if (!base::CreateTemporaryFile(&path_)) + return false; + +#if defined(OS_WIN) + // Keep the original extension. + if (!ext.empty()) { + base::FilePath new_path = path_.AddExtension(ext); + if (!base::Move(path_, new_path)) + return false; + path_ = new_path; + } +#endif + + return true; } bool ScopedTemporaryFile::InitFromFile(base::File* src, + const base::FilePath::StringType& ext, uint64 offset, uint64 size) { if (!src->IsValid()) return false; - if (!Init()) + if (!Init(ext)) return false; std::vector buf(size); diff --git a/atom/common/asar/scoped_temporary_file.h b/atom/common/asar/scoped_temporary_file.h index ffaee22e514e..23660a239011 100644 --- a/atom/common/asar/scoped_temporary_file.h +++ b/atom/common/asar/scoped_temporary_file.h @@ -22,11 +22,13 @@ class ScopedTemporaryFile { ScopedTemporaryFile(); virtual ~ScopedTemporaryFile(); - // Init an empty temporary file. - bool Init(); + // Init an empty temporary file with a certain extension. + bool Init(const base::FilePath::StringType& ext); // Init an temporary file and fill it with content of |path|. - bool InitFromFile(base::File* src, uint64 offset, uint64 size); + bool InitFromFile(base::File* src, + const base::FilePath::StringType& ext, + uint64 offset, uint64 size); base::FilePath path() const { return path_; } diff --git a/atom/common/atom_constants.cc b/atom/common/atom_constants.cc new file mode 100644 index 000000000000..dacda3c816c8 --- /dev/null +++ b/atom/common/atom_constants.cc @@ -0,0 +1,11 @@ +// 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/atom_constants.h" + +namespace atom { + +const char* kCORSHeader = "Access-Control-Allow-Origin: *"; + +} // namespace atom diff --git a/atom/common/atom_constants.h b/atom/common/atom_constants.h new file mode 100644 index 000000000000..e0d42e83eef9 --- /dev/null +++ b/atom/common/atom_constants.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_COMMON_ATOM_CONSTANTS_H_ +#define ATOM_COMMON_ATOM_CONSTANTS_H_ + +namespace atom { + +// Header to ignore CORS. +extern const char* kCORSHeader; + +} // namespace atom + +#endif // ATOM_COMMON_ATOM_CONSTANTS_H_ diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index 5f9d0f3ca0bb..a4930d0edb2e 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -6,8 +6,8 @@ #define ATOM_VERSION_H #define ATOM_MAJOR_VERSION 0 -#define ATOM_MINOR_VERSION 33 -#define ATOM_PATCH_VERSION 1 +#define ATOM_MINOR_VERSION 35 +#define ATOM_PATCH_VERSION 4 #define ATOM_VERSION_IS_RELEASE 1 diff --git a/atom/common/chrome_version.h b/atom/common/chrome_version.h index 250051683786..c92fee1649bd 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 "45.0.2454.85" +#define CHROME_VERSION_STRING "47.0.2526.73" #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 59b7fd51e45e..f4f0ff9b7b16 100644 --- a/atom/common/crash_reporter/crash_reporter.cc +++ b/atom/common/crash_reporter/crash_reporter.cc @@ -48,11 +48,11 @@ CrashReporter::GetUploadedReports(const std::string& path) { std::vector result; if (base::ReadFileToString(base::FilePath::FromUTF8Unsafe(path), &file_content)) { - std::vector reports; - base::SplitString(file_content, '\n', &reports); + std::vector reports = base::SplitString( + file_content, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); for (const std::string& report : reports) { - std::vector report_item; - base::SplitString(report, ',', &report_item); + std::vector report_item = base::SplitString( + report, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); int report_time = 0; if (report_item.size() >= 2 && base::StringToInt(report_item[0], &report_time)) { @@ -64,4 +64,23 @@ CrashReporter::GetUploadedReports(const std::string& path) { return result; } +void CrashReporter::InitBreakpad(const std::string& product_name, + const std::string& version, + const std::string& company_name, + const std::string& submit_url, + bool auto_submit, + bool skip_system_crash_handler) { +} + +void CrashReporter::SetUploadParameters() { +} + +#if defined(OS_MACOSX) && defined(MAS_BUILD) +// static +CrashReporter* CrashReporter::GetInstance() { + static CrashReporter crash_reporter; + return &crash_reporter; +} +#endif + } // namespace crash_reporter diff --git a/atom/common/crash_reporter/crash_reporter.h b/atom/common/crash_reporter/crash_reporter.h index c7d58ca3aa76..98832fea45de 100644 --- a/atom/common/crash_reporter/crash_reporter.h +++ b/atom/common/crash_reporter/crash_reporter.h @@ -40,8 +40,8 @@ class CrashReporter { const std::string& company_name, const std::string& submit_url, bool auto_submit, - bool skip_system_crash_handler) = 0; - virtual void SetUploadParameters() = 0; + bool skip_system_crash_handler); + virtual void SetUploadParameters(); StringMap upload_parameters_; bool is_browser_; diff --git a/atom/common/crash_reporter/crash_reporter_linux.cc b/atom/common/crash_reporter/crash_reporter_linux.cc index 8a5608dad0cb..6fe69f486956 100644 --- a/atom/common/crash_reporter/crash_reporter_linux.cc +++ b/atom/common/crash_reporter/crash_reporter_linux.cc @@ -130,7 +130,7 @@ bool CrashReporterLinux::CrashDone(const MinidumpDescriptor& minidump, // static CrashReporterLinux* CrashReporterLinux::GetInstance() { - return Singleton::get(); + return base::Singleton::get(); } // static diff --git a/atom/common/crash_reporter/crash_reporter_linux.h b/atom/common/crash_reporter/crash_reporter_linux.h index 2f7d639e9075..165c288ab2b1 100644 --- a/atom/common/crash_reporter/crash_reporter_linux.h +++ b/atom/common/crash_reporter/crash_reporter_linux.h @@ -12,7 +12,9 @@ #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" +namespace base { template struct DefaultSingletonTraits; +} namespace google_breakpad { class ExceptionHandler; @@ -34,7 +36,7 @@ class CrashReporterLinux : public CrashReporter { void SetUploadParameters() override; private: - friend struct DefaultSingletonTraits; + friend struct base::DefaultSingletonTraits; CrashReporterLinux(); virtual ~CrashReporterLinux(); diff --git a/atom/common/crash_reporter/crash_reporter_mac.h b/atom/common/crash_reporter/crash_reporter_mac.h index cbdb3c65feb1..f03154359155 100644 --- a/atom/common/crash_reporter/crash_reporter_mac.h +++ b/atom/common/crash_reporter/crash_reporter_mac.h @@ -14,7 +14,9 @@ #include "base/strings/string_piece.h" #include "vendor/crashpad/client/simple_string_dictionary.h" +namespace base { template struct DefaultSingletonTraits; +} namespace crash_reporter { @@ -31,7 +33,7 @@ class CrashReporterMac : public CrashReporter { void SetUploadParameters() override; private: - friend struct DefaultSingletonTraits; + friend struct base::DefaultSingletonTraits; CrashReporterMac(); virtual ~CrashReporterMac(); diff --git a/atom/common/crash_reporter/crash_reporter_mac.mm b/atom/common/crash_reporter/crash_reporter_mac.mm index 00f37cc3febb..74ac70125b7d 100644 --- a/atom/common/crash_reporter/crash_reporter_mac.mm +++ b/atom/common/crash_reporter/crash_reporter_mac.mm @@ -126,7 +126,7 @@ CrashReporterMac::GetUploadedReports(const std::string& path) { // static CrashReporterMac* CrashReporterMac::GetInstance() { - return Singleton::get(); + return base::Singleton::get(); } // static diff --git a/atom/common/crash_reporter/crash_reporter_win.cc b/atom/common/crash_reporter/crash_reporter_win.cc index be096da80e2c..939a02f090c5 100644 --- a/atom/common/crash_reporter/crash_reporter_win.cc +++ b/atom/common/crash_reporter/crash_reporter_win.cc @@ -11,6 +11,25 @@ #include "base/memory/singleton.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" +#include "content/public/common/result_codes.h" +#include "gin/public/debug.h" +#include "sandbox/win/src/nt_internals.h" + +#pragma intrinsic(_AddressOfReturnAddress) +#pragma intrinsic(_ReturnAddress) + +#ifdef _WIN64 +// See http://msdn.microsoft.com/en-us/library/ddssxxy8.aspx +typedef struct _UNWIND_INFO { + unsigned char Version : 3; + unsigned char Flags : 5; + unsigned char SizeOfProlog; + unsigned char CountOfCodes; + unsigned char FrameRegister : 4; + unsigned char FrameOffset : 4; + ULONG ExceptionHandler; +} UNWIND_INFO, *PUNWIND_INFO; +#endif namespace crash_reporter { @@ -24,6 +43,94 @@ const MINIDUMP_TYPE kSmallDumpType = static_cast( const wchar_t kWaitEventFormat[] = L"$1CrashServiceWaitEvent"; const wchar_t kPipeNameFormat[] = L"\\\\.\\pipe\\$1 Crash Service"; +typedef NTSTATUS (WINAPI* NtTerminateProcessPtr)(HANDLE ProcessHandle, + NTSTATUS ExitStatus); +char* g_real_terminate_process_stub = NULL; + +void TerminateProcessWithoutDump() { + // Patched stub exists based on conditions (See InitCrashReporter). + // As a side note this function also gets called from + // WindowProcExceptionFilter. + if (g_real_terminate_process_stub == NULL) { + ::TerminateProcess(::GetCurrentProcess(), content::RESULT_CODE_KILLED); + } else { + NtTerminateProcessPtr real_terminate_proc = + reinterpret_cast( + static_cast(g_real_terminate_process_stub)); + real_terminate_proc(::GetCurrentProcess(), content::RESULT_CODE_KILLED); + } +} + +#ifdef _WIN64 +int CrashForExceptionInNonABICompliantCodeRange( + PEXCEPTION_RECORD ExceptionRecord, + ULONG64 EstablisherFrame, + PCONTEXT ContextRecord, + PDISPATCHER_CONTEXT DispatcherContext) { + EXCEPTION_POINTERS info = { ExceptionRecord, ContextRecord }; + if (!CrashReporter::GetInstance()) + return EXCEPTION_CONTINUE_SEARCH; + return static_cast(CrashReporter::GetInstance())-> + CrashForException(&info); +} + +struct ExceptionHandlerRecord { + RUNTIME_FUNCTION runtime_function; + UNWIND_INFO unwind_info; + unsigned char thunk[12]; +}; + +bool RegisterNonABICompliantCodeRange(void* start, size_t size_in_bytes) { + ExceptionHandlerRecord* record = + reinterpret_cast(start); + + // We assume that the first page of the code range is executable and + // committed and reserved for breakpad. What could possibly go wrong? + + // All addresses are 32bit relative offsets to start. + record->runtime_function.BeginAddress = 0; + record->runtime_function.EndAddress = + base::checked_cast(size_in_bytes); + record->runtime_function.UnwindData = + offsetof(ExceptionHandlerRecord, unwind_info); + + // Create unwind info that only specifies an exception handler. + record->unwind_info.Version = 1; + record->unwind_info.Flags = UNW_FLAG_EHANDLER; + record->unwind_info.SizeOfProlog = 0; + record->unwind_info.CountOfCodes = 0; + record->unwind_info.FrameRegister = 0; + record->unwind_info.FrameOffset = 0; + record->unwind_info.ExceptionHandler = + offsetof(ExceptionHandlerRecord, thunk); + + // Hardcoded thunk. + // mov imm64, rax + record->thunk[0] = 0x48; + record->thunk[1] = 0xb8; + void* handler = &CrashForExceptionInNonABICompliantCodeRange; + memcpy(&record->thunk[2], &handler, 8); + + // jmp rax + record->thunk[10] = 0xff; + record->thunk[11] = 0xe0; + + // Protect reserved page against modifications. + DWORD old_protect; + return VirtualProtect(start, sizeof(ExceptionHandlerRecord), + PAGE_EXECUTE_READ, &old_protect) && + RtlAddFunctionTable(&record->runtime_function, 1, + reinterpret_cast(start)); +} + +void UnregisterNonABICompliantCodeRange(void* start) { + ExceptionHandlerRecord* record = + reinterpret_cast(start); + + RtlDeleteFunctionTable(&record->runtime_function); +} +#endif // _WIN64 + } // namespace CrashReporterWin::CrashReporterWin() { @@ -46,9 +153,9 @@ void CrashReporterWin::InitBreakpad(const std::string& product_name, return; } - base::string16 pipe_name = ReplaceStringPlaceholders( + base::string16 pipe_name = base::ReplaceStringPlaceholders( kPipeNameFormat, base::UTF8ToUTF16(product_name), NULL); - base::string16 wait_name = ReplaceStringPlaceholders( + base::string16 wait_name = base::ReplaceStringPlaceholders( kWaitEventFormat, base::UTF8ToUTF16(product_name), NULL); // Wait until the crash service is started. @@ -63,26 +170,48 @@ void CrashReporterWin::InitBreakpad(const std::string& product_name, // 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; breakpad_.reset(new google_breakpad::ExceptionHandler( temp_dir.value(), FilterCallback, MinidumpCallback, this, - handler_types, + google_breakpad::ExceptionHandler::HANDLER_ALL, kSmallDumpType, pipe_name.c_str(), GetCustomInfo(product_name, version, company_name))); if (!breakpad_->IsOutOfProcess()) LOG(ERROR) << "Cannot initialize out-of-process crash handler"; + +#ifdef _WIN64 + bool registered = false; + // Hook up V8 to breakpad. + { + // gin::Debug::SetCodeRangeCreatedCallback only runs the callback when + // Isolate is just created, so we have to manually run following code here. + void* code_range = nullptr; + size_t size = 0; + v8::Isolate::GetCurrent()->GetCodeRange(&code_range, &size); + if (code_range && size) + registered = RegisterNonABICompliantCodeRange(code_range, size); + } + if (registered) + gin::Debug::SetCodeRangeDeletedCallback(UnregisterNonABICompliantCodeRange); +#endif } void CrashReporterWin::SetUploadParameters() { upload_parameters_["platform"] = "win32"; } +int CrashReporterWin::CrashForException(EXCEPTION_POINTERS* info) { + if (breakpad_) { + breakpad_->WriteMinidumpForException(info); + TerminateProcessWithoutDump(); + } + return EXCEPTION_CONTINUE_SEARCH; +} + // static bool CrashReporterWin::FilterCallback(void* context, EXCEPTION_POINTERS* exinfo, @@ -130,7 +259,7 @@ google_breakpad::CustomClientInfo* CrashReporterWin::GetCustomInfo( // static CrashReporterWin* CrashReporterWin::GetInstance() { - return Singleton::get(); + return base::Singleton::get(); } // static diff --git a/atom/common/crash_reporter/crash_reporter_win.h b/atom/common/crash_reporter/crash_reporter_win.h index 72b9411d2191..181c9eabd23c 100644 --- a/atom/common/crash_reporter/crash_reporter_win.h +++ b/atom/common/crash_reporter/crash_reporter_win.h @@ -13,7 +13,9 @@ #include "base/memory/scoped_ptr.h" #include "vendor/breakpad/src/client/windows/handler/exception_handler.h" +namespace base { template struct DefaultSingletonTraits; +} namespace crash_reporter { @@ -29,8 +31,11 @@ class CrashReporterWin : public CrashReporter { bool skip_system_crash_handler) override; void SetUploadParameters() override; + // Crashes the process after generating a dump for the provided exception. + int CrashForException(EXCEPTION_POINTERS* info); + private: - friend struct DefaultSingletonTraits; + friend struct base::DefaultSingletonTraits; CrashReporterWin(); virtual ~CrashReporterWin(); diff --git a/atom/common/crash_reporter/win/crash_service.cc b/atom/common/crash_reporter/win/crash_service.cc index d315b0b9419e..67e22381aef8 100644 --- a/atom/common/crash_reporter/win/crash_service.cc +++ b/atom/common/crash_reporter/win/crash_service.cc @@ -118,7 +118,7 @@ HWND g_top_window = NULL; bool CreateTopWindow(HINSTANCE instance, const base::string16& application_name, bool visible) { - base::string16 class_name = ReplaceStringPlaceholders( + base::string16 class_name = base::ReplaceStringPlaceholders( kClassNameFormat, application_name, NULL); WNDCLASSEXW wcx = {0}; @@ -309,9 +309,9 @@ bool CrashService::Initialize(const base::string16& application_name, // Create or open an event to signal the browser process that the crash // service is initialized. - base::string16 wait_name = ReplaceStringPlaceholders( + base::string16 wait_name = base::ReplaceStringPlaceholders( kWaitEventFormat, application_name, NULL); - HANDLE wait_event = ::CreateEventW(NULL, TRUE, FALSE, wait_name.c_str()); + HANDLE wait_event = ::CreateEventW(NULL, TRUE, TRUE, wait_name.c_str()); ::SetEvent(wait_event); return true; @@ -524,4 +524,3 @@ PSECURITY_DESCRIPTOR CrashService::GetSecurityDescriptorForLowIntegrity() { } } // namespace breakpad - diff --git a/atom/common/crash_reporter/win/crash_service_main.cc b/atom/common/crash_reporter/win/crash_service_main.cc index 7a5eeb10133a..56d46970b67f 100644 --- a/atom/common/crash_reporter/win/crash_service_main.cc +++ b/atom/common/crash_reporter/win/crash_service_main.cc @@ -68,7 +68,7 @@ int Main(const wchar_t* cmd) { VLOG(1) << "Session start. cmdline is [" << cmd << "]"; // Setting the crash reporter. - base::string16 pipe_name = ReplaceStringPlaceholders(kPipeNameFormat, + base::string16 pipe_name = base::ReplaceStringPlaceholders(kPipeNameFormat, application_name, NULL); cmd_line.AppendSwitch("no-window"); diff --git a/atom/common/id_weak_map.cc b/atom/common/id_weak_map.cc index c5c4b60cac5c..a78dcbceba53 100644 --- a/atom/common/id_weak_map.cc +++ b/atom/common/id_weak_map.cc @@ -32,12 +32,18 @@ IDWeakMap::IDWeakMap() : next_id_(0) { IDWeakMap::~IDWeakMap() { } -int32_t IDWeakMap::Add(v8::Isolate* isolate, v8::Local object) { - int32_t id = GetNextID(); +void IDWeakMap::Set(v8::Isolate* isolate, + int32_t id, + v8::Local object) { 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; +} + +int32_t IDWeakMap::Add(v8::Isolate* isolate, v8::Local object) { + int32_t id = GetNextID(); + Set(isolate, id, object); return id; } diff --git a/atom/common/id_weak_map.h b/atom/common/id_weak_map.h index 9fe71ebb616f..72c64c6ae5d4 100644 --- a/atom/common/id_weak_map.h +++ b/atom/common/id_weak_map.h @@ -19,6 +19,9 @@ class IDWeakMap { IDWeakMap(); ~IDWeakMap(); + // Sets the object to WeakMap with the given |id|. + void Set(v8::Isolate* isolate, int32_t id, v8::Local object); + // Adds |object| to WeakMap and returns its allocated |id|. int32_t Add(v8::Isolate* isolate, v8::Local object); diff --git a/atom/common/keyboad_util.cc b/atom/common/keyboad_util.cc index 1baa829ff74a..7d7c5d99fabe 100644 --- a/atom/common/keyboad_util.cc +++ b/atom/common/keyboad_util.cc @@ -2,17 +2,19 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. +#include #include "atom/common/keyboad_util.h" namespace atom { // Return key code of the char. -ui::KeyboardCode KeyboardCodeFromCharCode(char c, bool* shifted) { +ui::KeyboardCode KeyboardCodeFromCharCode(base::char16 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 0x08: return ui::VKEY_BACK; + case 0x7F: return ui::VKEY_DELETE; + case 0x09: return ui::VKEY_TAB; + case 0x0D: return ui::VKEY_RETURN; case 0x1B: return ui::VKEY_ESCAPE; case ' ': return ui::VKEY_SPACE; @@ -70,4 +72,28 @@ ui::KeyboardCode KeyboardCodeFromCharCode(char c, bool* shifted) { } } +// Return key code of the char. +ui::KeyboardCode KeyboardCodeFromKeyIdentifier(const std::string& chr) { + if (chr == "enter") return ui::VKEY_RETURN; + if (chr == "backspace") return ui::VKEY_BACK; + if (chr == "delete") return ui::VKEY_DELETE; + if (chr == "tab") return ui::VKEY_TAB; + if (chr == "escape") return ui::VKEY_ESCAPE; + if (chr == "control") return ui::VKEY_CONTROL; + if (chr == "alt") return ui::VKEY_MENU; + if (chr == "shift") return ui::VKEY_SHIFT; + if (chr == "end") return ui::VKEY_END; + if (chr == "home") return ui::VKEY_HOME; + if (chr == "insert") return ui::VKEY_INSERT; + if (chr == "left") return ui::VKEY_LEFT; + if (chr == "up") return ui::VKEY_UP; + if (chr == "right") return ui::VKEY_RIGHT; + if (chr == "down") return ui::VKEY_DOWN; + if (chr == "pageup") return ui::VKEY_PRIOR; + if (chr == "pagedown") return ui::VKEY_NEXT; + if (chr == "printscreen") return ui::VKEY_SNAPSHOT; + + return ui::VKEY_UNKNOWN; +} + } // namespace atom diff --git a/atom/common/keyboad_util.h b/atom/common/keyboad_util.h index 0496886e40bd..4a85c190635d 100644 --- a/atom/common/keyboad_util.h +++ b/atom/common/keyboad_util.h @@ -5,13 +5,18 @@ #ifndef ATOM_COMMON_KEYBOAD_UTIL_H_ #define ATOM_COMMON_KEYBOAD_UTIL_H_ +#include #include "ui/events/keycodes/keyboard_codes.h" +#include "base/strings/string_util.h" namespace atom { // Return key code of the char, and also determine whether the SHIFT key is // pressed. -ui::KeyboardCode KeyboardCodeFromCharCode(char c, bool* shifted); +ui::KeyboardCode KeyboardCodeFromCharCode(base::char16 c, bool* shifted); + +// Return key code of the char from a string representation of the char +ui::KeyboardCode KeyboardCodeFromKeyIdentifier(const std::string& chr); } // namespace atom diff --git a/atom/common/lib/asar.coffee b/atom/common/lib/asar.coffee index 22d0e70b34fa..2373385f75d4 100644 --- a/atom/common/lib/asar.coffee +++ b/atom/common/lib/asar.coffee @@ -18,6 +18,7 @@ process.on 'exit', -> # Separate asar package's path from full path. splitPath = (p) -> + return [false] if process.noAsar # shortcut to disable asar. return [false] if typeof p isnt 'string' return [true, p, ''] if p.substr(-5) is '.asar' p = path.normalize p @@ -254,7 +255,8 @@ exports.wrapFsWithAsar = (fs) -> openSync = fs.openSync readFileSync = fs.readFileSync - fs.readFileSync = (p, options) -> + fs.readFileSync = (p, opts) -> + options = opts # this allows v8 to optimize this function [isAsar, asarPath, filePath] = splitPath p return readFileSync.apply this, arguments unless isAsar @@ -263,7 +265,9 @@ exports.wrapFsWithAsar = (fs) -> info = archive.getFileInfo filePath notFoundError asarPath, filePath unless info - return new Buffer(0) if info.size is 0 + + if info.size is 0 + return if options then '' else new Buffer(0) if info.unpacked realPath = archive.copyFileOut filePath @@ -352,3 +356,4 @@ exports.wrapFsWithAsar = (fs) -> overrideAPISync process, 'dlopen', 1 overrideAPISync require('module')._extensions, '.node', 1 overrideAPISync fs, 'openSync' + overrideAPISync child_process, 'execFileSync' diff --git a/atom/common/lib/init.coffee b/atom/common/lib/init.coffee index acb635edeaa4..735d9b0a31e5 100644 --- a/atom/common/lib/init.coffee +++ b/atom/common/lib/init.coffee @@ -9,21 +9,9 @@ process.atomBinding = (name) -> catch e process.binding "atom_common_#{name}" if /No such module/.test e.message -# Global module search paths. -globalPaths = Module.globalPaths - -# Don't lookup modules in user-defined search paths, see http://git.io/vf8sF. -homeDir = - if process.platform is 'win32' - process.env.USERPROFILE - else - process.env.HOME -if homeDir # Node only add user-defined search paths when $HOME is defined. - userModulePath = path.resolve homeDir, '.node_modules' - globalPaths.splice globalPaths.indexOf(userModulePath), 2 - -# Add common/api/lib to module search paths. -globalPaths.push path.resolve(__dirname, '..', 'api', 'lib') +unless process.env.ELECTRON_HIDE_INTERNAL_MODULES + # Add common/api/lib to module search paths. + Module.globalPaths.push path.resolve(__dirname, '..', 'api', 'lib') # setImmediate and process.nextTick makes use of uv_check and uv_prepare to # run the callbacks, however since we only run uv loop on requests, the diff --git a/atom/common/lib/reset-search-paths.coffee b/atom/common/lib/reset-search-paths.coffee new file mode 100644 index 000000000000..7061103306aa --- /dev/null +++ b/atom/common/lib/reset-search-paths.coffee @@ -0,0 +1,29 @@ +path = require 'path' +Module = require 'module' + +# Clear Node's global search paths. +Module.globalPaths.length = 0 + +# Clear current and parent(init.coffee)'s search paths. +module.paths = [] +module.parent.paths = [] + +# Prevent Node from adding paths outside this app to search paths. +Module._nodeModulePaths = (from) -> + from = path.resolve from + + # If "from" is outside the app then we do nothing. + skipOutsidePaths = from.startsWith process.resourcesPath + + # Following logoic is copied from module.js. + splitRe = if process.platform is 'win32' then /[\/\\]/ else /\// + paths = [] + + parts = from.split splitRe + for part, tip in parts by -1 + continue if part is 'node_modules' + dir = parts.slice(0, tip + 1).join path.sep + break if skipOutsidePaths and not dir.startsWith process.resourcesPath + paths.push path.join(dir, 'node_modules') + + paths diff --git a/atom/common/native_mate_converters/blink_converter.cc b/atom/common/native_mate_converters/blink_converter.cc index 67c7e7e95fd8..d192018da012 100644 --- a/atom/common/native_mate_converters/blink_converter.cc +++ b/atom/common/native_mate_converters/blink_converter.cc @@ -9,6 +9,7 @@ #include "atom/common/keyboad_util.h" #include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" #include "content/public/browser/native_web_keyboard_event.h" #include "native_mate/dictionary.h" #include "third_party/WebKit/public/web/WebDeviceEmulationParams.h" @@ -29,10 +30,10 @@ int VectorToBitArray(const std::vector& vec) { namespace mate { template<> -struct Converter { +struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Handle val, - char* out) { - std::string code = base::StringToLowerASCII(V8ToString(val)); + base::char16* out) { + base::string16 code = base::UTF8ToUTF16(V8ToString(val)); if (code.length() != 1) return false; *out = code[0]; @@ -44,7 +45,7 @@ template<> struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Handle val, blink::WebInputEvent::Type* out) { - std::string type = base::StringToLowerASCII(V8ToString(val)); + std::string type = base::ToLowerASCII(V8ToString(val)); if (type == "mousedown") *out = blink::WebInputEvent::MouseDown; else if (type == "mouseup") @@ -60,7 +61,7 @@ struct Converter { else if (type == "mousewheel") *out = blink::WebInputEvent::MouseWheel; else if (type == "keydown") - *out = blink::WebInputEvent::KeyDown; + *out = blink::WebInputEvent::RawKeyDown; else if (type == "keyup") *out = blink::WebInputEvent::KeyUp; else if (type == "char") @@ -77,11 +78,26 @@ struct Converter { } }; +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Handle val, + blink::WebMouseEvent::Button* out) { + std::string button = base::ToLowerASCII(V8ToString(val)); + if (button == "left") + *out = blink::WebMouseEvent::Button::ButtonLeft; + else if (button == "middle") + *out = blink::WebMouseEvent::Button::ButtonMiddle; + else if (button == "right") + *out = blink::WebMouseEvent::Button::ButtonRight; + 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)); + std::string modifier = base::ToLowerASCII(V8ToString(val)); if (modifier == "shift") *out = blink::WebInputEvent::ShiftKey; else if (modifier == "control" || modifier == "ctrl") @@ -142,16 +158,26 @@ bool Converter::FromV8( return false; if (!ConvertFromV8(isolate, val, static_cast(out))) return false; - char code; - if (!dict.Get("keyCode", &code)) - return false; + base::char16 code; + std::string identifier; bool shifted = false; - out->windowsKeyCode = atom::KeyboardCodeFromCharCode(code, &shifted); - if (out->windowsKeyCode == ui::VKEY_UNKNOWN) + + if (dict.Get("keyCode", &code)) + out->windowsKeyCode = atom::KeyboardCodeFromCharCode(code, &shifted); + else if (dict.Get("keyCode", &identifier)) + out->windowsKeyCode = atom::KeyboardCodeFromKeyIdentifier( + base::ToLowerASCII(identifier)); + else return false; + if (shifted) out->modifiers |= blink::WebInputEvent::ShiftKey; out->setKeyIdentifierFromWindowsKeyCode(); + if (out->type == blink::WebInputEvent::Char || + out->type == blink::WebInputEvent::RawKeyDown) { + out->text[0] = code; + out->unmodifiedText[0] = code; + } return true; } @@ -176,6 +202,7 @@ bool Converter::FromV8( return false; if (!dict.Get("x", &out->x) || !dict.Get("y", &out->y)) return false; + dict.Get("button", &out->button); dict.Get("globalX", &out->globalX); dict.Get("globalY", &out->globalY); dict.Get("movementX", &out->movementX); @@ -236,7 +263,7 @@ bool Converter::FromV8( std::string screen_position; if (dict.Get("screenPosition", &screen_position)) { - screen_position = base::StringToLowerASCII(screen_position); + screen_position = base::ToLowerASCII(screen_position); if (screen_position == "mobile") out->screenPosition = blink::WebDeviceEmulationParams::Mobile; else if (screen_position == "desktop") diff --git a/atom/common/native_mate_converters/callback.cc b/atom/common/native_mate_converters/callback.cc new file mode 100644 index 000000000000..8bf5c459b0a2 --- /dev/null +++ b/atom/common/native_mate_converters/callback.cc @@ -0,0 +1,137 @@ +// 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. + +#include "atom/common/native_mate_converters/callback.h" + +#include "content/public/browser/browser_thread.h" + +using content::BrowserThread; + +namespace mate { + +namespace internal { + +namespace { + +struct TranslaterHolder { + Translater translater; +}; + +// Cached JavaScript version of |CallTranslater|. +v8::Persistent g_call_translater; + +void CallTranslater(v8::Local external, + v8::Local state, + mate::Arguments* args) { + v8::Isolate* isolate = args->isolate(); + + // Check if the callback has already been called. + v8::Local called_symbol = mate::StringToSymbol(isolate, "called"); + if (state->Has(called_symbol)) { + args->ThrowError("callback can only be called for once"); + return; + } else { + state->Set(called_symbol, v8::Boolean::New(isolate, true)); + } + + TranslaterHolder* holder = static_cast(external->Value()); + holder->translater.Run(args); + delete holder; +} + +// func.bind(func, arg1). +// 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(); +} + +} // namespace + +// Destroy the class on UI thread when possible. +struct DeleteOnUIThread { + template + static void Destruct(const T* x) { + if (Locker::IsBrowserProcess() && + !BrowserThread::CurrentlyOn(BrowserThread::UI)) { + BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, x); + } else { + delete x; + } + } +}; + +// Like v8::Global, but ref-counted. +template +class RefCountedGlobal : public base::RefCountedThreadSafe, + DeleteOnUIThread> { + public: + RefCountedGlobal(v8::Isolate* isolate, v8::Local value) + : handle_(isolate, v8::Local::Cast(value)) { + } + + bool IsAlive() const { + return !handle_.IsEmpty(); + } + + v8::Local NewHandle(v8::Isolate* isolate) const { + return v8::Local::New(isolate, handle_); + } + + private: + v8::Global handle_; + + DISALLOW_COPY_AND_ASSIGN(RefCountedGlobal); +}; + +SafeV8Function::SafeV8Function(v8::Isolate* isolate, v8::Local value) + : v8_function_(new RefCountedGlobal(isolate, value)) { +} + +SafeV8Function::SafeV8Function(const SafeV8Function& other) + : v8_function_(other.v8_function_) { +} + +SafeV8Function::~SafeV8Function() { +} + +bool SafeV8Function::IsAlive() const { + return v8_function_.get() && v8_function_->IsAlive(); +} + +v8::Local SafeV8Function::NewHandle(v8::Isolate* isolate) const { + return v8_function_->NewHandle(isolate); +} + +v8::Local CreateFunctionFromTranslater( + v8::Isolate* isolate, const Translater& translater) { + // The FunctionTemplate is cached. + if (g_call_translater.IsEmpty()) + g_call_translater.Reset( + isolate, + mate::CreateFunctionTemplate(isolate, base::Bind(&CallTranslater))); + + v8::Local call_translater = + v8::Local::New(isolate, g_call_translater); + TranslaterHolder* holder = new TranslaterHolder; + holder->translater = translater; + return BindFunctionWith(isolate, + isolate->GetCurrentContext(), + call_translater->GetFunction(), + v8::External::New(isolate, holder), + v8::Object::New(isolate)); +} + +} // namespace internal + +} // namespace mate diff --git a/atom/common/native_mate_converters/callback.h b/atom/common/native_mate_converters/callback.h index 6e51cda79c49..6ef8e74c735a 100644 --- a/atom/common/native_mate_converters/callback.h +++ b/atom/common/native_mate_converters/callback.h @@ -10,6 +10,7 @@ #include "atom/common/api/locker.h" #include "base/bind.h" #include "base/callback.h" +#include "base/memory/weak_ptr.h" #include "native_mate/function_template.h" #include "native_mate/scoped_persistent.h" #include "third_party/WebKit/public/web/WebScopedMicrotaskSuppression.h" @@ -18,21 +19,40 @@ namespace mate { namespace internal { -typedef scoped_refptr > SafeV8Function; +template +class RefCountedGlobal; +// Manages the V8 function with RAII. +class SafeV8Function { + public: + SafeV8Function(v8::Isolate* isolate, v8::Local value); + SafeV8Function(const SafeV8Function& other); + ~SafeV8Function(); + + bool IsAlive() const; + v8::Local NewHandle(v8::Isolate* isolate) const; + + private: + scoped_refptr> v8_function_; +}; + +// Helper to invoke a V8 function with C++ parameters. template struct V8FunctionInvoker {}; template struct V8FunctionInvoker(ArgTypes...)> { - static v8::Local Go(v8::Isolate* isolate, SafeV8Function function, + static v8::Local Go(v8::Isolate* isolate, + const SafeV8Function& function, ArgTypes... raw) { Locker locker(isolate); v8::EscapableHandleScope handle_scope(isolate); + if (!function.IsAlive()) + return v8::Null(isolate); scoped_ptr script_scope( Locker::IsBrowserProcess() ? - nullptr : new blink::WebScopedRunV8Script(isolate)); - v8::Local holder = function->NewHandle(); + nullptr : new blink::WebScopedRunV8Script); + v8::Local holder = function.NewHandle(isolate); v8::Local context = holder->CreationContext(); v8::Context::Scope context_scope(context); std::vector> args = { ConvertToV8(isolate, raw)... }; @@ -43,14 +63,17 @@ struct V8FunctionInvoker(ArgTypes...)> { template struct V8FunctionInvoker { - static void Go(v8::Isolate* isolate, SafeV8Function function, + static void Go(v8::Isolate* isolate, + const SafeV8Function& function, ArgTypes... raw) { Locker locker(isolate); v8::HandleScope handle_scope(isolate); + if (!function.IsAlive()) + return; scoped_ptr script_scope( Locker::IsBrowserProcess() ? - nullptr : new blink::WebScopedRunV8Script(isolate)); - v8::Local holder = function->NewHandle(); + nullptr : new blink::WebScopedRunV8Script); + v8::Local holder = function.NewHandle(isolate); v8::Local context = holder->CreationContext(); v8::Context::Scope context_scope(context); std::vector> args = { ConvertToV8(isolate, raw)... }; @@ -60,31 +83,60 @@ struct V8FunctionInvoker { template struct V8FunctionInvoker { - static ReturnType Go(v8::Isolate* isolate, SafeV8Function function, + static ReturnType Go(v8::Isolate* isolate, + const SafeV8Function& function, ArgTypes... raw) { Locker locker(isolate); v8::HandleScope handle_scope(isolate); + ReturnType ret = ReturnType(); + if (!function.IsAlive()) + return ret; scoped_ptr script_scope( Locker::IsBrowserProcess() ? - nullptr : new blink::WebScopedRunV8Script(isolate)); - v8::Local holder = function->NewHandle(); + nullptr : new blink::WebScopedRunV8Script); + v8::Local holder = function.NewHandle(isolate); 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); + v8::Local result; + auto maybe_result = + holder->Call(context, holder, args.size(), &args.front()); + if (maybe_result.ToLocal(&result)) + Converter::FromV8(isolate, result, &ret); return ret; } }; +// Helper to pass a C++ funtion to JavaScript. +using Translater = base::Callback; +v8::Local CreateFunctionFromTranslater( + v8::Isolate* isolate, const Translater& translater); + +// Calls callback with Arguments. +template +struct NativeFunctionInvoker {}; + +template +struct NativeFunctionInvoker { + static void Go(base::Callback val, Arguments* args) { + using Indices = typename IndicesGenerator::type; + Invoker invoker(args, 0); + if (invoker.IsOK()) + invoker.DispatchToCallback(val); + } +}; + } // namespace internal template -struct Converter > { +struct Converter> { static v8::Local ToV8(v8::Isolate* isolate, - const base::Callback& val) { - return CreateFunctionTemplate(isolate, val)->GetFunction(); + const base::Callback& val) { + // We don't use CreateFunctionTemplate here because it creates a new + // FunctionTemplate everytime, which is cached by V8 and causes leaks. + internal::Translater translater = base::Bind( + &internal::NativeFunctionInvoker::Go, val); + return internal::CreateFunctionFromTranslater(isolate, translater); } static bool FromV8(v8::Isolate* isolate, v8::Local val, @@ -92,9 +144,8 @@ struct Converter > { if (!val->IsFunction()) return false; - internal::SafeV8Function function( - new RefCountedPersistent(isolate, val)); - *out = base::Bind(&internal::V8FunctionInvoker::Go, isolate, function); + *out = base::Bind(&internal::V8FunctionInvoker::Go, + isolate, internal::SafeV8Function(isolate, val)); return true; } }; diff --git a/atom/common/native_mate_converters/content_converter.cc b/atom/common/native_mate_converters/content_converter.cc new file mode 100644 index 000000000000..15a57dea5fb8 --- /dev/null +++ b/atom/common/native_mate_converters/content_converter.cc @@ -0,0 +1,100 @@ +// 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/content_converter.h" + +#include + +#include "atom/common/native_mate_converters/callback.h" +#include "atom/common/native_mate_converters/string16_converter.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/context_menu_params.h" +#include "native_mate/dictionary.h" + +namespace { + +void ExecuteCommand(content::WebContents* web_contents, + int action, + const content::CustomContextMenuContext& context) { + web_contents->ExecuteCustomContextMenuCommand(action, context); +} + +// Forward declaration for nested recursive call. +v8::Local MenuToV8(v8::Isolate* isolate, + content::WebContents* web_contents, + const content::CustomContextMenuContext& context, + const std::vector& menu); + +v8::Local MenuItemToV8( + v8::Isolate* isolate, + content::WebContents* web_contents, + const content::CustomContextMenuContext& context, + const content::MenuItem& item) { + mate::Dictionary v8_item = mate::Dictionary::CreateEmpty(isolate); + switch (item.type) { + case content::MenuItem::CHECKABLE_OPTION: + case content::MenuItem::GROUP: + v8_item.Set("checked", item.checked); + case content::MenuItem::OPTION: + case content::MenuItem::SUBMENU: + v8_item.Set("label", item.label); + v8_item.Set("enabled", item.enabled); + default: + v8_item.Set("type", item.type); + } + if (item.type == content::MenuItem::SUBMENU) + v8_item.Set("submenu", + MenuToV8(isolate, web_contents, context, item.submenu)); + else if (item.action > 0) + v8_item.Set("click", + base::Bind(ExecuteCommand, web_contents, item.action, context)); + return v8_item.GetHandle(); +} + +v8::Local MenuToV8(v8::Isolate* isolate, + content::WebContents* web_contents, + const content::CustomContextMenuContext& context, + const std::vector& menu) { + std::vector> v8_menu; + for (const auto& menu_item : menu) + v8_menu.push_back(MenuItemToV8(isolate, web_contents, context, menu_item)); + return mate::ConvertToV8(isolate, v8_menu); +} + +} // namespace + +namespace mate { + +// static +v8::Local Converter::ToV8( + v8::Isolate* isolate, const content::MenuItem::Type& val) { + switch (val) { + case content::MenuItem::CHECKABLE_OPTION: + return StringToV8(isolate, "checkbox"); + case content::MenuItem::GROUP: + return StringToV8(isolate, "radio"); + case content::MenuItem::SEPARATOR: + return StringToV8(isolate, "separator"); + case content::MenuItem::SUBMENU: + return StringToV8(isolate, "submenu"); + case content::MenuItem::OPTION: + default: + return StringToV8(isolate, "normal"); + } +} + +// static +v8::Local Converter::ToV8( + v8::Isolate* isolate, const ContextMenuParamsWithWebContents& val) { + const auto& params = val.first; + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.Set("x", params.x); + dict.Set("y", params.y); + if (params.custom_context.is_pepper_menu) + dict.Set("menu", MenuToV8(isolate, val.second, params.custom_context, + params.custom_items)); + return mate::ConvertToV8(isolate, dict); +} + +} // namespace mate diff --git a/atom/common/native_mate_converters/content_converter.h b/atom/common/native_mate_converters/content_converter.h new file mode 100644 index 000000000000..7edee24fa142 --- /dev/null +++ b/atom/common/native_mate_converters/content_converter.h @@ -0,0 +1,37 @@ +// 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_CONTENT_CONVERTER_H_ +#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_CONTENT_CONVERTER_H_ + +#include + +#include "content/public/common/menu_item.h" +#include "native_mate/converter.h" + +namespace content { +struct ContextMenuParams; +class WebContents; +} + +using ContextMenuParamsWithWebContents = + std::pair; + +namespace mate { + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const content::MenuItem::Type& val); +}; + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const ContextMenuParamsWithWebContents& val); +}; + +} // namespace mate + +#endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_CONTENT_CONVERTER_H_ diff --git a/atom/common/native_mate_converters/file_path_converter.h b/atom/common/native_mate_converters/file_path_converter.h index 468f506de8a3..7df1289e243b 100644 --- a/atom/common/native_mate_converters/file_path_converter.h +++ b/atom/common/native_mate_converters/file_path_converter.h @@ -21,6 +21,9 @@ struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Local val, base::FilePath* out) { + if (val->IsNull()) + return true; + base::FilePath::StringType path; if (Converter::FromV8(isolate, val, &path)) { *out = base::FilePath(path); diff --git a/atom/common/native_mate_converters/net_converter.cc b/atom/common/native_mate_converters/net_converter.cc new file mode 100644 index 000000000000..4749a4fedfc2 --- /dev/null +++ b/atom/common/native_mate_converters/net_converter.cc @@ -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. + +#include "atom/common/native_mate_converters/net_converter.h" + +#include + +#include "atom/common/node_includes.h" +#include "native_mate/dictionary.h" +#include "net/cert/x509_certificate.h" +#include "net/url_request/url_request.h" + +namespace mate { + +// static +v8::Local Converter::ToV8( + v8::Isolate* isolate, const net::URLRequest* val) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.Set("method", val->method()); + dict.Set("url", val->url().spec()); + dict.Set("referrer", val->referrer()); + return mate::ConvertToV8(isolate, dict); +} + +// static +v8::Local Converter::ToV8( + v8::Isolate* isolate, const net::AuthChallengeInfo* val) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.Set("isProxy", val->is_proxy); + dict.Set("scheme", val->scheme); + dict.Set("host", val->challenger.host()); + dict.Set("port", static_cast(val->challenger.port())); + dict.Set("realm", val->realm); + return mate::ConvertToV8(isolate, dict); +} + +// static +v8::Local Converter>::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); + auto buffer = node::Buffer::Copy(isolate, + encoded_data.data(), + encoded_data.size()).ToLocalChecked(); + dict.Set("data", buffer); + dict.Set("issuerName", val->issuer().GetDisplayName()); + return dict.GetHandle(); +} + +} // namespace mate diff --git a/atom/common/native_mate_converters/net_converter.h b/atom/common/native_mate_converters/net_converter.h new file mode 100644 index 000000000000..b11c55929b98 --- /dev/null +++ b/atom/common/native_mate_converters/net_converter.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_COMMON_NATIVE_MATE_CONVERTERS_NET_CONVERTER_H_ +#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_NET_CONVERTER_H_ + +#include "base/memory/ref_counted.h" +#include "native_mate/converter.h" + +namespace net { +class AuthChallengeInfo; +class URLRequest; +class X509Certificate; +} + +namespace mate { + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const net::URLRequest* val); +}; + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const net::AuthChallengeInfo* val); +}; + +template<> +struct Converter> { + static v8::Local ToV8(v8::Isolate* isolate, + const scoped_refptr& val); +}; + +} // namespace mate + +#endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_NET_CONVERTER_H_ diff --git a/atom/common/native_mate_converters/v8_value_converter.cc b/atom/common/native_mate_converters/v8_value_converter.cc index a91e614fc6dd..7d3a1277cb8b 100644 --- a/atom/common/native_mate_converters/v8_value_converter.cc +++ b/atom/common/native_mate_converters/v8_value_converter.cc @@ -152,6 +152,10 @@ v8::Local V8ValueConverter::ToV8ValueImpl( return ToV8Object(isolate, static_cast(value)); + case base::Value::TYPE_BINARY: + return ToArrayBuffer(isolate, + static_cast(value)); + default: LOG(ERROR) << "Unexpected value type: " << value->GetType(); return v8::Null(isolate); @@ -200,6 +204,13 @@ v8::Local V8ValueConverter::ToV8Object( return result.GetHandle(); } +v8::Local V8ValueConverter::ToArrayBuffer( + v8::Isolate* isolate, const base::BinaryValue* value) const { + return node::Buffer::Copy(isolate, + value->GetBuffer(), + value->GetSize()).ToLocalChecked(); +} + base::Value* V8ValueConverter::FromV8ValueImpl( FromV8ValueState* state, v8::Local val, diff --git a/atom/common/native_mate_converters/v8_value_converter.h b/atom/common/native_mate_converters/v8_value_converter.h index db108ad9b043..2b695b43747b 100644 --- a/atom/common/native_mate_converters/v8_value_converter.h +++ b/atom/common/native_mate_converters/v8_value_converter.h @@ -41,6 +41,9 @@ class V8ValueConverter { v8::Local ToV8Object( v8::Isolate* isolate, const base::DictionaryValue* dictionary) const; + v8::Local ToArrayBuffer( + v8::Isolate* isolate, + const base::BinaryValue* value) const; base::Value* FromV8ValueImpl(FromV8ValueState* state, v8::Local value, diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index 9d2004deec8b..b1cb84eead90 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -14,6 +14,7 @@ #include "atom/common/node_includes.h" #include "base/command_line.h" #include "base/base_paths.h" +#include "base/environment.h" #include "base/files/file_path.h" #include "base/message_loop/message_loop.h" #include "base/path_service.h" @@ -49,6 +50,7 @@ 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); @@ -141,6 +143,14 @@ void NodeBindings::Initialize() { // Init node. // (we assume node::Init would not modify the parameters under embedded mode). node::Init(nullptr, nullptr, nullptr, nullptr); + +#if defined(OS_WIN) + // uv_init overrides error mode to suppress the default crash dialog, bring + // it back if user wants to show it. + scoped_ptr env(base::Environment::Create()); + if (env->HasVar("ELECTRON_DEFAULT_ERROR_MODE")) + SetErrorMode(0); +#endif } node::Environment* NodeBindings::CreateEnvironment( @@ -218,7 +228,7 @@ void NodeBindings::UvRunOnce() { // Perform microtask checkpoint after running JavaScript. scoped_ptr script_scope( - is_browser_ ? nullptr : new blink::WebScopedRunV8Script(env->isolate())); + is_browser_ ? nullptr : new blink::WebScopedRunV8Script); // Deal with uv events. int r = uv_run(uv_loop_, UV_RUN_NOWAIT); diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index 46687becf84a..a0cb8384a340 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -6,7 +6,7 @@ namespace atom { -namespace switches { +namespace options { const char kTitle[] = "title"; const char kIcon[] = "icon"; @@ -17,51 +17,89 @@ const char kX[] = "x"; const char kY[] = "y"; const char kWidth[] = "width"; const char kHeight[] = "height"; -const char kMinWidth[] = "min-width"; -const char kMinHeight[] = "min-height"; -const char kMaxWidth[] = "max-width"; -const char kMaxHeight[] = "max-height"; +const char kMinWidth[] = "minWidth"; +const char kMinHeight[] = "minHeight"; +const char kMaxWidth[] = "maxWidth"; +const char kMaxHeight[] = "maxHeight"; const char kResizable[] = "resizable"; const char kFullscreen[] = "fullscreen"; // Whether the window should show in taskbar. -const char kSkipTaskbar[] = "skip-taskbar"; +const char kSkipTaskbar[] = "skipTaskbar"; // Start with the kiosk mode, see Opera's page for description: // http://www.opera.com/support/mastering/kiosk/ const char kKiosk[] = "kiosk"; // Make windows stays on the top of all other windows. -const char kAlwaysOnTop[] = "always-on-top"; - -const char kNodeIntegration[] = "node-integration"; +const char kAlwaysOnTop[] = "alwaysOnTop"; // Enable the NSView to accept first mouse event. -const char kAcceptFirstMouse[] = "accept-first-mouse"; +const char kAcceptFirstMouse[] = "acceptFirstMouse"; // Whether window size should include window frame. -const char kUseContentSize[] = "use-content-size"; +const char kUseContentSize[] = "useContentSize"; // The requested title bar style for the window -const char kTitleBarStyle[] = "title-bar-style"; - -// The WebPreferences. -const char kWebPreferences[] = "web-preferences"; - -// The factor of which page should be zoomed. -const char kZoomFactor[] = "zoom-factor"; +const char kTitleBarStyle[] = "titleBarStyle"; // The menu bar is hidden unless "Alt" is pressed. -const char kAutoHideMenuBar[] = "auto-hide-menu-bar"; +const char kAutoHideMenuBar[] = "autoHideMenuBar"; // Enable window to be resized larger than screen. -const char kEnableLargerThanScreen[] = "enable-larger-than-screen"; +const char kEnableLargerThanScreen[] = "enableLargerThanScreen"; // Forces to use dark theme on Linux. -const char kDarkTheme[] = "dark-theme"; +const char kDarkTheme[] = "darkTheme"; + +// Whether the window should be transparent. +const char kTransparent[] = "transparent"; + +// Window type hint. +const char kType[] = "type"; + +// Disable auto-hiding cursor. +const char kDisableAutoHideCursor[] = "disableAutoHideCursor"; + +// Use the OS X's standard window instead of the textured window. +const char kStandardWindow[] = "standardWindow"; + +// Default browser window background color. +const char kBackgroundColor[] = "backgroundColor"; + +// The WebPreferences. +const char kWebPreferences[] = "webPreferences"; + +// The factor of which page should be zoomed. +const char kZoomFactor[] = "zoomFactor"; + +// 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[] = "preloadURL"; + +// Enable the node integration. +const char kNodeIntegration[] = "nodeIntegration"; + +// Instancd ID of guest WebContents. +const char kGuestInstanceID[] = "guestInstanceId"; + +// Set page visiblity to always visible. +const char kPageVisibility[] = "pageVisibility"; // Enable DirectWrite on Windows. -const char kDirectWrite[] = "direct-write"; +const char kDirectWrite[] = "directWrite"; + +// Web runtime features. +const char kExperimentalFeatures[] = "experimentalFeatures"; +const char kExperimentalCanvasFeatures[] = "experimentalCanvasFeatures"; +const char kOverlayScrollbars[] = "overlayScrollbars"; +const char kSharedWorker[] = "sharedWorker"; + +} // namespace options + +namespace switches { // Enable plugins. const char kEnablePlugins[] = "enable-plugins"; @@ -72,41 +110,9 @@ const char kPpapiFlashPath[] = "ppapi-flash-path"; // Ppapi Flash version. const char kPpapiFlashVersion[] = "ppapi-flash-version"; -// Instancd ID of guest WebContents. -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"; - -// Window type hint. -const char kType[] = "type"; - -// Disable auto-hiding cursor. -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"; -const char kSubpixelFontScaling[] = "subpixel-font-scaling"; -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"; @@ -117,9 +123,24 @@ const char kRegisterStandardSchemes[] = "register-standard-schemes"; // TLS fallback will accept. const char kSSLVersionFallbackMin[] = "ssl-version-fallback-min"; +// Comma-separated list of SSL cipher suites to disable. +const char kCipherSuiteBlacklist[] = "cipher-suite-blacklist"; + // The browser process app model ID const char kAppUserModelId[] = "app-user-model-id"; +// The command line switch versions of the options. +const char kZoomFactor[] = "zoom-factor"; +const char kPreloadScript[] = "preload"; +const char kPreloadURL[] = "preload-url"; +const char kNodeIntegration[] = "node-integration"; +const char kGuestInstanceID[] = "guest-instance-id"; +const char kExperimentalFeatures[] = "experimental-features"; +const char kExperimentalCanvasFeatures[] = "experimental-canvas-features"; +const char kOverlayScrollbars[] = "overlay-scrollbars"; +const char kSharedWorker[] = "shared-worker"; +const char kPageVisibility[] = "page-visiblity"; + } // namespace switches } // namespace atom diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index 16046d19c822..6960db83bc10 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -7,7 +7,7 @@ namespace atom { -namespace switches { +namespace options { extern const char kTitle[]; extern const char kIcon[]; @@ -27,42 +27,60 @@ extern const char kFullscreen[]; extern const char kSkipTaskbar[]; extern const char kKiosk[]; 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[]; extern const char kEnableLargerThanScreen[]; extern const char kDarkTheme[]; -extern const char kDirectWrite[]; -extern const char kEnablePlugins[]; -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 kBackgroundColor[]; +extern const char kWebPreferences[]; +// WebPreferences. +extern const char kDirectWrite[]; +extern const char kZoomFactor[]; +extern const char kPreloadScript[]; +extern const char kPreloadURL[]; +extern const char kNodeIntegration[]; +extern const char kGuestInstanceID[]; extern const char kExperimentalFeatures[]; extern const char kExperimentalCanvasFeatures[]; -extern const char kSubpixelFontScaling[]; extern const char kOverlayScrollbars[]; -extern const char kOverlayFullscreenVideo[]; extern const char kSharedWorker[]; extern const char kPageVisibility[]; +} // namespace options + + +// Following are actually command line switches, should be moved to other files. + +namespace switches { + +extern const char kEnablePlugins[]; +extern const char kPpapiFlashPath[]; +extern const char kPpapiFlashVersion[]; +extern const char kClientCertificate[]; extern const char kDisableHttpCache[]; extern const char kRegisterStandardSchemes[]; extern const char kSSLVersionFallbackMin[]; - +extern const char kCipherSuiteBlacklist[]; extern const char kAppUserModelId[]; +extern const char kZoomFactor[]; +extern const char kPreloadScript[]; +extern const char kPreloadURL[]; +extern const char kNodeIntegration[]; +extern const char kGuestInstanceID[]; +extern const char kExperimentalFeatures[]; +extern const char kExperimentalCanvasFeatures[]; +extern const char kOverlayScrollbars[]; +extern const char kSharedWorker[]; +extern const char kPageVisibility[]; + } // namespace switches } // namespace atom diff --git a/atom/common/platform_util_mac.mm b/atom/common/platform_util_mac.mm index 1aa75effd35b..2f9e2b764236 100644 --- a/atom/common/platform_util_mac.mm +++ b/atom/common/platform_util_mac.mm @@ -20,7 +20,7 @@ void ShowItemInFolder(const base::FilePath& full_path) { DCHECK([NSThread isMainThread]); NSString* path_string = base::SysUTF8ToNSString(full_path.value()); if (!path_string || ![[NSWorkspace sharedWorkspace] selectFile:path_string - inFileViewerRootedAtPath:nil]) + inFileViewerRootedAtPath:@""]) LOG(WARNING) << "NSWorkspace failed to select file " << full_path.value(); } diff --git a/atom/common/platform_util_win.cc b/atom/common/platform_util_win.cc index 09ac5aca48f2..cca392952e65 100644 --- a/atom/common/platform_util_win.cc +++ b/atom/common/platform_util_win.cc @@ -5,7 +5,9 @@ #include "atom/common/platform_util.h" #include +#include #include +#include #include #include #include @@ -19,6 +21,7 @@ #include "base/strings/utf_string_conversions.h" #include "base/win/registry.h" #include "base/win/scoped_co_mem.h" +#include "base/win/scoped_com_initializer.h" #include "base/win/scoped_comptr.h" #include "base/win/windows_version.h" #include "url/gurl.h" @@ -42,11 +45,168 @@ bool ValidateShellCommandForScheme(const std::string& scheme) { return true; } +// Required COM implementation of IFileOperationProgressSink so we can +// precheck files before deletion to make sure they can be move to the +// Recycle Bin. +class DeleteFileProgressSink : public IFileOperationProgressSink { + public: + DeleteFileProgressSink(); + + private: + ULONG STDMETHODCALLTYPE AddRef(void); + ULONG STDMETHODCALLTYPE Release(void); + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID* ppvObj); + HRESULT STDMETHODCALLTYPE StartOperations(void); + HRESULT STDMETHODCALLTYPE FinishOperations(HRESULT); + HRESULT STDMETHODCALLTYPE PreRenameItem( + DWORD, IShellItem*, LPCWSTR); + HRESULT STDMETHODCALLTYPE PostRenameItem( + DWORD, IShellItem*, LPCWSTR, HRESULT, IShellItem*); + HRESULT STDMETHODCALLTYPE PreMoveItem( + DWORD, IShellItem*, IShellItem*, LPCWSTR); + HRESULT STDMETHODCALLTYPE PostMoveItem( + DWORD, IShellItem*, IShellItem*, LPCWSTR, HRESULT, IShellItem*); + HRESULT STDMETHODCALLTYPE PreCopyItem( + DWORD, IShellItem*, IShellItem*, LPCWSTR); + HRESULT STDMETHODCALLTYPE PostCopyItem( + DWORD, IShellItem*, IShellItem*, LPCWSTR, HRESULT, IShellItem*); + HRESULT STDMETHODCALLTYPE PreDeleteItem(DWORD, IShellItem*); + HRESULT STDMETHODCALLTYPE PostDeleteItem( + DWORD, IShellItem*, HRESULT, IShellItem*); + HRESULT STDMETHODCALLTYPE PreNewItem( + DWORD, IShellItem*, LPCWSTR); + HRESULT STDMETHODCALLTYPE PostNewItem( + DWORD, IShellItem*, LPCWSTR, LPCWSTR, DWORD, HRESULT, IShellItem*); + HRESULT STDMETHODCALLTYPE UpdateProgress(UINT, UINT); + HRESULT STDMETHODCALLTYPE ResetTimer(void); + HRESULT STDMETHODCALLTYPE PauseTimer(void); + HRESULT STDMETHODCALLTYPE ResumeTimer(void); + + ULONG m_cRef; +}; + +DeleteFileProgressSink::DeleteFileProgressSink() { + m_cRef = 0; +} + +HRESULT DeleteFileProgressSink::PreDeleteItem(DWORD dwFlags, IShellItem*) { + if (!(dwFlags & TSF_DELETE_RECYCLE_IF_POSSIBLE)) { + // TSF_DELETE_RECYCLE_IF_POSSIBLE will not be set for items that cannot be + // recycled. In this case, we abort the delete operation. This bubbles + // up and stops the Delete in IFileOperation. + return E_ABORT; + } + // Returns S_OK if successful, or an error value otherwise. In the case of an + // error value, the delete operation and all subsequent operations pending + // from the call to IFileOperation are canceled. + return S_OK; +} + +HRESULT DeleteFileProgressSink::QueryInterface(REFIID riid, LPVOID* ppvObj) { + // Always set out parameter to NULL, validating it first. + if (!ppvObj) + return E_INVALIDARG; + *ppvObj = nullptr; + if (riid == IID_IUnknown || riid == IID_IFileOperationProgressSink) { + // Increment the reference count and return the pointer. + *ppvObj = reinterpret_cast(this); + AddRef(); + return NOERROR; + } + return E_NOINTERFACE; +} + +ULONG DeleteFileProgressSink::AddRef() { + InterlockedIncrement(&m_cRef); + return m_cRef; +} + +ULONG DeleteFileProgressSink::Release() { + // Decrement the object's internal counter. + ULONG ulRefCount = InterlockedDecrement(&m_cRef); + if (0 == m_cRef) { + delete this; + } + return ulRefCount; +} + +HRESULT DeleteFileProgressSink::StartOperations() { + return S_OK; +} + +HRESULT DeleteFileProgressSink::FinishOperations(HRESULT) { + return S_OK; +} + +HRESULT DeleteFileProgressSink::PreRenameItem(DWORD, IShellItem*, LPCWSTR) { + return S_OK; +} + +HRESULT DeleteFileProgressSink::PostRenameItem( + DWORD, IShellItem*, __RPC__in_string LPCWSTR, HRESULT, IShellItem*) { + return E_NOTIMPL; +} + +HRESULT DeleteFileProgressSink::PreMoveItem( + DWORD, IShellItem*, IShellItem*, LPCWSTR) { + return E_NOTIMPL; +} + +HRESULT DeleteFileProgressSink::PostMoveItem( + DWORD, IShellItem*, IShellItem*, LPCWSTR, HRESULT, IShellItem*) { + return E_NOTIMPL; +} + +HRESULT DeleteFileProgressSink::PreCopyItem( + DWORD, IShellItem*, IShellItem*, LPCWSTR) { + return E_NOTIMPL; +} + +HRESULT DeleteFileProgressSink::PostCopyItem( + DWORD, IShellItem*, IShellItem*, LPCWSTR, HRESULT, IShellItem*) { + return E_NOTIMPL; +} + +HRESULT DeleteFileProgressSink::PostDeleteItem( + DWORD, IShellItem*, HRESULT, IShellItem*) { + return S_OK; +} + +HRESULT DeleteFileProgressSink::PreNewItem( + DWORD dwFlags, IShellItem*, LPCWSTR) { + return E_NOTIMPL; +} + +HRESULT DeleteFileProgressSink::PostNewItem( + DWORD, IShellItem*, LPCWSTR, LPCWSTR, DWORD, HRESULT, IShellItem*) { + return E_NOTIMPL; +} + +HRESULT DeleteFileProgressSink::UpdateProgress(UINT, UINT) { + return S_OK; +} + +HRESULT DeleteFileProgressSink::ResetTimer() { + return S_OK; +} + +HRESULT DeleteFileProgressSink::PauseTimer() { + return S_OK; +} + +HRESULT DeleteFileProgressSink::ResumeTimer() { + return S_OK; +} + } // namespace namespace platform_util { void ShowItemInFolder(const base::FilePath& full_path) { + base::win::ScopedCOMInitializer com_initializer; + if (!com_initializer.succeeded()) + return; + base::FilePath dir = full_path.DirName().AsEndingWithSeparator(); // ParseDisplayName will fail if the directory is "C:", it must be "C:\\". if (dir.empty()) @@ -147,8 +307,8 @@ bool OpenExternal(const GURL& url) { // "Some versions of windows (Win2k before SP3, Win XP before SP1) crash in // ShellExecute on long URLs (bug 161357 on bugzilla.mozilla.org). IE 5 and 6 // support URLS of 2083 chars in length, 2K is safe." - const size_t kMaxUrlLength = 2048; - if (escaped_url.length() > kMaxUrlLength) { + const size_t kMaxURLLength = 2048; + if (escaped_url.length() > kMaxURLLength) { NOTREACHED(); return false; } @@ -170,32 +330,40 @@ bool OpenExternal(const GURL& url) { } bool MoveItemToTrash(const base::FilePath& path) { - // SHFILEOPSTRUCT wants the path to be terminated with two NULLs, - // so we have to use wcscpy because wcscpy_s writes non-NULLs - // into the rest of the buffer. - wchar_t double_terminated_path[MAX_PATH + 1] = {0}; -#pragma warning(suppress:4996) // don't complain about wcscpy deprecation - wcscpy(double_terminated_path, path.value().c_str()); - - SHFILEOPSTRUCT file_operation = {0}; - file_operation.wFunc = FO_DELETE; - file_operation.pFrom = double_terminated_path; - file_operation.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOCONFIRMATION; - int err = SHFileOperation(&file_operation); - - // Since we're passing flags to the operation telling it to be silent, - // it's possible for the operation to be aborted/cancelled without err - // being set (although MSDN doesn't give any scenarios for how this can - // happen). See MSDN for SHFileOperation and SHFILEOPTSTRUCT. - if (file_operation.fAnyOperationsAborted) + base::win::ScopedCOMInitializer com_initializer; + if (!com_initializer.succeeded()) return false; - // Some versions of Windows return ERROR_FILE_NOT_FOUND (0x2) when deleting - // an empty directory and some return 0x402 when they should be returning - // ERROR_FILE_NOT_FOUND. MSDN says Vista and up won't return 0x402. Windows 7 - // can return DE_INVALIDFILES (0x7C) for nonexistent directories. - return (err == 0 || err == ERROR_FILE_NOT_FOUND || err == 0x402 || - err == 0x7C); + base::win::ScopedComPtr pfo; + if (FAILED(pfo.CreateInstance(CLSID_FileOperation))) + return false; + + // Elevation prompt enabled for UAC protected files. This overrides the + // SILENT, NO_UI and NOERRORUI flags. + if (FAILED(pfo->SetOperationFlags(FOF_NO_UI | + FOF_ALLOWUNDO | + FOF_NOERRORUI | + FOF_SILENT | + FOFX_SHOWELEVATIONPROMPT | + FOFX_RECYCLEONDELETE))) + return false; + + // Create an IShellItem from the supplied source path. + base::win::ScopedComPtr delete_item; + if (FAILED(SHCreateItemFromParsingName(path.value().c_str(), + NULL, + IID_PPV_ARGS(delete_item.Receive())))) + return false; + + base::win::ScopedComPtr delete_sink( + new DeleteFileProgressSink); + if (!delete_sink) + return false; + + // Processes the queued command DeleteItem. This will trigger + // the DeleteFileProgressSink to check for Recycle Bin. + return SUCCEEDED(pfo->DeleteItem(delete_item.get(), delete_sink.get())) && + SUCCEEDED(pfo->PerformOperations()); } void Beep() { diff --git a/atom/common/resources/mac/Info.plist b/atom/common/resources/mac/Info.plist index 332babe979e6..7b56a46470ea 100644 --- a/atom/common/resources/mac/Info.plist +++ b/atom/common/resources/mac/Info.plist @@ -2,12 +2,12 @@ - CFBundleExecutable - ${PRODUCT_NAME} Framework CFBundleIdentifier ${ATOM_BUNDLE_ID} CFBundleName - ${PRODUCT_NAME} Framework + ${PRODUCT_NAME} + CFBundleExecutable + ${PRODUCT_NAME} CFBundlePackageType FMWK NSSupportsAutomaticGraphicsSwitching diff --git a/atom/renderer/api/atom_api_web_frame.cc b/atom/renderer/api/atom_api_web_frame.cc index 4506658588c2..e9b2b03055e6 100644 --- a/atom/renderer/api/atom_api_web_frame.cc +++ b/atom/renderer/api/atom_api_web_frame.cc @@ -4,11 +4,13 @@ #include "atom/renderer/api/atom_api_web_frame.h" +#include "atom/common/api/api_messages.h" #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" +#include "content/public/renderer/render_view.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" #include "third_party/WebKit/public/web/WebDocument.h" @@ -34,6 +36,10 @@ void WebFrame::SetName(const std::string& name) { } double WebFrame::SetZoomLevel(double level) { + auto render_view = content::RenderView::FromWebView(web_frame_->view()); + // Notify guests if any for zoom level change. + render_view->Send( + new AtomViewHostMsg_ZoomLevelChanged(MSG_ROUTING_NONE, level)); return web_frame_->view()->setZoomLevel(level); } @@ -92,7 +98,7 @@ void WebFrame::RegisterURLSchemeAsSecure(const std::string& scheme) { blink::WebString::fromUTF8(scheme)); } -void WebFrame::RegisterURLSchemeAsBypassingCsp(const std::string& scheme) { +void WebFrame::RegisterURLSchemeAsBypassingCSP(const std::string& scheme) { // Register scheme to bypass pages's Content Security Policy. blink::WebSecurityPolicy::registerURLSchemeAsBypassingContentSecurityPolicy( blink::WebString::fromUTF8(scheme)); @@ -123,11 +129,11 @@ mate::ObjectTemplateBuilder WebFrame::GetObjectTemplateBuilder( &WebFrame::RegisterElementResizeCallback) .SetMethod("attachGuest", &WebFrame::AttachGuest) .SetMethod("setSpellCheckProvider", &WebFrame::SetSpellCheckProvider) - .SetMethod("registerUrlSchemeAsSecure", + .SetMethod("registerURLSchemeAsSecure", &WebFrame::RegisterURLSchemeAsSecure) - .SetMethod("registerUrlSchemeAsBypassingCsp", - &WebFrame::RegisterURLSchemeAsBypassingCsp) - .SetMethod("registerUrlSchemeAsPrivileged", + .SetMethod("registerURLSchemeAsBypassingCSP", + &WebFrame::RegisterURLSchemeAsBypassingCSP) + .SetMethod("registerURLSchemeAsPrivileged", &WebFrame::RegisterURLSchemeAsPrivileged); } diff --git a/atom/renderer/api/atom_api_web_frame.h b/atom/renderer/api/atom_api_web_frame.h index a3dec6cb7689..95a5a82a313d 100644 --- a/atom/renderer/api/atom_api_web_frame.h +++ b/atom/renderer/api/atom_api_web_frame.h @@ -57,7 +57,7 @@ class WebFrame : public mate::Wrappable { v8::Local provider); void RegisterURLSchemeAsSecure(const std::string& scheme); - void RegisterURLSchemeAsBypassingCsp(const std::string& scheme); + void RegisterURLSchemeAsBypassingCSP(const std::string& scheme); void RegisterURLSchemeAsPrivileged(const std::string& scheme); // mate::Wrappable: diff --git a/atom/renderer/api/lib/exports/electron.coffee b/atom/renderer/api/lib/exports/electron.coffee new file mode 100644 index 000000000000..5d7f2a57edd0 --- /dev/null +++ b/atom/renderer/api/lib/exports/electron.coffee @@ -0,0 +1,17 @@ +# Import common modules. +module.exports = require '../../../../common/api/lib/exports/electron' + +Object.defineProperties module.exports, + # Renderer side modules, please sort with alphabet order. + ipcRenderer: + enumerable: true + get: -> require '../ipc-renderer' + remote: + enumerable: true + get: -> require '../remote' + screen: + enumerable: true + get: -> require '../screen' + webFrame: + enumerable: true + get: -> require '../web-frame' diff --git a/atom/renderer/api/lib/ipc-renderer.coffee b/atom/renderer/api/lib/ipc-renderer.coffee new file mode 100644 index 000000000000..92be75aa20ba --- /dev/null +++ b/atom/renderer/api/lib/ipc-renderer.coffee @@ -0,0 +1,24 @@ +{EventEmitter} = require 'events' + +binding = process.atomBinding 'ipc' +v8Util = process.atomBinding 'v8_util' + +# Created by init.coffee. +ipcRenderer = v8Util.getHiddenValue global, 'ipc' + +# Delay the callback to next tick in case the browser is still in the middle +# of sending a message while the callback sends a sync message to browser, +# which can fail sometimes. +ipcRenderer.emit = (args...) -> + setTimeout (-> EventEmitter::emit.call ipcRenderer, args...), 0 + +ipcRenderer.send = (args...) -> + binding.send 'ipc-message', [args...] + +ipcRenderer.sendSync = (args...) -> + JSON.parse binding.sendSync('ipc-message-sync', [args...]) + +ipcRenderer.sendToHost = (args...) -> + binding.send 'ipc-message-host', [args...] + +module.exports = ipcRenderer diff --git a/atom/renderer/api/lib/ipc.coffee b/atom/renderer/api/lib/ipc.coffee index 1c508a3a5493..edd7d29b6f06 100644 --- a/atom/renderer/api/lib/ipc.coffee +++ b/atom/renderer/api/lib/ipc.coffee @@ -1,20 +1,19 @@ -binding = process.atomBinding 'ipc' -v8Util = process.atomBinding 'v8_util' +{ipcRenderer, deprecate} = require 'electron' +{EventEmitter} = require 'events' -# Created by init.coffee. -ipc = v8Util.getHiddenValue global, 'ipc' +# This module is deprecated, we mirror everything from ipcRenderer. +deprecate.warn 'ipc module', 'require("electron").ipcRenderer' -ipc.send = (args...) -> - binding.send 'ipc-message', [args...] - -ipc.sendSync = (args...) -> - JSON.parse binding.sendSync('ipc-message-sync', [args...]) - -ipc.sendToHost = (args...) -> - binding.send 'ipc-message-host', [args...] +# Routes events of ipcRenderer. +ipc = new EventEmitter +ipcRenderer.emit = (channel, event, args...) -> + ipc.emit channel, args... + EventEmitter::emit.apply ipcRenderer, arguments # Deprecated. -ipc.sendChannel = ipc.send -ipc.sendChannelSync = ipc.sendSync +for method of ipcRenderer when method.startsWith 'send' + ipc[method] = ipcRenderer[method] +deprecate.rename ipc, 'sendChannel', 'send' +deprecate.rename ipc, 'sendChannelSync', 'sendSync' module.exports = ipc diff --git a/atom/renderer/api/lib/remote.coffee b/atom/renderer/api/lib/remote.coffee index 00d22ab8cf6d..48cdd937fb07 100644 --- a/atom/renderer/api/lib/remote.coffee +++ b/atom/renderer/api/lib/remote.coffee @@ -1,6 +1,5 @@ -ipc = require 'ipc' +{ipcRenderer, CallbacksRegistry} = require 'electron' v8Util = process.atomBinding 'v8_util' -CallbacksRegistry = require 'callbacks-registry' callbacksRegistry = new CallbacksRegistry @@ -19,7 +18,7 @@ wrapArgs = (args, visited=[]) -> 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' + else if 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' @@ -33,7 +32,7 @@ wrapArgs = (args, visited=[]) -> else if typeof value is 'function' and v8Util.getHiddenValue value, 'returnValue' type: 'function-with-return-value', value: valueToMeta(value()) else if typeof value is 'function' - type: 'function', id: callbacksRegistry.add(value) + type: 'function', id: callbacksRegistry.add(value), location: v8Util.getHiddenValue value, 'location' else type: 'value', value: value @@ -46,7 +45,9 @@ metaToValue = (meta) -> 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' + when 'error' then metaToPlainObject meta + when 'date' then new Date(meta.value) + when 'exception' throw new Error("#{meta.message}\n#{meta.stack}") else if meta.type is 'function' @@ -56,7 +57,7 @@ metaToValue = (meta) -> constructor: -> if @constructor == RemoteFunction # Constructor call. - obj = ipc.sendSync 'ATOM_BROWSER_CONSTRUCTOR', meta.id, wrapArgs(arguments) + obj = ipcRenderer.sendSync 'ATOM_BROWSER_CONSTRUCTOR', meta.id, wrapArgs(arguments) # Returning object in constructor will replace constructed object # with the returned object. @@ -64,7 +65,7 @@ metaToValue = (meta) -> return metaToValue obj else # Function call. - ret = ipc.sendSync 'ATOM_BROWSER_FUNCTION_CALL', meta.id, wrapArgs(arguments) + ret = ipcRenderer.sendSync 'ATOM_BROWSER_FUNCTION_CALL', meta.id, wrapArgs(arguments) return metaToValue ret else ret = v8Util.createObjectWithName meta.name @@ -78,11 +79,11 @@ metaToValue = (meta) -> constructor: -> if @constructor is RemoteMemberFunction # Constructor call. - obj = ipc.sendSync 'ATOM_BROWSER_MEMBER_CONSTRUCTOR', meta.id, member.name, wrapArgs(arguments) + obj = ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_CONSTRUCTOR', meta.id, member.name, wrapArgs(arguments) return metaToValue obj else # Call member function. - ret = ipc.sendSync 'ATOM_BROWSER_MEMBER_CALL', meta.id, member.name, wrapArgs(arguments) + ret = ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_CALL', meta.id, member.name, wrapArgs(arguments) return metaToValue ret else Object.defineProperty ret, member.name, @@ -90,32 +91,47 @@ metaToValue = (meta) -> configurable: false, set: (value) -> # Set member data. - ipc.sendSync 'ATOM_BROWSER_MEMBER_SET', meta.id, member.name, value + ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_SET', meta.id, member.name, value value get: -> # Get member data. - ret = ipc.sendSync 'ATOM_BROWSER_MEMBER_GET', meta.id, member.name + ret = ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_GET', meta.id, member.name metaToValue ret # 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.id + ipcRenderer.send 'ATOM_BROWSER_DEREFERENCE', meta.id # Remember object's id. v8Util.setHiddenValue ret, 'atomId', meta.id ret +# Construct a plain object from the meta. +metaToPlainObject = (meta) -> + obj = switch meta.type + when 'error' then new Error + else {} + obj[name] = value for {name, value} in meta.members + obj + # Browser calls a callback in renderer. -ipc.on 'ATOM_RENDERER_CALLBACK', (id, args) -> +ipcRenderer.on 'ATOM_RENDERER_CALLBACK', (event, id, args) -> callbacksRegistry.apply id, metaToValue(args) # A callback in browser is released. -ipc.on 'ATOM_RENDERER_RELEASE_CALLBACK', (id) -> +ipcRenderer.on 'ATOM_RENDERER_RELEASE_CALLBACK', (event, id) -> callbacksRegistry.remove id +# List all built-in modules in browser process. +browserModules = ipcRenderer.sendSync 'ATOM_BROWSER_LIST_MODULES' +# And add a helper receiver for each one. +for name in browserModules + do (name) -> + Object.defineProperty exports, name, get: -> exports.getBuiltin name + # Get remote module. # (Just like node's require, the modules are cached permanently, note that this # is safe leak since the object is not expected to get freed in browser) @@ -123,26 +139,37 @@ moduleCache = {} exports.require = (module) -> return moduleCache[module] if moduleCache[module]? - meta = ipc.sendSync 'ATOM_BROWSER_REQUIRE', module + meta = ipcRenderer.sendSync 'ATOM_BROWSER_REQUIRE', module moduleCache[module] = metaToValue meta +# Optimize require('electron'). +moduleCache.electron = exports + +# Alias to remote.require('electron').xxx. +builtinCache = {} +exports.getBuiltin = (module) -> + return builtinCache[module] if builtinCache[module]? + + meta = ipcRenderer.sendSync 'ATOM_BROWSER_GET_BUILTIN', module + builtinCache[module] = metaToValue meta + # Get current BrowserWindow object. windowCache = null exports.getCurrentWindow = -> return windowCache if windowCache? - meta = ipc.sendSync 'ATOM_BROWSER_CURRENT_WINDOW', process.guestInstanceId + meta = ipcRenderer.sendSync 'ATOM_BROWSER_CURRENT_WINDOW' windowCache = metaToValue meta # Get current WebContents object. webContentsCache = null exports.getCurrentWebContents = -> return webContentsCache if webContentsCache? - meta = ipc.sendSync 'ATOM_BROWSER_CURRENT_WEB_CONTENTS' + meta = ipcRenderer.sendSync 'ATOM_BROWSER_CURRENT_WEB_CONTENTS' webContentsCache = metaToValue meta # Get a global object in browser. exports.getGlobal = (name) -> - meta = ipc.sendSync 'ATOM_BROWSER_GLOBAL', name + meta = ipcRenderer.sendSync 'ATOM_BROWSER_GLOBAL', name metaToValue meta # Get the process object in browser. @@ -159,5 +186,5 @@ exports.createFunctionWithReturnValue = (returnValue) -> # Get the guest WebContents from guestInstanceId. exports.getGuestWebContents = (guestInstanceId) -> - meta = ipc.sendSync 'ATOM_BROWSER_GUEST_WEB_CONTENTS', guestInstanceId + meta = ipcRenderer.sendSync 'ATOM_BROWSER_GUEST_WEB_CONTENTS', guestInstanceId metaToValue meta diff --git a/atom/renderer/api/lib/screen.coffee b/atom/renderer/api/lib/screen.coffee index c9da35431f24..9eecd49dc5bf 100644 --- a/atom/renderer/api/lib/screen.coffee +++ b/atom/renderer/api/lib/screen.coffee @@ -1 +1 @@ -module.exports = require('remote').require('screen') +module.exports = require('electron').remote.screen diff --git a/atom/renderer/api/lib/web-frame.coffee b/atom/renderer/api/lib/web-frame.coffee index 6525730e86f1..53564c615ca4 100644 --- a/atom/renderer/api/lib/web-frame.coffee +++ b/atom/renderer/api/lib/web-frame.coffee @@ -1 +1,9 @@ -module.exports = process.atomBinding('web_frame').webFrame +{deprecate} = require 'electron' +{webFrame} = process.atomBinding 'web_frame' + +# Deprecated. +deprecate.rename webFrame, 'registerUrlSchemeAsSecure', 'registerURLSchemeAsSecure' +deprecate.rename webFrame, 'registerUrlSchemeAsBypassingCSP', 'registerURLSchemeAsBypassingCSP' +deprecate.rename webFrame, 'registerUrlSchemeAsPrivileged', 'registerURLSchemeAsPrivileged' + +module.exports = webFrame diff --git a/atom/renderer/atom_render_view_observer.cc b/atom/renderer/atom_render_view_observer.cc index 456ca5ba4b24..931913dd75d0 100644 --- a/atom/renderer/atom_render_view_observer.cc +++ b/atom/renderer/atom_render_view_observer.cc @@ -31,6 +31,7 @@ #include "third_party/WebKit/public/web/WebScriptSource.h" #include "third_party/WebKit/public/web/WebView.h" #include "ui/base/resource/resource_bundle.h" +#include "native_mate/dictionary.h" namespace atom { @@ -142,7 +143,12 @@ void AtomRenderViewObserver::OnBrowserMessage(const base::string16& channel, v8::Local ipc; if (GetIPCObject(isolate, context, &ipc)) { - mate::EmitEvent(isolate, ipc, channel, ListValueToVector(isolate, args)); + auto args_vector = ListValueToVector(isolate, args); + // Insert the Event object, event.sender is ipc. + mate::Dictionary event = mate::Dictionary::CreateEmpty(isolate); + event.Set("sender", ipc); + args_vector.insert(args_vector.begin(), event.GetHandle()); + mate::EmitEvent(isolate, ipc, channel, args_vector); } } diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index b99372bf816d..7c04c04249a7 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -6,6 +6,7 @@ #include +#include "atom/common/api/api_messages.h" #include "atom/common/api/atom_bindings.h" #include "atom/common/node_bindings.h" #include "atom/common/node_includes.h" @@ -21,11 +22,13 @@ #include "content/public/renderer/render_frame.h" #include "content/public/renderer/render_frame_observer.h" #include "content/public/renderer/render_thread.h" +#include "ipc/ipc_message_macros.h" #include "third_party/WebKit/public/web/WebCustomElement.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 "third_party/WebKit/public/web/WebView.h" #if defined(OS_WIN) #include @@ -36,16 +39,8 @@ namespace atom { namespace { bool IsSwitchEnabled(base::CommandLine* command_line, - const char* switch_string, - bool* enabled) { - std::string value = command_line->GetSwitchValueASCII(switch_string); - if (value == "true") - *enabled = true; - else if (value == "false") - *enabled = false; - else - return false; - return true; + const char* switch_string) { + return command_line->GetSwitchValueASCII(switch_string) == "true"; } // Helper class to forward the messages to the client. @@ -64,6 +59,22 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { render_frame()->GetWebFrame(), context); } + bool OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(AtomRenderFrameObserver, message) + IPC_MESSAGE_HANDLER(AtomViewMsg_SetZoomLevel, OnSetZoomLevel) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + + return handled; + } + + void OnSetZoomLevel(double level) { + auto view = render_frame()->GetWebFrame()->view(); + if (view) + view->setZoomLevel(level); + } + private: AtomRendererClient* renderer_client_; @@ -197,10 +208,8 @@ 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) { + if (IsSwitchEnabled(command_line, switches::kPageVisibility)) { *override_state = blink::WebPageVisibilityStateVisible; return true; } @@ -210,19 +219,15 @@ bool AtomRendererClient::ShouldOverridePageVisibilityState( void AtomRendererClient::EnableWebRuntimeFeatures() { base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); - bool b; - if (IsSwitchEnabled(command_line, switches::kExperimentalFeatures, &b)) - blink::WebRuntimeFeatures::enableExperimentalFeatures(b); - if (IsSwitchEnabled(command_line, switches::kExperimentalCanvasFeatures, &b)) - blink::WebRuntimeFeatures::enableExperimentalCanvasFeatures(b); - if (IsSwitchEnabled(command_line, switches::kSubpixelFontScaling, &b)) - blink::WebRuntimeFeatures::enableSubpixelFontScaling(b); - if (IsSwitchEnabled(command_line, switches::kOverlayScrollbars, &b)) - blink::WebRuntimeFeatures::enableOverlayScrollbars(b); - if (IsSwitchEnabled(command_line, switches::kOverlayFullscreenVideo, &b)) - blink::WebRuntimeFeatures::enableOverlayFullscreenVideo(b); - if (IsSwitchEnabled(command_line, switches::kSharedWorker, &b)) - blink::WebRuntimeFeatures::enableSharedWorker(b); + + if (IsSwitchEnabled(command_line, switches::kExperimentalFeatures)) + blink::WebRuntimeFeatures::enableExperimentalFeatures(true); + if (IsSwitchEnabled(command_line, switches::kExperimentalCanvasFeatures)) + blink::WebRuntimeFeatures::enableExperimentalCanvasFeatures(true); + if (IsSwitchEnabled(command_line, switches::kOverlayScrollbars)) + blink::WebRuntimeFeatures::enableOverlayScrollbars(true); + if (IsSwitchEnabled(command_line, switches::kSharedWorker)) + blink::WebRuntimeFeatures::enableSharedWorker(true); } } // namespace atom diff --git a/atom/renderer/lib/init.coffee b/atom/renderer/lib/init.coffee index 274c50ec5f39..d9f104f8d4fc 100644 --- a/atom/renderer/lib/init.coffee +++ b/atom/renderer/lib/init.coffee @@ -7,17 +7,19 @@ Module = require 'module' # atom-renderer.js, we need to restore it here. process.argv.splice 1, 1 -# Add renderer/api/lib to require's search paths, which contains javascript part -# of Atom's built-in libraries. -globalPaths = Module.globalPaths -globalPaths.push path.resolve(__dirname, '..', 'api', 'lib') -# And also app. -globalPaths.push path.join(process.resourcesPath, 'app') -globalPaths.push path.join(process.resourcesPath, 'app.asar') +# Clear search paths. +require path.resolve(__dirname, '..', '..', 'common', 'lib', 'reset-search-paths') # Import common settings. require path.resolve(__dirname, '..', '..', 'common', 'lib', 'init') +globalPaths = Module.globalPaths +unless process.env.ELECTRON_HIDE_INTERNAL_MODULES + globalPaths.push path.resolve(__dirname, '..', 'api', 'lib') + +# Expose public APIs. +globalPaths.push path.resolve(__dirname, '..', 'api', 'lib', 'exports') + # The global variable will be used by ipc for event dispatching v8Util = process.atomBinding 'v8_util' v8Util.setHiddenValue global, 'ipc', new events.EventEmitter diff --git a/atom/renderer/lib/inspector.coffee b/atom/renderer/lib/inspector.coffee index 126f68f9608e..d5ddfd72e486 100644 --- a/atom/renderer/lib/inspector.coffee +++ b/atom/renderer/lib/inspector.coffee @@ -32,8 +32,8 @@ convertToMenuTemplate = (items) -> template createMenu = (x, y, items, document) -> - remote = require 'remote' - Menu = remote.require 'menu' + {remote} = require 'electron' + {Menu} = remote menu = Menu.buildFromTemplate convertToMenuTemplate(items) # The menu is expected to show asynchronously. @@ -42,9 +42,9 @@ createMenu = (x, y, items, document) -> DevToolsAPI.contextMenuCleared() showFileChooserDialog = (callback) -> - remote = require 'remote' - dialog = remote.require 'dialog' - files = dialog.showOpenDialog remote.getCurrentWindow(), null + {remote} = require 'electron' + {dialog} = remote + files = dialog.showOpenDialog {} callback pathToHtml5FileObject files[0] if files? pathToHtml5FileObject = (path) -> diff --git a/atom/renderer/lib/override.coffee b/atom/renderer/lib/override.coffee index 5cffdd486d9c..cb4fb8fbac2d 100644 --- a/atom/renderer/lib/override.coffee +++ b/atom/renderer/lib/override.coffee @@ -1,9 +1,8 @@ -ipc = require 'ipc' -remote = require 'remote' +{ipcRenderer, remote} = require 'electron' # Helper function to resolve relative url. a = window.top.document.createElement 'a' -resolveUrl = (url) -> +resolveURL = (url) -> a.href = url a.href @@ -11,24 +10,23 @@ resolveUrl = (url) -> class BrowserWindowProxy constructor: (@guestId) -> @closed = false - ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED', (guestId) => - if guestId is @guestId - @closed = true + ipcRenderer.once "ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_#{@guestId}", => + @closed = true close: -> - ipc.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', @guestId + ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', @guestId focus: -> - ipc.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', @guestId, 'focus' + ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', @guestId, 'focus' blur: -> - ipc.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', @guestId, 'blur' + ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', @guestId, 'blur' postMessage: (message, targetOrigin='*') -> - ipc.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', @guestId, message, targetOrigin + ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', @guestId, message, targetOrigin eval: (args...) -> - ipc.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', @guestId, 'executeJavaScript', args... + ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', @guestId, 'executeJavaScript', args... unless process.guestInstanceId? # Override default window.close. @@ -56,17 +54,11 @@ window.open = (url, frameName='', features='') -> options.height ?= 600 # Resolve relative urls. - url = resolveUrl url + 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 + guestId = ipcRenderer.sendSync 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, options if guestId new BrowserWindowProxy(guestId) else @@ -74,32 +66,30 @@ window.open = (url, frameName='', features='') -> # Use the dialog API to implement alert(). window.alert = (message, title='') -> - dialog = remote.require 'dialog' buttons = ['OK'] message = message.toString() - dialog.showMessageBox remote.getCurrentWindow(), {message, title, buttons} + remote.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'] cancelId = 1 - not dialog.showMessageBox remote.getCurrentWindow(), {message, title, buttons, cancelId} + not remote.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.') # Implement window.postMessage if current window is a guest window. -guestId = ipc.sendSync 'ATOM_SHELL_GUEST_WINDOW_MANAGER_GET_GUEST_ID' +guestId = ipcRenderer.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 + ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', guestId, message, targetOrigin, location.origin -ipc.on 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', (guestId, message, sourceOrigin) -> +ipcRenderer.on 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', (event, guestId, message, sourceOrigin) -> # Manually dispatch event instead of using postMessage because we also need to # set event.source. event = document.createEvent 'Event' @@ -111,10 +101,10 @@ ipc.on 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', (guestId, message, sourceOrigin) - # Forward history operations to browser. sendHistoryOperation = (args...) -> - ipc.send 'ATOM_SHELL_NAVIGATION_CONTROLLER', args... + ipcRenderer.send 'ATOM_SHELL_NAVIGATION_CONTROLLER', args... getHistoryOperation = (args...) -> - ipc.sendSync 'ATOM_SHELL_SYNC_NAVIGATION_CONTROLLER', args... + ipcRenderer.sendSync 'ATOM_SHELL_SYNC_NAVIGATION_CONTROLLER', args... window.history.back = -> sendHistoryOperation 'goBack' window.history.forward = -> sendHistoryOperation 'goForward' @@ -122,3 +112,7 @@ window.history.go = (offset) -> sendHistoryOperation 'goToOffset', offset Object.defineProperty window.history, 'length', get: -> getHistoryOperation 'length' + +# Make document.hidden return the correct value. +Object.defineProperty document, 'hidden', + get: -> !remote.getCurrentWindow().isVisible() diff --git a/atom/renderer/lib/web-view/guest-view-internal.coffee b/atom/renderer/lib/web-view/guest-view-internal.coffee index 2852d1122874..b28fec23ed83 100644 --- a/atom/renderer/lib/web-view/guest-view-internal.coffee +++ b/atom/renderer/lib/web-view/guest-view-internal.coffee @@ -1,19 +1,18 @@ -ipc = require 'ipc' -webFrame = require 'web-frame' +{ipcRenderer, webFrame} = require 'electron' requestId = 0 WEB_VIEW_EVENTS = 'load-commit': ['url', 'isMainFrame'] 'did-finish-load': [] - 'did-fail-load': ['errorCode', 'errorDescription', 'validatedUrl'] + 'did-fail-load': ['errorCode', 'errorDescription', 'validatedURL'] 'did-frame-finish-load': ['isMainFrame'] 'did-start-loading': [] 'did-stop-loading': [] - 'did-get-response-details': ['status', 'newUrl', 'originalUrl', + 'did-get-response-details': ['status', 'newURL', 'originalURL', 'httpResponseCode', 'requestMethod', 'referrer', 'headers'] - 'did-get-redirect-request': ['oldUrl', 'newUrl', 'isMainFrame'] + 'did-get-redirect-request': ['oldURL', 'newURL', 'isMainFrame'] 'dom-ready': [] 'console-message': ['level', 'message', 'line', 'sourceId'] 'new-window': ['url', 'frameName', 'disposition', 'options'] @@ -22,55 +21,59 @@ WEB_VIEW_EVENTS = 'gpu-crashed': [] 'plugin-crashed': ['name', 'version'] 'destroyed': [] - 'page-title-set': ['title', 'explicitSet'] + 'page-title-updated': ['title', 'explicitSet'] 'page-favicon-updated': ['favicons'] 'enter-html-full-screen': [] 'leave-html-full-screen': [] -dispatchEvent = (webView, event, args...) -> - throw new Error("Unknown event #{event}") unless WEB_VIEW_EVENTS[event]? - domEvent = new Event(event) - for f, i in WEB_VIEW_EVENTS[event] +DEPRECATED_EVENTS = + 'page-title-updated': 'page-title-set' + +dispatchEvent = (webView, eventName, eventKey, args...) -> + if DEPRECATED_EVENTS[eventName]? + dispatchEvent webView, DEPRECATED_EVENTS[eventName], eventKey, args... + domEvent = new Event(eventName) + for f, i in WEB_VIEW_EVENTS[eventKey] domEvent[f] = args[i] webView.dispatchEvent domEvent - webView.onLoadCommit domEvent if event == 'load-commit' + webView.onLoadCommit domEvent if eventName is 'load-commit' module.exports = registerEvents: (webView, viewInstanceId) -> - ipc.on "ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-#{viewInstanceId}", (event, args...) -> - dispatchEvent webView, event, args... + ipcRenderer.on "ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-#{viewInstanceId}", (event, eventName, args...) -> + dispatchEvent webView, eventName, eventName, args... - ipc.on "ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-#{viewInstanceId}", (channel, args...) -> + ipcRenderer.on "ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-#{viewInstanceId}", (event, channel, args...) -> domEvent = new Event('ipc-message') domEvent.channel = channel domEvent.args = [args...] webView.dispatchEvent domEvent - ipc.on "ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-#{viewInstanceId}", (args...) -> + ipcRenderer.on "ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-#{viewInstanceId}", (event, args...) -> domEvent = new Event('size-changed') for f, i in ['oldWidth', 'oldHeight', 'newWidth', 'newHeight'] domEvent[f] = args[i] webView.onSizeChanged domEvent deregisterEvents: (viewInstanceId) -> - ipc.removeAllListeners "ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-#{viewInstanceId}" - ipc.removeAllListeners "ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-#{viewInstanceId}" - ipc.removeAllListeners "ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-#{viewInstanceId}" + ipcRenderer.removeAllListeners "ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-#{viewInstanceId}" + ipcRenderer.removeAllListeners "ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-#{viewInstanceId}" + ipcRenderer.removeAllListeners "ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-#{viewInstanceId}" createGuest: (params, callback) -> requestId++ - ipc.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', params, requestId - ipc.once "ATOM_SHELL_RESPONSE_#{requestId}", callback + ipcRenderer.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', params, requestId + ipcRenderer.once "ATOM_SHELL_RESPONSE_#{requestId}", callback attachGuest: (elementInstanceId, guestInstanceId, params) -> - ipc.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_ATTACH_GUEST', elementInstanceId, guestInstanceId, params + ipcRenderer.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_ATTACH_GUEST', elementInstanceId, guestInstanceId, params webFrame.attachGuest elementInstanceId destroyGuest: (guestInstanceId) -> - ipc.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', guestInstanceId + ipcRenderer.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', guestInstanceId setSize: (guestInstanceId, params) -> - ipc.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_SIZE', guestInstanceId, params + ipcRenderer.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 + ipcRenderer.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 e980e6c96bfc..7760b400ce17 100644 --- a/atom/renderer/lib/web-view/web-view-attributes.coffee +++ b/atom/renderer/lib/web-view/web-view-attributes.coffee @@ -1,11 +1,12 @@ WebViewImpl = require './web-view' guestViewInternal = require './guest-view-internal' webViewConstants = require './web-view-constants' -remote = require 'remote' + +{remote} = require 'electron' # Helper function to resolve url set in attribute. a = document.createElement 'a' -resolveUrl = (url) -> +resolveURL = (url) -> a.href = url a.href @@ -115,7 +116,7 @@ class SrcAttribute extends WebViewAttribute getValue: -> if @webViewImpl.webviewNode.hasAttribute @name - resolveUrl @webViewImpl.webviewNode.getAttribute(@name) + resolveURL @webViewImpl.webviewNode.getAttribute(@name) else '' @@ -177,7 +178,7 @@ class SrcAttribute extends WebViewAttribute if useragent then opts.userAgent = useragent guestContents = remote.getGuestWebContents(@webViewImpl.guestInstanceId) - guestContents.loadUrl @getValue(), opts + guestContents.loadURL @getValue(), opts # Attribute specifies HTTP referrer. class HttpReferrerAttribute extends WebViewAttribute @@ -196,7 +197,7 @@ class PreloadAttribute extends WebViewAttribute getValue: -> return '' unless @webViewImpl.webviewNode.hasAttribute @name - preload = resolveUrl @webViewImpl.webviewNode.getAttribute(@name) + preload = resolveURL @webViewImpl.webviewNode.getAttribute(@name) protocol = preload.substr 0, 5 unless protocol is 'file:' console.error webViewConstants.ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE diff --git a/atom/renderer/lib/web-view/web-view.coffee b/atom/renderer/lib/web-view/web-view.coffee index 65e4501975fe..da36886f6ffa 100644 --- a/atom/renderer/lib/web-view/web-view.coffee +++ b/atom/renderer/lib/web-view/web-view.coffee @@ -1,8 +1,8 @@ +{deprecate, webFrame, remote, ipcRenderer} = require 'electron' v8Util = process.atomBinding 'v8_util' + guestViewInternal = require './guest-view-internal' webViewConstants = require './web-view-constants' -webFrame = require 'web-frame' -remote = require 'remote' # ID generator. nextId = 0 @@ -46,6 +46,7 @@ class WebViewImpl # that we don't end up allocating a second guest. if @guestInstanceId guestViewInternal.destroyGuest @guestInstanceId + @webContents = null @guestInstanceId = undefined @beforeFirstNavigation = true @attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId = true @@ -134,7 +135,7 @@ class WebViewImpl guestViewInternal.setSize @guestInstanceId, normal: newSize createGuest: -> - guestViewInternal.createGuest @buildParams(), (guestInstanceId) => + guestViewInternal.createGuest @buildParams(), (event, guestInstanceId) => @attachWindow guestInstanceId dispatchEvent: (webViewEvent) -> @@ -188,6 +189,7 @@ class WebViewImpl attachWindow: (guestInstanceId) -> @guestInstanceId = guestInstanceId + @webContents = remote.getGuestWebContents @guestInstanceId return true unless @internalInstanceId guestViewInternal.attachGuest @internalInstanceId, @guestInstanceId, @buildParams() @@ -250,57 +252,71 @@ registerWebViewElement = -> # Public-facing API methods. methods = [ - "getUrl" - "getTitle" - "isLoading" - "isWaitingForResponse" - "stop" - "reload" - "reloadIgnoringCache" - "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" - "replace" - "replaceMisspelling" - "send" - "getId" - "inspectServiceWorker" - "print" - "printToPDF" - "sendInputEvent" + 'getURL' + 'getTitle' + 'isLoading' + 'isWaitingForResponse' + 'stop' + 'reload' + 'reloadIgnoringCache' + 'canGoBack' + 'canGoForward' + 'canGoToOffset' + 'clearHistory' + 'goBack' + 'goForward' + 'goToIndex' + 'goToOffset' + 'isCrashed' + 'setUserAgent' + 'getUserAgent' + 'openDevTools' + 'closeDevTools' + 'isDevToolsOpened' + 'inspectElement' + 'setAudioMuted' + 'isAudioMuted' + 'undo' + 'redo' + 'cut' + 'copy' + 'paste' + 'pasteAndMatchStyle' + 'delete' + 'selectAll' + 'unselect' + 'replace' + 'replaceMisspelling' + 'getId' + 'downloadURL' + 'inspectServiceWorker' + 'print' + 'printToPDF' + ] + + nonblockMethods = [ + 'send', + 'sendInputEvent', + 'executeJavaScript', + 'insertCSS' ] # Forward proto.foo* method calls to WebViewImpl.foo*. - createHandler = (m) -> + createBlockHandler = (m) -> (args...) -> internal = v8Util.getHiddenValue this, 'internal' - remote.getGuestWebContents(internal.guestInstanceId)[m] args... - proto[m] = createHandler m for m in methods + internal.webContents[m] args... + proto[m] = createBlockHandler m for m in methods + + createNonBlockHandler = (m) -> + (args...) -> + internal = v8Util.getHiddenValue this, 'internal' + ipcRenderer.send('ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', internal.guestInstanceId, m, args...) + + proto[m] = createNonBlockHandler m for m in nonblockMethods + + # Deprecated. + deprecate.rename proto, 'getUrl', 'getURL' window.WebView = webFrame.registerEmbedderCustomElement 'webview', prototype: proto diff --git a/chromium_src/chrome/browser/chrome_process_finder_win.cc b/chromium_src/chrome/browser/chrome_process_finder_win.cc new file mode 100644 index 000000000000..5a662258a0be --- /dev/null +++ b/chromium_src/chrome/browser/chrome_process_finder_win.cc @@ -0,0 +1,86 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chrome_process_finder_win.h" + +#include +#include + +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/process/process.h" +#include "base/process/process_info.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/win/message_window.h" +#include "base/win/scoped_handle.h" +#include "base/win/win_util.h" +#include "base/win/windows_version.h" + + +namespace { + +int timeout_in_milliseconds = 20 * 1000; + +} // namespace + +namespace chrome { + +HWND FindRunningChromeWindow(const base::FilePath& user_data_dir) { + return base::win::MessageWindow::FindWindow(user_data_dir.value()); +} + +NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window, + bool fast_start) { + DCHECK(remote_window); + DWORD process_id = 0; + DWORD thread_id = GetWindowThreadProcessId(remote_window, &process_id); + if (!thread_id || !process_id) + return NOTIFY_FAILED; + + // Send the command line to the remote chrome window. + // Format is "START\0<<>>\0<<>>". + std::wstring to_send(L"START\0", 6); // want the NULL in the string. + base::FilePath cur_dir; + if (!base::GetCurrentDirectory(&cur_dir)) + return NOTIFY_FAILED; + to_send.append(cur_dir.value()); + to_send.append(L"\0", 1); // Null separator. + to_send.append(::GetCommandLineW()); + to_send.append(L"\0", 1); // Null separator. + + // Allow the current running browser window to make itself the foreground + // window (otherwise it will just flash in the taskbar). + ::AllowSetForegroundWindow(process_id); + + COPYDATASTRUCT cds; + cds.dwData = 0; + cds.cbData = static_cast((to_send.length() + 1) * sizeof(wchar_t)); + cds.lpData = const_cast(to_send.c_str()); + DWORD_PTR result = 0; + if (::SendMessageTimeout(remote_window, WM_COPYDATA, NULL, + reinterpret_cast(&cds), SMTO_ABORTIFHUNG, + timeout_in_milliseconds, &result)) { + return result ? NOTIFY_SUCCESS : NOTIFY_FAILED; + } + + // It is possible that the process owning this window may have died by now. + if (!::IsWindow(remote_window)) + return NOTIFY_FAILED; + + // If the window couldn't be notified but still exists, assume it is hung. + return NOTIFY_WINDOW_HUNG; +} + +base::TimeDelta SetNotificationTimeoutForTesting(base::TimeDelta new_timeout) { + base::TimeDelta old_timeout = + base::TimeDelta::FromMilliseconds(timeout_in_milliseconds); + timeout_in_milliseconds = new_timeout.InMilliseconds(); + return old_timeout; +} + +} // namespace chrome diff --git a/chromium_src/chrome/browser/chrome_process_finder_win.h b/chromium_src/chrome/browser/chrome_process_finder_win.h new file mode 100644 index 000000000000..a66429de5e74 --- /dev/null +++ b/chromium_src/chrome/browser/chrome_process_finder_win.h @@ -0,0 +1,39 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROME_PROCESS_FINDER_WIN_H_ +#define CHROME_BROWSER_CHROME_PROCESS_FINDER_WIN_H_ + +#include + +#include "base/time/time.h" + +namespace base { +class FilePath; +} + +namespace chrome { + +enum NotifyChromeResult { + NOTIFY_SUCCESS, + NOTIFY_FAILED, + NOTIFY_WINDOW_HUNG, +}; + +// Finds an already running Chrome window if it exists. +HWND FindRunningChromeWindow(const base::FilePath& user_data_dir); + +// Attempts to send the current command line to an already running instance of +// Chrome via a WM_COPYDATA message. +// Returns true if a running Chrome is found and successfully notified. +// |fast_start| is true when this is being called on the window fast start path. +NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window, + bool fast_start); + +// Changes the notification timeout to |new_timeout|, returns the old timeout. +base::TimeDelta SetNotificationTimeoutForTesting(base::TimeDelta new_timeout); + +} // namespace chrome + +#endif // CHROME_BROWSER_CHROME_PROCESS_FINDER_WIN_H_ diff --git a/chromium_src/chrome/browser/printing/print_view_manager_base.cc b/chromium_src/chrome/browser/printing/print_view_manager_base.cc index 35c62fbc5327..ede1d3b8ba8c 100644 --- a/chromium_src/chrome/browser/printing/print_view_manager_base.cc +++ b/chromium_src/chrome/browser/printing/print_view_manager_base.cc @@ -410,7 +410,7 @@ bool PrintViewManagerBase::RunInnerMessageLoop() { // be CPU bound, the page overly complex/large or the system just // memory-bound. static const int kPrinterSettingsTimeout = 60000; - base::OneShotTimer quit_timer; + base::OneShotTimer quit_timer; quit_timer.Start(FROM_HERE, TimeDelta::FromMilliseconds(kPrinterSettingsTimeout), base::MessageLoop::current(), &base::MessageLoop::Quit); diff --git a/chromium_src/chrome/browser/process_singleton.h b/chromium_src/chrome/browser/process_singleton.h new file mode 100644 index 000000000000..3eeb53393e12 --- /dev/null +++ b/chromium_src/chrome/browser/process_singleton.h @@ -0,0 +1,182 @@ +// 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_PROCESS_SINGLETON_H_ +#define CHROME_BROWSER_PROCESS_SINGLETON_H_ + +#if defined(OS_WIN) +#include +#endif // defined(OS_WIN) + +#include +#include + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/process/process.h" +#include "base/threading/non_thread_safe.h" +#include "ui/gfx/native_widget_types.h" + +#if defined(OS_POSIX) && !defined(OS_ANDROID) +#include "base/files/scoped_temp_dir.h" +#endif + +#if defined(OS_WIN) +#include "base/win/message_window.h" +#endif // defined(OS_WIN) + +namespace base { +class CommandLine; +} + +// ProcessSingleton ---------------------------------------------------------- +// +// This class allows different browser processes to communicate with +// each other. It is named according to the user data directory, so +// we can be sure that no more than one copy of the application can be +// running at once with a given data directory. +// +// Implementation notes: +// - the Windows implementation uses an invisible global message window; +// - the Linux implementation uses a Unix domain socket in the user data dir. + +class ProcessSingleton : public base::NonThreadSafe { + public: + enum NotifyResult { + PROCESS_NONE, + PROCESS_NOTIFIED, + PROFILE_IN_USE, + LOCK_ERROR, + }; + + // Implement this callback to handle notifications from other processes. The + // callback will receive the command line and directory with which the other + // Chrome process was launched. Return true if the command line will be + // handled within the current browser instance or false if the remote process + // should handle it (i.e., because the current process is shutting down). + using NotificationCallback = + base::Callback; + + ProcessSingleton(const base::FilePath& user_data_dir, + const NotificationCallback& notification_callback); + ~ProcessSingleton(); + + // Notify another process, if available. Otherwise sets ourselves as the + // singleton instance. Returns PROCESS_NONE if we became the singleton + // instance. Callers are guaranteed to either have notified an existing + // process or have grabbed the singleton (unless the profile is locked by an + // unreachable process). + // TODO(brettw): Make the implementation of this method non-platform-specific + // by making Linux re-use the Windows implementation. + NotifyResult NotifyOtherProcessOrCreate(); + + // Sets ourself up as the singleton instance. Returns true on success. If + // false is returned, we are not the singleton instance and the caller must + // exit. + // NOTE: Most callers should generally prefer NotifyOtherProcessOrCreate() to + // this method, only callers for whom failure is preferred to notifying + // another process should call this directly. + bool Create(); + + // Clear any lock state during shutdown. + void Cleanup(); + +#if defined(OS_POSIX) && !defined(OS_ANDROID) + static void DisablePromptForTesting(); +#endif +#if defined(OS_WIN) + // Called to query whether to kill a hung browser process that has visible + // windows. Return true to allow killing the hung process. + using ShouldKillRemoteProcessCallback = base::Callback; + void OverrideShouldKillRemoteProcessCallbackForTesting( + const ShouldKillRemoteProcessCallback& display_dialog_callback); +#endif + + protected: + // Notify another process, if available. + // Returns true if another process was found and notified, false if we should + // continue with the current process. + // On Windows, Create() has to be called before this. + NotifyResult NotifyOtherProcess(); + +#if defined(OS_POSIX) && !defined(OS_ANDROID) + // Exposed for testing. We use a timeout on Linux, and in tests we want + // this timeout to be short. + NotifyResult NotifyOtherProcessWithTimeout( + const base::CommandLine& command_line, + int retry_attempts, + const base::TimeDelta& timeout, + bool kill_unresponsive); + NotifyResult NotifyOtherProcessWithTimeoutOrCreate( + const base::CommandLine& command_line, + int retry_attempts, + const base::TimeDelta& timeout); + void OverrideCurrentPidForTesting(base::ProcessId pid); + void OverrideKillCallbackForTesting( + const base::Callback& callback); +#endif + + private: + NotificationCallback notification_callback_; // Handler for notifications. + +#if defined(OS_WIN) + HWND remote_window_; // The HWND_MESSAGE of another browser. + base::win::MessageWindow window_; // The message-only window. + bool is_virtualized_; // Stuck inside Microsoft Softricity VM environment. + HANDLE lock_file_; + base::FilePath user_data_dir_; + ShouldKillRemoteProcessCallback should_kill_remote_process_callback_; +#elif defined(OS_POSIX) && !defined(OS_ANDROID) + // Start listening to the socket. + void StartListening(int sock); + + // Return true if the given pid is one of our child processes. + // Assumes that the current pid is the root of all pids of the current + // instance. + bool IsSameChromeInstance(pid_t pid); + + // Extract the process's pid from a symbol link path and if it is on + // the same host, kill the process, unlink the lock file and return true. + // If the process is part of the same chrome instance, unlink the lock file + // and return true without killing it. + // If the process is on a different host, return false. + bool KillProcessByLockPath(); + + // Default function to kill a process, overridable by tests. + void KillProcess(int pid); + + // Allow overriding for tests. + base::ProcessId current_pid_; + + // Function to call when the other process is hung and needs to be killed. + // Allows overriding for tests. + base::Callback kill_callback_; + + // Path in file system to the socket. + base::FilePath socket_path_; + + // Path in file system to the lock. + base::FilePath lock_path_; + + // Path in file system to the cookie file. + base::FilePath cookie_path_; + + // Temporary directory to hold the socket. + base::ScopedTempDir socket_dir_; + + // Helper class for linux specific messages. LinuxWatcher is ref counted + // because it posts messages between threads. + class LinuxWatcher; + scoped_refptr watcher_; +#endif + + DISALLOW_COPY_AND_ASSIGN(ProcessSingleton); +}; + +#endif // CHROME_BROWSER_PROCESS_SINGLETON_H_ diff --git a/chromium_src/chrome/browser/process_singleton_posix.cc b/chromium_src/chrome/browser/process_singleton_posix.cc new file mode 100644 index 000000000000..98fb948730e0 --- /dev/null +++ b/chromium_src/chrome/browser/process_singleton_posix.cc @@ -0,0 +1,1061 @@ +// 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. + +// On Linux, when the user tries to launch a second copy of chrome, we check +// for a socket in the user's profile directory. If the socket file is open we +// send a message to the first chrome browser process with the current +// directory and second process command line flags. The second process then +// exits. +// +// Because many networked filesystem implementations do not support unix domain +// sockets, we create the socket in a temporary directory and create a symlink +// in the profile. This temporary directory is no longer bound to the profile, +// and may disappear across a reboot or login to a separate session. To bind +// them, we store a unique cookie in the profile directory, which must also be +// present in the remote directory to connect. The cookie is checked both before +// and after the connection. /tmp is sticky, and different Chrome sessions use +// different cookies. Thus, a matching cookie before and after means the +// connection was to a directory with a valid cookie. +// +// We also have a lock file, which is a symlink to a non-existent destination. +// The destination is a string containing the hostname and process id of +// chrome's browser process, eg. "SingletonLock -> example.com-9156". When the +// first copy of chrome exits it will delete the lock file on shutdown, so that +// a different instance on a different host may then use the profile directory. +// +// If writing to the socket fails, the hostname in the lock is checked to see if +// another instance is running a different host using a shared filesystem (nfs, +// etc.) If the hostname differs an error is displayed and the second process +// exits. Otherwise the first process (if any) is killed and the second process +// starts as normal. +// +// When the second process sends the current directory and command line flags to +// the first process, it waits for an ACK message back from the first process +// for a certain time. If there is no ACK message back in time, then the first +// process will be considered as hung for some reason. The second process then +// retrieves the process id from the symbol link and kills it by sending +// SIGKILL. Then the second process starts as normal. + +#include "chrome/browser/process_singleton.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "atom/common/atom_command_line.h" +#include "base/base_paths.h" +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/path_service.h" +#include "base/posix/eintr_wrapper.h" +#include "base/posix/safe_strerror.h" +#include "base/rand_util.h" +#include "base/sequenced_task_runner_helpers.h" +#include "base/single_thread_task_runner.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/platform_thread.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "content/public/browser/browser_thread.h" +#include "net/base/net_util.h" +#include "ui/base/l10n/l10n_util.h" + +#if defined(TOOLKIT_VIEWS) && defined(OS_LINUX) && !defined(OS_CHROMEOS) +#include "ui/views/linux_ui/linux_ui.h" +#endif + +using content::BrowserThread; + +namespace { + +// Timeout for the current browser process to respond. 20 seconds should be +// enough. +const int kTimeoutInSeconds = 20; +// Number of retries to notify the browser. 20 retries over 20 seconds = 1 try +// per second. +const int kRetryAttempts = 20; +static bool g_disable_prompt; +const char kStartToken[] = "START"; +const char kACKToken[] = "ACK"; +const char kShutdownToken[] = "SHUTDOWN"; +const char kTokenDelimiter = '\0'; +const int kMaxMessageLength = 32 * 1024; +const int kMaxACKMessageLength = arraysize(kShutdownToken) - 1; + +const char kLockDelimiter = '-'; + +const base::FilePath::CharType kSingletonCookieFilename[] = + FILE_PATH_LITERAL("SingletonCookie"); + +const base::FilePath::CharType kSingletonLockFilename[] = FILE_PATH_LITERAL("SingletonLock"); +const base::FilePath::CharType kSingletonSocketFilename[] = + FILE_PATH_LITERAL("SingletonSocket"); + +// Set the close-on-exec bit on a file descriptor. +// Returns 0 on success, -1 on failure. +int SetCloseOnExec(int fd) { + int flags = fcntl(fd, F_GETFD, 0); + if (-1 == flags) + return flags; + if (flags & FD_CLOEXEC) + return 0; + return fcntl(fd, F_SETFD, flags | FD_CLOEXEC); +} + +// Close a socket and check return value. +void CloseSocket(int fd) { + int rv = IGNORE_EINTR(close(fd)); + DCHECK_EQ(0, rv) << "Error closing socket: " << base::safe_strerror(errno); +} + +// Write a message to a socket fd. +bool WriteToSocket(int fd, const char *message, size_t length) { + DCHECK(message); + DCHECK(length); + size_t bytes_written = 0; + do { + ssize_t rv = HANDLE_EINTR( + write(fd, message + bytes_written, length - bytes_written)); + if (rv < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // The socket shouldn't block, we're sending so little data. Just give + // up here, since NotifyOtherProcess() doesn't have an asynchronous api. + LOG(ERROR) << "ProcessSingleton would block on write(), so it gave up."; + return false; + } + PLOG(ERROR) << "write() failed"; + return false; + } + bytes_written += rv; + } while (bytes_written < length); + + return true; +} + +struct timeval TimeDeltaToTimeVal(const base::TimeDelta& delta) { + struct timeval result; + result.tv_sec = delta.InSeconds(); + result.tv_usec = delta.InMicroseconds() % base::Time::kMicrosecondsPerSecond; + return result; +} + +// Wait a socket for read for a certain timeout. +// Returns -1 if error occurred, 0 if timeout reached, > 0 if the socket is +// ready for read. +int WaitSocketForRead(int fd, const base::TimeDelta& timeout) { + fd_set read_fds; + struct timeval tv = TimeDeltaToTimeVal(timeout); + + FD_ZERO(&read_fds); + FD_SET(fd, &read_fds); + + return HANDLE_EINTR(select(fd + 1, &read_fds, NULL, NULL, &tv)); +} + +// Read a message from a socket fd, with an optional timeout. +// If |timeout| <= 0 then read immediately. +// Return number of bytes actually read, or -1 on error. +ssize_t ReadFromSocket(int fd, + char* buf, + size_t bufsize, + const base::TimeDelta& timeout) { + if (timeout > base::TimeDelta()) { + int rv = WaitSocketForRead(fd, timeout); + if (rv <= 0) + return rv; + } + + size_t bytes_read = 0; + do { + ssize_t rv = HANDLE_EINTR(read(fd, buf + bytes_read, bufsize - bytes_read)); + if (rv < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + PLOG(ERROR) << "read() failed"; + return rv; + } else { + // It would block, so we just return what has been read. + return bytes_read; + } + } else if (!rv) { + // No more data to read. + return bytes_read; + } else { + bytes_read += rv; + } + } while (bytes_read < bufsize); + + return bytes_read; +} + +// Set up a sockaddr appropriate for messaging. +void SetupSockAddr(const std::string& path, struct sockaddr_un* addr) { + addr->sun_family = AF_UNIX; + CHECK(path.length() < arraysize(addr->sun_path)) + << "Socket path too long: " << path; + base::strlcpy(addr->sun_path, path.c_str(), arraysize(addr->sun_path)); +} + +// Set up a socket appropriate for messaging. +int SetupSocketOnly() { + int sock = socket(PF_UNIX, SOCK_STREAM, 0); + PCHECK(sock >= 0) << "socket() failed"; + + int rv = net::SetNonBlocking(sock); + DCHECK_EQ(0, rv) << "Failed to make non-blocking socket."; + rv = SetCloseOnExec(sock); + DCHECK_EQ(0, rv) << "Failed to set CLOEXEC on socket."; + + return sock; +} + +// Set up a socket and sockaddr appropriate for messaging. +void SetupSocket(const std::string& path, int* sock, struct sockaddr_un* addr) { + *sock = SetupSocketOnly(); + SetupSockAddr(path, addr); +} + +// Read a symbolic link, return empty string if given path is not a symbol link. +base::FilePath ReadLink(const base::FilePath& path) { + base::FilePath target; + if (!base::ReadSymbolicLink(path, &target)) { + // The only errno that should occur is ENOENT. + if (errno != 0 && errno != ENOENT) + PLOG(ERROR) << "readlink(" << path.value() << ") failed"; + } + return target; +} + +// Unlink a path. Return true on success. +bool UnlinkPath(const base::FilePath& path) { + int rv = unlink(path.value().c_str()); + if (rv < 0 && errno != ENOENT) + PLOG(ERROR) << "Failed to unlink " << path.value(); + + return rv == 0; +} + +// Create a symlink. Returns true on success. +bool SymlinkPath(const base::FilePath& target, const base::FilePath& path) { + if (!base::CreateSymbolicLink(target, path)) { + // Double check the value in case symlink suceeded but we got an incorrect + // failure due to NFS packet loss & retry. + int saved_errno = errno; + if (ReadLink(path) != target) { + // If we failed to create the lock, most likely another instance won the + // startup race. + errno = saved_errno; + PLOG(ERROR) << "Failed to create " << path.value(); + return false; + } + } + return true; +} + +// Extract the hostname and pid from the lock symlink. +// Returns true if the lock existed. +bool ParseLockPath(const base::FilePath& path, + std::string* hostname, + int* pid) { + std::string real_path = ReadLink(path).value(); + if (real_path.empty()) + return false; + + std::string::size_type pos = real_path.rfind(kLockDelimiter); + + // If the path is not a symbolic link, or doesn't contain what we expect, + // bail. + if (pos == std::string::npos) { + *hostname = ""; + *pid = -1; + return true; + } + + *hostname = real_path.substr(0, pos); + + const std::string& pid_str = real_path.substr(pos + 1); + if (!base::StringToInt(pid_str, pid)) + *pid = -1; + + return true; +} + +// Returns true if the user opted to unlock the profile. +bool DisplayProfileInUseError(const base::FilePath& lock_path, + const std::string& hostname, + int pid) { + // TODO: yolo + return true; +} + +bool IsChromeProcess(pid_t pid) { + base::FilePath other_chrome_path(base::GetProcessExecutablePath(pid)); + + auto command_line = base::CommandLine::ForCurrentProcess(); + base::FilePath exec_path(command_line->GetProgram()); + PathService::Get(base::FILE_EXE, &exec_path); + + return (!other_chrome_path.empty() && + other_chrome_path.BaseName() == exec_path.BaseName()); +} + +// A helper class to hold onto a socket. +class ScopedSocket { + public: + ScopedSocket() : fd_(-1) { Reset(); } + ~ScopedSocket() { Close(); } + int fd() { return fd_; } + void Reset() { + Close(); + fd_ = SetupSocketOnly(); + } + void Close() { + if (fd_ >= 0) + CloseSocket(fd_); + fd_ = -1; + } + private: + int fd_; +}; + +// Returns a random string for uniquifying profile connections. +std::string GenerateCookie() { + return base::Uint64ToString(base::RandUint64()); +} + +bool CheckCookie(const base::FilePath& path, const base::FilePath& cookie) { + return (cookie == ReadLink(path)); +} + +bool ConnectSocket(ScopedSocket* socket, + const base::FilePath& socket_path, + const base::FilePath& cookie_path) { + base::FilePath socket_target; + if (base::ReadSymbolicLink(socket_path, &socket_target)) { + // It's a symlink. Read the cookie. + base::FilePath cookie = ReadLink(cookie_path); + if (cookie.empty()) + return false; + base::FilePath remote_cookie = socket_target.DirName(). + Append(kSingletonCookieFilename); + // Verify the cookie before connecting. + if (!CheckCookie(remote_cookie, cookie)) + return false; + // Now we know the directory was (at that point) created by the profile + // owner. Try to connect. + sockaddr_un addr; + SetupSockAddr(socket_target.value(), &addr); + int ret = HANDLE_EINTR(connect(socket->fd(), + reinterpret_cast(&addr), + sizeof(addr))); + if (ret != 0) + return false; + // Check the cookie again. We only link in /tmp, which is sticky, so, if the + // directory is still correct, it must have been correct in-between when we + // connected. POSIX, sadly, lacks a connectat(). + if (!CheckCookie(remote_cookie, cookie)) { + socket->Reset(); + return false; + } + // Success! + return true; + } else if (errno == EINVAL) { + // It exists, but is not a symlink (or some other error we detect + // later). Just connect to it directly; this is an older version of Chrome. + sockaddr_un addr; + SetupSockAddr(socket_path.value(), &addr); + int ret = HANDLE_EINTR(connect(socket->fd(), + reinterpret_cast(&addr), + sizeof(addr))); + return (ret == 0); + } else { + // File is missing, or other error. + if (errno != ENOENT) + PLOG(ERROR) << "readlink failed"; + return false; + } +} + +#if defined(OS_MACOSX) +bool ReplaceOldSingletonLock(const base::FilePath& symlink_content, + const base::FilePath& lock_path) { + // Try taking an flock(2) on the file. Failure means the lock is taken so we + // should quit. + base::ScopedFD lock_fd(HANDLE_EINTR( + open(lock_path.value().c_str(), O_RDWR | O_CREAT | O_SYMLINK, 0644))); + if (!lock_fd.is_valid()) { + PLOG(ERROR) << "Could not open singleton lock"; + return false; + } + + int rc = HANDLE_EINTR(flock(lock_fd.get(), LOCK_EX | LOCK_NB)); + if (rc == -1) { + if (errno == EWOULDBLOCK) { + LOG(ERROR) << "Singleton lock held by old process."; + } else { + PLOG(ERROR) << "Error locking singleton lock"; + } + return false; + } + + // Successfully taking the lock means we can replace it with the a new symlink + // lock. We never flock() the lock file from now on. I.e. we assume that an + // old version of Chrome will not run with the same user data dir after this + // version has run. + if (!base::DeleteFile(lock_path, false)) { + PLOG(ERROR) << "Could not delete old singleton lock."; + return false; + } + + return SymlinkPath(symlink_content, lock_path); +} +#endif // defined(OS_MACOSX) + +} // namespace + +/////////////////////////////////////////////////////////////////////////////// +// ProcessSingleton::LinuxWatcher +// A helper class for a Linux specific implementation of the process singleton. +// This class sets up a listener on the singleton socket and handles parsing +// messages that come in on the singleton socket. +class ProcessSingleton::LinuxWatcher + : public base::MessageLoopForIO::Watcher, + public base::MessageLoop::DestructionObserver, + public base::RefCountedThreadSafe { + public: + // A helper class to read message from an established socket. + class SocketReader : public base::MessageLoopForIO::Watcher { + public: + SocketReader(ProcessSingleton::LinuxWatcher* parent, + base::MessageLoop* ui_message_loop, + int fd) + : parent_(parent), + ui_message_loop_(ui_message_loop), + fd_(fd), + bytes_read_(0) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + // Wait for reads. + base::MessageLoopForIO::current()->WatchFileDescriptor( + fd, true, base::MessageLoopForIO::WATCH_READ, &fd_reader_, this); + // If we haven't completed in a reasonable amount of time, give up. + timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(kTimeoutInSeconds), + this, &SocketReader::CleanupAndDeleteSelf); + } + + ~SocketReader() override { CloseSocket(fd_); } + + // MessageLoopForIO::Watcher impl. + void OnFileCanReadWithoutBlocking(int fd) override; + void OnFileCanWriteWithoutBlocking(int fd) override { + // SocketReader only watches for accept (read) events. + NOTREACHED(); + } + + // Finish handling the incoming message by optionally sending back an ACK + // message and removing this SocketReader. + void FinishWithACK(const char *message, size_t length); + + private: + void CleanupAndDeleteSelf() { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + parent_->RemoveSocketReader(this); + // We're deleted beyond this point. + } + + base::MessageLoopForIO::FileDescriptorWatcher fd_reader_; + + // The ProcessSingleton::LinuxWatcher that owns us. + ProcessSingleton::LinuxWatcher* const parent_; + + // A reference to the UI message loop. + base::MessageLoop* const ui_message_loop_; + + // The file descriptor we're reading. + const int fd_; + + // Store the message in this buffer. + char buf_[kMaxMessageLength]; + + // Tracks the number of bytes we've read in case we're getting partial + // reads. + size_t bytes_read_; + + base::OneShotTimer timer_; + + DISALLOW_COPY_AND_ASSIGN(SocketReader); + }; + + // We expect to only be constructed on the UI thread. + explicit LinuxWatcher(ProcessSingleton* parent) + : ui_message_loop_(base::MessageLoop::current()), + parent_(parent) { + } + + // Start listening for connections on the socket. This method should be + // called from the IO thread. + void StartListening(int socket); + + // This method determines if we should use the same process and if we should, + // opens a new browser tab. This runs on the UI thread. + // |reader| is for sending back ACK message. + void HandleMessage(const std::string& current_dir, + const std::vector& argv, + SocketReader* reader); + + // MessageLoopForIO::Watcher impl. These run on the IO thread. + void OnFileCanReadWithoutBlocking(int fd) override; + void OnFileCanWriteWithoutBlocking(int fd) override { + // ProcessSingleton only watches for accept (read) events. + NOTREACHED(); + } + + // MessageLoop::DestructionObserver + void WillDestroyCurrentMessageLoop() override { + fd_watcher_.StopWatchingFileDescriptor(); + } + + private: + friend struct BrowserThread::DeleteOnThread; + friend class base::DeleteHelper; + + ~LinuxWatcher() override { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + STLDeleteElements(&readers_); + + base::MessageLoopForIO* ml = base::MessageLoopForIO::current(); + ml->RemoveDestructionObserver(this); + } + + // Removes and deletes the SocketReader. + void RemoveSocketReader(SocketReader* reader); + + base::MessageLoopForIO::FileDescriptorWatcher fd_watcher_; + + // A reference to the UI message loop (i.e., the message loop we were + // constructed on). + base::MessageLoop* ui_message_loop_; + + // The ProcessSingleton that owns us. + ProcessSingleton* const parent_; + + std::set readers_; + + DISALLOW_COPY_AND_ASSIGN(LinuxWatcher); +}; + +void ProcessSingleton::LinuxWatcher::OnFileCanReadWithoutBlocking(int fd) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + // Accepting incoming client. + sockaddr_un from; + socklen_t from_len = sizeof(from); + int connection_socket = HANDLE_EINTR(accept( + fd, reinterpret_cast(&from), &from_len)); + if (-1 == connection_socket) { + PLOG(ERROR) << "accept() failed"; + return; + } + int rv = net::SetNonBlocking(connection_socket); + DCHECK_EQ(0, rv) << "Failed to make non-blocking socket."; + SocketReader* reader = new SocketReader(this, + ui_message_loop_, + connection_socket); + readers_.insert(reader); +} + +void ProcessSingleton::LinuxWatcher::StartListening(int socket) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + // Watch for client connections on this socket. + base::MessageLoopForIO* ml = base::MessageLoopForIO::current(); + ml->AddDestructionObserver(this); + ml->WatchFileDescriptor(socket, true, base::MessageLoopForIO::WATCH_READ, + &fd_watcher_, this); +} + +void ProcessSingleton::LinuxWatcher::HandleMessage( + const std::string& current_dir, const std::vector& argv, + SocketReader* reader) { + DCHECK(ui_message_loop_ == base::MessageLoop::current()); + DCHECK(reader); + + if (parent_->notification_callback_.Run(argv, + base::FilePath(current_dir))) { + // Send back "ACK" message to prevent the client process from starting up. + reader->FinishWithACK(kACKToken, arraysize(kACKToken) - 1); + } else { + LOG(WARNING) << "Not handling interprocess notification as browser" + " is shutting down"; + // Send back "SHUTDOWN" message, so that the client process can start up + // without killing this process. + reader->FinishWithACK(kShutdownToken, arraysize(kShutdownToken) - 1); + return; + } +} + +void ProcessSingleton::LinuxWatcher::RemoveSocketReader(SocketReader* reader) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + DCHECK(reader); + readers_.erase(reader); + delete reader; +} + +/////////////////////////////////////////////////////////////////////////////// +// ProcessSingleton::LinuxWatcher::SocketReader +// + +void ProcessSingleton::LinuxWatcher::SocketReader::OnFileCanReadWithoutBlocking( + int fd) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + DCHECK_EQ(fd, fd_); + while (bytes_read_ < sizeof(buf_)) { + ssize_t rv = HANDLE_EINTR( + read(fd, buf_ + bytes_read_, sizeof(buf_) - bytes_read_)); + if (rv < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + PLOG(ERROR) << "read() failed"; + CloseSocket(fd); + return; + } else { + // It would block, so we just return and continue to watch for the next + // opportunity to read. + return; + } + } else if (!rv) { + // No more data to read. It's time to process the message. + break; + } else { + bytes_read_ += rv; + } + } + + // Validate the message. The shortest message is kStartToken\0x\0x + const size_t kMinMessageLength = arraysize(kStartToken) + 4; + if (bytes_read_ < kMinMessageLength) { + buf_[bytes_read_] = 0; + LOG(ERROR) << "Invalid socket message (wrong length):" << buf_; + CleanupAndDeleteSelf(); + return; + } + + std::string str(buf_, bytes_read_); + std::vector tokens = base::SplitString( + str, std::string(1, kTokenDelimiter), + base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + + if (tokens.size() < 3 || tokens[0] != kStartToken) { + LOG(ERROR) << "Wrong message format: " << str; + CleanupAndDeleteSelf(); + return; + } + + // Stop the expiration timer to prevent this SocketReader object from being + // terminated unexpectly. + timer_.Stop(); + + std::string current_dir = tokens[1]; + // Remove the first two tokens. The remaining tokens should be the command + // line argv array. + tokens.erase(tokens.begin()); + tokens.erase(tokens.begin()); + + // Return to the UI thread to handle opening a new browser tab. + ui_message_loop_->task_runner()->PostTask( + FROM_HERE, base::Bind(&ProcessSingleton::LinuxWatcher::HandleMessage, + parent_, current_dir, tokens, this)); + fd_reader_.StopWatchingFileDescriptor(); + + // LinuxWatcher::HandleMessage() is in charge of destroying this SocketReader + // object by invoking SocketReader::FinishWithACK(). +} + +void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK( + const char *message, size_t length) { + if (message && length) { + // Not necessary to care about the return value. + WriteToSocket(fd_, message, length); + } + + if (shutdown(fd_, SHUT_WR) < 0) + PLOG(ERROR) << "shutdown() failed"; + + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(&ProcessSingleton::LinuxWatcher::RemoveSocketReader, + parent_, + this)); + // We will be deleted once the posted RemoveSocketReader task runs. +} + +/////////////////////////////////////////////////////////////////////////////// +// ProcessSingleton +// +ProcessSingleton::ProcessSingleton( + const base::FilePath& user_data_dir, + const NotificationCallback& notification_callback) + : notification_callback_(notification_callback), + current_pid_(base::GetCurrentProcId()) { + socket_path_ = user_data_dir.Append(kSingletonSocketFilename); + lock_path_ = user_data_dir.Append(kSingletonLockFilename); + cookie_path_ = user_data_dir.Append(kSingletonCookieFilename); + + kill_callback_ = base::Bind(&ProcessSingleton::KillProcess, + base::Unretained(this)); +} + +ProcessSingleton::~ProcessSingleton() { +} + +ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() { + return NotifyOtherProcessWithTimeout( + *base::CommandLine::ForCurrentProcess(), kRetryAttempts, + base::TimeDelta::FromSeconds(kTimeoutInSeconds), true); +} + +ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout( + const base::CommandLine& cmd_line, + int retry_attempts, + const base::TimeDelta& timeout, + bool kill_unresponsive) { + DCHECK_GE(retry_attempts, 0); + DCHECK_GE(timeout.InMicroseconds(), 0); + + base::TimeDelta sleep_interval = timeout / retry_attempts; + + ScopedSocket socket; + for (int retries = 0; retries <= retry_attempts; ++retries) { + // Try to connect to the socket. + if (ConnectSocket(&socket, socket_path_, cookie_path_)) + break; + + // If we're in a race with another process, they may be in Create() and have + // created the lock but not attached to the socket. So we check if the + // process with the pid from the lockfile is currently running and is a + // chrome browser. If so, we loop and try again for |timeout|. + + std::string hostname; + int pid; + if (!ParseLockPath(lock_path_, &hostname, &pid)) { + // No lockfile exists. + return PROCESS_NONE; + } + + if (hostname.empty()) { + // Invalid lockfile. + UnlinkPath(lock_path_); + return PROCESS_NONE; + } + + if (hostname != net::GetHostName() && !IsChromeProcess(pid)) { + // Locked by process on another host. If the user selected to unlock + // the profile, try to continue; otherwise quit. + if (DisplayProfileInUseError(lock_path_, hostname, pid)) { + UnlinkPath(lock_path_); + return PROCESS_NONE; + } + return PROFILE_IN_USE; + } + + if (!IsChromeProcess(pid)) { + // Orphaned lockfile (no process with pid, or non-chrome process.) + UnlinkPath(lock_path_); + return PROCESS_NONE; + } + + if (IsSameChromeInstance(pid)) { + // Orphaned lockfile (pid is part of same chrome instance we are, even + // though we haven't tried to create a lockfile yet). + UnlinkPath(lock_path_); + return PROCESS_NONE; + } + + if (retries == retry_attempts) { + // Retries failed. Kill the unresponsive chrome process and continue. + if (!kill_unresponsive || !KillProcessByLockPath()) + return PROFILE_IN_USE; + return PROCESS_NONE; + } + + base::PlatformThread::Sleep(sleep_interval); + } + + timeval socket_timeout = TimeDeltaToTimeVal(timeout); + setsockopt(socket.fd(), + SOL_SOCKET, + SO_SNDTIMEO, + &socket_timeout, + sizeof(socket_timeout)); + + // Found another process, prepare our command line + // format is "START\0\0\0...\0". + std::string to_send(kStartToken); + to_send.push_back(kTokenDelimiter); + + base::FilePath current_dir; + if (!PathService::Get(base::DIR_CURRENT, ¤t_dir)) + return PROCESS_NONE; + to_send.append(current_dir.value()); + + const std::vector& argv = atom::AtomCommandLine::argv(); + for (std::vector::const_iterator it = argv.begin(); + it != argv.end(); ++it) { + to_send.push_back(kTokenDelimiter); + to_send.append(*it); + } + + // Send the message + if (!WriteToSocket(socket.fd(), to_send.data(), to_send.length())) { + // Try to kill the other process, because it might have been dead. + if (!kill_unresponsive || !KillProcessByLockPath()) + return PROFILE_IN_USE; + return PROCESS_NONE; + } + + if (shutdown(socket.fd(), SHUT_WR) < 0) + PLOG(ERROR) << "shutdown() failed"; + + // Read ACK message from the other process. It might be blocked for a certain + // timeout, to make sure the other process has enough time to return ACK. + char buf[kMaxACKMessageLength + 1]; + ssize_t len = ReadFromSocket(socket.fd(), buf, kMaxACKMessageLength, timeout); + + // Failed to read ACK, the other process might have been frozen. + if (len <= 0) { + if (!kill_unresponsive || !KillProcessByLockPath()) + return PROFILE_IN_USE; + return PROCESS_NONE; + } + + buf[len] = '\0'; + if (strncmp(buf, kShutdownToken, arraysize(kShutdownToken) - 1) == 0) { + // The other process is shutting down, it's safe to start a new process. + return PROCESS_NONE; + } else if (strncmp(buf, kACKToken, arraysize(kACKToken) - 1) == 0) { +#if defined(TOOLKIT_VIEWS) && defined(OS_LINUX) && !defined(OS_CHROMEOS) + // Likely NULL in unit tests. + views::LinuxUI* linux_ui = views::LinuxUI::instance(); + if (linux_ui) + linux_ui->NotifyWindowManagerStartupComplete(); +#endif + + // Assume the other process is handling the request. + return PROCESS_NOTIFIED; + } + + NOTREACHED() << "The other process returned unknown message: " << buf; + return PROCESS_NOTIFIED; +} + +ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessOrCreate() { + return NotifyOtherProcessWithTimeoutOrCreate( + *base::CommandLine::ForCurrentProcess(), kRetryAttempts, + base::TimeDelta::FromSeconds(kTimeoutInSeconds)); +} + +ProcessSingleton::NotifyResult +ProcessSingleton::NotifyOtherProcessWithTimeoutOrCreate( + const base::CommandLine& command_line, + int retry_attempts, + const base::TimeDelta& timeout) { + NotifyResult result = NotifyOtherProcessWithTimeout( + command_line, retry_attempts, timeout, true); + if (result != PROCESS_NONE) + return result; + if (Create()) + return PROCESS_NONE; + // If the Create() failed, try again to notify. (It could be that another + // instance was starting at the same time and managed to grab the lock before + // we did.) + // This time, we don't want to kill anything if we aren't successful, since we + // aren't going to try to take over the lock ourselves. + result = NotifyOtherProcessWithTimeout( + command_line, retry_attempts, timeout, false); + if (result != PROCESS_NONE) + return result; + + return LOCK_ERROR; +} + +void ProcessSingleton::OverrideCurrentPidForTesting(base::ProcessId pid) { + current_pid_ = pid; +} + +void ProcessSingleton::OverrideKillCallbackForTesting( + const base::Callback& callback) { + kill_callback_ = callback; +} + +void ProcessSingleton::DisablePromptForTesting() { + g_disable_prompt = true; +} + +bool ProcessSingleton::Create() { + int sock; + sockaddr_un addr; + + // The symlink lock is pointed to the hostname and process id, so other + // processes can find it out. + base::FilePath symlink_content(base::StringPrintf( + "%s%c%u", + net::GetHostName().c_str(), + kLockDelimiter, + current_pid_)); + + // Create symbol link before binding the socket, to ensure only one instance + // can have the socket open. + if (!SymlinkPath(symlink_content, lock_path_)) { + // TODO(jackhou): Remove this case once this code is stable on Mac. + // http://crbug.com/367612 +#if defined(OS_MACOSX) + // On Mac, an existing non-symlink lock file means the lock could be held by + // the old process singleton code. If we can successfully replace the lock, + // continue as normal. + if (base::IsLink(lock_path_) || + !ReplaceOldSingletonLock(symlink_content, lock_path_)) { + return false; + } +#else + // If we failed to create the lock, most likely another instance won the + // startup race. + return false; +#endif + } + + // Create the socket file somewhere in /tmp which is usually mounted as a + // normal filesystem. Some network filesystems (notably AFS) are screwy and + // do not support Unix domain sockets. + if (!socket_dir_.CreateUniqueTempDir()) { + LOG(ERROR) << "Failed to create socket directory."; + return false; + } + + // Check that the directory was created with the correct permissions. + int dir_mode = 0; + CHECK(base::GetPosixFilePermissions(socket_dir_.path(), &dir_mode) && + dir_mode == base::FILE_PERMISSION_USER_MASK) + << "Temp directory mode is not 700: " << std::oct << dir_mode; + + // Setup the socket symlink and the two cookies. + base::FilePath socket_target_path = + socket_dir_.path().Append(kSingletonSocketFilename); + base::FilePath cookie(GenerateCookie()); + base::FilePath remote_cookie_path = + socket_dir_.path().Append(kSingletonCookieFilename); + UnlinkPath(socket_path_); + UnlinkPath(cookie_path_); + if (!SymlinkPath(socket_target_path, socket_path_) || + !SymlinkPath(cookie, cookie_path_) || + !SymlinkPath(cookie, remote_cookie_path)) { + // We've already locked things, so we can't have lost the startup race, + // but something doesn't like us. + LOG(ERROR) << "Failed to create symlinks."; + if (!socket_dir_.Delete()) + LOG(ERROR) << "Encountered a problem when deleting socket directory."; + return false; + } + + SetupSocket(socket_target_path.value(), &sock, &addr); + + if (bind(sock, reinterpret_cast(&addr), sizeof(addr)) < 0) { + PLOG(ERROR) << "Failed to bind() " << socket_target_path.value(); + CloseSocket(sock); + return false; + } + + if (listen(sock, 5) < 0) + NOTREACHED() << "listen failed: " << base::safe_strerror(errno); + + // In Electron the ProcessSingleton is created earlier than the IO + // thread gets created, so we have to postpone the call until message + // loop is up an running. + scoped_refptr task_runner( + base::ThreadTaskRunnerHandle::Get()); + task_runner->PostTask( + FROM_HERE, + base::Bind(&ProcessSingleton::StartListening, + base::Unretained(this), sock)); + + return true; +} + +void ProcessSingleton::Cleanup() { + UnlinkPath(socket_path_); + UnlinkPath(cookie_path_); + UnlinkPath(lock_path_); +} + +void ProcessSingleton::StartListening(int sock) { + watcher_ = new LinuxWatcher(this); + DCHECK(BrowserThread::IsMessageLoopValid(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(&ProcessSingleton::LinuxWatcher::StartListening, + watcher_.get(), + sock)); +} + +bool ProcessSingleton::IsSameChromeInstance(pid_t pid) { + pid_t cur_pid = current_pid_; + while (pid != cur_pid) { + pid = base::GetParentProcessId(pid); + if (pid < 0) + return false; + if (!IsChromeProcess(pid)) + return false; + } + return true; +} + +bool ProcessSingleton::KillProcessByLockPath() { + std::string hostname; + int pid; + ParseLockPath(lock_path_, &hostname, &pid); + + if (!hostname.empty() && hostname != net::GetHostName()) { + return DisplayProfileInUseError(lock_path_, hostname, pid); + } + UnlinkPath(lock_path_); + + if (IsSameChromeInstance(pid)) + return true; + + if (pid > 0) { + kill_callback_.Run(pid); + return true; + } + + LOG(ERROR) << "Failed to extract pid from path: " << lock_path_.value(); + return true; +} + +void ProcessSingleton::KillProcess(int pid) { + // TODO(james.su@gmail.com): Is SIGKILL ok? + int rv = kill(static_cast(pid), SIGKILL); + // ESRCH = No Such Process (can happen if the other process is already in + // progress of shutting down and finishes before we try to kill it). + DCHECK(rv == 0 || errno == ESRCH) << "Error killing process: " + << base::safe_strerror(errno); +} diff --git a/chromium_src/chrome/browser/process_singleton_win.cc b/chromium_src/chrome/browser/process_singleton_win.cc new file mode 100644 index 000000000000..14e53bec5fa7 --- /dev/null +++ b/chromium_src/chrome/browser/process_singleton_win.cc @@ -0,0 +1,328 @@ +// 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/process_singleton.h" + +#include + +#include "base/base_paths.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/process/process.h" +#include "base/process/process_info.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "base/win/metro.h" +#include "base/win/registry.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "chrome/browser/chrome_process_finder_win.h" +#include "content/public/common/result_codes.h" +#include "net/base/escape.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/gfx/win/hwnd_util.h" + +namespace { + +const char kLockfile[] = "lockfile"; + +// A helper class that acquires the given |mutex| while the AutoLockMutex is in +// scope. +class AutoLockMutex { + public: + explicit AutoLockMutex(HANDLE mutex) : mutex_(mutex) { + DWORD result = ::WaitForSingleObject(mutex_, INFINITE); + DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result; + } + + ~AutoLockMutex() { + BOOL released = ::ReleaseMutex(mutex_); + DPCHECK(released); + } + + private: + HANDLE mutex_; + DISALLOW_COPY_AND_ASSIGN(AutoLockMutex); +}; + +// A helper class that releases the given |mutex| while the AutoUnlockMutex is +// in scope and immediately re-acquires it when going out of scope. +class AutoUnlockMutex { + public: + explicit AutoUnlockMutex(HANDLE mutex) : mutex_(mutex) { + BOOL released = ::ReleaseMutex(mutex_); + DPCHECK(released); + } + + ~AutoUnlockMutex() { + DWORD result = ::WaitForSingleObject(mutex_, INFINITE); + DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result; + } + + private: + HANDLE mutex_; + DISALLOW_COPY_AND_ASSIGN(AutoUnlockMutex); +}; + +// Checks the visibility of the enumerated window and signals once a visible +// window has been found. +BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) { + bool* result = reinterpret_cast(param); + *result = ::IsWindowVisible(window) != 0; + // Stops enumeration if a visible window has been found. + return !*result; +} + +// Convert Command line string to argv. +base::CommandLine::StringVector CommandLineStringToArgv( + const std::wstring& command_line_string) { + int num_args = 0; + wchar_t** args = NULL; + args = ::CommandLineToArgvW(command_line_string.c_str(), &num_args); + base::CommandLine::StringVector argv; + for (int i = 0; i < num_args; ++i) + argv.push_back(std::wstring(args[i])); + LocalFree(args); + return argv; +} + +bool ParseCommandLine(const COPYDATASTRUCT* cds, + base::CommandLine::StringVector* parsed_command_line, + base::FilePath* current_directory) { + // We should have enough room for the shortest command (min_message_size) + // and also be a multiple of wchar_t bytes. The shortest command + // possible is L"START\0\0" (empty current directory and command line). + static const int min_message_size = 7; + if (cds->cbData < min_message_size * sizeof(wchar_t) || + cds->cbData % sizeof(wchar_t) != 0) { + LOG(WARNING) << "Invalid WM_COPYDATA, length = " << cds->cbData; + return false; + } + + // We split the string into 4 parts on NULLs. + DCHECK(cds->lpData); + const std::wstring msg(static_cast(cds->lpData), + cds->cbData / sizeof(wchar_t)); + const std::wstring::size_type first_null = msg.find_first_of(L'\0'); + if (first_null == 0 || first_null == std::wstring::npos) { + // no NULL byte, don't know what to do + LOG(WARNING) << "Invalid WM_COPYDATA, length = " << msg.length() << + ", first null = " << first_null; + return false; + } + + // Decode the command, which is everything until the first NULL. + if (msg.substr(0, first_null) == L"START") { + // Another instance is starting parse the command line & do what it would + // have done. + VLOG(1) << "Handling STARTUP request from another process"; + const std::wstring::size_type second_null = + msg.find_first_of(L'\0', first_null + 1); + if (second_null == std::wstring::npos || + first_null == msg.length() - 1 || second_null == msg.length()) { + LOG(WARNING) << "Invalid format for start command, we need a string in 4 " + "parts separated by NULLs"; + return false; + } + + // Get current directory. + *current_directory = base::FilePath(msg.substr(first_null + 1, + second_null - first_null)); + + const std::wstring::size_type third_null = + msg.find_first_of(L'\0', second_null + 1); + if (third_null == std::wstring::npos || + third_null == msg.length()) { + LOG(WARNING) << "Invalid format for start command, we need a string in 4 " + "parts separated by NULLs"; + } + + // Get command line. + const std::wstring cmd_line = + msg.substr(second_null + 1, third_null - second_null); + *parsed_command_line = CommandLineStringToArgv(cmd_line); + return true; + } + return false; +} + +bool ProcessLaunchNotification( + const ProcessSingleton::NotificationCallback& notification_callback, + UINT message, + WPARAM wparam, + LPARAM lparam, + LRESULT* result) { + if (message != WM_COPYDATA) + return false; + + // Handle the WM_COPYDATA message from another process. + const COPYDATASTRUCT* cds = reinterpret_cast(lparam); + + base::CommandLine::StringVector parsed_command_line; + base::FilePath current_directory; + if (!ParseCommandLine(cds, &parsed_command_line, ¤t_directory)) { + *result = TRUE; + return true; + } + + *result = notification_callback.Run(parsed_command_line, current_directory) ? + TRUE : FALSE; + return true; +} + +bool TerminateAppWithError() { + // TODO: This is called when the secondary process can't ping the primary + // process. Need to find out what to do here. + return false; +} + +} // namespace + +ProcessSingleton::ProcessSingleton( + const base::FilePath& user_data_dir, + const NotificationCallback& notification_callback) + : notification_callback_(notification_callback), + is_virtualized_(false), + lock_file_(INVALID_HANDLE_VALUE), + user_data_dir_(user_data_dir), + should_kill_remote_process_callback_( + base::Bind(&TerminateAppWithError)) { +} + +ProcessSingleton::~ProcessSingleton() { + if (lock_file_ != INVALID_HANDLE_VALUE) + ::CloseHandle(lock_file_); +} + +// Code roughly based on Mozilla. +ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() { + if (is_virtualized_) + return PROCESS_NOTIFIED; // We already spawned the process in this case. + if (lock_file_ == INVALID_HANDLE_VALUE && !remote_window_) { + return LOCK_ERROR; + } else if (!remote_window_) { + return PROCESS_NONE; + } + + switch (chrome::AttemptToNotifyRunningChrome(remote_window_, false)) { + case chrome::NOTIFY_SUCCESS: + return PROCESS_NOTIFIED; + case chrome::NOTIFY_FAILED: + remote_window_ = NULL; + return PROCESS_NONE; + case chrome::NOTIFY_WINDOW_HUNG: + // Fall through and potentially terminate the hung browser. + break; + } + + DWORD process_id = 0; + DWORD thread_id = ::GetWindowThreadProcessId(remote_window_, &process_id); + if (!thread_id || !process_id) { + remote_window_ = NULL; + return PROCESS_NONE; + } + base::Process process = base::Process::Open(process_id); + + // The window is hung. Scan for every window to find a visible one. + bool visible_window = false; + ::EnumThreadWindows(thread_id, + &BrowserWindowEnumeration, + reinterpret_cast(&visible_window)); + + // If there is a visible browser window, ask the user before killing it. + if (visible_window && !should_kill_remote_process_callback_.Run()) { + // The user denied. Quit silently. + return PROCESS_NOTIFIED; + } + + // Time to take action. Kill the browser process. + process.Terminate(content::RESULT_CODE_HUNG, true); + remote_window_ = NULL; + return PROCESS_NONE; +} + +ProcessSingleton::NotifyResult +ProcessSingleton::NotifyOtherProcessOrCreate() { + ProcessSingleton::NotifyResult result = PROCESS_NONE; + if (!Create()) { + result = NotifyOtherProcess(); + if (result == PROCESS_NONE) + result = PROFILE_IN_USE; + } + return result; +} + +// Look for a Chrome instance that uses the same profile directory. If there +// isn't one, create a message window with its title set to the profile +// directory path. +bool ProcessSingleton::Create() { + static const wchar_t kMutexName[] = L"Local\\AtomProcessSingletonStartup!"; + + remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_); + if (!remote_window_) { + // Make sure we will be the one and only process creating the window. + // We use a named Mutex since we are protecting against multi-process + // access. As documented, it's clearer to NOT request ownership on creation + // since it isn't guaranteed we will get it. It is better to create it + // without ownership and explicitly get the ownership afterward. + base::win::ScopedHandle only_me(::CreateMutex(NULL, FALSE, kMutexName)); + if (!only_me.IsValid()) { + DPLOG(FATAL) << "CreateMutex failed"; + return false; + } + + AutoLockMutex auto_lock_only_me(only_me.Get()); + + // We now own the mutex so we are the only process that can create the + // window at this time, but we must still check if someone created it + // between the time where we looked for it above and the time the mutex + // was given to us. + remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_); + if (!remote_window_) { + // We have to make sure there is no Chrome instance running on another + // machine that uses the same profile. + base::FilePath lock_file_path = user_data_dir_.AppendASCII(kLockfile); + lock_file_ = ::CreateFile(lock_file_path.value().c_str(), + GENERIC_WRITE, + FILE_SHARE_READ, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL | + FILE_FLAG_DELETE_ON_CLOSE, + NULL); + DWORD error = ::GetLastError(); + LOG_IF(WARNING, lock_file_ != INVALID_HANDLE_VALUE && + error == ERROR_ALREADY_EXISTS) << "Lock file exists but is writable."; + LOG_IF(ERROR, lock_file_ == INVALID_HANDLE_VALUE) + << "Lock file can not be created! Error code: " << error; + + if (lock_file_ != INVALID_HANDLE_VALUE) { + // Set the window's title to the path of our user data directory so + // other Chrome instances can decide if they should forward to us. + bool result = window_.CreateNamed( + base::Bind(&ProcessLaunchNotification, notification_callback_), + user_data_dir_.value()); + + // NB: Ensure that if the primary app gets started as elevated + // admin inadvertently, secondary windows running not as elevated + // will still be able to send messages + ::ChangeWindowMessageFilterEx(window_.hwnd(), WM_COPYDATA, MSGFLT_ALLOW, NULL); + CHECK(result && window_.hwnd()); + } + } + } + + return window_.hwnd() != NULL; +} + +void ProcessSingleton::Cleanup() { +} + +void ProcessSingleton::OverrideShouldKillRemoteProcessCallbackForTesting( + const ShouldKillRemoteProcessCallback& display_dialog_callback) { + should_kill_remote_process_callback_ = display_dialog_callback; +} diff --git a/chromium_src/chrome/browser/speech/tts_controller_impl.cc b/chromium_src/chrome/browser/speech/tts_controller_impl.cc index 6b66b6a61960..610ce1656759 100644 --- a/chromium_src/chrome/browser/speech/tts_controller_impl.cc +++ b/chromium_src/chrome/browser/speech/tts_controller_impl.cc @@ -111,7 +111,7 @@ TtsController* TtsController::GetInstance() { // static TtsControllerImpl* TtsControllerImpl::GetInstance() { - return Singleton::get(); + return base::Singleton::get(); } TtsControllerImpl::TtsControllerImpl() diff --git a/chromium_src/chrome/browser/speech/tts_controller_impl.h b/chromium_src/chrome/browser/speech/tts_controller_impl.h index 651f836cdf6c..6c8aa5747d20 100644 --- a/chromium_src/chrome/browser/speech/tts_controller_impl.h +++ b/chromium_src/chrome/browser/speech/tts_controller_impl.h @@ -77,7 +77,7 @@ class TtsControllerImpl : public TtsController { int GetMatchingVoice(const Utterance* utterance, std::vector& voices); - friend struct DefaultSingletonTraits; + friend struct base::DefaultSingletonTraits; // The current utterance being spoken. Utterance* current_utterance_; @@ -101,4 +101,4 @@ class TtsControllerImpl : public TtsController { DISALLOW_COPY_AND_ASSIGN(TtsControllerImpl); }; -#endif // CHROME_BROWSER_SPEECH_TTS_CONTROLLER_IMPL_H_ \ No newline at end of file +#endif // CHROME_BROWSER_SPEECH_TTS_CONTROLLER_IMPL_H_ diff --git a/chromium_src/chrome/browser/speech/tts_linux.cc b/chromium_src/chrome/browser/speech/tts_linux.cc index 43b28a5eade7..ba15516ce036 100644 --- a/chromium_src/chrome/browser/speech/tts_linux.cc +++ b/chromium_src/chrome/browser/speech/tts_linux.cc @@ -13,6 +13,7 @@ #include "base/synchronization/lock.h" #include "chrome/browser/speech/tts_platform.h" #include "content/public/browser/browser_thread.h" +#include "content/public/common/content_switches.h" #include "library_loaders/libspeechd.h" @@ -32,18 +33,17 @@ struct SPDChromeVoice { class TtsPlatformImplLinux : public TtsPlatformImpl { public: - virtual bool PlatformImplAvailable() override; - virtual bool Speak( - int utterance_id, - const std::string& utterance, - const std::string& lang, - const VoiceData& voice, - const UtteranceContinuousParameters& params) override; - virtual bool StopSpeaking() override; - virtual void Pause() override; - virtual void Resume() override; - virtual bool IsSpeaking() override; - virtual void GetVoices(std::vector* out_voices) override; + bool PlatformImplAvailable() override; + bool Speak(int utterance_id, + const std::string& utterance, + const std::string& lang, + const VoiceData& voice, + const UtteranceContinuousParameters& params) override; + bool StopSpeaking() override; + void Pause() override; + void Resume() override; + bool IsSpeaking() override; + void GetVoices(std::vector* out_voices) override; void OnSpeechEvent(SPDNotificationType type); @@ -52,7 +52,7 @@ class TtsPlatformImplLinux : public TtsPlatformImpl { private: TtsPlatformImplLinux(); - virtual ~TtsPlatformImplLinux(); + ~TtsPlatformImplLinux() override; // Initiate the connection with the speech dispatcher. void Initialize(); @@ -83,7 +83,7 @@ class TtsPlatformImplLinux : public TtsPlatformImpl { // uniquely identify a voice across all available modules. scoped_ptr > all_native_voices_; - friend struct DefaultSingletonTraits; + friend struct base::DefaultSingletonTraits; DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplLinux); }; @@ -94,6 +94,11 @@ SPDNotificationType TtsPlatformImplLinux::current_notification_ = TtsPlatformImplLinux::TtsPlatformImplLinux() : utterance_id_(0) { + const base::CommandLine& command_line = + *base::CommandLine::ForCurrentProcess(); + if (!command_line.HasSwitch(switches::kEnableSpeechDispatcher)) + return; + BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(&TtsPlatformImplLinux::Initialize, @@ -111,7 +116,7 @@ void TtsPlatformImplLinux::Initialize() { // http://crbug.com/317360 ANNOTATE_SCOPED_MEMORY_LEAK; conn_ = libspeechd_loader_.spd_open( - "chrome", "extension_api", NULL, SPD_MODE_SINGLE); + "chrome", "extension_api", NULL, SPD_MODE_THREADED); } if (!conn_) return; @@ -146,7 +151,7 @@ void TtsPlatformImplLinux::Reset() { if (conn_) libspeechd_loader_.spd_close(conn_); conn_ = libspeechd_loader_.spd_open( - "chrome", "extension_api", NULL, SPD_MODE_SINGLE); + "chrome", "extension_api", NULL, SPD_MODE_THREADED); } bool TtsPlatformImplLinux::PlatformImplAvailable() { @@ -187,6 +192,10 @@ bool TtsPlatformImplLinux::Speak( libspeechd_loader_.spd_set_voice_rate(conn_, 100 * log10(rate) / log10(3)); libspeechd_loader_.spd_set_voice_pitch(conn_, 100 * log10(pitch) / log10(3)); + // Support languages other than the default + if (!lang.empty()) + libspeechd_loader_.spd_set_language(conn_, lang.c_str()); + utterance_ = utterance; utterance_id_ = utterance_id; @@ -337,8 +346,9 @@ void TtsPlatformImplLinux::IndexMarkCallback(size_t msg_id, // static TtsPlatformImplLinux* TtsPlatformImplLinux::GetInstance() { - return Singleton >::get(); + return base::Singleton< + TtsPlatformImplLinux, + base::LeakySingletonTraits>::get(); } // static diff --git a/chromium_src/chrome/browser/speech/tts_mac.mm b/chromium_src/chrome/browser/speech/tts_mac.mm index acfa5b58bf3b..aafbd4692515 100644 --- a/chromium_src/chrome/browser/speech/tts_mac.mm +++ b/chromium_src/chrome/browser/speech/tts_mac.mm @@ -91,7 +91,7 @@ class TtsPlatformImplMac : public TtsPlatformImpl { int last_char_index_; bool paused_; - friend struct DefaultSingletonTraits; + friend struct base::DefaultSingletonTraits; DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplMac); }; @@ -291,7 +291,7 @@ TtsPlatformImplMac::~TtsPlatformImplMac() { // static TtsPlatformImplMac* TtsPlatformImplMac::GetInstance() { - return Singleton::get(); + return base::Singleton::get(); } @implementation ChromeTtsDelegate diff --git a/chromium_src/chrome/browser/speech/tts_win.cc b/chromium_src/chrome/browser/speech/tts_win.cc index c7b0a0ca724c..ac2582058894 100644 --- a/chromium_src/chrome/browser/speech/tts_win.cc +++ b/chromium_src/chrome/browser/speech/tts_win.cc @@ -15,26 +15,26 @@ class TtsPlatformImplWin : public TtsPlatformImpl { public: - virtual bool PlatformImplAvailable() { + bool PlatformImplAvailable() override { return true; } - virtual bool Speak( + bool Speak( int utterance_id, const std::string& utterance, const std::string& lang, const VoiceData& voice, - const UtteranceContinuousParameters& params); + const UtteranceContinuousParameters& params) override; - virtual bool StopSpeaking(); + bool StopSpeaking() override; - virtual void Pause(); + void Pause() override; - virtual void Resume(); + void Resume() override; - virtual bool IsSpeaking(); + bool IsSpeaking() override; - virtual void GetVoices(std::vector* out_voices) override; + void GetVoices(std::vector* out_voices) override; // Get the single instance of this class. static TtsPlatformImplWin* GetInstance(); @@ -43,7 +43,7 @@ class TtsPlatformImplWin : public TtsPlatformImpl { private: TtsPlatformImplWin(); - virtual ~TtsPlatformImplWin() {} + ~TtsPlatformImplWin() override {} void OnSpeechEvent(); @@ -57,7 +57,7 @@ class TtsPlatformImplWin : public TtsPlatformImpl { int char_position_; bool paused_; - friend struct DefaultSingletonTraits; + friend struct base::DefaultSingletonTraits; DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplWin); }; @@ -220,6 +220,8 @@ void TtsPlatformImplWin::OnSpeechEvent() { utterance_id_, TTS_EVENT_SENTENCE, char_position_, std::string()); break; + default: + break; } } } @@ -246,12 +248,12 @@ TtsPlatformImplWin::TtsPlatformImplWin() // static TtsPlatformImplWin* TtsPlatformImplWin::GetInstance() { - return Singleton >::get(); + return base::Singleton>::get(); } // static void TtsPlatformImplWin::SpeechEventCallback( WPARAM w_param, LPARAM l_param) { GetInstance()->OnSpeechEvent(); -} \ No newline at end of file +} diff --git a/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc b/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc index 0d2a6dd73824..cead675a74d3 100644 --- a/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc +++ b/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc @@ -21,7 +21,7 @@ const char kAppMenuRegistrarPath[] = "/com/canonical/AppMenu/Registrar"; // static GlobalMenuBarRegistrarX11* GlobalMenuBarRegistrarX11::GetInstance() { - return Singleton::get(); + return base::Singleton::get(); } void GlobalMenuBarRegistrarX11::OnWindowMapped(unsigned long xid) { @@ -39,7 +39,7 @@ void GlobalMenuBarRegistrarX11::OnWindowUnmapped(unsigned long xid) { } GlobalMenuBarRegistrarX11::GlobalMenuBarRegistrarX11() - : registrar_proxy_(NULL) { + : registrar_proxy_(nullptr) { // libdbusmenu uses the gio version of dbus; I tried using the code in dbus/, // but it looks like that's isn't sharing the bus name with the gio version, // even when |connection_type| is set to SHARED. @@ -49,11 +49,11 @@ GlobalMenuBarRegistrarX11::GlobalMenuBarRegistrarX11() G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START), - NULL, + nullptr, kAppMenuRegistrarName, kAppMenuRegistrarPath, kAppMenuRegistrarName, - NULL, // TODO: Probalby want a real cancelable. + nullptr, // TODO: Probalby want a real cancelable. static_cast(OnProxyCreatedThunk), this); } @@ -84,9 +84,9 @@ void GlobalMenuBarRegistrarX11::RegisterXID(unsigned long xid) { "RegisterWindow", g_variant_new("(uo)", xid, path.c_str()), G_DBUS_CALL_FLAGS_NONE, -1, - NULL, - NULL, - NULL); + nullptr, + nullptr, + nullptr); } void GlobalMenuBarRegistrarX11::UnregisterXID(unsigned long xid) { @@ -105,14 +105,14 @@ void GlobalMenuBarRegistrarX11::UnregisterXID(unsigned long xid) { "UnregisterWindow", g_variant_new("(u)", xid), G_DBUS_CALL_FLAGS_NONE, -1, - NULL, - NULL, - NULL); + nullptr, + nullptr, + nullptr); } void GlobalMenuBarRegistrarX11::OnProxyCreated(GObject* source, GAsyncResult* result) { - GError* error = NULL; + GError* error = nullptr; GDBusProxy* proxy = g_dbus_proxy_new_for_bus_finish(result, &error); if (error) { g_error_free(error); @@ -128,7 +128,7 @@ void GlobalMenuBarRegistrarX11::OnProxyCreated(GObject* source, g_signal_connect(registrar_proxy_, "notify::g-name-owner", G_CALLBACK(OnNameOwnerChangedThunk), this); - OnNameOwnerChanged(NULL, NULL); + OnNameOwnerChanged(nullptr, nullptr); } void GlobalMenuBarRegistrarX11::OnNameOwnerChanged(GObject* /* ignored */, diff --git a/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h b/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h index e35e87c0d20c..694f776b24ef 100644 --- a/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h +++ b/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h @@ -28,7 +28,7 @@ class GlobalMenuBarRegistrarX11 { void OnWindowUnmapped(unsigned long xid); private: - friend struct DefaultSingletonTraits; + friend struct base::DefaultSingletonTraits; GlobalMenuBarRegistrarX11(); ~GlobalMenuBarRegistrarX11(); diff --git a/chromium_src/chrome/common/chrome_constants.cc b/chromium_src/chrome/common/chrome_constants.cc new file mode 100644 index 000000000000..43a6ccdc7cb4 --- /dev/null +++ b/chromium_src/chrome/common/chrome_constants.cc @@ -0,0 +1,74 @@ +// 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/chrome_constants.h" + +#define FPL FILE_PATH_LITERAL + +namespace chrome { + +#if defined(OS_MACOSX) +const base::FilePath::CharType kFrameworkName[] = + FPL(ATOM_PRODUCT_NAME " Framework.framework"); +#endif // OS_MACOSX + +// filenames +const base::FilePath::CharType kCacheDirname[] = FPL("Cache"); +const base::FilePath::CharType kChannelIDFilename[] = FPL("Origin Bound Certs"); +const base::FilePath::CharType kCookieFilename[] = FPL("Cookies"); +const base::FilePath::CharType kCRLSetFilename[] = + FPL("Certificate Revocation Lists"); +const base::FilePath::CharType kCustomDictionaryFileName[] = + FPL("Custom Dictionary.txt"); +const base::FilePath::CharType kExtensionActivityLogFilename[] = + FPL("Extension Activity"); +const base::FilePath::CharType kExtensionsCookieFilename[] = + FPL("Extension Cookies"); +const base::FilePath::CharType kFirstRunSentinel[] = FPL("First Run"); +const base::FilePath::CharType kGCMStoreDirname[] = FPL("GCM Store"); +const base::FilePath::CharType kLocalStateFilename[] = FPL("Local State"); +const base::FilePath::CharType kLocalStorePoolName[] = FPL("LocalStorePool"); +const base::FilePath::CharType kMediaCacheDirname[] = FPL("Media Cache"); +const base::FilePath::CharType kNetworkPersistentStateFilename[] = + FPL("Network Persistent State"); +const base::FilePath::CharType kOfflinePageArchviesDirname[] = + FPL("Offline Pages/archives"); +const base::FilePath::CharType kOfflinePageMetadataDirname[] = + FPL("Offline Pages/metadata"); +const base::FilePath::CharType kPreferencesFilename[] = FPL("Preferences"); +const base::FilePath::CharType kProtectedPreferencesFilenameDeprecated[] = + FPL("Protected Preferences"); +const base::FilePath::CharType kReadmeFilename[] = FPL("README"); +const base::FilePath::CharType kResetPromptMementoFilename[] = + FPL("Reset Prompt Memento"); +const base::FilePath::CharType kSafeBrowsingBaseFilename[] = + FPL("Safe Browsing"); +const base::FilePath::CharType kSecurePreferencesFilename[] = + FPL("Secure Preferences"); +const base::FilePath::CharType kServiceStateFileName[] = FPL("Service State"); +const base::FilePath::CharType kSingletonCookieFilename[] = + FPL("SingletonCookie"); +const base::FilePath::CharType kSingletonLockFilename[] = FPL("SingletonLock"); +const base::FilePath::CharType kSingletonSocketFilename[] = + FPL("SingletonSocket"); +const base::FilePath::CharType kSupervisedUserSettingsFilename[] = + FPL("Managed Mode Settings"); +const base::FilePath::CharType kThemePackFilename[] = FPL("Cached Theme.pak"); +const base::FilePath::CharType kThemePackMaterialDesignFilename[] = + FPL("Cached Theme Material Design.pak"); +const base::FilePath::CharType kWebAppDirname[] = FPL("Web Applications"); + +// File name of the Pepper Flash plugin on different platforms. +const base::FilePath::CharType kPepperFlashPluginFilename[] = +#if defined(OS_MACOSX) + FPL("PepperFlashPlayer.plugin"); +#elif defined(OS_WIN) + FPL("pepflashplayer.dll"); +#else // OS_LINUX, etc. + FPL("libpepflashplayer.so"); +#endif + +} // namespace chrome + +#undef FPL diff --git a/chromium_src/chrome/common/chrome_constants.h b/chromium_src/chrome/common/chrome_constants.h new file mode 100644 index 000000000000..2df506ac5d59 --- /dev/null +++ b/chromium_src/chrome/common/chrome_constants.h @@ -0,0 +1,58 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// A handful of resource-like constants related to the Chrome application. + +#ifndef CHROME_COMMON_CHROME_CONSTANTS_H_ +#define CHROME_COMMON_CHROME_CONSTANTS_H_ + +#include "base/files/file_path.h" + +namespace chrome { + +#if defined(OS_MACOSX) +// NOTE: if you change the value of kFrameworkName, please don't forget to +// update components/test/run_all_unittests.cc as well. +// TODO(tfarina): Remove the comment above, when you fix components to use plist +// on Mac. +extern const base::FilePath::CharType kFrameworkName[]; +#endif // OS_MACOSX + +// filenames +extern const base::FilePath::CharType kCacheDirname[]; +extern const base::FilePath::CharType kChannelIDFilename[]; +extern const base::FilePath::CharType kCookieFilename[]; +extern const base::FilePath::CharType kCRLSetFilename[]; +extern const base::FilePath::CharType kCustomDictionaryFileName[]; +extern const base::FilePath::CharType kExtensionActivityLogFilename[]; +extern const base::FilePath::CharType kExtensionsCookieFilename[]; +extern const base::FilePath::CharType kFirstRunSentinel[]; +extern const base::FilePath::CharType kGCMStoreDirname[]; +extern const base::FilePath::CharType kLocalStateFilename[]; +extern const base::FilePath::CharType kLocalStorePoolName[]; +extern const base::FilePath::CharType kMediaCacheDirname[]; +extern const base::FilePath::CharType kNetworkPersistentStateFilename[]; +extern const base::FilePath::CharType kOfflinePageArchviesDirname[]; +extern const base::FilePath::CharType kOfflinePageMetadataDirname[]; +extern const base::FilePath::CharType kPreferencesFilename[]; +extern const base::FilePath::CharType kProtectedPreferencesFilenameDeprecated[]; +extern const base::FilePath::CharType kReadmeFilename[]; +extern const base::FilePath::CharType kResetPromptMementoFilename[]; +extern const base::FilePath::CharType kSafeBrowsingBaseFilename[]; +extern const base::FilePath::CharType kSecurePreferencesFilename[]; +extern const base::FilePath::CharType kServiceStateFileName[]; +extern const base::FilePath::CharType kSingletonCookieFilename[]; +extern const base::FilePath::CharType kSingletonLockFilename[]; +extern const base::FilePath::CharType kSingletonSocketFilename[]; +extern const base::FilePath::CharType kSupervisedUserSettingsFilename[]; +extern const base::FilePath::CharType kThemePackFilename[]; +extern const base::FilePath::CharType kThemePackMaterialDesignFilename[]; +extern const base::FilePath::CharType kWebAppDirname[]; + +// File name of the Pepper Flash plugin on different platforms. +extern const base::FilePath::CharType kPepperFlashPluginFilename[]; + +} // namespace chrome + +#endif // CHROME_COMMON_CHROME_CONSTANTS_H_ diff --git a/chromium_src/chrome/common/chrome_paths.cc b/chromium_src/chrome/common/chrome_paths.cc new file mode 100644 index 000000000000..d8a32446fe9e --- /dev/null +++ b/chromium_src/chrome/common/chrome_paths.cc @@ -0,0 +1,610 @@ +// 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/chrome_paths.h" + +#include "base/files/file_util.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/mac/bundle_locations.h" +#include "base/path_service.h" +#include "base/strings/string_util.h" +#include "base/sys_info.h" +#include "base/threading/thread_restrictions.h" +#include "base/version.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths_internal.h" + +#if defined(OS_ANDROID) +#include "base/android/path_utils.h" +#include "base/base_paths_android.h" +// ui/base must only be used on Android. See BUILD.gn for dependency info. +#include "ui/base/ui_base_paths.h" // nogncheck +#endif + +#if defined(OS_MACOSX) +#include "base/mac/foundation_util.h" +#endif + +#if defined(OS_WIN) +#include "base/win/registry.h" +#endif + +namespace { + +// The Pepper Flash plugins are in a directory with this name. +const base::FilePath::CharType kPepperFlashBaseDirectory[] = + FILE_PATH_LITERAL("PepperFlash"); + +#if defined(OS_MACOSX) && !defined(OS_IOS) +const base::FilePath::CharType kPepperFlashSystemBaseDirectory[] = + FILE_PATH_LITERAL("Internet Plug-Ins/PepperFlashPlayer"); +const base::FilePath::CharType kFlashSystemBaseDirectory[] = + FILE_PATH_LITERAL("Internet Plug-Ins"); +const base::FilePath::CharType kFlashSystemPluginName[] = + FILE_PATH_LITERAL("Flash Player.plugin"); +#endif + +const base::FilePath::CharType kInternalNaClPluginFileName[] = + FILE_PATH_LITERAL("internal-nacl-plugin"); + +#if defined(OS_LINUX) +// The path to the external extension .json files. +// /usr/share seems like a good choice, see: http://www.pathname.com/fhs/ +const base::FilePath::CharType kFilepathSinglePrefExtensions[] = +#if defined(GOOGLE_CHROME_BUILD) + FILE_PATH_LITERAL("/usr/share/google-chrome/extensions"); +#else + FILE_PATH_LITERAL("/usr/share/chromium/extensions"); +#endif // defined(GOOGLE_CHROME_BUILD) + +// The path to the hint file that tells the pepper plugin loader +// where it can find the latest component updated flash. +const base::FilePath::CharType kComponentUpdatedFlashHint[] = + FILE_PATH_LITERAL("latest-component-updated-flash"); +#endif // defined(OS_LINUX) + +static base::LazyInstance + g_invalid_specified_user_data_dir = LAZY_INSTANCE_INITIALIZER; + +// Gets the path for internal plugins. +bool GetInternalPluginsDirectory(base::FilePath* result) { +#if defined(OS_MACOSX) && !defined(OS_IOS) + // If called from Chrome, get internal plugins from a subdirectory of the + // framework. + if (base::mac::AmIBundled()) { + *result = chrome::GetFrameworkBundlePath(); + DCHECK(!result->empty()); + *result = result->Append("Internet Plug-Ins"); + return true; + } + // In tests, just look in the module directory (below). +#endif + + // The rest of the world expects plugins in the module directory. + return PathService::Get(base::DIR_MODULE, result); +} + +#if defined(OS_WIN) +// Gets the Flash path if installed on the system. |is_npapi| determines whether +// to return the NPAPI of the PPAPI version of the system plugin. +bool GetSystemFlashFilename(base::FilePath* out_path, bool is_npapi) { + const wchar_t kNpapiFlashRegistryRoot[] = + L"SOFTWARE\\Macromedia\\FlashPlayerPlugin"; + const wchar_t kPepperFlashRegistryRoot[] = + L"SOFTWARE\\Macromedia\\FlashPlayerPepper"; + const wchar_t kFlashPlayerPathValueName[] = L"PlayerPath"; + + base::win::RegKey path_key( + HKEY_LOCAL_MACHINE, + is_npapi ? kNpapiFlashRegistryRoot : kPepperFlashRegistryRoot, KEY_READ); + base::string16 path_str; + if (FAILED(path_key.ReadValue(kFlashPlayerPathValueName, &path_str))) + return false; + + *out_path = base::FilePath(path_str); + return true; +} +#endif + +} // namespace + +namespace chrome { + +bool PathProvider(int key, base::FilePath* result) { + // Some keys are just aliases... + switch (key) { + case chrome::DIR_APP: + return PathService::Get(base::DIR_MODULE, result); + case chrome::DIR_LOGS: +#ifdef NDEBUG + // Release builds write to the data dir + return PathService::Get(chrome::DIR_USER_DATA, result); +#else + // Debug builds write next to the binary (in the build tree) +#if defined(OS_MACOSX) + if (!PathService::Get(base::DIR_EXE, result)) + return false; + if (base::mac::AmIBundled()) { + // If we're called from chrome, dump it beside the app (outside the + // app bundle), if we're called from a unittest, we'll already + // outside the bundle so use the exe dir. + // exe_dir gave us .../Chromium.app/Contents/MacOS/Chromium. + *result = result->DirName(); + *result = result->DirName(); + *result = result->DirName(); + } + return true; +#else + return PathService::Get(base::DIR_EXE, result); +#endif // defined(OS_MACOSX) +#endif // NDEBUG + case chrome::FILE_RESOURCE_MODULE: + return PathService::Get(base::FILE_MODULE, result); + } + + // Assume that we will not need to create the directory if it does not exist. + // This flag can be set to true for the cases where we want to create it. + bool create_dir = false; + + base::FilePath cur; + switch (key) { + case chrome::DIR_USER_DATA: + if (!GetDefaultUserDataDirectory(&cur)) { + NOTREACHED(); + return false; + } + create_dir = true; + break; + case chrome::DIR_USER_DOCUMENTS: + if (!GetUserDocumentsDirectory(&cur)) + return false; + create_dir = true; + break; + case chrome::DIR_USER_MUSIC: + if (!GetUserMusicDirectory(&cur)) + return false; + break; + case chrome::DIR_USER_PICTURES: + if (!GetUserPicturesDirectory(&cur)) + return false; + break; + case chrome::DIR_USER_VIDEOS: + if (!GetUserVideosDirectory(&cur)) + return false; + break; + case chrome::DIR_DEFAULT_DOWNLOADS_SAFE: +#if defined(OS_WIN) || defined(OS_LINUX) + if (!GetUserDownloadsDirectorySafe(&cur)) + return false; + break; +#else + // Fall through for all other platforms. +#endif + case chrome::DIR_DEFAULT_DOWNLOADS: +#if defined(OS_ANDROID) + if (!base::android::GetDownloadsDirectory(&cur)) + return false; +#else + if (!GetUserDownloadsDirectory(&cur)) + return false; + // Do not create the download directory here, we have done it twice now + // and annoyed a lot of users. +#endif + break; + case chrome::DIR_CRASH_DUMPS: +#if defined(OS_CHROMEOS) + // ChromeOS uses a separate directory. See http://crosbug.com/25089 + cur = base::FilePath("/var/log/chrome"); +#elif defined(OS_ANDROID) + if (!base::android::GetCacheDirectory(&cur)) + return false; +#else + // The crash reports are always stored relative to the default user data + // directory. This avoids the problem of having to re-initialize the + // exception handler after parsing command line options, which may + // override the location of the app's profile directory. + if (!GetDefaultUserDataDirectory(&cur)) + return false; +#endif +#if defined(OS_MACOSX) + cur = cur.Append(FILE_PATH_LITERAL("Crashpad")); +#else + cur = cur.Append(FILE_PATH_LITERAL("Crash Reports")); +#endif + create_dir = true; + break; +#if defined(OS_WIN) + case chrome::DIR_WATCHER_DATA: + // The watcher data is always stored relative to the default user data + // directory. This allows the watcher to be initialized before + // command-line options have been parsed. + if (!GetDefaultUserDataDirectory(&cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("Diagnostics")); + break; +#endif + case chrome::DIR_RESOURCES: +#if defined(OS_MACOSX) + cur = base::mac::FrameworkBundlePath(); + cur = cur.Append(FILE_PATH_LITERAL("Resources")); +#else + if (!PathService::Get(chrome::DIR_APP, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("resources")); +#endif + break; + case chrome::DIR_INSPECTOR: + if (!PathService::Get(chrome::DIR_RESOURCES, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("inspector")); + break; + case chrome::DIR_APP_DICTIONARIES: +#if defined(OS_POSIX) + // We can't write into the EXE dir on Linux, so keep dictionaries + // alongside the safe browsing database in the user data dir. + // And we don't want to write into the bundle on the Mac, so push + // it to the user data dir there also. + if (!PathService::Get(chrome::DIR_USER_DATA, &cur)) + return false; +#else + if (!PathService::Get(base::DIR_EXE, &cur)) + return false; +#endif + cur = cur.Append(FILE_PATH_LITERAL("Dictionaries")); + create_dir = true; + break; + case chrome::DIR_INTERNAL_PLUGINS: + if (!GetInternalPluginsDirectory(&cur)) + return false; + break; + case chrome::DIR_PEPPER_FLASH_PLUGIN: + if (!GetInternalPluginsDirectory(&cur)) + return false; + cur = cur.Append(kPepperFlashBaseDirectory); + break; + case chrome::DIR_COMPONENT_UPDATED_PEPPER_FLASH_PLUGIN: + if (!PathService::Get(chrome::DIR_USER_DATA, &cur)) + return false; + cur = cur.Append(kPepperFlashBaseDirectory); + break; + case chrome::FILE_PEPPER_FLASH_SYSTEM_PLUGIN: +#if defined(OS_WIN) + if (!GetSystemFlashFilename(&cur, false)) + return false; +#elif defined(OS_MACOSX) && !defined(OS_IOS) + if (!GetLocalLibraryDirectory(&cur)) + return false; + cur = cur.Append(kPepperFlashSystemBaseDirectory); + cur = cur.Append(chrome::kPepperFlashPluginFilename); +#else + // Chrome on iOS does not supports PPAPI binaries, return false. + // TODO(wfh): If Adobe release PPAPI binaries for Linux, add support here. + return false; +#endif + break; + case chrome::FILE_FLASH_SYSTEM_PLUGIN: +#if defined(OS_WIN) + if (!GetSystemFlashFilename(&cur, true)) + return false; +#elif defined(OS_MACOSX) && !defined(OS_IOS) + if (!GetLocalLibraryDirectory(&cur)) + return false; + cur = cur.Append(kFlashSystemBaseDirectory); + cur = cur.Append(kFlashSystemPluginName); +#else + // Chrome on other platforms does not supports system NPAPI binaries. + return false; +#endif + break; + case chrome::FILE_LOCAL_STATE: + if (!PathService::Get(chrome::DIR_USER_DATA, &cur)) + return false; + cur = cur.Append(chrome::kLocalStateFilename); + break; + case chrome::FILE_RECORDED_SCRIPT: + if (!PathService::Get(chrome::DIR_USER_DATA, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("script.log")); + break; + case chrome::FILE_PEPPER_FLASH_PLUGIN: + if (!PathService::Get(chrome::DIR_PEPPER_FLASH_PLUGIN, &cur)) + return false; + cur = cur.Append(chrome::kPepperFlashPluginFilename); + break; + // TODO(teravest): Remove this case once the internal NaCl plugin is gone. + // We currently need a path here to look up whether the plugin is disabled + // and what its permissions are. + case chrome::FILE_NACL_PLUGIN: + if (!GetInternalPluginsDirectory(&cur)) + return false; + cur = cur.Append(kInternalNaClPluginFileName); + break; + // PNaCl is currenly installable via the component updater or by being + // simply built-in. DIR_PNACL_BASE is used as the base directory for + // installation via component updater. DIR_PNACL_COMPONENT will be + // the final location of pnacl, which is a subdir of DIR_PNACL_BASE. + case chrome::DIR_PNACL_BASE: + if (!PathService::Get(chrome::DIR_USER_DATA, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("pnacl")); + break; + // Where PNaCl files are ultimately located. The default finds the files + // inside the InternalPluginsDirectory / build directory, as if it + // was shipped along with chrome. The value can be overridden + // if it is installed via component updater. + case chrome::DIR_PNACL_COMPONENT: +#if defined(OS_MACOSX) + // PNaCl really belongs in the InternalPluginsDirectory but actually + // copying it there would result in the files also being shipped, which + // we don't want yet. So for now, just find them in the directory where + // they get built. + if (!PathService::Get(base::DIR_EXE, &cur)) + return false; + if (base::mac::AmIBundled()) { + // If we're called from chrome, it's beside the app (outside the + // app bundle), if we're called from a unittest, we'll already be + // outside the bundle so use the exe dir. + // exe_dir gave us .../Chromium.app/Contents/MacOS/Chromium. + cur = cur.DirName(); + cur = cur.DirName(); + cur = cur.DirName(); + } +#else + if (!GetInternalPluginsDirectory(&cur)) + return false; +#endif + cur = cur.Append(FILE_PATH_LITERAL("pnacl")); + break; +#if defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS) +#if defined(WIDEVINE_CDM_IS_COMPONENT) + case chrome::DIR_COMPONENT_WIDEVINE_CDM: + if (!PathService::Get(chrome::DIR_USER_DATA, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("WidevineCDM")); + break; +#endif // defined(WIDEVINE_CDM_IS_COMPONENT) + // TODO(xhwang): FILE_WIDEVINE_CDM_ADAPTER has different meanings. + // In the component case, this is the source adapter. Otherwise, it is the + // actual Pepper module that gets loaded. + case chrome::FILE_WIDEVINE_CDM_ADAPTER: + if (!GetInternalPluginsDirectory(&cur)) + return false; + cur = cur.AppendASCII(kWidevineCdmAdapterFileName); + break; +#endif // defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS) + case chrome::FILE_RESOURCES_PACK: +#if defined(OS_MACOSX) && !defined(OS_IOS) + if (base::mac::AmIBundled()) { + cur = base::mac::FrameworkBundlePath(); + cur = cur.Append(FILE_PATH_LITERAL("Resources")) + .Append(FILE_PATH_LITERAL("resources.pak")); + break; + } +#elif defined(OS_ANDROID) + if (!PathService::Get(ui::DIR_RESOURCE_PAKS_ANDROID, &cur)) + return false; +#else + // If we're not bundled on mac or Android, resources.pak should be next + // to the binary (e.g., for unit tests). + if (!PathService::Get(base::DIR_MODULE, &cur)) + return false; +#endif + cur = cur.Append(FILE_PATH_LITERAL("resources.pak")); + break; + case chrome::DIR_RESOURCES_EXTENSION: + if (!PathService::Get(base::DIR_MODULE, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("resources")) + .Append(FILE_PATH_LITERAL("extension")); + break; +#if defined(OS_CHROMEOS) + case chrome::DIR_CHROMEOS_WALLPAPERS: + if (!PathService::Get(chrome::DIR_USER_DATA, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("wallpapers")); + break; + case chrome::DIR_CHROMEOS_WALLPAPER_THUMBNAILS: + if (!PathService::Get(chrome::DIR_USER_DATA, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("wallpaper_thumbnails")); + break; + case chrome::DIR_CHROMEOS_CUSTOM_WALLPAPERS: + if (!PathService::Get(chrome::DIR_USER_DATA, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("custom_wallpapers")); + break; +#endif +#if defined(ENABLE_SUPERVISED_USERS) +#if defined(OS_LINUX) + case chrome::DIR_SUPERVISED_USERS_DEFAULT_APPS: + if (!PathService::Get(chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("managed_users")); + break; +#endif + case chrome::DIR_SUPERVISED_USER_INSTALLED_WHITELISTS: + if (!PathService::Get(chrome::DIR_USER_DATA, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("SupervisedUserInstalledWhitelists")); + break; +#endif + // The following are only valid in the development environment, and + // will fail if executed from an installed executable (because the + // generated path won't exist). + case chrome::DIR_GEN_TEST_DATA: +#if defined(OS_ANDROID) + // On Android, our tests don't have permission to write to DIR_MODULE. + // gtest/test_runner.py pushes data to external storage. + if (!PathService::Get(base::DIR_ANDROID_EXTERNAL_STORAGE, &cur)) + return false; +#else + if (!PathService::Get(base::DIR_MODULE, &cur)) + return false; +#endif + cur = cur.Append(FILE_PATH_LITERAL("test_data")); + if (!base::PathExists(cur)) // We don't want to create this. + return false; + break; + case chrome::DIR_TEST_DATA: + if (!PathService::Get(base::DIR_SOURCE_ROOT, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("chrome")); + cur = cur.Append(FILE_PATH_LITERAL("test")); + cur = cur.Append(FILE_PATH_LITERAL("data")); + if (!base::PathExists(cur)) // We don't want to create this. + return false; + break; + case chrome::DIR_TEST_TOOLS: + if (!PathService::Get(base::DIR_SOURCE_ROOT, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("chrome")); + cur = cur.Append(FILE_PATH_LITERAL("tools")); + cur = cur.Append(FILE_PATH_LITERAL("test")); + if (!base::PathExists(cur)) // We don't want to create this + return false; + break; +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_OPENBSD) + case chrome::DIR_POLICY_FILES: { +#if defined(GOOGLE_CHROME_BUILD) + cur = base::FilePath(FILE_PATH_LITERAL("/etc/opt/chrome/policies")); +#else + cur = base::FilePath(FILE_PATH_LITERAL("/etc/chromium/policies")); +#endif + break; + } +#endif +#if defined(OS_MACOSX) && !defined(OS_IOS) + case chrome::DIR_USER_LIBRARY: { + if (!GetUserLibraryDirectory(&cur)) + return false; + if (!base::PathExists(cur)) // We don't want to create this. + return false; + break; + } + case chrome::DIR_USER_APPLICATIONS: { + if (!GetUserApplicationsDirectory(&cur)) + return false; + if (!base::PathExists(cur)) // We don't want to create this. + return false; + break; + } +#endif +#if defined(OS_CHROMEOS) || (defined(OS_LINUX) && defined(CHROMIUM_BUILD)) || \ + (defined(OS_MACOSX) && !defined(OS_IOS)) + case chrome::DIR_USER_EXTERNAL_EXTENSIONS: { + if (!PathService::Get(chrome::DIR_USER_DATA, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("External Extensions")); + break; + } +#endif +#if defined(OS_LINUX) + case chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS: { + cur = base::FilePath(kFilepathSinglePrefExtensions); + break; + } +#endif + case chrome::DIR_EXTERNAL_EXTENSIONS: +#if defined(OS_MACOSX) && !defined(OS_IOS) + if (!chrome::GetGlobalApplicationSupportDirectory(&cur)) + return false; + + cur = cur.Append(FILE_PATH_LITERAL("Google")) + .Append(FILE_PATH_LITERAL("Chrome")) + .Append(FILE_PATH_LITERAL("External Extensions")); + create_dir = false; +#else + if (!PathService::Get(base::DIR_MODULE, &cur)) + return false; + + cur = cur.Append(FILE_PATH_LITERAL("extensions")); + create_dir = true; +#endif + break; + + case chrome::DIR_DEFAULT_APPS: +#if defined(OS_MACOSX) + cur = base::mac::FrameworkBundlePath(); + cur = cur.Append(FILE_PATH_LITERAL("Default Apps")); +#else + if (!PathService::Get(chrome::DIR_APP, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("default_apps")); +#endif + break; + +#if defined(OS_LINUX) || (defined(OS_MACOSX) && !defined(OS_IOS)) + case chrome::DIR_NATIVE_MESSAGING: +#if defined(OS_MACOSX) +#if defined(GOOGLE_CHROME_BUILD) + cur = base::FilePath(FILE_PATH_LITERAL( + "/Library/Google/Chrome/NativeMessagingHosts")); +#else + cur = base::FilePath(FILE_PATH_LITERAL( + "/Library/Application Support/Chromium/NativeMessagingHosts")); +#endif +#else // defined(OS_MACOSX) +#if defined(GOOGLE_CHROME_BUILD) + cur = base::FilePath(FILE_PATH_LITERAL( + "/etc/opt/chrome/native-messaging-hosts")); +#else + cur = base::FilePath(FILE_PATH_LITERAL( + "/etc/chromium/native-messaging-hosts")); +#endif +#endif // !defined(OS_MACOSX) + break; + + case chrome::DIR_USER_NATIVE_MESSAGING: + if (!PathService::Get(chrome::DIR_USER_DATA, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("NativeMessagingHosts")); + break; +#endif // defined(OS_LINUX) || (defined(OS_MACOSX) && !defined(OS_IOS)) +#if !defined(OS_ANDROID) + case chrome::DIR_GLOBAL_GCM_STORE: + if (!PathService::Get(chrome::DIR_USER_DATA, &cur)) + return false; + cur = cur.Append(kGCMStoreDirname); + break; +#endif // !defined(OS_ANDROID) +#if defined(OS_LINUX) + case chrome::FILE_COMPONENT_FLASH_HINT: + if (!PathService::Get(chrome::DIR_COMPONENT_UPDATED_PEPPER_FLASH_PLUGIN, + &cur)) { + return false; + } + cur = cur.Append(kComponentUpdatedFlashHint); + break; +#endif // defined(OS_LINUX) + + default: + return false; + } + + // TODO(bauerb): http://crbug.com/259796 + base::ThreadRestrictions::ScopedAllowIO allow_io; + if (create_dir && !base::PathExists(cur) && + !base::CreateDirectory(cur)) + return false; + + *result = cur; + return true; +} + +// This cannot be done as a static initializer sadly since Visual Studio will +// eliminate this object file if there is no direct entry point into it. +void RegisterPathProvider() { + PathService::RegisterProvider(PathProvider, PATH_START, PATH_END); +} + +void SetInvalidSpecifiedUserDataDir(const base::FilePath& user_data_dir) { + g_invalid_specified_user_data_dir.Get() = user_data_dir; +} + +const base::FilePath& GetInvalidSpecifiedUserDataDir() { + return g_invalid_specified_user_data_dir.Get(); +} + +} // namespace chrome diff --git a/chromium_src/chrome/common/chrome_paths.h b/chromium_src/chrome/common/chrome_paths.h new file mode 100644 index 000000000000..581fdc06f7c1 --- /dev/null +++ b/chromium_src/chrome/common/chrome_paths.h @@ -0,0 +1,152 @@ +// 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_COMMON_CHROME_PATHS_H__ +#define CHROME_COMMON_CHROME_PATHS_H__ + +#include "build/build_config.h" + +namespace base { +class FilePath; +} + +// This file declares path keys for the chrome module. These can be used with +// the PathService to access various special directories and files. + +namespace chrome { + +enum { + PATH_START = 1000, + + DIR_APP = PATH_START, // Directory where dlls and data reside. + DIR_LOGS, // Directory where logs should be written. + DIR_USER_DATA, // Directory where user data can be written. + DIR_CRASH_DUMPS, // Directory where crash dumps are written. +#if defined(OS_WIN) + DIR_WATCHER_DATA, // Directory where the Chrome watcher stores + // data. +#endif + DIR_RESOURCES, // Directory containing separate file resources + // used by Chrome at runtime. + DIR_INSPECTOR, // Directory where web inspector is located. + DIR_APP_DICTIONARIES, // Directory where the global dictionaries are. + DIR_USER_DOCUMENTS, // Directory for a user's "My Documents". + DIR_USER_MUSIC, // Directory for a user's music. + DIR_USER_PICTURES, // Directory for a user's pictures. + DIR_USER_VIDEOS, // Directory for a user's videos. + DIR_DEFAULT_DOWNLOADS_SAFE, // Directory for a user's + // "My Documents/Downloads", (Windows) or + // "Downloads". (Linux) + DIR_DEFAULT_DOWNLOADS, // Directory for a user's downloads. + DIR_INTERNAL_PLUGINS, // Directory where internal plugins reside. +#if defined(OS_POSIX) && !defined(OS_MACOSX) + DIR_POLICY_FILES, // Directory for system-wide read-only + // policy files that allow sys-admins + // to set policies for chrome. This directory + // contains subdirectories. +#endif +#if defined(OS_MACOSX) && !defined(OS_IOS) + DIR_USER_APPLICATIONS, // ~/Applications + DIR_USER_LIBRARY, // ~/Library +#endif +#if defined(OS_CHROMEOS) || (defined(OS_LINUX) && defined(CHROMIUM_BUILD)) || \ + (defined(OS_MACOSX) && !defined(OS_IOS)) + DIR_USER_EXTERNAL_EXTENSIONS, // Directory for per-user external extensions + // on Chrome Mac and Chromium Linux. + // On Chrome OS, this path is used for OEM + // customization. Getting this path does not + // create it. +#endif + +#if defined(OS_LINUX) + DIR_STANDALONE_EXTERNAL_EXTENSIONS, // Directory for 'per-extension' + // definition manifest files that + // describe extensions which are to be + // installed when chrome is run. +#endif + DIR_EXTERNAL_EXTENSIONS, // Directory where installer places .crx files. + + DIR_DEFAULT_APPS, // Directory where installer places .crx files + // to be installed when chrome is first run. + DIR_PEPPER_FLASH_PLUGIN, // Directory to the bundled Pepper Flash plugin, + // containing the plugin and the manifest. + DIR_COMPONENT_UPDATED_PEPPER_FLASH_PLUGIN, // Base directory of the Pepper + // Flash plugins downloaded by the + // component updater. + FILE_RESOURCE_MODULE, // Full path and filename of the module that + // contains embedded resources (version, + // strings, images, etc.). + FILE_LOCAL_STATE, // Path and filename to the file in which + // machine/installation-specific state is saved. + FILE_RECORDED_SCRIPT, // Full path to the script.log file that + // contains recorded browser events for + // playback. + FILE_PEPPER_FLASH_PLUGIN, // Full path to the bundled Pepper Flash plugin + // file. + FILE_PEPPER_FLASH_SYSTEM_PLUGIN, // Full path to the system version of the + // Pepper Flash plugin, downloadable from + // Adobe website. Querying this path might + // succeed no matter the file exists or not. + FILE_FLASH_SYSTEM_PLUGIN, // Full path to the system version of NPAPI + // Flash plugin, downloadable from Adobe + // website. Querying this path might succeed no + // matter the file exists or not. + FILE_NACL_PLUGIN, // Full path to the internal NaCl plugin file. + DIR_PNACL_BASE, // Full path to the base dir for PNaCl. + DIR_PNACL_COMPONENT, // Full path to the latest PNaCl version + // (subdir of DIR_PNACL_BASE). + DIR_COMPONENT_WIDEVINE_CDM, // Directory that contains component-updated + // Widevine CDM files. + FILE_WIDEVINE_CDM_ADAPTER, // Full path to the Widevine CDM adapter file. + FILE_RESOURCES_PACK, // Full path to the .pak file containing + // binary data (e.g., html files and images + // used by internal pages). + DIR_RESOURCES_EXTENSION, // Full path to extension resources. +#if defined(OS_CHROMEOS) + DIR_CHROMEOS_WALLPAPERS, // Directory where downloaded chromeos + // wallpapers reside. + DIR_CHROMEOS_WALLPAPER_THUMBNAILS, // Directory where downloaded chromeos + // wallpaper thumbnails reside. + DIR_CHROMEOS_CUSTOM_WALLPAPERS, // Directory where custom wallpapers + // reside. +#endif + DIR_SUPERVISED_USERS_DEFAULT_APPS, // Directory where installer places .crx + // files to be installed when managed user + // session starts. + DIR_SUPERVISED_USER_INSTALLED_WHITELISTS, // Directory where sanitized + // supervised user whitelists are + // installed. +#if defined(OS_LINUX) || (defined(OS_MACOSX) && !defined(OS_IOS)) + DIR_NATIVE_MESSAGING, // System directory where native messaging host + // manifest files are stored. + DIR_USER_NATIVE_MESSAGING, // Directory with Native Messaging Hosts + // installed per-user. +#endif +#if !defined(OS_ANDROID) + DIR_GLOBAL_GCM_STORE, // Directory where the global GCM instance + // stores its data. +#endif + + // Valid only in development environment; TODO(darin): move these + DIR_GEN_TEST_DATA, // Directory where generated test data resides. + DIR_TEST_DATA, // Directory where unit test data resides. + DIR_TEST_TOOLS, // Directory where unit test tools reside. +#if defined(OS_LINUX) + FILE_COMPONENT_FLASH_HINT, // A file in a known location that points to + // the component updated flash plugin. +#endif // defined(OS_LINUX) + + PATH_END +}; + +// Call once to register the provider for the path keys defined above. +void RegisterPathProvider(); + +// Get or set the invalid user data dir that was originally specified. +void SetInvalidSpecifiedUserDataDir(const base::FilePath& user_data_dir); +const base::FilePath& GetInvalidSpecifiedUserDataDir(); + +} // namespace chrome + +#endif // CHROME_COMMON_CHROME_PATHS_H__ diff --git a/chromium_src/chrome/common/chrome_paths_internal.h b/chromium_src/chrome/common/chrome_paths_internal.h new file mode 100644 index 000000000000..ae1cd623d773 --- /dev/null +++ b/chromium_src/chrome/common/chrome_paths_internal.h @@ -0,0 +1,112 @@ +// 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_COMMON_CHROME_PATHS_INTERNAL_H_ +#define CHROME_COMMON_CHROME_PATHS_INTERNAL_H_ + +#include + +#include "build/build_config.h" + +#if defined(OS_MACOSX) +#if defined(__OBJC__) +@class NSBundle; +#else +class NSBundle; +#endif +#endif + +namespace base { +class FilePath; +} + +namespace chrome { + +// Get the path to the user's data directory, regardless of whether +// DIR_USER_DATA has been overridden by a command-line option. +bool GetDefaultUserDataDirectory(base::FilePath* result); + +// Get the path to the user's cache directory. This is normally the +// same as the profile directory, but on Linux it can also be +// $XDG_CACHE_HOME and on Mac it can be under ~/Library/Caches. +// Note that the Chrome cache directories are actually subdirectories +// of this directory, with names like "Cache" and "Media Cache". +// This will always fill in |result| with a directory, sometimes +// just |profile_dir|. +void GetUserCacheDirectory(const base::FilePath& profile_dir, base::FilePath* result); + +// Get the path to the user's documents directory. +bool GetUserDocumentsDirectory(base::FilePath* result); + +#if defined(OS_WIN) || defined(OS_LINUX) +// Gets the path to a safe default download directory for a user. +bool GetUserDownloadsDirectorySafe(base::FilePath* result); +#endif + +// Get the path to the user's downloads directory. +bool GetUserDownloadsDirectory(base::FilePath* result); + +// Gets the path to the user's music directory. +bool GetUserMusicDirectory(base::FilePath* result); + +// Gets the path to the user's pictures directory. +bool GetUserPicturesDirectory(base::FilePath* result); + +// Gets the path to the user's videos directory. +bool GetUserVideosDirectory(base::FilePath* result); + +#if defined(OS_MACOSX) && !defined(OS_IOS) +// The "versioned directory" is a directory in the browser .app bundle. It +// contains the bulk of the application, except for the things that the system +// requires be located at spepcific locations. The versioned directory is +// in the .app at Contents/Versions/w.x.y.z. +base::FilePath GetVersionedDirectory(); + +// This overrides the directory returned by |GetVersionedDirectory()|, to be +// used when |GetVersionedDirectory()| can't automatically determine the proper +// location. This is the case when the browser didn't load itself but by, e.g., +// the app mode loader. This should be called before |ChromeMain()|. This takes +// ownership of the object |path| and the caller must not delete it. +void SetOverrideVersionedDirectory(const base::FilePath* path); + +// Most of the application is further contained within the framework. The +// framework bundle is located within the versioned directory at a specific +// path. The only components in the versioned directory not included in the +// framework are things that also depend on the framework, such as the helper +// app bundle. +base::FilePath GetFrameworkBundlePath(); + +// Get the local library directory. +bool GetLocalLibraryDirectory(base::FilePath* result); + +// Get the user library directory. +bool GetUserLibraryDirectory(base::FilePath* result); + +// Get the user applications directory. +bool GetUserApplicationsDirectory(base::FilePath* result); + +// Get the global Application Support directory (under /Library/). +bool GetGlobalApplicationSupportDirectory(base::FilePath* result); + +// Returns the NSBundle for the outer browser application, even when running +// inside the helper. In unbundled applications, such as tests, returns nil. +NSBundle* OuterAppBundle(); + +// Get the user data directory for the Chrome browser bundle at |bundle|. +// |bundle| should be the same value that would be returned from +[NSBundle +// mainBundle] if Chrome were launched normaly. This is used by app shims, +// which run from a bundle which isn't Chrome itself, but which need access to +// the user data directory to connect to a UNIX-domain socket therein. +// Returns false if there was a problem fetching the app data directory. +bool GetUserDataDirectoryForBrowserBundle(NSBundle* bundle, + base::FilePath* result); + +#endif // OS_MACOSX && !OS_IOS + +// Checks if the |process_type| has the rights to access the profile. +bool ProcessNeedsProfileDir(const std::string& process_type); + +} // namespace chrome + +#endif // CHROME_COMMON_CHROME_PATHS_INTERNAL_H_ diff --git a/chromium_src/chrome/common/chrome_paths_linux.cc b/chromium_src/chrome/common/chrome_paths_linux.cc new file mode 100644 index 000000000000..91348fec4808 --- /dev/null +++ b/chromium_src/chrome/common/chrome_paths_linux.cc @@ -0,0 +1,145 @@ +// 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/chrome_paths_internal.h" + +#include "base/base_paths.h" +#include "base/environment.h" +#include "base/files/file_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/nix/xdg_util.h" +#include "base/path_service.h" +#include "chrome/common/chrome_paths.h" + +namespace chrome { + +using base::nix::GetXDGDirectory; +using base::nix::GetXDGUserDirectory; +using base::nix::kDotConfigDir; +using base::nix::kXdgConfigHomeEnvVar; + +namespace { + +const char kDownloadsDir[] = "Downloads"; +const char kMusicDir[] = "Music"; +const char kPicturesDir[] = "Pictures"; +const char kVideosDir[] = "Videos"; + +// Generic function for GetUser{Music,Pictures,Video}Directory. +bool GetUserMediaDirectory(const std::string& xdg_name, + const std::string& fallback_name, + base::FilePath* result) { +#if defined(OS_CHROMEOS) + // No local media directories on CrOS. + return false; +#else + *result = GetXDGUserDirectory(xdg_name.c_str(), fallback_name.c_str()); + + base::FilePath home; + PathService::Get(base::DIR_HOME, &home); + if (*result != home) { + base::FilePath desktop; + if (!PathService::Get(base::DIR_USER_DESKTOP, &desktop)) + return false; + if (*result != desktop) { + return true; + } + } + + *result = home.Append(fallback_name); + return true; +#endif +} + +} // namespace + +// See http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html +// for a spec on where config files go. The net effect for most +// systems is we use ~/.config/chromium/ for Chromium and +// ~/.config/google-chrome/ for official builds. +// (This also helps us sidestep issues with other apps grabbing ~/.chromium .) +bool GetDefaultUserDataDirectory(base::FilePath* result) { + scoped_ptr env(base::Environment::Create()); + base::FilePath config_dir(GetXDGDirectory(env.get(), + kXdgConfigHomeEnvVar, + kDotConfigDir)); +#if defined(GOOGLE_CHROME_BUILD) + *result = config_dir.Append("google-chrome"); +#else + *result = config_dir.Append("chromium"); +#endif + return true; +} + +void GetUserCacheDirectory(const base::FilePath& profile_dir, + base::FilePath* result) { + // See http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + // for a spec on where cache files go. Our rule is: + // - if the user-data-dir in the standard place, + // use same subdirectory of the cache directory. + // (this maps ~/.config/google-chrome to ~/.cache/google-chrome as well + // as the same thing for ~/.config/chromium) + // - otherwise, use the profile dir directly. + + // Default value in cases where any of the following fails. + *result = profile_dir; + + scoped_ptr env(base::Environment::Create()); + + base::FilePath cache_dir; + if (!PathService::Get(base::DIR_CACHE, &cache_dir)) + return; + base::FilePath config_dir(GetXDGDirectory(env.get(), + kXdgConfigHomeEnvVar, + kDotConfigDir)); + + if (!config_dir.AppendRelativePath(profile_dir, &cache_dir)) + return; + + *result = cache_dir; +} + +bool GetUserDocumentsDirectory(base::FilePath* result) { + *result = GetXDGUserDirectory("DOCUMENTS", "Documents"); + return true; +} + +bool GetUserDownloadsDirectorySafe(base::FilePath* result) { + base::FilePath home; + PathService::Get(base::DIR_HOME, &home); + *result = home.Append(kDownloadsDir); + return true; +} + +bool GetUserDownloadsDirectory(base::FilePath* result) { + *result = GetXDGUserDirectory("DOWNLOAD", kDownloadsDir); + return true; +} + +// We respect the user's preferred pictures location, unless it is +// ~ or their desktop directory, in which case we default to ~/Music. +bool GetUserMusicDirectory(base::FilePath* result) { + return GetUserMediaDirectory("MUSIC", kMusicDir, result); +} + +// We respect the user's preferred pictures location, unless it is +// ~ or their desktop directory, in which case we default to ~/Pictures. +bool GetUserPicturesDirectory(base::FilePath* result) { + return GetUserMediaDirectory("PICTURES", kPicturesDir, result); +} + +// We respect the user's preferred pictures location, unless it is +// ~ or their desktop directory, in which case we default to ~/Videos. +bool GetUserVideosDirectory(base::FilePath* result) { + return GetUserMediaDirectory("VIDEOS", kVideosDir, result); +} + +bool ProcessNeedsProfileDir(const std::string& process_type) { + // For now we have no reason to forbid this on Linux as we don't + // have the roaming profile troubles there. Moreover the Linux breakpad needs + // profile dir access in all process if enabled on Linux. + return true; +} + +} // namespace chrome diff --git a/chromium_src/chrome/common/chrome_paths_mac.mm b/chromium_src/chrome/common/chrome_paths_mac.mm new file mode 100644 index 000000000000..4a4eb87dc254 --- /dev/null +++ b/chromium_src/chrome/common/chrome_paths_mac.mm @@ -0,0 +1,251 @@ +// 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/chrome_paths_internal.h" + +#import +#include + +#include + +#include "base/base_paths.h" +#include "base/logging.h" +#import "base/mac/foundation_util.h" +#import "base/mac/scoped_nsautorelease_pool.h" +#include "base/memory/scoped_ptr.h" +#include "base/path_service.h" +#include "chrome/common/chrome_constants.h" + +namespace { + +#if !defined(OS_IOS) +const base::FilePath* g_override_versioned_directory = NULL; + +// Return a retained (NOT autoreleased) NSBundle* as the internal +// implementation of chrome::OuterAppBundle(), which should be the only +// caller. +NSBundle* OuterAppBundleInternal() { + base::mac::ScopedNSAutoreleasePool pool; + + if (!base::mac::AmIBundled()) { + // If unbundled (as in a test), there's no app bundle. + return nil; + } + + if (!base::mac::IsBackgroundOnlyProcess()) { + // Shortcut: in the browser process, just return the main app bundle. + return [[NSBundle mainBundle] retain]; + } + + // From C.app/Contents/Versions/1.2.3.4, go up three steps to get to C.app. + base::FilePath versioned_dir = chrome::GetVersionedDirectory(); + base::FilePath outer_app_dir = versioned_dir.DirName().DirName().DirName(); + const char* outer_app_dir_c = outer_app_dir.value().c_str(); + NSString* outer_app_dir_ns = [NSString stringWithUTF8String:outer_app_dir_c]; + + return [[NSBundle bundleWithPath:outer_app_dir_ns] retain]; +} +#endif // !defined(OS_IOS) + +char* ProductDirNameForBundle(NSBundle* chrome_bundle) { + const char* product_dir_name = NULL; +#if !defined(OS_IOS) + base::mac::ScopedNSAutoreleasePool pool; + + NSString* product_dir_name_ns = + [chrome_bundle objectForInfoDictionaryKey:@"CrProductDirName"]; + product_dir_name = [product_dir_name_ns fileSystemRepresentation]; +#else + DCHECK(!chrome_bundle); +#endif + + if (!product_dir_name) { +#if defined(GOOGLE_CHROME_BUILD) + product_dir_name = "Google/Chrome"; +#else + product_dir_name = "Chromium"; +#endif + } + + // Leaked, but the only caller initializes a static with this result, so it + // only happens once, and that's OK. + return strdup(product_dir_name); +} + +// ProductDirName returns the name of the directory inside +// ~/Library/Application Support that should hold the product application +// data. This can be overridden by setting the CrProductDirName key in the +// outer browser .app's Info.plist. The default is "Google/Chrome" for +// officially-branded builds, and "Chromium" for unbranded builds. For the +// official canary channel, the Info.plist will have CrProductDirName set +// to "Google/Chrome Canary". +std::string ProductDirName() { +#if defined(OS_IOS) + static const char* product_dir_name = ProductDirNameForBundle(nil); +#else + // Use OuterAppBundle() to get the main app's bundle. This key needs to live + // in the main app's bundle because it will be set differently on the canary + // channel, and the autoupdate system dictates that there can be no + // differences between channels within the versioned directory. This would + // normally use base::mac::FrameworkBundle(), but that references the + // framework bundle within the versioned directory. Ordinarily, the profile + // should not be accessed from non-browser processes, but those processes do + // attempt to get the profile directory, so direct them to look in the outer + // browser .app's Info.plist for the CrProductDirName key. + static const char* product_dir_name = + ProductDirNameForBundle(chrome::OuterAppBundle()); +#endif + return std::string(product_dir_name); +} + +bool GetDefaultUserDataDirectoryForProduct(const std::string& product_dir, + base::FilePath* result) { + bool success = false; + if (result && PathService::Get(base::DIR_APP_DATA, result)) { + *result = result->Append(product_dir); + success = true; + } + return success; +} + +} // namespace + +namespace chrome { + +bool GetDefaultUserDataDirectory(base::FilePath* result) { + return GetDefaultUserDataDirectoryForProduct(ProductDirName(), result); +} + +bool GetUserDocumentsDirectory(base::FilePath* result) { + return base::mac::GetUserDirectory(NSDocumentDirectory, result); +} + +void GetUserCacheDirectory(const base::FilePath& profile_dir, + base::FilePath* result) { + // If the profile directory is under ~/Library/Application Support, + // use a suitable cache directory under ~/Library/Caches. For + // example, a profile directory of ~/Library/Application + // Support/Google/Chrome/MyProfileName would use the cache directory + // ~/Library/Caches/Google/Chrome/MyProfileName. + + // Default value in cases where any of the following fails. + *result = profile_dir; + + base::FilePath app_data_dir; + if (!PathService::Get(base::DIR_APP_DATA, &app_data_dir)) + return; + base::FilePath cache_dir; + if (!PathService::Get(base::DIR_CACHE, &cache_dir)) + return; + if (!app_data_dir.AppendRelativePath(profile_dir, &cache_dir)) + return; + + *result = cache_dir; +} + +bool GetUserDownloadsDirectory(base::FilePath* result) { + return base::mac::GetUserDirectory(NSDownloadsDirectory, result); +} + +bool GetUserMusicDirectory(base::FilePath* result) { + return base::mac::GetUserDirectory(NSMusicDirectory, result); +} + +bool GetUserPicturesDirectory(base::FilePath* result) { + return base::mac::GetUserDirectory(NSPicturesDirectory, result); +} + +bool GetUserVideosDirectory(base::FilePath* result) { + return base::mac::GetUserDirectory(NSMoviesDirectory, result); +} + +#if !defined(OS_IOS) + +base::FilePath GetVersionedDirectory() { + if (g_override_versioned_directory) + return *g_override_versioned_directory; + + // Start out with the path to the running executable. + base::FilePath path; + PathService::Get(base::FILE_EXE, &path); + + // One step up to MacOS, another to Contents. + path = path.DirName().DirName(); + DCHECK_EQ(path.BaseName().value(), "Contents"); + + if (base::mac::IsBackgroundOnlyProcess()) { + // path identifies the helper .app's Contents directory in the browser + // .app's versioned directory. Go up two steps to get to the browser + // .app's versioned directory. + path = path.DirName().DirName(); + } else { + // Go into the versioned directory. + path = path.Append("Frameworks"); + } + + return path; +} + +void SetOverrideVersionedDirectory(const base::FilePath* path) { + if (path != g_override_versioned_directory) { + delete g_override_versioned_directory; + g_override_versioned_directory = path; + } +} + +base::FilePath GetFrameworkBundlePath() { + // It's tempting to use +[NSBundle bundleWithIdentifier:], but it's really + // slow (about 30ms on 10.5 and 10.6), despite Apple's documentation stating + // that it may be more efficient than +bundleForClass:. +bundleForClass: + // itself takes 1-2ms. Getting an NSBundle from a path, on the other hand, + // essentially takes no time at all, at least when the bundle has already + // been loaded as it will have been in this case. The FilePath operations + // needed to compute the framework's path are also effectively free, so that + // is the approach that is used here. NSBundle is also documented as being + // not thread-safe, and thread safety may be a concern here. + + // The framework bundle is at a known path and name from the browser .app's + // versioned directory. + return GetVersionedDirectory().Append(kFrameworkName); +} + +bool GetLocalLibraryDirectory(base::FilePath* result) { + return base::mac::GetLocalDirectory(NSLibraryDirectory, result); +} + +bool GetUserLibraryDirectory(base::FilePath* result) { + return base::mac::GetUserDirectory(NSLibraryDirectory, result); +} + +bool GetUserApplicationsDirectory(base::FilePath* result) { + return base::mac::GetUserDirectory(NSApplicationDirectory, result); +} + +bool GetGlobalApplicationSupportDirectory(base::FilePath* result) { + return base::mac::GetLocalDirectory(NSApplicationSupportDirectory, result); +} + +NSBundle* OuterAppBundle() { + // Cache this. Foundation leaks it anyway, and this should be the only call + // to OuterAppBundleInternal(). + static NSBundle* bundle = OuterAppBundleInternal(); + return bundle; +} + +bool GetUserDataDirectoryForBrowserBundle(NSBundle* bundle, + base::FilePath* result) { + scoped_ptr + product_dir_name(ProductDirNameForBundle(bundle)); + return GetDefaultUserDataDirectoryForProduct(product_dir_name.get(), result); +} + +#endif // !defined(OS_IOS) + +bool ProcessNeedsProfileDir(const std::string& process_type) { + // For now we have no reason to forbid this on other MacOS as we don't + // have the roaming profile troubles there. + return true; +} + +} // namespace chrome diff --git a/chromium_src/chrome/common/chrome_paths_win.cc b/chromium_src/chrome/common/chrome_paths_win.cc new file mode 100644 index 000000000000..37f4ec2b05bd --- /dev/null +++ b/chromium_src/chrome/common/chrome_paths_win.cc @@ -0,0 +1,107 @@ +// 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/chrome_paths_internal.h" + +#include +#include +#include +#include +#include + +#include "base/files/file_path.h" +#include "base/path_service.h" +#include "base/win/metro.h" +#include "base/win/scoped_co_mem.h" +#include "chrome/common/chrome_constants.h" + +namespace chrome { + +namespace { + +// Generic function to call SHGetFolderPath(). +bool GetUserDirectory(int csidl_folder, base::FilePath* result) { + // We need to go compute the value. It would be nice to support paths + // with names longer than MAX_PATH, but the system functions don't seem + // to be designed for it either, with the exception of GetTempPath + // (but other things will surely break if the temp path is too long, + // so we don't bother handling it. + wchar_t path_buf[MAX_PATH]; + path_buf[0] = 0; + if (FAILED(SHGetFolderPath(NULL, csidl_folder, NULL, + SHGFP_TYPE_CURRENT, path_buf))) { + return false; + } + *result = base::FilePath(path_buf); + return true; +} + +} // namespace + +bool GetDefaultUserDataDirectory(base::FilePath* result) { + return PathService::Get(base::DIR_LOCAL_APP_DATA, result); +} + +void GetUserCacheDirectory(const base::FilePath& profile_dir, + base::FilePath* result) { + // This function does more complicated things on Mac/Linux. + *result = profile_dir; +} + +bool GetUserDocumentsDirectory(base::FilePath* result) { + return GetUserDirectory(CSIDL_MYDOCUMENTS, result); +} + +// Return a default path for downloads that is safe. +// We just use 'Downloads' under DIR_USER_DOCUMENTS. Localizing +// 'downloads' is not a good idea because Chrome's UI language +// can be changed. +bool GetUserDownloadsDirectorySafe(base::FilePath* result) { + if (!GetUserDocumentsDirectory(result)) + return false; + + *result = result->Append(L"Downloads"); + return true; +} + +// On Vista and higher, use the downloads known folder. Since it can be +// relocated to point to a "dangerous" folder, callers should validate that the +// returned path is not dangerous before using it. +bool GetUserDownloadsDirectory(base::FilePath* result) { + typedef HRESULT (WINAPI *GetKnownFolderPath)( + REFKNOWNFOLDERID, DWORD, HANDLE, PWSTR*); + GetKnownFolderPath f = reinterpret_cast( + GetProcAddress(GetModuleHandle(L"shell32.dll"), "SHGetKnownFolderPath")); + base::win::ScopedCoMem path_buf; + if (f && SUCCEEDED(f(FOLDERID_Downloads, 0, NULL, &path_buf))) { + *result = base::FilePath(std::wstring(path_buf)); + return true; + } + return GetUserDownloadsDirectorySafe(result); +} + +bool GetUserMusicDirectory(base::FilePath* result) { + return GetUserDirectory(CSIDL_MYMUSIC, result); +} + +bool GetUserPicturesDirectory(base::FilePath* result) { + return GetUserDirectory(CSIDL_MYPICTURES, result); +} + +bool GetUserVideosDirectory(base::FilePath* result) { + return GetUserDirectory(CSIDL_MYVIDEO, result); +} + +bool ProcessNeedsProfileDir(const std::string& process_type) { + // On windows we don't want subprocesses other than the browser process and + // service processes to be able to use the profile directory because if it + // lies on a network share the sandbox will prevent us from accessing it. + + if (process_type.empty()) + return true; + + return false; +} + +} // namespace chrome diff --git a/chromium_src/chrome/renderer/pepper/pepper_flash_renderer_host.cc b/chromium_src/chrome/renderer/pepper/pepper_flash_renderer_host.cc index fe5e28ebbeb8..66edd3f938ad 100644 --- a/chromium_src/chrome/renderer/pepper/pepper_flash_renderer_host.cc +++ b/chromium_src/chrome/renderer/pepper/pepper_flash_renderer_host.cc @@ -29,7 +29,6 @@ #include "third_party/skia/include/core/SkMatrix.h" #include "third_party/skia/include/core/SkPaint.h" #include "third_party/skia/include/core/SkPoint.h" -#include "third_party/skia/include/core/SkTemplates.h" #include "third_party/skia/include/core/SkTypeface.h" #include "ui/gfx/geometry/rect.h" #include "url/gurl.h" @@ -315,7 +314,7 @@ int32_t PepperFlashRendererHost::OnNavigate( bool rejected = false; while (header_iter.GetNext()) { std::string lower_case_header_name = - base::StringToLowerASCII(header_iter.name()); + base::ToLowerASCII(header_iter.name()); if (!IsSimpleHeader(lower_case_header_name, header_iter.values())) { rejected = true; 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 20ac1fdc9b4f..3bfe719a0c92 100644 --- a/chromium_src/chrome/renderer/printing/print_web_view_helper.cc +++ b/chromium_src/chrome/renderer/printing/print_web_view_helper.cc @@ -544,7 +544,6 @@ void PrepareFrameAndViewForPrint::CopySelection( // on the page). WebPreferences prefs = preferences; prefs.javascript_enabled = false; - prefs.java_enabled = false; blink::WebView* web_view = blink::WebView::create(this); owns_web_view_ = true; 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 dcd388fd1111..0b21de46995b 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 @@ -135,7 +135,8 @@ bool PrintWebViewHelper::PrintPagesNative(blink::WebFrame* frame, printed_page_params.page_size = page_size_in_dpi[i]; printed_page_params.content_area = content_area_in_dpi[i]; Send(new PrintHostMsg_DidPrintPage(routing_id(), printed_page_params)); - printed_page_params.metafile_data_handle = INVALID_HANDLE_VALUE; + // Send the rest of the pages with an invalid metafile handle. + printed_page_params.metafile_data_handle = base::SharedMemoryHandle(); } return true; } diff --git a/chromium_src/extensions/browser/app_window/size_constraints.cc b/chromium_src/extensions/browser/app_window/size_constraints.cc new file mode 100644 index 000000000000..6d248c16017a --- /dev/null +++ b/chromium_src/extensions/browser/app_window/size_constraints.cc @@ -0,0 +1,83 @@ +// 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 "extensions/browser/app_window/size_constraints.h" + +#include + +#include "ui/gfx/geometry/insets.h" + +namespace extensions { + +SizeConstraints::SizeConstraints() + : maximum_size_(kUnboundedSize, kUnboundedSize) {} + +SizeConstraints::SizeConstraints(const gfx::Size& min_size, + const gfx::Size& max_size) + : minimum_size_(min_size), maximum_size_(max_size) {} + +SizeConstraints::~SizeConstraints() {} + +// static +gfx::Size SizeConstraints::AddFrameToConstraints( + const gfx::Size& size_constraints, + const gfx::Insets& frame_insets) { + return gfx::Size( + size_constraints.width() == kUnboundedSize + ? kUnboundedSize + : size_constraints.width() + frame_insets.width(), + size_constraints.height() == kUnboundedSize + ? kUnboundedSize + : size_constraints.height() + frame_insets.height()); +} + +gfx::Size SizeConstraints::ClampSize(gfx::Size size) const { + const gfx::Size max_size = GetMaximumSize(); + if (max_size.width() != kUnboundedSize) + size.set_width(std::min(size.width(), max_size.width())); + if (max_size.height() != kUnboundedSize) + size.set_height(std::min(size.height(), max_size.height())); + size.SetToMax(GetMinimumSize()); + return size; +} + +bool SizeConstraints::HasMinimumSize() const { + const gfx::Size min_size = GetMinimumSize(); + return min_size.width() != kUnboundedSize || + min_size.height() != kUnboundedSize; +} + +bool SizeConstraints::HasMaximumSize() const { + const gfx::Size max_size = GetMaximumSize(); + return max_size.width() != kUnboundedSize || + max_size.height() != kUnboundedSize; +} + +bool SizeConstraints::HasFixedSize() const { + return !GetMinimumSize().IsEmpty() && GetMinimumSize() == GetMaximumSize(); +} + +gfx::Size SizeConstraints::GetMinimumSize() const { + return minimum_size_; +} + +gfx::Size SizeConstraints::GetMaximumSize() const { + return gfx::Size( + maximum_size_.width() == kUnboundedSize + ? kUnboundedSize + : std::max(maximum_size_.width(), minimum_size_.width()), + maximum_size_.height() == kUnboundedSize + ? kUnboundedSize + : std::max(maximum_size_.height(), minimum_size_.height())); +} + +void SizeConstraints::set_minimum_size(const gfx::Size& min_size) { + minimum_size_ = min_size; +} + +void SizeConstraints::set_maximum_size(const gfx::Size& max_size) { + maximum_size_ = max_size; +} + +} // namespace extensions diff --git a/chromium_src/extensions/browser/app_window/size_constraints.h b/chromium_src/extensions/browser/app_window/size_constraints.h new file mode 100644 index 000000000000..ecacf1e5eb13 --- /dev/null +++ b/chromium_src/extensions/browser/app_window/size_constraints.h @@ -0,0 +1,57 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_BROWSER_APP_WINDOW_SIZE_CONSTRAINTS_H_ +#define EXTENSIONS_BROWSER_APP_WINDOW_SIZE_CONSTRAINTS_H_ + +#include "ui/gfx/geometry/size.h" + +namespace gfx { +class Insets; +} + +namespace extensions { + +class SizeConstraints { + public: + // The value SizeConstraints uses to represent an unbounded width or height. + // This is an enum so that it can be declared inline here. + enum { kUnboundedSize = 0 }; + + SizeConstraints(); + SizeConstraints(const gfx::Size& min_size, const gfx::Size& max_size); + ~SizeConstraints(); + + // Adds frame insets to a size constraint. + static gfx::Size AddFrameToConstraints(const gfx::Size& size_constraints, + const gfx::Insets& frame_insets); + + // Returns the bounds with its size clamped to the min/max size. + gfx::Size ClampSize(gfx::Size size) const; + + // When gfx::Size is used as a min/max size, a zero represents an unbounded + // component. This method checks whether either component is specified. + // Note we can't use gfx::Size::IsEmpty as it returns true if either width + // or height is zero. + bool HasMinimumSize() const; + bool HasMaximumSize() const; + + // This returns true if all components are specified, and min and max are + // equal. + bool HasFixedSize() const; + + gfx::Size GetMaximumSize() const; + gfx::Size GetMinimumSize() const; + + void set_minimum_size(const gfx::Size& min_size); + void set_maximum_size(const gfx::Size& max_size); + + private: + gfx::Size minimum_size_; + gfx::Size maximum_size_; +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_APP_WINDOW_SIZE_CONSTRAINTS_H_ diff --git a/chromium_src/library_loaders/libspeechd.h b/chromium_src/library_loaders/libspeechd.h index 0d62f2c5da6e..f7b276287a41 100644 --- a/chromium_src/library_loaders/libspeechd.h +++ b/chromium_src/library_loaders/libspeechd.h @@ -33,6 +33,7 @@ class LibSpeechdLoader { 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; + decltype(&::spd_set_language) spd_set_language; private: diff --git a/chromium_src/library_loaders/libspeechd_loader.cc b/chromium_src/library_loaders/libspeechd_loader.cc index 606661000528..f09ea3ae861b 100644 --- a/chromium_src/library_loaders/libspeechd_loader.cc +++ b/chromium_src/library_loaders/libspeechd_loader.cc @@ -201,6 +201,19 @@ bool LibSpeechdLoader::Load(const std::string& library_name) { return false; } +#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN) + spd_set_language = + reinterpret_castspd_set_language)>( + dlsym(library_, "spd_set_language")); +#endif +#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED) + spd_set_language = &::spd_set_language; +#endif + if (!spd_set_language) { + CleanUp(true); + return false; + } + loaded_ = true; return true; @@ -227,5 +240,6 @@ void LibSpeechdLoader::CleanUp(bool unload) { spd_set_synthesis_voice = NULL; spd_list_modules = NULL; spd_set_output_module = NULL; + spd_set_language = NULL; } diff --git a/chromium_src/net/test/embedded_test_server/stream_listen_socket.cc b/chromium_src/net/test/embedded_test_server/stream_listen_socket.cc index 1056983a8e2f..897b23bbd56e 100644 --- a/chromium_src/net/test/embedded_test_server/stream_listen_socket.cc +++ b/chromium_src/net/test/embedded_test_server/stream_listen_socket.cc @@ -228,7 +228,7 @@ void StreamListenSocket::CloseSocket() { void StreamListenSocket::WatchSocket(WaitState state) { #if defined(OS_WIN) WSAEventSelect(socket_, socket_event_, FD_ACCEPT | FD_CLOSE | FD_READ); - watcher_.StartWatching(socket_event_, this); + watcher_.StartWatchingOnce(socket_event_, this); #elif defined(OS_POSIX) // Implicitly calls StartWatchingFileDescriptor(). base::MessageLoopForIO::current()->WatchFileDescriptor( @@ -264,7 +264,7 @@ void StreamListenSocket::OnObjectSignaled(HANDLE object) { return; } // The object was reset by WSAEnumNetworkEvents. Watch for the next signal. - watcher_.StartWatching(object, this); + watcher_.StartWatchingOnce(object, this); if (ev.lNetworkEvents == 0) { // Occasionally the event is set even though there is no new data. diff --git a/docs-translations/es/README.md b/docs-translations/es/README.md index 497cc4e05ccf..e69e76b1c4c7 100644 --- a/docs-translations/es/README.md +++ b/docs-translations/es/README.md @@ -1,32 +1,33 @@ ## Guías -* [Distribución de aplicaciones](tutorial/application-distribution.md) -* [Empaquetamiento de aplicaciones](tutorial/application-packaging.md) -* [Utilizando módulos nativos](tutorial/using-native-node-modules.md) -* [Depurando el proceso principal](tutorial/debugging-main-process.md) +* [Plataformas Soportadas](tutorial/supported-platforms.md) +* [Distribución de la Aplicación](tutorial/application-distribution.md) +* [Empaquetamiento de la Aplicación](tutorial/application-packaging.md) +* [Utilizando Módulos Node Nativos](tutorial/using-native-node-modules.md) +* [Depurando el Proceso Principal](tutorial/debugging-main-process.md) * [Utilizando Selenium y WebDriver](tutorial/using-selenium-and-webdriver.md) * [Extensión DevTools](tutorial/devtools-extension.md) -* [Utilizando el plugin pepper flash](tutorial/using-pepper-flash-plugin.md) +* [Utilizando el plugin Pepper Flash](tutorial/using-pepper-flash-plugin.md) ## Tutoriales -* [Introducción](../../docs/tutorial/quick-start.md) -* [Integración con el entorno de escritorio](../../docs/tutorial/desktop-environment-integration.md) -* [Detección del evento en línea/fuera de línea](../../docs/tutorial/online-offline-events.md) +* [Introducción](tutorial/quick-start.md) +* [Integración con el entorno de escritorio](tutorial/desktop-environment-integration.md) +* [Detección del evento en línea/fuera de línea](tutorial/online-offline-events.md) -## API +## Referencias a la API -* [Sinopsis](../../docs/api/synopsis.md) -* [Proceso](../../docs/api/process.md) -* [Parámetros CLI soportados (Chrome)](../../docs/api/chrome-command-line-switches.md) +* [Sinopsis](api/synopsis.md) +* [Proceso](api/process.md) +* [Parámetros CLI soportados (Chrome)](api/chrome-command-line-switches.md) -Elementos DOM customizados: +### Elementos DOM personalizados: * [Objeto `File`](../../docs/api/file-object.md) * [Etiqueta ``](../../docs/api/web-view-tag.md) * [Función `window.open`](../../docs/api/window-open.md) -Módulos del proceso principal: +### Módulos del Proceso Principal: * [app](../../docs/api/app.md) * [auto-updater](../../docs/api/auto-updater.md) @@ -34,21 +35,23 @@ Módulos del proceso principal: * [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) +* [ipc (proceso principal)](../../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) +* [web-contents](../../docs/api/web-contents.md) * [tray](../../docs/api/tray.md) -Módulos del renderer (página web): +### Módulos del proceso de renderizado (Página Web): -* [ipc (renderer)](../../docs/api/ipc-renderer.md) +* [ipc (renderizador)](../../docs/api/ipc-renderer.md) * [remote](../../docs/api/remote.md) * [web-frame](../../docs/api/web-frame.md) -Módulos de ambos procesos: +### Módulos de Ambos Procesos: * [clipboard](../../docs/api/clipboard.md) * [crash-reporter](../../docs/api/crash-reporter.md) @@ -58,11 +61,11 @@ Módulos de ambos procesos: ## 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) +* [Guía de Estilo](development/coding-style.md) +* [Estructura de los directorios del Código Fuente](development/source-code-directory-structure.md) +* [Diferencias Técnicas con NW.js (anteriormente conocido como node-webkit)](development/atom-shell-vs-node-webkit.md) +* [Repaso del Sistema de Compilación](development/build-system-overview.md) +* [Instrucciones de Compilación (Mac)](development/build-instructions-osx.md) +* [Instrucciones de Compilación (Windows)](../../development/build-instructions-windows.md) +* [Instrucciones de Compilación (Linux)](development/build-instructions-linux.md) +* [Configurando un Servidor de Símbolos en el depurador](../../development/setting-up-symbol-server.md) diff --git a/docs-translations/es/api/chrome-command-line-switches.md b/docs-translations/es/api/chrome-command-line-switches.md new file mode 100644 index 000000000000..56973ec06ad3 --- /dev/null +++ b/docs-translations/es/api/chrome-command-line-switches.md @@ -0,0 +1,119 @@ +# Parámetros CLI soportados (Chrome) + +Esta página lista las líneas de comandos usadas por el navegador Chrome que también son +soportadas por Electron. Puedes usar [app.commandLine.appendSwitch][append-switch] para +anexarlas en el script principal de tu aplicación antes de que el evento [ready][ready] del +módulo [app][app] sea emitido: + +```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` + +Establece el `path` del archivo de certificado del cliente. + +## --ignore-connections-limit=`domains` + +Ignora el límite de conexiones para la lista de `domains` separados por `,`. + +## --disable-http-cache + +Deshabilita la caché del disco para las peticiones HTTP. + +## --remote-debugging-port=`port` + +Habilita la depuración remota a través de HTTP en el puerto especificado. + +## --proxy-server=`address:port` + +Usa un servidor proxy especificado, que sobreescribe la configuración del sistema. +Este cambio solo afecta peticiones HTTP y HTTPS. + +## --proxy-pac-url=`url` + +Utiliza el script PAC en la `url` especificada. + +## --no-proxy-server + +No usa un servidor proxy y siempre establece conexiones directas. Anula cualquier +otra bandera de servidor proxy bandera que se pase. + +## --host-rules=`rules` + +Una lista separada por comas de `rules` (reglas) que controlan cómo se asignan los +nombres de host. + +Por ejemplo: + +* `MAP * 127.0.0.1` Obliga a todos los nombres de host a ser asignados a 127.0.0.1 +* `MAP *.google.com proxy` Obliga todos los subdominios google.com a resolverse con + "proxy". +* `MAP test.com [::1]:77` Obliga a resolver "test.com" con un bucle invertido de IPv6. + También obligará a que el puerto de la dirección respuesta sea 77. +* `MAP * baz, EXCLUDE www.google.com` Reasigna todo a "baz", excepto a "www.google.com". + +Estas asignaciones especifican el host final en una petición de red (Anfitrión de la conexión TCP +y de resolución de conexión directa, y el `CONNECT` en una conexión proxy HTTP, y el host final de +la conexión proxy `SOCKS`). + +## --host-resolver-rules=`rules` + +Como `--host-rules` pero estas `rules` solo se aplican al solucionador. + +[app]: app.md +[append-switch]: app.md#appcommandlineappendswitchswitch-value +[ready]: app.md#event-ready + +## --ignore-certificate-errors + +Ignora errores de certificado relacionados. + +## --ppapi-flash-path=`path` + +Asigna la ruta `path` del pepper flash plugin. + +## --ppapi-flash-version=`version` + +Asigna la versión `version` del pepper flash plugin. + +## --log-net-log=`path` + +Permite guardar y escribir eventos de registros de red en `path`. + +## --ssl-version-fallback-min=`version` + +Establece la versión mínima de SSL/TLS ("tls1", "tls1.1" o "tls1.2") que +el repliegue de TLC aceptará. + +## --enable-logging + +Imprime el registro de Chromium en consola. + +Este cambio no puede ser usado en `app.commandLine.appendSwitch` ya que se analiza antes de que la +aplicación del usuario esté cargada. + +## --v=`log_level` + +Da el máximo nivel activo de V-logging por defecto; 0 es el predeterminado. Valores positivos +son normalmente usados para los niveles de V-logging. + +Este modificador sólo funciona cuando también se pasa `--enable-logging`. + +## --vmodule=`pattern` + +Da los niveles máximos de V-logging por módulo para sobreescribir el valor dado por +`--v`. Ej. `my_module=2,foo*=3` cambiaría el nivel de registro para todo el código, +los archivos de origen `my_module.*` y `foo*.*`. + +Cualquier patrón que contiene un slash o un slash invertido será probado contra toda la ruta +y no sólo con el módulo. Ej. `*/foo/bar/*=2` cambiaría el nivel de registro para todo el código +en los archivos origen bajo un directorio `foo/bar`. + +Este modificador sólo funciona cuando también se pasa `--enable-logging`. diff --git a/docs-translations/es/api/process.md b/docs-translations/es/api/process.md new file mode 100644 index 000000000000..9e95ba988541 --- /dev/null +++ b/docs-translations/es/api/process.md @@ -0,0 +1,47 @@ +# process + +El objeto `process` en Electron tiene las siguientes diferencias con respecto +al node convencional: + +* `process.type` String - El tipo del proceso puede ser `browser` (ej. proceso + principal) o `renderer`. +* `process.versions['electron']` String - Versión de Electron. +* `process.versions['chrome']` String - Versión de Chromium. +* `process.resourcesPath` String - Ruta al código fuente JavaScript. + +## Events + +### Event: 'loaded' + +Se emite cuando Electron ha cargado su script de inicialización interna y +está comenzando a cargar la página web o el script principal. + +Puede ser usado por el script precargado para añadir de nuevo los símbolos globales +de Node eliminados, al alcance global cuando la integración de Node está apagada: + +```js +// preload.js +var _setImmediate = setImmediate; +var _clearImmediate = clearImmediate; +process.once('loaded', function() { + global.setImmediate = _setImmediate; + global.clearImmediate = _clearImmediate; +}); +``` + +## Methods + +El objeto `process` tiene los siguientes métodos: + +### `process.hang` + +Interrumpe el hilo principal del proceso actual. + + +### process.setFdLimit(maxDescriptors) _OS X_ _Linux_ + +* `maxDescriptors` Integer + +Establece el límite dinámico del descriptor del archivo en `maxDescriptors` +o en el límite estricto del Sistema Operativo, el que sea menor para el +proceso actual. diff --git a/docs-translations/es/api/synopsis.md b/docs-translations/es/api/synopsis.md new file mode 100644 index 000000000000..534fafcf2f7c --- /dev/null +++ b/docs-translations/es/api/synopsis.md @@ -0,0 +1,47 @@ +# Synopsis + +Todos los [Módulos integrados de Node.js](http://nodejs.org/api/) se encuentran +disponibles en Electron y módulos de terceros son támbien totalmente compatibles +(incluyendo los [módulos nativos](../tutorial/using-native-node-modules.md)). + +Electron también provee algunos módulos integrados adicionales para desarrollar +aplicaciones nativas de escritorio. Algunos módulos sólo se encuentran disponibles +en el proceso principal, algunos sólo en el proceso renderer (página web), y +algunos pueden ser usados en ambos procesos. + +La regla básica es: Si un módulo es +[GUI](https://es.wikipedia.org/wiki/Interfaz_gráfica_de_usuario) o de bajo nivel, +entonces solo estará disponible en el proceso principal. Necesitas familiarizarte +con el concepto de [scripts para proceso principal vs scripts para proceso renderer] +(../tutorial/quick-start.md#the-main-process) para ser capaz de usar esos módulos. + +El script del proceso principal es como un script normal de 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'); +}); +``` + +El proceso renderer no es diferente de una página web normal, excepto por la +capacidad extra de utilizar módulos de node: + +```html + + + + + + +``` + +Para ejecutar tu aplicación, lee [Ejecutar la aplicación](../tutorial/quick-start.md#run-your-app). diff --git a/docs-translations/es/development/atom-shell-vs-node-webkit.md b/docs-translations/es/development/atom-shell-vs-node-webkit.md new file mode 100644 index 000000000000..434e67658074 --- /dev/null +++ b/docs-translations/es/development/atom-shell-vs-node-webkit.md @@ -0,0 +1,34 @@ +#Diferencias Técnicas entre Electron y NW.js (anteriormente conocido como node-webkit) + +**Nota:Electron se llamaba antes Atom Shell.** + +Como NW.js, Electron proporciona una plataforma para escribir aplicaciones de escritorio con JavaScript y HTML y tiene la integración de nodo para permitir el acceso al sistema de bajo nivel de las páginas web. + +Pero también hay diferencias fundamentales entre los dos proyectos que hacen a Electron un producto totalmente independiente de NW.js: + +**1. Ingreso a la aplicación** + +En NW.js el principal punto de ingreso de una aplicación es una página web. Usted especifica una página principal de URL en el `package.json` y se abre en una ventana del navegador como ventana principal de la aplicación. + +En Electron, el punto de ingreso es un script de JavaScript. En lugar de proporcionar una dirección URL directamente, usted crea manualmente una ventana del navegador y carga un archivo HTML utilizando la API. También es necesario escuchar a los eventos de la ventana para decidir cuándo salir de la aplicación. + +Electron funciona más como el tiempo de ejecución(Runtime) de Node.js. Las Api's de Electron son de bajo nivel asi que puede usarlo para las pruebas del navegador en lugar de usar [PhantomJS.](http://phantomjs.org/) + +**2.Construir un sistema** + +Con el fin de evitar la complejidad de la construcción de todo Chromium, Electron utiliza `libchromiumcontent` para acceder a al contenido Chromium's API. `libchromiumcontent` es solo una liberia compartida que incluye el módulo de contenido de Chromium y todas sus dependencias. Los usuarios no necesitan una máquina potente para construir con Electron. + +**3.Integración de Node** + +In NW.js, the Node integration in web pages requires patching Chromium to 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 code for how that was done. + +En NW.js, la integración de Node en las páginas web requiere parchear Chromium para que funcione, mientras que en Electron elegimos una manera diferente para integrar el cilco libuv con cada ciclo de mensaje de las plataformas para evitar el hacking en Chromium. Ver el código [`node_bindings`][node-bindings] de cómo se hizo. + + +**4. Multi-contexto** + +Si usted es un usuario experimentado NW.js, usted debe estar familiarizado con el concepto de contexto Node y el contexto web. Estos conceptos fueron inventados debido a la forma cómo se implementó NW.js. + +Mediante el uso de la característica [multi-contexto](http://strongloop.com/strongblog/whats-new-node-js-v0-12-multiple-context-execution/) de Node, Electron no introduce un nuevo contexto JavaScript en páginas web.Resultados de búsqueda + +[node-bindings]: https://github.com/atom/electron/tree/master/atom/common diff --git a/docs-translations/es/development/build-instructions-linux.md b/docs-translations/es/development/build-instructions-linux.md new file mode 100644 index 000000000000..28ad828fa709 --- /dev/null +++ b/docs-translations/es/development/build-instructions-linux.md @@ -0,0 +1,96 @@ +#Instrucciones de Compilación (Linux) + +Siga las siguientes pautas para la construcción de Electron en Linux. +#Requisitos previos + + * Python 2.7.x. Algunas distribuciones como CentOS siguen utilizando Python 2.6.x por lo que puede que tenga que comprobar su versión de Python con `Python -V`. + * Node.js v0.12.x. Hay varias formas de instalar Node. Puede descargar el código fuente de Node.js y compilar desde las fuentes. Si lo hace, permite la instalación de Node en el directorio personal como usuario estándar. O intentar de repositorios como NodeSource. + * Clang 3.4 o mayor. + * Cabeceras de desarrollo de GTK + y libnotify. + +En Ubuntu, instalar las siguientes bibliotecas: + +`$ 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` + +En Fedora, instale las siguientes bibliotecas: + +`$ 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` + +Otras distribuciones pueden ofrecer paquetes similares para la instalación, a través de gestores de paquetes como el pacman. O puede compilarlo a partir del código fuente. + +#Si utiliza máquinas virtuales para la construcción + +Si usted planea construir Electron en una máquina virtual, necesitará un dispositivo de al menos 25 gigabytes de tamaño. + +#Obteniendo el codigo + +`$ git clone https://github.com/atom/electron.git` + +#Bootstrapping (Arranque) + +The bootstrap script will download all necessary build dependencies and create 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. + +El script de bootstrap descargará todas las dependencias necesarias para construcción y creara los archivos del proyecto de construcción. Debe tener Python 2.7.x para que la secuencia de comandos tenga éxito. La descarga de determinados archivos puede llevar mucho tiempo. Nótese que estamos usando`ninja` para construir Electron por lo que no hay `Makefile` generado. + + $ cd electron + $ ./script/bootstrap.py -v + +#compilación cruzada + +Si usted quiere construir para un `arm` objetivo también debe instalar las siguientes dependencias: + +`$ 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: +cruzar y compilar para `arm` o `ia32` objetivos, debe pasar el parámetro `--target_arch` al script `bootstrap.py`: +`$ ./script/bootstrap.py -v --target_arch=arm` + +#Construcción + +Si a usted le gustaría construir dos objetivos de `Release` y `Debug`: + + `$ ./script/build.py` + + +Este script causará que el ejecutable de Electron se muy grande para ser colocado en el directorio `out / R`. El tamaño del archivo es de más de 1,3 gigabytes. Esto sucede porque el binario de destino lanzamiento contiene los símbolos de depuración. Para reducir el tamaño de archivo, ejecute el script `create-dist.py`: + +`$ ./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. + +Esto pondrá una distribución a trabajar con tamaños de archivo mucho más pequeños en el directorio `dist`. Después de ejecutar el script create-dist.py, es posible que desee quitar el binario 1.3+ gigabyte que todavía está en `out/R`. + +También se puede construir sólo el objetivo `Debug`: +`$ ./script/build.py -c D` + +Después de la construcción está hecho, usted puede encontrar el `Electron` de depuración binario bajo `out / D`. + +#Limpieza + +Para limpiar los archivos de creación: + +`$ ./script/clean.py` + +#Solución de problemas +Asegúrese de que ha instalado todas las dependencias de construcción. + +#Error al cargar bibliotecas compartidas: libtinfo.so.5 + +Prebulit clang will try to link to libtinfo.so.5. Depending on the host architecture, symlink to appropriate libncurses: +preconstruir `clang` intentará enlazar a `libtinfo.so.5`. Dependiendo de la arquitectura anfitrión, enlace simbólico apropiado a `libncurses` : + +`$ sudo ln -s /usr/lib/libncurses.so.5 /usr/lib/libtinfo.so.5` + +#Pruebas +Pon a prueba tus cambios que ajustan al estilo de codificación proyecto mediante: + +`$ ./script/cpplint.py` + +prueba de funcionalidad utilizando: + +`$ ./script/test.py` diff --git a/docs-translations/es/development/build-instructions-osx.md b/docs-translations/es/development/build-instructions-osx.md new file mode 100644 index 000000000000..6e2d7b7f392e --- /dev/null +++ b/docs-translations/es/development/build-instructions-osx.md @@ -0,0 +1,48 @@ +#Instrucciones de Compilación (Mac) +Siga las siguientes pautas para la construcción de Electron en OS X. + +#Requisitos previos + + `OS X >= 10.8` + `Xcode >= 5.1` + `node.js (external)` + + +Si está utilizando Python descargado de Homebrew, también es necesario instalar los siguientes módulos de python: + `pyobjc` + +#Obtener el Código + +`$ git clone https://github.com/atom/electron.git` + +#Bootstrapping (arranque) + +The bootstrap script will download all necessary build dependencies and create the build project files. Notice that we're using ninja to build Electron so there is no Xcode project generated. + +El script de bootstrap descargará todas las dependencias de construcción necesarias y creara los archivos del proyecto de compilación. notemos que estamos usando `ninja` para construir Electron por lo que no hay un proyecto de Xcode generado. + +`$ cd electron` +`$ ./script/bootstrap.py -v` + +#Construcción +Construir ambos objetivos de `Release` y `Debug`: + +`$ ./script/build.py` + +También sólo se puede construir el objetivo de `Debug`: +`$ ./script/build.py -c D` + +Después de la construcción está hecho, usted puede encontrar `Electron.app` bajo `out / D.` + +#Soporte de 32bit + +Electron sólo puede construirse para un objetivo de 64 bits en OS X y no hay un plan para apoyar a 32 bit OS X en el futuro. + +#Pruebas + +Pon a prueba tus cambios ajustandose al estilo de codificación del proyecto mediante: +`$ ./script/cpplint.py` + +Prueba la funcionalidad usando: + +`$ ./script/test.py` diff --git a/docs-translations/es/development/build-system-overview.md b/docs-translations/es/development/build-system-overview.md new file mode 100644 index 000000000000..1e6a42da84bf --- /dev/null +++ b/docs-translations/es/development/build-system-overview.md @@ -0,0 +1,35 @@ +#Repaso del Sistema de construcción +Electron utiliza `gyp` para la generación de proyectos y` ninja` para la contrucción. Las Configuraciones del proyecto se pueden encontrar en los archivos `.gypi` y `.gyp `. + +#Archivos Gyp +los siguientes archivos `gyp` contienen las principales reglas para la contrucción en electron: + + * `atom.gyp` define en si como se compila en Electron. + * `common.gypi` ajusta las configuraciones de generación de Node para construir junto con Chromium. + * `vendor/brightray/brightray.gyp` define cómo se construye `brightray` e incluye las configuraciones predeterminadas para linkear con Chromium. + * `vendor/brightray/brightray.gypi` incluye configuraciones de generación generales sobre la construcción. + +#Construir un componente +Desde Chromium es un proyecto bastante largo, la etapa de enlace final puede tomar pocos minutos, lo que hace que sea difícil para el desarrollo. Con el fin de resolver esto, Chromium introdujo el "componente de construcción", que se basa en construir cada componente como una libreria compartida por separado, haciendo que se enlace muy rápido, pero sacrificando el tamaño del archivo y el rendimiento. + +En Electron tomamos un enfoque muy similar: para versiones de `Debug` (depuración), el binario será linkeado a una versión de la libreria compartida de los componentes de Chromium para lograr un tiempo de enlace rápido; para versiones de `Release` (lanzamiento), el binario será linkeado a las versiones de las librerias estáticas, por lo que puede tener es posible tener un mejor tamaño binario y rendimiento. + +#Bootstrapping minimo (minimo arranque) +Todos los binarios pre-compilados de Chromium (`libchromiumcontent`) son descargados al ejecutar el script de arranque. Por defecto ambas librerias estáticas y librerias compartidas se descargarán y el tamaño final debe estar entre 800 MB y 2 GB dependiendo de la plataforma. + +Por defecto, `libchromiumcontent` se descarga de Amazon Web Services. Si se establece la variable de entorno `LIBCHROMIUMCONTENT_MIRROR`, el bootstrap script se descargará de ella. `libchromiumcontent-qiniu-mirror` es un espejo para el` libchromiumcontent`. Si tiene problemas para acceder a AWS, puede cambiar la dirección de descarga a la misma a través de `exportación LIBCHROMIUMCONTENT_MIRROR = http: // 7xk3d2.dl1.z0.glb.clouddn.com /` + +Si sólo desea construir en Electron rápidamente para pruebas o desarrollo, puede descargar sólo las versiones de librerias compartidas pasando el parámetro `--dev`: + +`$ ./script/bootstrap.py --dev` +`$ ./script/build.py -c D` + +#generación de proyecto de dos frases +Los enlaces de Electron con diferentes conjuntos de librerias en versiones `Release` y `Debug`. `gyp`, sin embargo, no es compatible con la configuración de los diferentes ajustes de enlace para diferentes configuraciones. + +Para evitar que Electron utilice una variable de `gyp` `libchromiumcontent_component` para controlar qué configuraciones de enlace usar y sólo generar un objetivo cuando se ejecute `gyp`. + +#Nombres de destino +A diferencia de la mayoría de los proyectos que utilizan `Release` y `Debug` como nombres de destino, Electron utiliza `R` y `D` en su lugar. Esto se debe a `gyp` bloquea aleatoriamente si sólo hay una configuración de `Release` o `Debug` definidas, y Electron sólo tiene que generar un objetivo a la vez como se ha indicado anteriormente. + +Esto sólo afecta a los desarrolladores, si usted está construyendo Electron para rebranding no se ven afectados. diff --git a/docs-translations/es/development/coding-style.md b/docs-translations/es/development/coding-style.md new file mode 100644 index 000000000000..de02a33a86d3 --- /dev/null +++ b/docs-translations/es/development/coding-style.md @@ -0,0 +1,37 @@ +# Guía de estilo de código + +Esta es la guía de estilo de código para Electron. + +## C++ y Python + +Para C++ y Python, nosotros seguimos la [guía de estilo](http://www.chromium.org/developers/coding-style) de Chromium. +Además hay un script `script/cpplint.py` para verificar si todos los archivos +siguen el estilo. + +La versión de Python que estamos usando ahora es Python 2.7. + +El código C++ usa muchas abstracciones y tipos de Chromium, por eso +se recomienda familiarizarse con ellos. Un buen lugar para iniciar es +el documento de Chromium sobre [Abstracciones importantes y estructras de datos](https://www.chromium.org/developers/coding-style/important-abstractions-and-data-structures). El documento menciona algunos tipos especiales, tipos por alcance (que +automaticamente liberan su memoria cuando salen de su alcance), mecanismos de +registro de eventos, etcétera. + +## CoffeeScript + +Para CoffeeScript, nosotros seguimos la [guía de estilo](https://github.com/styleguide/javascript) de Github y también las +siguientes reglas: + +* Los archivos **NO** deberían terminar con una nueva línea, por que se busca + seguir los estilos que usa Google. +* Los nombres de los archivos debén estar concatenados con `-` en vez de `_`, + por ejemplo `nombre-de-archivo.coffee` en vez de `nombre_de_archivo.coffee`, + esto es por que en [github/atom](https://github.com/github/atom) + los nombres de los módulos usualmente estan en la forma `nombre-de-modulo`. + Esta regla aplica únicamente a los archivos `.coffee`. + +## Nombres de las API + +Al crear una nueva API, nosotros deberíamos preferir usar metodos `get` y `set` +en vez de usar el estilo de jQuery que utiliza una sola función. Por ejemplo, +se prefiere `.getText()` y `.setText()` por sobre `.text([text])`. Hay una +[discusión](https://github.com/atom/electron/issues/46) sobre esto. diff --git a/docs-translations/es/development/source-code-directory-structure.md b/docs-translations/es/development/source-code-directory-structure.md new file mode 100644 index 000000000000..cc738d241277 --- /dev/null +++ b/docs-translations/es/development/source-code-directory-structure.md @@ -0,0 +1,62 @@ +# Estructura de los directorios del código fuente + +El código fuente de electron es separado en pocas partes, en su mayoría +siguiendo las especificaciones para separar archivos que usa Chromium. + +Quizá necesites familiarizarte con la [arquitectura multiprocesos](http://dev.chromium.org/developers/design-documents/multi-process-architecture) de Chromium para comprender mejor el código fuente. + +## Estructura del código fuente + +``` +Electron +├──atom - Código fuente de Electron. +| ├── app - Código de arranque. +| ├── browser - La interfaz incluyendo la ventana principal, UI, +| | y todas las cosas del proceso principal. Este le habla al renderizador +| | para manejar las páginas web. +| | ├── lib - Código Javascript para inicializar el proceso principal. +| | ├── ui - Implementaciones de UI para distintas plataformas. +| | | ├── cocoa - Código fuente específico para Cocoa. +| | | ├── gtk - Código fuente específico para GTK+. +| | | └── win - Código fuente específico para Windows GUI. +| | ├── default_app - La página por defecto para mostrar cuando Electron +| | | es iniciado sin proveer una app. +| | ├── api - La implementación de las APIs para el proceso principal. +| | | └── lib - Código Javascript parte de la implementación de la API. +| | ├── net - Código relacionado a la red. +| | ├── mac - Código fuente de Objective-C específico para Mac. +| | └── resources - Iconos, archivos específicos de plataforma, etc. +| ├── renderer - Código que se ejecuta en el proceso de renderizado. +| | ├── lib - Código Javascript del proceso de inicio del renderizador. +| | └── api - La implementación de las APIs para el proceso de renderizado. +| | └── lib - Código Javascript parte de la implementación de la API. +| └── common - Código que se utiliza en ambos procesos, el principal y el de +| renderizado. Incluye algunas funciones de utilidad y código para integrar +| el ciclo de mensajes de Node en el ciclo de mensajes de Chromium. +| ├── lib - Código Javascript común para la inicialización. +| └── api - La implementación de APIs comunes, y los fundamentos de +| los módulos integrados de Electron. +| └── lib - Código Javascript parte de la implementación de la API. +├── chromium_src - Código fuente copiado de Chromium. +├── docs - Documentación. +├── spec - Pruebas automaticas. +├── atom.gyp - Reglas de compilado de Electron. +└── common.gypi - Configuración específica para compilar y reglas + de empaquetado para otros componentes como `node` y `breakpad`. +``` + +## Estructura de otros directorios + +* **script** - Scripts usados para propositos de desarrollo + como compilar, empaquetar, realizar pruebas, etc. +* **tools** - Scripts de ayuda usados por los archivos gyp, contrario a la + carpeta `scripts`, estos scripts nunca deberían ser llamados por los usuarios. +* **vendor** - Código fuente de dependencias externas, no usamos `third_party` + como nombre por que se podría confundir con el mismo directorio + en las carpetas del código fuente de Chromium. +* **node_modules** - Módulos de node usados para la compilación. +* **out** - Directorio temporal de salida usado por `ninja`. +* **dist** - Directorio temporal creado por `script/create-dist.py` cuando + se esta creando una distribución. +* **external_binaries** - Binarios descargados de frameworks externos que no + soportan la compilación con `gyp`. diff --git a/docs-translations/es/styleguide.md b/docs-translations/es/styleguide.md new file mode 100644 index 000000000000..4948035ffffe --- /dev/null +++ b/docs-translations/es/styleguide.md @@ -0,0 +1,100 @@ +# Gúia de estilo de Electron + +Encuentra el apartado correcto para cada tarea: [leer la documentación de Electron](#reading-electron-documentation) +o [escribir documentación para Electron](#writing-electron-documentation). + +## Escribir Documentación para Electron + +Estas son las maneras en las que construimos la documentación de Electron. + +- Máximo un título `h1` por página. +- Utilizar `bash` en lugar de `cmd` en los bloques de código (por el resaltado + de sintaxis). +- Los títulos `h1` en el documento deben corresponder al nombre del objeto + (ej. `browser-window` → `BrowserWindow`). + - Archivos separados por guiones, mas sin embargo, es correcto. +- No subtítulos seguidos por otros subtítulos, añadir por lo menos un enunciado + de descripción. +- Métodos de cabecera son delimitados con apóstrofes: `código`. +- Cabeceras de Eventos son delimitados con 'comillas' simples. +- No generar listas de mas de dos niveles (debido al renderizador de Markdown + desafortunadamente). +- Agregar títulos de sección: Eventos, Métodos de Clases y Métodos de Instancia. +- Utilizar 'deberá' en lugar de 'debería' al describir resultados. +- Eventos y Métodos son cabeceras `h3`. +- Argumentos opcionales escritos como `function (required[, optional])`. +- Argumentos opcionales son denotados cuando se llaman en listas. +- Delimitador de línea de 80-columnas. +- Métodos específicos de Plataformas son denotados en itálicas seguidas por la cabecera del método. + - ```### `method(foo, bar)` _OS X_``` +- Preferir 'en el ___ proceso' en lugar de 'sobre el' + +### Traducciones de la Documentación + +Traducciones de documentos de Electron se encuentran dentro del folder +`docs-translations`. + +Para agregar otro set (o un set parcial): + +- Crear un subdirectorio nombrado igual a la abreviación del lenguaje. +- Dentro de ese subdirectorio, duplicar el directorio de `docs`, manteniendo los + mismos nombres de directorios y archivos. +- Traducir los archivos. +- Actualizar el `README.md` dentro del subdirectorio del lenguaje apuntando a + los archivos que has traducido. +- Agregar un enlace al folder de tu traducción en la sección principal Electron +[README](https://github.com/atom/electron#documentation-translations). + +## Leyendo la Documentación de Electron + +Estos son algunos consejos para entender la sintaxis de la documentación de +Electron. + +### Métodos + +Un ejemplo de la documentación del [método](https://developer.mozilla.org/en-US/docs/Glossary/Method): + +--- + +`methodName(required[, optional]))` + +* `require` String, **required** +* `optional` Integer + +--- + +El nombre del método es seguido por los argumentos que recibe. Argumentos +opcionales son denotados por corchetes rodeados por el argumento opcional y la +coma requerida si el argumento opcional fuera seguido por otro argumento. + +Debajo del método se encuentra más información detallada de cada uno de los +argumentos. El tipo de argumento es denotado por los tipos comúnes: +[`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) +o un tipo personalizado como el [`webContent`](api/web-content.md) de Electron. + +### Eventos + +Un ejemplo de documentación del [evento](https://developer.mozilla.org/en-US/docs/Web/API/Event): + +--- + +Event: 'wake-up' + +Returns: + +* `time` String + +--- + +El evento es una cadena que es utilizada luego de un método observador `.on`. Si +regresa un valor, él y su tipo son denotados abajo. Si se estaba a la escucha y +respondió a este evento se debería ver así: + +```javascript +Alarm.on('wake-up', function(time) { + console.log(time) +}) +``` diff --git a/docs-translations/es/tutorial/application-distribution.md b/docs-translations/es/tutorial/application-distribution.md index 644da8c3c87b..c957332dd862 100644 --- a/docs-translations/es/tutorial/application-distribution.md +++ b/docs-translations/es/tutorial/application-distribution.md @@ -1,10 +1,11 @@ -# Distribución de aplicaciones +# Distribución de la Aplicación -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/`): +Para distribuir tu aplicación con Electron, el directorio que contiene la +aplicación deberá llamarse `app`, y ser colocado debajo del directorio de +recursos de Electron (en OSX es `Electron.app/Contents/Resources/`, en Linux y +Windows es `resources/`), de esta forma: -En OSX: +En OS X: ```text electron/Electron.app/Contents/Resources/app/ @@ -22,18 +23,19 @@ electron/resources/app └── 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. +Luego ejecutar `Electron.app` (o `electron` en Linux, `electron.exe` en Windows), +y Electron será iniciado como tu aplicación. El directorio `electron` será +entonces tu distribución que recibirán los usuarios finales. -## Empaquetando tu aplicación como un archivo +## Empaquetando tu aplicación en 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. +Además de distribuir tu aplicación al copiar todos los archivos de código fuente, +también puedes empaquetar tu aplicación como un archivo [asar](https://github.com/atom/asar) +y de esta forma evitar exponer 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. +Para utilizar un archivo `asar` en reemplazo del directorio `app`, debes de +renombrar el archivo a `app.asar`, y colocarlo por debajo el directorio de recursos +de Electron (ver en seguida), Electron intentará leer el archivo y arrancar desde el. En OS X: @@ -49,30 +51,33 @@ electron/resources/ └── app.asar ``` -Más detalles en [Empaquetamiento de aplicaciones](application-packaging-es.md). +Más detalles en [Empaquetado de Aplicaciones](application-packaging.md). -## Rebranding con binarios descargados +## Redefinición con Binarios Descargados -Luego de empaquetar tu aplicación con Electron, podría ser útil agregar tu marca -antes de realizar la distribución. +Luego de empaquetar tu aplicación en Electron, querrás redefinir Electron antes +de distribuirlo a los usuarios. ### 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). +Puedes renombrar `electron.exe` a cualquier nombre que desees, y editar su ícono +y otra información con herramientas como [rcedit](https://github.com/atom/rcedit) +o [ResEdit](http://www.resedit.net). -### OS X +### OSX -Puedes renombrar `Electron.app` a cualquier nombre que desees. También debes modificar los campos -`CFBundleDisplayName`, `CFBundleIdentifier` y `CFBundleName` en los siguientes archivos: +Puedes renombrar `Electron.app` a cualquier nombre que desees, y tendrás que +renombrar 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. +También puedes renombrar el helper de la aplicación para evitar que aparezca +como `Electron Helper` en el Monitor de Actividades. Pero asegurate de renombrar +el nombre de archivo del ejecutable. -La estructura de una aplicación renombrada sería así: +La estructura de una aplicación renombrada será: ``` MyApp.app/Contents @@ -98,17 +103,19 @@ MyApp.app/Contents Puedes renombrar el ejectuable `electron` a cualquier nombre que desees. -## Rebranding desde el código fuente de Electron +## Redefinición mediante la recompilación de Electron desde el código fuente -También es posible agregar tu marca a Electron mediante un build personalizado. -Para realizar esto debes modificar el archivo `atom.gyp`. +También es posible redefinir Electron cambiando el nombre del producto y +compilandolo desde sus fuentes. Para realizar esto necesitas modificar el +archivo `atom.gyp` y realizar una compilación desde cero. ### 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: - +La modificación a mano del código de Electron y su compilación puede resultar +complicada, por lo cual se ha generado una tarea Grunt para manejar esto de +forma automaticamente: [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. +Esta tarea se encargará de modificar el archivo `.gyp`, compilar el código desde +las fuentes, y luego reconstruir los módulos nativos de la aplicación para que +coincidan con el nuevo nombre del ejecutable. diff --git a/docs-translations/es/tutorial/application-packaging.md b/docs-translations/es/tutorial/application-packaging.md index 56698c1aaecb..c0cca7ecd916 100644 --- a/docs-translations/es/tutorial/application-packaging.md +++ b/docs-translations/es/tutorial/application-packaging.md @@ -70,7 +70,7 @@ También puedes mostrar una página web contenida en un `asar` utilizando `Brows ```javascript var BrowserWindow = require('browser-window'); var win = new BrowserWindow({width: 800, height: 600}); -win.loadUrl('file:///path/to/example.asar/static/index.html'); +win.loadURL('file:///path/to/example.asar/static/index.html'); ``` ### API Web diff --git a/docs-translations/es/tutorial/online-offline-events.md b/docs-translations/es/tutorial/online-offline-events.md index 0e43f9b16109..450702e2f84c 100644 --- a/docs-translations/es/tutorial/online-offline-events.md +++ b/docs-translations/es/tutorial/online-offline-events.md @@ -12,7 +12,7 @@ var onlineStatusWindow; app.on('ready', function() { onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); + onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); }); ``` @@ -50,7 +50,7 @@ var onlineStatusWindow; app.on('ready', function() { onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); + onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); }); ipc.on('online-status-changed', function(event, status) { diff --git a/docs-translations/es/tutorial/quick-start.md b/docs-translations/es/tutorial/quick-start.md index 5c3095deb3ef..ee1127eb0269 100644 --- a/docs-translations/es/tutorial/quick-start.md +++ b/docs-translations/es/tutorial/quick-start.md @@ -2,7 +2,7 @@ ## 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. +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, @@ -65,7 +65,6 @@ a ser ejecutado por el proceso principal. Un ejemplo de `package.json` podría v ``` 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. @@ -95,7 +94,7 @@ app.on('ready', function() { mainWindow = new BrowserWindow({width: 800, height: 600}); // cargar el index.html de nuestra aplicación. - mainWindow.loadUrl('file://' + __dirname + '/index.html'); + mainWindow.loadURL('file://' + __dirname + '/index.html'); // Desplegar devtools. mainWindow.openDevTools(); diff --git a/docs-translations/es/tutorial/supported-platforms.md b/docs-translations/es/tutorial/supported-platforms.md new file mode 100644 index 000000000000..95ccc267722d --- /dev/null +++ b/docs-translations/es/tutorial/supported-platforms.md @@ -0,0 +1,30 @@ +# Plataformas soportadas + +Las siguientes plataformas son soportadas por Electron: + +### OS X + +Sólo se proveen binarios de 64 bit para OS X. +La versión mínima soportada es OS X 10.8. + +### Windows + +Windows 7 y posteriores son soportados, las versiones antiguas no son soportadas (y no funcionan). + +Se proveen binarios para las arquitecturas `x86` y `amd64` (x64). +Nota: La versión para `ARM` de Windows no está soportada aún. + +### Linux + +Los binarios preconstruidos para `ia32`(`i686`) y `x64`(`amd64`) son construidos sobre +Ubuntu 12.04, el binario para `arm` es construido sobre ARM v7 con la ABI hard-float +y NEON para Debian Wheezy. + +La posibilidad de que un binario preconstruido se ejecute en una distribución determinada +depende de las librerías contra las que fue enlazado Electron. +Por ahora sólo se garantiza la ejecución en Ubuntu 12.04, aunque también se ha verificado +el funcionamiento de los binarios preconstruidos en las siguientes plataformas: + +* Ubuntu 12.04 and later +* Fedora 21 +* Debian 8 diff --git a/docs-translations/es/tutorial/using-pepper-flash-plugin.md b/docs-translations/es/tutorial/using-pepper-flash-plugin.md index fbb2b6f83aa0..4e45524fb6af 100644 --- a/docs-translations/es/tutorial/using-pepper-flash-plugin.md +++ b/docs-translations/es/tutorial/using-pepper-flash-plugin.md @@ -31,7 +31,7 @@ app.on('window-all-closed', function() { // Specify flash path. // On Windows, it might be /path/to/pepflashplayer.dll -// On Mac, /path/to/PepperFlashPlayer.plugin +// On OS X, /path/to/PepperFlashPlayer.plugin // On Linux, /path/to/libpepflashplayer.so app.commandLine.appendSwitch('ppapi-flash-path', '/path/to/libpepflashplayer.so'); @@ -46,7 +46,7 @@ app.on('ready', function() { 'plugins': true } }); - mainWindow.loadUrl('file://' + __dirname + '/index.html'); + mainWindow.loadURL('file://' + __dirname + '/index.html'); // Something else }); ``` diff --git a/docs-translations/jp/README.md b/docs-translations/jp/README.md new file mode 100644 index 000000000000..32b14b0fdd2b --- /dev/null +++ b/docs-translations/jp/README.md @@ -0,0 +1,3 @@ +# チュートリアル + +* [クイックスタート](tutorial/quick-start.md) diff --git a/docs-translations/jp/quick-start.md b/docs-translations/jp/tutorial/quick-start.md similarity index 99% rename from docs-translations/jp/quick-start.md rename to docs-translations/jp/tutorial/quick-start.md index aa26a8a55ab0..9a929ff84dc4 100644 --- a/docs-translations/jp/quick-start.md +++ b/docs-translations/jp/tutorial/quick-start.md @@ -74,7 +74,7 @@ app.on('ready', function() { mainWindow = new BrowserWindow({width: 800, height: 600}); // and load the index.html of the app. - mainWindow.loadUrl('file://' + __dirname + '/index.html'); + mainWindow.loadURL('file://' + __dirname + '/index.html'); // Open the devtools. mainWindow.openDevTools(); diff --git a/docs-translations/ko-KR/README.md b/docs-translations/ko-KR/README.md index 1e0ba63e7b6d..e7aae9e6d6fa 100644 --- a/docs-translations/ko-KR/README.md +++ b/docs-translations/ko-KR/README.md @@ -1,11 +1,26 @@ -## 개발 가이드 +반드시 사용하는 Electron 버전과 문서 버전을 일치시켜야 합니다. 버전 숫자는 문서 페이지 +URL에 포함되어 있습니다. 만약 그렇지 않다면, 아마 현재 보고 있는 문서는 개발 중인 +브랜치의 문서를 보고 있을 가능성이 있으며 해당 문서는 추후 API의 변경 가능성이 있고 +현재 사용하고 있는 Electron의 버전과 호환되지 않을 수 있습니다. 이 경우 atom.io의 +[사용할 수 있는 버전](http://electron.atom.io/docs/) 목록에서 다른 버전으로 변경할 +수 있습니다. 또한 GitHub 인터페이스의 "Switch branches/tags" 드롭다운 메뉴에서도 +사용 중인 Electron 버전으로 변경할 수 있습니다. +**역주:** 한국어 번역 문서는 atom.io에 반영이 되어있지 않습니다. 따라서 번역 문서는 +GitHub 프로젝트내에서만 볼 수 있고 `master` 브랜치의 문서는 현재 개발중인 프로젝트의 +문서입니다. 한국어 번역 문서는 현재 `upstream` 원본 문서의 변경에 따라 최대한 문서의 +버전을 맞추려고 노력하고 있지만 가끔 누락된 번역이 존재할 수 있습니다. + +## 개발 가이드 + +* [지원하는 플랫폼](tutorial/supported-platforms.md) * [어플리케이션 배포](tutorial/application-distribution.md) +* [Mac 앱스토어 어플리케이션 제출 가이드](tutorial/mac-app-store-submission-guide.md) * [어플리케이션 패키징](tutorial/application-packaging.md) * [네이티브 Node 모듈 사용하기](tutorial/using-native-node-modules.md) * [메인 프로세스 디버깅하기](tutorial/debugging-main-process.md) * [Selenium 과 WebDriver 사용하기](tutorial/using-selenium-and-webdriver.md) -* [개발자 콘솔 확장기능](tutorial/devtools-extension.md) +* [개발자 도구 확장 기능](tutorial/devtools-extension.md) * [Pepper 플래시 플러그인 사용하기](tutorial/using-pepper-flash-plugin.md) ## 튜토리얼 @@ -18,7 +33,8 @@ * [개요](api/synopsis.md) * [Process 객체](api/process.md) -* [크롬 Command Line 스위치 지원](api/chrome-command-line-switches.md) +* [크롬 명령줄 스위치 지원](api/chrome-command-line-switches.md) +* [환경 변수](api/environment-variables.md) ### 커스텀 DOM elements: @@ -28,33 +44,33 @@ ### 메인 프로세스에서 사용할 수 있는 모듈: -* [app (0% 번역됨)](api/app.md) -* [auto-updater](api/auto-updater.md) -* [browser-window (0% 번역됨)](api/browser-window.md) -* [content-tracing](api/content-tracing.md) +* [app](api/app.md) +* [autoUpdater](api/auto-updater.md) +* [BrowserWindow](api/browser-window.md) +* [contentTracing](api/content-tracing.md) * [dialog](api/dialog.md) -* [global-shortcut](api/global-shortcut.md) -* [ipc (main process)](api/ipc-main-process.md) -* [menu](api/menu.md) -* [menu-item](api/menu-item.md) -* [power-monitor](api/power-monitor.md) -* [power-save-blocker](api/power-save-blocker.md) +* [globalShortcut](api/global-shortcut.md) +* [ipcMain](api/ipc-main.md) +* [Menu](api/menu.md) +* [MenuItem](api/menu-item.md) +* [powerMonitor](api/power-monitor.md) +* [powerSaveBlocker](api/power-save-blocker.md) * [protocol](api/protocol.md) * [session](api/session.md) -* [web-contents (0% 번역됨)](api/web-contents.md) -* [tray](api/tray.md) +* [webContents](api/web-contents.md) +* [Tray](api/tray.md) ### 랜더러 프로세스에서 사용할 수 있는 모듈 (웹 페이지): -* [ipc (renderer)](api/ipc-renderer.md) +* [ipcRenderer](api/ipc-renderer.md) * [remote](api/remote.md) -* [web-frame](api/web-frame.md) +* [webFrame](api/web-frame.md) ### 두 프로세스 모두 사용할 수 있는 모듈: * [clipboard](api/clipboard.md) -* [crash-reporter](api/crash-reporter.md) -* [native-image](api/native-image.md) +* [crashReporter](api/crash-reporter.md) +* [nativeImage](api/native-image.md) * [screen](api/screen.md) * [shell](api/shell.md) @@ -64,7 +80,7 @@ * [소스 코드 디렉터리 구조](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) +* [빌드 설명서 (OS X)](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-KR/api/accelerator.md b/docs-translations/ko-KR/api/accelerator.md index 9e72543a4cc6..99b549bbb70d 100644 --- a/docs-translations/ko-KR/api/accelerator.md +++ b/docs-translations/ko-KR/api/accelerator.md @@ -10,8 +10,9 @@ Accelerator는 키보드 단축키를 표현하는 문자열입니다, 여러 ## 플랫폼에 관련하여 주의할 점 -Linux와 Windows에서는 `Command`키가 없으므로 작동하지 않습니다. 대신에 `CommandOrControl`을 -사용하면 OS X의 `Command`와 Linux, Windows의 `Control` 모두 지원할 수 있습니다. +Linux와 Windows에서는 `Command`키가 없으므로 작동하지 않습니다. 대신에 +`CommandOrControl`을 사용하면 OS X의 `Command`와 Linux, Windows의 `Control` 모두 +지원할 수 있습니다. `Super`키는 Windows와 Linux 에서는 `윈도우`키를, OS X에서는 `Cmd`키로 맵핑됩니다. diff --git a/docs-translations/ko-KR/api/app.md b/docs-translations/ko-KR/api/app.md new file mode 100644 index 000000000000..095e7b39c626 --- /dev/null +++ b/docs-translations/ko-KR/api/app.md @@ -0,0 +1,489 @@ +# app + +`app` 모듈은 어플리케이션의 생명주기 제어를 책임집니다. + +밑의 예제는 마지막 윈도우 창이 종료되었을 때, 어플리케이션을 종료시키는 예제입니다: + +```javascript +const app = require('electron').app; +app.on('window-all-closed', function() { + app.quit(); +}); +``` + +## Events + +`app` 객체는 다음과 같은 이벤트를 가지고 있습니다: + +### Event: 'will-finish-launching' + +어플리케이션이 기본적인 시작 준비를 마치면 발생하는 이벤트입니다. +Windows, Linux 운영체제에서의 `will-finish-launching` 이벤트는 `ready` 이벤트와 +동일합니다. OS X에서의 이벤트는 `NSApplication`의 +`applicationWillFinishLaunching`에 대한 알림으로 표현됩니다. 대개 이곳에서 +`open-file`과 `open-url` 이벤트 리스너를 설정하고 crash reporter와 auto updater를 +시작합니다. + +대부분의 경우, 모든 것을 `ready` 이벤트 핸들러로 해결해야 합니다. + +### Event: 'ready' + +Electron이 초기화를 끝냈을 때 발생하는 이벤트입니다. + +### Event: 'window-all-closed' + +모든 윈도우 창이 종료되었을 때 발생하는 이벤트입니다. + +이 이벤트는 어플리케이션이 완전히 종료되지 않았을 때만 발생합니다. +만약 사용자가 `Cmd + Q`를 입력했거나 개발자가 `app.quit()`를 호출했다면, +Electron은 먼저 모든 윈도우 창의 종료를 시도하고 `will-quit` 이벤트를 발생시킵니다. +그리고 `will-quit` 이벤트가 발생했을 땐 `window-all-closed` 이벤트가 발생하지 +않습니다. + +**역주:** 이 이벤트는 말 그대로 현재 어플리케이션에서 윈도우 창만 완전히 종료됬을 때 +발생하는 이벤트 입니다. 따라서 어플리케이션을 완전히 종료하려면 이 이벤트에서 +`app.quit()`를 호출해 주어야 합니다. + +### Event: 'before-quit' + +Returns: + +* `event` Event + +어플리케이션 윈도우 창들이 닫히기 시작할 때 발생하는 이벤트입니다. +`event.preventDefault()` 호출은 이벤트의 기본 동작을 방지하기 때문에 +이를 통해 어플리케이션의 종료를 방지할 수 있습니다. + +### Event: 'will-quit' + +Returns: + +* `event` Event + +모든 윈도우 창들이 종료되고 어플리케이션이 종료되기 시작할 때 발생하는 이벤트 입니다. +`event.preventDefault()` 호출을 통해 어플리케이션의 종료를 방지할 수 있습니다. + +`will-quit` 와 `window-all-closed` 이벤트의 차이점을 확인하려면 `window-all-close` +이벤트의 설명을 참고하세요. + +### Event: 'quit' + +어플리케이션이 종료될 때 발생하는 이벤트입니다. + +### Event: 'open-file' _OS X_ + +Returns: + +* `event` Event +* `path` String + +사용자가 어플리케이션을 통해 파일을 열고자 할 때 발생하는 이벤트입니다. + +`open-file` 이벤트는 보통 어플리케이션이 열려 있을 때 OS가 파일을 열기 위해 +어플리케이션을 재사용할 때 발생합니다. 이 이벤트는 파일을 dock에 떨어트릴 때, +어플리케이션이 실행되기 전에도 발생합니다. 따라서 이 이벤트를 제대로 처리하려면 +`open-file` 이벤트 핸들러를 어플리케이션이 시작하기 전에 등록해 놓았는지 확실히 +확인해야 합니다. (`ready` 이벤트가 발생하기 전에) + +이 이벤트를 처리할 땐 반드시 `event.preventDefault()`를 호출해야 합니다. + +Windows에선 `process.argv`를 통해 파일 경로를 얻을 수 있습니다. + +### Event: 'open-url' _OS X_ + +Returns: + +* `event` Event +* `url` String + +유저가 어플리케이션을 통해 URL을 열고자 할 때 발생하는 이벤트입니다. + +어플리케이션에서 URL을 열기 위해 반드시 URL 스킴이 등록되어 있어야 합니다. + +이 이벤트를 처리할 땐 반드시 `event.preventDefault()`를 호출해야 합니다. + +### Event: 'activate' _OS X_ + +Returns: + +* `event` Event +* `hasVisibleWindows` Boolean + +어플리케이션이 활성화 되었을 때 발생하는 이벤트 입니다. +이 이벤트는 어플리케이션의 dock 아이콘을 클릭했을 때 주로 발생합니다. + +### Event: 'browser-window-blur' + +Returns: + +* `event` Event +* `window` BrowserWindow + +[browserWindow](browser-window.md)에 대한 포커스가 사라졌을 때 발생하는 이벤트 입니다. + +### Event: 'browser-window-focus' + +Returns: + +* `event` Event +* `window` BrowserWindow + +[browserWindow](browser-window.md)에 대한 포커스가 발생했을 때 발생하는 이벤트 입니다. + +**역주:** _포커스_는 창을 클릭해서 활성화 시켰을 때를 말합니다. + +### Event: 'browser-window-created' + +Returns: + +* `event` Event +* `window` BrowserWindow + +새로운 [browserWindow](browser-window.md)가 생성되었을 때 발생하는 이벤트 입니다. + +### Event: 'certificate-error' + +Returns: + +* `event` Event +* `webContents` [WebContents](web-contents.md) +* `url` URL +* `error` String - 에러 코드 +* `certificate` Object + * `data` Buffer - PEM 인코딩된 데이터 + * `issuerName` String +* `callback` Function + +`url`에 대한 `certificate` 인증서의 유효성 검증에 실패했을 때 발생하는 이벤트입니다. +인증서를 신뢰한다면 `event.preventDefault()` 와 `callback(true)`를 호출하여 +기본 동작을 방지하고 인증을 승인할 수 있습니다. + +```javascript +session.on('certificate-error', function(event, webContents, url, error, certificate, callback) { + if (url == "https://github.com") { + // Verification logic. + event.preventDefault(); + callback(true); + } else { + callback(false); + } +}); +``` + +### Event: 'select-client-certificate' + +Returns: + +* `event` Event +* `webContents` [WebContents](web-contents.md) +* `url` URL +* `certificateList` [Objects] + * `data` Buffer - PEM으로 인코딩된 데이터 + * `issuerName` String - 발급자의 공통 이름 +* `callback` Function + +클라이언트 인증이 요청되었을 때 발생하는 이벤트 입니다. + +`url`은 클라이언트 인증서를 요청하는 탐색 항목에 해당합니다. +그리고 `callback`은 목록에서 필터링된 항목과 함께 호출될 필요가 있습니다. +이 이벤트에서의 `event.preventDefault()` 호출은 초기 인증 때 저장된 데이터를 사용하는 +것을 막습니다. + +```javascript +app.on('select-client-certificate', function(event, webContents, url, list, callback) { + event.preventDefault(); + callback(list[0]); +}) +``` + +### Event: 'login' + +Returns: + +* `event` Event +* `webContents` [WebContents](web-contents.md) +* `request` Object + * `method` String + * `url` URL + * `referrer` URL +* `authInfo` Object + * `isProxy` Boolean + * `scheme` String + * `host` String + * `port` Integer + * `realm` String +* `callback` Function + +`webContents`가 기본 인증을 요청할 때 발생하는 이벤트입니다. + +기본 동작은 인증 요청을 모두 취소시킵니다. +동작을 새로 정의하려면 반드시 `event.preventDefault()`를 호출한 후 +`callback(username, password)` 형태의 콜백을 호출하여 인증을 처리해야 합니다. + +```javascript +app.on('login', function(event, webContents, request, authInfo, callback) { + event.preventDefault(); + callback('username', 'secret'); +}) +``` + +### Event: 'gpu-process-crashed' + +GPU가 작동하던 중 크래시가 일어났을 때 발생하는 이벤트입니다. + +## Methods + +`app` 객체는 다음과 같은 메서드를 가지고 있습니다: + +**참고:** 몇몇 메서드는 특정 플랫폼에서만 작동합니다. + +### `app.quit()` + +모든 윈도우 창 종료를 시도합니다. `before-quit` 이벤트가 먼저 발생합니다. +모든 윈도우 창이 성공적으로 종료되면 `will-quit` 이벤트가 발생하고 기본 동작에 따라 +어플리케이션이 종료됩니다. + +이 함수는 모든 `beforeunload`와 `unload` 이벤트 핸들러가 제대로 실행됨을 보장합니다. +`beforeunload` 이벤트 핸들러에서 `false`를 반환했을 때 윈도우 창 종료가 취소 될 수 +있습니다. + +### `app.exit(exitCode)` + +* `exitCode` Integer + +`exitCode`와 함께 어플리케이션을 즉시 종료합니다. + +모든 윈도우 창은 사용자의 동의 여부에 상관없이 즉시 종료되며 `before-quit` 이벤트와 +`will-quit` 이벤트가 발생하지 않습니다. + +### `app.getAppPath()` + +현재 어플리케이션의 디렉터리를 반환합니다. + +### `app.getPath(name)` + +* `name` String + +`name`에 관련한 특정 디렉터리 또는 파일의 경로를 반환합니다. +경로를 가져오는 데 실패할 경우 `Error`를 반환합니다. + +**역주:** 이 메서드는 운영체제에서 지정한 특수 디렉터리를 가져오는데 사용할 수 있습니다. + +`name`은 다음 목록에 있는 경로 중 하나를 선택해 사용할 수 있습니다: + +* `home` - 사용자의 홈 디렉터리. +* `appData` - 각 사용자의 어플리케이션 데이터 디렉터리. 기본 경로는 다음과 같습니다: + * `%APPDATA%` - Windows + * `$XDG_CONFIG_HOME` 또는 `~/.config` - Linux + * `~/Library/Application Support` - OS X +* `userData` - 어플리케이션의 설정을 저장하는 디렉터리. + 이 디렉터리는 기본적으로 `appData`에 어플리케이션 이름으로 생성된 폴더가 지정됩니다. +* `temp` - 임시 폴더 디렉터리. +* `userDesktop` - 현재 사용자의 데스트탑 디렉터리. +* `exe` - 현재 실행중인 Electron 바이너리 파일. +* `module` - `libchromiumcontent` 라이브러리. +* `desktop` - 사용자의 데스크탑 디렉터리. +* `documents` - 사용자의 "내 문서" 디렉터리. +* `downloads` - 사용자의 다운로드 디렉터리. +* `music` - 사용자의 음악 디렉터리. +* `pictures` - 사용자의 사진 디렉터리. +* `videos` - 사용자의 동영상 디렉터리. + +### `app.setPath(name, path)` + +* `name` String +* `path` String + +`name`에 대한 특정 디렉터리나 파일의 경로인 `path`를 재정의합니다. +만약 지정한 디렉터리의 경로가 존재하지 않으면 디렉터리가 이 메서드를 통해 새로 +생성됩니다. 재정의에 실패했을 땐 `Error`를 반환합니다. + +이 메서드는 `app.getPath`에 정의되어 있는 `name`의 경로만 재정의할 수 있습니다. + +기본적으로, 웹 페이지의 쿠키와 캐시는 `userData` 디렉터리에 저장됩니다. +만약 이 위치를 변경하고자 한다면, 반드시 `app` 모듈의 `ready` 이벤트가 발생하기 전에 +`userData` 경로를 재정의해야 합니다. + +### `app.getVersion()` + +로드된 어플리케이션의 버전을 반환합니다. + +만약 `package.json` 파일에서 어플리케이션의 버전을 찾을 수 없는 경우, 현재 번들 또는 +실행 파일의 버전을 반환합니다. + +### `app.getName()` + +`package.json`에서 기술된 현재 어플리케이션의 이름을 반환합니다. + +npm 모듈 규칙에 따라 대부분의 경우 `package.json`의 `name` 필드는 소문자 이름을 +사용합니다. 하지만 Electron은 `name`대신 `productName` 필드를 주로 사용하기 때문에 +반드시 이 필드도 같이 지정해야 합니다. 이 필드는 맨 앞글자가 대문자인 어플리케이션 +전체 이름을 지정해야 합니다. + +### `app.getLocale()` + +현재 어플리케이션의 [로케일](https://ko.wikipedia.org/wiki/%EB%A1%9C%EC%BC%80%EC%9D%BC)을 +반환합니다. + +### `app.addRecentDocument(path)` _OS X_ _Windows_ + +* `path` String + +최근 문서 목록에 `path`를 추가합니다. + +이 목록은 OS에 의해 관리됩니다. 최근 문서 목록은 Windows의 경우 작업 표시줄에서 찾을 +수 있고, OS X의 경우 dock 메뉴에서 찾을 수 있습니다. + +### `app.clearRecentDocuments()` _OS X_ _Windows_ + +최근 문서 목록을 모두 비웁니다. + +### `app.setUserTasks(tasks)` _Windows_ + +* `tasks` Array - `Task` 객체의 배열 + +Windows에서 사용할 수 있는 JumpList의 [Tasks][tasks] 카테고리에 `task`를 추가합니다. + +`tasks`는 다음과 같은 구조를 가지는 `Task` 객체의 배열입니다: + +`Task` Object +* `program` String - 실행할 프로그램의 경로. + 보통 현재 작동중인 어플리케이션의 경로인 `process.execPath`를 지정합니다. +* `arguments` String - `program`이 실행될 때 사용될 명령줄 인자. +* `title` String - JumpList에 표시할 문자열. +* `description` String - 이 작업에 대한 설명. +* `iconPath` String - JumpList에 표시될 아이콘의 절대 경로. + 아이콘을 포함하고 있는 임의의 리소스 파일을 사용할 수 있습니다. + 보통 어플리케이션의 아이콘을 그대로 사용하기 위해 `process.execPath`를 지정합니다. +* `iconIndex` Integer - 아이콘 파일의 인덱스. 만약 아이콘 파일이 두 개 이상의 + 아이콘을 가지고 있을 경우, 사용할 아이콘의 인덱스를 이 옵션으로 지정해 주어야 합니다. + 단, 아이콘을 하나만 포함하고 있는 경우 0을 지정하면 됩니다. + +### `app.allowNTLMCredentialsForAllDomains(allow)` + +* `allow` Boolean + +항상 동적으로 HTTP NTLM 또는 Negotiate 인증에 자격 증명을 보낼 것인지 설정합니다. + +기본적으로 Electron은 "로컬 인터넷" 사이트 URL에서 NTLM/Kerberos 자격 증명만을 +보냅니다. (같은 도메인 내에서) 그러나 기업 네트워크가 잘못 구성된 경우 종종 작업에 +실패할 수 있습니다. 이때 이 메서드를 통해 모든 URL을 허용할 수 있습니다. + +### `app.makeSingleInstance(callback)` + +* `callback` Function + +현재 어플리케이션을 **Single Instance Application**으로 만들어줍니다. +이 메서드는 어플리케이션이 여러 번 실행됐을 때 다중 인스턴스가 생성되는 대신 한 개의 +주 인스턴스만 유지되도록 만들 수 있습니다. 이때 중복 생성된 인스턴스는 주 인스턴스에 +신호를 보내고 종료됩니다. + +`callback`은 주 인스턴스가 생성된 이후 또 다른 인스턴스가 생성됐을 때 +`callback(argv, workingDirectory)` 형식으로 호출됩니다. `argv`는 두 번째 인스턴스의 +명령줄 인수이며 `workingDirectory`는 현재 작업중인 디렉터리입니다. 보통 대부분의 +어플리케이션은 이러한 콜백이 호출될 때 주 윈도우 창을 포커스하고 최소화되어있으면 창 +복구를 실행합니다. + +`callback`은 `app`의 `ready` 이벤트가 발생한 후 실행됨을 보장합니다. + +이 메서드는 현재 실행된 어플리케이션이 주 인스턴스인 경우 `false`를 반환하고 +어플리케이션의 로드가 계속 진행 되도록 합니다. 그리고 두 번째 중복된 인스턴스 생성인 +경우 `true`를 반환합니다. (다른 인스턴스에 인수가 전달됬을 때) 이 불리언 값을 통해 +중복 생성된 인스턴스는 즉시 종료시켜야 합니다. + +OS X에선 사용자가 Finder에서 어플리케이션의 두 번째 인스턴스를 열려고 했을 때 자동으로 +**Single Instance**화 하고 `open-file`과 `open-url` 이벤트를 발생시킵니다. 그러나 +사용자가 어플리케이션을 CLI 터미널에서 실행하면 운영체제 시스템의 싱글 인스턴스 +메커니즘이 무시되며 그대로 중복 실행됩니다. 따라서 OS X에서도 이 메서드를 통해 확실히 +중복 실행을 방지하는 것이 좋습니다. + +다음 예제는 두 번째 인스턴스가 생성되었을 때 중복된 인스턴스를 종료하고 주 어플리케이션 +인스턴스의 윈도우 창을 활성화 시키는 예제입니다: + +```javascript +var myWindow = null; + +var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) { + // 어플리케이션을 중복 실행했습니다. 주 어플리케이션 인스턴스를 활성화 합니다. + if (myWindow) { + if (myWindow.isMinimized()) myWindow.restore(); + myWindow.focus(); + } + return true; +}); + +if (shouldQuit) { + app.quit(); + return; +} + +// 윈도우 창을 생성하고 각종 리소스를 로드하고 작업합니다.. +app.on('ready', function() { +}); +``` + +### `app.setAppUserModelId(id)` _Windows_ + +* `id` String + +[Application User Model ID][app-user-model-id]를 `id`로 변경합니다. + +### `app.commandLine.appendSwitch(switch[, value])` + +Chrominum의 명령줄에 스위치를 추가합니다. `value`는 추가적인 값을 뜻하며 옵션입니다. + +**참고:** 이 메서드는 `process.argv`에 영향을 주지 않습니다. 개발자들은 보통 +Chrominum의 로우 레벨 수준의 동작을 제어하기 위해 주로 사용합니다. + +### `app.commandLine.appendArgument(value)` + +Chrominum의 명령줄에 인수를 추가합니다. 인수는 올바르게 인용됩니다. + +**참고:** 이 메서드는 `process.argv`에 영향을 주지 않습니다. + +### `app.dock.bounce([type])` _OS X_ + +* `type` String (optional) - `critical` 또는 `informational`을 지정할 수 있습니다. + 기본값은 `informational` 입니다. + +`critical`이 전달되면 dock 아이콘이 어플리케이션이 활성화되거나 요청이 중지되기 전까지 +통통 튑니다. + +`informational`이 전달되면 dock 아이콘이 1초만 통통 튑니다. 하지만 어플리케이션이 +활성화되거나 요청이 중지되기 전까지 요청은 계속 활성화로 유지 됩니다. + +또한 요청을 취소할 때 사용할 수 있는 ID를 반환합니다. + +### `app.dock.cancelBounce(id)` _OS X_ + +* `id` Integer + +`app.dock.bounce([type])` 메서드에서 반환한 `id`의 통통 튀는 효과를 취소합니다. + +### `app.dock.setBadge(text)` _OS X_ + +* `text` String + +dock의 badge에 표시할 문자열을 설정합니다. + +### `app.dock.getBadge()` _OS X_ + +dock의 badge에 설정된 문자열을 반환합니다. + +### `app.dock.hide()` _OS X_ + +dock 아이콘을 숨깁니다. + +### `app.dock.show()` _OS X_ + +dock 아이콘을 표시합니다. + +### `app.dock.setMenu(menu)` _OS X_ + +* `menu` Menu + +어플리케이션의 [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 +[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx diff --git a/docs-translations/ko-KR/api/auto-updater.md b/docs-translations/ko-KR/api/auto-updater.md index 23f881c32ba8..04593ee57600 100644 --- a/docs-translations/ko-KR/api/auto-updater.md +++ b/docs-translations/ko-KR/api/auto-updater.md @@ -1,86 +1,37 @@ # autoUpdater -**이 모듈은 현재 OS X에서만 사용할 수 있습니다.** +이 모듈은 `Squirrel` 자동 업데이트 프레임워크의 인터페이스를 제공합니다. -Windows 어플리케이션 인스톨러를 생성하려면 [atom/grunt-electron-installer](https://github.com/atom/grunt-electron-installer)를 참고하세요. +## 플랫폼별 참고 사항 -`auto-updater` 모듈은 [Squirrel.Mac](https://github.com/Squirrel/Squirrel.Mac) 프레임워크의 간단한 Wrapper입니다. +`autoUpdater`는 기본적으로 모든 플랫폼에 대해 같은 API를 제공하지만, 여전히 플랫폼별로 +약간씩 다른 점이 있습니다. -Squirrel.Mac은 업데이트 설치를 위해 `.app` 폴더에 -[codesign](https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/codesign.1.html) -툴을 사용한 서명을 요구합니다. +### OS X -## Squirrel +OS X에선 `auto-updater` 모듈이 [Squirrel.Mac][squirrel-mac]를 기반으로 작동합니다. +따라서 이 모듈을 작동시키기 위해 특별히 준비해야 할 작업은 없습니다. +서버 사이드 요구 사항은 [서버 지원][server-support]을 참고하세요. -Squirrel은 어플리케이션이 **안전하고 투명한 업데이트**를 제공할 수 있도록 하는데 초점이 맞춰진 OS X 프레임워크입니다. +### Windows -Squirrel은 사용자에게 어플리케이션의 업데이트를 알릴 필요 없이 서버가 지시하는 버전을 받아온 후 자동으로 업데이트합니다. -이 기능을 사용하면 Squirrel을 통해 클라이언트의 어플리케이션을 지능적으로 업데이트 할 수 있습니다. +Windows에선 `auto-updater` 모듈을 사용하기 전에 어플리케이션을 사용자의 장치에 +설치해야 합니다. [grunt-electron-installer][installer]를 사용하여 어플리케이션 +인스톨러를 만드는 것을 권장합니다. -또한 요청시 커스텀 헤더 또는 요청 본문에 인증 정보를 포함시킬 수 있습니다. -서버에선 이러한 요청을 분류 처리하여 적당한 업데이트를 제공할 수 있습니다. +Squirrel로 생성된 인스톨러는 [Application User Model ID][app-user-model-id]와 함께 +`com.squirrel.PACKAGE_ID.YOUR_EXE_WITHOUT_DOT_EXE`으로 형식화된 바로가기 아이콘을 +생성합니다. `com.squirrel.slack.Slack` 과 `com.squirrel.code.Code`가 그 예시입니다. +`app.setAppUserModelId` API를 통해 어플리케이션 ID를 동일하게 유지해야 합니다. 그렇지 +않으면 Windows 작업 표시줄에 어플리케이션을 고정할 때 제대로 적용되지 않을 수 있습니다. -Squirrel JSON 업데이트 요청시 처리는 반드시 어떤 업데이트가 필요한지 요청의 기준에 맞춰 동적으로 생성되어야 합니다. -Squirrel은 사용해야 하는 업데이트 선택하는 과정을 서버에 의존합니다. [서버 지원](#server-support)을 참고하세요. +서버 사이드 요구 사항 또한 OS X와 다르게 적용됩니다. 자세한 내용은 +[Squirrel.Windows][squirrel-windows]를 참고하세요. -Squirrel의 인스톨러는 오류에 관대하게 설계되었습니다. 그리고 업데이트가 유효한지 확인합니다. +### Linux -## 업데이트 요청 - -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 지원을 신경 쓸 필요가 없습니다. +Linux는 따로 `auto-updater`를 지원하지 않습니다. +각 배포판의 패키지 관리자를 통해 어플리케이션 업데이트를 제공하는 것을 권장합니다. ## Events @@ -88,10 +39,11 @@ Squirrel은 "url"로 `Accept: application/zip` 헤더와 함께 업데이트 zip ### Event: 'error' -* `event` Event -* `message` String +Returns: -업데이트시 에러가 나면 발생하는 이벤트입니다. +* `error` Error + +업데이트에 문제가 생기면 발생하는 이벤트입니다. ### Event: 'checking-for-update' @@ -107,20 +59,21 @@ Squirrel은 "url"로 `Accept: application/zip` 헤더와 함께 업데이트 zip ### Event: 'update-downloaded' +Returns: + * `event` Event * `releaseNotes` String * `releaseName` String * `releaseDate` Date -* `updateUrl` String -* `quitAndUpdate` Function +* `updateURL` String -업데이트의 다운로드가 완료되었을 때 발생하는 이벤트입니다. `quitAndUpdate()`를 호출하면 어플리케이션을 종료하고 업데이트를 설치합니다. +업데이트의 다운로드가 완료되었을 때 발생하는 이벤트입니다. ## Methods `autoUpdater` 객체에서 사용할 수 있는 메서드입니다: -### `autoUpdater.setFeedUrl(url)` +### `autoUpdater.setFeedURL(url)` * `url` String @@ -128,4 +81,16 @@ Squirrel은 "url"로 `Accept: application/zip` 헤더와 함께 업데이트 zip ### `autoUpdater.checkForUpdates()` -서버에 새로운 업데이트가 있는지 요청을 보내 확인합니다. API를 사용하기 전에 `setFeedUrl`를 호출해야 합니다. +서버에 새로운 업데이트가 있는지 요청을 보내 확인합니다. API를 사용하기 전에 +`setFeedURL`를 호출해야 합니다. + +### `autoUpdater.quitAndInstall()` + +어플리케이션을 다시 시작하고 다운로드된 업데이트를 설치합니다. +이 메서드는 `update-downloaded` 이벤트가 발생한 이후에만 사용할 수 있습니다. + +[squirrel-mac]: https://github.com/Squirrel/Squirrel.Mac +[server-support]: https://github.com/Squirrel/Squirrel.Mac#server-support +[squirrel-windows]: https://github.com/Squirrel/Squirrel.Windows +[installer]: https://github.com/atom/grunt-electron-installer +[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx diff --git a/docs-translations/ko-KR/api/browser-window.md b/docs-translations/ko-KR/api/browser-window.md new file mode 100644 index 000000000000..6ff934afa095 --- /dev/null +++ b/docs-translations/ko-KR/api/browser-window.md @@ -0,0 +1,742 @@ +# BrowserWindow + +`BrowserWindow` 클래스는 브라우저 창(윈도우 창)을 만드는 역할을 담당합니다. + +다음 예제는 윈도우 창을 생성합니다: + +```javascript +const BrowserWindow = require('electron').BrowserWindow; + +var win = new BrowserWindow({ width: 800, height: 600, show: false }); +win.on('closed', function() { + win = null; +}); + +win.loadURL('https://github.com'); +win.show(); +``` + +또한 [Frameless Window](frameless-window.md) API를 사용하여 창 테두리가 없는 윈도우 +창을 생성할 수 있습니다. + +## Class: BrowserWindow + +`BrowserWindow`는 [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)를 +상속받은 클래스 입니다. + +`BrowserWindow`는 `options`를 통해 네이티브 속성을 포함한 새로운 윈도우 창을 +생성합니다. + +### `new BrowserWindow([options])` + +`options` 객체 (optional), 사용할 수 있는 속성들: + +* `width` Integer - 윈도우 창의 가로 너비. 기본값은 `800`입니다. +* `height` Integer - 윈도우 창의 세로 높이. 기본값은 `600`입니다. +* `x` Integer - 화면을 기준으로 창 좌측을 오프셋 한 위치. 기본값은 `화면중앙`입니다. +* `y` Integer - 화면을 기준으로 창 상단을 오프셋 한 위치. 기본값은 `화면중앙`입니다. +* `useContentSize` Boolean - `width`와 `height`를 웹 페이지의 크기로 사용합니다. + 이 속성을 사용하면 웹 페이지의 크기에 윈도우 프레임 크기가 추가되므로 실제 창은 조금 + 더 커질 수 있습니다. 기본값은 `false`입니다. +* `center` Boolean - 윈도우 창을 화면 정 중앙에 위치시킵니다. +* `minWidth` Integer - 윈도우 창의 최소 가로 너비. 기본값은 `0`입니다. +* `minHeight` Integer - 윈도우 창의 최소 세로 높이. 기본값은 `0`입니다. +* `maxWidth` Integer - 윈도우 창의 최대 가로 너비. 기본값은 `제한없음`입니다. +* `maxHeight` Integer - 윈도우 창의 최대 세로 높이. 기본값은 `제한없음`입니다. +* `resizable` Boolean - 윈도우 창의 크기를 재조정 할 수 있는지 여부. 기본값은 `true` + 입니다. +* `alwaysOnTop` Boolean - 윈도우 창이 언제나 다른 창들 위에 유지되는지 여부. + 기본값은 `false`입니다. +* `fullscreen` Boolean - 윈도우 창의 전체화면 활성화 여부. 기본값은 `false` 입니다. + `false`로 지정했을 경우 OS X에선 전체화면 버튼이 숨겨지거나 비활성화됩니다. +* `skipTaskbar` Boolean - 작업표시줄 어플리케이션 아이콘 표시 스킵 여부. 기본값은 + `false`입니다. +* `kiosk` Boolean - Kiosk(키오스크) 모드. 기본값은 `false`입니다. +* `title` String - 기본 윈도우 창 제목. 기본값은 `"Electron"`입니다. +* `icon` [NativeImage](native-image.md) - 윈도우 아이콘, 생략하면 실행 파일의 + 아이콘이 대신 사용됩니다. +* `show` Boolean - 윈도우가 생성되면 보여줄지 여부. 기본값은 `true`입니다. +* `frame` Boolean - `false`로 지정하면 창을 [Frameless Window](frameless-window.md) + 형태로 생성합니다. 기본값은 `true`입니다. +* `acceptFirstMouse` Boolean - 윈도우가 비활성화 상태일 때 내부 컨텐츠 클릭 시 + 활성화 되는 동시에 단일 mouse-down 이벤트를 발생시킬지 여부. 기본값은 `false`입니다. +* `disableAutoHideCursor` Boolean - 파이핑중 자동으로 커서를 숨길지 여부. 기본값은 + `false`입니다. +* `autoHideMenuBar` Boolean - `Alt`를 누르지 않는 한 어플리케이션 메뉴바를 숨길지 + 여부. 기본값은 `false`입니다. +* `enableLargerThanScreen` Boolean - 윈도우 창 크기가 화면 크기보다 크게 재조정 될 + 수 있는지 여부. 기본값은 `false`입니다. +* `backgroundColor` String - 16진수로 표현된 윈도우의 배경 색. `#66CD00` 또는 + `#FFF`가 사용될 수 있습니다. 이 속성은 Linux와 Windows에만 구현되어 있습니다. + 기본값은 `#000`(검정)입니다. +* `darkTheme` Boolean - 설정에 상관 없이 무조건 어두운 윈도우 테마를 사용합니다. + 몇몇 GTK+3 데스크톱 환경에서만 작동합니다. 기본값은 `false`입니다. +* `transparent` Boolean - 윈도우 창을 [투명화](frameless-window.md)합니다. 기본값은 + `false`입니다. +* `type` String - 특정 플랫폼에만 적용되는 윈도우 창의 종류를 지정합니다. 기본적으로 + 이 속성이 `undefined`일 경우 표준 윈도우가 사용됩니다. 사용할 수 있는 창의 종류는 + 다음과 같습니다: + * Linux의 경우: `desktop`, `dock`, `toolbar`, `splash`, `notification` 종류를 + 사용할 수 있습니다. + * OS X의 경우: `desktop`, `textured` 종류를 사용할 수 있습니다. `textured` 종류는 + 창을 그라디언트 형태로 표현합니다 (`NSTexturedBackgroundWindowMask`) `desktop` + 종류는 데스크탑 배경 레벨에 윈도우를 배치합니다 (`kCGDesktopWindowLevel - 1`). + 참고로 이렇게 만들어진 윈도우는 포커스, 키보드, 마우스 이벤트를 받을 수 없습니다. + 하지만 편법으로 `globalShortcut`을 통해 키 입력을 받을 수 있습니다. +* `standardWindow` Boolean - OS X의 표준 윈도우를 텍스쳐 윈도우 대신 사용합니다. + 기본 값은 `true`입니다. +* `titleBarStyle` String, OS X - 윈도우 타이틀 바 스타일을 지정합니다. 이 속성은 + OS X 10.10 Yosemite 이후 버전만 지원합니다. 다음 3가지 종류의 값을 사용할 수 + 있습니다: + * `default` 또는 미지정: 표준 Mac 회색 불투명 스타일을 사용합니다. + * `hidden`: 타이틀 바를 숨기고 컨텐츠 전체를 윈도우 크기에 맞춥니다. + 타이틀 바는 없어지지만 표준 창 컨트롤 ("신호등 버튼")은 왼쪽 상단에 유지됩니다. + * `hidden-inset`: `hidden` 타이틀 바 속성과 함께 신호등 버튼이 윈도우 모서리로부터 + 약간 더 안쪽으로 들어가도록합니다. +* `webPreferences` Object - 웹 페이지 기능을 설정합니다. 사용할 수 있는 속성은 + 다음과 같습니다: + * `nodeIntegration` Boolean - node(node.js) 통합 여부. 기본값은 `true`입니다. + * `preload` String - 스크립트를 지정하면 페이지 내의 다른 스크립트가 작동하기 전에 + 로드됩니다. 여기서 지정한 스크립트는 node 통합 활성화 여부에 상관없이 언제나 모든 + node API에 접근할 수 있습니다. 이 속성의 스크립트 경로는 절대 경로로 지정해야 + 합니다. node 통합이 비활성화되어있을 경우, preload 스크립트는 node의 global + 심볼들을 다시 global 스코프로 다시 포함 시킬 수 있습니다. + [여기](process.md#event-loaded)의 예제를 참고하세요. + * `partition` String - 페이지에서 사용할 세션을 지정합니다. 만약 `partition`이 + `persist:`로 시작하면 페이지는 지속성 세션을 사용하며 다른 모든 앱 내의 + 페이지에서 같은 `partition`을 사용할 수 있습니다. 만약 `persist:` 접두어로 + 시작하지 않으면 페이지는 인-메모리 세션을 사용합니다. 여러 페이지에서 같은 + `partition`을 지정하면 같은 세션을 공유할 수 있습니다. `partition`을 지정하지 + 않으면 어플리케이션의 기본 세션이 사용됩니다. + * `zoomFactor` Number - 페이지의 기본 줌 값을 지정합니다. 예를 들어 `300%`를 + 표현하려면 `3.0`으로 지정합니다. 기본값은 `1.0`입니다. + * `javascript` Boolean - 자바스크립트를 활성화합니다. 기본값은 `false`입니다. + * `webSecurity` Boolean - `false`로 지정하면 same-origin 정책을 비활성화합니다. + (이 속성은 보통 사람들에 의해 웹 사이트를 테스트할 때 사용합니다) 그리고 + `allowDisplayingInsecureContent`와 `allowRunningInsecureContent` 두 속성을 + 사용자가 `true`로 지정되지 않은 경우 `true`로 지정합니다. 기본값은 + `true`입니다. + * `allowDisplayingInsecureContent` Boolean - https 페이지에서 http URL에서 + 로드한 이미지 같은 리소스를 표시할 수 있도록 허용합니다. 기본값은 `false`입니다. + * `allowRunningInsecureContent` Boolean - https 페이지에서 http URL에서 로드한 + JavaScript와 CSS 또는 플러그인을 실행시킬 수 있도록 허용합니다. 기본값은 + `false`입니다. + * `images` Boolean - 이미지 지원을 활성화합니다. 기본값은 `true`입니다. + * `java` Boolean - Java 지원을 활성화합니다. 기본값은 `false`입니다. + * `textAreasAreResizable` Boolean - HTML TextArea 요소의 크기를 재조정을 + 허용합니다. 기본값은 `true`입니다. + * `webgl` Boolean - WebGL 지원을 활성화합니다. 기본값은 `true`입니다. + * `webaudio` Boolean - WebAudio 지원을 활성화합니다. 기본값은 `true`입니다. + * `plugins` Boolean - 플러그인 활성화 여부를 지정합니다. 기본값은 `false`입니다. + * `experimentalFeatures` Boolean - Chrome의 실험적인 기능을 활성화합니다. + 기본값은 `false`입니다. + * `experimentalCanvasFeatures` Boolean - Chrome의 실험적인 캔버스(canvas) 기능을 + 활성화합니다. 기본값은 `false`입니다. + * `overlayScrollbars` Boolean - 오버레이 스크롤바를 활성화합니다. 기본값은 + `false`입니다. + * `overlayFullscreenVideo` Boolean - 오버레이 전체화면 비디오 기능을 활성화합니다. + 기본값은 `false`입니다. + * `sharedWorker` Boolean - SharedWorker 기능을 활성화합니다. 기본값은 + `false`입니다. + * `directWrite` Boolean - Windows에서 폰트 랜더링을 위해 DirectWrite를 + 사용하는지를 지정합니다. 기본값은 `true`입니다. + * `pageVisibility` Boolean - 현재 윈도우의 가시성을 반영하는 대신 페이지가 + visible 또는 hidden 중 지정된 상태를 계속 유지하도록 합니다. 이 속성을 `true`로 + 지정하면 DOM 타이머의 스로틀링을 방지할 수 있습니다. 기본값은 `false`입니다. + +## Events + +`BrowserWindow` 객체는 다음과 같은 이벤트를 발생시킵니다: + +**참고:** 몇몇 이벤트는 라벨에 특정한 OS에서만 작동합니다. + +### Event: 'page-title-updated' + +Returns: + +* `event` Event + +문서의 제목이 변경될 때 발생하는 이벤트 입니다. `event.preventDefault()`를 호출하여 +네이티브 윈도우의 제목이 변경되는 것을 방지할 수 있습니다. + +### Event: 'close' + +Returns: + +* `event` Event + +윈도우가 닫히기 시작할 때 발생하는 이벤트입니다. +이 이벤트는 DOM의 `beforeunload` 와 `unload` 이벤트가 호출되기 전에 발생합니다. +`event.preventDefault()`를 호출하여 윈도우 종료를 취소할 수 있습니다. + +보통 창을 닫아야 할지 결정하기 위해 `beforeunload` 이벤트를 사용하려고 할 것입니다. +이 이벤트는 윈도우 컨텐츠를 새로고칠 때도 발생합니다. +Electron에선 빈 문자열 또는 `false`를 전달할 경우 윈도우 종료를 취소합니다. + +예시는 다음과 같습니다: + +```javascript +window.onbeforeunload = function(e) { + console.log('I do not want to be closed'); + + // 반드시 문자열을 반환해야 하고 사용자에게 페이지 언로드에 대한 확인 창을 보여주는 + // 보통의 브라우저와는 달리 Electron은 개발자에게 더 많은 옵션을 제공합니다. + // 빈 문자열을 반환하거나 false를 반환하면 페이지 언로드를 방지합니다. + // 또한 dialog API를 통해 사용자에게 어플리케이션을 종료할지에 대한 확인 창을 + // 보여줄 수도 있습니다. + e.returnValue = false; +}; +``` + +### Event: 'closed' + +윈도우 종료가 완료된 경우 발생하는 이벤트입니다. 이 이벤트가 발생했을 경우 반드시 +윈도우 창의 레퍼런스가 더 이상 사용되지 않도록 제거해야 합니다. + +### Event: 'unresponsive' + +웹 페이지가 응답하지 않을 때 발생하는 이벤트입니다. + +### Event: 'responsive' + +응답하지 않는 웹 페이지가 다시 응답하기 시작했을 때 발생하는 이벤트입니다. + +### Event: 'blur' + +윈도우가 포커스를 잃었을 떄 발생하는 이벤트입니다. + +### Event: 'focus' + +윈도우가 포커스를 가졌을 때 발생하는 이벤트입니다. + +### Event: 'maximize' + +윈도우가 최대화됐을 때 발생하는 이벤트입니다. + +### Event: 'unmaximize' + +윈도우의 최대화 상태가 해제되었을 때 발생하는 이벤트입니다. + +### Event: 'minimize' + +윈도우가 최소화됐을 때 발생하는 이벤트입니다. + +### Event: 'restore' + +윈도우가 최소화 상태에서 복구되었을 때 발생하는 이벤트입니다. + +### Event: 'resize' + +윈도우의 크기가 재조정될 때 발생하는 이벤트입니다. + +### Event: 'move' + +윈도우가 새로운 위치로 이동될 때 발생하는 이벤트입니다. + +__참고__: OS X에선 이 이벤트가 그저 `moved` 이벤트의 별칭(alias)으로 사용됩니다. + +### Event: 'moved' _OS X_ + +윈도우가 새로운 위치로 이동되었을 때 발생하는 이벤트입니다. (한 번만) + +### Event: 'enter-full-screen' + +윈도우가 풀 스크린 모드로 진입할 때 발생하는 이벤트입니다. + +### Event: 'leave-full-screen' + +윈도우가 풀 스크린 모드에서 해제될 때 발생하는 이벤트입니다. + +### Event: 'enter-html-full-screen' + +윈도우가 HTML API에 의해 풀 스크린 모드로 진입할 때 발생하는 이벤트입니다. + +### Event: 'leave-html-full-screen' + +윈도우가 HTML API에 의해 풀 스크린 모드에서 해제될 때 발생하는 이벤트입니다. + +### Event: 'app-command' _Windows_ + +[App Command](https://msdn.microsoft.com/en-us/library/windows/desktop/ms646275(v=vs.85).aspx)가 +호출됐을 때 발생하는 이벤트입니다. 이 이벤트는 일반적으로 키보드 미디어 키 또는 +브라우저 커맨드(기본 동작 키)에 관련되어 있습니다. 예를 들어 Windows에서 작동하는 +몇몇 마우스는 "뒤로가기" 같은 동작을 포함하고 있습니다. + +```javascript +someWindow.on('app-command', function(e, cmd) { + // 마우스의 뒤로가기 버튼을 눌렀을 때 뒤로가기 탐색을 실행합니다 + if (cmd === 'browser-backward' && someWindow.webContents.canGoBack()) { + someWindow.webContents.goBack(); + } +}); +``` + +## Methods + +`BrowserWindow` 객체는 다음과 같은 메서드를 가지고 있습니다: + +### `BrowserWindow.getAllWindows()` + +열려있는 모든 브라우저 윈도우의 배열을 반환합니다. + +### `BrowserWindow.getFocusedWindow()` + +어플리케이션에서 포커스된 윈도우를 반환합니다. + +### `BrowserWindow.fromWebContents(webContents)` + +* `webContents` [WebContents](web-contents.md) + +`webContents`를 소유하고 있는 윈도우를 찾습니다. + +### `BrowserWindow.fromId(id)` + +* `id` Integer + +ID에 해당하는 윈도우를 찾습니다. + +### `BrowserWindow.addDevToolsExtension(path)` + +* `path` String + +`path`에 있는 개발자 도구 확장 기능을 추가합니다. 그리고 확장 기능의 이름을 반환합니다. + +확장 기능은 기억됩니다. 따라서 API는 단 한 번만 호출되어야 합니다. +이 API는 실제 프로그램 작성에 사용할 수 없습니다. + +### `BrowserWindow.removeDevToolsExtension(name)` + +* `name` String + +`name`에 해당하는 개발자 도구 확장 기능을 제거합니다. + +## Instance Properties + +`new BrowserWindow`로 생성한 객체는 다음과 같은 속성을 가지고 있습니다: + +```javascript +// `win`은 BrowserWindow의 인스턴스입니다 +var win = new BrowserWindow({ width: 800, height: 600 }); +``` + +### `win.webContents` + +윈도우의 `WebContents` 객체입니다. 모든 웹 페이지와 관련된 이벤트와 작업이 이 객체를 +통해 수행됩니다. + +메서드나 이벤트에 대한 자세한 내용은 [`webContents` 문서](web-contents.md)를 +참고하세요. + +### `win.id` + +윈도우의 유일 ID입니다. + +## Instance Methods + +`new BrowserWindow`로 생성한 객체는 다음과 같은 메서드들을 가지고 있습니다: + +**참고:** 몇몇 메서드들은 라벨에서 특정한 운영체제 시스템에서만 작동합니다. + +### `win.destroy()` + +윈도우를 강제로 닫습니다. 웹 페이지의 `unload` 와 `beforeunload` 이벤트는 일어나지 +않습니다. 또한 이 윈도우의 `close`도 일어나지 않습니다. 하지만 `closed` 이벤트는 +반드시 발생함을 보장합니다. + +이 메서드는 렌더러 프로세스가 예기치 않게 크래시가 일어났을 경우에만 사용해야 합니다. + +### `win.close()` + +윈도우의 종료를 시도합니다. 이 메서드는 사용자가 윈도우의 닫기 버튼을 클릭했을 때와 +같은 효과를 냅니다. 웹 페이지는 로드가 취소되고 종료됩니다. 자세한 내용은 +[close 이벤트](#event-close)를 참고하세요. + +### `win.focus()` + +윈도우에 포커스를 맞춥니다. + +### `win.isFocused()` + +윈도우가 포커스되었는지 여부를 반환합니다. + +### `win.show()` + +윈도우를 표시하고 포커스합니다. + +### `win.showInactive()` + +윈도우를 표시만 하고 포커스하지 않습니다. + +### `win.hide()` + +윈도우를 숨깁니다. + +### `win.isVisible()` + +윈도우가 사용자에게 표시되고 있는지 여부를 반환합니다. + +### `win.maximize()` + +윈도우를 최대화 시킵니다. + +### `win.unmaximize()` + +윈도우 최대화를 취소합니다. + +### `win.isMaximized()` + +윈도우가 최대화 되어있는지 여부를 반환합니다. + +### `win.minimize()` + +윈도우를 최소화 시킵니다. 어떤 플랫폼은 최소화된 윈도우가 Dock에 표시됩니다. + +### `win.restore()` + +최소화된 윈도우를 이전 상태로 되돌립니다. + +### `win.isMinimized()` + +윈도우가 최소화되었는지 여부를 반환합니다. + +### `win.setFullScreen(flag)` + +* `flag` Boolean + +윈도우의 전체화면 상태를 지정합니다. + +### `win.isFullScreen()` + +윈도우가 전체화면 모드 상태인지 여부를 반환합니다. + +### `win.setAspectRatio(aspectRatio[, extraSize])` _OS X_ + +* `aspectRatio` 유지하려 하는 컨텐츠 뷰 일부의 종횡비 +* `extraSize` Object (optional) - 종횡비를 유지하는 동안 포함되지 않을 엑스트라 크기. + 사용 가능한 속성: + * `width` Integer + * `height` Integer + +이 메서드는 윈도우의 종횡비를 유지하는 기능을 수행합니다. 엑스트라 크기는 개발자가 +픽셀로 특정한 공간이 있을 때 종횡비 계산에서 제외됩니다. 이 API는 윈도우의 크기와 +컨텐츠 사이즈의 차이를 이미 고려하고 있습니다. + +일반 윈도우에서 작동하는 HD 비디오 플레이어와 관련된 컨트롤을 고려합니다. +만약 15 픽셀의 컨트롤이 왼쪽 가장자리에 있고 25 픽셀의 컨트롤이 오른쪽 가장자리에 +있으며 50 픽셀의 컨트롤이 플레이어 밑에 있을 때 플레이어 자체가 16:9 종횡비(HD의 표준 +종횡비는 @1920x1080)를 유지하기 위해선 이 함수를 16/9, [ 40, 50 ] 인수와 함께 +호출해야 합니다. 두번째 인수 엑스트라 크기는 존재하는 크기만 관여하고 컨텐츠 뷰 내의 +크기는 관여하지 않습니다. 그저 전체 컨텐츠 뷰 내에 있는 모든 엑스트라 너비, 높이 영역이 +합해집니다. + +### `win.setBounds(options)` + +`options` Object, properties: + +* `x` Integer +* `y` Integer +* `width` Integer +* `height` Integer + +윈도우를 지정한 `width`, `height`, `x`, `y`로 크기 재조정 및 이동합니다. + +### `win.getBounds()` + +윈도우의 width, height, x, y 값을 가지는 객체를 반환합니다. + +### `win.setSize(width, height)` + +* `width` Integer +* `height` Integer + +`width`와 `height` 값으로 윈도우 크기를 재조정합니다. (너비, 높이) + +### `win.getSize()` + +윈도우의 너비, 높이값을 가지는 배열을 반환합니다. + +### `win.setContentSize(width, height)` + +* `width` Integer +* `height` Integer + +윈도우 클라이언트 영역(웹 페이지)의 크기를 `width`, `height`로 재조정합니다. + +### `win.getContentSize()` + +윈도우 클라이언트 영역의 너비, 높이 크기를 배열로 반환합니다. + +### `win.setMinimumSize(width, height)` + +* `width` Integer +* `height` Integer + +윈도우의 최소 `width`, `height` 크기를 지정합니다. + +### `win.getMinimumSize()` + +윈도우의 최소 너비, 높이 크기를 배열로 반환합니다. + +### `win.setMaximumSize(width, height)` + +* `width` Integer +* `height` Integer + +윈도우의 최대 `width`, `height` 크기를 지정합니다. + +### `win.getMaximumSize()` + +윈도우의 최대 너비, 높이 크기를 배열로 반환합니다. + +### `win.setResizable(resizable)` + +* `resizable` Boolean + +윈도우의 크기가 사용자에 의해 재조정될 수 있는지를 지정합니다. + +### `win.isResizable()` + +윈도우의 크기가 사용자에 의해 재조정될 수 있는지 여부를 반환합니다. + +### `win.setAlwaysOnTop(flag)` + +* `flag` Boolean + +윈도우가 언제나 다른 윈도우들 위에 표시되는지 여부를 지정합니다. 이 설정을 활성화 하면 +윈도우는 포커스 될 수 없는 툴박스 윈도우가 아닌 일반 윈도우로 유지됩니다. + +### `win.isAlwaysOnTop()` + +윈도우가 언제나 다른 윈도우들 위에 표시되는지 여부를 반환합니다. + +### `win.center()` + +윈도우를 화면 정 중앙으로 이동합니다. + +### `win.setPosition(x, y)` + +* `x` Integer +* `y` Integer + +윈도우의 위치를 `x`, `y`로 이동합니다. + +### `win.getPosition()` + +윈도우의 위치를 배열로 반환합니다. + +### `win.setTitle(title)` + +* `title` String + +`title`을 네이티브 윈도우의 제목으로 지정합니다. + +### `win.getTitle()` + +윈도우의 제목을 반환합니다. + +**참고:** 웹 페이지의 제목과 네이티브 윈도우의 제목은 서로 다를 수 있습니다. + +### `win.flashFrame(flag)` + +* `flag` Boolean + +사용자가 윈도우에 관심을 가질 수 있도록 창을 깜빡이거나 이를 중지합니다. + +### `win.setSkipTaskbar(skip)` + +* `skip` Boolean + +어플리케이션 아이콘을 작업표시줄에 보이지 않도록 설정합니다. + +### `win.setKiosk(flag)` + +* `flag` Boolean + +Kiosk(키오스크) 모드를 설정합니다. + +### `win.isKiosk()` + +현재 윈도우가 kiosk 모드인지 여부를 반환합니다. + +### `win.hookWindowMessage(message, callback)` _Windows_ + +* `message` Integer +* `callback` Function + +Windows 메시지 훅을 등록합니다. `callback`은 WndProc에서 메시지를 받았을 때 호출됩니다. + +### `win.isWindowMessageHooked(message)` _Windows_ + +* `message` Integer + +지정한 메시지가 후킹됬는지 여부를 반환합니다. + +### `win.unhookWindowMessage(message)` _Windows_ + +* `message` Integer + +지정한 메시지 훅을 등록 해제합니다. + +### `win.unhookAllWindowMessages()` _Windows_ + +모든 메시지 훅을 등록 해제합니다. + +### `win.setRepresentedFilename(filename)` _OS X_ + +* `filename` String + +윈도우 대표 파일의 경로명을 설정합니다. 파일의 아이콘이 윈도우 타이틀 바에 표시됩니다. + +### `win.getRepresentedFilename()` _OS X_ + +윈도우 대표 파일의 경로명을 반환합니다. + +### `win.setDocumentEdited(edited)` _OS X_ + +* `edited` Boolean + +윈도우의 문서가 변경되었는지 여부를 설정합니다. 그리고 `true`로 설정했을 때 타이틀 바의 +아이콘이 회색으로 표시됩니다. + +### `win.isDocumentEdited()` _OS X_ + +윈도우의 문서가 변경되었는지 여부를 반환합니다. + +### `win.focusOnWebView()` + +### `win.blurWebView()` + +### `win.capturePage([rect, ]callback)` + +* `rect` Object (optional) - 캡쳐할 페이지의 영역. + 사용할 수 있는 속성은 다음과 같습니다: + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer +* `callback` Function + +페이지의 스크린샷을 `rect`에 설정한 만큼 캡처합니다. 캡처가 완료되면 `callback`이 +`callback(image)` 형식으로 호출됩니다. `image`는 [NativeImage](native-image.md)의 +인스턴스이며 스크린샷 데이터를 담고있습니다. `rect`를 생략하면 페이지 전체를 캡처합니다. + +### `win.print([options])` + +`webContents.print([options])` API와 같습니다. + +### `win.printToPDF(options, callback)` + +`webContents.printToPDF(options, callback)` API와 같습니다. + +### `win.loadURL(url[, options])` + +`webContents.loadURL(url[, options])` API와 같습니다. + +### `win.reload()` + +`webContents.reload` API와 같습니다. + +### `win.setMenu(menu)` _Linux_ _Windows_ + +* `menu` Menu + +지정한 `menu`를 윈도우의 메뉴로 설정합니다 `null`을 설정하면 메뉴를 제거합니다. + +### `win.setProgressBar(progress)` + +* `progress` Double + +작업표시줄에 표시되고 있는 어플리케이션 아이콘에 진행 상태를 표시합니다. [0, 1.0] +사이의 값을 지정할 수 있습니다. + +진행 상태가 < 0 이 되면 진행 상태 표시를 제거합니다. +진행 상태가 > 1 이 되면 불확정 상태 표시로 전환합니다. + +Linux 플랫폼에선 Unity 데스크톱 환경만 지원합니다. 그리고 이 기능을 사용하려면 +`*.desktop` 파일을 생성한 후 `package.json`의 `desktopName` 필드에 파일 이름을 +지정해야 합니다. 기본적으로 `app.getName().desktop`을 통해 접근합니다. + +### `win.setOverlayIcon(overlay, description)` _Windows 7+_ + +* `overlay` [NativeImage](native-image.md) - 작업표시줄 아이콘의 우측 하단에 표시될 +아이콘입니다. `null`로 지정하면 빈 오버레이가 사용됩니다 +* `description` String - 접근성 설정에 의한 스크린 리더에 제공될 설명입니다 + +현재 작업표시줄 아이콘에 16px 크기의 오버레이를 지정합니다. 보통 이 기능은 +어플리케이션의 여러 상태를 사용자에게 소극적으로 알리기 위한 방법으로 사용됩니다. + +### `win.setThumbarButtons(buttons)` _Windows 7+_ + +`buttons` - `button` 객체의 배열: + +`button` 객체는 다음과 같은 속성을 가지고 있습니다: + +* `icon` [NativeImage](native-image.md) - 미리보기 툴바에 보여질 아이콘. +* `tooltip` String (optional) - 버튼의 툴팁 텍스트. +* `flags` Array (optional) - 버튼의 특정 동작 및 상태 제어. 기본적으로 `enabled`이 + 사용됩니다. 이 속성은 다음 문자열들을 포함할 수 있습니다: + * `enabled` - 사용자가 사용할 수 있도록 버튼이 활성화 됩니다. + * `disabled` - 버튼이 비활성화 됩니다. 버튼은 표시되지만 시각적인 상태는 사용자의 + 동작에 응답하지 않는 비활성화 상태로 표시됩니다. + * `dismissonclick` - 버튼이 클릭되면 작업표시줄 버튼의 미리보기(flyout)가 즉시 + 종료됩니다. + * `nobackground` - 버튼의 테두리를 표시하지 않습니다. 이미지에만 사용할 수 있습니다. + * `hidden` - 버튼을 사용자에게 표시되지 않도록 숨깁니다. + * `noninteractive` - 버튼은 활성화되어 있지만 반응이 제거되며 버튼을 눌러도 + 눌려지지 않은 상태를 유지합니다. 이 값은 버튼을 알림의 용도로 사용하기 위해 + 만들어졌습니다. +* `click` - Function + +윈도우 작업표시줄 버튼 레이아웃의 미리보기 이미지 영역에 미리보기 툴바와 버튼 세트를 +지정합니다. 반환되는 `Boolean` 값은 미리보기 툴바가 성공적으로 추가됬는지를 알려줍니다. + +미리보기 이미지 영역의 제한된 크기로 인해 미리보기 툴바에 추가될 수 있는 최대 버튼의 +개수는 7개이며 이 이상 추가될 수 없습니다. 플랫폼의 제약으로 인해 미리보기 툴바는 한 번 +설정되면 삭제할 수 없습니다. 하지만 이 API에 빈 배열을 전달하여 버튼들을 제거할 수 +있습니다. + +### `win.showDefinitionForSelection()` _OS X_ + +페이지의 선택된 단어에 대한 사전 검색 결과 팝업을 표시합니다. + +### `win.setAutoHideMenuBar(hide)` + +* `hide` Boolean + +메뉴 막대 자동 숨김 기능을 활성화 합니다. +숨겨진 메뉴는 사용자가 `Alt` 키를 단일 입력했을 때만 표시됩니다. + +메뉴 막대가 이미 표시되고 있을 때 `setAutoHideMenuBar(true)`를 호출한다고 해서 +메뉴가 즉시 숨겨지지는 않습니다. + +### `win.isMenuBarAutoHide()` + +메뉴 막대 자동 숨김 상태인지 여부를 반환합니다. + +### `win.setMenuBarVisibility(visible)` + +* `visible` Boolean + +메뉴 막대의 표시 여부를 설정합니다. 만약 메뉴 막대 자동 숨김 상태라면 여전히 사용자가 +`Alt` 키를 입력하여 메뉴 막대를 표시되도록 할 수 있습니다. + +**역주:** 기본 메뉴 막대를 완전히 없애려면 `win.setMenu(null)`를 호출해야 합니다. +단순히 이 API를 사용하면 여전히 메뉴에 등록된 핫 키가 작동합니다. + +### `win.isMenuBarVisible()` + +메뉴 막대가 표시되고 있는지 여부를 반환합니다. + +### `win.setVisibleOnAllWorkspaces(visible)` + +* `visible` Boolean + +윈도우가 모든 워크스페이스에서 표시될지 여부를 설정합니다. + +**참고:** 이 API는 Windows에서 아무 일도 하지 않습니다. + +### `win.isVisibleOnAllWorkspaces()` + +윈도우가 모든 워크스페이스에서 표시될지 여부를 반환합니다. + +**참고:** 이 API는 Windows에서 언제나 false를 반환합니다. diff --git a/docs-translations/ko-KR/api/chrome-command-line-switches.md b/docs-translations/ko-KR/api/chrome-command-line-switches.md index 2d9158fcc2f4..443a8f218743 100644 --- a/docs-translations/ko-KR/api/chrome-command-line-switches.md +++ b/docs-translations/ko-KR/api/chrome-command-line-switches.md @@ -1,11 +1,12 @@ -# 크롬 Command-Line 스위치 지원 +# 크롬 명령줄 스위치 지원 -크롬 Command-Line 스위치는 크롬 브라우저에서 제공되는 추가 옵션이며 Electron에서도 지원합니다. -[app][app]의 [ready][ready]이벤트가 작동하기 전에 [app.commandLine.appendSwitch][append-switch] API를 사용하면 -어플리케이션 내부에서 스위치들을 추가할 수 있습니다: +크롬 명령줄(Command-Line) 스위치는 크롬 브라우저에서 제공되는 추가 옵션이며 +Electron에서도 지원합니다. [app][app]의 [ready][ready]이벤트가 작동하기 전에 +[app.commandLine.appendSwitch][append-switch] API를 사용하면 어플리케이션 내부에서 +스위치를 추가할 수 있습니다: ```javascript -var app = require('app'); +const app = require('electron').app; app.commandLine.appendSwitch('remote-debugging-port', '8315'); app.commandLine.appendSwitch('host-rules', 'MAP * 127.0.0.1'); @@ -28,11 +29,40 @@ HTTP 요청 캐시를 비활성화 합니다. ## --remote-debugging-port=`port` -지정한 `port`에 HTTP기반의 리모트 디버거를 활성화 시킵니다. (개발자 콘솔) +지정한 `port`에 HTTP 기반의 리모트 디버거를 활성화 시킵니다. (개발자 도구) + +## --js-flags=`flags` + +JS 엔진에 지정한 플래그를 전달합니다. `flags`를 메인 프로세스에서 활성화하고자 한다면, +Electron이 시작되기 전에 스위치를 전달해야 합니다. + +```bash +$ electron --js-flags="--harmony_proxies --harmony_collections" your-app +``` ## --proxy-server=`address:port` -시스템 설정의 프록시 서버를 무시하고 지정한 서버로 연결합니다. HTTP와 HTTPS 요청에만 적용됩니다. +시스템 설정의 프록시 서버를 무시하고 지정한 서버로 연결합니다. HTTP와 HTTPS 요청에만 +적용됩니다. + +시스템 프록시 서버 설정을 무시하고 지정한 서버로 연결합니다. 이 스위치는 HTTP와 HTTPS +그리고 WebSocket 요청에만 적용됩니다. 그리고 모든 프록시 서버가 HTTPS가 WebSocket +요청을 지원하지 않고 있을 수 있으므로 사용시 주의해야 합니다. + +## --proxy-bypass-list=`hosts` + +Electron이 세미콜론으로 구분된 호스트 리스트에서 지정한 프록시 서버를 건너뛰도록 +지시합니다. + +예시: + +```javascript +app.commandLine.appendSwitch('proxy-bypass-list', ';*.google.com;*foo.com;1.2.3.4:5678')` +``` + +위 예시는 로컬 주소(`localhost`, `127.0.0.1`, 등)와 `google.com`의 서브도메인, +`foo.com`을 접미사로 가지는 호스트, `1.2.3.4:5678` 호스트를 제외한 모든 연결에서 +프록시 서버를 사용합니다. ## --proxy-pac-url=`url` @@ -40,32 +70,30 @@ HTTP 요청 캐시를 비활성화 합니다. ## --no-proxy-server -프록시 서버를 사용하지 않습니다. 다른 프록시 서버 플래그 및 설정을 무시하고 언제나 직접 연결을 사용합니다. +프록시 서버를 사용하지 않습니다. 다른 프록시 서버 플래그 및 설정을 무시하고 언제나 직접 +연결을 사용합니다. ## --host-rules=`rules` -Hostname 맵핑 규칙을 설정합니다. (`,`로 분리) +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 호스트) +* `MAP * 127.0.0.1` 강제적으로 모든 호스트네임을 127.0.0.1로 맵핑합니다 +* `MAP *.google.com proxy` 강제적으로 모든 google.com의 서브도메인을 "proxy"로 + 연결합니다 +* `MAP test.com [::1]:77` 강제적으로 "test.com"을 IPv6 루프백으로 연결합니다. + 소켓 주소의 포트 또한 77로 고정됩니다. +* `MAP * baz, EXCLUDE www.google.com` "www.google.com"을 제외한 모든 것들을 + "baz"로 맵핑합니다. + +이 맵핑은 네트워크 요청시의 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 인증서 에러를 무시합니다. @@ -84,26 +112,40 @@ Net log 이벤트를 활성화하고 `path`에 로그를 기록합니다. ## --ssl-version-fallback-min=`version` -Fallback SSL/TLS 최소 버전을 지정합니다. ("tls1", "tls1.1", "tls1.2") +TLS fallback에서 사용할 SSL/TLS 최소 버전을 지정합니다. ("tls1", "tls1.1", +"tls1.2") + +## --cipher-suite-blacklist=`cipher_suites` + +SSL 암호화를 비활성화할 대상 목록을 지정합니다. (`,`로 구분) + +## --enable-logging + +Chromium의 로그를 콘솔에 출력합니다. + +이 스위치는 어플리케이션이 로드되기 전에 분석 되므로 `app.commandLine.appendSwitch` +메서드에선 사용할 수 없습니다. 하지만 `ELECTRON_ENABLE_LOGGING` 환경 변수를 설정하면 +본 스위치를 지정한 것과 같은 효과를 낼 수 있습니다. ## --v=`log_level` -기본 V-logging 최대 활성화 레벨을 지정합니다. 기본값은 0입니다. 기본적으로 양수를 레벨로 사용합니다. +기본 V-logging 최대 활성화 레벨을 지정합니다. 기본값은 0입니다. 기본적으로 양수를 +레벨로 사용합니다. -`--v=-1`를 사용하면 로깅이 비활성화 됩니다. +이 스위치는 `--enable-logging` 스위치를 같이 지정해야 작동합니다. ## --vmodule=`pattern` `--v` 옵션에 전달된 값을 덮어쓰고 모듈당 최대 V-logging 레벨을 지정합니다. -예를 들어 `my_module=2,foo*=3`는 `my_module.*`, `foo*.*`와 같은 파일 이름 패턴을 가진 모든 소스 코드들의 로깅 레벨을 각각 2와 3으로 설정합니다. +예를 들어 `my_module=2,foo*=3`는 `my_module.*`, `foo*.*`와 같은 파일 이름 패턴을 +가진 모든 소스 코드들의 로깅 레벨을 각각 2와 3으로 설정합니다. -또한 슬래시(`/`) 또는 백슬래시(`\`)를 포함하는 패턴은 지정한 경로에 대해 패턴을 테스트 합니다. -예를 들어 `*/foo/bar/*=2` 표현식은 `foo/bar` 디렉터리 안의 모든 소스 코드의 로깅 레벨을 2로 지정합니다. +또한 슬래시(`/`) 또는 백슬래시(`\`)를 포함하는 패턴은 지정한 경로에 대해 패턴을 테스트 +합니다. 예를 들어 `*/foo/bar/*=2` 표현식은 `foo/bar` 디렉터리 안의 모든 소스 코드의 +로깅 레벨을 2로 지정합니다. -모든 크로미움과 관련된 로그를 비활성화하고 어플리케이션의 로그만 활성화 하려면 다음과 같이 코드를 작성하면 됩니다: +이 스위치는 `--enable-logging` 스위치를 같이 지정해야 작동합니다. - -```javascript -app.commandLine.appendSwitch('v', -1); -app.commandLine.appendSwitch('vmodule', 'console=0'); -``` +[app]: app.md +[append-switch]: app.md#appcommandlineappendswitchswitch-value +[ready]: app.md#event-ready diff --git a/docs-translations/ko-KR/api/clipboard.md b/docs-translations/ko-KR/api/clipboard.md index 080078274e01..2a0c98da2c09 100644 --- a/docs-translations/ko-KR/api/clipboard.md +++ b/docs-translations/ko-KR/api/clipboard.md @@ -1,16 +1,17 @@ # clipboard -`clipboard` 모듈은 복사/붙여넣기 작업을 수행하는 방법을 제공합니다. 다음 예제는 클립보드에 문자열을 씁니다: +`clipboard` 모듈은 복사/붙여넣기 작업을 수행하는 방법을 제공합니다. 다음 예제는 +클립보드에 문자열을 씁니다: ```javascript -var clipboard = require('clipboard'); +const clipboard = require('electron').clipboard; clipboard.writeText('Example String'); ``` -X Window 시스템에선 selection 클립보드도 존재합니다. 이를 사용하려면 인자 뒤에 `selection` 문자열을 같이 지정해주어야 합니다: +X Window 시스템에선 selection 클립보드도 존재합니다. 이를 사용하려면 인자 뒤에 +`selection` 문자열을 같이 지정해주어야 합니다: ```javascript -var clipboard = require('clipboard'); clipboard.writeText('Example String', 'selection'); console.log(clipboard.readText('selection')); ``` @@ -19,7 +20,8 @@ console.log(clipboard.readText('selection')); `clipboard` 모듈은 다음과 같은 메서드를 가지고 있습니다: -**참고:** Experimental 마크가 붙은 API는 실험적인 기능이며 차후 최신 버전에서 제거될 수 있습니다. +**참고:** Experimental 마크가 붙은 API는 실험적인 기능이며 차후 최신 버전에서 제거될 +수 있습니다. ### `clipboard.readText([type])` @@ -78,7 +80,6 @@ console.log(clipboard.readText('selection')); 클립보드가 지정한 `data`의 형식을 지원하는지 확인합니다. ```javascript -var clipboard = require('clipboard'); console.log(clipboard.has('

selection

')); ``` @@ -98,7 +99,6 @@ console.log(clipboard.has('

selection

')); * `type` String (optional) ```javascript -var clipboard = require('clipboard'); clipboard.write({text: 'test', html: "test"}); ``` diff --git a/docs-translations/ko-KR/api/content-tracing.md b/docs-translations/ko-KR/api/content-tracing.md index 08221fbc7076..ed73ce319f68 100644 --- a/docs-translations/ko-KR/api/content-tracing.md +++ b/docs-translations/ko-KR/api/content-tracing.md @@ -1,12 +1,19 @@ # contentTracing -`content-tracing` 모듈은 Chromium 컨텐츠 모듈단에서 생성된 데이터를 수집하고 추적하는데 사용됩니다. -이 모듈은 웹 인터페이스를 포함하고 있지 않으며 크롬 브라우저에서 `chrome://tracing/` 페이지를 열어 생성된 파일을 로드하면 결과를 볼 수 있습니다. +`content-tracing` 모듈은 Chromium 컨텐츠 모듈단에서 생성된 데이터를 수집하고 +추적하는데 사용됩니다. 이 모듈은 웹 인터페이스를 포함하고 있지 않으며 크롬 +브라우저에서 `chrome://tracing/` 페이지를 열어 생성된 파일을 로드하면 결과를 볼 수 +있습니다. ```javascript -var contentTracing = require('content-tracing'); +const contentTracing = require('electron').contentTracing; -contentTracing.startRecording('*', contentTracing.DEFAULT_OPTIONS, function() { +const options = { + categoryFilter: '*', + traceOptions: 'record-until-full,enable-sampling' +}; + +contentTracing.startRecording(options, function() { console.log('Tracing started'); setTimeout(function() { @@ -27,7 +34,8 @@ contentTracing.startRecording('*', contentTracing.DEFAULT_OPTIONS, function() { 카테고리 그룹 세트를 가져옵니다. 카테고리 그룹은 도달된 코드 경로를 변경할 수 있습니다. -모든 child 프로세스가 `getCategories` 요청을 승인하면 `callback`이 한 번 호출되며 인자에 카테고리 그룹의 배열이 전달됩니다. +모든 child 프로세스가 `getCategories` 요청을 승인하면 `callback`이 한 번 호출되며 +인자에 카테고리 그룹의 배열이 전달됩니다. ### `contentTracing.startRecording(options, callback)` @@ -38,12 +46,13 @@ contentTracing.startRecording('*', contentTracing.DEFAULT_OPTIONS, function() { 모든 프로세스에서 레코딩을 시작합니다. -레코딩은 지역적으로 즉시 실행됩니다. 그리고 비동기로 child 프로세스는 곧 EnableRecording 요청을 받게 됩니다. -모든 child 프로세스가 `startRecording` 요청을 승인하면 `callback`이 한 번 호출됩니다. +레코딩은 지역적으로 즉시 실행됩니다. 그리고 비동기로 child 프로세스는 곧 +EnableRecording 요청을 받게 됩니다. 모든 child 프로세스가 `startRecording` 요청을 +승인하면 `callback`이 한 번 호출됩니다. `categoryFilter`는 어떤 카테고리 그룹이 트레이싱 되어야 하는지 필터링할 수 있습니다. -필터는 `-` 접두사를 통해 특정 카테고리 그룹을 제외할 수 있습니다. -카테고리 패턴은 같은 리스트 내에서 포함과 제외를 함께 사용할 수 없습니다. +필터는 `-` 접두사를 통해 특정 카테고리 그룹을 제외할 수 있습니다. 카테고리 패턴은 같은 +리스트 내에서 포함과 제외를 함께 사용할 수 없습니다. 예제: @@ -51,7 +60,8 @@ contentTracing.startRecording('*', contentTracing.DEFAULT_OPTIONS, function() { * `test_MyTest*,test_OtherStuff`, * `"-excluded_category1,-excluded_category2` -`traceOptions`은 어떤 종류의 트레이싱을 사용할 수 있는지 지정하고 콤마로 리스트를 구분합니다. +`traceOptions`은 어떤 종류의 트레이싱을 사용할 수 있는지 지정하고 콤마로 리스트를 +구분합니다. 사용할 수 있는 옵션은 다음과 같습니다: @@ -62,12 +72,13 @@ contentTracing.startRecording('*', contentTracing.DEFAULT_OPTIONS, function() { * `enable-systrace` 첫번째부터 3번째까지의 옵션은 추적 레코딩 모드입니다. 이에 따라 상호 배타적입니다. -만약 레코딩 모드가 한 개 이상 지정되면 마지막 지정한 모드만 사용됩니다. -어떤 모드도 설정되지 않았다면 `record-until-full` 모드가 기본으로 사용됩니다. +만약 레코딩 모드가 한 개 이상 지정되면 마지막 지정한 모드만 사용됩니다. 어떤 모드도 +설정되지 않았다면 `record-until-full` 모드가 기본으로 사용됩니다. 추적 옵션은 `traceOptions`이 파싱되어 적용되기 전까지 다음과 같은 기본값이 사용됩니다. -`record-until-full`이 기본 모드, `enable-sampling`과 `enable-systrace`옵션은 포함되지 않음 +`record-until-full`이 기본 모드, `enable-sampling`과 `enable-systrace`옵션은 +포함되지 않음 ## `contentTracing.stopRecording(resultFilePath, callback)` @@ -76,15 +87,18 @@ contentTracing.startRecording('*', contentTracing.DEFAULT_OPTIONS, function() { 모든 프로세스에서 레코딩을 중지합니다. -Child 프로세스는 일반적으로 추적 데이터와 희귀한 플러시 그리고 추적 데이터를 메인 프로세스로 보내는 작업에 대해 캐싱 합니다. -이러한 일을 하는 이유는 IPC를 통해 추적 데이터를 보내는 작업은 매우 비싼 연산을 동반하기 때문입니다. -우리는 추적에 의한 런타임 오버헤드를 피하고자 합니다. -그래서 추적이 끝나면 모든 child 프로세스에 보류된 추적 데이터를 플러시 할 것인지 물어봅니다. +Child 프로세스는 일반적으로 추적 데이터와 희귀한 플러시 그리고 추적 데이터를 메인 +프로세스로 보내는 작업에 대해 캐싱 합니다. 이러한 일을 하는 이유는 IPC를 통해 추적 +데이터를 보내는 작업은 매우 비싼 연산을 동반하기 때문입니다. 우리는 추적에 의한 런타임 +오버헤드를 피하고자 합니다. 그래서 추적이 끝나면 모든 child 프로세스에 보류된 추적 +데이터를 플러시 할 것인지 물어봅니다. -모든 child 프로세스가 `stopRecording` 요청을 승인하면 `callback`에 추적 데이터 파일을 포함하여 한 번 호출됩니다. +모든 child 프로세스가 `stopRecording` 요청을 승인하면 `callback`에 추적 데이터 +파일을 포함하여 한 번 호출됩니다. -추적 데이터는 `resultFilePath` 해당 경로가 비어있는 경우에 한 해 해당 경로에 작성되거나 임시 파일에 작성됩니다. -실제 파일 경로는 null이 아닌 이상 `callback`을 통해 전달됩니다. +추적 데이터는 `resultFilePath` 해당 경로가 비어있는 경우에 한 해 해당 경로에 +작성되거나 임시 파일에 작성됩니다. 실제 파일 경로는 null이 아닌 이상 `callback`을 +통해 전달됩니다. ### `contentTracing.startMonitoring(options, callback)` @@ -95,7 +109,8 @@ Child 프로세스는 일반적으로 추적 데이터와 희귀한 플러시 모든 프로세스에서 모니터링을 시작합니다. -모니터링은 지역적으로 즉시 시작됩니다. 그리고 이내 자식 프로세스들이 `startMonitoring` 비동기 요청을 받습니다. +모니터링은 지역적으로 즉시 시작됩니다. 그리고 이내 자식 프로세스들이 +`startMonitoring` 비동기 요청을 받습니다. 모든 자식 프로세스가 `startMonitoring` 요청을 승인하면 `callback`이 한 번 호출됩니다. @@ -114,18 +129,21 @@ Child 프로세스는 일반적으로 추적 데이터와 희귀한 플러시 현재 모니터링 추적 데이터를 가져옵니다. -자식 프로세스들은 일반적으로 추적 데이터를 캐싱하며 드물게 플러시 하거나 메인 프로세스로 추적 데이터를 보냅니다. -왜냐하면 IPC를 통해 추적 데이터를 보내는데에는 많은 자원을 소비하기 때문입니다. -그리고 우리는 추적시 발생하는 불필요한 런타임 오버헤드를 피하고자 합니다. -그래서 추적이 끝나면 반드시 비동기로 자식 프로세스들의 보류된 추적 데이터를 플러시 할 것인지 물어봅니다. +자식 프로세스들은 일반적으로 추적 데이터를 캐싱하며 드물게 플러시 하거나 메인 +프로세스로 추적 데이터를 보냅니다. 왜냐하면 IPC를 통해 추적 데이터를 보내는데에는 +많은 자원을 소비하기 때문입니다. 그리고 우리는 추적시 발생하는 불필요한 런타임 +오버헤드를 피하고자 합니다. 그래서 추적이 끝나면 반드시 비동기로 자식 프로세스들의 +보류된 추적 데이터를 플러시 할 것인지 물어봅니다. -모든 자식 프로세스가 `captureMonitoringSnapshot` 요청을 승인하면 추적 데이터 파일을 포함하는 `callback`이 한 번 호출됩니다. +모든 자식 프로세스가 `captureMonitoringSnapshot` 요청을 승인하면 추적 데이터 파일을 +포함하는 `callback`이 한 번 호출됩니다. ### `contentTracing.getTraceBufferUsage(callback)` * `callback` Function -추적 버퍼 % 전체 상태의 프로세스간 최대치를 가져옵니다. TraceBufferUsage 값이 결정되면 `callback`이 한 번 호출됩니다. +추적 버퍼 % 전체 상태의 프로세스간 최대치를 가져옵니다. TraceBufferUsage 값이 +결정되면 `callback`이 한 번 호출됩니다. ### `contentTracing.setWatchEvent(categoryName, eventName, callback)` @@ -137,4 +155,5 @@ Child 프로세스는 일반적으로 추적 데이터와 희귀한 플러시 ### `contentTracing.cancelWatchEvent()` -Watch 이벤트를 중단합니다. 만약 추적이 활성화되어 있다면 이 메서드는 watch 이벤트 콜백과 race가 일어날 것입니다. +Watch 이벤트를 중단합니다. 만약 추적이 활성화되어 있다면 이 메서드는 watch 이벤트 +콜백과 race가 일어날 것입니다. diff --git a/docs-translations/ko-KR/api/crash-reporter.md b/docs-translations/ko-KR/api/crash-reporter.md index 3a7bc35f0982..a4d42f013366 100644 --- a/docs-translations/ko-KR/api/crash-reporter.md +++ b/docs-translations/ko-KR/api/crash-reporter.md @@ -1,16 +1,17 @@ # crashReporter -`crash-reporter` 모듈은 어플리케이션의 크래시 정보를 자동으로 원격 서버에 업로드하는데 사용합니다. +`crash-reporter` 모듈은 어플리케이션의 크래시 정보를 자동으로 원격 서버에 +업로드하는데 사용합니다. 다음 예제는 윈격 서버에 어플리케이션 크래시 정보를 자동으로 보고하는 예제입니다: ```javascript -var crashReporter = require('crash-reporter'); +const crashReporter = require('electron').crashReporter; crashReporter.start({ productName: 'YourName', companyName: 'YourCompany', - submitUrl: 'https://your-domain.com/url-to-submit', + submitURL: 'https://your-domain.com/url-to-submit', autoSubmit: true }); ``` @@ -25,7 +26,7 @@ crashReporter.start({ * `productName` String, 기본값: Electron * `companyName` String, 기본값: GitHub, Inc -* `submitUrl` String, 기본값: http://54.249.141.255:1127/post +* `submitURL` String, 기본값: http://54.249.141.255:1127/post * 크래시 리포트는 POST 방식으로 이 URL로 전송됩니다. * `autoSubmit` Boolean, 기본값: true * true로 지정할 경우 유저의 승인 없이 자동으로 오류를 보고합니다. @@ -34,12 +35,13 @@ crashReporter.start({ * 크래시 리포트 시 같이 보낼 추가 정보를 지정하는 객체입니다. * 문자열로 된 속성만 정상적으로 보내집니다. * 중첩 객체는 지원되지 않습니다. (Nested objects are not supported) - + 다른 crashReporter API를 사용하기 전에 이 메서드를 먼저 호출해야 합니다. -**참고:** OS X에선 Windows와 Linux의 `breakpad`와 달리 새로운 `crashpad` 클라이언트를 사용합니다. -오류 수집 기능을 활성화 시키려면 오류를 수집하고 싶은 메인 프로세스나 랜더러 프로세스에서 -`crashReporter.start` 메서드를 호출하여 `crashpad`를 초기화 해야합니다. +**참고:** OS X에선 Windows와 Linux의 `breakpad`와 달리 새로운 `crashpad` +클라이언트를 사용합니다. 오류 수집 기능을 활성화 시키려면 오류를 수집하고 싶은 메인 +프로세스나 랜더러 프로세스에서 `crashReporter.start` 메서드를 호출하여 `crashpad`를 +초기화 해야합니다. ### `crashReporter.getLastCrashReport()` @@ -52,13 +54,12 @@ crashReporter.start({ ## crash-reporter 업로드 형식 -Crash Reporter는 다음과 같은 데이터를 `submitUrl`에 `POST` 방식으로 전송합니다: +Crash Reporter는 다음과 같은 데이터를 `submitURL`에 `POST` 방식으로 전송합니다: -* `rept` String - 예시 'electron-crash-service' * `ver` String - Electron의 버전 * `platform` String - 예시 'win32' * `process_type` String - 예시 'renderer' -* `ptime` Number +* `guid` String - e.g. '5e1286fc-da97-479e-918b-6bfb0c3d1c72' * `_version` String - `package.json`내의 `version` 필드 * `_productName` String - Crash Reporter의 `options` 객체에서 정의한 제품명. * `prod` String - 기본 제품의 이름. 이 경우 Electron으로 표시됩니다. diff --git a/docs-translations/ko-KR/api/dialog.md b/docs-translations/ko-KR/api/dialog.md index 08530c054797..bb92c1f43e89 100644 --- a/docs-translations/ko-KR/api/dialog.md +++ b/docs-translations/ko-KR/api/dialog.md @@ -1,17 +1,19 @@ # dialog -`dialog` 모듈은 파일 열기, 알림과 같은 네이티브 시스템의 대화 상자를 조작할 때 사용할 수 있는 모듈입니다. -이 모듈을 사용하면 웹 어플리케이션에서 일반 네이티브 어플리케이션과 비슷한 사용자 경험을 제공할 수 있습니다. +`dialog` 모듈은 파일 열기, 알림과 같은 네이티브 시스템의 대화 상자를 조작할 때 사용할 +수 있는 모듈입니다. 이 모듈을 사용하면 웹 어플리케이션에서 일반 네이티브 어플리케이션과 +비슷한 사용자 경험을 제공할 수 있습니다. 다음 예제는 파일과 디렉터리를 다중으로 선택하는 대화 상자를 표시하는 예제입니다: ```javascript var win = ...; // 대화 상자를 사용할 BrowserWindow 객체 -var dialog = require('dialog'); +const dialog = require('electron').dialog; console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', 'multiSelections' ]})); ``` -**OS X 알림**: 대화 상자를 시트처럼 보여지게 하려면 `browserWindow` 인자에 `BrowserWindow` 객체의 참조를 제공하면 됩니다. +**OS X 참고**: 대화 상자를 시트처럼 보여지게 하려면 `browserWindow` 인자에 +`BrowserWindow` 객체의 참조를 제공하면 됩니다. ## Methods @@ -25,15 +27,18 @@ console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', ' * `defaultPath` String * `filters` Array * `properties` Array - 대화 상자가 사용할 기능(모드)이 담긴 배열입니다. - 다음을 포함할 수 있습니다: `openFile`, `openDirectory`, `multiSelections`, `createDirectory` + 다음을 포함할 수 있습니다: `openFile`, `openDirectory`, `multiSelections`, + `createDirectory` * `callback` Function (optional) -사용할 대화 상자의 기능이 담긴 배열입니다. 다음을 포함할 수 있습니다: `openFile`, `openDirectory`, `multiSelections`, `createDirectory` +사용할 대화 상자의 기능이 담긴 배열입니다. 다음을 포함할 수 있습니다: `openFile`, +`openDirectory`, `multiSelections`, `createDirectory` -작업에 성공하면 콜백으로 유저가 선택한 파일의 경로를 포함한 배열을 반환합니다. 그 외엔 `undefined`를 반환합니다. +작업에 성공하면 콜백으로 유저가 선택한 파일의 경로를 포함한 배열을 반환합니다. 그 외의 +경우엔 `undefined`를 반환합니다. -`filters`를 지정하면 유저가 선택 가능한 파일 형식을 지정할 수 있습니다. -유저가 선택할 수 있는 타입에 제한을 두려면 다음과 같이 할 수 있습니다: +`filters`를 지정하면 유저가 선택 가능한 파일 형식을 지정할 수 있습니다. 유저가 선택할 +수 있는 타입에 제한을 두려면 다음과 같이 할 수 있습니다: ```javascript { @@ -46,14 +51,17 @@ console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', ' } ``` -`extensions` 배열은 반드시 와일드카드와 마침표를 제외한 파일 확장자를 포함시켜야 합니다. -(예를 들어 `'png'`는 가능하지만 `'.png'`와 `'*.png'`는 안됩니다) -모든 파일을 보여주려면 `'*'`와 같은 와일드카드를 사용하면 됩니다. (다른 와일드카드는 지원하지 않습니다) +`extensions` 배열은 반드시 와일드카드와 마침표를 제외한 파일 확장자를 포함시켜야 +합니다. (예를 들어 `'png'`는 가능하지만 `'.png'`와 `'*.png'`는 안됩니다) 모든 파일을 +보여주려면 `'*'`와 같은 와일드카드를 사용하면 됩니다. (다른 와일드카드는 지원하지 + 않습니다) -`callback`이 전달되면 메소드가 비동기로 작동되며 결과는 `callback(filenames)`을 통해 전달됩니다. +`callback`이 전달되면 메서드가 비동기로 작동되며 결과는 `callback(filenames)`을 +통해 전달됩니다. -**참고:** Windows와 Linux에선 파일 선택 모드, 디렉터리 선택 모드를 동시에 사용할 수 없습니다. -이러한 이유로 `properties`를 `['openFile', 'openDirectory']`로 설정하면 디렉터리 선택 대화 상자가 표시됩니다. +**참고:** Windows와 Linux에선 파일 선택 모드, 디렉터리 선택 모드를 동시에 사용할 수 +없습니다. 이러한 이유로 `properties`를 `['openFile', 'openDirectory']`로 설정하면 +디렉터리 선택 대화 상자가 표시됩니다. ### `dialog.showSaveDialog([browserWindow][, options][, callback])` @@ -64,43 +72,52 @@ console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', ' * `filters` Array * `callback` Function (optional) -작업에 성공하면 콜백으로 유저가 선택한 파일의 경로를 포함한 배열을 반환합니다. 그 외엔 `undefined`를 반환합니다. +작업에 성공하면 콜백으로 유저가 선택한 파일의 경로를 포함한 배열을 반환합니다. 그 외엔 +`undefined`를 반환합니다. -`filters`를 지정하면 유저가 저장 가능한 파일 형식을 지정할 수 있습니다. 사용 방법은 `dialog.showOpenDialog`의 `filters` 속성과 같습니다. +`filters`를 지정하면 유저가 저장 가능한 파일 형식을 지정할 수 있습니다. 사용 방법은 +`dialog.showOpenDialog`의 `filters` 속성과 같습니다. -`callback`이 전달되면 메소드가 비동기로 작동되며 결과는 `callback(filename)`을 통해 전달됩니다. +`callback`이 전달되면 메서드가 비동기로 작동되며 결과는 `callback(filename)`을 통해 전달됩니다. ### `dialog.showMessageBox([browserWindow][, options][, callback])` * `browserWindow` BrowserWindow * `options` Object - * `type` String - `"none"`, `"info"`, `"error"`, `"question"`, `"warning"` 중 하나를 사용할 수 있습니다. - Windows에선 따로 `icon`을 설정하지 않은 이상 "question"과 "info"는 같은 아이콘으로 표시됩니다. + * `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로 지정하면 됩니다. + * `cancelId` Integer - 유저가 대화 상자의 버튼을 클릭하지 않고 대화 상자를 취소했을 + 때 반환되는 버튼의 인덱스입니다. 기본적으로 버튼 리스트가 "cancel" 또는 "no" + 라벨을 가지고 있을 때 해당 버튼의 인덱스를 반환합니다. 따로 두 라벨이 지정되지 + 않은 경우 0을 반환합니다. OS X와 Windows에선 `cancelId` 지정 여부에 상관없이 + "Cancel" 버튼이 언제나 `cancelId`로 지정됩니다. + * `noLink` Boolean - Windows Electron은 "Cancel"이나 "Yes"와 같은 흔히 사용되는 + 버튼을 찾으려고 시도하고 대화 상자 내에서 해당 버튼을 커맨드 링크처럼 만듭니다. + 이 기능으로 앱을 좀 더 Modern Windows 앱처럼 만들 수 있습니다. 이 기능을 원하지 + 않으면 `noLink`를 true로 지정하면 됩니다. * `callback` Function -대화 상자를 표시합니다. `browserWindow`를 지정하면 대화 상자가 완전히 닫힐 때까지 지정한 창을 사용할 수 없습니다. -완료 시 유저가 선택한 버튼의 인덱스를 반환합니다. +대화 상자를 표시합니다. `browserWindow`를 지정하면 대화 상자가 완전히 닫힐 때까지 +지정한 창을 사용할 수 없습니다. 완료 시 유저가 선택한 버튼의 인덱스를 반환합니다. -역주: 부정을 표현하는 "아니오", "취소"와 같은 한글 단어는 지원되지 않습니다. -만약 OS X 또는 Windows에서 "확인", "취소"와 같은 순서로 버튼을 지정하게 될 때 Alt + f4로 해당 대화 상자를 끄게 되면 "확인"을 누른 것으로 판단되어 버립니다. -이를 해결하려면 "Cancel"을 대신 사용하거나 BrowserWindow API를 사용하여 대화 상자를 직접 구현해야합니다. +역주: 부정을 표현하는 "아니오", "취소"와 같은 한글 단어는 지원되지 않습니다. 만약 +OS X 또는 Windows에서 "확인", "취소"와 같은 순서로 버튼을 지정하게 될 때 Alt + f4로 +해당 대화 상자를 끄게 되면 "확인"을 누른 것으로 판단되어 버립니다. 이를 해결하려면 +"Cancel"을 대신 사용하거나 BrowserWindow API를 사용하여 대화 상자를 직접 구현해야 +합니다. -`callback`이 전달되면 메소드가 비동기로 작동되며 결과는 `callback(response)`을 통해 전달됩니다. +`callback`이 전달되면 메서드가 비동기로 작동되며 결과는 `callback(response)`을 통해 +전달됩니다. ### `dialog.showErrorBox(title, content)` 에러 메시지를 보여주는 대화 상자를 표시합니다. -이 API는 `app` 모듈의 `ready` 이벤트가 발생하기 전에 사용할 수 있습니다. -이 메소드는 보통 어플리케이션이 시작되기 전에 특정한 에러를 표시하기 위해 사용됩니다. +이 API는 `app` 모듈의 `ready` 이벤트가 발생하기 전에 사용할 수 있습니다. 이 메서드는 +보통 어플리케이션이 시작되기 전에 특정한 에러를 표시하기 위해 사용됩니다. diff --git a/docs-translations/ko-KR/api/download-item.md b/docs-translations/ko-KR/api/download-item.md new file mode 100644 index 000000000000..a367d0e000bd --- /dev/null +++ b/docs-translations/ko-KR/api/download-item.md @@ -0,0 +1,100 @@ +# DownloadItem + +`DownloadItem`은 EventEmitter를 상속받았으며 Electron의 다운로드 아이템을 표현합니다. +이 클래스 객체는 `Session` 모듈의 `will-download` 이벤트에 사용되며 사용자가 다운로드 +아이템을 다룰 수 있도록 도와줍니다. + +```javascript +// 메인 프로세스 +win.webContents.session.on('will-download', function(event, item, webContents) { + // Set the save path, making Electron not to prompt a save dialog. + item.setSavePath('/tmp/save.pdf'); + console.log(item.getMimeType()); + console.log(item.getFilename()); + console.log(item.getTotalBytes()); + item.on('updated', function() { + console.log('Received bytes: ' + item.getReceivedBytes()); + }); + item.on('done', function(e, state) { + if (state == "completed") { + console.log("Download successfully"); + } else { + console.log("Download is cancelled or interrupted that can't be resumed"); + } + }); +``` + +## Events + +### Event: 'updated' + +`downloadItem`이 갱신될 때 발생하는 이벤트입니다. + +### Event: 'done' + +* `event` Event +* `state` String + * `completed` - 다운로드가 성공적으로 완료되었습니다. + * `cancelled` - 다운로드가 취소되었습니다. + * `interrupted` - 다운로드 중 파일 서버로부터의 연결이 끊겼습니다. + +다운로드가 종료될 때 발생하는 이벤트입니다. 이 이벤트는 다운로드 중 문제가 발생하여 +중단되거나, 모두 성공적으로 완료된 경우, `downloadItem.cancel()` 같은 메서드를 통해 +취소하는 경우의 종료 작업이 모두 포함됩니다. + +## Methods + +`downloadItem` 객체는 다음과 같은 메서드를 가지고 있습니다: + +### `downloadItem.setSavePath(path)` + +* `path` String - 다운로드 아이템을 저장할 파일 경로를 지정합니다. + +이 API는 세션의 `will-download` 콜백 함수에서만 사용할 수 있습니다. 사용자가 API를 +통해 아무 경로도 설정하지 않을 경우 Electron은 기본 루틴 파일 저장을 실행합니다. +(파일 대화 상자를 엽니다) + +### `downloadItem.pause()` + +다운로드를 일시 중지합니다. + +### `downloadItem.resume()` + +중디된 다운로드를 재개합니다. + +### `downloadItem.cancel()` + +다운로드를 취소합니다. + +### `downloadItem.getURL()` + +다운로드 아이템의 URL을 표현하는 문자열을 반환합니다. + +### `downloadItem.getMimeType()` + +다우로드 아이템의 MIME 타입을 표현하는 문자열을 반환합니다. + +### `downloadItem.hasUserGesture()` + +현재 다운로드가 유저 제스쳐(작업)로인한 다운로드인지 여부를 반환합니다. + +### `downloadItem.getFilename()` + +다운로드 아이템의 파일 이름을 표현하는 문자열을 반환합니다. + +**참고:** 실제 파일 이름과 로컬 디스크에 저장되는 파일의 이름은 서로 다를 수 있습니다. +예를 들어 만약 사용자가 파일을 저장할 때 파일 이름을 바꿨다면 실제 파일 이름과 저장 +파일 이름은 서로 달라지게 됩니다. + +### `downloadItem.getTotalBytes()` + +현재 아이템의 전체 다운로드 크기를 정수로 반환합니다. 크기가 unknown이면 0을 +반환합니다. + +### `downloadItem.getReceivedBytes()` + +현재 아이템의 다운로드 완료된 바이트 값을 정수로 반환합니다. + +### `downloadItem.getContentDisposition()` + +응답 헤더에서 Content-Disposition 필드를 문자열로 반환합니다. diff --git a/docs-translations/ko-KR/api/environment-variables.md b/docs-translations/ko-KR/api/environment-variables.md new file mode 100644 index 000000000000..11d350692161 --- /dev/null +++ b/docs-translations/ko-KR/api/environment-variables.md @@ -0,0 +1,49 @@ +# 환경 변수 + +Electron의 몇몇 동작은 명령 줄과 어플리케이션의 코드보다 먼저 초기화되어야 하므로 환경 변수에 의해 작동합니다. + +POSIX 쉘의 예시입니다: + +```bash +$ export ELECTRON_ENABLE_LOGGING=true +$ electron +``` + +Windows 콘솔의 예시입니다: + +```powershell +> set ELECTRON_ENABLE_LOGGING=true +> electron +``` + +## `ELECTRON_RUN_AS_NODE` + +프로세스를 일반 Node.js 프로세스처럼 시작합니다. (electron 모듈 제외) + +## `ELECTRON_ENABLE_LOGGING` + +Chrome의 내부 로그를 콘솔에 출력합니다. + +## `ELECTRON_ENABLE_STACK_DUMPING` + +Electron이 크래시되면, 콘솔에 stack trace를 출력합니다. + +이 환경 변수는 `crashReporter`가 시작되지 않았을 경우 작동하지 않습니다. + +## `ELECTRON_DEFAULT_ERROR_MODE` _Windows_ + +Electron이 크래시되면, 크래시 정보 창을 표시합니다. + +이 환경 변수는 `crashReporter`가 시작되지 않았을 경우 작동하지 않습니다. + +## `ELECTRON_NO_ATTACH_CONSOLE` _Windows_ + +현재 콘솔 세션에 소속시키지 않습니다. + +## `ELECTRON_FORCE_WINDOW_MENU_BAR` _Linux_ + +Linux의 글로벌 메뉴 막대를 사용하지 않습니다. + +## `ELECTRON_HIDE_INTERNAL_MODULES` + +`require('ipc')`같은 예전 방식의 빌트인 모듈을 비활성화합니다. diff --git a/docs-translations/ko-KR/api/file-object.md b/docs-translations/ko-KR/api/file-object.md index 6006220647dd..a2a19a530d4a 100644 --- a/docs-translations/ko-KR/api/file-object.md +++ b/docs-translations/ko-KR/api/file-object.md @@ -1,8 +1,8 @@ # `File` 객체 -DOM의 File 인터페이스는 네이티브 파일을 추상화 합니다. -유저가 직접 HTML5 File API를 이용하여 작업할 때 선택된 파일의 경로를 알 수 있도록 -Electron은 파일의 실제 경로를 담은 `path` 속성을 File 인터페이스에 추가하였습니다. +DOM의 File 인터페이스는 네이티브 파일을 추상화 합니다. 유저가 직접 HTML5 File API를 +이용하여 작업할 때 선택된 파일의 경로를 알 수 있도록 Electron은 파일의 실제 경로를 +담은 `path` 속성을 File 인터페이스에 추가하였습니다. 다음 예제는 앱으로 드래그 앤 드롭한 파일의 실제 경로를 가져옵니다: diff --git a/docs-translations/ko-KR/api/frameless-window.md b/docs-translations/ko-KR/api/frameless-window.md index 64422460775f..e13794e0fdfd 100644 --- a/docs-translations/ko-KR/api/frameless-window.md +++ b/docs-translations/ko-KR/api/frameless-window.md @@ -1,31 +1,35 @@ # Frameless Window -Frameless Window는 [테두리](https://developer.mozilla.org/en-US/docs/Glossary/Chrome)가 없는 창입니다. -이 기능은 윈도우 창의 일부분인 툴바와 같이 웹 페이지의 일부분이 아닌 부분을 보이지 않도록 합니다. -[`BrowserWindow`](browser-window.md) 클래스의 옵션에서 설정할 수 있습니다. +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`로 지정하면 됩니다: +Frameless Window를 만드려면 [BrowserWindow](browser-window.md) 객체의 +`options` 객체에서 `frame` 옵션을 `false`로 지정하면 됩니다: ```javascript -var BrowserWindow = require('browser-window'); +const BrowserWindow = require('electron').BrowserWindow; var win = new BrowserWindow({ width: 800, height: 600, frame: false }); ``` -### 최신 Mac에서 사용할 수 있는 대안 +### 최신 OS X에서 사용할 수 있는 대안 -OS X 10.10 Yosemite 이후의 최신 버전부터는 테두리가 없는 창을 만들 때 새로운 방법을 사용할 수 있습니다. -`frame` 옵션을 `false`로 지정하여 제목과 창 구성 요소를 모두 비활성화하는 대신 새로운 `title-bar-style` -옵션을 통해 제목만 숨기고 창 구성 요소("흔히 신호등으로 알고 있는")의 기능과 창 크기를 그대로 유지할 수 있습니다: +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' }); +var win = new BrowserWindow({ 'title-bar-style': 'hidden' }); +``` ## 투명한 창 만들기 -Frameless Window의 창의 배경을 투명하게 만들고 싶다면 `transparent` 옵션을 `true`로 바꿔주기만 하면됩니다: +Frameless Window 창의 배경을 투명하게 만들고 싶다면 `transparent` 옵션을 `true`로 +바꿔주기만 하면됩니다: ```javascript var win = new BrowserWindow({ transparent: true, frame: false }); @@ -33,30 +37,38 @@ 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(데스크톱 창 관리자)가 비활성화되어 있을 경우 투명한 창이 작동하지 않습니다. +* 투명한 영역을 통과하여 클릭할 수 없습니다. 우리는 이 문제를 해결하기 위해 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를 사용할 수 있도록 해줍니다. + 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`를 지정해서 드래그 불가능 영역을 만들 수도 있습니다. 현재 사각형 형태의 범위만 지원합니다. +기본적으로 Frameless Window는 드래그 할 수 없습니다. 어플리케이션의 CSS에서 특정 +범위를 `-webkit-app-region: drag`로 지정하면 OS의 기본 타이틀 바 처럼 드래그 되도록 +할 수 있습니다. 그리고 `-webkit-app-region: no-drag`를 지정해서 드래그 불가능 영역을 +만들 수도 있습니다. 현재 사각형 형태의 범위만 지원합니다. -창 전체를 드래그 가능하게 만드려면 `-webkit-app-region: drag`을 `body`의 스타일에 지정하면 됩니다: +창 전체를 드래그 가능하게 만드려면 `-webkit-app-region: drag`을 `body`의 스타일에 +지정하면 됩니다: ```html ``` -참고로 창 전체를 드래그 영역으로 지정할 경우 사용자가 버튼을 클릭할 수 없게 되므로 버튼은 드래그 불가능 영역으로 지정해야 합니다: +참고로 창 전체를 드래그 영역으로 지정할 경우 사용자가 버튼을 클릭할 수 없게 되므로 +버튼은 드래그 불가능 영역으로 지정해야 합니다: ```css button { @@ -64,13 +76,15 @@ button { } ``` -따로 커스텀 타이틀 바를 만들어 사용할 때는 타이틀 바 내부의 모든 버튼을 드래그 불가능 영역으로 지정해야 합니다. +따로 커스텀 타이틀 바를 만들어 사용할 때는 타이틀 바 내부의 모든 버튼을 드래그 불가 +영역으로 지정해야 합니다. ## 텍스트 선택 -Frameless Window에서 텍스트가 선택되는 드래그 동작은 혼란을 야기할 수 있습니다. -예를 들어 타이틀 바를 드래그 할 때 타이틀 바의 텍스트를 실수로 선택할 수 있습니다. -이를 방지하기 위해 다음과 같이 드래그 영역의 텍스트 선택 기능을 비활성화해야 할 필요가 있습니다: +Frameless Window에서 텍스트가 선택되는 드래그 동작은 혼란을 야기할 수 있습니다. 예를 +들어 타이틀 바를 드래그 할 때 타이틀 바의 텍스트를 실수로 선택할 수 있습니다. 이를 +방지하기 위해 다음과 같이 드래그 영역의 텍스트 선택 기능을 비활성화해야 할 필요가 +있습니다: ```css .titlebar { @@ -81,5 +95,7 @@ Frameless Window에서 텍스트가 선택되는 드래그 동작은 혼란을 ## 컨텍스트 메뉴 -몇몇 플랫폼에선 드래그 가능 영역이 non-client 프레임으로 처리됩니다. 이러한 플랫폼에선 드래그 가능 영역에서 오른쪽 클릭 할 경우 시스템 메뉴가 팝업 됩니다. -이러한 이유로 컨텍스트 메뉴 지정 시 모든 플랫폼에서 정상적으로 작동하게 하려면 커스텀 컨텍스트 메뉴를 드래그 영역 내에 만들어선 안됩니다. +몇몇 플랫폼에선 드래그 가능 영역이 non-client 프레임으로 처리됩니다. 이러한 플랫폼에선 +드래그 가능 영역에서 오른쪽 클릭 할 경우 시스템 메뉴가 팝업 됩니다. 이러한 이유로 +컨텍스트 메뉴 지정 시 모든 플랫폼에서 정상적으로 작동하게 하려면 커스텀 컨텍스트 메뉴를 +드래그 영역 내에 만들어선 안됩니다. diff --git a/docs-translations/ko-KR/api/global-shortcut.md b/docs-translations/ko-KR/api/global-shortcut.md index 84c1c70798d7..9617d463e87e 100644 --- a/docs-translations/ko-KR/api/global-shortcut.md +++ b/docs-translations/ko-KR/api/global-shortcut.md @@ -1,18 +1,23 @@ # global-shortcut -`global-shortcut` 모듈은 운영체제의 전역 키보드 단축키를 등록/해제 하는 방법을 제공합니다. -이 모듈을 사용하여 사용자가 다양한 작업을 편하게 할 수 있도록 단축키를 정의 할 수 있습니다. +`global-shortcut` 모듈은 운영체제의 전역 키보드 단축키를 등록/해제 하는 방법을 +제공합니다. 이 모듈을 사용하여 사용자가 다양한 작업을 편하게 할 수 있도록 단축키를 +정의 할 수 있습니다. -**참고:** 등록된 단축키는 어플리케이션이 백그라운드로 작동(창이 포커스 되지 않음) 할 때도 계속해서 작동합니다. -이 모듈은 `app` 모듈의 `ready` 이벤트 이전에 사용할 수 없습니다. +**참고:** 등록된 단축키는 어플리케이션이 백그라운드로 작동(창이 포커스 되지 않음) 할 +때도 계속해서 작동합니다. 이 모듈은 `app` 모듈의 `ready` 이벤트 이전에 사용할 수 +없습니다. ```javascript -var app = require('app'); -var globalShortcut = require('global-shortcut'); +const electron = require('electron'); +const app = electron.app; +const globalShortcut = electron.globalShortcut; app.on('ready', function() { // 'ctrl+x' 단축키를 리스너에 등록합니다. - var ret = globalShortcut.register('ctrl+x', function() { console.log('ctrl+x is pressed'); }) + var ret = globalShortcut.register('ctrl+x', function() { + console.log('ctrl+x is pressed'); + }); if (!ret) { console.log('registration failed'); @@ -40,13 +45,19 @@ app.on('will-quit', function() { * `accelerator` [Accelerator](accelerator.md) * `callback` Function -`accelerator`로 표현된 전역 단축키를 등록합니다. 유저로부터 등록된 단축키가 눌렸을 경우 `callback` 함수가 호출됩니다. +`accelerator`로 표현된 전역 단축키를 등록합니다. 유저로부터 등록된 단축키가 눌렸을 +경우 `callback` 함수가 호출됩니다. `accelerator` 단축키가 등록되었을 경우 +`true`를 반환합니다. 그 외엔 `false`를 반환합니다. 예를 들어 지정한 +`accelerator`가 이미 다른 호출자 또는 네이티브 어플리케이션에서 등록된 상태를 +생각할 수 있습니다. + ### `globalShortcut.isRegistered(accelerator)` * `accelerator` [Accelerator](accelerator.md) -지정된 `accelerator` 단축키가 등록되었는지 여부를 확인합니다. 반환값은 boolean(true, false) 입니다. +지정된 `accelerator` 단축키가 등록되었는지 여부를 확인합니다. 반환값은 boolean값 +입니다. ### `globalShortcut.unregister(accelerator)` @@ -56,4 +67,4 @@ app.on('will-quit', function() { ### `globalShortcut.unregisterAll()` -모든 전역 단축키 등록을 해제합니다. +모든 전역 단축키의 등록을 해제합니다. diff --git a/docs-translations/ko-KR/api/ipc-main-process.md b/docs-translations/ko-KR/api/ipc-main-process.md deleted file mode 100644 index ad4e545ce474..000000000000 --- a/docs-translations/ko-KR/api/ipc-main-process.md +++ /dev/null @@ -1,71 +0,0 @@ -# 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-KR/api/ipc-main.md b/docs-translations/ko-KR/api/ipc-main.md new file mode 100644 index 000000000000..6eb0d5527582 --- /dev/null +++ b/docs-translations/ko-KR/api/ipc-main.md @@ -0,0 +1,69 @@ +# ipcMain + +`ipcMain` 모듈은 메인 프로세스에서 사용할 때 랜더러 프로세스(웹 페이지)에서 전달된 +동기/비동기 메시지를 주고 받는 방법을 제공합니다. 랜더러 프로세스에서 메시지를 전달하면 +이 모듈을 통해 메시지를 받을 수 있습니다. + +## 메시지 전송 + +물론 메시지를 받는 것 말고도 메인 프로세스에서 랜더러 프로세스로 보내는 것도 가능합니다. +자세한 내용은 [webContents.send](web-contents.md#webcontentssendchannel-args)를 +참고하세요. + +* 메시지를 전송할 때 이벤트 이름은 `channel`이 됩니다. +* 메시지에 동기로 응답할 땐 반드시 `event.returnValue`를 설정해야 합니다. +* 메시지를 비동기로 응답할 땐 `event.sender.send(...)` 메서드를 사용할 수 있습니다. + +다음 예제는 랜더러 프로세스와 메인 프로세스간에 메시지를 전달하고 받는 예제입니다: + +```javascript +// 메인 프로세스 +const ipcMain = require('electron').ipcMain; +ipcMain.on('asynchronous-message', function(event, arg) { + console.log(arg); // "ping" 출력 + event.sender.send('asynchronous-reply', 'pong'); +}); + +ipcMain.on('synchronous-message', function(event, arg) { + console.log(arg); // "ping" 출력 + event.returnValue = 'pong'; +}); +``` + +```javascript +// 랜더러 프로세스 (웹 페이지) +const ipcRenderer = require('electron').ipcRenderer; +console.log(ipc.sendSync('synchronous-message', 'ping')); // "pong" 출력 + +ipcRenderer.on('asynchronous-reply', function(arg) { + console.log(arg); // "pong" 출력 +}); +ipcRenderer.send('asynchronous-message', 'ping'); +``` + +## 메시지 리스닝 + +`ipcMain`은 다음과 같은 이벤트 리스닝 메서드를 가지고 있습니다: + +### `ipcMain.on(channel, callback)` + +* `channel` String - 이벤트 이름 +* `callback` Function + +이벤트가 발생하면 `event` 객체와 `arg` 메시지와 함께 `callback`이 호출됩니다. + +## IPC 이벤트 + +`callback`에서 전달된 `event` 객체는 다음과 같은 메서드와 속성을 가지고 있습니다: + +### `event.returnValue` + +이 메시지를 지정하면 동기 메시지를 전달합니다. + +### `event.sender` + +메시지를 보낸 `webContents` 객체를 반환합니다. `event.sender.send` 메서드를 통해 +비동기로 메시지를 전달할 수 있습니다. 자세한 내용은 +[webContents.send][webcontents-send]를 참고하세요. + +[webcontents-send]: web-contents.md#webcontentssendchannel-args diff --git a/docs-translations/ko-KR/api/ipc-renderer.md b/docs-translations/ko-KR/api/ipc-renderer.md index 67864c4e1157..69c7936d66ea 100644 --- a/docs-translations/ko-KR/api/ipc-renderer.md +++ b/docs-translations/ko-KR/api/ipc-renderer.md @@ -1,45 +1,53 @@ -# ipc (renderer) +# ipcRenderer -`ipc` (renderer) 모듈은 메인 프로세스로 동기 또는 비동기 메시지를 보내고 받는 방법을 제공합니다. -물론 메인 프로세스로부터 받은 메시지에 응답할 수도 있습니다. +`ipcRenderer` 모듈은 랜더러 프로세스에서 메인 프로세스로 동기/비동기 메시지를 주고 +받는 방법을 제공합니다. 또한 메인 프로세스로부터 받은 메시지에 응답할 수도 있습니다. -**참고:** 만약 랜더러 프로세스에서 메인 프로세스의 모듈을 직접적 사용하고 싶다면 [remote](remote.md) 모듈을 사용하는 것을 고려해보는 것이 좋습니다. +[ipcMain](ipc-main.md)에서 코드 예제를 확인할 수 있습니다. -[ipc (main process)](ipc-main-process.md)에서 예제를 확인 할 수 있습니다. +## 메시지 리스닝 -## Methods +`ipcRenderer`는 다음과 같은 이벤트 리스닝 메서드를 가지고 있습니다: -`ipc` 모듈은 다음과 같은 메서드를 가지고 있습니다: +### `ipcRenderer.on(channel, callback)` -**참고:** 이 메소드들을 사용하여 `message`를 보낼 땐 반드시 메인 프로세스의 -[`ipc (main process)`](ipc-main-process.md) 모듈에서도 이벤트 리스너를 등록해 두어야 합니다. +* `channel` String - 이벤트 이름 +* `callback` Function -### `ipc.send(channel[, arg1][, arg2][, ...])` +이벤트가 일어나면 `event` 객체와 임의의 인자와 함께 `callback` 함수가 호출됩니다. + +## 메시지 보내기 + +`ipcRenderer`는 다음과 같은 메시지 전송 메서드를 가지고 있습니다: + +### `ipcRenderer.send(channel[, arg1][, arg2][, ...])` * `channel` String - 이벤트 이름 * `arg` (optional) -`channel`을 통해 메인 프로세스에 비동기 메시지를 보냅니다. -옵션으로 `arg`에 한 개 또는 여러 개의 메시지를 포함할 수 있습니다. 모든 타입을 사용할 수 있습니다. -메인 프로세스는 `ipc`를 통해 `channel` 이벤트를 리스닝 할 수 있습니다. +`channel`을 통해 메인 프로세스에 비동기 메시지를 보냅니다. 그리고 필요에 따라 임의의 +인자를 사용할 수도 있습니다. 메인 프로세스는 `ipcMain` 모듈의 `channel` 이벤트를 통해 +이벤트를 리스닝 할 수 있습니다. -### `ipc.sendSync(channel[, arg1][, arg2][, ...])` +### `ipcRenderer.sendSync(channel[, arg1][, arg2][, ...])` * `channel` String - 이벤트 이름 * `arg` (optional) -`channel`을 통해 메인 프로세스에 동기 메시지를 보냅니다. -옵션으로 `arg`에 한 개 또는 여러 개의 메시지를 포함할 수 있습니다. 모든 타입을 사용할 수 있습니다. -메인 프로세스는 `ipc`를 통해 `channel` 이벤트를 리스닝 할 수 있습니다. +`channel`을 통해 메인 프로세스에 동기 메시지를 보냅니다. 그리고 필요에 따라 임의의 +인자를 사용할 수도 있습니다. 메인 프로세스는 `ipc`를 통해 `channel` 이벤트를 리스닝 +할 수 있습니다. -메인 프로세스에선 `ipc` 모듈의 `channel` 이벤트를 통해 받은 `event.returnValue`로 회신 할 수 있습니다. +메인 프로세스에선 `ipcMain` 모듈의 `channel` 이벤트를 통해 받은 +`event.returnValue`로 회신 할 수 있습니다. -**참고:** 동기 메시징은 모든 랜더러 프로세스의 작업을 일시 중단시킵니다. 이 메서드를 사용하는 것을 권장하지 않습니다. +__참고:__ 동기 메서드는 모든 랜더러 프로세스의 작업을 일시 중단시킵니다. 사용 목적이 +확실하지 않다면 사용하지 않는 것이 좋습니다. -### `ipc.sendToHost(channel[, arg1][, arg2][, ...])` +### `ipcRenderer.sendToHost(channel[, arg1][, arg2][, ...])` * `channel` String - 이벤트 이름 * `arg` (optional) -`ipc.send`와 비슷하지만 이벤트를 메인 프로세스 대신 호스트 페이지내의 ``로 보냅니다. -옵션으로 `arg`에 한 개 또는 여러 개의 메시지를 포함할 수 있습니다. 모든 타입을 사용할 수 있습니다. +`ipcRenderer.send`와 비슷하지만 이벤트를 메인 프로세스 대신 호스트 페이지내의 +`` 요소로 보냅니다. diff --git a/docs-translations/ko-KR/api/menu-item.md b/docs-translations/ko-KR/api/menu-item.md index 8f1021009954..388df1c7d571 100644 --- a/docs-translations/ko-KR/api/menu-item.md +++ b/docs-translations/ko-KR/api/menu-item.md @@ -1,6 +1,7 @@ # MenuItem -`menu-item` 모듈은 어플리케이션 또는 컨텐츠 [`menu`](menu.md)에 아이템을 추가할 수 있도록 관련 클래스를 제공합니다. +`menu-item` 모듈은 어플리케이션 또는 컨텍스트 [`menu`](menu.md)에 항목 아이템을 +추가할 수 있도록 관련 클래스를 제공합니다. [`menu`](menu.md)에서 예제를 확인할 수 있습니다. @@ -11,9 +12,12 @@ ### new MenuItem(options) * `options` Object - * `click` Function - 메뉴 아이템이 클릭될 때 `click(menuItem, browserWindow)` 형태로 호출 되는 콜백 함수 - * `role` String - 메뉴 아이템의 액션을 정의합니다. 이 속성을 지정하면 `click` 속성이 무시됩니다. - * `type` String - `MenuItem`의 타입 `normal`, `separator`, `submenu`, `checkbox` 또는 `radio` 사용가능 + * `click` Function - 메뉴 아이템이 클릭될 때 `click(menuItem, browserWindow)` + 형태로 호출 되는 콜백 함수 + * `role` String - 메뉴 아이템의 액션을 정의합니다. 이 속성을 지정하면 `click` + 속성이 무시됩니다. + * `type` String - `MenuItem`의 타입 `normal`, `separator`, `submenu`, + `checkbox` 또는 `radio` 사용가능 * `label` String * `sublabel` String * `accelerator` [Accelerator](accelerator.md) @@ -21,15 +25,17 @@ * `enabled` Boolean * `visible` Boolean * `checked` Boolean - * `submenu` Menu - 보조메뉴를 설정합니다. `type`이 `submenu`일 경우 반드시 설정해야합니다. 일반 메뉴 아이템일 경우 생략할 수 있습니다. - * `id` String - 현재 메뉴 아이템에 대해 유일키를 지정합니다. 이 키는 이후 `position` 옵션에서 사용할 수 있습니다. - * `position` String - 미리 지정한 `id`를 이용하여 메뉴 아이템의 위치를 세밀하게 조정합니다. + * `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. +메뉴 아이템을 생성할 때, 다음 목록과 일치하는 표준 동작은 수동으로 직접 구현하는 대신 +`role` 속성을 지정하여 고유 OS 경험을 최대한 살릴 수 있습니다. -The `role` property can have following values: +`role` 속성은 다음 값을 가질 수 있습니다: * `undo` * `redo` @@ -37,16 +43,16 @@ The `role` property can have following values: * `copy` * `paste` * `selectall` -* `minimize` - Minimize current window -* `close` - Close current window +* `minimize` - 현재 윈도우를 최소화합니다 +* `close` - 현재 윈도우를 닫습니다 -On OS X `role` can also have following additional values: +OS X에서의 `role`은 다음 값을 추가로 가질 수 있습니다: -* `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 +* `about` - `orderFrontStandardAboutPanel` 액션에 대응 +* `hide` - `hide` 액션에 대응 +* `hideothers` - `hideOtherApplications` 액션에 대응 +* `unhide` - `unhideAllApplications` 액션에 대응 +* `front` - `arrangeInFront` 액션에 대응 +* `window` - 부 메뉴를 가지는 "Window" 메뉴 +* `help` - 부 메뉴를 가지는 "Help" 메뉴 +* `services` - 부 메뉴를 가지는 "Services" 메뉴 diff --git a/docs-translations/ko-KR/api/menu.md b/docs-translations/ko-KR/api/menu.md index a8ac499f5768..f01021ae1082 100644 --- a/docs-translations/ko-KR/api/menu.md +++ b/docs-translations/ko-KR/api/menu.md @@ -1,19 +1,21 @@ # Menu -`menu` 클래스는 어플리케이션 메뉴와 [컨텍스트 메뉴](https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/PopupGuide/ContextMenus)를 만들 때 사용됩니다. -이 모듈은 메인 프로세스용 모듈이지만 `remote` 모듈을 통해 랜더러 프로세스에서도 사용할 수 있습니다. +`menu` 클래스는 어플리케이션 메뉴와 [컨텍스트 메뉴](https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/PopupGuide/ContextMenus)를 +만들 때 사용됩니다. 이 모듈은 메인 프로세스용 모듈이지만 `remote` 모듈을 통해 랜더러 +프로세스에서도 사용할 수 있습니다. 각 메뉴는 여러 개의 [메뉴 아이템](menu-item.md)으로 구성되고 서브 메뉴를 가질 수도 있습니다. -다음 예제는 웹 페이지 내에서 [remote](remote.md) 모듈을 활용하여 동적으로 메뉴를 생성하는 예제입니다. -그리고 유저가 페이지에서 오른쪽 클릭을 할 때마다 마우스 위치에 팝업 형태로 메뉴를 표시합니다: +다음 예제는 웹 페이지 내에서 [remote](remote.md) 모듈을 활용하여 동적으로 메뉴를 +생성하는 예제입니다. 그리고 유저가 페이지에서 오른쪽 클릭을 할 때마다 마우스 위치에 +팝업 형태로 메뉴를 표시합니다: ```html ``` -또 하나의 예를 들자면 다음 예제는 랜더러 프로세스에서 template API를 사용하여 어플리케이션 메뉴를 만듭니다: +또 하나의 예를 들자면 다음 예제는 랜더러 프로세스에서 template API를 사용하여 +어플리케이션 메뉴를 만듭니다: ```javascript var template = [ @@ -130,14 +133,14 @@ var template = [ submenu: [ { label: 'Learn More', - click: function() { require('shell').openExternal('http://electron.atom.io') } + click: function() { require('electron').shell.openExternal('http://electron.atom.io') } }, ] }, ]; if (process.platform == 'darwin') { - var name = require('app').getName(); + var name = require('electron').app.getName(); template.unshift({ label: name, submenu: [ @@ -210,14 +213,15 @@ Menu.setApplicationMenu(menu); * `menu` Menu -지정한 `menu`를 어플리케이션 메뉴로 만듭니다. OS X에선 상단바에 표시되며 Windows와 Linux에선 각 창의 상단에 표시됩니다. +지정한 `menu`를 어플리케이션 메뉴로 만듭니다. OS X에선 상단바에 표시되며 Windows와 +Linux에선 각 창의 상단에 표시됩니다. ### `Menu.sendActionToFirstResponder(action)` _OS X_ * `action` String -`action`을 어플리케이션의 first responder에 전달합니다. -이 메서드는 Cocoa 메뉴 동작을 에뮬레이트 하는데 사용되며 보통 `MenuItem`의 `selector` 속성에 사용됩니다. +`action`을 어플리케이션의 first responder에 전달합니다. 이 메서드는 Cocoa 메뉴 +동작을 에뮬레이트 하는데 사용되며 보통 `MenuItem`의 `role` 속성에 사용됩니다. **참고:** 이 메서드는 OS X에서만 사용할 수 있습니다. @@ -225,19 +229,20 @@ Menu.setApplicationMenu(menu); * `template` Array -기본적으로 `template`는 [MenuItem](menu-item.md)을 생성할 때 사용하는 `options`의 배열입니다. 사용법은 위에서 설명한 것과 같습니다. +기본적으로 `template`는 [MenuItem](menu-item.md)을 생성할 때 사용하는 `options`의 +배열입니다. 사용법은 위에서 설명한 것과 같습니다. -또한 `template`에는 다른 속성도 추가할 수 있으며 메뉴가 만들어질 때 해당 메뉴 아이템의 프로퍼티로 변환됩니다. +또한 `template`에는 다른 속성도 추가할 수 있으며 메뉴가 만들어질 때 해당 메뉴 아이템의 +프로퍼티로 변환됩니다. -### `Menu.popup(browserWindow[, x, y])` +### `Menu.popup([browserWindow, x, y])` -* `browserWindow` BrowserWindow +* `browserWindow` BrowserWindow (optional) * `x` Number (optional) -* `y` Number (만약 `x`를 지정했을 경우 `y`도 필수로 지정해야 합니다) +* `y` Number (만약 `x`를 지정했을 경우 반드시 `y`도 지정해야 합니다) -메뉴를 `browserWindow` 내부 팝업으로 표시합니다. -옵션으로 메뉴를 표시할 `(x,y)` 좌표를 지정할 수 있습니다. -따로 좌표를 지정하지 않은 경우 마우스 커서 위치에 표시됩니다. +메뉴를 `browserWindow` 내부 팝업으로 표시합니다. 옵션으로 메뉴를 표시할 `(x,y)` +좌표를 지정할 수 있습니다. 따로 좌표를 지정하지 않은 경우 마우스 커서 위치에 표시됩니다. ### `Menu.append(menuItem)` @@ -259,13 +264,14 @@ Menu.setApplicationMenu(menu); ## OS X 어플리케이션 메뉴에 대해 알아 둬야 할 것들 OS X에선 Windows, Linux와 달리 완전히 다른 어플리케이션 메뉴 스타일을 가지고 있습니다. -그래서 어플리케이션을 네이티브처럼 작동할 수 있도록 하기 위해 다음 몇 가지 유의 사항을 숙지해야 합니다. +그래서 어플리케이션을 네이티브처럼 작동할 수 있도록 하기 위해 다음 몇 가지 유의 사항을 +숙지해야 합니다. ### 기본 메뉴 -OS X엔 `Services`나 `Windows`와 같은 많은 시스템 지정 기본 메뉴가 있습니다. -기본 메뉴를 만들려면 반드시 다음 리스트 중 한 가지를 선택하여 메뉴의 `role`로 지정해야 합니다. -그러면 Electron이 자동으로 인식하여 해당 메뉴를 기본 메뉴로 만듭니다: +OS X엔 `Services`나 `Windows`와 같은 많은 시스템 지정 기본 메뉴가 있습니다. 기본 +메뉴를 만들려면 반드시 다음 리스트 중 한 가지를 선택하여 메뉴의 `role`로 지정해야 +합니다. 그러면 Electron이 자동으로 인식하여 해당 메뉴를 기본 메뉴로 만듭니다: * `window` * `help` @@ -273,29 +279,38 @@ OS X엔 `Services`나 `Windows`와 같은 많은 시스템 지정 기본 메뉴 ### 메뉴 아이템 기본 동작 -OS X는 몇가지 메뉴 아이템에 대해 `About xxx`, `Hide xxx`, `Hide Others`와 같은 기본 동작을 제공하고 있습니다. -메뉴 아이템의 기본 동작을 지정하려면 반드시 메뉴 아이템의 `role` 속성을 지정해야 합니다. +OS X는 몇가지 메뉴 아이템에 대해 `About xxx`, `Hide xxx`, `Hide Others`와 같은 +기본 동작을 제공하고 있습니다. 메뉴 아이템의 기본 동작을 지정하려면 반드시 메뉴 +아이템의 `role` 속성을 지정해야 합니다. ### 메인 메뉴의 이름 -OS X에선 지정한 어플리케이션 메뉴에 상관없이 메뉴의 첫번째 라벨은 언제나 어플리케이션의 이름이 됩니다. -어플리케이션 이름을 변경하려면 앱 번들내의 `Info.plist` 파일을 수정해야합니다. -자세한 내용은 [About Information Property List Files][AboutInformationPropertyListFiles] 문서를 참고하세요. +OS X에선 지정한 어플리케이션 메뉴에 상관없이 메뉴의 첫번째 라벨은 언제나 어플리케이션의 +이름이 됩니다. 어플리케이션 이름을 변경하려면 앱 번들내의 `Info.plist` 파일을 수정해야 +합니다. 자세한 내용은 [About Information Property List Files][AboutInformationPropertyListFiles] 문서를 참고하세요. ## 메뉴 아이템 위치 -`Menu.buildFromTemplate`로 메뉴를 만들 때 `position`과 `id`를 사용해서 아이템의 위치를 지정할 수 있습니다. +`Menu.buildFromTemplate`로 메뉴를 만들 때 `position`과 `id`를 사용해서 아이템의 +위치를 지정할 수 있습니다. -`MenuItem`의 `position` 속성은 `[placement]=[id]`와 같은 형식을 가지며 `placement`는 -`before`, `after`, `endof` 속성 중 한가지를 사용할 수 있고 `id`는 메뉴 아이템이 가지는 유일 ID 입니다: +`MenuItem`의 `position` 속성은 `[placement]=[id]`와 같은 형식을 가지며 +`placement`는 `before`, `after`, `endof` 속성 중 한가지를 사용할 수 있고 `id`는 +메뉴 아이템이 가지는 유일 ID 입니다: -* `before` - 이 아이템을 지정한 id 이전의 위치에 삽입합니다. 만약 참조된 아이템이 없을 경우 메뉴의 맨 뒤에 삽입됩니다. -* `after` - 이 아이템을 지정한 id 다음의 위치에 삽입합니다. 만약 참조된 아이템이 없을 경우 메뉴의 맨 뒤에 삽입됩니다. -* `endof` - 이 아이템을 id의 논리 그룹에 맞춰서 각 그룹의 항목 뒤에 삽입합니다. (그룹은 분리자 아이템에 의해 만들어집니다) - 만약 참조된 아이템의 분리자 그룹이 존재하지 않을 경우 지정된 id로 새로운 분리자 그룹을 만든 후 해당 그룹의 뒤에 삽입됩니다. +* `before` - 이 아이템을 지정한 id 이전의 위치에 삽입합니다. 만약 참조된 아이템이 + 없을 경우 메뉴의 맨 뒤에 삽입됩니다. +* `after` - 이 아이템을 지정한 id 다음의 위치에 삽입합니다. 만약 참조된 아이템이 + 없을 경우 메뉴의 맨 뒤에 삽입됩니다. +* `endof` - 이 아이템을 id의 논리 그룹에 맞춰서 각 그룹의 항목 뒤에 삽입합니다. + (그룹은 분리자 아이템에 의해 만들어집니다) 만약 참조된 아이템의 분리자 그룹이 + 존재하지 않을 경우 지정된 id로 새로운 분리자 그룹을 만든 후 해당 그룹의 뒤에 + 삽입됩니다. -위치를 지정한 아이템의 뒤에 위치가 지정되지 않은 아이템이 있을 경우 각 아이템의 위치가 지정되기 전까지 모든 아이템이 위치가 지정된 아이템의 뒤에 삽입됩니다. -따라서 위치를 이동하고 싶은 특정 그룹의 아이템들이 있을 경우 해당 그룹의 맨 첫번째 메뉴 아이템의 위치만을 지정하면 됩니다. +위치를 지정한 아이템의 뒤에 위치가 지정되지 않은 아이템이 있을 경우 각 아이템의 위치가 +지정되기 전까지 모든 아이템이 위치가 지정된 아이템의 뒤에 삽입됩니다. 따라서 위치를 +이동하고 싶은 특정 그룹의 아이템들이 있을 경우 해당 그룹의 맨 첫번째 메뉴 아이템의 +위치만을 지정하면 됩니다. ### 예제 diff --git a/docs-translations/ko-KR/api/native-image.md b/docs-translations/ko-KR/api/native-image.md index 423cedb98300..917dbb3a71b8 100644 --- a/docs-translations/ko-KR/api/native-image.md +++ b/docs-translations/ko-KR/api/native-image.md @@ -1,19 +1,19 @@ -# NativeImage +# nativeImage -Electron은 파일 경로 또는 `NativeImage` 인스턴스를 통해 이미지를 사용할 수 있는 API를 가지고 있습니다. -`null`을 전달할 경우 빈 이미지가 생성됩니다. +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`로 트레이 메뉴를 생성합니다: +이 예제는 클립보드로부터 가져온 `nativeImage`로 트레이 메뉴를 생성합니다: ```javascript -var clipboard = require('clipboard'); var image = clipboard.readImage(); var appIcon = new Tray(image); ``` @@ -27,11 +27,15 @@ var appIcon = new Tray(image); ## 고해상도 이미지 -플랫폼이 high-DPI를 지원하는 경우 `@2x`와 같이 이미지의 파일명 뒤에 접미사를 추가하여 고해상도 이미지로 지정할 수 있습니다. +플랫폼이 high-DPI를 지원하는 경우 `@2x`와 같이 이미지의 파일명 뒤에 접미사를 추가하여 +고해상도 이미지로 지정할 수 있습니다. -예를 들어 `icon.png` 라는 기본 해상도의 이미지를 기준으로 크기를 두 배로 늘린 이미지를 `icon@2x.png` 처럼 지정하면 고해상도 이미지로 처리됩니다. +예를 들어 `icon.png` 라는 기본 해상도의 이미지를 기준으로 크기를 두 배로 늘린 이미지를 +`icon@2x.png` 처럼 지정하면 고해상도 이미지로 처리됩니다. -서로 다른 해상도(DPI)의 이미지를 같이 지원하고 싶다면 다중 해상도의 이미지를 접미사를 붙여 한 폴더에 같이 넣으면 됩니다. 이 이미지를 사용(로드)할 땐 따로 접미사를 붙이지 않습니다: +서로 다른 해상도(DPI)의 이미지를 같이 지원하고 싶다면 다중 해상도의 이미지를 접미사를 +붙여 한 폴더에 같이 넣으면 됩니다. 이 이미지를 사용(로드)할 땐 따로 접미사를 붙이지 +않습니다: ```text images/ @@ -61,53 +65,56 @@ var appIcon = new Tray('/Users/somebody/images/icon.png'); ## 템플릿 이미지 -템플릿 이미지는 검은색과 명확한 색상(알파 채널)으로 이루어져 있습니다. -템플릿 이미지는 단독 이미지로 사용되지 않고 다른 컨텐츠와 혼합되어 최종 외관 만드는데 사용됩니다. +템플릿 이미지는 검은색과 명확한 색상(알파 채널)으로 이루어져 있습니다. 템플릿 이미지는 +단독 이미지로 사용되지 않고 다른 컨텐츠와 혼합되어 최종 외관 만드는데 사용됩니다. -가장 일반적으로 템플릿 이미지는 밝고 어두운 테마 색상으로 변경할 수 있는 메뉴 바 아이콘 등에 사용되고 있습니다. +가장 일반적으로 템플릿 이미지는 밝고 어두운 테마 색상으로 변경할 수 있는 메뉴 바 아이콘 +등에 사용되고 있습니다. **참고:** 템플릿 이미지는 OS X 운영체제만 지원합니다. -템플릿 이미지를 지정하려면 다음 예제와 같이 파일명에 `Template` 문자열을 추가해야 합니다: +템플릿 이미지를 지정하려면 다음 예제와 같이 파일명에 `Template` 문자열을 추가해야 +합니다: * `xxxTemplate.png` * `xxxTemplate@2x.png` ## Methods -`NativeImage` 클래스는 다음과 같은 메서드를 가지고 있습니다: +`nativeImage` 클래스는 다음과 같은 메서드를 가지고 있습니다: -### `NativeImage.createEmpty()` +### `nativeImage.createEmpty()` -빈 `NativeImage` 인스턴스를 만듭니다. +빈 `nativeImage` 인스턴스를 만듭니다. -### `NativeImage.createFromPath(path)` +### `nativeImage.createFromPath(path)` * `path` String -`path`로부터 이미지를 로드하여 새로운 `NativeImage` 인스턴스를 만듭니다. +`path`로부터 이미지를 로드하여 새로운 `nativeImage` 인스턴스를 만듭니다. -### `NativeImage.createFromBuffer(buffer[, scaleFactor])` +### `nativeImage.createFromBuffer(buffer[, scaleFactor])` * `buffer` [Buffer][buffer] * `scaleFactor` Double (optional) -`buffer`로부터 이미지를 로드하여 새로운 `NativeImage` 인스턴스를 만듭니다. `scaleFactor`는 1.0이 기본입니다. +`buffer`로부터 이미지를 로드하여 새로운 `nativeImage` 인스턴스를 만듭니다. +`scaleFactor`의 기본값은 1.0 입니다. -### `NativeImage.createFromDataUrl(dataUrl)` +### `nativeImage.createFromDataURL(dataURL)` -* `dataUrl` String +* `dataURL` String -`dataUrl`로부터 이미지를 로드하여 새로운 `NativeImage` 인스턴스를 만듭니다. +`dataURL`로부터 이미지를 로드하여 새로운 `nativeImage` 인스턴스를 만듭니다. ## Instance Methods `nativeImage` 인스턴스 객체에서 사용할 수 있는 메서드 입니다: ```javascript -var NativeImage = require('native-image'); +const nativeImage = require('electron').nativeImage; -var image = NativeImage.createFromPath('/Users/somebody/images/icon.png'); +var image = nativeImage.createFromPath('/Users/somebody/images/icon.png'); ``` ### `image.toPng()` @@ -120,7 +127,7 @@ var image = NativeImage.createFromPath('/Users/somebody/images/icon.png'); `JPEG` 이미지를 인코딩한 데이터를 [Buffer][buffer]로 반환합니다. -### `image.toDataUrl()` +### `image.toDataURL()` 이미지를 data URL로 반환합니다. @@ -142,4 +149,4 @@ var image = NativeImage.createFromPath('/Users/somebody/images/icon.png'); 이미지가 템플릿 이미지인지 확인합니다. -[buffer]: https://iojs.org/api/buffer.html#buffer_class_buffer +[buffer]: https://nodejs.org/api/buffer.html#buffer_class_buffer diff --git a/docs-translations/ko-KR/api/power-monitor.md b/docs-translations/ko-KR/api/power-monitor.md index 990bf54e48c0..29a3aabc84a1 100644 --- a/docs-translations/ko-KR/api/power-monitor.md +++ b/docs-translations/ko-KR/api/power-monitor.md @@ -1,8 +1,9 @@ -# power-monitor +# powerMonitor `power-monitor` 모듈은 PC의 파워 상태를 나타냅니다. (주로 노트북 등에서 사용됩니다) -이 모듈은 메인 프로세스에서만 사용할 수 있으며, (remote 모듈(RPC)을 사용해도 작동이 됩니다) -메인 프로세스의 `app` 모듈에서 `ready` 이벤트를 호출하기 전까지 사용할 수 없습니다. +이 모듈은 메인 프로세스에서만 사용할 수 있으며, (remote 모듈(RPC)을 사용해도 작동이 +됩니다) 메인 프로세스의 `app` 모듈에서 `ready` 이벤트를 호출하기 전까지 사용할 수 +없습니다. 예제: @@ -10,7 +11,7 @@ var app = require('app'); app.on('ready', function() { - require('power-monitor').on('suspend', function() { + require('electron').powerMonitor.on('suspend', function() { console.log('절전모드로 진입합니다!'); }); }); diff --git a/docs-translations/ko-KR/api/power-save-blocker.md b/docs-translations/ko-KR/api/power-save-blocker.md index 0521a82b0e70..560b3fc9cef5 100644 --- a/docs-translations/ko-KR/api/power-save-blocker.md +++ b/docs-translations/ko-KR/api/power-save-blocker.md @@ -1,11 +1,12 @@ # powerSaveBlocker -`power-save-blocker` 모듈은 시스템이 저전력(슬립) 모드로 진입하는 것을 막고 앱 시스템과 화면이 항상 활성화 상태를 유지할 수 있도록 하는 몇가지 유틸리티를 제공하는 모듈입니다. +`powerSaveBlocker` 모듈은 시스템이 저전력(슬립) 모드로 진입하는 것을 막고 앱 시스템과 +화면이 항상 활성화 상태를 유지할 수 있도록 하는 몇가지 유틸리티를 제공하는 모듈입니다. 예제: ```javascript -var powerSaveBlocker = require('power-save-blocker'); +const powerSaveBlocker = require('electron').powerSaveBlocker; var id = powerSaveBlocker.start('prevent-display-sleep'); console.log(powerSaveBlocker.isStarted(id)); @@ -20,27 +21,36 @@ powerSaveBlocker.stop(id); ### `powerSaveBlocker.start(type)` * `type` String - Power save blocker 종류 - * `prevent-app-suspension` - 저전력 모드 등으로 인한 어플리케이션 작동 중단을 방지합니다. - 시스템을 항시 활성화 상태로 만듭니다. 하지만 화면은 자동으로 꺼질 수 있습니다. 사용 예시: 파일 다운로드, 음악 재생 등. - * `prevent-display-sleep`- 슬립 모드 등으로 인한 어플리케이션의 작동 중단을 방지합니다. - 시스템을 항시 활성화 상태로 만들고 슬립 모드(화면 꺼짐)를 방지합니다. 사용 예시: 비디오 재생 등. + * `prevent-app-suspension` - 저전력 모드 등으로 인한 어플리케이션 작동 중단을 + 방지합니다. 시스템을 항시 활성화 상태로 만듭니다. 하지만 화면은 자동으로 꺼질 수 + 있습니다. 사용 예시: 파일 다운로드, 음악 재생 등. + * `prevent-display-sleep`- 슬립 모드 등으로 인한 어플리케이션의 작동 중단을 + 방지합니다. 시스템을 항시 활성화 상태로 만들고 슬립 모드(화면 꺼짐)를 방지합니다. + 사용 예시: 비디오 재생 등. -시스템이 저전력 모드(슬립)로 진입하는 것을 막기 시작합니다. 정수로 된 식별 ID를 반환합니다. +시스템이 저전력 모드(슬립)로 진입하는 것을 막기 시작합니다. 정수로 된 식별 ID를 +반환합니다. -**참고:** `prevent-display-sleep` 모드는 `prevent-app-suspension` 보다 우선 순위가 높습니다. -두 모드 중 가장 높은 우선 순위의 모드만 작동합니다. 다시 말해 `prevent-display-sleep` 모드는 언제나 `prevent-app-suspension` 모드의 효과를 덮어씌웁니다. +**참고:** `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가 작동하기 시작합니다. +예를 들어 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. +* `id` Integer - `powerSaveBlocker.start`로부터 반환되는 power save blocker 식별 +ID. 설정한 power save blocker를 중지합니다. ### `powerSaveBlocker.isStarted(id)` -* `id` Integer - `powerSaveBlocker.start`로 부터 반환되는 power save blocker 식별 ID. +* `id` Integer - `powerSaveBlocker.start`로부터 반환되는 power save blocker 식별 +ID. 지정한 id의 `powerSaveBlocker`가 실행 중인지 확인합니다. diff --git a/docs-translations/ko-KR/api/process.md b/docs-translations/ko-KR/api/process.md index f557f8cc6be5..de24b5ac174e 100644 --- a/docs-translations/ko-KR/api/process.md +++ b/docs-translations/ko-KR/api/process.md @@ -2,10 +2,40 @@ Electron의 `process` 객체는 기존의 node와는 달리 약간의 차이점이 있습니다: -* `process.type` String - 프로세스의 타입, `browser` (메인 프로세스) 또는 `renderer`가 됩니다. +* `process.type` String - 프로세스의 타입, `browser` (메인 프로세스) 또는 + `renderer`가 됩니다. * `process.versions['electron']` String - Electron의 버전. * `process.versions['chrome']` String - Chromium의 버전. -* `process.resourcesPath` String - JavaScript 소스코드의 경로. +* `process.resourcesPath` String - JavaScript 소스 코드의 경로. +* `process.mas` Boolean - Mac 앱 스토어용 빌드일 때 `true`로 지정됩니다. 다른 + 빌드일 땐 `undefined`로 지정됩니다. + +## Events + +### Event: 'loaded' + +Electron 내부 초기화 스크립트의 로드가 완료되고, 웹 페이지나 메인 스크립트를 로드하기 +시작할 때 발생하는 이벤트입니다. + +이 이벤트는 preload 스크립트를 통해 node 통합이 꺼져있는 전역 스코프에 node의 전역 +심볼들을 다시 추가할 때 사용할 수 있습니다: + +```javascript +// preload.js +var _setImmediate = setImmediate; +var _clearImmediate = clearImmediate; +process.once('loaded', function() { + global.setImmediate = _setImmediate; + global.clearImmediate = _clearImmediate; +}); +``` + +## Properties + +### `process.noAsar` + +이 속성을 `true`로 지정하면 Node 빌트인 모듈의 `asar` 아카이브 지원을 비활성화 시킬 +수 있습니다. ## Methods @@ -19,4 +49,5 @@ Electron의 `process` 객체는 기존의 node와는 달리 약간의 차이점 * `maxDescriptors` Integer -`maxDescriptors`에 file descriptor 소프트 리미트를 설정하거나 OS 하드 리미트를 설정합니다. 값은 현재 프로세스에 대해 낮은 값이어야 합니다. +현재 프로세스 파일 디스크립터의 제한 값을 소프트 제한 `maxDescriptors`의 값이나 OS 하드 +제한 중 낮은 값으로 설정합니다. diff --git a/docs-translations/ko-KR/api/protocol.md b/docs-translations/ko-KR/api/protocol.md index d79963239585..a85ac1428ca6 100644 --- a/docs-translations/ko-KR/api/protocol.md +++ b/docs-translations/ko-KR/api/protocol.md @@ -1,15 +1,17 @@ # protocol -`protocol` 모듈은 이미 있는 프로토콜의 동작을 가로채거나 새로운 프로토콜을 만들 수 있는 기능을 제공합니다. +`protocol` 모듈은 이미 있는 프로토콜의 동작을 가로채거나 새로운 프로토콜을 만들 수 +있는 기능을 제공합니다. 다음 예제는 `file://` 프로토콜과 비슷한 일을 하는 커스텀 프로토콜을 설정합니다: ```javascript -var app = require('app'); -var path = require('path'); +const electron = require('electron'); +const app = electron.app; +const path = require('path'); app.on('ready', function() { - var protocol = require('protocol'); + var protocol = electron.protocol; protocol.registerFileProtocol('atom', function(request, callback) { var url = request.url.substr(7); callback({path: path.normalize(__dirname + '/' + url)}); @@ -30,8 +32,8 @@ app.on('ready', function() { * `schemes` Array - 표준 스킴으로 등록할 커스텀 스킴 리스트 -표준 `scheme`의 형식은 RFC 3986 [일반 URI 구문](https://tools.ietf.org/html/rfc3986#section-3) 표준을 따릅니다. -이 형식은 `file:`과 `filesystem:`을 포함합니다. +표준 `scheme`의 형식은 RFC 3986 [일반 URI 구문](https://tools.ietf.org/html/rfc3986#section-3) +표준을 따릅니다. 이 형식은 `file:`과 `filesystem:`을 포함합니다. ### `protocol.registerFileProtocol(scheme, handler[, completion])` @@ -39,20 +41,23 @@ app.on('ready', function() { * `handler` Function * `completion` Function (optional) -`scheme`에 파일을 응답으로 보내는 프로토콜을 등록합니다. -`handler`는 `scheme`와 함께 `request`가 생성될 때 `handler(request, callback)` 형식으로 호출됩니다. -`completion` 콜백은 `scheme`가 성공적으로 등록되었을 때 `completion(null)` 형식으로 호출되고 -등록에 실패했을 땐 `completion(error)` 형식으로 에러 내용을 담아 호출됩니다. +`scheme`에 파일을 응답으로 보내는 프로토콜을 등록합니다. `handler`는 `scheme`와 함께 +`request`가 생성될 때 `handler(request, callback)` 형식으로 호출됩니다. +`completion` 콜백은 `scheme`가 성공적으로 등록되었을 때 `completion(null)` 형식으로 +호출되고, 등록에 실패했을 땐 `completion(error)` 형식으로 에러 내용을 담아 호출됩니다. -`request`를 처리할 때 반드시 파일 경로 또는 `path` 속성을 포함하는 객체를 인자에 포함하여 `callback`을 호출해야 합니다. -예: `callback(filePath)` 또는 `callback({path: filePath})`. +`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)에서 확인할 수 있습니다. +만약 `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`을 사용하여 표준 스킴으로 처리되도록 할 수 있습니다. +기본적으로 `scheme`은 `http:`와 같이 처리됩니다. `file:`과 같이 "일반적인 URI 문법" +과는 다르게 인식되는 프로토콜은 `protocol.registerStandardSchemes`을 사용하여 표준 +스킴으로 처리되도록 할 수 있습니다. ### `protocol.registerBufferProtocol(scheme, handler[, completion])` @@ -60,8 +65,9 @@ app.on('ready', function() { * `handler` Function * `completion` Function (optional) -`scheme`에 `Buffer`를 응답으로 보내는 프로토콜을 등록합니다. -반드시 `Buffer` 또는 `data`, `mimeType`, `chart` 속성을 포함한 객체 중 하나를 인자에 포함하여 `callback`을 호출해야 합니다. +`scheme`에 `Buffer`를 응답으로 보내는 프로토콜을 등록합니다. 반드시 `Buffer` 또는 +`data`, `mimeType`, `chart` 속성을 포함한 객체 중 하나를 인자에 포함하여 +`callback`을 호출해야 합니다. 예제: @@ -80,8 +86,9 @@ protocol.registerBufferProtocol('atom', function(request, callback) { * `handler` Function * `completion` Function (optional) -`scheme`에 `문자열`을 응답으로 보내는 프로토콜을 등록합니다. -반드시 `문자열` 또는 `data`, `mimeType`, `chart` 속성을 포함한 객체 중 하나를 인자에 포함하여 `callback`을 호출해야 합니다. +`scheme`에 `문자열`을 응답으로 보내는 프로토콜을 등록합니다. 반드시 `문자열` 또는 +`data`, `mimeType`, `chart` 속성을 포함한 객체 중 하나를 인자에 포함하여 +`callback`을 호출해야 합니다. ### `protocol.registerHttpProtocol(scheme, handler[, completion])` @@ -89,10 +96,12 @@ protocol.registerBufferProtocol('atom', function(request, callback) { * `handler` Function * `completion` Function (optional) -`scheme`에 HTTP 요청을 응답으로 보내는 프로토콜을 등록합니다. -반드시 `url`, `method`, `referer`, `session` 속성을 포함하는 객체를 인자에 포함하여 `callback`을 호출해야 합니다. +`scheme`에 HTTP 요청을 응답으로 보내는 프로토콜을 등록합니다. 반드시 `url`, +`method`, `referrer`, `session` 속성을 포함하는 객체를 인자에 포함하여 `callback`을 +호출해야 합니다. -기본적으로 HTTP 요청은 현재 세션을 재사용합니다. 만약 서로 다른 세션에 요청을 보내고 싶으면 `session`을 `null`로 지정해야 합니다. +기본적으로 HTTP 요청은 현재 세션을 재사용합니다. 만약 서로 다른 세션에 요청을 보내고 +싶으면 `session`을 `null`로 지정해야 합니다. ### `protocol.unregisterProtocol(scheme[, completion])` @@ -106,7 +115,8 @@ protocol.registerBufferProtocol('atom', function(request, callback) { * `scheme` String * `callback` Function -`scheme`에 동작(handler)이 등록되어 있는지 여부를 확인합니다. `callback`으로 결과(boolean)가 반환됩니다. +`scheme`에 동작(handler)이 등록되어 있는지 여부를 확인합니다. `callback`으로 +결과(boolean)가 반환됩니다. ### `protocol.interceptFileProtocol(scheme, handler[, completion])` @@ -130,7 +140,8 @@ protocol.registerBufferProtocol('atom', function(request, callback) { * `handler` Function * `completion` Function (optional) -`scheme` 프로토콜을 가로채고 `handler`를 `Buffer` 전송에 대한 새로운 동작으로 사용합니다. +`scheme` 프로토콜을 가로채고 `handler`를 `Buffer` 전송에 대한 새로운 동작으로 +사용합니다. ### `protocol.interceptHttpProtocol(scheme, handler[, completion])` @@ -138,7 +149,8 @@ protocol.registerBufferProtocol('atom', function(request, callback) { * `handler` Function * `completion` Function (optional) -`scheme` 프로토콜을 가로채고 `handler`를 HTTP 프로토콜의 요청에 대한 새로운 동작으로 사용합니다. +`scheme` 프로토콜을 가로채고 `handler`를 HTTP 프로토콜의 요청에 대한 새로운 동작으로 +사용합니다. ### `protocol.uninterceptProtocol(scheme[, completion])` diff --git a/docs-translations/ko-KR/api/remote.md b/docs-translations/ko-KR/api/remote.md index 756acd429a1b..935b09865130 100644 --- a/docs-translations/ko-KR/api/remote.md +++ b/docs-translations/ko-KR/api/remote.md @@ -1,51 +1,62 @@ # remote -`remote` 모듈은 메인 프로세스와 랜더러 프로세스(웹 페이지) 사이의 inter-process (IPC) 통신을 간단하게 추상화 한 모듈입니다. +`remote` 모듈은 메인 프로세스와 랜더러 프로세스(웹 페이지) 사이의 inter-process +(IPC) 통신을 간단하게 추상화 한 모듈입니다. -Electron의 랜더러 프로세스에선 GUI와 관련 없는 모듈만 사용할 수 있습니다. -기본적으로 랜더러 프로세스에서 메인 프로세스의 API를 사용하려면 메인 프로세스와 inter-process 통신을 해야 합니다. -하지만 `remote` 모듈을 사용하면 따로 inter-process 통신을 하지 않고 직접 명시적으로 모듈을 사용할 수 있습니다. -Java의 [RMI](http://en.wikipedia.org/wiki/Java_remote_method_invocation)와 개념이 비슷합니다. +Electron의 메인 프로세스에선 GUI와 관련 있는(`dialog`, `menu`등) 모듈만 사용할 수 +있습니다. 랜더러 프로세스에서 이러한 모듈들을 사용하려면 `ipc` 모듈을 통해 메인 +프로세스와 inter-process 통신을 해야합니다. 또한, `remote` 모듈을 사용하면 +inter-process 통신을 하지 않고도 간단한 API를 통해 직접 메인 프로세스의 모듈과 +메서드를 사용할 수 있습니다. 이 개념은 Java의 [RMI][rmi]와 비슷합니다. 다음 예제는 랜더러 프로세스에서 브라우저 창을 만드는 예제입니다: ```javascript -var remote = require('remote'); -var BrowserWindow = remote.require('browser-window'); +const remote = require('electron').remote; +const BrowserWindow = remote.BrowserWindow; var win = new BrowserWindow({ width: 800, height: 600 }); -win.loadUrl('https://github.com'); +win.loadURL('https://github.com'); ``` -**참고:** 반대로 메인 프로세스에서 랜더러 프로세스에 접근 하려면 [webContents.executeJavascript](web-contents.md#webcontentsexecutejavascriptcode-usergesture) 메서드를 사용하면 됩니다. +**참고:** 반대로 메인 프로세스에서 랜더러 프로세스에 접근 하려면 [webContents.executeJavascript](web-contents.md#webcontentsexecutejavascriptcode-usergesture) +메서드를 사용하면 됩니다. ## Remote 객체 -`remote` 모듈로부터 반환된 각 객체(메서드 포함)는 메인 프로세스의 객체를 추상화 한 객체입니다. (우리는 그것을 remote 객체 또는 remote 함수라고 부릅니다) -Remote 모듈의 메서드를 호출하거나, 객체에 접근하거나, 생성자로 객체를 생성하는 등의 작업은 실질적으로 동기형 inter-process 메시지를 보냅니다. +`remote` 모듈로부터 반환된 각 객체(메서드 포함)는 메인 프로세스의 객체를 추상화 한 +객체입니다. (우리는 그것을 remote 객체 또는 remote 함수라고 부릅니다) Remote 모듈의 +메서드를 호출하거나, 객체에 접근하거나, 생성자로 객체를 생성하는 등의 작업은 실질적으로 +동기형 inter-process 메시지를 보냅니다. -위의 예제에서 사용한 두 `BrowserWindow`와 `win`은 remote 객체입니다. 그리고 `new BrowserWindow`이 생성하는 `BrowserWindow` 객체는 랜더러 프로세스에서 생성되지 않습니다. -대신에 이 `BrowserWindow` 객체는 메인 프로세스에서 생성되며 랜더러 프로세스에 `win` 객체와 같이 이에 대응하는 remote 객체를 반환합니다. +위의 예제에서 사용한 두 `BrowserWindow`와 `win`은 remote 객체입니다. 그리고 +`new BrowserWindow`이 생성하는 `BrowserWindow` 객체는 랜더러 프로세스에서 생성되지 +않습니다. 대신에 이 `BrowserWindow` 객체는 메인 프로세스에서 생성되며 랜더러 +프로세스에 `win` 객체와 같이 이에 대응하는 remote 객체를 반환합니다. ## Remote 객체의 생명 주기 -Electron은 랜더러 프로세스의 remote 객체가 살아있는 한(다시 말해서 GC(garbage collection)가 일어나지 않습니다) 대응하는 메인 프로세스의 객체는 릴리즈되지 않습니다. +Electron은 랜더러 프로세스의 remote 객체가 살아있는 한(다시 말해서 GC(garbage +collection)가 일어나지 않습니다) 대응하는 메인 프로세스의 객체는 릴리즈되지 않습니다. Remote 객체가 GC 되려면 대응하는 메인 프로세스 내부 객체의 참조가 해제되어야만 합니다. -만약 remote 객체가 랜더러 프로세스에서 누수가 생겼다면 (예시: 맵에 저장하고 할당 해제하지 않음) 대응하는 메인 프로세스의 객체도 누수가 생깁니다. -그래서 remote 객체를 사용할 땐 메모리 누수가 생기지 않도록 매우 주의해서 사용해야 합니다. +만약 remote 객체가 랜더러 프로세스에서 누수가 생겼다면 (예시: 맵에 저장하고 할당 +해제하지 않음) 대응하는 메인 프로세스의 객체도 누수가 생깁니다. 그래서 remote 객체를 +사용할 땐 메모리 누수가 생기지 않도록 매우 주의해서 사용해야 합니다. 참고로 문자열, 숫자와 같은 원시 값 타입은 복사에 의한 참조로 전달됩니다. ## 메인 프로세스로 콜백 넘기기 -메인 프로세스의 코드는 `remote` 모듈을 통해 랜더러 프로세스가 전달하는 콜백 함수를 받을 수 있습니다. -하지만 이 작업은 반드시 주의를 기울여 사용해야 합니다. +메인 프로세스의 코드는 `remote` 모듈을 통해 랜더러 프로세스가 전달하는 콜백 함수를 +받을 수 있습니다. 하지만 이 작업은 반드시 주의를 기울여 사용해야 합니다. -첫째, 데드락을 피하기 위해 메인 프로세스로 전달된 콜백들은 비동기로 호출됩니다. -이러한 이유로 메인 프로세스로 전달된 콜백들의 반환 값을 내부 함수에서 언제나 정상적으로 받을 것이라고 예측해선 안됩니다. +첫째, 데드락을 피하기 위해 메인 프로세스로 전달된 콜백들은 비동기로 호출됩니다. 이러한 +이유로 메인 프로세스로 전달된 콜백들의 반환 값을 내부 함수에서 언제나 정상적으로 받을 +것이라고 예측해선 안됩니다. -예를 들어 메인 프로세스에서 `Array.map` 같은 메서드를 사용할 때 랜더러 프로세스에서 전달된 함수를 사용해선 안됩니다: +예를 들어 메인 프로세스에서 `Array.map` 같은 메서드를 사용할 때 랜더러 프로세스에서 +전달된 함수를 사용해선 안됩니다: ```javascript // mapNumbers.js 메인 프로세스 @@ -76,10 +87,12 @@ console.log(withRendererCb, withLocalCb) // [true, true, true], [2, 3, 4] 보다시피 랜더러 콜백의 동기 반환 값은 예상되지 않은 처리입니다. 그리고 메인 프로세스에서 처리한 함수의 반환 값과 일치하지 않습니다. -둘째, 콜백들은 메인 프로세스로 전달, 호출된 이후에도 자동으로 함수의 참조가 릴리즈 되지 않습니다. -함수 참조는 메인 프로세스에서 GC가 일어나기 전까지 계속 프로세스에 남아있게 됩니다. +둘째, 콜백들은 메인 프로세스로 전달, 호출된 이후에도 자동으로 함수의 참조가 릴리즈 되지 +않습니다. 함수 참조는 메인 프로세스에서 GC가 일어나기 전까지 계속 프로세스에 남아있게 +됩니다. -다음 코드를 보면 느낌이 올 것입니다. 이 예제는 remote 객체에 `close` 이벤트 콜백을 설치합니다: +다음 코드를 보면 느낌이 올 것입니다. 이 예제는 remote 객체에 `close` 이벤트 콜백을 +설치합니다: ```javascript var remote = require('remote'); @@ -89,14 +102,27 @@ remote.getCurrentWindow().on('close', function() { }); ``` -하지만 이 코드 처럼 이벤트를 명시적으로 제거하지 않는 이상 콜백 함수의 참조가 계속해서 메인 프로세스에 남아있게 됩니다. -만약 명시적으로 콜백을 제거하지 않으면 매 번 창을 새로고침 할 때마다 콜백을 새로 설치합니다. -게다가 이전 콜백이 제거되지 않고 계속해서 쌓이면서 메모리 누수가 발생합니다. +하지만 이 코드 처럼 이벤트를 명시적으로 제거하지 않는 이상 콜백 함수의 참조가 계속해서 +메인 프로세스에 남아있게 됩니다. 만약 명시적으로 콜백을 제거하지 않으면 매 번 창을 +새로고침 할 때마다 콜백을 새로 설치합니다. 게다가 이전 콜백이 제거되지 않고 계속해서 +쌓이면서 메모리 누수가 발생합니다. -설상가상으로 이전에 설치된 콜백의 콘텍스트가 릴리즈 되고 난 후(예: 페이지 새로고침) `close` 이벤트가 발생하면 예외가 발생하고 메인 프로세스가 작동 중지됩니다. +설상가상으로 이전에 설치된 콜백의 콘텍스트가 릴리즈 되고 난 후(예: 페이지 새로고침) +`close` 이벤트가 발생하면 예외가 발생하고 메인 프로세스가 작동 중지됩니다. -이러한 문제를 피하려면 랜더러 프로세스에서 메인 프로세스로 넘긴 함수의 참조를 사용 후 확실하게 제거해야 합니다. -작업 후 이벤트 콜백을 포함하여 책임 있게 함수의 참조를 제거하거나 메인 프로세스에서 랜더러 프로세스가 종료될 때 내부적으로 함수 참조를 제거하도록 설계해야 합니다. +이러한 문제를 피하려면 랜더러 프로세스에서 메인 프로세스로 넘긴 함수의 참조를 사용 후 +확실하게 제거해야 합니다. 작업 후 이벤트 콜백을 포함하여 책임 있게 함수의 참조를 +제거하거나 메인 프로세스에서 랜더러 프로세스가 종료될 때 내부적으로 함수 참조를 +제거하도록 설계해야 합니다. + +## 메인 프로세스의 빌트인 모듈에 접근 + +메인 프로세스의 빌트인 모듈은 `remote` 모듈에 getter로 등록되어 있습니다. 따라서 +`remote` 모듈을 `electron` 모듈처럼 직접 사용할 수 있습니다. + +```javascript +const app = remote.app; +``` ## Methods @@ -124,4 +150,7 @@ remote.getCurrentWindow().on('close', function() { ### `remote.process` -메인 프로세스의 `process` 객체를 반환합니다. `remote.getGlobal('process')`와 같습니다. 하지만 캐시 됩니다. +메인 프로세스의 `process` 객체를 반환합니다. `remote.getGlobal('process')`와 +같습니다. 하지만 캐시 됩니다. + +[rmi]: http://en.wikipedia.org/wiki/Java_remote_method_invocation diff --git a/docs-translations/ko-KR/api/screen.md b/docs-translations/ko-KR/api/screen.md index 26975cbb1e27..dfd85fb446e1 100644 --- a/docs-translations/ko-KR/api/screen.md +++ b/docs-translations/ko-KR/api/screen.md @@ -3,22 +3,25 @@ `screen` 모듈은 화면 크기, 디스플레이, 커서 위치 등등의 다양한 정보를 가져옵니다. 이 모듈은 `app` 모듈의 `ready` 이벤트가 발생하기 전까지 사용할 수 없습니다. -`screen`은 [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)를 상속 받았습니다. +`screen`은 [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)를 +상속 받았습니다. -**참고:** 랜더러 / DevTools에선 이미 DOM 속성이 `window.screen`을 가지고 있으므로 `screen = require('screen')` 형식으로 모듈을 사용할 수 없습니다. -밑의 예제와 같이 `atomScreen` 같은 이름으로 모듈 이름을 대체하여 사용해야 합니다. +**참고:** 랜더러 / DevTools에선 이미 DOM 속성이 `window.screen`을 가지고 있으므로 +`screen = require('screen')` 형식으로 모듈을 사용할 수 없습니다. 아래의 예제와 같이 +`electronScreen` 같은 이름으로 모듈 이름을 대체하여 사용해야 합니다. 다음 예제는 화면 전체를 채우는 윈도우 창을 생성합니다: ```javascript -var app = require('app'); -var BrowserWindow = require('browser-window'); +const electron = require('electron'); +const app = electron.app; +const BrowserWindow = electron.BrowserWindow; var mainWindow; app.on('ready', function() { - var atomScreen = require('screen'); - var size = atomScreen.getPrimaryDisplay().workAreaSize; + var electronScreen = electron.screen; + var size = electronScreen.getPrimaryDisplay().workAreaSize; mainWindow = new BrowserWindow({ width: size.width, height: size.height }); }); ``` @@ -32,11 +35,11 @@ var BrowserWindow = require('browser-window'); var mainWindow; app.on('ready', function() { - var atomScreen = require('screen'); - var displays = atomScreen.getAllDisplays(); + var electronScreen = require('screen'); + var displays = electronScreen.getAllDisplays(); var externalDisplay = null; for (var i in displays) { - if (displays[i].bounds.x > 0 || displays[i].bounds.y > 0) { + if (displays[i].bounds.x != 0 || displays[i].bounds.y != 0) { externalDisplay = displays[i]; break; } @@ -45,7 +48,7 @@ app.on('ready', function() { if (externalDisplay) { mainWindow = new BrowserWindow({ x: externalDisplay.bounds.x + 50, - y: externalDisplay.bounds.y + 50, + y: externalDisplay.bounds.y + 50 }); } }); diff --git a/docs-translations/ko-KR/api/session.md b/docs-translations/ko-KR/api/session.md index cb912aabb100..3fda5747f602 100644 --- a/docs-translations/ko-KR/api/session.md +++ b/docs-translations/ko-KR/api/session.md @@ -1,27 +1,60 @@ # session -`session` 객체는 [`BrowserWindow`](browser-window.md)의 [`webContents`](web-contents.md)의 프로퍼티입니다. -다음과 같이 `BrowserWindow` 인스턴스에서 접근할 수 있습니다: +`session` 모듈은 새로운 `Session` 객체를 만드는데 사용할 수 있습니다. + +또한 존재하는 [`BrowserWindow`](browser-window.md)의 +[`webContents`](web-contents.md)에서 `session` 속성으로 접근할 수도 있습니다. ```javascript var BrowserWindow = require('browser-window'); var win = new BrowserWindow({ width: 800, height: 600 }); -win.loadUrl("http://github.com"); +win.loadURL("http://github.com"); -var session = win.webContents.session +var ses = win.webContents.session ``` -## Events +## Methods -### Event: 'will-download' +`session` 모듈은 다음과 같은 메서드를 가지고 있습니다: + +### session.fromPartition(partition) + +* `partition` String + +`partition` 문자열로 부터 새로운 `Session` 인스턴스를 만들어 반환합니다. + +`partition`이 `persist:`로 시작하면 페이지는 지속성 세션을 사용하며 다른 모든 앱 내의 +페이지에서 같은 `partition`을 사용할 수 있습니다. 만약 `persist:` 접두어로 시작하지 +않으면 페이지는 인-메모리 세션을 사용합니다. `partition`을 지정하지 않으면 어플리케이션의 +기본 세션이 반환됩니다. + +## Properties + +`session` 모듈은 다음과 같은 속성을 가지고 있습니다: + +### session.defaultSession + +어플리케이션의 기본 세션 객체를 반환합니다. + +## Class: Session + +`session` 모듈을 사용하여 `Session` 객체를 생성할 수 있습니다: + +```javascript +const session = require('electron').session; + +var ses = session.fromPartition('persist:name'); + ``` + +### Instance Events + +`Session` 객체는 다음과 같은 이벤트를 가지고 있습니다: + +#### Event: 'will-download' * `event` Event -* `item` Object - * `url` String - * `filename` String - * `mimeType` String - * `hasUserGesture` Boolean +* `item` [DownloadItem](download-item.md) * `webContents` [WebContents](web-contents.md) Electron의 `webContents`에서 `item`을 다운로드할 때 발생하는 이벤트입니다. @@ -31,26 +64,27 @@ Electron의 `webContents`에서 `item`을 다운로드할 때 발생하는 이 ```javascript session.on('will-download', function(event, item, webContents) { event.preventDefault(); - require('request')(item.url, function(data) { + require('request')(item.getURL(), function(data) { require('fs').writeFileSync('/somewhere', data); }); }); ``` -## Methods +### Instance Methods -`session` 객체는 다음과 같은 메서드와 속성을 가지고 있습니다: +`Session` 객체는 다음과 같은 메서드와 속성을 가지고 있습니다: -### `session.cookies` +#### `ses.cookies` -`cookies` 속성은 쿠키를 조작하는 방법을 제공합니다. 예를 들어 다음과 같이 할 수 있습니다: +`cookies` 속성은 쿠키를 조작하는 방법을 제공합니다. 예를 들어 다음과 같이 할 수 +있습니다: ```javascript var BrowserWindow = require('browser-window'); var win = new BrowserWindow({ width: 800, height: 600 }); -win.loadUrl('https://github.com'); +win.loadURL('https://github.com'); win.webContents.on('did-finish-load', function() { // 모든 쿠키를 가져옵니다. @@ -77,11 +111,12 @@ win.webContents.on('did-finish-load', function() { }); ``` -### `session.cookies.get(details, callback)` +#### `ses.cookies.get(details, callback)` -`details` Object, properties: +`details` Object -* `url` String - `url`에 관련된 쿠키를 가져옵니다. 이 속성을 비워두면 모든 url의 쿠키를 가져옵니다. +* `url` String - `url`에 관련된 쿠키를 가져옵니다. 이 속성을 비워두면 모든 url의 + 쿠키를 가져옵니다. * `name` String - 이름을 기준으로 쿠키를 필터링합니다. * `domain` String - `domain`과 일치하는 도메인과 서브 도메인에 대한 쿠키를 가져옵니다. * `path` String - `path`와 일치하는 경로에 대한 쿠키를 가져옵니다. @@ -96,28 +131,34 @@ win.webContents.on('did-finish-load', function() { * `domain` String - 쿠키의 도메인. * `host_only` String - 쿠키가 호스트 전용인가에 대한 여부. * `path` String - 쿠키의 경로. - * `secure` Boolean - 쿠키가 안전한 것으로 표시되는지에 대한 여부. (일반적으로 HTTPS) + * `secure` Boolean - 쿠키가 안전한 것으로 표시되는지에 대한 여부. (일반적으로 + HTTPS) * `http_only` Boolean - 쿠키가 HttpOnly로 표시되는지에 대한 여부. - * `session` Boolean - 쿠키가 세션 쿠키 또는 만료일이 있는 영구 쿠키인지에 대한 여부. - * `expirationDate` Double - (Option) UNIX 시간으로 표시되는 쿠키의 만료일에 대한 초 단위 시간. 세션 쿠키는 지원되지 않음. + * `session` Boolean - 쿠키가 세션 쿠키 또는 만료일이 있는 영구 쿠키인지에 대한 + 여부. + * `expirationDate` Double - (Option) UNIX 시간으로 표시되는 쿠키의 만료일에 + 대한 초 단위 시간. 세션 쿠키는 지원되지 않음. -### `session.cookies.set(details, callback)` +#### `ses.cookies.set(details, callback)` -`details` Object, properties: +`details` Object -* `url` String - `url`에 관련된 쿠키를 가져옵니다. +* `url` String - `url`에 관련된 쿠키를 가져옵니다. * `name` String - 쿠키의 이름입니다. 기본적으로 비워두면 생략됩니다. * `value` String - 쿠키의 값입니다. 기본적으로 비워두면 생략됩니다. * `domain` String - 쿠키의 도메인입니다. 기본적으로 비워두면 생략됩니다. * `path` String - 쿠키의 경로입니다. 기본적으로 비워두면 생략됩니다. -* `secure` Boolean - 쿠키가 안전한 것으로 표시되는지에 대한 여부입니다. 기본값은 false입니다. -* `session` Boolean - 쿠키가 HttpOnly로 표시되는지에 대한 여부입니다. 기본값은 false입니다. -* `expirationDate` Double - UNIX 시간으로 표시되는 쿠키의 만료일에 대한 초 단위 시간입니다. 생략하면 쿠키는 세션 쿠키가 됩니다. +* `secure` Boolean - 쿠키가 안전한 것으로 표시되는지에 대한 여부입니다. 기본값은 + false입니다. +* `session` Boolean - 쿠키가 HttpOnly로 표시되는지에 대한 여부입니다. 기본값은 + false입니다. +* `expirationDate` Double - UNIX 시간으로 표시되는 쿠키의 만료일에 대한 초 단위 + 시간입니다. 생략하면 쿠키는 세션 쿠키가 됩니다. * `callback` Function - function(error) * `error` Error -### `session.cookies.remove(details, callback)` +#### `ses.cookies.remove(details, callback)` * `details` Object, proprties: * `url` String - 쿠키와 관련된 URL입니다. @@ -125,16 +166,17 @@ win.webContents.on('did-finish-load', function() { * `callback` Function - function(error) * `error` Error -### `session.clearCache(callback)` +#### `ses.clearCache(callback)` * `callback` Function - 작업이 완료되면 호출됩니다. 세션의 HTTP 캐시를 비웁니다. -### `session.clearStorageData([options, ]callback)` +#### `ses.clearStorageData([options, ]callback)` * `options` Object (optional), proprties: - * `origin` String - `scheme://host:port`와 같은 `window.location.origin` 규칙을 따르는 origin 문자열. + * `origin` String - `scheme://host:port`와 같은 `window.location.origin` 규칙을 + 따르는 origin 문자열. * `storages` Array - 비우려는 스토리지의 종류, 다음과 같은 타입을 포함할 수 있습니다: `appcache`, `cookies`, `filesystem`, `indexdb`, `local storage`, `shadercache`, `websql`, `serviceworkers` @@ -144,13 +186,16 @@ win.webContents.on('did-finish-load', function() { 웹 스토리지의 데이터를 비웁니다. -### `session.setProxy(config, callback)` +#### `ses.setProxy(config, callback)` * `config` String * `callback` Function - 작업이 완료되면 호출됩니다. 세션에 사용할 프록시 `config`를 분석하고 프록시를 적용합니다. +세션에 사용할 프록시는 `config`가 PAC 주소일 경우 그대로 적용하고, 다른 형식일 경우 +다음 규칙에 따라 적용합니다. + ``` config = scheme-proxies[";"] scheme-proxies = ["="] @@ -159,29 +204,86 @@ 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. + "http=foopy:80;ftp=foopy2" -- http:// URL에 "foopy:80" HTTP 프록시를 + 사용합니다. "foopy2:80" 는 ftp:// URL에 + 사용됩니다. + "foopy:80" -- 모든 URL에 "foopy:80" 프록시를 사용합니다. + "foopy:80,bar,direct://" -- 모든 URL에 "foopy:80" HTTP 프록시를 + 사용합니다. 문제가 발생하여 "foopy:80"를 + 사용할 수 없는 경우 "bar"를 대신 사용하여 + 장애를 복구하며 그 다음 문제가 생긴 경우 + 프록시를 사용하지 않습니다. + "socks4://foopy" -- 모든 URL에 "foopy:1000" SOCKS v4 프록시를 + 사용합니다. + "http=foopy,socks5://bar.com -- http:// URL에 "foopy" HTTP 프록시를 + 사용합니다. 문제가 발생하여 "foopy"를 + 사용할 수 없는 경우 SOCKS5 "bar.com" + 프록시를 대신 사용합니다. + "http=foopy,direct:// -- http:// URL에 "foopy" HTTP 프록시를 + 사용합니다. 그리고 문제가 발생하여 "foopy"를 + 사용할 수 없는 경우 프록시를 사용하지 않습니다. + "http=foopy;socks=foopy2 -- http:// URL에 "foopy" HTTP 프록시를 + 사용합니다. 그리고 "socks4://foopy2" + 프록시를 다른 모든 URL에 사용합니다. ``` -### `session.setDownloadPath(path)` +### `app.resolveProxy(url, callback)` + +* `url` URL +* `callback` Function + +`url`의 프록시 정보를 해석합니다. `callback`은 요청이 수행되었을 때 +`callback(proxy)` 형태로 호출됩니다. + +#### `ses.setDownloadPath(path)` * `path` String - 다운로드 위치 -다운로드 저장 위치를 지정합니다. 기본 다운로드 위치는 각 어플리케이션 데이터 디렉터리의 `Downloads` 폴더입니다. +다운로드 저장 위치를 지정합니다. 기본 다운로드 위치는 각 어플리케이션 데이터 디렉터리의 +`Downloads` 폴더입니다. + +#### `ses.enableNetworkEmulation(options)` + +* `options` Object + * `offline` Boolean - 네트워크의 오프라인 상태 여부 + * `latency` Double - 밀리세컨드 단위의 RTT + * `downloadThroughput` Double - Bps 단위의 다운로드 주기 + * `uploadThroughput` Double - Bps 단위의 업로드 주기 + +제공된 설정으로 `session`의 네트워크를 에뮬레이트합니다. + +```javascript +// 50kbps의 처리량과 함께 500ms의 레이턴시로 GPRS 연결을 에뮬레이트합니다. +window.webContents.session.enableNetworkEmulation({ + latency: 500, + downloadThroughput: 6400, + uploadThroughput: 6400 +}); + +// 네트워크가 끊긴 상태를 에뮬레이트합니다. +window.webContents.session.enableNetworkEmulation({offline: true}); +``` + +#### `ses.disableNetworkEmulation()` + +활성화된 `session`의 에뮬레이션을 비활성화합니다. 기본 네트워크 설정으로 돌아갑니다. + +#### `ses.setCertificateVerifyProc(proc)` + + * `proc` Function + +`session`에 인증서의 유효성을 확인하는 프로세스(proc)를 등록합니다. `proc`은 서버 +인증서 유효성 검증 요청이 들어왔을 때 언제나 `proc(hostname, certificate, callback)` +형식으로 호출됩니다. `callback(true)`을 호출하면 인증을 승인하고 `callback(false)`를 +호출하면 인증을 거부합니다. + +`setCertificateVerifyProc(null)`을 호출하면 기본 검증 프로세스로 되돌립니다. + +```javascript +myWindow.webContents.session.setCertificateVerifyProc(function(hostname, cert, callback) { + if (hostname == 'github.com') + callback(true); + else + callback(false); +}); +``` diff --git a/docs-translations/ko-KR/api/shell.md b/docs-translations/ko-KR/api/shell.md index a84436f78071..4183d3b2502d 100644 --- a/docs-translations/ko-KR/api/shell.md +++ b/docs-translations/ko-KR/api/shell.md @@ -5,7 +5,7 @@ 다음 예제는 설정된 URL을 유저의 기본 브라우저로 엽니다: ```javascript -var shell = require('shell'); +const shell = require('electron').shell; shell.openExternal('https://github.com'); ``` @@ -29,9 +29,10 @@ shell.openExternal('https://github.com'); * `url` String -제공된 외부 프로토콜 URL을 기반으로 데스크톱의 기본 프로그램으로 엽니다. (예를 들어 mailto: URL은 유저의 기본 이메일 에이전트로 URL을 엽니다.) +제공된 외부 프로토콜 URL을 기반으로 데스크톱의 기본 프로그램으로 엽니다. (예를 들어 +mailto: URL은 유저의 기본 이메일 에이전트로 URL을 엽니다.) -역주: 폴더는 'file:\\\\C:\\'와 같이 지정하여 열 수 있습니다. (`\\`로 경로를 표현한 이유는 Escape 문자열을 참고하세요.) +역주: 폴더는 'file:\\\\C:\\'와 같이 지정하여 열 수 있습니다. (Windows의 경우) ### `shell.moveItemToTrash(fullPath)` diff --git a/docs-translations/ko-KR/api/synopsis.md b/docs-translations/ko-KR/api/synopsis.md index 3b1633da6d5d..fd0791cd4a8e 100644 --- a/docs-translations/ko-KR/api/synopsis.md +++ b/docs-translations/ko-KR/api/synopsis.md @@ -1,40 +1,83 @@ # 개요 -Electron은 모든 [Node.js의 built-in 모듈](http://nodejs.org/api/)과 third-party node 모듈을 완벽하게 지원합니다. ([네이티브 모듈](../tutorial/using-native-node-modules.md) 포함) +Electron은 모든 [Node.js의 built-in 모듈](http://nodejs.org/api/)과 third-party +node 모듈을 완벽하게 지원합니다. ([네이티브 모듈](../tutorial/using-native-node-modules.md) +포함) -Electron은 네이티브 데스크톱 어플리케이션을 개발 할 수 있도록 추가적인 built-in 모듈을 제공합니다. -몇몇 모듈은 메인 프로세스에서만 사용할 수 있고 어떤 모듈은 랜더러 프로세스(웹 페이지)에서만 사용할 수 있습니다. -또한 두 프로세스 모두 사용할 수 있는 모듈도 있습니다. +또한 Electron은 네이티브 데스크톱 어플리케이션을 개발 할 수 있도록 추가적인 built-in +모듈을 제공합니다. 몇몇 모듈은 메인 프로세스에서만 사용할 수 있고 어떤 모듈은 랜더러 +프로세스(웹 페이지)에서만 사용할 수 있습니다. 또한 두 프로세스 모두 사용할 수 있는 +모듈도 있습니다. -기본적인 규칙으로 [GUI](https://en.wikipedia.org/wiki/Graphical_user_interface)와 저 수준 시스템에 관련된 모듈들은 오직 메인 프로세스에서만 사용할 수 있습니다. -[메인 프로세스 vs. 랜더러 프로세스](../tutorial/quick-start.md#메인 프로세스) 컨셉에 익숙해야 이 모듈들을 사용하기 쉬우므로 관련 문서를 읽어 보는 것을 권장합니다. +기본적인 규칙으로 [GUI][gui]와 저 수준 시스템에 관련된 모듈들은 오직 메인 +프로세스에서만 사용할 수 있습니다. [메인 프로세스 vs. 랜더러 프로세스](../tutorial/quick-start.md#메인-프로세스) +컨셉에 익숙해야 모듈을 다루기 쉬우므로 관련 문서를 읽어 보는 것을 권장합니다. 메인 프로세스 스크립트는 일반 Node.js 스크립트와 비슷합니다: ```javascript -var app = require('app'); -var BrowserWindow = require('browser-window'); +const electron = require('electron'); +const app = electron.app; +const BrowserWindow = electron.BrowserWindow; var window = null; app.on('ready', function() { window = new BrowserWindow({width: 800, height: 600}); - window.loadUrl('https://github.com'); + window.loadURL('https://github.com'); }); ``` -랜더러 프로세스도 예외적인 node module들을 사용할 수 있다는 점을 제외하면 일반 웹 페이지와 크게 다를게 없습니다: +랜더러 프로세스도 예외적인 node module들을 사용할 수 있다는 점을 제외하면 일반 웹 +페이지와 크게 다를게 없습니다: ```html - - - + + + ``` -어플리케이션을 실행하려면 [앱 실행하기](../tutorial/quick-start.md#앱 실행하기) 문서를 참고하기 바랍니다. +어플리케이션을 실행하려면 [앱 실행하기](../tutorial/quick-start.md#앱 실행하기) +문서를 참고하기 바랍니다. + +## 분리 할당 + +만약 CoffeeScript나 Babel을 사용하고 있다면, 빌트인 모듈을 사용할 때 +[분리 할당][desctructuring-assignment]을 통해 직관적으로 사용할 수 있습니다: + +```javascript +const {app, BrowserWindow} = require('electron') +``` + +아직 플레인 자바스크립트를 쓰고 있다면, Chrome이 ES6를 완전히 지원하기 전까지 기다려야 +합니다. + +## 이전 스타일의 빌트인 모듈 비활성화 + +v0.35.0 이전 버전에선 빌트인 모듈이 모두 `require('module-name')`같은 형식으로 +사용되었습니다. 하지만 [많은 단점][issue-387]이 있기 때문에 현재 API가 변경되었습니다. +하지만 오래된 앱의 호환성 유지를 위해 아직 구 버전 API를 지원하고 있습니다. + +완벽하게 모든 구 버전 API를 비활성화하려면 `ELECTRON_HIDE_INTERNAL_MODULES` 환경 +변수를 설정하면 됩니다: + +```javascript +process.env.ELECTRON_HIDE_INTERNAL_MODULES = 'true' +``` + +또는 `hideInternalModules` API를 사용해도 됩니다: + +```javascript +require('electron').hideInternalModules() +``` + +[gui]: https://en.wikipedia.org/wiki/Graphical_user_interface +[main-process]: ../tutorial/quick-start.md#메인-프로세스 +[desctructuring-assignment]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment +[issue-387]: https://github.com/atom/electron/issues/387 diff --git a/docs-translations/ko-KR/api/tray.md b/docs-translations/ko-KR/api/tray.md index beccf6d8b34a..e182f6471e7a 100644 --- a/docs-translations/ko-KR/api/tray.md +++ b/docs-translations/ko-KR/api/tray.md @@ -1,11 +1,13 @@ # Tray -`Tray`는 OS의 알림 영역에 아이콘을 표시합니다. 보통 컨텍스트 메뉴(context menu)를 같이 사용합니다. +`Tray`는 OS의 알림 영역에 아이콘을 표시합니다. 보통 컨텍스트 메뉴(context menu)를 +같이 사용합니다. ```javascript -var app = require('app'); -var Menu = require('menu'); -var Tray = require('tray'); +const electron = require('electron'); +const app = electron.app; +const Menu = electron.Menu; +const Tray = electron.Tray; var appIcon = null; app.on('ready', function(){ @@ -24,13 +26,15 @@ app.on('ready', function(){ __플랫폼별 한계:__ -* Linux에서는 앱 알림 표시기(app indicator)가 지원되면 해당 기능을 사용합니다. 만약 지원하지 않으면 `GtkStatusIcon`을 대신 사용합니다. -* Linux 배포판이 앱 알림 표시기만 지원하고 있다면 `libappindicator1`를 설치하여 트레이 아이콘이 작동하도록 만들 수 있습니다. +* Linux에서는 앱 알림 표시기(app indicator)가 지원되면 해당 기능을 사용합니다. 만약 + 지원하지 않으면 `GtkStatusIcon`을 대신 사용합니다. +* Linux 배포판이 앱 알림 표시기만 지원하고 있다면 `libappindicator1`를 설치하여 + 트레이 아이콘이 작동하도록 만들 수 있습니다. * 앱 알림 표시기는 컨텍스트 메뉴를 가지고 있을 때만 보입니다. -* Linux에서 앱 알림 표시기가 사용될 경우, `clicked` 이벤트는 무시됩니다. +* Linux에서 앱 표시기가 사용될 경우, `click` 이벤트는 무시됩니다. -이러한 이유로 Tray API가 모든 플랫폼에서 똑같이 작동하게 하고 싶다면 `clicked` 이벤트에 의존해선 안됩니다. -그리고 언제나 컨텍스트 메뉴를 포함해야 합니다. +이러한 이유로 Tray API가 모든 플랫폼에서 똑같이 작동하게 하고 싶다면 `click` 이벤트에 +의존해선 안되며 언제나 컨텍스트 메뉴를 포함해야 합니다. ## Class: Tray @@ -46,9 +50,9 @@ __플랫폼별 한계:__ `Tray` 모듈은 다음과 같은 이벤트를 가지고 있습니다: -**참고:** 몇가지 이벤트는 특정한 플랫폼에서만 작동합니다. +**참고:** 몇몇 이벤트는 특정한 플랫폼에서만 작동합니다. -### Event: 'clicked' +### Event: 'click' * `event` Event * `altKey` Boolean @@ -65,7 +69,7 @@ __플랫폼별 한계:__ __주의:__ `bounds`는 OS X 와 Windows에서만 작동합니다. -### Event: 'right-clicked' _OS X_ _Windows_ +### Event: 'right-click' _OS X_ _Windows_ * `event` Event * `altKey` Boolean @@ -80,7 +84,7 @@ __주의:__ `bounds`는 OS X 와 Windows에서만 작동합니다. 트레이 아이콘을 오른쪽 클릭될 때 호출 됩니다. -### Event: 'double-clicked' _OS X_ _Windows_ +### Event: 'double-click' _OS X_ _Windows_ * `event` Event * `altKey` Boolean @@ -97,15 +101,19 @@ __주의:__ `bounds`는 OS X 와 Windows에서만 작동합니다. ### Event: 'balloon-show' _Windows_ -알림풍선이 보여질 때 발생하는 이벤트입니다. +풍선 팝업이 보여질 때 발생하는 이벤트입니다. -### Event: 'balloon-clicked' _Windows_ +### Event: 'balloon-click' _Windows_ -알림풍선이 클릭될 때 발생하는 이벤트입니다. +풍선 팝업이 클릭될 때 발생하는 이벤트입니다. ### Event: 'balloon-closed' _Windows_ -알림풍선이 시간이 지나 사라지거나 유저가 클릭하여 닫을 때 발생하는 이벤트입니다. +풍선 팝업이 시간이 지나 사라지거나 유저가 클릭하여 닫을 때 발생하는 이벤트입니다. + +### Event: 'drop' _OS X_ + +드래그 가능한 아이템이 트레이 아이콘에 드롭되면 발생하는 이벤트입니다. ### Event: 'drop-files' _OS X_ @@ -114,11 +122,23 @@ __주의:__ `bounds`는 OS X 와 Windows에서만 작동합니다. 트레이 아이콘에 파일이 드롭되면 발생하는 이벤트입니다. +### Event: 'drag-enter' _OS X_ + +트레이 아이콘에 드래그 작업이 시작될 때 발생하는 이벤트입니다. + +### Event: 'drag-leave' _OS X_ + +트레이 아이콘에 드래그 작업이 종료될 때 발생하는 이벤트입니다. + +### Event: 'drag-end' _OS X_ + +트레이 아이콘에 드래그 작업이 종료되거나 다른 위치에서 종료될 때 발생하는 이벤트입니다. + ## Methods `Tray` 모듈은 다음과 같은 메서드를 가지고 있습니다: -**참고:** 몇가지 메서드는 특정한 플랫폼에서만 작동합니다. +**참고:** 몇몇 메서드는 특정 플랫폼에서만 작동합니다. ### `Tray.destroy()` @@ -152,7 +172,8 @@ __주의:__ `bounds`는 OS X 와 Windows에서만 작동합니다. * `highlight` Boolean -트레이 아이콘을 클릭했을 때 하이라이트 될지 설정합니다. +트레이 아이콘이 클릭됐을 때 아이콘의 배경이 파란색으로 하이라이트 될지 여부를 지정합니다. +기본값은 true입니다. ### `Tray.displayBalloon(options)` _Windows_ @@ -161,14 +182,18 @@ __주의:__ `bounds`는 OS X 와 Windows에서만 작동합니다. * `title` String * `content` String -트레이에 알림풍선을 생성합니다. +트레이에 풍선 팝업을 생성합니다. -### `Tray.popContextMenu([position])` _OS X_ _Windows_ +### `Tray.popUpContextMenu([menu, position])` _OS X_ _Windows_ -* `position` Object (optional) - 팝업 메뉴 위치 +* `menu` Menu (optional) +* `position` Object (optional) - 팝업 메뉴의 위치 * `x` Integer * `y` Integer +트레이 아이콘의 컨텍스트 메뉴를 팝업시킵니다. `menu`가 전달되면, `menu`가 트레이 +메뉴의 컨텍스트 메뉴 대신 표시됩니다. + `position`은 Windows에서만 사용할 수 있으며 기본값은 (0, 0)입니다. ### `Tray.setContextMenu(menu)` diff --git a/docs-translations/ko-KR/api/web-contents.md b/docs-translations/ko-KR/api/web-contents.md new file mode 100644 index 000000000000..4667ba733967 --- /dev/null +++ b/docs-translations/ko-KR/api/web-contents.md @@ -0,0 +1,725 @@ +# webContents + +`webContents`는 [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)를 +상속받았습니다. 웹 페이지의 렌더링과 관리를 책임지며 +[`BrowserWindow`](browser-window.md)의 속성입니다. 다음은 `webContents` 객체에 +접근하는 예제입니다: + +```javascript +const BrowserWindow = require('electron').BrowserWindow; + +var win = new BrowserWindow({width: 800, height: 1500}); +win.loadURL("http://github.com"); + +var webContents = win.webContents; +``` + +## Events + +`webContents` 객체는 다음과 같은 이벤트들을 발생시킵니다: + +### Event: 'did-finish-load' + +탐색 작업이 끝났을 때 발생하는 이벤트입니다. 브라우저의 탭의 스피너가 멈추고 `onload` +이벤트가 발생했을 때를 말합니다. + +### Event: 'did-fail-load' + +Returns: + +* `event` Event +* `errorCode` Integer +* `errorDescription` String +* `validatedURL` String + +이 이벤트는 `did-finish-load`와 비슷하나, 로드가 실패했거나 취소되었을 때 발생합니다. +예를 들면 `window.stop()`이 실행되었을 때 발생합니다. 발생할 수 있는 전체 에러 코드의 +목록과 설명은 [여기](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h)서 +확인할 수 있습니다. + +### Event: 'did-frame-finish-load' + +Returns: + +* `event` Event +* `isMainFrame` Boolean + +프레임(Frame)이 탐색을 끝냈을 때 발생하는 이벤트입니다. + +### Event: 'did-start-loading' + +브라우저 탭의 스피너가 회전을 시작한 때와 같은 시점에 대응하는 이벤트입니다. + +### Event: 'did-stop-loading' + +브라우저 탭의 스피너가 회전을 멈추었을 때와 같은 시점에 대응하는 이벤트입니다. + +### Event: 'did-get-response-details' + +Returns: + +* `event` Event +* `status` Boolean +* `newURL` String +* `originalURL` String +* `httpResponseCode` Integer +* `requestMethod` String +* `referrer` String +* `headers` Object + +요청한 리소스에 관련된 자세한 정보를 사용할 수 있을 때 발생하는 이벤트입니다. +`status`는 리소스를 다운로드하기 위한 소켓 연결을 나타냅니다. + +### Event: 'did-get-redirect-request' + +Returns: + +* `event` Event +* `oldURL` String +* `newURL` String +* `isMainFrame` Boolean +* `httpResponseCode` Integer +* `requestMethod` String +* `referrer` String +* `headers` Object + +리소스를 요청하는 동안에 리다이렉트 응답을 받았을 때 발생하는 이벤트입니다. + +### Event: 'dom-ready' + +Returns: + +* `event` Event + +주어진 프레임의 문서가 로드되었을 때 발생하는 이벤트입니다. + +### Event: 'page-favicon-updated' + +Returns: + +* `event` Event +* `favicons` Array - URL 배열 + +페이지가 favicon(파비콘) URL을 받았을 때 발생하는 이벤트입니다. + +### Event: 'new-window' + +Returns: + +* `event` Event +* `url` String +* `frameName` String +* `disposition` String - `default`, `foreground-tab`, `background-tab`, + `new-window`, `other`중 하나일 수 있습니다. +* `options` Object - 새로운 `BrowserWindow` 객체를 만들 때 사용되는 옵션 객체입니다. + +페이지가 `url`에 대하여 새로운 윈도우를 열기위해 요청한 경우 발생하는 이벤트입니다. +`window.open`이나 ``과 같은 외부 링크에 의해 요청될 수 있습니다. + +기본값으로 `BrowserWindow`는 `url`을 기반으로 생성됩니다. + +`event.preventDefault()`를 호출하면 새로운 창이 생성되는 것을 방지할 수 있습니다. + +### Event: 'will-navigate' + +Returns: + +* `event` Event +* `url` String + +사용자 또는 페이지가 새로운 페이지로 이동할 때 발생하는 이벤트입니다. +`window.location` 객체가 변경되거나 사용자가 페이지의 링크를 클릭했을 때 발생합니다. + +이 이벤트는 `webContents.loadURL`과 `webContents.back` 같은 API를 이용하여 +프로그램적으로 시작된 탐색에 대해서는 발생하지 않습니다. + +`event.preventDefault()`를 호출하면 탐색을 방지할 수 있습니다. + +### Event: 'crashed' + +렌더러 프로세스가 예기치 못하게 종료되었을 때 발생되는 이벤트입니다. + +### Event: 'plugin-crashed' + +Returns: + +* `event` Event +* `name` String +* `version` String + +플러그인 프로세스가 예기치 못하게 종료되었을 때 발생되는 이벤트입니다. + +### Event: 'destroyed' + +`webContents`가 소멸될 때 발생되는 이벤트입니다. + +### Event: 'devtools-opened' + +개발자 도구가 열렸을 때 발생되는 이벤트입니다. + +### Event: 'devtools-closed' + +개발자 도구가 닫혔을 때 발생되는 이벤트입니다. + +### Event: 'devtools-focused' + +개발자 도구에 포커스가 가거나 개발자 도구가 열렸을 때 발생되는 이벤트입니다. + +### Event: 'certificate-error' + +Returns: + +* `event` Event +* `url` URL +* `error` String - 에러 코드 +* `certificate` Object + * `data` Buffer - PEM 인코딩된 데이터 + * `issuerName` String +* `callback` Function + +`url`에 대한 `certificate` 인증서의 유효성 검증에 실패했을 때 발생하는 이벤트입니다. + +사용법은 [`app`의 `certificate-error` 이벤트](app.md#event-certificate-error)와 +같습니다. + +### Event: 'select-client-certificate' + +Returns: + +* `event` Event +* `url` URL +* `certificateList` [Objects] + * `data` Buffer - PEM 인코딩된 데이터 + * `issuerName` String - 인증서 발급자 이름 +* `callback` Function + +클라이언트 인증이 요청되었을 때 발생하는 이벤트 입니다. + +사용법은 [`app`의 `select-client-certificate` 이벤트](app.md#event-select-client-certificate)와 +같습니다. + +### Event: 'login' + +Returns: + +* `event` Event +* `request` Object + * `method` String + * `url` URL + * `referrer` URL +* `authInfo` Object + * `isProxy` Boolean + * `scheme` String + * `host` String + * `port` Integer + * `realm` String +* `callback` Function + +`webContents`가 기본 인증을 수행하길 원할 때 발생되는 이벤트입니다. + +[`app`의 `login`이벤트](app.md#event-login)와 사용 방법은 같습니다. + +## Instance Methods + +`webContents`객체는 다음과 같은 인스턴스 메서드들을 가지고 있습니다. + +### `webContents.loadURL(url[, options])` + +* `url` URL +* `options` Object (optional), 속성들: + * `httpReferrer` String - HTTP Referrer url. + * `userAgent` String - 요청을 시작한 유저 에이전트. + * `extraHeaders` String - "\n"로 구분된 Extra 헤더들. + +윈도우에 웹 페이지 `url`을 로드합니다. `url`은 `http://` or `file://`과 같은 +프로토콜 접두사를 가지고 있어야 합니다. 만약 반드시 http 캐시를 사용하지 않고 로드해야 +하는 경우 `pragma` 헤더를 사용할 수 있습니다. + +```javascript +const options = {"extraHeaders" : "pragma: no-cache\n"} +webContents.loadURL(url, options) +``` + +### `webContents.downloadURL(url)` + +* `url` URL + +`url`의 리소스를 탐색 없이 다운로드를 시작합니다. `session`의 `will-download` +이벤트가 발생합니다. + +### `webContents.getURL()` + +현재 웹 페이지의 URL을 반환합니다. + +```javascript +var win = new BrowserWindow({width: 800, height: 600}); +win.loadURL("http://github.com"); + +var currentURL = win.webContents.getURL(); +``` + +### `webContents.getTitle()` + +현재 웹 페이지의 제목을 반환합니다. + +### `webContents.isLoading()` + +현재 웹 페이지가 리소스를 로드중인지 여부를 반환합니다. + +### `webContents.isWaitingForResponse()` + +현재 웹 페이지가 페이지의 메인 리소스로부터 첫 응답을 기다리고있는지 여부를 반환합니다. + +### `webContents.stop()` + +대기중인 탐색 작업을 모두 멈춥니다. + +### `webContents.reload()` + +현재 웹 페이지를 새로고침합니다. + +### `webContents.reloadIgnoringCache()` + +현재 웹 페이지의 캐시를 무시한 채로 새로고침합니다. + +### `webContents.canGoBack()` + +브라우저가 이전 웹 페이지로 돌아갈 수 있는지 여부를 반환합니다. + +### `webContents.canGoForward()` + +브라우저가 다음 웹 페이지로 이동할 수 있는지 여부를 반환합니다. + +### `webContents.canGoToOffset(offset)` + +* `offset` Integer + +웹 페이지가 `offset`로 이동할 수 있는지 여부를 반환합니다. + +### `webContents.clearHistory()` + +탐색 기록을 삭제합니다. + +### `webContents.goBack()` + +브라우저가 이전 웹 페이지로 이동하게 합니다. + +### `webContents.goForward()` + +브라우저가 다음 웹 페이지로 이동하게 합니다. + +### `webContents.goToIndex(index)` + +* `index` Integer + +브라우저가 지정된 절대 웹 페이지 인덱스로 탐색하게 합니다. + +### `webContents.goToOffset(offset)` + +* `offset` Integer + +"current entry"에서 지정된 offset으로 탐색합니다. + +### `webContents.isCrashed()` + +렌더러 프로세스가 예기치 않게 종료되었는지 여부를 반환합니다. + +### `webContents.setUserAgent(userAgent)` + +* `userAgent` String + +현재 웹 페이지의 유저 에이전트를 덮어씌웁니다. + +### `webContents.getUserAgent()` + +현재 웹 페이지의 유저 에이전트 문자열을 반환합니다. + +### `webContents.insertCSS(css)` + +* `css` String + +CSS 코드를 현재 웹 페이지에 삽입합니다. + +### `webContents.executeJavaScript(code[, userGesture])` + +* `code` String +* `userGesture` Boolean (optional) + +페이지에서 자바스크립트 코드를 실행합니다. + +기본적으로 `requestFullScreen`와 같은 몇몇 HTML API들은 사용자의 조작에 의해서만 +호출될 수 있습니다. `userGesture`를 `true`로 설정하면 이러한 제약을 무시할 수 +있습니다. + +### `webContents.setAudioMuted(muted)` + +* `muted` Boolean + +현재 웹 페이지의 소리를 음소거합니다. + +### `webContents.isAudioMuted()` + +현재 페이지가 음소거 되어있는지 여부를 반환합니다. + +### `webContents.undo()` + +웹 페이지에서 `undo` 편집 커맨드를 실행합니다. + +### `webContents.redo()` + +웹 페이지에서 `redo` 편집 커맨드를 실행합니다. + +### `webContents.cut()` + +웹 페이지에서 `cut` 편집 커맨드를 실행합니다. + +### `webContents.copy()` + +웹 페이지에서 `copy` 편집 커맨드를 실행합니다. + +### `webContents.paste()` + +웹 페이지에서 `paste` 편집 커맨드를 실행합니다. + +### `webContents.pasteAndMatchStyle()` + +웹 페이지에서 `pasteAndMatchStyle` 편집 커맨드를 실행합니다. + +### `webContents.delete()` + +웹 페이지에서 `delete` 편집 커맨드를 실행합니다. + +### `webContents.selectAll()` + +웹 페이지에서 `selectAll` 편집 커맨드를 실행합니다. + +### `webContents.unselect()` + +웹 페이지에서 `unselect` 편집 커맨드를 실행합니다. + +### `webContents.replace(text)` + +* `text` String + +웹 페이지에서 `replace` 편집 커맨드를 실행합니다. + +### `webContents.replaceMisspelling(text)` + +* `text` String + +웹 페이지에서 `replaceMisspelling` 편집 커맨드를 실행합니다. + +### `webContents.hasServiceWorker(callback)` + +* `callback` Function + +ServiceWorker가 등록되어있는지 확인하고 `callback`에 대한 응답으로 boolean 값을 +반환합니다. + +### `webContents.unregisterServiceWorker(callback)` + +* `callback` Function + +ServiceWorker가 존재하면 모두 등록을 해제하고 JS Promise가 만족될 때 `callback`에 +대한 응답으로 boolean을 반환하거나 JS Promise가 만족되지 않을 때 `false`를 반환합니다. + +### `webContents.print([options])` + +`options` Object (optional), properties: + +* `silent` Boolean - 사용자에게 프린트 설정을 묻지 않습니다. 기본값을 `false`입니다. +* `printBackground` Boolean - 웹 페이지의 배경 색과 이미지를 출력합니다. 기본값은 + `false`입니다. + +윈도우의 웹 페이지를 프린트합니다. `silent`가 `false`로 지정되어있을 땐, Electron이 +시스템의 기본 프린터와 기본 프린터 설정을 가져옵니다. + +웹 페이지에서 `window.print()`를 호출하는 것은 +`webContents.print({silent: false, printBackground: false})`를 호출하는 것과 +같습니다. + +**참고:** Windows에서의 프린터 API는 `pdf.dll`에 의존합니다. 따라서 어플리케이션이 +print기능을 사용하지 않는 경우 전체 바이너리 크기를 줄이기 위해 `pdf.dll`을 삭제해도 +됩니다. + +### `webContents.printToPDF(options, callback)` + +`options` Object, properties: + +* `marginsType` Integer - 사용할 마진의 종류를 지정합니다. + * 0 - default + * 1 - none + * 2 - minimum +* `pageSize` String - 생성되는 PDF의 페이지 크기를 지정합니다. + * `A4` + * `A3` + * `Legal` + * `Letter` + * `Tabloid` +* `printBackground` Boolean - CSS 배경을 프린트할지 여부를 정합니다. +* `printSelectionOnly` Boolean - 선택된 영역만 프린트할지 여부를 정합니다. +* `landscape` Boolean - landscape을 위해선 `true`를, portrait를 위해선 `false`를 + 사용합니다. + +`callback` Function - `function(error, data) {}` + +* `error` Error +* `data` Buffer - PDF 파일 내용. + +Chromium의 미리보기 프린팅 커스텀 설정을 이용하여 윈도우의 웹 페이지를 PDF로 +프린트합니다. + +기본으로 비어있는 `options`은 다음과 같이 여겨지게 됩니다: + +```javascript +{ + marginsType: 0, + printBackground: false, + printSelectionOnly: false, + landscape: false +} +``` + +```javascript +const BrowserWindow = require('electron').BrowserWindow; +const fs = require('fs'); + +var win = new BrowserWindow({width: 800, height: 600}); +win.loadURL("http://github.com"); + +win.webContents.on("did-finish-load", function() { + // Use default printing options + win.webContents.printToPDF({}, function(error, data) { + if (error) throw error; + fs.writeFile("/tmp/print.pdf", data, function(error) { + if (error) + throw error; + console.log("Write PDF successfully."); + }) + }) +}); +``` + +### `webContents.addWorkSpace(path)` + +* `path` String + +특정 경로를 개발자 도구의 워크스페이스에 추가합니다. 반드시 개발자 도구의 생성이 완료된 +이후에 사용해야 합니다. + +```javascript +mainWindow.webContents.on('devtools-opened', function() { + mainWindow.webContents.addWorkSpace(__dirname); +}); +``` + +### `webContents.removeWorkSpace(path)` + +* `path` String + +특정 경로를 개발자 도구의 워크스페이스에서 제거합니다. + +### `webContents.openDevTools([options])` + +* `options` Object (optional). Properties: + * `detach` Boolean - 새 창에서 개발자 도구를 엽니다. + +개발자 도구를 엽니다. + +### `webContents.closeDevTools()` + +개발자 도구를 닫습니다. + +### `webContents.isDevToolsOpened()` + +개발자 도구가 열려있는지 여부를 반환합니다. + +### `webContents.toggleDevTools()` + +개발자 도구를 토글합니다. + +### `webContents.isDevToolsFocused()` + +개발자 도구에 포커스가 가있는지 여부를 반화합니다. + +### `webContents.inspectElement(x, y)` + +* `x` Integer +* `y` Integer + +(`x`, `y`)위치의 엘레먼트를 조사합니다. + +### `webContents.inspectServiceWorker()` + +서비스 워커 컨텍스트(service worker context)를 위한 개발자 도구를 엽니다. + +### `webContents.send(channel[, arg1][, arg2][, ...])` + +* `channel` String +* `arg` (optional) + +`channel`을 통하여 렌더러 프로세스에 비동기 메시지를 보냅니다. 임의의 인수를 보낼수도 +있습니다. 렌더러 프로세스는 `ipcRenderer` 모듈을 통하여 `channel`를 리슨하여 메시지를 +처리할 수 있습니다. + +메인 프로세스에서 렌더러 프로세스로 메시지를 보내는 예시 입니다: + +```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 + + + + + + +``` + +### `webContents.enableDeviceEmulation(parameters)` + +`parameters` Object, properties: + +* `screenPosition` String - 에뮬레이트 할 화면 종료를 지정합니다 + (기본값: `desktop`) + * `desktop` + * `mobile` +* `screenSize` Object - 에뮬레이트 화면의 크기를 지정합니다 (screenPosition == + mobile) + * `width` Integer - 에뮬레이트 화면의 너비를 지정합니다 + * `height` Integer - 에뮬레이트 화면의 높이를 지정합니다 +* `viewPosition` Object - 화면에서 뷰의 위치 (screenPosition == mobile) (기본값: + `{x: 0, y: 0}`) + * `x` Integer - 좌상단 모서리로부터의 x 축의 오프셋 + * `y` Integer - 좌상단 모서리로부터의 y 축의 오프셋 +* `deviceScaleFactor` Integer - 디바이스의 스케일 팩터(scale factor)를 지정합니다. + (0일 경우 기본 디바이스 스케일 팩터를 기본으로 사용합니다. 기본값: `0`) +* `viewSize` Object - 에뮬레이트 된 뷰의 크기를 지정합니다 (빈 값은 덮어쓰지 않는 + 다는 것을 의미합니다) + * `width` Integer - 에뮬레이트 된 뷰의 너비를 지정합니다 + * `height` Integer - 에뮬레이트 된 뷰의 높이를 지정합니다 +* `fitToView` Boolean - 에뮬레이트의 뷰가 사용 가능한 공간에 맞추어 스케일 다운될지를 + 지정합니다 (기본값: `false`) +* `offset` Object - 사용 가능한 공간에서 에뮬레이트 된 뷰의 오프셋을 지정합니다 (fit + to view 모드 외에서) (기본값: `{x: 0, y: 0}`) + * `x` Float - 좌상단 모서리에서 x 축의 오프셋을 지정합니다 + * `y` Float - 좌상단 모서리에서 y 축의 오프셋을 지정합니다 +* `scale` Float - 사용 가능한 공간에서 에뮬레이드 된 뷰의 스케일 (fit to view 모드 + 외에서, 기본값: `1`) + +`parameters`로 디바이스 에뮬레이션을 사용합니다. + +### `webContents.disableDeviceEmulation()` + +`webContents.enableDeviceEmulation`로 활성화된 디바이스 에뮬레이선을 비활성화 합니다. + +### `webContents.sendInputEvent(event)` + +* `event` Object + * `type` String (**required**) - 이벤트의 타입. 다음 값들을 사용할 수 있습니다: + `mouseDown`, `mouseUp`, `mouseEnter`, `mouseLeave`, `contextMenu`, + `mouseWheel`, `mouseMove`, `keyDown`, `keyUp`, `char`. + * `modifiers` Array - 이벤트의 수정자(modifier)들에 대한 배열. 다음 값들을 포함 + 할 수 있습니다: `shift`, `control`, `alt`, `meta`, `isKeypad`, `isAutoRepeat`, + `leftButtonDown`, `middleButtonDown`, `rightButtonDown`, `capsLock`, + `numLock`, `left`, `right`. + +Input `event`를 웹 페이지로 전송합니다. + +키보드 이벤트들에 대해서는 `event` 객체는 다음 속성들을 사용할 수 있습니다: + +* `keyCode` Char or String (**required**) - 키보드 이벤트로 보내지는 문자. 단일 + UTF-8 문자를 사용할 수 있고 이벤트를 발생시키는 다음 키 중 하나를 포함할 수 있습니다: + `enter`, `backspace`, `delete`, `tab`, `escape`, `control`, `alt`, `shift`, + `end`, `home`, `insert`, `left`, `up`, `right`, `down`, `pageUp`, `pageDown`, + `printScreen` + +마우스 이벤트들에 대해서는 `event` 객체는 다음 속성들을 사용할 수 있습니다: + +* `x` Integer (**required**) +* `y` Integer (**required**) +* `button` String - 눌린 버튼. 다음 값들이 가능합니다. `left`, `middle`, `right` +* `globalX` Integer +* `globalY` Integer +* `movementX` Integer +* `movementY` Integer +* `clickCount` Integer + +`mouseWheel` 이벤트에 대해서는 `event` 객체는 다음 속성들을 사용할 수 있습니다: + +* `deltaX` Integer +* `deltaY` Integer +* `wheelTicksX` Integer +* `wheelTicksY` Integer +* `accelerationRatioX` Integer +* `accelerationRatioY` Integer +* `hasPreciseScrollingDeltas` Boolean +* `canScroll` Boolean + +### `webContents.beginFrameSubscription(callback)` + +* `callback` Function + +캡처된 프레임과 프레젠테이션 이벤트를 구독하기 시작합니다. `callback`은 +프레젠테이션 이벤트가 발생했을 때 `callback(frameBuffer)` 형태로 호출됩니다. + +`frameBuffer`는 raw 픽셀 데이터를 가지고 있는 `Buffer` 객체입니다. 많은 장치에서 +32비트 BGRA 포맷을 사용하여 효율적으로 픽셀 데이터를 저장합니다. 하지만 실질적인 +데이터 저장 방식은 프로세서의 엔디안 방식에 따라서 달라집니다. (따라서 현대의 많은 +프로세서에선 little-endian 방식을 사용하므로 위의 포맷을 그대로 표현합니다. 하지만 +몇몇 프로세서는 big-endian 방식을 사용하는데, 이 경우 32비트 ARGB 포맷을 사용합니다) + +### `webContents.endFrameSubscription()` + +프레임 프레젠테이션 이벤트들에 대한 구독을 중지합니다. + +### `webContents.savePage(fullPath, saveType, callback)` + +* `fullPath` String - 전체 파일 경로. +* `saveType` String - 저장 타입을 지정합니다. + * `HTMLOnly` - 페이지의 HTML만 저장합니다. + * `HTMLComplete` - 페이지의 완성된 HTML을 저장합니다. + * `MHTML` - 페이지의 완성된 HTML을 MHTML로 저장합니다. +* `callback` Function - `function(error) {}`. + * `error` Error + +만약 페이지를 저장하는 프로세스가 성공적으로 끝났을 경우 true를 반환합니다. + +```javascript +win.loadURL('https://github.com'); + +win.webContents.on('did-finish-load', function() { + win.webContents.savePage('/tmp/test.html', 'HTMLComplete', function(error) { + if (!error) + console.log("Save page successfully"); + }); +}); +``` + +## Instance Properties + +`WebContents`객체들은 다음 속성들을 가지고 있습니다: + +### `webContents.session` + +이 webContents에서 사용하는 [session](session.md) 객체를 반환합니다. + +### `webContents.devToolsWebContents` + +이 `WebContents`에 대한 개발자 도구의 `WebContents`를 가져옵니다. + +**참고:** 사용자가 절대로 이 객체를 저장해서는 안 됩니다. 개발자 도구가 닫혔을 때, +`null`이 반환될 수 있습니다. diff --git a/docs-translations/ko-KR/api/web-frame.md b/docs-translations/ko-KR/api/web-frame.md index d09114e559c8..2e4a469b5485 100644 --- a/docs-translations/ko-KR/api/web-frame.md +++ b/docs-translations/ko-KR/api/web-frame.md @@ -1,11 +1,12 @@ # webFrame -`web-frame` 모듈은 현재 웹 페이지의 랜더링 상태를 설정 할 수 있도록 관련 유틸리티를 제공하는 모듈입니다. +`web-frame` 모듈은 현재 웹 페이지의 랜더링 상태를 설정 할 수 있도록 관련 유틸리티를 +제공하는 모듈입니다. 다음 예제는 현재 페이지를 200% 줌 합니다: ```javascript -var webFrame = require('web-frame'); +var webFrame = require('electron').webFrame; webFrame.setZoomFactor(2); ``` @@ -18,7 +19,8 @@ webFrame.setZoomFactor(2); * `factor` Number - Zoom 값 -지정한 값으로 페이지를 줌 합니다. 줌 값은 퍼센트를 100으로 나눈 값입니다. (예시: 300% = 3.0) +지정한 값으로 페이지를 줌 합니다. 줌 값은 퍼센트를 100으로 나눈 값입니다. +(예시: 300% = 3.0) ### `webFrame.getZoomFactor()` @@ -28,8 +30,9 @@ webFrame.setZoomFactor(2); * `level` Number - Zoom level -지정한 레벨로 줌 레벨을 변경합니다. 0은 "기본 크기" 입니다. -그리고 각각 레벨 값을 올리거나 내릴 때마다 20%씩 커지거나 작아지고 기본 크기의 50%부터 300%까지 조절 제한이 있습니다. +지정한 레벨로 줌 레벨을 변경합니다. 0은 "기본 크기" 입니다. 그리고 각각 레벨 값을 +올리거나 내릴 때마다 20%씩 커지거나 작아지고 기본 크기의 50%부터 300%까지 조절 제한이 +있습니다. ### `webFrame.getZoomLevel()` @@ -50,30 +53,38 @@ webFrame.setZoomFactor(2); Input field나 text area에 철자 검사(spell checking) 제공자를 설정합니다. -`provider`는 반드시 전달된 단어의 철자가 맞았는지 검사하는 `spellCheck` 메소드를 가지고 있어야 합니다. +`provider`는 반드시 전달된 단어의 철자가 맞았는지 검사하는 `spellCheck` 메소드를 +가지고 있어야 합니다. [node-spellchecker][spellchecker]를 철자 검사 제공자로 사용하는 예제입니다: ```javascript -require('web-frame').setSpellCheckProvider("en-US", true, { +webFrame.setSpellCheckProvider("en-US", true, { spellCheck: function(text) { return !(require('spellchecker').isMisspelled(text)); } }); ``` -### `webFrame.registerUrlSchemeAsSecure(scheme)` +### `webFrame.registerURLSchemeAsSecure(scheme)` * `scheme` String 지정한 `scheme`을 보안 스킴으로 등록합니다. -보안 스킴은 혼합된 컨텐츠 경고를 발생시키지 않습니다. 예를 들어 `https` 와 `data`는 네트워크 공격자로부터 손상될 가능성이 없기 때문에 보안 스킴이라고 할 수 있습니다. +보안 스킴은 혼합된 컨텐츠 경고를 발생시키지 않습니다. 예를 들어 `https` 와 `data`는 +네트워크 공격자로부터 손상될 가능성이 없기 때문에 보안 스킴이라고 할 수 있습니다. -### `webFrame.registerUrlSchemeAsBypassingCsp(scheme)` +### `webFrame.registerURLSchemeAsBypassingCSP(scheme)` * `scheme` String -현재 페이지 컨텐츠의 보안 정책에 상관없이 이 `scheme`로부터 리소스가 로드됩니다. +현재 페이지 컨텐츠의 보안 정책에 상관없이 `scheme`로부터 리소스가 로드됩니다. + +### `webFrame.registerURLSchemeAsPrivileged(scheme)` + + * `scheme` String + +보안 `scheme`를 지정합니다. 리소스와 ServiceWorker 설정에 대해 보안 정책을 우회합니다. [spellchecker]: https://github.com/atom/node-spellchecker diff --git a/docs-translations/ko-KR/api/web-view-tag.md b/docs-translations/ko-KR/api/web-view-tag.md index 69b94465aede..935976b6bde9 100644 --- a/docs-translations/ko-KR/api/web-view-tag.md +++ b/docs-translations/ko-KR/api/web-view-tag.md @@ -1,24 +1,29 @@ # `` 태그 -`guest` 컨텐츠(웹 페이지)를 Electron 앱 페이지에 삽입하기 위해 `webview` 태그를 사용할 수 있습니다. -게스트 컨텐츠는 `webview` 컨테이너에 담겨 대상 페이지에 삽입되고 해당 페이지에선 게스트 컨텐츠의 배치 및 렌더링 과정을 조작할 수 있습니다. +`guest` 컨텐츠(웹 페이지)를 Electron 앱 페이지에 삽입하기 위해 `webview` 태그를 +사용할 수 있습니다. 게스트 컨텐츠는 `webview` 컨테이너에 담겨 대상 페이지에 삽입되고 +해당 페이지에선 게스트 컨텐츠의 배치 및 렌더링 과정을 조작할 수 있습니다. `iframe`과는 달리 `webview`는 어플리케이션과 분리된 프로세스에서 작동합니다. -이는 웹 페이지와 같은 권한을 가지지 않고 앱과 임베디드(게스트) 컨텐츠간의 모든 상호작용이 비동기로 작동한다는 것을 의미합니다. -따라서 임베디드 컨텐츠로부터 어플리케이션을 안전하게 유지할 수 있습니다. +이는 웹 페이지와 같은 권한을 가지지 않고 앱과 임베디드(게스트) 컨텐츠간의 모든 +상호작용이 비동기로 작동한다는 것을 의미합니다. 따라서 임베디드 컨텐츠로부터 +어플리케이션을 안전하게 유지할 수 있습니다. ## 예제 -웹 페이지를 어플리케이션에 삽입하려면 `webview` 태그를 사용해 원하는 타겟 페이지에 추가하면 됩니다. (게스트 컨텐츠가 앱 페이지에 추가 됩니다) -간단한 예로 `webview` 태그의 `src` 속성에 페이지를 지정하고 css 스타일을 이용해서 컨테이너의 외관을 설정할 수 있습니다: +웹 페이지를 어플리케이션에 삽입하려면 `webview` 태그를 사용해 원하는 타겟 페이지에 +추가하면 됩니다. (게스트 컨텐츠가 앱 페이지에 추가 됩니다) 간단한 예로 `webview` +태그의 `src` 속성에 페이지를 지정하고 css 스타일을 이용해서 컨테이너의 외관을 설정할 +수 있습니다: ```html ``` -게스트 컨텐츠를 조작하기 위해 자바스크립트로 `webview` 태그의 이벤트를 리스닝 하여 응답을 받을 수 있습니다. -다음 예제를 참고하세요: 첫번째 리스너는 페이지 로딩 시작시의 이벤트를 확인하고 두번째 리스너는 페이지의 로딩이 끝난시점을 확인합니다. -그리고 페이지를 로드하는 동안 "loading..." 메시지를 표시합니다. +게스트 컨텐츠를 조작하기 위해 자바스크립트로 `webview` 태그의 이벤트를 리스닝 하여 +응답을 받을 수 있습니다. 다음 예제를 참고하세요: 첫번째 리스너는 페이지 로딩 시작시의 +이벤트를 확인하고 두번째 리스너는 페이지의 로딩이 끝난시점을 확인합니다. 그리고 +페이지를 로드하는 동안 "loading..." 메시지를 표시합니다. ```html - + alertOnlineStatus(); + + ``` -메인 프로세스에서 이 이벤트를 처리할 필요가 있는 경우 이벤트를 메인 프로세스로 보낼 수 있습니다. -메인 프로세스는 `navigator` 객체를 가지고 있지 않기 때문에 이 이벤트를 직접 사용할 수 없습니다. +메인 프로세스에서 이 이벤트를 처리할 필요가 있는 경우 이벤트를 메인 프로세스로 보낼 수 +있습니다. 메인 프로세스는 `navigator` 객체를 가지고 있지 않기 때문에 이 이벤트를 직접 +사용할 수는 없습니다. -대신 다음 예제와 같이 Electron의 inter-process communication(ipc) 유틸리티를 사용하면 이벤트를 메인 프로세스로 전달할 수 있습니다. +대신 다음 예제와 같이 Electron의 inter-process communication(ipc) 유틸리티를 +사용하면 이벤트를 메인 프로세스로 전달할 수 있습니다. _main.js_ ```javascript -var app = require('app'); -var ipc = require('ipc'); -var BrowserWindow = require('browser-window'); -var onlineStatusWindow; +const electron = require('electron'); +const app = electron.app; +const ipcMain = electron.ipcMain; +const BrowserWindow = electron.BrowserWindow; +var onlineStatusWindow; app.on('ready', function() { onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); + onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); }); -ipc.on('online-status-changed', function(event, status) { +ipcMain.on('online-status-changed', function(event, status) { console.log(status); }); ``` @@ -63,18 +68,18 @@ _online-status.html_ ```html - - - + updateOnlineStatus(); + + ``` diff --git a/docs-translations/ko-KR/tutorial/quick-start.md b/docs-translations/ko-KR/tutorial/quick-start.md index 8c2e043061ec..cfdc19f00e9c 100644 --- a/docs-translations/ko-KR/tutorial/quick-start.md +++ b/docs-translations/ko-KR/tutorial/quick-start.md @@ -2,38 +2,46 @@ ## 소개 -Electron은 자바스크립트와 함께 제공된 풍부한 네이티브 API를 사용하여 멋진 데스크탑 어플리케이션을 만들 수 있도록 해주는 프레임워크입니다. -이 프레임워크의 io.js(node.js)는 웹 서버 개발이 아닌 데스크탑 어플리케이션 개발에 초점을 맞췄습니다. +Electron은 자바스크립트와 함께 제공된 풍부한 네이티브 API를 사용하여 멋진 데스크탑 +어플리케이션을 만들 수 있도록 해주는 프레임워크입니다. 이 프레임워크의 Node.js는 웹 +서버 개발이 아닌 데스크탑 어플리케이션 개발에 초점을 맞췄습니다. -이 말은 Electron이 GUI 라이브러리의 자바스크립트 바인딩이라는 뜻이 아닙니다. -대신, Electron은 웹 페이지의 GUI를 사용합니다. 쉽게 말해 Electron은 자바스크립트를 사용하여 조작하는 작은 Chromium 브라우저로 볼 수 있습니다. +이 말은 Electron이 GUI 라이브러리의 자바스크립트 바인딩이라는 뜻이 아닙니다. 대신, +Electron은 웹 페이지의 GUI를 사용합니다. 쉽게 말해 Electron은 자바스크립트를 사용하여 +조작하는 작은 Chromium 브라우저로 볼 수 있습니다. ### 메인 프로세스 -Electron은 실행될 때 __메인 프로세스__로 불리는 `package.json`의 `main` 스크립트를 호출합니다. -이 스크립트는 메인 프로세스에서 작동합니다. GUI 컴포넌트를 조작하거나 웹 페이지 창을 생성할 수 있습니다. +Electron은 실행될 때 __메인 프로세스__로 불리는 `package.json`의 `main` 스크립트를 +호출합니다. 이 스크립트는 메인 프로세스에서 작동합니다. GUI 컴포넌트를 조작하거나 웹 +페이지 창을 생성할 수 있습니다. ### 랜더러 프로세스 Electron이 웹페이지를 보여줄 때 Chromium의 multi-processes 구조도 같이 사용됩니다. Electron 프로세스 내에서 작동하는 웹 페이지를 __랜더러 프로세스__ 라고 불립니다. -보통 일반 브라우저의 웹 페이지들은 샌드박스가 적용된 환경에서 작동하며 네이티브 리소스에는 접근할 수 없도록 되어 있습니다. -하지만 Electron은 웹 페이지 내에서 io.js(node.js) API를 사용하여 low-level 수준으로 운영체제와 상호작용할 수 있습니다. +보통 일반 브라우저의 웹 페이지들은 샌드박스가 적용된 환경에서 작동하며 네이티브 +리소스에는 접근할 수 없도록 되어 있습니다. 하지만 Electron은 웹 페이지 내에서 Node.js +API를 사용하여 low-level 수준으로 운영체제와 상호작용할 수 있습니다. ### 메인 프로세스와 랜더러 프로세스의 차이점 메인 프로세스는 `BrowserWindow` Class를 사용하여 새로운 창을 만들 수 있습니다. -`BrowserWindow` 인스턴스는 따로 분리된 프로세스에서 랜더링 되며 이 프로세스를 랜더러 프로세스라고 합니다. -`BrowserWindow` 인스턴스가 소멸할 때 그 창의 랜더러 프로세스도 같이 소멸합니다. +`BrowserWindow` 인스턴스는 따로 분리된 프로세스에서 랜더링 되며 이 프로세스를 랜더러 +프로세스라고 합니다. `BrowserWindow` 인스턴스가 소멸할 때 그 창의 랜더러 프로세스도 +같이 소멸합니다. -메인 프로세스는 모든 웹 페이지와 랜더러 프로세스를 관리하며 랜더러 프로세스는 각각의 프로세스에 고립되며 웹 페이지의 작동에만 영향을 끼칩니다. +메인 프로세스는 모든 웹 페이지와 랜더러 프로세스를 관리하며 랜더러 프로세스는 각각의 +프로세스에 고립되며 웹 페이지의 작동에만 영향을 끼칩니다. -웹 페이지 내에선 기본적으로 네이티브 GUI와 관련된 API를 호출할 수 없도록 설계 되어 있습니다. -왜냐하면 웹 페이지 내에서 네이티브 GUI 리소스를 관리하는 것은 보안에 취약하고 리소스를 누수시킬 수 있기 때문입니다. -꼭 웹 페이지 내에서 API를 사용해야 한다면 메인 프로세스에서 그 작업을 처리할 수 있도록 메인 프로세스와 통신을 해야 합니다. +웹 페이지 내에선 기본적으로 네이티브 GUI와 관련된 API를 호출할 수 없도록 설계 되어 +있습니다. 왜냐하면 웹 페이지 내에서 네이티브 GUI 리소스를 관리하는 것은 보안에 취약하고 +리소스를 누수시킬 수 있기 때문입니다. 꼭 웹 페이지 내에서 API를 사용해야 한다면 메인 +프로세스에서 그 작업을 처리할 수 있도록 메인 프로세스와 통신을 해야 합니다. -Electron에는 메인 프로세스와 랜더러 프로세스 사이에 통신을 할 수 있도록 [ipc](../api/ipc-renderer.md) 모듈을 제공하고 있습니다. +Electron에는 메인 프로세스와 랜더러 프로세스 사이에 통신을 할 수 있도록 +[ipc](../api/ipc-renderer.md) 모듈을 제공하고 있습니다. 또는 [remote](../api/remote.md) 모듈을 사용하여 RPC 스타일로 통신할 수도 있습니다. ## 첫번째 Electron 앱 만들기 @@ -47,9 +55,9 @@ your-app/ └── index.html ``` -`package.json`은 node 모듈의 package.json과 같습니다. -그리고 `main` 필드에 스크립트 파일을 지정하면 메인 프로세스의 엔트리 포인트로 사용합니다. -예를 들어 사용할 수 있는 `package.json`은 다음과 같습니다: +`package.json`은 node 모듈의 package.json과 같습니다. 그리고 `main` 필드에 스크립트 +파일을 지정하면 메인 프로세스의 엔트리 포인트로 사용합니다. 예를 들어 사용할 수 있는 +`package.json`은 다음과 같습니다: ```json { @@ -59,19 +67,22 @@ your-app/ } ``` -__알림__: 만약 `main` 필드가 `package.json`에 설정되어 있지 않으면 Electron은 자동으로 같은 디렉터리의 `index.js`를 로드합니다. +__알림__: 만약 `main` 필드가 `package.json`에 설정되어 있지 않으면 Electron은 +자동으로 같은 디렉터리의 `index.js`를 로드합니다. -반드시 `main.js`에서 창을 만들고 시스템 이밴트를 처리해야합니다. 대표적인 예제로 다음과 같이 작성할 수 있습니다: +반드시 `main.js`에서 창을 만들고 시스템 이벤트를 처리해야 합니다. 대표적인 예제로 +다음과 같이 작성할 수 있습니다: ```javascript -var app = require('app'); // 어플리케이션 기반을 조작 하는 모듈. -var BrowserWindow = require('browser-window'); // 네이티브 브라우저 창을 만드는 모듈. +const electron = require('electron'); +const app = electron.app; // 어플리케이션 기반을 조작 하는 모듈. +const BrowserWindow = electron.BrowserWindow; // 네이티브 브라우저 창을 만드는 모듈. // Electron 개발자에게 crash-report를 보냄. -require('crash-reporter').start(); +electron.crashReporter.start(); // 윈도우 객체를 전역에 유지합니다. 만약 이렇게 하지 않으면 -// 자바스크립트 GC가 일어날 때 창이 자동으로 닫혀버립니다. +// 자바스크립트 GC가 일어날 때 창이 멋대로 닫혀버립니다. var mainWindow = null; // 모든 창이 닫히면 어플리케이션 종료. @@ -90,10 +101,10 @@ app.on('ready', function() { mainWindow = new BrowserWindow({width: 800, height: 600}); // 그리고 현재 디렉터리의 index.html을 로드합니다. - mainWindow.loadUrl('file://' + __dirname + '/index.html'); + mainWindow.loadURL('file://' + __dirname + '/index.html'); - // 개발자 콘솔을 엽니다. - mainWindow.openDevTools(); + // 개발자 도구를 엽니다. + mainWindow.webContents.openDevTools(); // 창이 닫히면 호출됩니다. mainWindow.on('closed', function() { @@ -111,11 +122,13 @@ app.on('ready', function() { + 헬로 월드!

헬로 월드!

- 이 어플리케이션은 io.js 과 + 이 어플리케이션은 node , + Chrome , Electron 을 사용합니다. @@ -123,34 +136,36 @@ app.on('ready', function() { ## 앱 실행하기 -앱을 작성한 후 [어플리케이션 배포](application-distribution.md) 가이드를 따라 앱을 패키징 하고 패키징한 앱을 실행할 수 있습니다. -또한 Electron 실행파일을 다운로드 받아 바로 실행해 볼 수도 있습니다. +앱을 작성한 후 [어플리케이션 배포](application-distribution.md) 가이드를 따라 앱을 +패키징 하고 패키징한 앱을 실행할 수 있습니다. 또한 Electron 실행파일을 다운로드 받아 +바로 실행해 볼 수도 있습니다. -### electron-prebuilt +### electron-prebuilt 사용 -`npm`을 통해 `electron-prebuilt` 패키지를 전역에 설치하면 간단한 명령으로 앱을 실행할 수 있습니다. +`npm`을 통해 `electron-prebuilt` 패키지를 전역에 설치하면 간단한 명령으로 앱을 +실행할 수 있습니다. -앱 디렉터리 내에서 이렇게 실행합니다: +앱 디렉터리 내에서 다음 명령으로 실행할 수 있습니다: ```bash electron . ``` -또는 앱 디렉터리 밖에서 앱 디렉터리를 입력해도 됩니다: +또는 앱 디렉터리 밖에서 앱을 실행할 수도 있습니다: ```bash electron app ``` -npm 모듈을 로컬에 설치했다면 이렇게 실행할 수 있습니다: +npm 모듈을 로컬에 설치했다면 다음 명령으로 실행할 수 있습니다: ```bash ./node_modules/.bin/electron . ``` -### 임의로 다운로드 받은 Electron +### 다운로드 받은 Electron 바이너리 사용 -만약 Electron 바이너리를 임의로 다운로드 받았다면 다음과 같이 앱 디렉터리에 놓고 실행하면 됩니다. +따로 Electron 바이너리를 다운로드 받았다면 다음 예시와 같이 실행하면 됩니다. #### Windows @@ -173,6 +188,27 @@ $ ./Electron.app/Contents/MacOS/Electron your-app/ 어플리케이션 실행파일은 `Electron`의 release 패키지에 포함되어 있습니다. [여기](https://github.com/atom/electron/releases)에서 다운로드 받을 수 있습니다. -### 배포용 파일 만들기 +### 배포용 실행 파일 만들기 -어플리케이션 작성을 완료했다면 [어플리케이션 배포](application-distribution.md) 가이드를 통해 제작한 앱을 본격적으로 배포할 수 있습니다. \ No newline at end of file +어플리케이션 작성을 모두 끝냈다면 [어플리케이션 배포](application-distribution.md) +가이드를 통해 제작한 앱을 패키징하고 배포할 수 있습니다. + +### 미리 작성된 앱 실행하기 + +[`atom/electron-quick-start`](https://github.com/atom/electron-quick-start) +저장소를 클론하면 이 문서에서 작성한 예제 앱을 바로 실행해 볼 수 있습니다. + +**참고**: 이 예제를 실행시키려면 [Git](https://git-scm.com)과 +[Node.js](https://nodejs.org/en/download/)가 필요합니다. (CLI에서 실행 가능한 + [npm](https://npmjs.org)이 있어야 합니다) + +**역주**: `npm`은 보통 Node.js를 설치하면 자동으로 같이 설치됩니다. + +```bash +# 저장소를 클론합니다 +$ git clone https://github.com/atom/electron-quick-start +# 저장소 안으로 들어갑니다 +$ cd electron-quick-start +# 어플리케이션의 종속성 모듈을 설치한 후 실행합니다 +$ npm install && npm start +``` diff --git a/docs-translations/ko-KR/tutorial/supported-platforms.md b/docs-translations/ko-KR/tutorial/supported-platforms.md new file mode 100644 index 000000000000..3b3a00d72156 --- /dev/null +++ b/docs-translations/ko-KR/tutorial/supported-platforms.md @@ -0,0 +1,30 @@ +# 지원하는 플랫폼 + +Electron에선 다음과 같은 플랫폼을 지원합니다: + +### OS X + +OS X는 64비트 바이너리만 제공됩니다. 그리고 최소 OS X 지원 버전은 10.8입니다. + +### Windows + +Windows 7 이후 버전만 지원됩니다. Windows Vista에서도 작동할 수 있지만 아직 모든 작동 +테스트가 완료되지 않았습니다. + +윈도우용 바이너리는 `x86`과 `x64` 모두 제공됩니다. 그리고 `ARM` 버전 윈도우는 아직 +지원하지 않습니다. (역주: 추후 지원할 가능성이 있습니다) + +### Linux + +Ubuntu 12.04 버전에서 빌드된 `ia32`(`i686`), `x64`(`amd64`) 바이너리가 제공됩니다. +그리고 `arm` 버전 바이너리는 ARM v7 hard-float ABI와 Debian Wheezy용 NEON에 맞춰 +제공됩니다. + +미리 빌드된 바이너리가 배포판에서 작동할 수 있는지 여부는 Electron이 빌드된 플랫폼에서 +링크된 라이브러리에 따라 달라집니다. 그래서 현재 Linux 바이너리는 Ubuntu 12.04 버전만 +정상적인 작동이 보장됩니다. 하지만 다음 플랫폼들은 미리 빌드된 Electron 바이너리가 +정상적으로 작동하는 것을 확인했습니다: + +* Ubuntu 12.04 이후 버전 +* Fedora 21 +* Debian 8 diff --git a/docs-translations/ko-KR/tutorial/using-native-node-modules.md b/docs-translations/ko-KR/tutorial/using-native-node-modules.md index 913c300be881..d1c3aa167ad6 100644 --- a/docs-translations/ko-KR/tutorial/using-native-node-modules.md +++ b/docs-translations/ko-KR/tutorial/using-native-node-modules.md @@ -1,54 +1,69 @@ # 네이티브 node 모듈 사용하기 -Electron에선 node.js 네이티브 모듈이 지원됩니다. 하지만 Electron은 공식 node.js의 V8 엔진과는 다른 V8 버전을 사용합니다. -이러한 이유로 네이티브 모듈을 사용하기 위해선 Electron의 V8 버전에 맞춰 네이티브 모듈을 다시 빌드하고 헤더를 변경해야 합니다. +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__ 버전을 사용하기 때문에 호환성 문제가 발생할 수 있습니다. +네이티브 모듈은 node.js가 새로운 V8 버전을 사용함으로 인해 작동하지 않을 수 있습니다. +사용하는 네이티브 모듈이 Electron에 맞춰 작동할 수 있도록 하려면 Electron에서 사용하는 +node.js의 버전을 확인할 필요가 있습니다. Electron에서 사용하는 node 버전은 +[releases](https://github.com/atom/electron/releases)에서 확인할 수 있으며 +`process.version`을 출력하여 버전을 확인할 수도 있습니다. +([시작하기](https://github.com/atom/electron/blob/master/docs/tutorial/quick-start.md)의 +예제를 참고하세요) -이 문제를 해결하기 위해선 모듈이 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로 포팅 할 필요가 있습니다. +혹시 직접 만든 네이티브 모듈이 있다면 [NAN](https://github.com/nodejs/nan/) 모듈을 +사용하는 것을 고려해보는 것이 좋습니다. 이 모듈은 다중 버전의 node.js를 지원하기 쉽게 +만들어 줍니다. 이를 통해 오래된 모듈을 새 버전의 node.js에 맞게 포팅 할 수 있습니다. +Electron도 이 모듈을 통해 포팅된 네이티브 모듈을 사용할 수 있습니다. -## 네이티브 모듈 설치하는 방법 +## 네이티브 모듈을 설치하는 방법 네이티브 모듈을 설치하는 방법은 세 가지 종류가 있습니다. ### 쉬운 방법 -[`electron-rebuild`](https://github.com/paulcbetts/electron-rebuild) 패키지를 사용하면 빠르고 간단하게 네이티브 모듈을 다시 빌드할 수 있습니다. +[`electron-rebuild`](https://github.com/paulcbetts/electron-rebuild) 패키지를 +사용하면 빠르고 간단하게 네이티브 모듈을 다시 빌드할 수 있습니다. -다음 예제는 `electron-rebuild`를 통해 자동으로 모듈의 헤더를 다운로드하고 네이티브 모듈을 빌드합니다: +다음 예제는 `electron-rebuild`를 통해 자동으로 모듈의 헤더를 다운로드하고 네이티브 +모듈을 빌드합니다: ```sh npm install --save-dev electron-rebuild # 필요한 네이티브 모듈을 `npm install`로 설치한 후 다음 명령을 실행하세요: -node ./node_modules/.bin/electron-rebuild +./node_modules/.bin/electron-rebuild + +# Windows에서 문제가 발생하면 다음 명령을 대신 실행하세요: +.\node_modules\.bin\electron-rebuild.cmd ``` -### 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`을 이용한 방법 또한 `npm`을 통해 설치할 수도 있습니다. 환경변수가 필요한 것을 제외하고 일반 Node 모듈을 설치하는 방법과 완전히 똑같습니다: ```bash export npm_config_disturl=https://atom.io/download/atom-shell -export npm_config_target=0.29.1 +export npm_config_target=0.33.1 export npm_config_arch=x64 +export npm_config_runtime=electron HOME=~/.electron-gyp npm install module-name ``` + +### `node-gyp`를 이용한 방법 + +Node 모듈을 `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`에게 알려줍니다. diff --git a/docs-translations/ko-KR/tutorial/using-pepper-flash-plugin.md b/docs-translations/ko-KR/tutorial/using-pepper-flash-plugin.md index f47f20d8a71e..2f15a3532eea 100644 --- a/docs-translations/ko-KR/tutorial/using-pepper-flash-plugin.md +++ b/docs-translations/ko-KR/tutorial/using-pepper-flash-plugin.md @@ -5,33 +5,20 @@ Pepper 플래시 플러그인을 사용하려면 Pepper 플래시 플러그인 ## 플래시 플러그인 준비하기 -크롬 브라우저의 `chrome://plugins` 페이지에 접속한 후 `세부정보`에서 플래시 플러그인의 위치와 버전을 찾을 수 있습니다. -Electron에서 플래시 플러그인을 지원하기 위해선 이 두 가지를 복사해 와야 합니다. +크롬 브라우저의 `chrome://plugins` 페이지에 접속한 후 `세부정보`에서 플래시 +플러그인의 위치와 버전을 찾을 수 있습니다. Electron에서 플래시 플러그인을 지원하기 +위해선 이 두 가지를 복사해 와야 합니다. ## Electron 스위치 추가 -플러그인을 사용하려면 Electron 커맨드 라인에 `--ppapi-flash-path` 와 `ppapi-flash-version` 플래그를 app의 ready 이벤트가 호출되기 전에 추가해야 합니다. +플러그인을 사용하려면 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 +// OS X의 경우, /path/to/PepperFlashPlayer.plugin // Linux의 경우, /path/to/libpepflashplayer.so app.commandLine.appendSwitch('ppapi-flash-path', '/path/to/libpepflashplayer.so'); @@ -46,7 +33,7 @@ app.on('ready', function() { 'plugins': true } }); - mainWindow.loadUrl('file://' + __dirname + '/index.html'); + mainWindow.loadURL('file://' + __dirname + '/index.html'); // Something else }); ``` diff --git a/docs-translations/ko-KR/tutorial/using-selenium-and-webdriver.md b/docs-translations/ko-KR/tutorial/using-selenium-and-webdriver.md index 33dc6311ce35..d0684e0cfe10 100644 --- a/docs-translations/ko-KR/tutorial/using-selenium-and-webdriver.md +++ b/docs-translations/ko-KR/tutorial/using-selenium-and-webdriver.md @@ -1,19 +1,19 @@ # Selenium 과 WebDriver 사용하기 -[ChromeDriver - WebDriver for Chrome][chrome-driver]로 부터 인용: +[ChromeDriver - WebDriver for Chrome][chrome-driver]로부터 인용: > WebDriver는 많은 브라우저에서 웹 앱을 자동적으로 테스트하는 툴입니다. -> 이 툴킷은 웹 페이지를 자동으로 탐색하고 유저 폼을 사용하거나 자바스크립트를 실행하는 등의 작업을 수행할 수 있습니다. -> ChromeDriver는 Chromium의 WebDriver wire 프로토콜 스텐드얼론 서버 구현입니다. -> Chromium 과 WebDriver 팀 멤버에 의해 개발되었습니다. +> 이 툴킷은 웹 페이지를 자동으로 탐색하고 유저 폼을 사용하거나 자바스크립트를 실행하는 +> 등의 작업을 수행할 수 있습니다. ChromeDriver는 Chromium의 WebDriver wire 프로토콜 +> 스텐드얼론 서버 구현입니다. Chromium 과 WebDriver 팀 멤버에 의해 개발되었습니다. -Electron에서 `chromedriver`를 사옹하려면 드라이버에서 Electron을 찾을 수 있도록 해야 하며 -Electron은 Chrome 브라우저와 비슷하다는 점을 기억해야 합니다. +Electron에서 `chromedriver`를 사옹하려면 드라이버에서 Electron을 찾을 수 있도록 해야 +하며 Electron은 Chrome 브라우저와 비슷하다는 점을 기억해야 합니다. ## WebDriverJs 설정하기 -[WebDriverJs](https://code.google.com/p/selenium/wiki/WebDriverJs)는 WebDriver를 사용하여 테스트 할 수 있도록 도와주는 node 패키지입니다. -다음 예제를 참고하세요. +[WebDriverJs](https://code.google.com/p/selenium/wiki/WebDriverJs)는 WebDriver를 +사용하여 테스트 할 수 있도록 도와주는 node 패키지입니다. 다음 예제를 참고하세요. ### 1. 크롬 드라이버 시작 @@ -35,18 +35,22 @@ $ npm install selenium-webdriver ### 3. 크롬 드라이버에 연결 -`selenium-webdriver`를 Electron과 같이 사용하는 방법은 기본적으로 upstream과 같습니다. -한가지 다른점이 있다면 수동으로 크롬 드라이버 연결에 대해 설정하고 Electron 실행파일의 위치를 전달합니다: +`selenium-webdriver`를 Electron과 같이 사용하는 방법은 기본적으로 upstream과 +같습니다. 한가지 다른점이 있다면 수동으로 크롬 드라이버 연결에 대해 설정하고 Electron +실행파일의 위치를 전달합니다: ```javascript -var webdriver = require('selenium-webdriver'); +const 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'}}) + .withCapabilities({ + chromeOptions: { + // 여기에 사용중인 Electron 바이너리의 경로를 지정하세요. + binary: '/Path-to-Your-App.app/Contents/MacOS/Atom', + } + }) .forBrowser('electron') .build(); @@ -64,14 +68,15 @@ driver.quit(); ## WebdriverIO 설정하기 -[WebdriverIO](http://webdriver.io/)는 웹 드라이버와 함께 테스트를 위해 제공되는 node 패키지입니다. +[WebdriverIO](http://webdriver.io/)는 웹 드라이버와 함께 테스트를 위해 제공되는 +node 패키지입니다. ### 1. 크롬 드라이버 시작 먼저, `chromedriver` 바이너리를 다운로드 받고 실행합니다: ```bash -$ chromedriver --url-base=/wd/hub --port=9515 +$ chromedriver --url-base=wd/hub --port=9515 Starting ChromeDriver (v2.10.291558) on port 9515 Only local connections are allowed. ``` @@ -86,18 +91,21 @@ $ npm install webdriverio ### 3. 크롬 드라이버에 연결 ```javascript -var webdriverio = require('webdriverio'); +const webdriverio = require('webdriverio'); var options = { host: "localhost", // localhost에서 작동중인 크롬 드라이버 서버를 사용합니다. port: 9515, // 연결할 크롬 드라이버 서버의 포트를 설정합니다. desiredCapabilities: { browserName: 'chrome', - chromeOptions: {binary: '/Path-to-Your-App.app/Electron'} // Electron 바이너리의 위치 + chromeOptions: { + binary: '/Path-to-Your-App/electron', // Electron 바이너리 경로 + args: [/* cli arguments */] // 선택 사항, 'app=' + /path/to/your/app/ + } } }; var client = webdriverio.remote(options); - + client .init() .url('http://google.com') @@ -109,9 +117,13 @@ client .end(); ``` -## 작업환경 +## 작업 환경 -따로 Electron을 다시 빌드하지 않는 경우 간단히 어플리케이션을 Electron의 리소스 디렉터리에 -[배치](application-distribution.md)하여 바로 테스트 할 수 있습니다. +따로 Electron을 다시 빌드하지 않는 경우 간단히 어플리케이션을 Electron의 리소스 +디렉터리에 [배치](application-distribution.md)하여 바로 테스트 할 수 있습니다. + +또한, Electron 바이너리의 명령줄 인수에 어플리케이션 폴더를 지정하는 방법으로 실행할 +수도 있습니다. 이 방법을 사용하면 어플리케이션 폴더를 Electron의 `resource` +디렉터리로 복사하는 불필요한 과정을 생략할 수 있습니다. [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 index e60d9505a24a..f458e51da8f3 100644 --- a/docs-translations/pt-BR/README.md +++ b/docs-translations/pt-BR/README.md @@ -1,71 +1,82 @@ +Por favor, certifique-se de que está utilizando a documentação que corresponde à sua versão do Electron. +O número da versão deve ser uma parte da URL da página. Se não for, você provavelmente está utilizando +a documentação de um branch de desenvolvimento que pode conter mudanças na API que não são compatíveis +com a sua versão do Electron. Se este for o caso, você pode mudar para uma versão diferente da +documentação na lista de [versões disponíveis](http://electron.atom.io/docs/) em atom.io, +ou se você estiver usando a interface do GitHub, abra o *dropdown* "Switch branches/tags" e +selecione a *tag* que corresponde à sua versão. + ## 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) +* [Platformas Suportadas](../../tutorial/supported-platforms.md) +* [Distribuição de Aplicações](tutorial/application-distribution.md) +* [Guia de Submissão da Mac App Store](../../tutorial/mac-app-store-submission-guide.md) +* [Empacotamento da Aplicação](tutorial/application-packaging.md) +* [Usando Módulos Nativos do Node](tutorial/using-native-node-modules.md) +* [Depuração do Processo Principal](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) +* [Usando o Plugin Pepper Flash](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) +* [Introdução](tutorial/quick-start.md) +* [Integração com o Ambiente de Desenvolvimento](tutorial/desktop-environment-integration.md) +* [Evento de Detecção Online/Offline](tutorial/online-offline-events.md) -## API - Referencias +## API - Referências * [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) +* [Variáveis de Ambiente](../../docs/api/environment-variables.md) -DOM elementos personalizados: +### Elementos DOM 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: +### Módulos para o Processo 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) +* [app](api/app.md) +* [autoUpdater](api/auto-updater.md) +* [BrowserWindow](../../docs/api/browser-window.md) +* [contentTracing](../../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) +* [globalShortcut](../../docs/api/global-shortcut.md) +* [ipcMain](../../docs/api/ipc-main-process.md) +* [Menu](../../docs/api/menu.md) +* [MenuItem](../../docs/api/menu-item.md) +* [powerMonitor](../../docs/api/power-monitor.md) +* [powerSaveBlocker](../../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) +* [Tray](../../docs/api/tray.md) -Módulos do renderizador (web page): +### Módulos para o Processo Renderizador: -* [ipc (renderer)](../../docs/api/ipc-renderer.md) +* [ipcRenderer](../../docs/api/ipc-renderer.md) * [remote](../../docs/api/remote.md) -* [web-frame](../../docs/api/web-frame.md) +* [webFrame](../../docs/api/web-frame.md) -Módulos de ambos os processos: +### Módulos para ambos os processos: * [clipboard](../../docs/api/clipboard.md) -* [crash-reporter](../../docs/api/crash-reporter.md) -* [native-image](../../docs/api/native-image.md) +* [crashReporter](../../docs/api/crash-reporter.md) +* [nativeImage](../../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 +* [Estilo de Código](development/coding-style.md) +* [Estrutura de Diretórios de Código Fonte](../../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) diff --git a/docs-translations/pt-BR/api/accelerator.md b/docs-translations/pt-BR/api/accelerator.md index 87987258b11c..92d70c844e83 100644 --- a/docs-translations/pt-BR/api/accelerator.md +++ b/docs-translations/pt-BR/api/accelerator.md @@ -1,7 +1,7 @@ # 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 `+`. +Um acelerador é uma string que representa um atalho de tecla. Ele pode conter +múltiplos modificadores e códigos chaves, combinados pelo caractere `+`. Exemplos: @@ -11,13 +11,13 @@ Exemplos: ## 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 +então use `CommandOrControl` que representa a tecla `Command` existente no OS X 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. +e para a tecla `Cmd` para OS X. -## Modificadores disponiveis +## Modificadores disponíveis * `Command` (ou `Cmd` abreviado) * `Control` (ou `Ctrl` abreviado) @@ -26,21 +26,21 @@ e para a tecla `Cmd` para OSX. * `Shift` * `Super` -## Códigos chaves disponiveis +## Códigos chaves disponíveis -* `0` to `9` -* `A` to `Z` -* `F1` to `F24` -* Punctuations like `~`, `!`, `@`, `#`, `$`, etc. +* `0` até `9` +* `A` até `Z` +* `F1` até `F24` +* Pontuações como `~`, `!`, `@`, `#`, `$`, 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 +* `Return` (ou `Enter` como pseudônimo) +* `Up`, `Down`, `Left` e `Right` +* `Home` e `End` +* `PageUp` e `PageDown` +* `Escape` (ou `Esc` abreviado) +* `VolumeUp`, `VolumeDown` e `VolumeMute` +* `MediaNextTrack`, `MediaPreviousTrack`, `MediaStop` e `MediaPlayPause` diff --git a/docs-translations/pt-BR/api/app.md b/docs-translations/pt-BR/api/app.md new file mode 100644 index 000000000000..85cfaade5852 --- /dev/null +++ b/docs-translations/pt-BR/api/app.md @@ -0,0 +1,452 @@ +# app + +O módulo `app` é responsável por controlar o ciclo de vida do aplicativo. + +O exemplo a seguir mostra como fechar o aplicativo quando a última janela é fechada: + +```javascript +const app = require('electron').app; +app.on('window-all-closed', function() { + app.quit(); +}); +``` + +## Eventos + +O objeto `app` emite os seguintes eventos: + +### Evento: 'will-finish-launching' + +Emitido quando o aplicativo finaliza a inicialização básica. No Windows e no Linux, +o evento `will-finish-launching` é o mesmo que o evento `ready`; No OS X, +esse evento representa a notificação `applicationWillFinishLaunching` do `NSApplication`. +Normalmente aqui seriam criados *listeners* para os eventos `open-file` e `open-url`, e inicializar o *crash reporter* e atualizador automático. + +Na maioria dos casos, você deve fazer tudo no manipulador de eventos do `ready`. + +### Evento: 'ready' + +Emitido quando o Electron finaliza a inicialização. + +### Evento: 'window-all-closed' + +Emitido quando todas as janelas forem fechadas. + +Este evento só é emitido quando o aplicativo não for fechar. Se o +usuário pressionou`Cmd + Q`, ou o desenvolvedor chamou `app.quit()`, +o Electron tentará primeiro fechar todas as janelas e então emitir o +evento `will-quit`, e neste caso o evento `window-all-closed` não +seria emitido. + +### Evento: 'before-quit' + +Retorna: + +* `event` Event + +Emitido antes que o aplicativo comece a fechar suas janelas. +Chamar `event.preventDefault()` irá impedir o comportamento padrão, +que é terminar o aplicativo. + +### Evento: 'will-quit' + +Retorna: + +* `event` Event + +Emitido quando todas as janelas foram fechadas e o aplicativo irá finalizar. +Chamar `event.preventDefault()` irá impedir o comportamento padrão, +que é terminar o aplicativo. + +Veja a descrição do evento `window-all-closed` para as diferenças entre o +evento `will-quit` e `window-all-closed`. + +### Evento: 'quit' + +Emitido quando o aplicativo está finalizando. + +### Evento: 'open-file' _OS X_ + +Retorna: + +* `event` Event +* `path` String + +Emitido quando o usuário deseja abrir um arquivo com o aplicativo. O evento +`open-file` normalmente é emitido quando o aplicativo já está aberto e o S.O. +quer reutilizar o aplicativo para abrir o arquivo. `open-file` também é emitido +quando um arquivo é jogado no *dock* e o aplicativo ainda não está rodando. +Certifique-se de utilizar um *listener* para o evento `open-file` cedo na +inicialização do seu aplicativo para cuidar deste caso (antes mesmo do evento +`ready` ser emitido). + +Você deve chamar `event.preventDefault()` se quiser cuidar deste caso. + +No Windows, você deve fazer o *parse* do `process.argv` para pegar o +endereço do arquivo. + +### Evento: 'open-url' _OS X_ + +Retorna: + +* `event` Event +* `url` String + +Emitido quando o usuário deseja abrir uma URL com o aplicativo. O esquema deve +ser registrado para ser aberto pelo seu aplicativo. + +Você deve chamar `event.preventDefault()` se quiser cuidar deste caso. + +### Evento: 'activate' _OS X_ + +Retorna: + +* `event` Event +* `hasVisibleWindows` Boolean + +Emitido quando o aplicativo é ativado, que normalmente acontece quando o ícone +do aplicativo no *dock* é clicado. + +### Evento: 'browser-window-blur' + +Retorna: + +* `event` Event +* `window` BrowserWindow + +Emitido quando uma [browserWindow](../../../docs/api/browser-window.md) fica embaçada. + +### Evento: 'browser-window-focus' + +Retorna: + +* `event` Event +* `window` BrowserWindow + +Emitido quando uma [browserWindow](../../../docs/api/browser-window.md) é focada. + +### Evento: 'browser-window-created' + +Retorna: + +* `event` Event +* `window` BrowserWindow + +Emitido quando uma nova [browserWindow](../../../docs/api/browser-window.md) é criada. + +### Evento: 'certificate-error' + +Returns: + +* `event` Event +* `webContents` [WebContents](../../../docs/api/web-contents.md) +* `url` URL +* `error` String - O código de erro +* `certificate` Object + * `data` Buffer - dados codificados PEM + * `issuerName` String +* `callback` Function + +Emitido quando há uma falha na verificação do `certificate` para a `url`, +para confiar no certificado, você deve impedir o comportamento padrão com +`event.preventDefault()` e chamar `callback(true)`. + +```javascript +session.on('certificate-error', function(event, webContents, url, error, certificate, callback) { + if (url == "https://github.com") { + // Lógica de verificação. + event.preventDefault(); + callback(true); + } else { + callback(false); + } +}); +``` + +### Evento: 'select-client-certificate' + +Retorna: + +* `event` Event +* `webContents` [WebContents](../../../docs/api/web-contents.md) +* `url` URL +* `certificateList` [Objects] + * `data` Buffer - dados codificados PEM + * `issuerName` String - Nome Comum do Emissor +* `callback` Function + +Emitido quando um certificado de cliente é requisitado. + +A `url` corresponde à entrada de navegação requisitando o certificado do +cliente e `callback` precisa ser chamada com uma entrada filtrada da lista. +Usar `event.preventDefault()` impede o aplicativo de usar o primeiro certificado +da memória. + +```javascript +app.on('select-client-certificate', function(event, webContents, url, list, callback) { + event.preventDefault(); + callback(list[0]); +}) +``` + +### Evento: 'login' + +Retorna: + +* `event` Event +* `webContents` [WebContents](../../../docs/api/web-contents.md) +* `request` Object + * `method` String + * `url` URL + * `referrer` URL +* `authInfo` Object + * `isProxy` Boolean + * `scheme` String + * `host` String + * `port` Integer + * `realm` String +* `callback` Function + +Emitido quando `webContents` deseja fazer autenticação básica. + +O comportamento padrão é cancelar todas as autenticações, para sobrescrever +isto, você deve impedir esse comportamento com `event.preventDefault()` e +chamar `callback(username, password)` com as credenciais. + +```javascript +app.on('login', function(event, webContents, request, authInfo, callback) { + event.preventDefault(); + callback('username', 'secret'); +}) +``` + +### Evento: 'gpu-process-crashed' + +Emitido quando o processo da gpu falha. + +## Métodos + +O objeto `app` possui os seguintes métodos: + +**Nota:** Alguns métodos só estão disponíveis em sistemas operacionais específicos e estão rotulados como tal. + +### `app.quit()` + +Tente fechar todas as janelas. O evento `before-quit` será emitido primeiro. Se todas +as janelas fecharem com sucesso, o evento `will-quit` será emitido e por padrão o +aplicativo irá terminar. + +Este método garante que todos os manipuladores de evento `beforeunload` e `unload` +sejam corretamente executados. É possível que uma janela cancele o processo de +encerramento ao retornar `false` no manipulador de evento `beforeunload`. + +### `app.exit(exitCode)` + +* `exitCode` Integer + +Finaliza imediatamente com `exitCode`. + +Todas as janelas serão fechadas imediatamente sem perguntar ao usuário, e os eventos +`before-quit` e `will-quit` não serão emitidos. + +### `app.getAppPath()` + +Retorna o atual diretório do aplicativo. + +### `app.getPath(name)` + +* `name` String + +Retorna um endereço para um diretório especial ou arquivo associado com `nome`. +Numa falha um `Error` é lançado. + +Você pode requisitar os seguintes endereços pelo nome: + +* `home` Diretório *home* do usuário. +* `appData` Diretório de dados do aplicativo por usuário, que por padrão aponta para: + * `%APPDATA%` no Windows + * `$XDG_CONFIG_HOME` ou `~/.config` no Linux + * `~/Library/Application Support` no OS X +* `userData` O diretório para guardar os arquivos de configuração do seu aplicativo, que por padrão é o diretório `appData` concatenado com o nome do seu aplicativo. +* `temp` Diretório temporário. +* `exe` O arquivo executável atual. +* `module` A biblioteca `libchromiumcontent`. +* `desktop` O diretório *Desktop* do usuário atual. +* `documents` Diretório "Meus Documentos" do usuário. +* `downloads` Diretório dos downloads do usuário. +* `music` Diretório de músicas do usuário. +* `pictures` Diretório de imagens do usuário. +* `videos` Diretório de vídeos do usuário. + +### `app.setPath(name, path)` + +* `name` String +* `path` String + +Sobrescreve o `path` para um diretório especial ou arquivo associado com `name`. +Se o endereço especifica um diretório não existente, o diretório será criado por +este método. Numa falha um `Error` é lançado. + +Você pode sobrescrever apenas endereços com um `name` definido em `app.getPath`. + +Por padrão, *cookies* e *caches* de páginas web serão guardadas no diretório `userData`. Se você quiser mudar esta localização, você deve sobrescrever o +endereço `userData` antes que o evento `ready` do módulo `app` seja emitido. + +### `app.getVersion()` + +Retorna a versão do aplicativo carregado. Se nenhuma versão for encontrada no +arquivo `package.json` do aplicativo, a versão do pacote ou executável atual é +retornada. + +### `app.getName()` + +Retorna o nome do aplicativo atual, que é o nome no arquivo `package.json` do +aplicativo. + +Normalmente o campo `name` do `package.json` é um nome curto em letras minúsculas, +de acordo com as especificações de módulos npm. Normalmente você deve também +especificar um campo `productName`, que é o nome completo em letras maiúsculas do +seu aplicativo, e que será preferido ao `name` pelo Electron. + +### `app.getLocale()` + +Retorna a localidade atual do aplicativo. + +### `app.addRecentDocument(path)` _OS X_ _Windows_ + +* `path` String + +Adiciona `path` à lista de documentos recentes. + +Esta lista é gerenciada pelo S.O.. No Windows você pode visitar a lista pela +barra de tarefas, e no OS X você pode visita-la pelo *dock*. + +### `app.clearRecentDocuments()` _OS X_ _Windows_ + +Limpa a lista de documentos recentes. + +### `app.setUserTasks(tasks)` _Windows_ + +* `tasks` Array - Vetor de objetos `Task` + +Adiciona `tasks` à categoria [Tasks][tasks] do JumpList no Windows. + +`tasks` é um vetor de objetos `Task` no seguinte formato: + +`Task` Object +* `program` String - Endereço do programa a ser executado, normalmente você deve especificar `process.execPath` que abre o programa atual. +* `arguments` String - Os argumentos de linha de comando quando `program` é executado. +* `title` String - A string a ser exibida em uma JumpList. +* `description` String - Descrição desta *task*. +* `iconPath` String - O endereço absoluto para um ícone a ser exibido em uma JumpList, que pode ser um arquivo arbitrário que contém um ícone. Normalmente você pode especificar `process.execPath` para mostrar o ícone do programa. +* `iconIndex` Integer - O índice do ícone do arquivo do icone. Se um arquivo de ícone consiste de dois ou mais ícones, defina este valor para identificar o ícone. Se o arquivo de ícone consiste de um ícone apenas, este valor é 0. + +### `app.allowNTLMCredentialsForAllDomains(allow)` + +* `allow` Boolean + +Define dinamicamente se sempre envia credenciais para HTTP NTLM ou autenticação *Negotiate* - normalmente, o Electron irá mandar apenas credenciais NTLM/Kerberos para URLs que se enquadram em sites "Intranet Local" (estão no mesmo domínio que você). +Entretanto, esta detecção frequentemente falha quando redes corporativas são mal configuradas, então isso permite optar por esse comportamento e habilitá-lo para todas as URLs. + +### `app.makeSingleInstance(callback)` + +* `callback` Function + +Este método faz da sua aplicação uma Aplicação de Instância Única - invés de permitir múltiplas instâncias do seu aplicativo rodarem, isto irá assegurar que apenas uma única instância do seu aplicativo rodará, e outras instâncias sinalizam esta instância e finalizam. + +`callback` será chamado com `callback(argv, workingDirectory)` quando uma segunda instância tenha sido executada. `argv` é um vetor de argumentos de linha de comando da segunda instância, e `workingDirectory` é o atual endereço de seu diretório. +Normalmente aplicativos respondem à isso não minimizando sua janela primária e dando foco à ela. + +É garantida a execução do `callback` após o evento `ready` do `app` ser emitido. + +Este método retorna `false` caso seu processo seja a instância primária do aplicativo e seu aplicativo deve continuar carregando. E retorna `true` caso seu processo tenha enviado seus parâmetros para outra instância, e você deve imediatamente finalizar. + +No OS X o sistema enforça instância única automaticamente quando usuários tentam abrir uma segunda instância do seu aplicativo no *Finder*, e os eventos `open-file` e `open-url` serão emitidos para isso. Entretanto, quando usuários inicializam seu aplicativo na linha de comando, o mecanismo de instância única do sistema será ignorado e você terá de utilizar esse método para assegurar-se de ter uma instância única. + +Um exemplo de ativação da janela de primeira instância quando uma segunda instância inicializa: + +```js +var myWindow = null; + +var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) { + // Alguém tentou rodar uma segunda instância, devemos focar nossa janela + if (myWindow) { + if (myWindow.isMinimized()) myWindow.restore(); + myWindow.focus(); + } + return true; +}); + +if (shouldQuit) { + app.quit(); + return; +} + +// Cria myWindow, carrega o resto do aplicativo, etc... +app.on('ready', function() { +}); +``` + +### `app.setAppUserModelId(id)` _Windows_ + +* `id` String + +Muda o [Application User Model ID][app-user-model-id] para `id`. + +### `app.commandLine.appendSwitch(switch[, value])` + +Adiciona uma opção (com `value` opcional) à linha de comando do Chromium. + +**Nota:** Isto não irá afetar `process.argv`, e é utilizado principalmente por desenvolvedores para controlar alguns comportamentos de baixo nível do Chromium. + +### `app.commandLine.appendArgument(value)` + +Adiciona um argumento à linha de comando do Chromium. O argumento será passado com aspas corretamente. + +**Nota:** Isto não irá afetar `process.argv`. + +### `app.dock.bounce([type])` _OS X_ + +* `type` String (opcional) - Pode ser `critical` ou `informational`. O padrão é + `informational` + +Quando `critical` é passado, o ícone do *dock* irá pular até que o aplicativo se torne ativo ou a requisição seja cancelada. + +Quando `informational` é passado, o ícone do *dock* irá pular por um segundo. +Entretanto, a requisição se mantém ativa até que o aplicativo se torne ativo ou a requisição seja cancelada. + +Retorna um ID representando a requisição. + +### `app.dock.cancelBounce(id)` _OS X_ + +* `id` Integer + +Cancela o salto do `id`. + +### `app.dock.setBadge(text)` _OS X_ + +* `text` String + +Define a string a ser exibida na área de *badging* do *dock*. + +### `app.dock.getBadge()` _OS X_ + +Retorna a string da *badge* do *dock*. + +### `app.dock.hide()` _OS X_ + +Esconde o ícone do *dock*. + +### `app.dock.show()` _OS X_ + +Exibe o ícone do *dock*. + +### `app.dock.setMenu(menu)` _OS X_ + +* `menu` Menu + +Define o [menu do dock][dock-menu] do aplicativo. + +[dock-menu]:https://developer.apple.com/library/mac/documentation/Carbon/Conceptual/customizing_docktile/concepts/dockconcepts.html#//apple_ref/doc/uid/TP30000986-CH2-TPXREF103 +[tasks]:http://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks +[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx diff --git a/docs-translations/pt-BR/api/auto-updater.md b/docs-translations/pt-BR/api/auto-updater.md new file mode 100644 index 000000000000..27c3ef3c0740 --- /dev/null +++ b/docs-translations/pt-BR/api/auto-updater.md @@ -0,0 +1,85 @@ +# autoUpdater + +Este módulo oferece uma interface para o framework de atualização automática `Squirrel`. + +## Avisos sobre Plataformas + +Embora o `autoUpdater` ofereça uma API uniforme para diferentes plataformas, existem diferenças sutis em cada plataforma. + +### OS X + +No OS X, o módulo `autoUpdater` é construído sobre o [Squirrel.Mac][squirrel-mac], o que significa que você não precisa de nenhuma configuração especial para fazê-lo funcionar. Para requerimentos de servidor, você pode ler [Server Support][server-support]. + +### Windows + +No Windows, você deve instalar seu aplicativo na máquina de um usuário antes que possa usar o auto-updater, então é recomendado utilizar o módulo [grunt-electron-installer][installer] para gerar um instalador do Windows. + +O instalador gerado com Squirrel irá criar um ícone de atalho com um [Application User Model ID][app-user-model-id] no formato `com.squirrel.PACKAGE_ID.YOUR_EXE_WITHOUT_DOT_EXE`, por exemplo: `com.squirrel.slack.Slack` e `com.squirrel.code.Code`. Você precisa usar o mesmo ID para seu aplicativo a API `app.setAppUserModelId`, senão o Windows não conseguirá fixar seu aplicativo corretamente na barra de tarefas. + +A configuração do servidor também é diferente do OS X. Você pode ler a documentação do [Squirrel.Windows][squirrel-windows] para mais detalhes. + +### Linux + +Não há suporte nativo do auto-updater para Linux, então é recomendado utilizar o gerenciador de pacotes da distribuição para atualizar seu aplicativo. + +## Eventos + +O objeto `autoUpdater` emite os seguintes eventos: + +### Evento: 'error' + +Retorna: + +* `error` Error + +Emitido quando há um erro durante a atualização. + +### Evento: 'checking-for-update' + +Emitido quando está verificando se uma atualização foi inicializada. + +### Evento: 'update-available' + +Emitido quando há uma atualização disponível. A autalização é baixada automaticamente. + +### Evento: 'update-not-available' + +Emitido quando não há uma atualização disponível. + +### Evento: 'update-downloaded' + +Retorna: + +* `event` Event +* `releaseNotes` String +* `releaseName` String +* `releaseDate` Date +* `updateURL` String + +Emitido quando uma atualização foi baixada. + +No Windows apenas `releaseName` está disponível. + +## Métodos + +O objeto `autoUpdater` possui os seguintes métodos: + +### `autoUpdater.setFeedURL(url)` + +* `url` String + +Define a `url` e inicializa o auto-updater. A `url` não pode ser alterada uma vez que foi definida. + +### `autoUpdater.checkForUpdates()` + +Pergunta ao servidor se há uma atualização. Você deve chamar `setFeedURL` antes de usar esta API. + +### `autoUpdater.quitAndInstall()` + +Reinicia o aplicativo e instala a atualização após esta ter sido baixada. Só deve ser chamado após o `update-downloaded` ter sido emitido. + +[squirrel-mac]: https://github.com/Squirrel/Squirrel.Mac +[server-support]: https://github.com/Squirrel/Squirrel.Mac#server-support +[squirrel-windows]: https://github.com/Squirrel/Squirrel.Windows +[installer]: https://github.com/atom/grunt-electron-installer +[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx diff --git a/docs-translations/pt-BR/api/process.md b/docs-translations/pt-BR/api/process.md index 3da0dc58391b..1c20e2df1187 100644 --- a/docs-translations/pt-BR/api/process.md +++ b/docs-translations/pt-BR/api/process.md @@ -1,22 +1,48 @@ # process -O objeto `process` no Electron tem as seguintes diferenças de um upstream node: +O objeto `process` no Electron tem as seguintes diferenças do objeto no upstream node: -* `process.type` String - Tipo de processo, pode ser `browser` (i.e. main process) +* `process.type` String - Tipo de processo, pode ser `browser` (processo principal) 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. +* `process.resourcesPath` String - Caminho para o código fonte JavaScript. +* `process.mas` Boolean - Para build da Mac App Store, este valor é `true`, para outros builds é `undefined`. + +## Eventos + +### Evento: 'loaded' + +Emitido quando o Electron carregou seu script de inicialização interno e está começando a carregar a página web ou o script principal. + +Pode ser utilizado pelo script pré-carregamento (preload.js abaixo) para adicionar símbolos globais do Node removidos para o escopo global quando a integração do node é desligada: + +```js +// preload.js +var _setImmediate = setImmediate; +var _clearImmediate = clearImmediate; +process.once('loaded', function() { + global.setImmediate = _setImmediate; + global.clearImmediate = _clearImmediate; +}); +``` + +## Propriedades + +### `process.noAsar` + +Definir isto para `true` pode desabilitar o suporte para arquivos `asar` nos módulos nativos do Node. # Métodos -O objeto `process` tem os seguintes método: + +O objeto `process` tem os seguintes métodos: ### `process.hang` -Afeta a thread principal do processo atual. +Faz com que o *thread* principal do processo congele. -## process.setFdLimit(MaxDescritores) _OS X_ _Linux_ +### `process.setFdLimit(maxDescriptors)` _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 +o que for menor para o processo atual. diff --git a/docs-translations/pt-BR/api/shell.md b/docs-translations/pt-BR/api/shell.md index 7c3f24ade509..65e0a2d42636 100644 --- a/docs-translations/pt-BR/api/shell.md +++ b/docs-translations/pt-BR/api/shell.md @@ -1,11 +1,11 @@ # shell -O módulo `shell` fornece funções relacionadas intereções com o OS do usuário. +O módulo `shell` fornece funções relacionadas à integração com o desktop. Um exemplo para abrir uma URL no browser padrão do usuário: ```javascript -var shell = require('shell'); +const shell = require('shell'); shell.openExternal('https://github.com'); ``` @@ -17,26 +17,26 @@ O módulo `shell` tem os seguintes métodos: * `fullPath` String -Exibe o arquivo no gerenciador de arquivos padrão do sistema. Se possivel, seleciona o arquivo automaticamente. +Exibe o arquivo num gerenciador de arquivos. Se possivel, seleciona o arquivo. ### `shell.openItem(fullPath)` * `fullPath` String -Abre o arquivo em seu programa padrão. +Abre o arquivo de maneira padrão do desktop. ### `shell.openExternal(url)` * `url` String -Abre o arquivo seguido de um protocol em seu programa padrão. (Por -exemplo, mailto:foo@bar.com.) +Abre a URL de protocolo externo de maneira padrão do desktop. (Por +exemplo, mailto: URLs no programa de email padrão do usuário) ### `shell.moveItemToTrash(fullPath)` * `fullPath` String -Move o arquivo para a lixeira e retorna um boolean com o resultado da operação. +Move o arquivo para a lixeira e retorna um status boolean com o resultado da operação. ### `shell.beep()` diff --git a/docs-translations/pt-BR/development/coding-style.md b/docs-translations/pt-BR/development/coding-style.md new file mode 100644 index 000000000000..b0068d568924 --- /dev/null +++ b/docs-translations/pt-BR/development/coding-style.md @@ -0,0 +1,26 @@ +# Estilo de Codificação + +Estas são as diretrizes de estilo para codificar no Electron. + +## C++ e Python + +Para C++ e Python, seguimos o [Estilo de Codificação](http://www.chromium.org/developers/coding-style) do projeto Chromium. Há também um +script `script/cpplint.py` para verificar se todos os arquivos estão em conformidade. + +A versão Python que estamos usando agora é a Python 2.7. + +O código C++ usa várias abstrações e tipos do Chromium, por isso é recomendado familiarizar-se com eles. Um bom lugar para começar é com a documentação do Chromium [Important Abstractions and Data Structures](https://www.chromium.org/developers/coding-style/important-abstractions-and-data-structures). O documento menciona alguns tipos especiais, *scoped types* (que automaticamente liberam sua memória ao sair do escopo), mecanismos de *log* etc. + +## CoffeeScript + +Para CoffeeScript, seguimos o [Guia de Estilo] (https://github.com/styleguide/javascript) do GitHub com as seguintes regras: + +* Os arquivos **NÃO DEVEM** terminar com uma nova linha, porque queremos corresponder aos padrões de estilo Google. + +* Os nomes dos arquivos devem ser concatenados com `-` em vez de `_`, por exemplo, `file-name.coffee` em vez de `file_name.coffee`, porque no [github/atom](https://github.com/github/atom) os nomes dos módulos são geralmente da forma `module-name`. Esta regra só se aplica aos arquivos com extensão `.coffee`. +* +## Nomes de APIs + +Ao criar uma nova API, devemos preferencialmente utilizar métodos getters e setters em vez do +estilo de uma função única do jQuery. Por exemplo, `.getText()` e `.setText(text)` são preferenciais a `.text([text])`. Existe uma +[discussão](https://github.com/atom/electron/issues/46) sobre este assunto. diff --git a/docs-translations/pt-BR/tutorial/application-packaging.md b/docs-translations/pt-BR/tutorial/application-packaging.md index f55cbb2f7d64..209188b2f5f9 100644 --- a/docs-translations/pt-BR/tutorial/application-packaging.md +++ b/docs-translations/pt-BR/tutorial/application-packaging.md @@ -71,7 +71,7 @@ Você também pode renderizar uma página web apartir de um arquivo `asar` utili ```javascript var BrowserWindow = require('browser-window'); var win = new BrowserWindow({width: 800, height: 600}); -win.loadUrl('file:///path/to/example.asar/static/index.html'); +win.loadURL('file:///path/to/example.asar/static/index.html'); ``` ### API Web @@ -155,4 +155,4 @@ 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 +Mais informações no repositório [asar](https://github.com/atom/asar) diff --git a/docs-translations/pt-BR/tutorial/debugging-main-process.md b/docs-translations/pt-BR/tutorial/debugging-main-process.md new file mode 100644 index 000000000000..ee79a523e928 --- /dev/null +++ b/docs-translations/pt-BR/tutorial/debugging-main-process.md @@ -0,0 +1,53 @@ +# Depurando o Processo Principal + +A janela do navegador, DevTools, pode somente depurar o processo de renderização +de scripts (por exemplo, as páginas da web). Para providenciar um modo de +depurar os scripts através do processo principal, o Electron criou as opções +`--debug` e `--debug-brk`. + +## Opções da Linha de Comando + +Use a seguinte opção na linha de comando para depurar o processo principal do +Electron: + +### `--debug=[porta]` + +Quando este comando é usado, o Electron irá executar o protocolo de depuração +V8 mandando as mensagens na `porta`. A `porta` padrão é `5858`. + +### `--debug-brk=[porta]` + +Semelhante ao `--debug`, porém pausa o script na primeira linha. + +## Usando node-inspector para depurar + +__Nota:__ O Electron usa a versão v0.11.13 do Node, a qual, atualmenta não +funciona muito bem com node-inspector, e o processo principal irá quebrar se +você inspecionar o objeto `process` pelo console do node-inspector. + +### 1. Inicie o servidor [node-inspector][node-inspector] + +```bash +$ node-inspector +``` + +### 2. Habilite o modo de depuração para o Electron + +Você pode também iniciar o Electron com um ponto de depuração, desta maneira: + +```bash +$ electron --debug=5858 sua/aplicacao +``` + +ou para pausar o script na primeira linha: + +```bash +$ electron --debug-brk=5858 sua/aplicacao +``` + +### 3. Carregue o debugger UI + +Abra este endereço http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 +usando o Chrome. + +[node-inspector]: https://github.com/node-inspector/node-inspector diff --git a/docs-translations/pt-BR/tutorial/desktop-environment-integration.md b/docs-translations/pt-BR/tutorial/desktop-environment-integration.md new file mode 100644 index 000000000000..20a78d32d537 --- /dev/null +++ b/docs-translations/pt-BR/tutorial/desktop-environment-integration.md @@ -0,0 +1,260 @@ +# Integração com o ambiente desktop + +Diferentes sistemas operacionais possuem diferentes formas de integrar +aplicacões desktop em seus ambientes. Por exemplo, no Windows, as aplicações podem +inserir atalhos no JumpList da barra de tarefas, no Mac, aplicações podem implementar um +menu customizado na dock. + +Este guia explica como integrar suas aplicações no ambiente desktop com a API +do Electron. + +## Documentos Recentes (Windows & OS X) + +O Windows e o OS X disponibilizam um acesso fácil para a lista de arquivos +abertos recentemente pela aplicação através do JumpList ou Dock Menu respectivamente. + +__JumpList:__ + +![JumpList Recent Files](http://i.msdn.microsoft.com/dynimg/IC420538.png) + +__Dock menu da Aplicação:__ + + + +Para adicionar um arquivo para os documentos recentes, você pode usar a API +[app.addRecentDocument][addrecentdocument]: + +```javascript +var app = require('app'); +app.addRecentDocument('/Users/USERNAME/Desktop/work.type'); +``` + +E você pode usar a API [app.clearRecentDocuments][clearrecentdocuments] para +limpar a lista de documentos recentes. + +```javascript +app.clearRecentDocuments(); +``` + +### Notas para Windows + +A fim de ser possível usar estas funcionalidades no Windows, sua aplicação deve +estar registrada como um handler daquele tipo de documento, caso contrário, o +arquivo não será exibido no JumpList mesmo depois de você ter adicionado isto. +Você pode encontrar qualquer coisa sobre o registro da aplicacão em +[Application Registration][app-registration]. + +Quando um usuário clica em um arquivo na JumpList, uma nova instância da sua aplicacão +deve ser iniciada com o caminho do arquivo adicionado como um argumento de +linha de comando. + +### Notas para OS X + +Quando um arquivo for requisitado pelo menu de documentos recentes, o evento `open-file` +do módulo `app` irá ser emitido. + +## Dock Menu customizado (OS X) + +OS X permite que desenvolvedores especifiquem um menu customizado para a dock, +que normalmente contém alguns atalhos para as funcionalidades mais utilizadas +da sua aplicação. + +__Dock menu do Terminal.app:__ + + + +Para criar seu Dock Menu customizado, você pode usar a API `app.dock.setMenu`, +ela está disponível apenas no 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); +``` + +## Tarefas do Usuário (Windows) + +No Windows você pode especificar ações customizadas na categoria `Tarefas` do JumpList, +esse texto foi copiado do 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. + +__Tarefas do Internet Explorer:__ + +![IE](http://i.msdn.microsoft.com/dynimg/IC420539.png) + +Ao contrário do Menu Dock no OS X que é um verdadeiro menu, tarefas do usuário no Windows +funcionam como atalhos, de uma forma que quando o usuário clica em uma tarefa, um programa +deve ser executado com os argumentos especificados. + +Para setar tarefas do usuário para sua aplicação, você pode usar a API +[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' + } +]); +``` + +Para limpar sua lista de tarefas, apenas chame `app.setUserTasks` com um +array vazio. + +```javascript +app.setUserTasks([]); +``` + +As tarefas do usuário são exibidas mesmo depois da aplicação ser fechada, +então o ícone e o caminho do programa especificado pela tarefa deve existir +até sua aplicação ser desinstalada. + +## Miniaturas na Barra de Ferramentas + +No Windows você pode adicionar uma miniatura na barra de ferramentas com botões +específicos para a janela e barra de tarefas para aplicação. Isso provê ao usuário +uma forma de acessar um comando específico para janela sem ser necessário restaurar +ou ativar a janela. + +Isto é ilustrado no 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. + +__Miniaturas da barra de tarefas do Windows Media Player:__ + +![player](https://i-msdn.sec.s-msft.com/dynimg/IC420540.png) + +Você pode usar [BrowserWindow.setThumbarButtons][setthumbarbuttons] para criar +miniaturas na barra de ferramentas para sua aplicação. + +``` +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."); } + } +]); +``` + +Para limpar os botões na miniatura da barra de ferramentas, apenas chame +`BrowserWindow.setThumbarButtons` com um array vazio. + +```javascript +win.setThumbarButtons([]); +``` + +## Unity Launcher Shortcuts (Linux) + +No Unity, você pode adicionar entradas customizadas para estes lançadores modificando +o arquivo `.desktop`, veja [Adding Shortcuts to a Launcher][unity-launcher]. + +__Launcher shortcuts do Audacious:__ + +![audacious](https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles?action=AttachFile&do=get&target=shortcuts.png) + +## Barra de Progresso na Barra de Tarefas (Windows & Unity) + +No Windows o botão na barra de tarefas pode ser usado para exibir uma barra de progresso. +Isto permite que a janela exiba informação sobre o progresso de algum processo sem +a necessidade do usuário mudar de janela. + +A Unity DE também tem uma funcionalidade parecida que permite especificar uma barra +de progresso no ícone do lançador. + +__Barra de Progresso no botão da barra de tarefas:__ + +![Barra de Progresso na Barra de Tarefas](https://cloud.githubusercontent.com/assets/639601/5081682/16691fda-6f0e-11e4-9676-49b6418f1264.png) + +__Barra de progresso no Unity launcher:__ + +![Unity Launcher](https://cloud.githubusercontent.com/assets/639601/5081747/4a0a589e-6f0f-11e4-803f-91594716a546.png) + +Para adicionar uma barra de progresso para uma janela, você pode ver a API: +[BrowserWindow.setProgressBar][setprogressbar]: + +```javascript +var window = new BrowserWindow({...}); +window.setProgressBar(0.5); +``` + +## Representação do arquivo na janela (OS X) + +No OS X, uma janela pode possuir a representação de um arquivo na barra de título, +permitindo que ao usuário acionar um Command-Click ou Control-Click sobre o título da janela, +uma pop-up de navegação entre arquivos é exibida. + +Você também pode inserir um estado de edição na janela para que o ícone do arquivo +possa indicar se o documento nesta janela foi modificado. + +__Menu popup da representação de arquivo:__ + + + +Para inserir o arquivo de representacão da janela, você pode usar as API +[BrowserWindow.setRepresentedFilename][setrepresentedfilename] e +[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/pt-BR/tutorial/online-offline-events.md b/docs-translations/pt-BR/tutorial/online-offline-events.md new file mode 100644 index 000000000000..8cdc1a6647e5 --- /dev/null +++ b/docs-translations/pt-BR/tutorial/online-offline-events.md @@ -0,0 +1,83 @@ +# Online/Offline Event Detection + +Os eventos de detecão Online e Offile podem ser implementados no processo +de renderização utilizando a API padrão do HTML, como é mostrado no exemplo +a seguir. + +_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 + + + + + + +``` + +Pode haver casos onde você também deseja responder a estes eventos no processo principal. +Mas o processo principal não consegue detectar esses eventos diretamente, pois não possui +um objeto `navigator`. Utilizando a ferramentas para comunicação entre processos, os eventos +podem ser direcionados para o processo principal e manipulados quando necessário. Você +pode ver isto no exemplo abaixo. + +_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/pt-BR/tutorial/quick-start.md b/docs-translations/pt-BR/tutorial/quick-start.md new file mode 100644 index 000000000000..eecf67878fc4 --- /dev/null +++ b/docs-translations/pt-BR/tutorial/quick-start.md @@ -0,0 +1,192 @@ +# Introdução + +Electron permite criar aplicações desktop com puro JavaScript através de +um runtime com APIs ricas e nativas. Você pode ver isso como uma variação do +runtime do io.js que é focado em aplicações desktop em vez de web servers. + +Isso não significa que o Electron é uma ligação em JavaScript para bibliotecas +de interface gráfica (GUI). Em vez disso, Electron usa páginas web como +interface gráfica, então você pode ver isso também como um navegador Chromium +mínimo, controlado por JavaScript. + +### Processo Principal + +No Electron, o processo que executa o script principal (main) do `package.json` +é chamado __processo principal__. O script que roda no processo principal pode +mostrar uma GUI criando páginas web. + +### Processo Renderizador + +Já que o Electron usa o Chromium para mostrar as páginas web, a arquitetura +multi-processo do Chromium também é usada. Cada página web no Electron roda em +seu próprio processo, o que é chamado de __processo renderizador__. + +Em navegadores comuns, as páginas web normalmente rodam em um ambiente em sandbox +e não têm permissão de acesso para recursos nativos. Usuários Electron, entretanto, +têm o poder de usar as APIs do io.js nas páginas web, permitindo interações de baixo +nível no sistema operacional. + +### Diferenças Entre o Processo Principal e o Processo Renderizador + +O processo principal cria as páginas web criando instâncias de `BrowserWindow`. +Cada instância de `BrowserWindow` roda a página web em seu próprio processo renderizador. +Quando uma instância de `BrowserWindow` é destruída, o processo renderizador +correspondente também é finalizado. + +O processo principal gerencia todas as páginas web de seus processos renderizadores +correspondentes. Cada processo renderizador é isolado e toma conta de sua +respectiva página web. + +Nas páginas web, chamar APIs nativas relacionadas à GUI não é permitido porque +gerenciar recursos de GUI em páginas web é muito perigoso e torna fácil o vazamento de +recursos. Se você quer realizar operações com GUI em páginas web, o processo +renderizador da página web deve se comunicar com o processo principal para requisitar +que o processo principal realize estas operações. + +No Electron, nós fornecemos o módulo [ipc](../../../docs/api/ipc-renderer.md) para +comunicação entre o processo principal e o processo renderizador. Que é também um +módulo [remoto](../../../docs/api/remote.md) para comunicação RPC. + +## Crie seu Primeiro App Electron + +Geralmente, um app Electron é estruturado assim: + +```text +seu-app/ +├── package.json +├── main.js +└── index.html +``` + +O formato de `package.json` é exatamente o mesmo que o dos módulos do Node, +e o script especificado pelo campo `main` é o script de inicialização do seu app, +que irá executar o processo principal. Um exemplo do seu `package.json` deve parecer +com isso: + +```json +{ + "name" : "seu-app", + "version" : "0.1.0", + "main" : "main.js" +} +``` + +__Nota__: Se o campo `main` não estiver presente no `package.json`, o Electron irá +tentar carregar um `index.js` + +O `main.js` deve criar as janelas e os manipuladores de eventos do sistema, um típico +exemplo: + +```javascript +var app = require('app'); // Módulo para controlar o ciclo de vida do app. +var BrowserWindow = require('browser-window'); // Módulo para criar uma janela nativa do browser. + +// Relate falhas para nossos servidores. +require('crash-reporter').start(); + +// Mantenha uma referência global para o objeto window, se você não o fizer, +// a janela será fechada automaticamente quando o objeto JavaScript for +// coletado pelo garbage collector. +var mainWindow = null; + +// Sair quando todas as janelas estiverem fechadas. +app.on('window-all-closed', function() { + // No OS X é comum para as aplicações na barra de menu + // continuarem ativas até que o usuário saia explicitamente + // com Cmd + Q + if (process.platform != 'darwin') { + app.quit(); + } +}); + +// Esse método irá ser chamado quando o Electron finalizar +// a inicialização e estiver pronto para criar janelas do browser. +app.on('ready', function() { + // Criar a janela do navegador. + mainWindow = new BrowserWindow({width: 800, height: 600}); + + // e carrega o index.html do app. + mainWindow.loadURL('file://' + __dirname + '/index.html'); + + // Abre os DevTools. + mainWindow.openDevTools(); + + // Emitido quando a janela é fechada. + mainWindow.on('closed', function() { + // Desfaz a referência para o objeto window, normalmente você deverá + // guardar as janelas em um array se seu app suportar várias janelas, + // essa é a hora que você deverá deletar o elemento correspondente. + mainWindow = null; + }); +}); +``` + +Finalmente o `index.html` é a página web que você quer mostrar: + +```html + + + + + Hello World! + + +

Hello World!

+ Nós estamos usando io.js + e Electron . + + +``` + +## Execute seu App + +Uma vez que você criou seus arquivos `main.js`, `index.html`, e `package.json` iniciais, +você provavelmente vai querer tentar executar seu app localmente para testa-lo e ter +certeza que funciona como você espera. + +### electron-prebuilt + +Se você instalou `electron-prebuilt` globalmente com `npm`, então você irá precisar apenas +rodar o seguinte comando no diretório fonte do seu app: + +```bash +electron . +``` + +Se você o instalou localmente, então execute: + +```bash +./node_modules/.bin/electron . +``` + +### Binário do Electron Baixado Manualmente + +Se você baixou o Electron manualmente, você pode também usar o binário incluído para +executar seu app diretamente. + +#### Windows + +```bash +$ .\electron\electron.exe seu-app\ +``` + +#### Linux + +```bash +$ ./electron/electron seu-app/ +``` + +#### OS X + +```bash +$ ./Electron.app/Contents/MacOS/Electron seu-app/ +``` + +`Electron.app` aqui é uma parte do pacote de lançamento do Electron, você pode baixa-lo +[aqui](https://github.com/atom/electron/releases). + +### Executar como uma distribuição + +Depois de terminar seu app, você pode criar uma distribuição seguindo o guia +[Distribuição de aplicações](./application-distribution.md) e então executar o app +empacotado. diff --git a/docs-translations/pt-BR/tutorial/using-native-node-modules.md b/docs-translations/pt-BR/tutorial/using-native-node-modules.md new file mode 100644 index 000000000000..c5e9eeddd61e --- /dev/null +++ b/docs-translations/pt-BR/tutorial/using-native-node-modules.md @@ -0,0 +1,68 @@ +# Usando Módulos Nativos do Node + +Os módulos nativos do Node são suportados pelo Electron, desde que o Electron +esteja usando uma versão diferente da oficial V8 do Node, você tem de +especificar manualmente a localização das headers do Electron quando compilar os +módulos nativos. + +## Compatibilidade de Módulos Nativos do Node + +Módulos nativos podem quebrar quando utilizar a nova versão do Node, V8. +Para ter certeza que o módulo que você está interessado em trabalhar com o +Electron, você deve checar se a versão do Node utilizada é compatível com a +usada pelo Electron. +Você pode verificar qual versão do Node foi utilizada no Electron olhando na +página [releases](https://github.com/atom/electron/releases) ou usando +`process.version` (veja [Quick Start](https://github.com/atom/electron/blob/master/docs/tutorial/quick-start.md) +por exemplo). + +Considere usar [NAN](https://github.com/nodejs/nan/) para seus próprios +módulos, caso isso facilite o suporte da múltiplas versões do Node. Isso é +também de grande ajuda para fazer a portabilidade dos módulos antigos para as +versões mais novas do Node, assim podendo trabalhar com o Electron. + +## Como Instalar os Módulos Nativos + +Existem três maneiras de instalar os módulos nativos: + +### O Modo Fácil + +O modo mais direto para recompilar os módulos é pelo pacote +[`electron-rebuild`](https://github.com/paulcbetts/electron-rebuild), +o que lida com passos manuais para baixar as headers e construir os módulos +nativos: + +```sh +npm install --save-dev electron-rebuild + +# Sempre que rodar npm install, execute também: +node ./node_modules/.bin/electron-rebuild +``` + +### Via npm + +Você pode usar também `npm` para instalar os módulos. Os passos são exatamente +os mesmos com os módulos Node, exceto que você precisa configurar algumas +variáveis da ambiente: + +```bash +export npm_config_disturl=https://atom.io/download/atom-shell +export npm_config_target=0.33.1 +export npm_config_arch=x64 +export npm_config_runtime=electron +HOME=~/.electron-gyp npm install module-name +``` + +### O modo node-gyp + +Para compilar os módulos do Node com as headers do Electron, você precisa dizer +ao `node-gyp` onde baixar as headers e qual versão 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 +``` + +A tag `HOME=~/.electron-gyp` altera onde encontrar as headers de desenvolvimento. +A tag `--target=0.29.1` é a versão do Electron. A tag `--dist-url=...` especifica +onde baixar as headers. A tag `--arch=x64` diz ao módulo que é feito em 64bit. diff --git a/docs-translations/pt-BR/tutorial/using-pepper-flash-plugin.md b/docs-translations/pt-BR/tutorial/using-pepper-flash-plugin.md new file mode 100644 index 000000000000..e5222ba0772d --- /dev/null +++ b/docs-translations/pt-BR/tutorial/using-pepper-flash-plugin.md @@ -0,0 +1,68 @@ +# Usando o Plugin Pepper Flash + +Electron atualmente suporta o plugin Pepper Flash. Para usá-lo no Electron, +você deve especificar manualmente a localização do plugin e então ele será +habilitado em sua aplicação. + +## Prepare uma cópia do plugin Flash + +Tanto no OS X como no Linux, os detalhes do plugin Pepper Flash podem ser +encontrados navegando por `chrome://plugins` no navegador Chrome. Essa +localização e versão são úteis para o suporte do plugin Electron's Pepper Flash. +Você pode também copiar para outra localização. + +## Adicionando a opçao Electron + +Você pode adicionar diretamente `--ppapi-flash-path` e `ppapi-flash-version` +para a linha de comando do Electron ou usando o método +`app.commandLine.appendSwitch` após o evento pronto. Também, adicione a opção +`plugins` em `browser-window`. +Por exemplo: + +```javascript +var app = require('app'); +var BrowserWindow = require('browser-window'); + +// Informa os erros ao ao servidor. +require('crash-reporter').start(); + +// Mantém uma referência global da janela, se não manter, a janela irá fechar +// automaticamente quando o objeto javascript for GCed. +var mainWindow = null; + +// Sai assim que todas as janelas forem fechadas. +app.on('window-all-closed', function() { + if (process.platform != 'darwin') { + app.quit(); + } +}); + +// Epecifica o caminho do flash. +// No Windows, deve ser /path/to/pepflashplayer.dll +// No OS X, /path/to/PepperFlashPlayer.plugin +// No Linux, /path/to/libpepflashplayer.so +app.commandLine.appendSwitch('ppapi-flash-path', '/path/to/libpepflashplayer.so'); + +// Especifica a versão do flash, por exemplo, 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'); + // Algo mais +}); +``` + +## Ative o plugin Flash na tag `` + +Adicione o atributo `plugins` na tag ``. + +```html + +``` diff --git a/docs-translations/ru-RU/README.md b/docs-translations/ru-RU/README.md new file mode 100644 index 000000000000..07e50028df94 --- /dev/null +++ b/docs-translations/ru-RU/README.md @@ -0,0 +1,82 @@ +Пожалуйста, убедитесь, что вы используете документацию, которые соответствует вашей версии Electron. +Номер версии должен быть частью адреса страницы. Если это не так, вы +возможно,используете документицию ветки разработки, которая может содержать изменения api, +которые не совместимы с вашей версией Electron. Если это так, +Вы можете переключиться на другую версию документации в списке +[доступные версии](http://electron.atom.io/docs/) на atom.io, или +если вы используете интерфейс GitHub, откройте список "переключение ветки/тега" и +выберите тег, который соответствует вашей версии. + +## Руководства + +* [Поддерживаемые платформы](tutorial/supported-platforms.md) +* [Application Distribution](tutorial/application-distribution.md) +* [Mac App Store Submission Guide](tutorial/mac-app-store-submission-guide.md) +* [Application Packaging](tutorial/application-packaging.md) +* [Using Native Node Modules](tutorial/using-native-node-modules.md) +* [Отладка главного процесса](tutorial/debugging-main-process.md) +* [Использование Selenium и WebDriver](tutorial/using-selenium-and-webdriver.md) +* [DevTools Extension](tutorial/devtools-extension.md) +* [Использование Pepper Flash Plugin](tutorial/using-pepper-flash-plugin.md) + +## Учебники + +* [Быстрый старт](tutorial/quick-start.md) +* [Desktop Environment Integration](tutorial/desktop-environment-integration.md) +* [Online/Offline Event Detection](tutorial/online-offline-events.md) + +## API References + +* [Краткий обзор](api/synopsis.md) +* [Process Object](api/process.md) +* [Поддерживаемые параметры командной строки Chrome](api/chrome-command-line-switches.md) + +### Пользовательские элементы DOM: + +* [`File` Object](api/file-object.md) +* [`` Tag](api/web-view-tag.md) +* [`window.open` Function](api/window-open.md) + +### Modules for the Main Process: + +* [app](api/app.md) +* [autoUpdater](api/auto-updater.md) +* [BrowserWindow](api/browser-window.md) +* [contentTracing](api/content-tracing.md) +* [dialog](api/dialog.md) +* [globalShortcut](api/global-shortcut.md) +* [ipcMain](api/ipc-main.md) +* [Menu](api/menu.md) +* [MenuItem](api/menu-item.md) +* [powerMonitor](api/power-monitor.md) +* [powerSaveBlocker](api/power-save-blocker.md) +* [protocol](api/protocol.md) +* [session](api/session.md) +* [webContents](api/web-contents.md) +* [Tray](api/tray.md) + +### Модули для Renderer Process (Web Page): + +* [ipcRenderer](api/ipc-renderer.md) +* [remote](api/remote.md) +* [webFrame](api/web-frame.md) + +### Modules for Both Processes: + +* [clipboard](api/clipboard.md) +* [crashReporter](api/crash-reporter.md) +* [nativeImage](api/native-image.md) +* [screen](api/screen.md) +* [shell](api/shell.md) + +## Разработка + +* [Стиль кодирования](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) +* [Обзор системы сборки](development/build-system-overview.md) +* [Инструкции по сборке (OS X)](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/th-TH/README.md b/docs-translations/th-TH/README.md new file mode 100644 index 000000000000..3b9f52077111 --- /dev/null +++ b/docs-translations/th-TH/README.md @@ -0,0 +1,73 @@ +## คู่มือ + +* [แพลตฟอร์มที่รองรับ](tutorial/supported-platforms.md) +* [การเผยแพร่แอปพลิเคชัน](tutorial/application-distribution.md) +* [แนวทางการส่งแอปเข้า Mac App Store](tutorial/mac-app-store-submission-guide.md) +* [การบรรจุแอปพลิเคชัน](tutorial/application-packaging.md) +* [การใช้โมดูลของ Node](tutorial/using-native-node-modules.md) +* [การหาข้อผิดพลาดในกระบวนการหลัก](tutorial/debugging-main-process.md) +* [การใช้งาน Selenium และ WebDriver](tutorial/using-selenium-and-webdriver.md) +* [ส่วนเสริมของ 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) +* [web-contents](api/web-contents.md) +* [tray](api/tray.md) + +### โมดูลสำหรับกระบวนการ Renderer (เว็บเพจ): + +* [ipc (renderer)](api/ipc-renderer.md) +* [remote](api/remote.md) +* [web-frame](api/web-frame.md) + +### Modules for Both Processes: + +* [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) +* [ขั้นตอนการสร้าง (OS X)](development/build-instructions-osx.md) +* [ขั้นตอนการสร้าง (Windows)](development/build-instructions-windows.md) +* [ขั้นตอนการสร้าง (Linux)](development/build-instructions-linux.md) +* [Setting Up Symbol Server in debugger](development/setting-up-symbol-server.md) +* [การติดตั้งเซิร์ฟเวอร์ Symbol Server ใน debugger](development/setting-up-symbol-server.md) diff --git a/docs-translations/zh-CN/README.md b/docs-translations/zh-CN/README.md index 124d030c46ee..5c4303a0a6f8 100644 --- a/docs-translations/zh-CN/README.md +++ b/docs-translations/zh-CN/README.md @@ -1,5 +1,6 @@ ## 向导 +* [支持平台](tutorial/supported-platforms.md) * [应用部署](tutorial/application-distribution.md) * [应用打包](tutorial/application-packaging.md) * [使用原生模块](tutorial/using-native-node-modules.md) @@ -61,7 +62,7 @@ ## 开发 * [编码规范](development/coding-style.md) -* [源码文件结构](development/source-code-directory-structure.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) diff --git a/docs-translations/zh-CN/api/app.md b/docs-translations/zh-CN/api/app.md new file mode 100644 index 000000000000..03eb083dfa6e --- /dev/null +++ b/docs-translations/zh-CN/api/app.md @@ -0,0 +1,315 @@ +# app + +`app` 模块是为了控制整个应用的生命周期设计的。 + +下面的这个例子将会展示如何在最后一个窗口被关闭时退出应用: + +```javascript +var app = require('app'); +app.on('window-all-closed', function() { + app.quit(); +}); +``` + +## 事件 + +`app` 对象会触发以下的事件: + +### 事件: 'will-finish-launching' + +当应用程序完成基础的启动的时候被触发。在 Windows 和 Linux 中, +`will-finish-launching` 事件与 `ready` 事件是相同的; 在 OS X 中, +这个时间相当于 `NSApplication` 中的 `applicationWillFinishLaunching` 提示。 +你应该经常在这里为 `open-file` 和 `open-url` 设置监听器,并启动崩溃报告和自动更新。 + +在大多数的情况下,你应该只在 `ready` 事件处理器中完成所有的业务。 + +### 事件: 'ready' + +当 Electron 完成初始化时被触发。 + +### 事件: 'window-all-closed' + +当所有的窗口都被关闭时触发。 + +这个时间仅在应用还没有退出时才能触发。 如果用户按下了 `Cmd + Q`, +或者开发者调用了 `app.quit()` ,Electron 将会先尝试关闭所有的窗口再触发 `will-quit` 事件, +在这种情况下 `window-all-closed` 不会被触发。 + +### 事件: 'before-quit' + +返回: + +* `event` 事件 + +在应用程序开始关闭它的窗口的时候被触发。 +调用 `event.preventDefault()` 将会阻止终止应用程序的默认行为。 + +### 事件: 'will-quit' + +返回: + +* `event` 事件 + +当所有的窗口已经被关闭,应用即将退出时被触发。 +调用 `event.preventDefault()` 将会阻止终止应用程序的默认行为。 + +你可以在 `window-all-closed` 事件的描述中看到 `will-quit` 事件 +和 `window-all-closed` 事件的区别。 + +### 事件: 'quit' + +当应用程序正在退出时触发。 + +### 事件: 'open-file' + +返回: + +* `event` 事件 +* `path` 字符串 + +当用户想要在应用中打开一个文件时触发。`open-file` 事件常常在应用已经打开并且系统想要再次使用应用打开文件时被触发。 + `open-file` 也会在一个文件被拖入 dock 且应用还没有运行的时候被触发。 +请确认在应用启动的时候(甚至在 `ready` 事件被触发前)就对 `open-file` 事件进行监听,以处理这种情况。 + +如果你想处理这个事件,你应该调用 `event.preventDefault()` 。 + +### 事件: 'open-url' + +返回: + +* `event` 事件 +* `url` 字符串 + +当用户想要在应用中打开一个url的时候被触发。URL格式必须要提前标识才能被你的应用打开。 + +如果你想处理这个事件,你应该调用 `event.preventDefault()` 。 + +### 事件: 'activate' _OS X_ + +返回: + +* `event` 事件 +* `hasVisibleWindows` 布尔值 + +当应用被激活时触发,常用于点击应用的 dock 图标的时候。 + +### 事件: 'browser-window-blur' + +返回: + +* `event` 事件 +* `window` 浏览器窗口 + +当一个 [浏览器窗口](browser-window.md) 失去焦点的时候触发。 + +### 事件: 'browser-window-focus' + +返回: + +* `event` 事件 +* `window` 浏览器窗口 + +当一个 [浏览器窗口](browser-window.md) 获得焦点的时候触发。 + +### 事件: 'browser-window-created' + +返回: + +* `event` 事件 +* `window` 浏览器窗口 + +当一个 [浏览器窗口](browser-window.md) 被创建的时候触发。 + +### 事件: 'select-certificate' + +当一个客户端认证被请求的时候被触发。 + +返回: + +* `event` 事件 +* `webContents` [web组件](browser-window.md#class-webcontents) +* `url` 字符串 +* `certificateList` 对象 + * `data` PEM 编码数据 + * `issuerName` 发行者的公有名称 +* `callback` 函数 + +```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. + +### 事件: 'gpu-process-crashed' + +当GPU进程崩溃时触发。 + +## 方法 + +`app` 对象拥有以下的方法: + +**提示:** 有的方法只能用于特定的操作系统。 + +### `app.quit()` + +试图关掉所有的窗口。`before-quit` 事件将会被最先触发。如果所有的窗口都被成功关闭了, +`will-quit` 事件将会被触发,默认下应用将会被关闭。 + +这个方法保证了所有的 `beforeunload` 和 `unload` 事件处理器被正确执行。会存在一个窗口被 `beforeunload` 事件处理器返回 `false` 取消退出的可能性。 + +### `app.getAppPath()` + +返回当前应用所在的文件路径。 + +### `app.getPath(name)` + +* `name` 字符串 + +返回一个与 `name` 参数相关的特殊文件夹或文件路径。当失败时抛出一个 `Error` 。 + +你可以通过名称请求以下的路径: + +* `home` 用户的 home 文件夹。 +* `appData` 所有用户的应用数据文件夹,默认对应: + * `%APPDATA%` Windows 中 + * `$XDG_CONFIG_HOME` or `~/.config` Linux 中 + * `~/Library/Application Support` OS X 中 +* `userData` 储存你应用程序设置文件的文件夹,默认是 `appData` 文件夹附加应用的名称。 +* `cache` 所有用户应用程序缓存的文件夹,默认对应: + * `%APPDATA%` Windows 中 (没有一个通用的缓存位置) + * `$XDG_CACHE_HOME` 或 `~/.cache` Linux 中 + * `~/Library/Caches` OS X 中 +* `userCache` 用于存放应用程序缓存的文件夹,默认是 `cache` 文件夹附加应用的名称。 +* `temp` 临时文件夹。 +* `userDesktop` 当前用户的桌面文件夹。 +* `exe` 当前的可执行文件。 +* `module` `libchromiumcontent` 库。 + +### `app.setPath(name, path)` + +* `name` 字符串 +* `path` 字符串 + +重写 `path` 参数到一个特别的文件夹或者是一个和 `name` 参数有关系的文件。 +如果这个路径指向的文件夹不存在,这个文件夹将会被这个方法创建。 +如果错误则抛出 `Error` 。 + +你只可以指向 `app.getPath` 中定义过 `name` 的路径。You can only override paths of a `name` defined in `app.getPath`. + +默认情况下,网页的 cookie 和缓存都会储存在 `userData` 文件夹。 +如果你想要改变这个位置,你需要在 `app` 模块中的 `ready` 事件被触发之前重写 `userData` 的路径。 + +### `app.getVersion()` + +返回加载应用程序的版本。如果应用程序的 `package.json` 文件中没有写版本号, +将会返回当前包或者可执行文件的版本。 + +### `app.getName()` + +返回当前应用程序的 `package.json` 文件中的名称。 + +通常 `name` 字段是一个短的小写字符串,其命名规则按照 npm 中的模块命名规则。你应该单独列举一个 +`productName` 字段,用于表示你的应用程序的完整名称,这个名称将会被 Electron 优先采用。 + +### `app.getLocale()` + +返回当前应用程序的位置。 + +### `app.resolveProxy(url, callback)` + +* `url` URL +* `callback` 函数 + +为 `url` 解析代理信息。 `callback` 在请求被执行之后将会被 `callback(proxy)` 调用。 + +### `app.addRecentDocument(path)` + +* `path` 字符串 + +为最近访问的文档列表中添加 `path` 。 + +这个列表由操作系统进行管理。在 Windows 中您可以通过任务条进行访问,在 OS X 中你可以通过dock 菜单进行访问。 + +### `app.clearRecentDocuments()` + +清除最近访问的文档列表。 + +### `app.setUserTasks(tasks)` _Windows_ + +* `tasks` 由 `Task` 对象构成的数组 + +将 `tasks` 添加到 Windows 中 JumpList 功能的 [Tasks][tasks] 分类中。 + +`tasks` 中的 `Task` 对象格式如下: + +`Task` 对象 +* `program` 字符串 - 执行程序的路径,通常你应该说明当前程序的路径为 `process.execPath` 字段。 +* `arguments` 字符串 - 当 `program` 执行时的命令行参数。 +* `title` 字符串 - JumpList 中显示的标题。 +* `description` 字符串 - 对这个任务的描述。 +* `iconPath` 字符串 - JumpList 中显示的 icon 的绝对路径,可以是一个任意包含一个icon的资源文件。你通常可以通过指明 `process.execPath` 来显示程序中的icon。 +* `iconIndex` 整数 - icon文件中的icon目录。如果一个icon文件包括了两个或多个icon,就需要设置这个值以确定icon。如果一个文件仅包含一个icon,那么这个值为0。 + +### `app.commandLine.appendSwitch(switch[, value])` + +通过可选的参数 `value` 给 Chromium 命令行中添加一个开关。 +Append a switch (with optional `value`) to Chromium's command line. + +**贴士:** 这不会影响 `process.argv` ,这个方法主要被开发者用于控制一些低层级的 Chromium 行为。 + +### `app.commandLine.appendArgument(value)` + +给 Chromium 命令行中加入一个参数。这个参数是当前正在被引用的。 + +**贴士:** 这不会影响 `process.argv`。 + +### `app.dock.bounce([type])` _OS X_ + +* `type` 字符串 (可选的) - 可以是 `critical` 或 `informational`。默认下是 `informational` + +当输入 `critical` 时,dock 中的 icon 将会开始弹跳直到应用被激活或者这个请求被取消。 + +当输入 `informational` 时,dock 中的 icon 只会弹跳一秒钟。 +然而,这个请求仍然会激活,直到应用被激活或者请求被取消。 + +返回一个表示这个请求的 ID。 + +### `app.dock.cancelBounce(id)` _OS X_ + +* `id` 整数 + +取消这个 `id` 对应的请求。 + +### `app.dock.setBadge(text)` _OS X_ + +* `text` 字符串 + +设置 dock 中显示的字符。 + +### `app.dock.getBadge()` _OS X_ + +返回 dock 中显示的字符。 + +### `app.dock.hide()` _OS X_ + +隐藏 dock 中的 icon。 + +### `app.dock.show()` _OS X_ + +显示 dock 中的 icon。 + +### `app.dock.setMenu(menu)` _OS X_ + +* `menu` 菜单 + +设置应用的 [dock 菜单][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 \ No newline at end of file diff --git a/docs-translations/zh-CN/api/ipc-main-process.md b/docs-translations/zh-CN/api/ipc-main-process.md new file mode 100644 index 000000000000..75d5785b2e5f --- /dev/null +++ b/docs-translations/zh-CN/api/ipc-main-process.md @@ -0,0 +1,68 @@ +# ipc (主进程) + +在主进程使用`ipc`模块时,`ipc`负责捕获从渲染进程(网页)发送的同步或者是异步消息. + +## 发送消息 + +主进程也可以向渲染进程发送信息,具体可以看[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); // 打印 "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` - 事件名称. +* `callback` - 回调函数. + +当事件发生的时候,会传入`callback` `event`和`arg`参数. + +## IPC 事件 + +传入`callback`的`event`对象含有下列方法. + +### `Event.returnValue` + +在同步消息中,设置这个值将会被返回. + +### `Event.sender` + +返回一个可以发送消息的`WebContents`. + +### `Event.sender.send(channel[.arg1][,arg2][,...])` + +* `channel` - 事件名称. +* `arg` (选用) + +这个可以发送一个可带参数的异步消息回渲染进程. 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 index cf6859d50645..beea91e3ff16 100644 --- a/docs-translations/zh-CN/development/atom-shell-vs-node-webkit.md +++ b/docs-translations/zh-CN/development/atom-shell-vs-node-webkit.md @@ -12,7 +12,7 @@ __1. 应用的入口__ 在 Electron 中,入口是一个 JavaScript 脚本。不同于直接提供一个URL,你需要手动创建一个浏览器窗口,然后通过 API 加载 HTML 文件。你还可以监听窗口事件,决定何时让应用退出。 -Electron 的工作方式更像 Node.js 运行时。 Electron 的 APIs 更加底层,因此你可以它替代 [PhantomJS](http://phantomjs.org/) 做浏览器测试。 +Electron 的工作方式更像 Node.js 运行时。 Electron 的 APIs 更加底层,因此你可以用它替代 [PhantomJS](http://phantomjs.org/) 做浏览器测试。 __2. 构建系统__ @@ -20,10 +20,12 @@ __2. 构建系统__ __3. Node 集成__ -在 NW.js,网页中的 Node 集成需要通过给 Chromium 打补丁来实现。但在 Electron 中,我们选择了另一种方式:通过各个平台的消息循环与 libuv 的循环集成,避免了直接在 Chromium 上做改动。你可以看 [`node_bindings`](../../atom/common/) 来了解这是如何完成的。 +在 NW.js,网页中的 Node 集成需要通过给 Chromium 打补丁来实现。但在 Electron 中,我们选择了另一种方式:通过各个平台的消息循环与 libuv 的循环集成,避免了直接在 Chromium 上做改动。你可以看 [`node_bindings`][node-bindings] 来了解这是如何完成的。 __4. 多上下文__ 如果你是有经验的 NW.js 用户,你应该会熟悉 Node 上下文和 web 上下文的概念。这些概念的产生源于 NW.js 的实现方式。 通过使用 Node 的[多上下文](http://strongloop.com/strongblog/whats-new-node-js-v0-12-multiple-context-execution/)特性,Electron不需要在网页中引入新的 JavaScript 上下文。 + +[node-bindings]: https://github.com/atom/electron/tree/master/atom/common diff --git a/docs-translations/zh-CN/development/source-code-directory-structure.md b/docs-translations/zh-CN/development/source-code-directory-structure.md new file mode 100644 index 000000000000..fd3676c2e988 --- /dev/null +++ b/docs-translations/zh-CN/development/source-code-directory-structure.md @@ -0,0 +1,53 @@ +# 源码目录结构 + +Electron 的源代码主要依据 Chromium 的拆分约定被拆成了许多部分。 + +为了更好地理解源代码,您可能需要了解一下 +[Chromium 的多进程架构](http://dev.chromium.org/developers/design-documents/multi-process-architecture)。 + +## 源代码的结构 + +``` +Electron +├──atom - Electron 的源代码 +| ├── app - 系统入口代码 +| ├── browser - 包含了主窗口、UI 和其他所有与主进程有关的东西,它会告诉渲染进程如何管理页面 +| |   ├── lib - 主进程初始化代码中 JavaScript 部分的代码 +| | ├── ui - 不同平台上 UI 部分的实现 +| | | ├── cocoa - Cocoa 部分的源代码 +| | | ├── gtk - GTK+ 部分的源代码 +| | | └── win - Windows GUI 部分的源代码 +| | ├── default_app - 在没有指定 app 的情况下 Electron 启动时默认显示的页面 +| | ├── api - 主进程 API 的实现 +| | | └── lib - API 实现中 Javascript 部分的代码 +| | ├── net - 网络相关的代码 +| | ├── mac - 与 Mac 有关的 Objective-C 代码 +| | └── resources - 图标,平台相关的文件等 +| ├── renderer - 运行在渲染进程中的代码 +| | ├── lib - 渲染进程初始化代码中 JavaScript 部分的代码 +| | └── api - 渲染进程 API 的实现 +| | └── lib - API 实现中 Javascript 部分的代码 +| └── common - 同时被主进程和渲染进程用到的代码,包括了一些用来将 node 的事件循环 +| | 整合到 Chromium 的事件循环中时用到的工具函数和代码 +| ├── lib - 同时被主进程和渲染进程使用到的 Javascript 初始化代码 +| └── api - 同时被主进程和渲染进程使用到的 API 的实现以及 Electron 内置模块的基础设施 +| └── lib - API 实现中 Javascript 部分的代码 +├── chromium_src - 从 Chromium 项目中拷贝来的代码 +├── docs - 英语版本的文档 +├── docs-translations - 各种语言版本的文档翻译 +├── spec - 自动化测试 +├── atom.gyp - Electron 的构建规则 +└── common.gypi - 为诸如 `node` 和 `breakpad` 等其他组件准备的编译设置和构建规则 +``` + +## 其他目录的结构 + +* **script** - 用于诸如构建、打包、测试等开发用途的脚本 +* **tools** - 在 gyp 文件中用到的工具脚本,但与 `script` 目录不同, + 该目录中的脚本不应该被用户直接调用 +* **vendor** - 第三方依赖项的源代码,为了防止人们将它与 Chromium 源码中的同名目录相混淆, + 在这里我们不使用 `third_party` 作为目录名 +* **node_modules** - 在构建中用到的第三方 node 模块 +* **out** - `ninja` 的临时输出目录 +* **dist** - 由脚本 `script/create-dist.py` 创建的临时发布目录 +* **external_binaries** - 下载的不支持通过 `gyp` 构建的预编译第三方框架 diff --git a/docs-translations/zh-CN/tutorial/application-distribution.md b/docs-translations/zh-CN/tutorial/application-distribution.md new file mode 100644 index 000000000000..c1fddce15ea4 --- /dev/null +++ b/docs-translations/zh-CN/tutorial/application-distribution.md @@ -0,0 +1,109 @@ +# 应用部署 + +为了使用Electron部署你的应用程序,你存放应用程序的文件夹需要叫做 `app` 并且需要放在 Electron 的资源文件夹下(在 OS X 中是指 `Electron.app/Contents/Resources/`,在 Linux 和 Windows 中是指 `resources/`) +就像这样: + +在 OS X 中: + +```text +electron/Electron.app/Contents/Resources/app/ +├── package.json +├── main.js +└── index.html +``` + +在 Windows 和 Linux 中: + +```text +electron/resources/app +├── package.json +├── main.js +└── index.html +``` + +然后运行 `Electron.app` (或者 Linux 中的 `electron`,Windows 中的 `electron.exe`), +接着 Electron 就会以你的应用程序的方式启动。`electron` 文件夹将被部署并可以分发给最终的使用者。 + +## 将你的应用程序打包成一个文件 + +除了通过拷贝所有的资源文件来分发你的应用程序之外,你可以可以通过打包你的应用程序为一个 [asar](https://github.com/atom/asar) 库文件以避免暴露你的源代码。 + +为了使用一个 `asar` 库文件代替 `app` 文件夹,你需要修改这个库文件的名字为 `app.asar` , +然后将其放到 Electron 的资源文件夹下,然后 Electron 就会试图读取这个库文件并从中启动。 +如下所示: + +在 OS X 中: + +```text +electron/Electron.app/Contents/Resources/ +└── app.asar +``` + +在 Windows 和 Linux 中: + +```text +electron/resources/ +└── app.asar +``` + +更多的细节请见 [Application packaging](application-packaging.md). + +## 更换名称与下载二进制文件 + +在使用 Electron 打包你的应用程序之后,你可能需要在分发给用户之前修改打包的名字。 + +### Windows + +你可以将 `electron.exe` 改成任意你喜欢的名字,然后可以使用像 +[rcedit](https://github.com/atom/rcedit) 或者[ResEdit](http://www.resedit.net) +编辑它的icon和其他信息。 + +### OS X + +你可以将 `Electron.app` 改成任意你喜欢的名字,然后你也需要修改这些文件中的 +`CFBundleDisplayName`, `CFBundleIdentifier` 以及 `CFBundleName` 字段。 +这些文件如下: + +* `Electron.app/Contents/Info.plist` +* `Electron.app/Contents/Frameworks/Electron Helper.app/Contents/Info.plist` + +你也可以重命名帮助应用程序以避免在应用程序监视器中显示 `Electron Helper`, +但是请确保你已经修改了帮助应用的可执行文件的名字。 + +一个改过名字的应用程序的构造可能是这样的: + +``` +MyApp.app/Contents +├── Info.plist +├── MacOS/ +│   └── MyApp +└── Frameworks/ + ├── MyApp Helper EH.app + | ├── Info.plist + | └── MacOS/ + |    └── MyApp Helper EH + ├── MyApp Helper NP.app + | ├── Info.plist + | └── MacOS/ + |    └── MyApp Helper NP + └── MyApp Helper.app + ├── Info.plist + └── MacOS/ +    └── MyApp Helper +``` + +### Linux + +你可以将 `electron` 改成任意你喜欢的名字。 + +## 通过重编译源代码来更换名称 + +通过修改产品名称并重编译源代码来更换 Electron 的名称也是可行的。 +你需要修改 `atom.gyp` 文件并彻底重编译一次。 + +### grunt打包脚本 + +手动的检查 Electron 代码并重编译是很复杂晦涩的,因此有一个 Grunt任务可以自动自动的处理 +这些内容 [grunt-build-atom-shell](https://github.com/paulcbetts/grunt-build-atom-shell). + +这个任务会自动的处理编辑 `.gyp` 文件,从源代码进行编译,然后重编译你的应用程序的本地 Node 模块以匹配这个新的可执行文件的名称。 diff --git a/docs-translations/zh-CN/tutorial/application-packaging.md b/docs-translations/zh-CN/tutorial/application-packaging.md new file mode 100644 index 000000000000..ee4b7bf63b85 --- /dev/null +++ b/docs-translations/zh-CN/tutorial/application-packaging.md @@ -0,0 +1,141 @@ +# 应用打包 + +为舒缓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中有两类APIs:Node.js提供的Node APIs和Chromium提供的Web APIs。这两种APIs都支持从`asar`包中读取文件。 + +### Node API + +由于Electron中打了特别补丁, Node APIs中如`fs.readFile`或者`require`之类的方法可以将`asar`视之为虚拟文件夹,读取`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 +const fs = require('fs'); +fs.readFileSync('/path/to/example.asar/file.txt'); +``` + +列出`asar`包中根目录下的所有文件: + +```javascript +const fs = require('fs'); +fs.readdirSync('/path/to/example.asar'); +``` + +使用`asar`包中的一个模块: + +```javascript +require('/path/to/example.asar/dir/module.js'); +``` + +你也可以使用`BrowserWindow`来显示一个`asar`包里的web页面: + +```javascript +const BrowserWindow = require('electron').BrowserWindow; +var win = new BrowserWindow({width: 800, height: 600}); +win.loadURL('file:///path/to/example.asar/static/index.html'); +``` + +### Web API + +在Web页面里,用`file:`协议可以获取`asar`包中文件。和Node API一样,视`asar`包如虚拟文件夹。 + +例如,用`$.get`获取文件: + +```html + +``` + +### 像“文件”那样处理`asar`包 + +有些场景,如:核查`asar`包的校验和,我们需要像读取“文件”那样读取`asar`包的内容(而不是当成虚拟文件夹)。你可以使用内置的`original-fs`(提供和`fs`一样的APIs)模块来读取`asar`包的真实信息。 + +```javascript +var originalFs = require('original-fs'); +originalFs.readFileSync('/path/to/example.asar'); +``` + +## Node API缺陷 + +尽管我们已经尽了最大努力使得`asar`包在Node API下的应用尽可能的趋向于真实的目录结构,但仍有一些底层Node API我们无法保证其正常工作。 + +### `asar`包是只读的 + +`asar`包中的内容不可更改,所以Node APIs里那些可以用来修改文件的方法在对待`asar`包时都无法正常工作。 + +### Working Directory在`asar`包中无效 + +尽管`asar`包是虚拟文件夹,但其实并没有真实的目录架构对应在文件系统里,所以你不可能将working Directory设置成`asar`包里的一个文件夹。将`asar`中的文件夹以`cwd`形式作为参数传入一些API中也会报错。 + +### API中的额外“开箱” + +大部分`fs`API可以无需解压即从`asar`包中读取文件或者文件的信息,但是在处理一些依赖真实文件路径的底层系统方法时,Electron会将所需文件解压到临时目录下,然后将临时目录下的真实文件路径传给底层系统方法使其正常工作。 对于这类API,耗费会略多一些。 + +以下是一些需要额外解压的APIs: + +* `child_process.execFile` +* `child_process.execFileSync` +* `fs.open` +* `fs.openSync` +* `process.dlopen` - `require`native模块时用到 + +### `fs.stat`获取的stat信息不可靠 + +对`asar`包中的文件取`fs.stat`,返回的`Stats`对象不是精确值,因为这些文件不是真实存在于文件系统里。所以除了文件大小和文件类型以外,你不应该依赖`Stats`对象的值。 + +### 执行`asar`包中的程序 + +Node中有一些可以执行程序的API,如`child_process.exec`,`child_process.spawn`和`child_process.execFile`等,但只有`execFile`可以执行`asar`包中的程序。 + +因为`exec`和`spawn`允许`command`替代`file`作为输入,而`command`是需要在shell下执行的,目前没有可靠的方法来判断`command`中是否在操作一个`asar`包中的文件,而且即便可以判断,我们依旧无法保证可以在无任何副作用的情况下替换`command`中的文件路径。 + +## 打包时排除文件 + +如上所述,一些Node API会在调用时将文件解压到文件系统中,除了效率问题外,也有可能引起杀毒软件的注意! + +为解决这个问题,你可以在生成`asar`包时使用`--unpack`选项来排除一些文件,使其不打包到`asar`包中,下面是如何排除一些用作共享用途的native模块的方法: + +```bash +$ asar pack app app.asar --unpack *.node +``` + +经过上述命令后,除了生成的`app.asar`包以外,还有一个包含了排除文件的`app.asar.unpacked`文件夹,你需要将这个文件夹一起拷贝,提供给用户。 + +[asar]: https://github.com/atom/asar diff --git a/docs-translations/zh-CN/tutorial/debugging-main-process.md b/docs-translations/zh-CN/tutorial/debugging-main-process.md new file mode 100644 index 000000000000..48f3579394e7 --- /dev/null +++ b/docs-translations/zh-CN/tutorial/debugging-main-process.md @@ -0,0 +1,48 @@ +# 主进程调试 + +浏览器窗口的开发工具仅能调试渲染器的进程脚本(比如web 页面)。为了提供一个可以调试主进程 +的方法,Electron 提供了 `--debug` 和 `--debug-brk` 开关。 + +## 命令行开关 + +使用如下的命令行开关来调试 Electron 的主进程: + +### `--debug=[port]` + +当这个开关用于 Electron 时,它将会监听 V8 引擎中有关 `port` 的调试器协议信息。 +默认的 `port` 是 `5858`。 + +### `--debug-brk=[port]` + +就像 `--debug` 一样,但是会在第一行暂停脚本运行。 + +## 使用 node-inspector 来调试 + +__备注:__ Electron 使用 node v0.11.13 版本,目前对 node-inspector支持的不是特别好, +如果你通过 node-inspector 的 console 来检查 `process` 对象,主进程就会崩溃。 + +### 1. 开始 [node-inspector][node-inspector] 服务 + +```bash +$ node-inspector +``` + +### 2. 打开 Electron 的调试模式 + +你也可以用调试参数来运行 Electron : + +```bash +$ electron --debug=5858 your/app +``` + +或者,在第一行暂停你的脚本: + +```bash +$ electron --debug-brk=5858 your/app +``` + +### 3. 加载调试器界面 + +在 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/zh-CN/tutorial/online-offline-events.md b/docs-translations/zh-CN/tutorial/online-offline-events.md index de292490f0be..764b81aa5d2f 100644 --- a/docs-translations/zh-CN/tutorial/online-offline-events.md +++ b/docs-translations/zh-CN/tutorial/online-offline-events.md @@ -9,7 +9,7 @@ var onlineStatusWindow; app.on('ready', function() { onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); + onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); }); ```` @@ -43,7 +43,7 @@ var onlineStatusWindow; app.on('ready', function() { onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); + onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); }); ipc.on('online-status-changed', function(event, status) { @@ -69,4 +69,4 @@ ipc.on('online-status-changed', function(event, status) { -```` \ No newline at end of file +```` diff --git a/docs-translations/zh-CN/tutorial/quick-start.md b/docs-translations/zh-CN/tutorial/quick-start.md index 165c30142e72..906db8f4458b 100644 --- a/docs-translations/zh-CN/tutorial/quick-start.md +++ b/docs-translations/zh-CN/tutorial/quick-start.md @@ -68,7 +68,7 @@ app.on('ready', function() { mainWindow = new BrowserWindow({width: 800, height: 600}); // 加载应用的 index.html - mainWindow.loadUrl('file://' + __dirname + '/index.html'); + mainWindow.loadURL('file://' + __dirname + '/index.html'); // 打开开发工具 mainWindow.openDevTools(); diff --git a/docs-translations/zh-CN/tutorial/supported-platforms.md b/docs-translations/zh-CN/tutorial/supported-platforms.md new file mode 100644 index 000000000000..a819e3a0817c --- /dev/null +++ b/docs-translations/zh-CN/tutorial/supported-platforms.md @@ -0,0 +1,27 @@ +# 支持的平台 + +以下的平台是 Electron 目前支持的: + +### OS X + +对于 OS X 系统仅有64位的二进制文档,支持的最低版本是 OS X 10.8。 + +### Windows + +仅支持 Windows 7 及其以后的版本,之前的版本中是不能工作的。 + +对于 Windows 提供 `x86` 和 `amd64` (x64) 版本的二进制文件。需要注意的是 +`ARM` 版本的 Windows 目前尚不支持. + +### Linux + +预编译的 `ia32`(`i686`) 和 `x64`(`amd64`) 版本 Electron 二进制文件都是在 +Ubuntu 12.04 下编译的,`arm` 版的二进制文件是在 ARM v7(硬浮点 ABI 与 +Debian Wheezy 版本的 NEON)下完成的。 + +预编译二进制文件是否能够运行,取决于其中是否包括了编译平台链接的库,所以只有 Ubuntu 12.04 +可以保证正常工作,但是以下的平台也被正事可以运行 Electron的预编译版本: + +* Ubuntu 12.04 及更新 +* Fedora 21 +* Debian 8 diff --git a/docs-translations/zh-CN/tutorial/using-native-node-modules.md b/docs-translations/zh-CN/tutorial/using-native-node-modules.md new file mode 100644 index 000000000000..e03e7b8948a1 --- /dev/null +++ b/docs-translations/zh-CN/tutorial/using-native-node-modules.md @@ -0,0 +1,53 @@ +# 使用原生模块 + +Electron同样也支持原生模块,但由于和官方的Node相比使用了不同的V8引擎,如果你想编译原生模块,则需要手动设置Electron的headers的位置。 + +## 原生Node模块的兼容性 + +当Node开始换新的V8引擎版本时,原生模块可能“坏”掉。为确保一切工作正常,你需要检查你想要使用的原生模块是否被Electron内置的Node支持。你可以在[这里](https://github.com/atom/electron/releases)查看Electron内置的Node版本,或者使用`process.version`(参考:[快速入门](https://github.com/atom/electron/blob/master/docs/tutorial/quick-start.md))查看。 + +考虑到[NAN](https://github.com/nodejs/nan/)可以使你的开发更容易对多版本Node的支持,建议使用它来开发你自己的模块。你也可以使用[NAN](https://github.com/nodejs/nan/)来移植旧的模块到新的Node版本,以使它们可以在新的Electron下良好工作。 + +## 如何安装原生模块 + +如下三种方法教你安装原生模块: + +### 最简单方式 + +最简单的方式就是通过[`electron-rebuild`](https://github.com/paulcbetts/electron-rebuild)包重新编译原生模块,它帮你自动完成了下载headers、编译原生模块等步骤: + +```sh +npm install --save-dev electron-rebuild + +# 每次运行"npm install"时,也运行这条命令 +./node_modules/.bin/electron-rebuild + +# 在windows下如果上述命令遇到了问题,尝试这个: +.\node_modules\.bin\electron-rebuild.cmd +``` + +### 通过npm安装 + +你当然也可以通过`npm`安装原生模块。大部分步骤和安装普通模块时一样,除了以下一些系统环境变量你需要自己操作: + +```bash +export npm_config_disturl=https://atom.io/download/atom-shell +export npm_config_target=0.33.1 +export npm_config_arch=x64 +export npm_config_runtime=electron +HOME=~/.electron-gyp npm install module-name +``` + +### 通过node-gyp安装 + +你需要告诉`node-gyp`去哪下载Electron的headers,以及下载什么版本: + +```bash +$ cd /path-to-module/ +$ HOME=~/.electron-gyp node-gyp rebuild --target=0.29.1 --arch=x64 --dist-url=https://atom.io/download/atom-shell +``` + +`HOME=~/.electron-gyp`设置了去哪找开发时的headers。 +`--target=0.29.1`设置了Electron的版本 +`--dist-url=...`设置了Electron的headers的下载地址 +`--arch=x64`设置了该模块为适配64bit操作系统而编译 diff --git a/docs-translations/zh-TW/README.md b/docs-translations/zh-TW/README.md index cc75ba51c144..1e69c9744387 100644 --- a/docs-translations/zh-TW/README.md +++ b/docs-translations/zh-TW/README.md @@ -1,7 +1,9 @@ ## 導引 +* [支援平台](tutorial/supported-platforms.md) * [應用程式發布](tutorial/application-distribution.md) * [應用程式打包](tutorial/application-packaging.md) +* [Mac App Store 上架指引](tutorial/mac-app-store-submission-guide.md) * [使用原生 node 模組](tutorial/using-native-node-modules.md) * [主行程 Debug](tutorial/debugging-main-process.md) * [使用 Selenium 和 WebDriver](tutorial/using-selenium-and-webdriver.md) @@ -64,7 +66,7 @@ * [源碼目錄結構](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) +* [構建步驟 (OS X)](development/build-instructions-osx.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/synopsis.md b/docs-translations/zh-TW/api/synopsis.md index 484f5e353847..e28d1fd8cb0d 100644 --- a/docs-translations/zh-TW/api/synopsis.md +++ b/docs-translations/zh-TW/api/synopsis.md @@ -20,7 +20,7 @@ var window = null; app.on('ready', function() { window = new BrowserWindow({width: 800, height: 600}); - window.loadUrl('https://github.com'); + window.loadURL('https://github.com'); }); ``` diff --git a/docs-translations/zh-TW/tutorial/online-offline-events.md b/docs-translations/zh-TW/tutorial/online-offline-events.md index a4366f88a03a..234a02710eb7 100644 --- a/docs-translations/zh-TW/tutorial/online-offline-events.md +++ b/docs-translations/zh-TW/tutorial/online-offline-events.md @@ -12,7 +12,7 @@ var onlineStatusWindow; app.on('ready', function() { onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); + onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); }); ``` @@ -50,7 +50,7 @@ var onlineStatusWindow; app.on('ready', function() { onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); + onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); }); ipc.on('online-status-changed', function(event, status) { diff --git a/docs-translations/zh-TW/tutorial/quick-start.md b/docs-translations/zh-TW/tutorial/quick-start.md index aee2baa713bf..18c62c5e75ce 100644 --- a/docs-translations/zh-TW/tutorial/quick-start.md +++ b/docs-translations/zh-TW/tutorial/quick-start.md @@ -3,7 +3,7 @@ ## 簡介 Electron 可以讓你使用純 JavaScript 提供豐富的原生的 APIs 來創造桌面應用程式。 -你可以把它視為一個 io.js 的變體,專注於桌面應用程式而不是 web 伺服器。 +你可以把它視為一個 Node.js 的變體,專注於桌面應用程式而不是 web 伺服器。 這不表示 Electron 是一個用 JavaScript 去綁定 GUI 函式庫。取而代之的,Electron 是使用網頁介面來作為它的 GUI , 所以你可以把它看作是一個被 JavaScript 所控制且精簡化的 Chromium 瀏覽器。 @@ -19,7 +19,7 @@ Electron 可以讓你使用純 JavaScript 提供豐富的原生的 APIs 來創 每一個網頁在 Electron 裏執行各自的行程,被稱為 __渲染行程__。 在一般瀏覽器中,網頁通常會在沙盒環境下運行,並且不允許存取原生資源。然而, -Electron 的用戶擁有在網頁中呼叫 io.js APIs 的能力,允許低級別操作與作業系統的交互作用。 +Electron 的用戶擁有在網頁中呼叫 Node.js APIs 的能力,允許低級別操作與作業系統的交互作用。 ## 主行程與渲染行程的區別 @@ -30,7 +30,7 @@ Electron 的用戶擁有在網頁中呼叫 io.js APIs 的能力,允許低級 在網頁中,是不允許呼叫原生 GUI 相關 APIs 因為管理原生 GUI 資源在網頁上是非常危險而且容易造成資源洩露。 如果你想要在網頁中呼叫 GUI 相關的 APIs 的操作,網頁的渲染行程必須與主行程進行通訊,請求主行程進行相關的操作。 -在 Electron ,我們提供用於在主行程與渲染行程之間通訊的 [ipc][1] 模組。並且也有一個遠端模使用 RPC 通訊方式 [remote][2]。 +在 Electron,我們提供用於在主行程與渲染行程之間通訊的 [ipc](../api/ipc-renderer.md) 模組。並且也有一個遠端模組使用 RPC 通訊方式 [remote](../api/remote.md)。 # 打造你第一個 Electron 應用 @@ -43,7 +43,7 @@ your-app/ └── index.html ``` -`package.json ` 的格式與 Node 的模組完全一樣,並且有個腳本被指定為 `main` 是用來啟動你的應用程式,它運行在主行程上。 +`package.json` 的格式與 Node 的模組完全一樣,並且有個腳本被指定為 `main` 是用來啟動你的應用程式,它運行在主行程上。 你應用裡的 一個範例在你的 `package.json` 看起來可能像這樣: ```json @@ -85,10 +85,10 @@ app.on('ready', function() {   mainWindow = new BrowserWindow({width: 800, height: 600});   // 載入應用程式的 index.html -  mainWindow.loadUrl('file://' + __dirname + '/index.html'); +  mainWindow.loadURL('file://' + __dirname + '/index.html');   // 打開開發者工具 -  mainWindow.openDevTools(); +  mainWindow.webContents.openDevTools();   // 當window 被關閉,這個事件會被觸發   mainWindow.on('closed', function() { @@ -110,8 +110,9 @@ app.on('ready', function() {           

Hello World!

-    We are using io.js -    and Electron . + We are using node , + Chrome , + and Electron .    ``` @@ -160,6 +161,17 @@ $ ./Electron.app/Contents/MacOS/Electron your-app/ # 作為版本發行 在你完成了你的應用程式後,你可以依照 [應用部署](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 +Clone 與執行本篇教學的程式碼,它們都放在 [`atom/electron-quick-start`](https://github.com/atom/electron-quick-start) 這個 repository。 + +**Note**: 執行這個範例需要 [Git](https://git-scm.com) 以及 [Node.js](https://nodejs.org/en/download/) (其中包括 [npm](https://npmjs.org)) 在你的作業系統。 + +```bash +# Clone the repository +$ git clone https://github.com/atom/electron-quick-start +# Go into the repository +$ cd electron-quick-start +# Install dependencies and run the app +$ npm install && npm start +``` diff --git a/docs/README.md b/docs/README.md index cc25e9d0ff66..3a47eff0fabf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,17 @@ +Please make sure that you use the documents that match your Electron version. +The version number should be a part of the page URL. If it's not, you are +probably using the documentation of a development branch which may contain API +changes that are not compatible with your Electron version. If that's the case, +you can switch to a different version of the documentation at the +[available versions](http://electron.atom.io/docs/) list on atom.io, or if +you're using the GitHub interface, open the "Switch branches/tags" dropdown and +select the tag that matches your version. + ## Guides +* [Supported Platforms](tutorial/supported-platforms.md) * [Application Distribution](tutorial/application-distribution.md) +* [Mac App Store Submission Guide](tutorial/mac-app-store-submission-guide.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) @@ -19,6 +30,7 @@ * [Synopsis](api/synopsis.md) * [Process Object](api/process.md) * [Supported Chrome Command Line Switches](api/chrome-command-line-switches.md) +* [Environment Variables](api/environment-variables.md) ### Custom DOM Elements: @@ -29,33 +41,33 @@ ### Modules for the Main Process: * [app](api/app.md) -* [auto-updater](api/auto-updater.md) -* [browser-window](api/browser-window.md) -* [content-tracing](api/content-tracing.md) +* [autoUpdater](api/auto-updater.md) +* [BrowserWindow](api/browser-window.md) +* [contentTracing](api/content-tracing.md) * [dialog](api/dialog.md) -* [global-shortcut](api/global-shortcut.md) -* [ipc (main process)](api/ipc-main-process.md) -* [menu](api/menu.md) -* [menu-item](api/menu-item.md) -* [power-monitor](api/power-monitor.md) -* [power-save-blocker](api/power-save-blocker.md) +* [globalShortcut](api/global-shortcut.md) +* [ipcMain](api/ipc-main.md) +* [Menu](api/menu.md) +* [MenuItem](api/menu-item.md) +* [powerMonitor](api/power-monitor.md) +* [powerSaveBlocker](api/power-save-blocker.md) * [protocol](api/protocol.md) * [session](api/session.md) -* [web-contents](api/web-contents.md) -* [tray](api/tray.md) +* [webContents](api/web-contents.md) +* [Tray](api/tray.md) ### Modules for the Renderer Process (Web Page): -* [desktop-capturer](api/desktop-capturer.md) -* [ipc (renderer)](api/ipc-renderer.md) +* [desktopCapturer](api/desktop-capturer.md) +* [ipcRenderer](api/ipc-renderer.md) * [remote](api/remote.md) -* [web-frame](api/web-frame.md) +* [webFrame](api/web-frame.md) ### Modules for Both Processes: * [clipboard](api/clipboard.md) -* [crash-reporter](api/crash-reporter.md) -* [native-image](api/native-image.md) +* [crashReporter](api/crash-reporter.md) +* [nativeImage](api/native-image.md) * [screen](api/screen.md) * [shell](api/shell.md) @@ -65,7 +77,7 @@ * [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 (OS X)](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/app.md b/docs/api/app.md index e4a8d561e2f5..3faf4a1f0ac6 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -6,7 +6,7 @@ The following example shows how to quit the application when the last window is closed: ```javascript -var app = require('app'); +const app = require('electron').app; app.on('window-all-closed', function() { app.quit(); }); @@ -66,7 +66,7 @@ the `will-quit` and `window-all-closed` events. Emitted when the application is quitting. -### Event: 'open-file' +### Event: 'open-file' _OS X_ Returns: @@ -82,7 +82,9 @@ 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' +On Windows, you have to parse `process.argv` to get the filepath. + +### Event: 'open-url' _OS X_ Returns: @@ -99,7 +101,7 @@ You should call `event.preventDefault()` if you want to handle this event. Returns: * `event` Event -* `hasVisibleWindows` Bool +* `hasVisibleWindows` Boolean Emitted when the application is activated, which usually happens when clicks on the applications's dock icon. @@ -131,32 +133,92 @@ Returns: Emitted when a new [browserWindow](browser-window.md) is created. -### Event: 'select-certificate' - -Emitted when a client certificate is requested. +### Event: 'certificate-error' Returns: * `event` Event -* `webContents` [WebContents](browser-window.md#class-webcontents) -* `url` String -* `certificateList` [Objects] - * `data` PEM encoded data - * `issuerName` Issuer's Common Name +* `webContents` [WebContents](web-contents.md) +* `url` URL +* `error` String - The error code +* `certificate` Object + * `data` Buffer - PEM encoded data + * `issuerName` String * `callback` Function +Emitted when failed to verify the `certificate` for `url`, to trust the +certificate you should prevent the default behavior with +`event.preventDefault()` and call `callback(true)`. + ```javascript -app.on('select-certificate', function(event, host, url, list, callback) { - event.preventDefault(); - callback(list[0]); -}) +session.on('certificate-error', function(event, webContents, url, error, certificate, callback) { + if (url == "https://github.com") { + // Verification logic. + event.preventDefault(); + callback(true); + } else { + callback(false); + } +}); ``` +### Event: 'select-client-certificate' + +Returns: + +* `event` Event +* `webContents` [WebContents](web-contents.md) +* `url` URL +* `certificateList` [Objects] + * `data` Buffer - PEM encoded data + * `issuerName` String - Issuer's Common Name +* `callback` Function + +Emitted when a client certificate is requested. + 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. +```javascript +app.on('select-client-certificate', function(event, webContents, url, list, callback) { + event.preventDefault(); + callback(list[0]); +}) +``` + +### Event: 'login' + +Returns: + +* `event` Event +* `webContents` [WebContents](web-contents.md) +* `request` Object + * `method` String + * `url` URL + * `referrer` URL +* `authInfo` Object + * `isProxy` Boolean + * `scheme` String + * `host` String + * `port` Integer + * `realm` String +* `callback` Function + +Emitted when `webContents` wants to do basic auth. + +The default behavior is to cancel all authentications, to override this you +should prevent the default behavior with `event.preventDefault()` and call +`callback(username, password)` with the credentials. + +```javascript +app.on('login', function(event, webContents, request, authInfo, callback) { + event.preventDefault(); + callback('username', 'secret'); +}) +``` + ### Event: 'gpu-process-crashed' Emitted when the gpu process crashes. @@ -169,7 +231,7 @@ The `app` object has the following methods: ### `app.quit()` -Try to close all windows. The `before-quit` event will emitted first. If all +Try to close all windows. The `before-quit` event will be emitted first. If all windows are successfully closed, the `will-quit` event will be emitted and by default the application will terminate. @@ -177,6 +239,15 @@ 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.exit(exitCode)` + +* `exitCode` Integer + +Exits immediately with `exitCode`. + +All windows will be closed immediately without asking user and the `before-quit` +and `will-quit` events will not be emitted. + ### `app.getAppPath()` Returns the current application directory. @@ -197,16 +268,15 @@ You can request the following paths by the name: * `~/Library/Application Support` on OS X * `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. +* `desktop` The current user's Desktop directory. +* `documents` Directory for a user's "My Documents". +* `downloads` Directory for a user's downloads. +* `music` Directory for a user's music. +* `pictures` Directory for a user's pictures. +* `videos` Directory for a user's videos. ### `app.setPath(name, path)` @@ -219,7 +289,7 @@ created by this method. On failure an `Error` is thrown. You can only override paths of a `name` defined in `app.getPath`. -By default, web pages's cookies and caches will be stored under the `userData` +By default, web pages' 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. @@ -243,15 +313,7 @@ preferred over `name` by Electron. Returns the current application locale. -### `app.resolveProxy(url, callback)` - -* `url` URL -* `callback` Function - -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)` _OS X_ _Windows_ * `path` String @@ -260,7 +322,7 @@ Adds `path` to the recent documents list. 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()` _OS X_ _Windows_ Clears the recent documents list. @@ -270,7 +332,7 @@ Clears the recent documents list. Adds `tasks` to the [Tasks][tasks] category of the JumpList on Windows. -`tasks` is an array of `Task` objects in following format: +`tasks` is an array of `Task` objects in the following format: `Task` Object * `program` String - Path of the program to execute, usually you should @@ -286,6 +348,76 @@ Adds `tasks` to the [Tasks][tasks] category of the JumpList on Windows. 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. +### `app.allowNTLMCredentialsForAllDomains(allow)` + +* `allow` Boolean + +Dynamically sets whether to always send credentials for HTTP NTLM or Negotiate +authentication - normally, Electron will only send NTLM/Kerberos credentials for +URLs that fall under "Local Intranet" sites (i.e. are in the same domain as you). +However, this detection often fails when corporate networks are badly configured, +so this lets you co-opt this behavior and enable it for all URLs. + +### `app.makeSingleInstance(callback)` + +* `callback` Function + +This method makes your application a Single Instance Application - instead of +allowing multiple instances of your app to run, this will ensure that only a +single instance of your app is running, and other instances signal this +instance and exit. + +`callback` will be called with `callback(argv, workingDirectory)` when a second +instance has been executed. `argv` is an Array of the second instance's command +line arguments, and `workingDirectory` is its current working directory. Usually +applications respond to this by making their primary window focused and +non-minimized. + +The `callback` is guaranteed to be executed after the `ready` event of `app` +gets emitted. + +This method returns `false` if your process is the primary instance of the +application and your app should continue loading. And returns `true` if your +process has sent its parameters to another instance, and you should immediately +quit. + +On OS X the system enforces single instance automatically when users try to open +a second instance of your app in Finder, and the `open-file` and `open-url` +events will be emitted for that. However when users start your app in command +line the system's single instance machanism will be bypassed and you have to +use this method to ensure single instance. + +An example of activating the window of primary instance when a second instance +starts: + +```js +var myWindow = null; + +var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) { + // Someone tried to run a second instance, we should focus our window + if (myWindow) { + if (myWindow.isMinimized()) myWindow.restore(); + myWindow.focus(); + } + return true; +}); + +if (shouldQuit) { + app.quit(); + return; +} + +// Create myWindow, load the rest of the app, etc... +app.on('ready', function() { +}); +``` + +### `app.setAppUserModelId(id)` _Windows_ + +* `id` String + +Changes the [Application User Model ID][app-user-model-id] to `id`. + ### `app.commandLine.appendSwitch(switch[, value])` Append a switch (with optional `value`) to Chromium's command line. @@ -346,3 +478,4 @@ 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 +[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx diff --git a/docs/api/auto-updater.md b/docs/api/auto-updater.md index 9734f592c8fe..fa4f92cb936d 100644 --- a/docs/api/auto-updater.md +++ b/docs/api/auto-updater.md @@ -1,106 +1,38 @@ # autoUpdater -**This module has only been implemented for OS X.** +This module provides an interface for the `Squirrel` auto-updater framework. -Check out [atom/grunt-electron-installer](https://github.com/atom/grunt-electron-installer) -to build a Windows installer for your app. +## Platform notices -The `auto-updater` module is a simple wrapper around the -[Squirrel.Mac](https://github.com/Squirrel/Squirrel.Mac) framework. +Though `autoUpdater` provides a uniform API for different platforms, there are +still some subtle differences on each platform. -Squirrel.Mac requires that your `.app` folder is signed using the -[codesign](https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/codesign.1.html) -utility for updates to be installed. +### OS X -## Squirrel +On OS X, the `autoUpdater` module is built upon [Squirrel.Mac][squirrel-mac], +meaning you don't need any special setup to make it work. For server-side +requirements, you can read [Server Support][server-support]. -Squirrel is an OS X framework focused on making application updates **as safe -and transparent as updates to a website**. +### Windows -Instead of publishing a feed of versions from which your app must select, -Squirrel updates to the version your server tells it to. This allows you to -intelligently update your clients based on the request you give to Squirrel. +On Windows, you have to install your app into a user's machine before you can +use the auto-updater, so it is recommended to use +[grunt-electron-installer][installer] module to generate a Windows installer. -Your request can include authentication details, custom headers or a request -body so that your server has the context it needs in order to supply the most -suitable update. +The installer generated with Squirrel will create a shortcut icon with an +[Application User Model ID][app-user-model-id] in the format of +`com.squirrel.PACKAGE_ID.YOUR_EXE_WITHOUT_DOT_EXE`, examples are +`com.squirrel.slack.Slack` and `com.squirrel.code.Code`. You have to use the +same ID for your app with `app.setAppUserModelId` API, otherwise Windows will +not be able to pin your app properly in task bar. -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 to determine whether an update is required. See -[Server Support](#server-support). +The server-side setup is also different from OS X. You can read the documents of +[Squirrel.Windows][squirrel-windows] to get more details. -Squirrel's installer is designed to be fault tolerant and ensures that any -updates installed are valid. +### Linux -## Update Requests - -Squirrel is indifferent to the request the client application provides for -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). - -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 -username, to allow the server to deliver as fine grained an update as you -would like. - -How you include the version identifier or other criteria is specific to the -server that you are requesting updates from. A common approach is to use query -parameters, like this: - -```javascript -// In the main process -var app = require('app'); -var autoUpdater = require('auto-updater'); -autoUpdater.setFeedUrl('http://mycompany.com/myapp/latest?version=' + app.getVersion()); -``` - -## Server Support - -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 -[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 -currently running version. To save redundantly downloading the same version -multiple times your server must not inform the client to update. - -If no update is required your server must respond with a status code of -[204 No Content](http://tools.ietf.org/html/rfc2616#section-10.2.5). Squirrel -will check for an update again at the interval you specify. - -## Update JSON Format - -When an update is available, Squirrel expects the following schema in response -to the update request provided: - -```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" -} -``` - -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. - -## 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. +There is not built-in support for auto-updater on Linux, so it is recommended to +use the distribution's package manager to update your app. ## Events @@ -110,8 +42,7 @@ The `autoUpdater` object emits the following events: Returns: -* `event` Event -* `message` String +* `error` Error Emitted when there is an error while updating. @@ -136,24 +67,35 @@ Returns: * `releaseNotes` String * `releaseName` String * `releaseDate` Date -* `updateUrl` String -* `quitAndUpdate` Function +* `updateURL` String -Emitted when an update has been downloaded. Calling `quitAndUpdate()` will -restart the application and install the update. +Emitted when an update has been downloaded. + +On Windows only `releaseName` is available. ## Methods The `autoUpdater` object has the following methods: -### `autoUpdater.setFeedUrl(url)` +### `autoUpdater.setFeedURL(url)` * `url` String -Set the `url` and initialize the auto updater. The `url` cannot be changed +Sets the `url` and initialize the auto updater. The `url` cannot be changed once it is set. ### `autoUpdater.checkForUpdates()` -Ask the server whether there is an update. You must call `setFeedUrl` before +Asks the server whether there is an update. You must call `setFeedURL` before using this API. + +### `autoUpdater.quitAndInstall()` + +Restarts the app and installs the update after it has been downloaded. It +should only be called after `update-downloaded` has been emitted. + +[squirrel-mac]: https://github.com/Squirrel/Squirrel.Mac +[server-support]: https://github.com/Squirrel/Squirrel.Mac#server-support +[squirrel-windows]: https://github.com/Squirrel/Squirrel.Windows +[installer]: https://github.com/atom/grunt-electron-installer +[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index a1870bc66afd..db02b6aa14b3 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -4,14 +4,14 @@ The `BrowserWindow` class gives you the ability to create a browser window. For example: ```javascript -var BrowserWindow = require('browser-window'); +const BrowserWindow = require('electron').BrowserWindow; var win = new BrowserWindow({ width: 800, height: 600, show: false }); win.on('closed', function() { win = null; }); -win.loadUrl('https://github.com'); +win.loadURL('https://github.com'); win.show(); ``` @@ -24,104 +24,123 @@ You can also create a window without chrome by using [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter). It creates a new `BrowserWindow` with native properties as set by the `options`. -Properties `width` and `height` are required. -### `new BrowserWindow(options)` +### `new BrowserWindow([options])` -`options` Object, properties: +`options` Object (optional), 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 +* `width` Integer - Window's width in pixels. Default is `800`. +* `height` Integer - Window's height in pixels. Default is `600`. +* `x` Integer - Window's left offset from screen. Default is to center the + window. +* `y` Integer - Window's top offset from screen. Default is to center the + window. +* `useContentSize` 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. + frame's size and be slightly larger. Default is `false`. * `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. +* `minWidth` Integer - Window's minimum width. Default is `0`. +* `minHeight` Integer - Window's minimum height. Default is `0`. +* `maxWidth` Integer - Window's maximum width. Default is no limit. +* `maxHeight` Integer - Window's maximum height. Default is no limit. +* `resizable` Boolean - Whether window is resizable. Default is `true`. +* `alwaysOnTop` Boolean - Whether the window should always stay on top of + other windows. Default is `false`. * `fullscreen` Boolean - Whether the window should show in fullscreen. When set to `false` the fullscreen button will be hidden or disabled on OS X. -* `skip-taskbar` Boolean - Whether to show the window in taskbar. -* `kiosk` Boolean - The kiosk mode. -* `title` String - Default window title. + Default is `false`. +* `skipTaskbar` Boolean - Whether to show the window in taskbar. Default is + `false`. +* `kiosk` Boolean - The kiosk mode. Default is `false`. +* `title` String - Default window title. Default is `"Electron"`. * `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. +* `show` Boolean - Whether window should be shown when created. Default is + `true`. * `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. + [Frameless Window](frameless-window.md). Default is `true`. +* `acceptFirstMouse` Boolean - Whether the web view accepts a single + mouse-down event that simultaneously activates the window. Default is `false`. +* `disableAutoHideCursor` Boolean - Whether to hide cursor when typing. Default + is `false`. +* `autoHideMenuBar` Boolean - Auto hide the menu bar unless the `Alt` + key is pressed. Default is `false`. +* `enableLargerThanScreen` Boolean - Enable the window to be resized larger + than screen. Default is `false`. +* `backgroundColor` String - Window's background color as Hexadecimal value, + like `#66CD00` or `#FFF`. This is only implemented on Linux and Windows. + Default is `#000` (black). +* `darkTheme` Boolean - Forces using dark theme for the window, only works on + some GTK+3 desktop environments. Default is `false`. * `transparent` Boolean - Makes the window [transparent](frameless-window.md). -* `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. + Default is `false`. +* `type` String - Specifies the type of the window, which applies + additional platform-specific properties. By default it's undefined and you'll + get a regular app window. Supported values: + * On Linux, possible types are `desktop`, `dock`, `toolbar`, `splash`, + `notification`. + * On OS X, possible types are `desktop`, `textured`. The `textured` type adds + metal gradient appearance (`NSTexturedBackgroundWindowMask`). The `desktop` + type places the window at the desktop background window level + (`kCGDesktopWindowLevel - 1`). Note that desktop window will not receive + focus, keyboard or mouse events, but you can use `globalShortcut` to receive + input sparingly. +* `titleBarStyle` 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 + * `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 +* `webPreferences` Object - Settings of web page's features, properties: + * `nodeIntegration` Boolean - Whether node integration is enabled. Default is `true`. * `preload` String - Specifies a script that will be loaded before other scripts run in the page. This script will always have access to node APIs - no matter whether node integration is turned on for the page, and the path - of `preload` script has to be absolute path. + no matter whether node integration is turned on or off. The value should + be the absolute file path to the script. + When node integration is turned off, the preload script can reintroduce + Node global symbols back to the global scope. See example + [here](process.md#event-loaded). * `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 + * `zoomFactor` Number - The default zoom factor of the page, `3.0` represents + `300%`. Default is `1.0`. + * `javascript` Boolean - Enables JavaScript support. Default is `true`. + * `webSecurity` 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 + `allowDisplayingInsecureContent` and `allowRunningInsecureContent` to + `true` if these two options are not set by user. Default is `true`. + * `allowDisplayingInsecureContent` Boolean - Allow an https page to display + content like images from http URLs. Default is `false`. + * `allowRunningInsecureContent` Boolean - Allow a https page to run + JavaScript, CSS or plugins from http URLs. Default is `false`. + * `images` Boolean - Enables image support. Default is `true`. + * `textAreasAreResizable` Boolean - Make TextArea elements resizable. Default + is `true`. + * `webgl` Boolean - Enables WebGL support. Default is `true`. + * `webaudio` Boolean - Enables WebAudio support. Default is `true`. + * `plugins` Boolean - Whether plugins should be enabled. Default is `false`. + * `experimentalFeatures` Boolean - Enables Chromium's experimental features. + Default is `false`. + * `experimentalCanvasFeatures` Boolean - Enables Chromium's experimental + canvas features. Default is `false`. + * `overlayScrollbars` Boolean - Enables overlay scrollbars. Default is + `false`. + * `sharedWorker` Boolean - Enables Shared Worker support. Default is `false`. + * `directWrite` Boolean - Enables DirectWrite font rendering system on + Windows. Default is `true`. + * `pageVisibility` 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. + timers. Default is `false`. ## Events @@ -232,19 +251,7 @@ Emitted when the window enters full screen state triggered by html api. Emitted when the window leaves full screen state triggered by html api. -### Event: 'devtools-opened' - -Emitted when DevTools is opened. - -### Event: 'devtools-closed' - -Emitted when DevTools is closed. - -### Event: 'devtools-focused' - -Emitted when DevTools is focused / opened. - -### Event: 'app-command': +### Event: 'app-command' _Windows_ 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 @@ -303,11 +310,8 @@ Remove the DevTools extension whose name is `name`. 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` @@ -318,16 +322,6 @@ 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. - -### `win.devToolsWebContents` - -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. - ### `win.id` The unique ID of this window. @@ -338,14 +332,6 @@ 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 @@ -574,6 +560,30 @@ Enters or leaves the kiosk mode. Returns whether the window is in kiosk mode. +### `win.hookWindowMessage(message, callback)` _Windows_ + +* `message` Integer +* `callback` Function + +Hooks a windows message. The `callback` is called when +the message is received in the WndProc. + +### `win.isWindowMessageHooked(message)` _Windows_ + +* `message` Integer + +Returns `true` or `false` depending on whether the message is hooked. + +### `win.unhookWindowMessage(message)` _Windows_ + +* `message` Integer + +Unhook the window message. + +### `win.unhookAllWindowMessages()` _Windows_ + +Unhooks all of the window messages. + ### `win.setRepresentedFilename(filename)` _OS X_ * `filename` String @@ -592,45 +602,10 @@ Returns the pathname of the file the window represents. Specifies whether the window’s document has been edited, and the icon in title bar will become grey when set to `true`. -### `win.IsDocumentEdited()` _OS X_ +### `win.isDocumentEdited()` _OS X_ Whether the window's document has been edited. -### `win.openDevTools([options])` - -* `options` Object (optional). Properties: - * `detach` Boolean - opens DevTools in a new window - -Opens the developer tools. - -### `win.closeDevTools()` - -Closes the developer tools. - -### `win.isDevToolsOpened()` - -Returns whether the developer tools are opened. - -### `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`). - -### `win.inspectServiceWorker()` - -Opens the developer tools for the service worker context present in the web -contents. - ### `win.focusOnWebView()` ### `win.blurWebView()` @@ -657,9 +632,9 @@ Same as `webContents.print([options])` Same as `webContents.printToPDF(options, callback)` -### `win.loadUrl(url[, options])` +### `win.loadURL(url[, options])` -Same as `webContents.loadUrl(url[, options])`. +Same as `webContents.loadURL(url[, options])`. ### `win.reload()` diff --git a/docs/api/chrome-command-line-switches.md b/docs/api/chrome-command-line-switches.md index cd633fc0460c..edeba0c9ad1b 100644 --- a/docs/api/chrome-command-line-switches.md +++ b/docs/api/chrome-command-line-switches.md @@ -1,12 +1,12 @@ # Supported Chrome command line switches -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: +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: ```javascript -var app = require('app'); +const app = require('electron').app; app.commandLine.appendSwitch('remote-debugging-port', '8315'); app.commandLine.appendSwitch('host-rules', 'MAP * 127.0.0.1'); @@ -31,10 +31,37 @@ Disables the disk cache for HTTP requests. Enables remote debugging over HTTP on the specified `port`. +## --js-flags=`flags` + +Specifies the flags passed to JS engine. It has to be passed when starting +Electron if you want to enable the `flags` in the main process. + +```bash +$ electron --js-flags="--harmony_proxies --harmony_collections" your-app +``` + ## --proxy-server=`address:port` -Use a specified proxy server, which overrides the system setting. This switch only -affects HTTP and HTTPS requests. +Use a specified proxy server, which overrides the system setting. This switch +only affects requests with HTTP protocol, including HTTPS and WebSocket +requests. It is also noteworthy that not all proxy servers support HTTPS and +WebSocket requests. + +## --proxy-bypass-list=`hosts` + +Instructs Electron to bypass the proxy server for the given semi-colon-separated +list of hosts. This flag has an effect only if used in tandem with +`--proxy-server`. + +For example: + +```javascript +app.commandLine.appendSwitch('proxy-bypass-list', ';*.google.com;*foo.com;1.2.3.4:5678')` +``` + +Will use the proxy server for all hosts except for local addresses (`localhost`, +`127.0.0.1` etc.), `google.com` subdomains, hosts that contain the suffix +`foo.com` and anything at `1.2.3.4:5678`. ## --proxy-pac-url=`url` @@ -89,15 +116,27 @@ Enables net log events to be saved and writes them to `path`. ## --ssl-version-fallback-min=`version` -Set the minimum SSL/TLS version ("tls1", "tls1.1" or "tls1.2") that TLS +Sets the minimum SSL/TLS version ("tls1", "tls1.1" or "tls1.2") that TLS fallback will accept. +## --cipher-suite-blacklist=`cipher_suites` + +Specify comma-separated list of SSL cipher suites to disable. + +## --enable-logging + +Prints Chromium's logging into console. + +This switch can not be used in `app.commandLine.appendSwitch` since it is parsed +earlier than user's app is loaded, but you can set the `ELECTRON_ENABLE_LOGGING` +environment variable to achieve the same effect. + ## --v=`log_level` Gives the default maximal active V-logging level; 0 is the default. Normally positive values are used for V-logging levels. -Passing `--v=-1` will disable logging. +This switch only works when `--enable-logging` is also passed. ## --vmodule=`pattern` @@ -109,10 +148,4 @@ 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 the source files under a `foo/bar` directory. -To disable all chromium related logs and only enable your application logs you -can do: - -```javascript -app.commandLine.appendSwitch('v', -1); -app.commandLine.appendSwitch('vmodule', 'console=0'); -``` +This switch only works when `--enable-logging` is also passed. diff --git a/docs/api/clipboard.md b/docs/api/clipboard.md index a99605baea18..7cb5b840bc1c 100644 --- a/docs/api/clipboard.md +++ b/docs/api/clipboard.md @@ -4,7 +4,7 @@ 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'); +const clipboard = require('electron').clipboard; clipboard.writeText('Example String'); ``` @@ -12,7 +12,6 @@ On X Window systems, there is also a selection clipboard. To manipulate it you need to pass `selection` to each method: ```javascript -var clipboard = require('clipboard'); clipboard.writeText('Example String', 'selection'); console.log(clipboard.readText('selection')); ``` @@ -82,7 +81,6 @@ Returns an array of supported formats for the clipboard `type`. Returns whether the clipboard supports the format of specified `data`. ```javascript -var clipboard = require('clipboard'); console.log(clipboard.has('

selection

')); ``` @@ -102,7 +100,6 @@ Reads `data` from the clipboard. * `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 2e05fc67668f..aae5306523ae 100644 --- a/docs/api/content-tracing.md +++ b/docs/api/content-tracing.md @@ -6,9 +6,14 @@ so you need to open `chrome://tracing/` in a Chrome browser and load the generated file to view the result. ```javascript -var contentTracing = require('content-tracing'); +const contentTracing = require('electron').contentTracing; -contentTracing.startRecording('*', contentTracing.DEFAULT_OPTIONS, function() { +const options = { + categoryFilter: '*', + traceOptions: 'record-until-full,enable-sampling' +} + +contentTracing.startRecording(options, function() { console.log('Tracing started'); setTimeout(function() { diff --git a/docs/api/crash-reporter.md b/docs/api/crash-reporter.md index 86670fcc90c9..6c66a855f753 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -6,12 +6,12 @@ The following is an example of automatically submitting a crash report to a remote server: ```javascript -var crashReporter = require('crash-reporter'); +const crashReporter = require('electron').crashReporter; crashReporter.start({ productName: 'YourName', companyName: 'YourCompany', - submitUrl: 'https://your-domain.com/url-to-submit', + submitURL: 'https://your-domain.com/url-to-submit', autoSubmit: true }); ``` @@ -26,7 +26,7 @@ The `crash-reporter` module has the following methods: * `productName` String, default: Electron. * `companyName` String, default: GitHub, Inc. -* `submitUrl` String, default: http://54.249.141.255:1127/post. +* `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. @@ -57,13 +57,12 @@ ID. ## crash-reporter Payload -The crash reporter will send the following data to the `submitUrl` as `POST`: +The crash reporter will send the following data to the `submitURL` as `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'. -* `ptime` Number +* `guid` String - e.g. '5e1286fc-da97-479e-918b-6bfb0c3d1c72' * `_version` String - The version in `package.json`. * `_productName` String - The product name in the `crashReporter` `options` object. diff --git a/docs/api/dialog.md b/docs/api/dialog.md index 0fadfa37f80c..884fb7c07327 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -8,7 +8,7 @@ An example of showing a dialog to select multiple files and directories: ```javascript var win = ...; // BrowserWindow in which to show the dialog -var dialog = require('dialog'); +const dialog = require('electron').dialog; console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', 'multiSelections' ]})); ``` @@ -114,4 +114,6 @@ will be passed via `callback(response)`. Displays a modal dialog that shows an error message. 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. +it is usually used to report errors in early stage of startup. If called +before the app `ready`event on Linux, the message will be emitted to stderr, +and no GUI dialog will appear. diff --git a/docs/api/download-item.md b/docs/api/download-item.md index 722dc5d8cf78..756353b8ba3d 100644 --- a/docs/api/download-item.md +++ b/docs/api/download-item.md @@ -39,7 +39,7 @@ Emits when the `downloadItem` gets updated. * `interrupted` - An error broke the connection with the file server. Emits when the download is in a terminal state. This includes a completed -download, a cancelled download(via `downloadItem.cancel()`), and interrputed +download, a cancelled download(via `downloadItem.cancel()`), and interrupted download that can't be resumed. ## Methods diff --git a/docs/api/environment-variables.md b/docs/api/environment-variables.md new file mode 100644 index 000000000000..6b000aaa1080 --- /dev/null +++ b/docs/api/environment-variables.md @@ -0,0 +1,50 @@ +# Environment variables + +Some behaviors of Electron are controlled by environment variables, because they +are initialized earlier than command line and the app's code. + +Examples on POSIX shells: + +```bash +$ export ELECTRON_ENABLE_LOGGING=true +$ electron +``` + +on Windows console: + +```powershell +> set ELECTRON_ENABLE_LOGGING=true +> electron +``` + +## `ELECTRON_RUN_AS_NODE` + +Starts the process as a normal Node.js process. + +## `ELECTRON_ENABLE_LOGGING` + +Prints Chrome's internal logging to console. + +## `ELECTRON_ENABLE_STACK_DUMPING` + +When Electron crashed, prints the stack trace to console. + +This environment variable will not work if `crashReporter` is started. + +## `ELECTRON_DEFAULT_ERROR_MODE` _Windows_ + +Shows Windows's crash dialog when Electron crashed. + +This environment variable will not work if `crashReporter` is started. + +## `ELECTRON_NO_ATTACH_CONSOLE` _Windows_ + +Don't attach to current console session. + +## `ELECTRON_FORCE_WINDOW_MENU_BAR` _Linux_ + +Don't use global menu bar on Linux. + +## `ELECTRON_HIDE_INTERNAL_MODULES` + +Turns off compatibility mode for old built-in modules like `require('ipc')`. diff --git a/docs/api/frameless-window.md b/docs/api/frameless-window.md index 8d64a6fcd7c8..e70749f2894c 100644 --- a/docs/api/frameless-window.md +++ b/docs/api/frameless-window.md @@ -1,6 +1,6 @@ # Frameless Window -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. +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 web page. These are options on the [`BrowserWindow`](browser-window.md) class. ## Create a frameless window @@ -9,11 +9,11 @@ To create a frameless window, you need to set `frame` to `false` in ```javascript -var BrowserWindow = require('browser-window'); +const BrowserWindow = require('electron').BrowserWindow; var win = new BrowserWindow({ width: 800, height: 600, frame: false }); ``` -### Alternatives on Mac +### Alternatives on OS X 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 @@ -23,8 +23,7 @@ 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' }); +var win = new BrowserWindow({ 'title-bar-style': 'hidden' }); ``` ## Transparent window diff --git a/docs/api/global-shortcut.md b/docs/api/global-shortcut.md index adba06e1adcf..ab6123bd877e 100644 --- a/docs/api/global-shortcut.md +++ b/docs/api/global-shortcut.md @@ -9,14 +9,15 @@ 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'); +const electron = require('electron'); +const app = electron.app; +const globalShortcut = electron.globalShortcut; 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'); @@ -45,7 +46,10 @@ The `global-shortcut` module has the following methods: * `callback` Function Registers a global shortcut of `accelerator`. The `callback` is called when -the registered shortcut is pressed by the user. +the registered shortcut is pressed by the user. Returns `true` if the shortcut +`accelerator` was registered, `false` otherwise. For example, the specified +`accelerator` has already been registered by another caller or other native +applications. ### `globalShortcut.isRegistered(accelerator)` @@ -62,4 +66,4 @@ Unregisters the global shortcut of `accelerator`. ### `globalShortcut.unregisterAll()` -Unregisters all the global shortcuts. +Unregisters all of the global shortcuts. diff --git a/docs/api/ipc-main-process.md b/docs/api/ipc-main-process.md deleted file mode 100644 index 98d9c3c22d43..000000000000 --- a/docs/api/ipc-main-process.md +++ /dev/null @@ -1,76 +0,0 @@ -# ipc (main process) - -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. - -## Sending Messages - -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. - -- 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. -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 -// In renderer process (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'); -``` - -## Listening for Messages - -The `ipc` module has the following method to listen for events: - -### `ipc.on(channel, callback)` - -* `channel` String - The event name. -* `callback` Function - -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-main.md b/docs/api/ipc-main.md new file mode 100644 index 000000000000..cdbc0ce34eec --- /dev/null +++ b/docs/api/ipc-main.md @@ -0,0 +1,71 @@ +# ipcMain + +The `ipcMain` 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. + +## Sending Messages + +It is also possible to send messages from the main process to the renderer +process, see [webContents.send][webcontents-send] for more information. + +* When sending a message, the event name is the `channel`. +* To reply a synchronous message, you need to set `event.returnValue`. +* To send an asynchronous back to the sender, you can use + `event.sender.send(...)`. + +An example of sending and handling messages between the render and main +processes: + +```javascript +// In main process. +const ipcMain = require('electron').ipcMain; +ipcMain.on('asynchronous-message', function(event, arg) { + console.log(arg); // prints "ping" + event.sender.send('asynchronous-reply', 'pong'); +}); + +ipcMain.on('synchronous-message', function(event, arg) { + console.log(arg); // prints "ping" + event.returnValue = 'pong'; +}); +``` + +```javascript +// In renderer process (web page). +const ipcRenderer = require('electron').ipcRenderer; +console.log(ipcRenderer.sendSync('synchronous-message', 'ping')); // prints "pong" + +ipcRenderer.on('asynchronous-reply', function(event, arg) { + console.log(arg); // prints "pong" +}); +ipcRenderer.send('asynchronous-message', 'ping'); +``` + +## Listening for Messages + +The `ipcMain` module has the following method to listen for events: + +### `ipcMain.on(channel, callback)` + +* `channel` String - The event name. +* `callback` Function + +When the event occurs the `callback` is called with an `event` object and a +message, `arg`. + +## IPC Event + +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, you can call +`event.sender.send` to reply to the asynchronous message, see +[webContents.send][webcontents-send] for more information. + +[webcontents-send]: web-contents.md#webcontentssendchannel-args diff --git a/docs/api/ipc-renderer.md b/docs/api/ipc-renderer.md index 752af2ebe293..01f0bb3a83dd 100644 --- a/docs/api/ipc-renderer.md +++ b/docs/api/ipc-renderer.md @@ -1,52 +1,54 @@ -# ipc (renderer) +# ipcRenderer -The `ipc` module provides a few methods so you can send synchronous and +The `ipcRenderer` module provides a few methods so you can send synchronous and 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 [ipcMain](ipc-main.md) for code examples. -See [ipc (main process)](ipc-main-process.md) for code examples. +## Listening for Messages -## Methods +The `ipcRenderer` module has the following method to listen for events: -The `ipc` module has the following methods for sending messages: +### `ipcRenderer.on(channel, callback)` -**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). +* `channel` String - The event name. +* `callback` Function -### `ipc.send(channel[, arg1][, arg2][, ...])` +When the event occurs the `callback` is called with an `event` object and +arbitrary arguments. + +## Sending Messages + +The `ipcRenderer` module has the following methods for sending messages: + +### `ipcRenderer.send(channel[, arg1][, arg2][, ...])` * `channel` String - The event name. * `arg` (optional) -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`. +Send an event to the main process asynchronously via a `channel`, you can also +send arbitrary arguments. The main process handles it by listening for the +`channel` event with `ipcMain`. -### `ipc.sendSync(channel[, arg1][, arg2][, ...])` +### `ipcRenderer.sendSync(channel[, arg1][, arg2][, ...])` * `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`. +Send an event to the main process synchronously via a `channel`, you can also +send arbitrary arguments. -The main process handles it by listening for the `channel` event with `ipc` and -replies by setting the `event.returnValue`. +The main process handles it by listening for the `channel` event with +`ipcMain` and replies by setting `event.returnValue`. -**Note:** Sending a synchronous message will block the whole renderer process so -using this method is not recommended. +__Note:__ Sending a synchronous message will block the whole renderer process, +unless you know what you are doing you should never use it. -### `ipc.sendToHost(channel[, arg1][, arg2][, ...])` +### `ipcRenderer.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. +Like `ipcRenderer.send` but the event will be sent to the `` element in +the host page instead of the main process. diff --git a/docs/api/menu-item.md b/docs/api/menu-item.md index 89524d9f2352..37079233fc0c 100644 --- a/docs/api/menu-item.md +++ b/docs/api/menu-item.md @@ -1,6 +1,6 @@ # MenuItem -The `menu-item` module allows you to add items to an application or content +The `menu-item` module allows you to add items to an application or context [`menu`](menu.md). See [`menu`](menu.md) for examples. diff --git a/docs/api/menu.md b/docs/api/menu.md index f48b07e7e40a..b5f2fbe9516e 100644 --- a/docs/api/menu.md +++ b/docs/api/menu.md @@ -16,9 +16,9 @@ the user right clicks the page: ```html - + + + ``` To run your app, read [Run your app](../tutorial/quick-start.md#run-your-app). + +## Destructuring assignment + +If you are using CoffeeScript or Babel, you can also use +[destructuring assignment][desctructuring-assignment] to make it easier to use +built-in modules: + +```javascript +const {app, BrowserWindow} = require('electron') +``` + +However if you are using plain JavaScript, you have to wait until Chrome fully +supports ES6. + +## Disable old styles of using built-in modules + +Before v0.35.0, all built-in modules have to be used in the form of +`require('module-name')`, though it has [many disadvantages][issue-387], we are +still supporting it for compatibility with old apps. + +To disable the old styles completely, you can set the +`ELECTRON_HIDE_INTERNAL_MODULES` environment variable: + +```javascript +process.env.ELECTRON_HIDE_INTERNAL_MODULES = 'true' +``` + +Or call the `hideInternalModules` API: + +```javascript +require('electron').hideInternalModules() +``` + +[gui]: https://en.wikipedia.org/wiki/Graphical_user_interface +[main-process]: ../tutorial/quick-start.md#the-main-process +[desctructuring-assignment]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment +[issue-387]: https://github.com/atom/electron/issues/387 diff --git a/docs/api/tray.md b/docs/api/tray.md index 528705acb325..08a43638be14 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -4,9 +4,10 @@ A `Tray` represents an icon in an operating system's notification area, it is usually attached with a context menu. ```javascript -var app = require('app'); -var Menu = require('menu'); -var Tray = require('tray'); +const electron = require('electron'); +const app = electron.app; +const Menu = electron.Menu; +const Tray = electron.Tray; var appIcon = null; app.on('ready', function(){ @@ -30,10 +31,10 @@ __Platform limitations:__ * 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. +* When app indicator is used on Linux, the `click` event is ignored. 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. +rely on the `click` event and always attach a context menu to the tray icon. ## Class: Tray @@ -52,7 +53,7 @@ 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: 'click' * `event` Event * `altKey` Boolean @@ -69,7 +70,7 @@ Emitted when the tray icon is clicked. __Note:__ The `bounds` payload is only implemented on OS X and Windows. -### Event: 'right-clicked' _OS X_ _Windows_ +### Event: 'right-click' _OS X_ _Windows_ * `event` Event * `altKey` Boolean @@ -84,7 +85,7 @@ __Note:__ The `bounds` payload is only implemented on OS X and Windows. Emitted when the tray icon is right clicked. -### Event: 'double-clicked' _OS X_ _Windows_ +### Event: 'double-click' _OS X_ _Windows_ * `event` Event * `altKey` Boolean @@ -103,7 +104,7 @@ Emitted when the tray icon is double clicked. Emitted when the tray balloon shows. -### Event: 'balloon-clicked' _Windows_ +### Event: 'balloon-click' _Windows_ Emitted when the tray balloon is clicked. @@ -112,6 +113,10 @@ Emitted when the tray balloon is clicked. Emitted when the tray balloon is closed because of timeout or user manually closes it. +### Event: 'drop' _OS X_ + +Emitted when any dragged items are dropped on the tray icon. + ### Event: 'drop-files' _OS X_ * `event` @@ -119,6 +124,18 @@ closes it. Emitted when dragged files are dropped in the tray icon. +### Event: 'drag-enter' _OS X_ + +Emitted when a drag operation enters the tray icon. + +### Event: 'drag-leave' _OS X_ + +Emitted when a drag operation exits the tray icon. + +### Event: 'drag-end' _OS X_ + +Emitted when a drag operation ends on the tray or ends at another location. + ## Methods The `Tray` module has the following methods: @@ -158,7 +175,8 @@ Sets the title displayed aside of the tray icon in the status bar. * `highlight` Boolean -Sets whether the tray icon is highlighted when it is clicked. +Sets whether the tray icon's background becomes highlighted (in blue) +when the tray icon is clicked. Defaults to true. ### `Tray.displayBalloon(options)` _Windows_ @@ -169,12 +187,16 @@ Sets whether the tray icon is highlighted when it is clicked. Displays a tray balloon. -### `Tray.popUpContextMenu([position])` _OS X_ _Windows_ +### `Tray.popUpContextMenu([menu, position])` _OS X_ _Windows_ -* `position` Object (optional)- The pop up position. +* `menu` Menu (optional) +* `position` Object (optional) - The pop up position. * `x` Integer * `y` Integer +Popups the context menu of tray icon. When `menu` is passed, the `menu` will +showed instead of the tray's context menu. + The `position` is only available on Windows, and it is (0, 0) by default. ### `Tray.setContextMenu(menu)` diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index c2dccc3a876c..c35bbc9ae33c 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -8,10 +8,10 @@ the [`BrowserWindow`](browser-window.md) object. An example of accessing the `webContents` object: ```javascript -var BrowserWindow = require('browser-window'); +const BrowserWindow = require('electron').BrowserWindow; var win = new BrowserWindow({width: 800, height: 1500}); -win.loadUrl("http://github.com"); +win.loadURL("http://github.com"); var webContents = win.webContents; ``` @@ -32,10 +32,11 @@ Returns: * `event` Event * `errorCode` Integer * `errorDescription` String -* `validatedUrl` 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. +The full list of error codes and their meaning is available [here](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h). ### Event: 'did-frame-finish-load' @@ -60,8 +61,8 @@ Returns: * `event` Event * `status` Boolean -* `newUrl` String -* `originalUrl` String +* `newURL` String +* `originalURL` String * `httpResponseCode` Integer * `requestMethod` String * `referrer` String @@ -75,8 +76,8 @@ Emitted when details regarding a requested resource are available. Returns: * `event` Event -* `oldUrl` String -* `newUrl` String +* `oldURL` String +* `newURL` String * `isMainFrame` Boolean * `httpResponseCode` Integer * `requestMethod` String @@ -98,7 +99,7 @@ Emitted when the document in the given frame is loaded. Returns: * `event` Event -* `favicons` Array - Array of Urls +* `favicons` Array - Array of URLs Emitted when page receives favicon urls. @@ -132,7 +133,7 @@ Emitted when a user or the page wants to start navigation. It can happen when th `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`. +APIs like `webContents.loadURL` and `webContents.back`. Calling `event.preventDefault()` will prevent the navigation. @@ -154,39 +155,111 @@ Emitted when a plugin process has crashed. Emitted when `webContents` is destroyed. +### Event: 'devtools-opened' + +Emitted when DevTools is opened. + +### Event: 'devtools-closed' + +Emitted when DevTools is closed. + +### Event: 'devtools-focused' + +Emitted when DevTools is focused / opened. + +### Event: 'certificate-error' + +Returns: + +* `event` Event +* `url` URL +* `error` String - The error code +* `certificate` Object + * `data` Buffer - PEM encoded data + * `issuerName` String +* `callback` Function + +Emitted when failed to verify the `certificate` for `url`. + +The usage is the same with [the `certificate-error` event of +`app`](app.md#event-certificate-error). + +### Event: 'select-client-certificate' + +Returns: + +* `event` Event +* `url` URL +* `certificateList` [Objects] + * `data` Buffer - PEM encoded data + * `issuerName` String - Issuer's Common Name +* `callback` Function + +Emitted when a client certificate is requested. + +The usage is the same with [the `select-client-certificate` event of +`app`](app.md#event-select-client-certificate). + +### Event: 'login' + +Returns: + +* `event` Event +* `request` Object + * `method` String + * `url` URL + * `referrer` URL +* `authInfo` Object + * `isProxy` Boolean + * `scheme` String + * `host` String + * `port` Integer + * `realm` String +* `callback` Function + +Emitted when `webContents` wants to do basic auth. + +The usage is the same with [the `login` event of `app`](app.md#event-login). + ## 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])` +### `webContents.loadURL(url[, options])` * `url` URL * `options` Object (optional), properties: * `httpReferrer` String - A HTTP Referrer url. * `userAgent` String - A user agent originating the request. + * `extraHeaders` String - Extra headers separated by "\n" Loads the `url` in the window, the `url` must contain the protocol prefix, -e.g. the `http://` or `file://`. - -### `webContents.getUrl()` +e.g. the `http://` or `file://`. If the load should bypass http cache then +use the `pragma` header to achieve it. ```javascript -var BrowserWindow = require('browser-window'); - -var win = new BrowserWindow({width: 800, height: 600}); -win.loadUrl("http://github.com"); - -var currentUrl = win.webContents.getUrl(); +const options = {"extraHeaders" : "pragma: no-cache\n"} +webContents.loadURL(url, options) ``` +### `webContents.downloadURL(url)` + +* `url` URL + +Initiates a download of the resource at `url` without navigating. The +`will-download` event of `session` will be triggered. + +### `webContents.getURL()` + Returns URL of the current web page. +```javascript +var win = new BrowserWindow({width: 800, height: 600}); +win.loadURL("http://github.com"); + +var currentURL = win.webContents.getURL(); +``` + ### `webContents.getTitle()` Returns the title of the current web page. @@ -283,7 +356,7 @@ this limitation. ### `webContents.setAudioMuted(muted)` -+ `muted` Boolean +* `muted` Boolean Mute the audio on the current web page. @@ -410,11 +483,11 @@ By default, an empty `options` will be regarded as: ``` ```javascript -var BrowserWindow = require('browser-window'); -var fs = require('fs'); +const BrowserWindow = require('electron').BrowserWindow; +const fs = require('fs'); var win = new BrowserWindow({width: 800, height: 600}); -win.loadUrl("http://github.com"); +win.loadURL("http://github.com"); win.webContents.on("did-finish-load", function() { // Use default printing options @@ -433,7 +506,14 @@ win.webContents.on("did-finish-load", function() { * `path` String -Adds the specified path to DevTools workspace. +Adds the specified path to DevTools workspace. Must be used after DevTools +creation: + +```javascript +mainWindow.webContents.on('devtools-opened', function() { + mainWindow.webContents.addWorkSpace(__dirname); +}); +``` ### `webContents.removeWorkSpace(path)` @@ -441,13 +521,48 @@ Adds the specified path to DevTools workspace. Removes the specified path from DevTools workspace. -### `webContents.send(channel[, args...])` +### `webContents.openDevTools([options])` + +* `options` Object (optional). Properties: + * `detach` Boolean - opens DevTools in a new window + +Opens the developer tools. + +### `webContents.closeDevTools()` + +Closes the developer tools. + +### `webContents.isDevToolsOpened()` + +Returns whether the developer tools are opened. + +### `webContents.toggleDevTools()` + +Toggles the developer tools. + +### `webContents.isDevToolsFocused()` + +Returns whether the developer tools is focused. + +### `webContents.inspectElement(x, y)` + +* `x` Integer +* `y` Integer + +Starts inspecting element at position (`x`, `y`). + +### `webContents.inspectServiceWorker()` + +Opens the developer tools for the service worker context. + +### `webContents.send(channel[, arg1][, arg2][, ...])` * `channel` String -* `args...` (optional) +* `arg` (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. +Send an asynchronous message to renderer process via `channel`, you can also +send arbitrary arguments. The renderer process can handle the message by +listening to the `channel` event with the `ipcRenderer` module. An example of sending messages from the main process to the renderer process: @@ -456,7 +571,7 @@ An example of sending messages from the main process to the renderer process: var window = null; app.on('ready', function() { window = new BrowserWindow({width: 800, height: 600}); - window.loadUrl('file://' + __dirname + '/index.html'); + window.loadURL('file://' + __dirname + '/index.html'); window.webContents.on('did-finish-load', function() { window.webContents.send('ping', 'whoooooooh!'); }); @@ -468,7 +583,7 @@ app.on('ready', function() { @@ -476,13 +591,6 @@ app.on('ready', function() { ``` -**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: @@ -523,7 +631,7 @@ Disable device emulation enabled by `webContents.enableDeviceEmulation`. * `event` Object * `type` String (**required**) - The type of the event, can be `mouseDown`, `mouseUp`, `mouseEnter`, `mouseLeave`, `contextMenu`, `mouseWheel`, - `keyDown`, `keyUp`, `char`. + `mouseMove`, `keyDown`, `keyUp`, `char`. * `modifiers` Array - An array of modifiers of the event, can include `shift`, `control`, `alt`, `meta`, `isKeypad`, `isAutoRepeat`, `leftButtonDown`, `middleButtonDown`, `rightButtonDown`, `capsLock`, @@ -533,14 +641,17 @@ 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 `=`. +* `keyCode` Char or String (**required**) - The character that will be sent + as the keyboard event. Can be a single UTF-8 character, or the name of the + key that generates the event. Accepted key names are `enter`, `backspace`, + `delete`, `tab`, `escape`, `control`, `alt`, `shift`, `end`, `home`, `insert`, + `left`, `up`, `right`, `down`, `pageUp`, `pageDown`, `printScreen` For mouse events, the `event` object also have following properties: * `x` Integer (**required**) * `y` Integer (**required**) +* `button` String - The button pressed, can be `left`, `middle`, `right` * `globalX` Integer * `globalY` Integer * `movementX` Integer @@ -565,9 +676,50 @@ For the `mouseWheel` event, the `event` object also have following properties: 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. +The `frameBuffer` is a `Buffer` that contains raw pixel data. On most machines, +the pixel data is effectively stored in 32bit BGRA format, but the actual +representation depends on the endianness of the processor (most modern +processors are little-endian, on machines with big-endian processors the data +is in 32bit ARGB format). ### `webContents.endFrameSubscription()` End subscribing for frame presentation events. + +### `webContents.savePage(fullPath, saveType, callback)` + +* `fullPath` String - The full file path. +* `saveType` String - Specify the save type. + * `HTMLOnly` - Save only the HTML of the page. + * `HTMLComplete` - Save complete-html page. + * `MHTML` - Save complete-html page as MHTML. +* `callback` Function - `function(error) {}`. + * `error` Error + +Returns true if the process of saving page has been initiated successfully. + +```javascript +win.loadURL('https://github.com'); + +win.webContents.on('did-finish-load', function() { + win.webContents.savePage('/tmp/test.html', 'HTMLComplete', function(error) { + if (!error) + console.log("Save page successfully"); + }); +}); +``` + +## Instance Properties + +`WebContents` objects also have the following properties: + +### `webContents.session` + +Returns the [session](session.md) object used by this webContents. + +### `webContents.devToolsWebContents` + +Get the `WebContents` of DevTools for this `WebContents`. + +**Note:** Users should never store this object because it may become `null` +when the DevTools has been closed. diff --git a/docs/api/web-frame.md b/docs/api/web-frame.md index 33597543b773..38c5e30db43f 100644 --- a/docs/api/web-frame.md +++ b/docs/api/web-frame.md @@ -6,7 +6,7 @@ web page. An example of zooming current page to 200%. ```javascript -var webFrame = require('web-frame'); +var webFrame = require('electron').webFrame; webFrame.setZoomFactor(2); ``` @@ -59,14 +59,14 @@ whether the word passed is correctly spelled. An example of using [node-spellchecker][spellchecker] as provider: ```javascript -require('web-frame').setSpellCheckProvider("en-US", true, { +webFrame.setSpellCheckProvider("en-US", true, { spellCheck: function(text) { return !(require('spellchecker').isMisspelled(text)); } }); ``` -### `webFrame.registerUrlSchemeAsSecure(scheme)` +### `webFrame.registerURLSchemeAsSecure(scheme)` * `scheme` String @@ -76,14 +76,14 @@ 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)` +### `webFrame.registerURLSchemeAsBypassingCSP(scheme)` * `scheme` String Resources will be loaded from this `scheme` regardless of the current page's Content Security Policy. -### `webFrame.registerUrlSchemeAsPrivileged(scheme)` +### `webFrame.registerURLSchemeAsPrivileged(scheme)` * `scheme` String diff --git a/docs/api/web-view-tag.md b/docs/api/web-view-tag.md index 3fda3a98edb5..e317ef8eab1e 100644 --- a/docs/api/web-view-tag.md +++ b/docs/api/web-view-tag.md @@ -164,13 +164,14 @@ 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()` +### `.getURL()` Returns URL of guest page. @@ -262,7 +263,7 @@ Injects CSS into the guest page. * `code` String * `userGesture` Boolean - Default `false`. -Evaluates `code` in page. If `userGesture` is set, it will the create user +Evaluates `code` in page. If `userGesture` is set, it will create the user gesture context in the page. HTML APIs like `requestFullScreen`, which require user action, can take advantage of this option for automation. @@ -355,15 +356,16 @@ Prints `webview`'s web page. Same with `webContents.print([options])`. Prints webview's web page as PDF, Same with `webContents.printToPDF(options, callback)` -### `.send(channel[, args...])` +### `.send(channel[, arg1][, arg2][, ...])` * `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. +Send an asynchronous message to renderer process via `channel`, you can also +send arbitrary arguments. The renderer process can handle the message by +listening to the `channel` event with the `ipcRenderer` module. -See [WebContents.send](web-contents.md#webcontentssendchannel-args) for +See [webContents.send](web-contents.md#webcontentssendchannel-args) for examples. ### `.sendInputEvent(event)` @@ -372,7 +374,7 @@ examples. Sends an input `event` to the page. -See [WebContents.sendInputEvent](web-contents.md##webcontentssendinputeventevent) +See [webContents.sendInputEvent](web-contents.md##webcontentssendinputeventevent) for detailed description of `event` object. ## DOM events @@ -401,7 +403,7 @@ Returns: * `errorCode` Integer * `errorDescription` String -* `validatedUrl` 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. @@ -427,8 +429,8 @@ Corresponds to the points in time when the spinner of the tab stops spinning. Returns: * `status` Boolean -* `newUrl` String -* `originalUrl` String +* `newURL` String +* `originalURL` String * `httpResponseCode` Integer * `requestMethod` String * `referrer` String @@ -441,8 +443,8 @@ Fired when details regarding a requested resource is available. Returns: -* `oldUrl` String -* `newUrl` String +* `oldURL` String +* `newURL` String * `isMainFrame` Boolean Fired when a redirect was received while requesting a resource. @@ -451,21 +453,21 @@ Fired when a redirect was received while requesting a resource. Fired when document in the given frame is loaded. -### Event: 'page-title-set' +### Event: 'page-title-updated' Returns: * `title` String * `explicitSet` Boolean -Fired when page title is set during navigation. `explicitSet` is false when title is synthesised from file -url. +Fired when page title is set during navigation. `explicitSet` is false when +title is synthesised from file url. ### Event: 'page-favicon-updated' Returns: -* `favicons` Array - Array of Urls. +* `favicons` Array - Array of URLs. Fired when page receives favicon urls. @@ -514,7 +516,7 @@ The following example code opens the new url in system's default browser. ```javascript webview.addEventListener('new-window', function(e) { - require('shell').openExternal(e.url); + require('electron').shell.openExternal(e.url); }); ``` @@ -554,9 +556,9 @@ webview.send('ping'); ```javascript // In guest page. -var ipc = require('ipc'); -ipc.on('ping', function() { - ipc.sendToHost('pong'); +var ipcRenderer = require('electron').ipcRenderer; +ipcRenderer.on('ping', function() { + ipcRenderer.sendToHost('pong'); }); ``` diff --git a/docs/development/atom-shell-vs-node-webkit.md b/docs/development/atom-shell-vs-node-webkit.md index c1fffa304ab5..76fa5d57d289 100644 --- a/docs/development/atom-shell-vs-node-webkit.md +++ b/docs/development/atom-shell-vs-node-webkit.md @@ -35,7 +35,7 @@ __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 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. +[`node_bindings`][node-bindings] code for how that was done. __4. Multi-context__ @@ -46,3 +46,5 @@ of how NW.js was implemented. By using the [multi-context](http://strongloop.com/strongblog/whats-new-node-js-v0-12-multiple-context-execution/) feature of Node, Electron doesn't introduce a new JavaScript context in web pages. + +[node-bindings]: https://github.com/atom/electron/tree/master/atom/common diff --git a/docs/styleguide.md b/docs/styleguide.md index 77c90467ca93..b471c19fba52 100644 --- a/docs/styleguide.md +++ b/docs/styleguide.md @@ -65,7 +65,11 @@ 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) +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 diff --git a/docs/tutorial/application-distribution.md b/docs/tutorial/application-distribution.md index 4f1f5202a008..d65bc08ca87f 100644 --- a/docs/tutorial/application-distribution.md +++ b/docs/tutorial/application-distribution.md @@ -35,7 +35,7 @@ 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 -below, and Electron will then try read the archive and start from it. +below, and Electron will then try to read the archive and start from it. On OS X: diff --git a/docs/tutorial/application-packaging.md b/docs/tutorial/application-packaging.md index 0cf3a6b596f4..b42a2f9298ba 100644 --- a/docs/tutorial/application-packaging.md +++ b/docs/tutorial/application-packaging.md @@ -51,14 +51,14 @@ $ asar list /path/to/example.asar Read a file in the `asar` archive: ```javascript -var fs = require('fs'); +const fs = require('fs'); fs.readFileSync('/path/to/example.asar/file.txt'); ``` List all files under the root of the archive: ```javascript -var fs = require('fs'); +const fs = require('fs'); fs.readdirSync('/path/to/example.asar'); ``` @@ -71,9 +71,9 @@ require('/path/to/example.asar/dir/module.js'); You can also display a web page in an `asar` archive with `BrowserWindow`: ```javascript -var BrowserWindow = require('browser-window'); +const BrowserWindow = require('electron').BrowserWindow; var win = new BrowserWindow({width: 800, height: 600}); -win.loadUrl('file:///path/to/example.asar/static/index.html'); +win.loadURL('file:///path/to/example.asar/static/index.html'); ``` ### Web API @@ -103,6 +103,14 @@ var originalFs = require('original-fs'); originalFs.readFileSync('/path/to/example.asar'); ``` +You can also set `process.noAsar` to `true` to disable the support for `asar` in +the `fs` module: + +```javascript +process.noAsar = true; +fs.readFileSync('/path/to/example.asar'); +``` + ## Limitations on Node API Even though we tried hard to make `asar` archives in the Node API work like @@ -132,6 +140,7 @@ work. This adds a little overhead for those APIs. APIs that requires extra unpacking are: * `child_process.execFile` +* `child_process.execFileSync` * `fs.open` * `fs.openSync` * `process.dlopen` - Used by `require` on native modules @@ -143,6 +152,17 @@ 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. +### Executing Binaries Inside `asar` Archive + +There are Node APIs that can execute binaries like `child_process.exec`, +`child_process.spawn` and `child_process.execFile`, but only `execFile` is +supported to execute binaries inside `asar` archive. + +This is because `exec` and `spawn` accept `command` instead of `file` as input, +and `command`s are executed under shell. There is no reliable way to determine +whether a command uses a file in asar archive, and even if we do, we can not be +sure whether we can replace the path in command without side effects. + ## Adding Unpacked Files in `asar` Archive As stated above, some Node APIs will unpack the file to filesystem when diff --git a/docs/tutorial/debugging-main-process.md b/docs/tutorial/debugging-main-process.md index 38c6e61ffcde..aa95ae312b9a 100644 --- a/docs/tutorial/debugging-main-process.md +++ b/docs/tutorial/debugging-main-process.md @@ -19,7 +19,7 @@ Like `--debug` but pauses the script on the first line. ## Use node-inspector for Debugging -__Note:__ Electron uses node v0.11.13, which currently doesn't work very well +__Note:__ Electron doesn't currently work very well with node-inspector, and the main process will crash if you inspect the `process` object under node-inspector's console. diff --git a/docs/tutorial/desktop-environment-integration.md b/docs/tutorial/desktop-environment-integration.md index 3132edffcc27..6e570ee71f92 100644 --- a/docs/tutorial/desktop-environment-integration.md +++ b/docs/tutorial/desktop-environment-integration.md @@ -8,6 +8,67 @@ applications can put a custom menu in the dock menu. This guide explains how to integrate your application into those desktop environments with Electron APIs. +## Notifications (Windows, Linux, OS X) + +All three operating systems provide means for applications to send notifications +to the user. Electron conveniently allows developers to send notifications with +the [HTML5 Notification API](https://notifications.spec.whatwg.org/), using +the currently running operating system's native notification APIs to display it. + +```javascript +var myNotification = new Notification('Title', { + body: 'Lorem Ipsum Dolor Sit Amet' +}); + +myNotification.onclick = function () { + console.log('Notification clicked') +} +``` + +While code and user experience across operating systems are similar, but there +are fine differences. + +### Windows + +* On Windows 10, notifications "just work". +* On Windows 8.1 and Windows 8, a shortcut to your app, with a [Application User +Model ID][app-user-model-id], must be installed to the Start screen. Note, +however, that it does not need to be pinned to the Start screen. +* On Windows 7 and below, notifications are not supported. You can however send +"balloon notifications" using the [Tray API](tray-balloon). + +To use an image in your notification, pass a local image file (preferably `png`) +in the `icon` property of your notification's options. The notification will +still display if you submit and incorrect or `http/https`-based URL, but the +image will not be displayed. + +```javascript +new Notification('Title', { + body: 'Notification with icon', + icon: 'file:///C:/Users/feriese/Desktop/icon.png' +}); +``` + +Keep furthermore in mind that the maximum length for the body is 250 characters, +with the Windows team recommending that notifications should be kept to 200 +characters. + +### Linux + +Notifications are sent using `libnotify`, it can show notifications on any +desktop environment that follows [Desktop Notifications +Specification][notification-spec], including Cinnamon, Enlightenment, Unity, +GNOME, KDE. + +### OS X + +Notifications are straight-forward on OS X, you should however be aware of +[Apple's Human Interface guidelines regarding +notifications](https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/OSXHIGuidelines/NotificationCenter.html). + +Note that notifications are limited to 256 bytes in size - and will be truncated +if you exceed that limit. + ## Recent documents (Windows & OS X) Windows and OS X provide easy access to a list of recent documents opened by @@ -25,7 +86,6 @@ To add a file to recent documents, you can use the [app.addRecentDocument][addrecentdocument] API: ```javascript -var app = require('app'); app.addRecentDocument('/Users/USERNAME/Desktop/work.type'); ``` @@ -43,7 +103,8 @@ 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 the JumpList, a new instance of your application will be started with the path of the file added as a command line argument. +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 @@ -63,8 +124,10 @@ To set your custom dock menu, you can use the `app.dock.setMenu` API, which is only available on OS X: ```javascript -var app = require('app'); -var Menu = require('menu'); +const electron = require('electron'); +const app = electron.app; +const Menu = electron.Menu; + var dockMenu = Menu.buildFromTemplate([ { label: 'New Window', click: function() { console.log('New Window'); } }, { label: 'New Window with Settings', submenu: [ @@ -110,7 +173,6 @@ To set user tasks for your application, you can use [app.setUserTasks][setusertaskstasks] API: ```javascript -var app = require('app'); app.setUserTasks([ { program: process.execPath, @@ -154,12 +216,13 @@ __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: +You can use [BrowserWindow.setThumbarButtons][setthumbarbuttons] to set +thumbnail toolbar in your application: + +```javascript +const BrowserWindow = require('electron').BrowserWindow; +const path = require('path'); -``` -var BrowserWindow = require('browser-window'); -var path = require('path'); var win = new BrowserWindow({ width: 800, height: 600 @@ -188,8 +251,8 @@ 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]. +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:__ @@ -223,7 +286,7 @@ window.setProgressBar(0.5); ## Represented File of Window (OS X) On OS X a window can set its represented file, so the file's icon can show in -the 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 title a path popup will show. You can also set the edited state of a window so that the file icon can indicate @@ -252,3 +315,6 @@ window.setDocumentEdited(true); [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 +[tray-balloon]: ../api/tray.md#traydisplayballoonoptions-windows +[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx +[notification-spec]: https://developer.gnome.org/notification-spec/ diff --git a/docs/tutorial/devtools-extension.md b/docs/tutorial/devtools-extension.md index e9466f14b648..7c7ea7d64a24 100644 --- a/docs/tutorial/devtools-extension.md +++ b/docs/tutorial/devtools-extension.md @@ -8,6 +8,8 @@ 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. +** NOTE: React DevTools does not work, follow the issue here https://github.com/atom/electron/issues/915 ** + For example, to use the [React DevTools Extension](https://github.com/facebook/react-devtools) , first you need to download its source code: @@ -16,18 +18,21 @@ $ cd /some-directory $ git clone --recursive https://github.com/facebook/react-devtools.git ``` +Follow the instructions in [`react-devtools/shells/chrome/Readme.md`](https://github.com/facebook/react-devtools/blob/master/shells/chrome/Readme.md) to build the extension. + 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'); +const BrowserWindow = require('electron').remote.BrowserWindow; +BrowserWindow.addDevToolsExtension('/some-directory/react-devtools/shells/chrome'); ``` 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'); +BrowserWindow.removeDevToolsExtension('React Developer Tools'); ``` ## Format of DevTools Extension diff --git a/docs/tutorial/mac-app-store-submission-guide.md b/docs/tutorial/mac-app-store-submission-guide.md new file mode 100644 index 000000000000..036676fe853a --- /dev/null +++ b/docs/tutorial/mac-app-store-submission-guide.md @@ -0,0 +1,120 @@ +# Mac App Store Submission Guide + +Since v0.34.0, Electron allows submitting packaged apps to the Mac App Store +(MAS). This guide provides information on: how to submit your app and the +limitations of the MAS build. + +__Note:__ Submitting an app to Mac App Store requires enrolling [Apple Developer +Program][developer-program], which costs money. + +## How to Submit Your App + +The following steps introduce a simple way to submit your app to Mac App Store. +However, these steps do not ensure your app will be approved by Apple; you +still need to read Apple's [Submitting Your App][submitting-your-app] guide on +how to meet the Mac App Store requirements. + +### Get Certificate + +To submit your app to the Mac App Store, you first must get a certificate from +Apple. You can follow these [existing guides][nwjs-guide] on web. + +### Sign Your App + +After getting the certificate from Apple, you can package your app by following +[Application Distribution](application-distribution.md), and then proceed to +signing your app. This step is basically the same with other programs, but the +key is to sign every dependency of Electron one by one. + +First, you need to prepare two entitlements files. + +`child.plist`: + +```xml + + + + + com.apple.security.app-sandbox + + com.apple.security.inherit + + + +``` + +`parent.plist`: + +```xml + + + + + com.apple.security.app-sandbox + + + +``` + +And then sign your app with the following script: + +```bash +#!/bin/bash + +# Name of your app. +APP="YourApp" +# The path of you app to sign. +APP_PATH="/path/to/YouApp.app" +# The path to the location you want to put the signed package. +RESULT_PATH="~/Desktop/$APP.pkg" +# The name of certificates you requested. +APP_KEY="3rd Party Mac Developer Application: Company Name (APPIDENTITY)" +INSTALLER_KEY="3rd Party Mac Developer Installer: Company Name (APPIDENTITY)" + +FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks" + +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Libraries/libnode.dylib" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Electron Framework" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper.app/" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper EH.app/" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper NP.app/" +codesign -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH" +productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH" +``` + +If you are new to app sandboxing under OS X, you should also read through +Apple's [Enabling App Sandbox][enable-app-sandbox] to have a basic idea, then +add keys for the permissions needed by your app to the entitlements files. + +### Upload Your App and Submit for Review + +After signing your app, you can use Application Loader to upload it to iTunes +Connect for processing, making sure you have [created a record][create-record] +before uploading. Then you can [submit your app for review][submit-for-review]. + +## Limitations of MAS Build + +In order to satisfy all requirements for app sandboxing, the following modules +have been disabled in the MAS build: + +* `crash-reporter` +* `auto-updater` + +and the following behaviors have been changed: + +* Video capture may not work for some machines. +* Certain accessibility features may not work. +* Apps will not be aware of DNS changes. + +Also, due to the usage of app sandboxing, the resources which can be accessed by + the app are strictly limited; you can read [App Sandboxing][app-sandboxing] for + more information. + +[developer-program]: https://developer.apple.com/support/compare-memberships/ +[submitting-your-app]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourApp/SubmittingYourApp.html +[nwjs-guide]: https://github.com/nwjs/nw.js/wiki/Mac-App-Store-%28MAS%29-Submission-Guideline#first-steps +[enable-app-sandbox]: https://developer.apple.com/library/ios/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html +[create-record]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/CreatingiTunesConnectRecord.html +[submit-for-review]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SubmittingTheApp.html +[app-sandboxing]: https://developer.apple.com/app-sandboxing/ diff --git a/docs/tutorial/online-offline-events.md b/docs/tutorial/online-offline-events.md index 88f9a32f2ec6..d143118e0158 100644 --- a/docs/tutorial/online-offline-events.md +++ b/docs/tutorial/online-offline-events.md @@ -6,13 +6,14 @@ using standard HTML5 APIs, as shown in the following example. _main.js_ ```javascript -var app = require('app'); -var BrowserWindow = require('browser-window'); -var onlineStatusWindow; +const electron = require('electron'); +const app = electron.app; +const BrowserWindow = electron.BrowserWindow; +var onlineStatusWindow; app.on('ready', function() { onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); + onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); }); ``` @@ -21,18 +22,18 @@ _online-status.html_ ```html - - - + alertOnlineStatus(); + + ``` @@ -45,17 +46,18 @@ to the main process and handled as needed, as shown in the following example. _main.js_ ```javascript -var app = require('app'); -var ipc = require('ipc'); -var BrowserWindow = require('browser-window'); -var onlineStatusWindow; +const electron = require('electron'); +const app = electron.app; +const ipcMain = electron.ipcMain; +const BrowserWindow = electron.BrowserWindow; +var onlineStatusWindow; app.on('ready', function() { onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); + onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); }); -ipc.on('online-status-changed', function(event, status) { +ipcMain.on('online-status-changed', function(event, status) { console.log(status); }); ``` @@ -65,18 +67,18 @@ _online-status.html_ ```html - - - + updateOnlineStatus(); + + ``` diff --git a/docs/tutorial/quick-start.md b/docs/tutorial/quick-start.md index 4ce65a1dc1ae..4c61413436dd 100644 --- a/docs/tutorial/quick-start.md +++ b/docs/tutorial/quick-start.md @@ -2,7 +2,7 @@ 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 +as a variant of the Node.js runtime that is focused on desktop applications instead of web servers. This doesn't mean Electron is a JavaScript binding to graphical user interface @@ -22,8 +22,9 @@ 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 -io.js APIs in web pages allowing lower level operating system interactions. +allowed access to native resources. Electron users, however, have the power to +use Node.js APIs in web pages allowing lower level operating system +interactions. ### Differences Between Main Process and Renderer Process @@ -77,11 +78,12 @@ The `main.js` should create windows and handle system events, a typical example being: ```javascript -var app = require('app'); // Module to control application life. -var BrowserWindow = require('browser-window'); // Module to create native browser window. +const electron = require('electron'); +const app = electron.app; // Module to control application life. +const BrowserWindow = electron.BrowserWindow; // Module to create native browser window. // Report crashes to our server. -require('crash-reporter').start(); +electron.crashReporter.start(); // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. @@ -103,10 +105,10 @@ app.on('ready', function() { mainWindow = new BrowserWindow({width: 800, height: 600}); // and load the index.html of the app. - mainWindow.loadUrl('file://' + __dirname + '/index.html'); + mainWindow.loadURL('file://' + __dirname + '/index.html'); // Open the DevTools. - mainWindow.openDevTools(); + mainWindow.webContents.openDevTools(); // Emitted when the window is closed. mainWindow.on('closed', function() { @@ -129,8 +131,9 @@ Finally the `index.html` is the web page you want to show:

Hello World!

- We are using io.js - and Electron . + We are using node , + Chrome , + and Electron . ``` @@ -143,8 +146,8 @@ working as expected. ### electron-prebuilt -If you've installed `electron-prebuilt` globally with `npm`, then you need only -run the following in your app's source directory: +If you've installed `electron-prebuilt` globally with `npm`, then you will only need +to run the following in your app's source directory: ```bash electron . @@ -158,7 +161,7 @@ If you've installed it locally, then run: ### Manually Downloaded Electron Binary -If you downloaded Electron manually, you can also just use the included +If you downloaded Electron manually, you can also use the included binary to execute your app directly. #### Windows @@ -187,3 +190,19 @@ it from [here](https://github.com/atom/electron/releases). After you're done writing your app, you can create a distribution by following the [Application Distribution](./application-distribution.md) guide and then executing the packaged app. + +### Try this Example + +Clone and run the code in this tutorial by using the [`atom/electron-quick-start`](https://github.com/atom/electron-quick-start) +repository. + +**Note**: Running this requires [Git](https://git-scm.com) and [Node.js](https://nodejs.org/en/download/) (which includes [npm](https://npmjs.org)) on your system. + +```bash +# Clone the repository +$ git clone https://github.com/atom/electron-quick-start +# Go into the repository +$ cd electron-quick-start +# Install dependencies and run the app +$ npm install && npm start +``` diff --git a/docs/tutorial/supported-platforms.md b/docs/tutorial/supported-platforms.md new file mode 100644 index 000000000000..9d1a293d5f5d --- /dev/null +++ b/docs/tutorial/supported-platforms.md @@ -0,0 +1,31 @@ +# Supported Platforms + +Following platforms are supported by Electron: + +### OS X + +Only 64bit binaries are provided for OS X, and the minimum OS X version +supported is OS X 10.8. + +### Windows + +Windows 7 and later are supported, older operating systems are not supported +(and do not work). + +Both `x86` and `amd64` (x64) binaries are provided for Windows. Please note, the +`ARM` version of Windows is not supported for now. + +### Linux + +The prebuilt `ia32`(`i686`) and `x64`(`amd64`) binaries of Electron are built on +Ubuntu 12.04, the `arm` binary is built against ARM v7 with hard-float ABI and +NEON for Debian Wheezy. + +Whether the prebuilt binary can run on a distribution depends on whether the +distribution includes the libraries that Electron is linked to on the building +platform, so only Ubuntu 12.04 is guaranteed to work, but following platforms +are also verified to be able to run the prebuilt binaries of Electron: + +* Ubuntu 12.04 and later +* Fedora 21 +* Debian 8 diff --git a/docs/tutorial/using-native-node-modules.md b/docs/tutorial/using-native-node-modules.md index 0e6477fc4c04..2defedd74183 100644 --- a/docs/tutorial/using-native-node-modules.md +++ b/docs/tutorial/using-native-node-modules.md @@ -6,16 +6,17 @@ the location of Electron's headers when building native modules. ## 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 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. +Native modules might break when Node starts using a new version of V8. +To make sure the module you're interested in will work with Electron, you should +check if it supports the internal Node version used by Electron. +You can check what version of Node is used in Electron by looking it up in +the [releases](https://github.com/atom/electron/releases) page or by using +`process.version` (see [Quick Start](https://github.com/atom/electron/blob/master/docs/tutorial/quick-start.md) +for example). -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 or later -versions of Node or io.js. +Consider using [NAN](https://github.com/nodejs/nan/) for your own modules, since +it makes it easier to support multiple versions of Node. It's also helpful for +porting old modules to newer versions of Node so they can work with Electron. ## How to Install Native Modules @@ -30,8 +31,11 @@ which handles the manual steps of downloading headers and building native module ```sh npm install --save-dev electron-rebuild -# Every time you run npm install, run this -node ./node_modules/.bin/electron-rebuild +# Every time you run "npm install", run this +./node_modules/.bin/electron-rebuild + +# On Windows if you have trouble, try: +.\node_modules\.bin\electron-rebuild.cmd ``` ### The npm Way diff --git a/docs/tutorial/using-pepper-flash-plugin.md b/docs/tutorial/using-pepper-flash-plugin.md index 5c8820c2fad4..a9918b220ac0 100644 --- a/docs/tutorial/using-pepper-flash-plugin.md +++ b/docs/tutorial/using-pepper-flash-plugin.md @@ -19,26 +19,9 @@ 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 OS X, /path/to/PepperFlashPlayer.plugin // On Linux, /path/to/libpepflashplayer.so app.commandLine.appendSwitch('ppapi-flash-path', '/path/to/libpepflashplayer.so'); @@ -53,7 +36,7 @@ app.on('ready', function() { 'plugins': true } }); - mainWindow.loadUrl('file://' + __dirname + '/index.html'); + mainWindow.loadURL('file://' + __dirname + '/index.html'); // Something else }); ``` diff --git a/docs/tutorial/using-selenium-and-webdriver.md b/docs/tutorial/using-selenium-and-webdriver.md index ebc6d2fa7801..035dabdfe79f 100644 --- a/docs/tutorial/using-selenium-and-webdriver.md +++ b/docs/tutorial/using-selenium-and-webdriver.md @@ -41,14 +41,17 @@ upstream, except that you have to manually specify how to connect chrome driver and where to find Electron's binary: ```javascript -var webdriver = require('selenium-webdriver'); +const webdriver = require('selenium-webdriver'); var driver = new webdriver.Builder() // The "9515" is the port opened by chrome driver. .usingServer('http://localhost:9515') - .withCapabilities({chromeOptions: { - // Here is the path to your Electron binary. - binary: '/Path-to-Your-App.app/Contents/MacOS/Atom'}}) + .withCapabilities({ + chromeOptions: { + // Here is the path to your Electron binary. + binary: '/Path-to-Your-App.app/Contents/MacOS/Atom', + } + }) .forBrowser('electron') .build(); @@ -74,7 +77,7 @@ driver. First you need to download the `chromedriver` binary, and run it: ```bash -$ chromedriver --url-base=/wd/hub --port=9515 +$ chromedriver --url-base=wd/hub --port=9515 Starting ChromeDriver (v2.10.291558) on port 9515 Only local connections are allowed. ``` @@ -90,13 +93,16 @@ $ npm install webdriverio ### 3. Connect to chrome driver ```javascript -var webdriverio = require('webdriverio'); +const webdriverio = require('webdriverio'); var options = { host: "localhost", // Use localhost as chrome driver server port: 9515, // "9515" is the port opened by chrome driver. desiredCapabilities: { browserName: 'chrome', - chromeOptions: {binary: '/Path-to-Your-App.app/Electron'} // Path to your Electron binary. + chromeOptions: { + binary: '/Path-to-Your-App/electron', // Path to your Electron binary. + args: [/* cli arguments */] // Optional, perhaps 'app=' + /path/to/your/app/ + } } }; @@ -119,4 +125,8 @@ 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. +Alternatively, pass an argument to run with your electron binary that points to +your app's folder. This eliminates the need to copy-paste your app into +Electron's resource directory. + [chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ diff --git a/filenames.gypi b/filenames.gypi index 46e06bebd183..aefe493335f1 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -9,19 +9,24 @@ ], 'coffee_sources': [ 'atom/browser/api/lib/app.coffee', - 'atom/browser/api/lib/atom-delegate.coffee', 'atom/browser/api/lib/auto-updater.coffee', + 'atom/browser/api/lib/auto-updater/auto-updater-native.coffee', + 'atom/browser/api/lib/auto-updater/auto-updater-win.coffee', + 'atom/browser/api/lib/auto-updater/squirrel-update-win.coffee', 'atom/browser/api/lib/browser-window.coffee', 'atom/browser/api/lib/content-tracing.coffee', 'atom/browser/api/lib/dialog.coffee', + 'atom/browser/api/lib/exports/electron.coffee', 'atom/browser/api/lib/global-shortcut.coffee', 'atom/browser/api/lib/ipc.coffee', + 'atom/browser/api/lib/ipc-main.coffee', 'atom/browser/api/lib/menu.coffee', '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/session.coffee', 'atom/browser/api/lib/screen.coffee', 'atom/browser/api/lib/tray.coffee', 'atom/browser/api/lib/web-contents.coffee', @@ -35,9 +40,12 @@ 'atom/common/api/lib/callbacks-registry.coffee', 'atom/common/api/lib/clipboard.coffee', 'atom/common/api/lib/crash-reporter.coffee', + 'atom/common/api/lib/deprecate.coffee', + 'atom/common/api/lib/exports/electron.coffee', 'atom/common/api/lib/native-image.coffee', 'atom/common/api/lib/shell.coffee', 'atom/common/lib/init.coffee', + 'atom/common/lib/reset-search-paths.coffee', 'atom/renderer/lib/chrome-api.coffee', 'atom/renderer/lib/init.coffee', 'atom/renderer/lib/inspector.coffee', @@ -47,7 +55,9 @@ 'atom/renderer/lib/web-view/web-view-attributes.coffee', 'atom/renderer/lib/web-view/web-view-constants.coffee', 'atom/renderer/api/lib/desktop-capturer.coffee', + 'atom/renderer/api/lib/exports/electron.coffee', 'atom/renderer/api/lib/ipc.coffee', + 'atom/renderer/api/lib/ipc-renderer.coffee', 'atom/renderer/api/lib/remote.coffee', 'atom/renderer/api/lib/screen.coffee', 'atom/renderer/api/lib/web-frame.coffee', @@ -111,12 +121,11 @@ 'atom/browser/api/trackable_object.h', 'atom/browser/api/frame_subscriber.cc', 'atom/browser/api/frame_subscriber.h', + 'atom/browser/api/save_page_handler.cc', + 'atom/browser/api/save_page_handler.h', 'atom/browser/auto_updater.cc', 'atom/browser/auto_updater.h', - 'atom/browser/auto_updater_delegate.h', - 'atom/browser/auto_updater_linux.cc', 'atom/browser/auto_updater_mac.mm', - 'atom/browser/auto_updater_win.cc', 'atom/browser/atom_access_token_store.cc', 'atom/browser/atom_access_token_store.h', 'atom/browser/atom_browser_client.cc', @@ -127,16 +136,16 @@ '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_browser_main_parts_posix.cc', 'atom/browser/atom_javascript_dialog_manager.cc', 'atom/browser/atom_javascript_dialog_manager.h', 'atom/browser/atom_quota_permission_context.cc', 'atom/browser/atom_quota_permission_context.h', + 'atom/browser/atom_resource_dispatcher_host_delegate.cc', + 'atom/browser/atom_resource_dispatcher_host_delegate.h', 'atom/browser/atom_speech_recognition_manager_delegate.cc', 'atom/browser/atom_speech_recognition_manager_delegate.h', - 'atom/browser/atom_ssl_config_service.cc', - 'atom/browser/atom_ssl_config_service.h', 'atom/browser/bridge_task_runner.cc', 'atom/browser/bridge_task_runner.h', 'atom/browser/browser.cc', @@ -149,12 +158,15 @@ 'atom/browser/common_web_contents_delegate.h', 'atom/browser/javascript_environment.cc', 'atom/browser/javascript_environment.h', + 'atom/browser/login_handler.cc', + 'atom/browser/login_handler.h', 'atom/browser/mac/atom_application.h', 'atom/browser/mac/atom_application.mm', 'atom/browser/mac/atom_application_delegate.h', 'atom/browser/mac/atom_application_delegate.mm', 'atom/browser/native_window.cc', 'atom/browser/native_window.h', + 'atom/browser/native_window_views_win.cc', 'atom/browser/native_window_views.cc', 'atom/browser/native_window_views.h', 'atom/browser/native_window_mac.h', @@ -164,6 +176,10 @@ '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_cert_verifier.cc', + 'atom/browser/net/atom_cert_verifier.h', + 'atom/browser/net/atom_ssl_config_service.cc', + 'atom/browser/net/atom_ssl_config_service.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', @@ -188,8 +204,6 @@ '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', - 'atom/browser/ui/cocoa/event_processing_window.mm', 'atom/browser/ui/file_dialog.h', 'atom/browser/ui/file_dialog_gtk.cc', 'atom/browser/ui/file_dialog_mac.mm', @@ -251,6 +265,8 @@ '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', @@ -272,6 +288,8 @@ 'atom/common/asar/scoped_temporary_file.h', 'atom/common/atom_command_line.cc', 'atom/common/atom_command_line.h', + 'atom/common/atom_constants.cc', + 'atom/common/atom_constants.h', 'atom/common/common_message_generator.cc', 'atom/common/common_message_generator.h', 'atom/common/crash_reporter/crash_reporter.cc', @@ -300,13 +318,18 @@ '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.cc', 'atom/common/native_mate_converters/callback.h', + 'atom/common/native_mate_converters/content_converter.cc', + 'atom/common/native_mate_converters/content_converter.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', 'atom/common/native_mate_converters/gurl_converter.h', 'atom/common/native_mate_converters/image_converter.cc', 'atom/common/native_mate_converters/image_converter.h', + 'atom/common/native_mate_converters/net_converter.cc', + 'atom/common/native_mate_converters/net_converter.h', 'atom/common/native_mate_converters/string16_converter.h', 'atom/common/native_mate_converters/v8_value_converter.cc', 'atom/common/native_mate_converters/v8_value_converter.h', @@ -344,6 +367,8 @@ 'atom/utility/atom_content_utility_client.h', 'chromium_src/chrome/browser/browser_process.cc', 'chromium_src/chrome/browser/browser_process.h', + 'chromium_src/chrome/browser/chrome_process_finder_win.cc', + 'chromium_src/chrome/browser/chrome_process_finder_win.h', 'chromium_src/chrome/browser/chrome_notification_types.h', 'chromium_src/chrome/browser/extensions/global_shortcut_listener.cc', 'chromium_src/chrome/browser/extensions/global_shortcut_listener.h', @@ -376,6 +401,9 @@ '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/process_singleton_posix.cc', + 'chromium_src/chrome/browser/process_singleton_win.cc', + 'chromium_src/chrome/browser/process_singleton.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', @@ -402,6 +430,14 @@ 'chromium_src/chrome/browser/ui/views/color_chooser_aura.h', '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_constants.cc', + 'chromium_src/chrome/common/chrome_constants.h', + 'chromium_src/chrome/common/chrome_paths.cc', + 'chromium_src/chrome/common/chrome_paths.h', + 'chromium_src/chrome/common/chrome_paths_internal.h', + 'chromium_src/chrome/common/chrome_paths_linux.cc', + 'chromium_src/chrome/common/chrome_paths_mac.mm', + 'chromium_src/chrome/common/chrome_paths_win.cc', 'chromium_src/chrome/common/chrome_utility_messages.h', 'chromium_src/chrome/common/pref_names.cc', 'chromium_src/chrome/common/pref_names.h', @@ -434,6 +470,8 @@ 'chromium_src/chrome/renderer/tts_dispatcher.cc', 'chromium_src/chrome/renderer/tts_dispatcher.h', 'chromium_src/chrome/utility/utility_message_handler.h', + 'chromium_src/extensions/browser/app_window/size_constraints.cc', + 'chromium_src/extensions/browser/app_window/size_constraints.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', diff --git a/package.json b/package.json index 101fdeee257a..a5d56e3a9908 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,13 @@ { "name": "electron", "devDependencies": { - "asar": "0.8.x", + "asar": "^0.8.0", "coffee-script": "^1.9.2", "coffeelint": "^1.9.4", - "request": "*", - "runas": "3.x" + "request": "*" + }, + "optionalDependencies": { + "runas": "^3.0.0" }, "private": true, "scripts": { diff --git a/script/bootstrap.py b/script/bootstrap.py index d5ad41a61c59..6eaf635bfd86 100755 --- a/script/bootstrap.py +++ b/script/bootstrap.py @@ -39,7 +39,9 @@ def main(): update_submodules() setup_python_libs() update_node_modules('.') - bootstrap_brightray(args.dev, args.url, args.target_arch) + bootstrap_brightray(args.dev, args.url, args.target_arch, + args.libcc_source_path, args.libcc_shared_library_path, + args.libcc_static_library_path) if args.target_arch in ['arm', 'ia32'] and PLATFORM == 'linux': download_sysroot(args.target_arch) @@ -69,6 +71,14 @@ def parse_args(): 'prompts.') parser.add_argument('--target_arch', default=get_target_arch(), help='Manually specify the arch to build for') + parser.add_argument('--libcc_source_path', required=False, + help='The source path of libchromiumcontent. ' \ + 'NOTE: All options of libchromiumcontent are ' \ + 'required OR let electron choose it') + parser.add_argument('--libcc_shared_library_path', required=False, + help='The shared library path of libchromiumcontent.') + parser.add_argument('--libcc_static_library_path', required=False, + help='The static library path of libchromiumcontent.') return parser.parse_args() @@ -91,15 +101,23 @@ def setup_python_libs(): execute_stdout([sys.executable, 'setup.py', 'build']) -def bootstrap_brightray(is_dev, url, target_arch): +def bootstrap_brightray(is_dev, url, target_arch, libcc_source_path, + libcc_shared_library_path, + libcc_static_library_path): bootstrap = os.path.join(VENDOR_DIR, 'brightray', 'script', 'bootstrap') args = [ '--commit', LIBCHROMIUMCONTENT_COMMIT, '--target_arch', target_arch, - url, + url ] if is_dev: args = ['--dev'] + args + if (libcc_source_path != None and + libcc_shared_library_path != None and + libcc_static_library_path != None): + args += ['--libcc_source_path', libcc_source_path, + '--libcc_shared_library_path', libcc_shared_library_path, + '--libcc_static_library_path', libcc_static_library_path] execute_stdout([sys.executable, bootstrap] + args) @@ -178,7 +196,7 @@ def create_chrome_version_h(): def touch_config_gypi(): config_gypi = os.path.join(SOURCE_ROOT, 'vendor', 'node', 'config.gypi') with open(config_gypi, 'w+') as f: - content = '\n{}' + content = "\n{'variables':{}}" if f.read() != content: f.write(content) diff --git a/script/bump-version.py b/script/bump-version.py index f910ae3ef95f..3ee0b23df3b8 100755 --- a/script/bump-version.py +++ b/script/bump-version.py @@ -106,10 +106,11 @@ def update_info_plist(version): line = lines[i] if 'CFBundleVersion' in line: lines[i + 1] = ' {0}\n'.format(version) + if 'CFBundleShortVersionString' in line: + lines[i + 1] = ' {0}\n'.format(version) - with open(info_plist, 'w') as f: - f.write(''.join(lines)) - return + with open(info_plist, 'w') as f: + f.write(''.join(lines)) def tag_version(version): diff --git a/script/create-dist.py b/script/create-dist.py index ca7e21642872..29f81ebd8579 100755 --- a/script/create-dist.py +++ b/script/create-dist.py @@ -1,5 +1,6 @@ #!/usr/bin/env python +import glob import os import re import shutil @@ -8,7 +9,8 @@ import sys import stat from lib.config import LIBCHROMIUMCONTENT_COMMIT, BASE_URL, PLATFORM, \ - get_target_arch, get_chromedriver_version + get_target_arch, get_chromedriver_version, \ + get_platform_key from lib.util import scoped_cwd, rm_rf, get_atom_shell_version, make_zip, \ execute, atom_gyp @@ -167,14 +169,21 @@ def create_symbols(): dump_symbols = os.path.join(SOURCE_ROOT, 'script', 'dump-symbols.py') execute([sys.executable, dump_symbols, destination]) + if PLATFORM == 'darwin': + dsyms = glob.glob(os.path.join(OUT_DIR, '*.dSYM')) + for dsym in dsyms: + shutil.copytree(dsym, os.path.join(DIST_DIR, os.path.basename(dsym))) + def create_dist_zip(): dist_name = '{0}-{1}-{2}-{3}.zip'.format(PROJECT_NAME, ATOM_SHELL_VERSION, - PLATFORM, get_target_arch()) + get_platform_key(), + get_target_arch()) zip_file = os.path.join(SOURCE_ROOT, 'dist', dist_name) with scoped_cwd(DIST_DIR): - files = TARGET_BINARIES[PLATFORM] + ['LICENSE', 'version'] + files = TARGET_BINARIES[PLATFORM] + ['LICENSE', 'LICENSES.chromium.html', + 'version'] if PLATFORM == 'linux': files += [lib for lib in SYSTEM_LIBRARIES if os.path.exists(lib)] dirs = TARGET_DIRECTORIES[PLATFORM] @@ -182,12 +191,12 @@ def create_dist_zip(): def create_chrome_binary_zip(binary, version): - dist_name = '{0}-{1}-{2}-{3}.zip'.format(binary, version, PLATFORM, + dist_name = '{0}-{1}-{2}-{3}.zip'.format(binary, version, get_platform_key(), get_target_arch()) zip_file = os.path.join(SOURCE_ROOT, 'dist', dist_name) with scoped_cwd(DIST_DIR): - files = ['LICENSE'] + files = ['LICENSE', 'LICENSES.chromium.html'] if PLATFORM == 'win32': files += [binary + '.exe'] else: @@ -198,14 +207,23 @@ def create_chrome_binary_zip(binary, version): def create_symbols_zip(): dist_name = '{0}-{1}-{2}-{3}-symbols.zip'.format(PROJECT_NAME, ATOM_SHELL_VERSION, - PLATFORM, + get_platform_key(), get_target_arch()) - zip_file = os.path.join(SOURCE_ROOT, 'dist', dist_name) + zip_file = os.path.join(DIST_DIR, dist_name) + licenses = ['LICENSE', 'LICENSES.chromium.html', 'version'] with scoped_cwd(DIST_DIR): - files = ['LICENSE', 'version'] dirs = ['{0}.breakpad.syms'.format(PROJECT_NAME)] - make_zip(zip_file, files, dirs) + make_zip(zip_file, licenses, dirs) + + if PLATFORM == 'darwin': + dsym_name = '{0}-{1}-{2}-{3}-dsym.zip'.format(PROJECT_NAME, + ATOM_SHELL_VERSION, + get_platform_key(), + get_target_arch()) + with scoped_cwd(DIST_DIR): + dsyms = glob.glob('*.dSYM') + make_zip(os.path.join(DIST_DIR, dsym_name), licenses, dsyms) if __name__ == '__main__': diff --git a/script/lib/config.py b/script/lib/config.py index 68f216785d60..159839c7f559 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -7,8 +7,8 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ - 'http://github-janky-artifacts.s3.amazonaws.com/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = '04523758cda2a96d2454f9056fb1fb9a1c1f95f1' + 'http://gh-contractor-zcbenz.s3.amazonaws.com/libchromiumcontent' +LIBCHROMIUMCONTENT_COMMIT = '451ea93cc3090f7000f8f0daa4cb84e90ad6c842' PLATFORM = { 'cygwin': 'win32', @@ -20,6 +20,13 @@ PLATFORM = { verbose_mode = False +def get_platform_key(): + if os.environ.has_key('MAS_BUILD'): + return 'mas' + else: + return PLATFORM + + def get_target_arch(): try: target_arch_path = os.path.join(__file__, '..', '..', '..', 'vendor', diff --git a/script/test.py b/script/test.py index 28aeac9dc1ff..7f75d3113d69 100755 --- a/script/test.py +++ b/script/test.py @@ -16,6 +16,9 @@ PRODUCT_NAME = atom_gyp()['product_name%'] def main(): os.chdir(SOURCE_ROOT) + # Disable old APIs + os.environ['ELECTRON_HIDE_INTERNAL_MODULES'] = 'true' + config = 'D' if len(sys.argv) == 2 and sys.argv[1] == '-R': config = 'R' diff --git a/script/update-external-binaries.py b/script/update-external-binaries.py index 49e73435ab51..fae268ea8cb7 100755 --- a/script/update-external-binaries.py +++ b/script/update-external-binaries.py @@ -8,7 +8,7 @@ from lib.config import get_target_arch from lib.util import safe_mkdir, rm_rf, extract_zip, tempdir, download -VERSION = 'v0.7.0' +VERSION = 'v0.8.0' SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) FRAMEWORKS_URL = 'http://github.com/atom/atom-shell-frameworks/releases' \ '/download/' + VERSION diff --git a/script/update.py b/script/update.py index abb3756ca365..e91e8401cbff 100755 --- a/script/update.py +++ b/script/update.py @@ -55,11 +55,17 @@ def run_gyp(target_arch, component): # Avoid using the old gyp lib in system. env['PYTHONPATH'] = os.path.pathsep.join([gyp_pylib, env.get('PYTHONPATH', '')]) + # Whether to build for Mac App Store. + if os.environ.has_key('MAS_BUILD'): + mas_build = 1 + else: + mas_build = 0 defines = [ '-Dlibchromiumcontent_component={0}'.format(component), '-Dtarget_arch={0}'.format(target_arch), '-Dhost_arch={0}'.format(get_host_arch()), '-Dlibrary=static_library', + '-Dmas_build={0}'.format(mas_build), ] return subprocess.call([python, gyp, '-f', 'ninja', '--depth', '.', 'atom.gyp', '-Icommon.gypi'] + defines, env=env) diff --git a/script/upload.py b/script/upload.py index 6fc421e6b7a2..3245d9caaa76 100755 --- a/script/upload.py +++ b/script/upload.py @@ -7,7 +7,8 @@ import subprocess import sys import tempfile -from lib.config import PLATFORM, get_target_arch, get_chromedriver_version +from lib.config import PLATFORM, get_target_arch, get_chromedriver_version, \ + get_platform_key from lib.util import atom_gyp, execute, get_atom_shell_version, parse_version, \ scoped_cwd from lib.github import GitHub @@ -24,14 +25,18 @@ OUT_DIR = os.path.join(SOURCE_ROOT, 'out', 'R') DIST_DIR = os.path.join(SOURCE_ROOT, 'dist') DIST_NAME = '{0}-{1}-{2}-{3}.zip'.format(PROJECT_NAME, ATOM_SHELL_VERSION, - PLATFORM, + get_platform_key(), get_target_arch()) SYMBOLS_NAME = '{0}-{1}-{2}-{3}-symbols.zip'.format(PROJECT_NAME, ATOM_SHELL_VERSION, - PLATFORM, + get_platform_key(), get_target_arch()) +DSYM_NAME = '{0}-{1}-{2}-{3}-dsym.zip'.format(PROJECT_NAME, + ATOM_SHELL_VERSION, + get_platform_key(), + get_target_arch()) MKSNAPSHOT_NAME = 'mksnapshot-{0}-{1}-{2}.zip'.format(ATOM_SHELL_VERSION, - PLATFORM, + get_platform_key(), get_target_arch()) @@ -81,11 +86,13 @@ def main(): # Upload atom-shell with GitHub Releases API. upload_atom_shell(github, release, os.path.join(DIST_DIR, DIST_NAME)) upload_atom_shell(github, release, os.path.join(DIST_DIR, SYMBOLS_NAME)) + if PLATFORM == 'darwin': + upload_atom_shell(github, release, os.path.join(DIST_DIR, DSYM_NAME)) # Upload chromedriver and mksnapshot for minor version update. if parse_version(args.version)[2] == '0': chromedriver = 'chromedriver-{0}-{1}-{2}.zip'.format( - get_chromedriver_version(), PLATFORM, get_target_arch()) + get_chromedriver_version(), get_platform_key(), get_target_arch()) upload_atom_shell(github, release, os.path.join(DIST_DIR, chromedriver)) upload_atom_shell(github, release, os.path.join(DIST_DIR, MKSNAPSHOT_NAME)) @@ -167,11 +174,10 @@ def create_or_get_release_draft(github, releases, tag, tag_exists): def create_release_draft(github, tag): + name = '{0} {1}'.format(PROJECT_NAME, tag) 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') diff --git a/spec/api-app-spec.coffee b/spec/api-app-spec.coffee index 4b1a04e00827..490727488d99 100644 --- a/spec/api-app-spec.coffee +++ b/spec/api-app-spec.coffee @@ -1,7 +1,6 @@ assert = require 'assert' -remote = require 'remote' -app = remote.require 'app' -BrowserWindow = remote.require 'browser-window' +{remote} = require 'electron' +{app, BrowserWindow} = remote.require 'electron' describe 'app module', -> describe 'app.getVersion()', -> diff --git a/spec/api-browser-window-spec.coffee b/spec/api-browser-window-spec.coffee index 2e4a66b92b9a..86beb1fdc600 100644 --- a/spec/api-browser-window-spec.coffee +++ b/spec/api-browser-window-spec.coffee @@ -1,12 +1,12 @@ assert = require 'assert' fs = require 'fs' path = require 'path' -remote = require 'remote' http = require 'http' url = require 'url' os = require 'os' -BrowserWindow = remote.require 'browser-window' +{remote, screen} = require 'electron' +{ipcMain, BrowserWindow} = remote.require 'electron' isCI = remote.process.argv[2] == '--ci' @@ -31,14 +31,14 @@ describe 'browser-window module', -> fs.unlinkSync(test) assert.equal String(content), 'unload' done() - w.loadUrl 'file://' + path.join(fixtures, 'api', 'unload.html') + w.loadURL 'file://' + path.join(fixtures, 'api', 'unload.html') it 'should emit beforeunload handler', (done) -> w.on 'onbeforeunload', -> done() w.webContents.on 'did-finish-load', -> w.close() - w.loadUrl 'file://' + path.join(fixtures, 'api', 'beforeunload-false.html') + w.loadURL 'file://' + path.join(fixtures, 'api', 'beforeunload-false.html') describe 'window.close()', -> it 'should emit unload handler', (done) -> @@ -48,23 +48,23 @@ describe 'browser-window module', -> fs.unlinkSync(test) assert.equal String(content), 'close' done() - w.loadUrl 'file://' + path.join(fixtures, 'api', 'close.html') + w.loadURL 'file://' + path.join(fixtures, 'api', 'close.html') it 'should emit beforeunload handler', (done) -> w.on 'onbeforeunload', -> done() - w.loadUrl 'file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html') + w.loadURL 'file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html') - describe 'BrowserWindow.loadUrl(url)', -> + describe 'BrowserWindow.loadURL(url)', -> it 'should emit did-start-loading event', (done) -> w.webContents.on 'did-start-loading', -> done() - w.loadUrl 'about:blank' + w.loadURL 'about:blank' it 'should emit did-fail-load event', (done) -> w.webContents.on 'did-fail-load', -> done() - w.loadUrl 'file://a.txt' + w.loadURL 'file://a.txt' describe 'BrowserWindow.show()', -> it 'should focus on window', -> @@ -138,10 +138,10 @@ describe 'browser-window module', -> w.setResizable not w.isResizable() assert.deepEqual s, w.getSize() - describe '"use-content-size" option', -> + describe '"useContentSize" option', -> it 'make window created with content size when used', -> w.destroy() - w = new BrowserWindow(show: false, width: 400, height: 400, 'use-content-size': true) + w = new BrowserWindow(show: false, width: 400, height: 400, useContentSize: true) contentSize = w.getContentSize() assert.equal contentSize[0], 400 assert.equal contentSize[1], 400 @@ -153,7 +153,7 @@ describe 'browser-window module', -> it 'works for framless window', -> w.destroy() - w = new BrowserWindow(show: false, frame: false, width: 400, height: 400, 'use-content-size': true) + w = new BrowserWindow(show: false, frame: false, width: 400, height: 400, useContentSize: true) contentSize = w.getContentSize() assert.equal contentSize[0], 400 assert.equal contentSize[1], 400 @@ -167,22 +167,22 @@ describe 'browser-window module', -> it 'creates browser window with hidden title bar', -> w.destroy() - w = new BrowserWindow(show: false, width: 400, height: 400, 'title-bar-style': 'hidden') + w = new BrowserWindow(show: false, width: 400, height: 400, titleBarStyle: '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') + w = new BrowserWindow(show: false, width: 400, height: 400, titleBarStyle: 'hidden-inset') contentSize = w.getContentSize() assert.equal contentSize[1], 400 - describe '"enable-larger-than-screen" option', -> + describe '"enableLargerThanScreen" option', -> return if process.platform is 'linux' beforeEach -> w.destroy() - w = new BrowserWindow(show: true, width: 400, height: 400, 'enable-larger-than-screen': true) + w = new BrowserWindow(show: true, width: 400, height: 400, enableLargerThanScreen: true) it 'can move the window out of screen', -> w.setPosition -10, -10 @@ -191,7 +191,7 @@ describe 'browser-window module', -> assert.equal after[1], -10 it 'can set the window larger than screen', -> - size = require('screen').getPrimaryDisplay().size + size = screen.getPrimaryDisplay().size size.width += 100 size.height += 100 w.setSize size.width, size.height @@ -201,55 +201,55 @@ describe 'browser-window module', -> describe '"web-preferences" option', -> afterEach -> - remote.require('ipc').removeAllListeners('answer') + ipcMain.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) -> + ipcMain.once 'answer', (event, test) -> assert.equal(test, 'preload') done() w.destroy() w = new BrowserWindow show: false - 'web-preferences': + webPreferences: preload: preload - w.loadUrl 'file://' + path.join(fixtures, 'api', 'preload.html') + 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) -> + ipcMain.once 'answer', (event, test) -> assert.equal(test, 'undefined') done() w.destroy() w = new BrowserWindow show: false - 'web-preferences': + webPreferences: preload: preload - 'node-integration': false - w.loadUrl 'file://' + path.join(fixtures, 'api', 'blank.html') + nodeIntegration: false + w.loadURL 'file://' + path.join(fixtures, 'api', 'blank.html') describe 'beforeunload handler', -> it 'returning true would not prevent close', (done) -> w.on 'closed', -> done() - w.loadUrl 'file://' + path.join(fixtures, 'api', 'close-beforeunload-true.html') + w.loadURL 'file://' + path.join(fixtures, 'api', 'close-beforeunload-true.html') it 'returning non-empty string would not prevent close', (done) -> w.on 'closed', -> done() - w.loadUrl 'file://' + path.join(fixtures, 'api', 'close-beforeunload-string.html') + w.loadURL 'file://' + path.join(fixtures, 'api', 'close-beforeunload-string.html') it 'returning false would prevent close', (done) -> w.on 'onbeforeunload', -> done() - w.loadUrl 'file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html') + w.loadURL 'file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html') it 'returning empty string would prevent close', (done) -> w.on 'onbeforeunload', -> done() - w.loadUrl 'file://' + path.join(fixtures, 'api', 'close-beforeunload-empty-string.html') + w.loadURL 'file://' + path.join(fixtures, 'api', 'close-beforeunload-empty-string.html') describe 'new-window event', -> return if isCI and process.platform is 'darwin' @@ -259,15 +259,16 @@ describe 'browser-window module', -> assert.equal url, 'http://host/' assert.equal frameName, 'host' done() - w.loadUrl "file://#{fixtures}/pages/window-open.html" + w.loadURL "file://#{fixtures}/pages/window-open.html" it 'emits when link with target is called', (done) -> + @timeout 10000 w.webContents.once 'new-window', (e, url, frameName) -> e.preventDefault() assert.equal url, 'http://host/' assert.equal frameName, 'target' done() - w.loadUrl "file://#{fixtures}/pages/target-name.html" + w.loadURL "file://#{fixtures}/pages/target-name.html" describe 'maximize event', -> return if isCI @@ -296,8 +297,37 @@ describe 'browser-window module', -> xdescribe 'beginFrameSubscription method', -> it 'subscribes frame updates', (done) -> - w.loadUrl "file://#{fixtures}/api/blank.html" + w.loadURL "file://#{fixtures}/api/blank.html" w.webContents.beginFrameSubscription (data) -> assert.notEqual data.length, 0 w.webContents.endFrameSubscription() done() + + describe 'save page', -> + savePageDir = path.join fixtures, 'save_page' + savePageHtmlPath = path.join savePageDir, 'save_page.html' + savePageJsPath = path.join savePageDir, 'save_page_files', 'test.js' + savePageCssPath = path.join savePageDir, 'save_page_files', 'test.css' + it 'should save page', (done) -> + w.webContents.on 'did-finish-load', -> + w.webContents.savePage savePageHtmlPath, 'HTMLComplete', (error) -> + assert.equal error, null + assert fs.existsSync savePageHtmlPath + assert fs.existsSync savePageJsPath + assert fs.existsSync savePageCssPath + fs.unlinkSync savePageCssPath + fs.unlinkSync savePageJsPath + fs.unlinkSync savePageHtmlPath + fs.rmdirSync path.join savePageDir, 'save_page_files' + fs.rmdirSync savePageDir + done() + + w.loadURL "file://#{fixtures}/pages/save_page/index.html" + + describe 'BrowserWindow options argument is optional', -> + it 'should create a window with default size (800x600)', -> + w.destroy() + w = new BrowserWindow() + size = w.getSize() + assert.equal size[0], 800 + assert.equal size[1], 600 diff --git a/spec/api-clipboard-spec.coffee b/spec/api-clipboard-spec.coffee index ddb38825e35f..19da3fc75f1b 100644 --- a/spec/api-clipboard-spec.coffee +++ b/spec/api-clipboard-spec.coffee @@ -1,8 +1,8 @@ assert = require 'assert' -clipboard = require 'clipboard' -nativeImage = require 'native-image' path = require 'path' +{clipboard, nativeImage} = require 'electron' + describe 'clipboard module', -> fixtures = path.resolve __dirname, 'fixtures' @@ -11,7 +11,7 @@ describe 'clipboard module', -> p = path.join fixtures, 'assets', 'logo.png' i = nativeImage.createFromPath p clipboard.writeImage p - assert.equal clipboard.readImage().toDataUrl(), i.toDataUrl() + assert.equal clipboard.readImage().toDataURL(), i.toDataURL() describe 'clipboard.readText()', -> it 'returns unicode string correctly', -> @@ -49,4 +49,4 @@ describe 'clipboard module', -> 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() + assert.equal clipboard.readImage().toDataURL(), i.toDataURL() diff --git a/spec/api-crash-reporter-spec.coffee b/spec/api-crash-reporter-spec.coffee index 60b630bc2ffc..676dbf9d6929 100644 --- a/spec/api-crash-reporter-spec.coffee +++ b/spec/api-crash-reporter-spec.coffee @@ -2,11 +2,10 @@ assert = require 'assert' path = require 'path' http = require 'http' url = require 'url' -remote = require 'remote' -formidable = require 'formidable' +multiparty = require 'multiparty' -crashReporter = remote.require 'crash-reporter' -BrowserWindow = remote.require 'browser-window' +{remote} = require 'electron' +{app, crashReporter, BrowserWindow} = remote.require 'electron' describe 'crash-reporter module', -> fixtures = path.resolve __dirname, 'fixtures' @@ -15,8 +14,8 @@ describe 'crash-reporter module', -> beforeEach -> w = new BrowserWindow(show: false) afterEach -> w.destroy() - # It is not working on 64bit Windows. - return if process.platform is 'win32' and process.arch is 'x64' + # It is not working for mas build. + return if process.mas # The crash-reporter test is not reliable on CI machine. isCI = remote.process.argv[2] == '--ci' @@ -24,12 +23,15 @@ describe 'crash-reporter module', -> it 'should send minidump when renderer crashes', (done) -> @timeout 120000 + called = false server = http.createServer (req, res) -> server.close() - form = new formidable.IncomingForm() - process.throwDeprecation = false + form = new multiparty.Form() form.parse req, (error, fields, files) -> - process.throwDeprecation = true + # This callback can be called for twice sometimes. + return if called + called = true + assert.equal fields['prod'], 'Electron' assert.equal fields['ver'], process.versions['electron'] assert.equal fields['process_type'], 'renderer' @@ -38,8 +40,7 @@ describe 'crash-reporter module', -> assert.equal fields['extra2'], 'extra2' assert.equal fields['_productName'], 'Zombies' assert.equal fields['_companyName'], 'Umbrella Corporation' - assert.equal fields['_version'], require('remote').require('app').getVersion() - assert files['upload_file_minidump']['name']? + assert.equal fields['_version'], app.getVersion() res.end('abc-123-def') done() @@ -53,5 +54,6 @@ describe 'crash-reporter module', -> protocol: 'file' pathname: path.join fixtures, 'api', 'crash.html' search: "?port=#{port}" - crashReporter.start {'submitUrl': 'http://127.0.0.1:' + port} - w.loadUrl url + if process.platform is 'darwin' + 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 142f06c00ff3..1aa715c9f759 100644 --- a/spec/api-ipc-spec.coffee +++ b/spec/api-ipc-spec.coffee @@ -1,9 +1,8 @@ assert = require 'assert' -ipc = require 'ipc' path = require 'path' -remote = require 'remote' -BrowserWindow = remote.require 'browser-window' +{ipcRenderer, remote} = require 'electron' +{ipcMain, BrowserWindow} = remote.require 'electron' comparePaths = (path1, path2) -> if process.platform is 'win32' @@ -17,8 +16,8 @@ describe 'ipc module', -> describe 'remote.require', -> it 'should returns same object for the same module', -> - dialog1 = remote.require 'dialog' - dialog2 = remote.require 'dialog' + dialog1 = remote.require 'electron' + dialog2 = remote.require 'electron' assert.equal dialog1, dialog2 it 'should work when object contains id property', -> @@ -70,21 +69,34 @@ describe 'ipc module', -> describe 'ipc.sender.send', -> it 'should work when sending an object containing id property', (done) -> obj = id: 1, name: 'ly' - ipc.once 'message', (message) -> + ipcRenderer.once 'message', (event, message) -> assert.deepEqual message, obj done() - ipc.send 'message', obj + ipcRenderer.send 'message', obj describe 'ipc.sendSync', -> it 'can be replied by setting event.returnValue', -> - msg = ipc.sendSync 'echo', 'test' + msg = ipcRenderer.sendSync 'echo', 'test' assert.equal msg, 'test' it 'does not crash when reply is not sent and browser is destroyed', (done) -> @timeout 10000 w = new BrowserWindow(show: false) - remote.require('ipc').once 'send-sync-message', (event) -> + ipcMain.once 'send-sync-message', (event) -> event.returnValue = null w.destroy() done() - w.loadUrl 'file://' + path.join(fixtures, 'api', 'send-sync-message.html') + w.loadURL 'file://' + path.join(fixtures, 'api', 'send-sync-message.html') + + describe 'remote listeners', -> + w = null + afterEach -> + w.destroy() + + it 'can be added and removed correctly', -> + w = new BrowserWindow(show: false) + listener = -> + w.on 'test', listener + assert.equal w.listenerCount('test'), 1 + w.removeListener 'test', listener + assert.equal w.listenerCount('test'), 0 diff --git a/spec/api-menu-spec.coffee b/spec/api-menu-spec.coffee index f51d8580cc52..f23face228a9 100644 --- a/spec/api-menu-spec.coffee +++ b/spec/api-menu-spec.coffee @@ -1,8 +1,7 @@ assert = require 'assert' -remote = require 'remote' -Menu = remote.require 'menu' -MenuItem = remote.require 'menu-item' +{remote} = require 'electron' +{Menu, MenuItem} = remote.require 'electron' describe 'menu module', -> describe 'Menu.buildFromTemplate', -> diff --git a/spec/api-protocol-spec.coffee b/spec/api-protocol-spec.coffee index 02fd8d5e402a..034b9765927c 100644 --- a/spec/api-protocol-spec.coffee +++ b/spec/api-protocol-spec.coffee @@ -1,8 +1,9 @@ assert = require 'assert' http = require 'http' path = require 'path' -remote = require 'remote' -protocol = remote.require 'protocol' + +{remote} = require 'electron' +{protocol} = remote.require 'electron' describe 'protocol module', -> protocolName = 'sp' @@ -23,9 +24,12 @@ describe 'protocol module', -> it 'does not crash when handler is called twice', (done) -> doubleHandler = (request, callback) -> - callback(text) - callback() + try + callback(text) + callback() + catch protocol.registerStringProtocol protocolName, doubleHandler, (error) -> + return done(error) if error $.ajax url: "#{protocolName}://fake-host" success: (data) -> @@ -36,6 +40,7 @@ describe 'protocol module', -> it 'sends error when callback is called with nothing', (done) -> protocol.registerBufferProtocol protocolName, emptyHandler, (error) -> + return done(error) if error $.ajax url: "#{protocolName}://fake-host" success: (data) -> @@ -48,6 +53,7 @@ describe 'protocol module', -> handler = (request, callback) -> setImmediate -> callback(text) protocol.registerStringProtocol protocolName, handler, (error) -> + return done(error) if error $.ajax url: "#{protocolName}://fake-host" success: (data) -> @@ -66,6 +72,7 @@ describe 'protocol module', -> it 'sends string as response', (done) -> handler = (request, callback) -> callback(text) protocol.registerStringProtocol protocolName, handler, (error) -> + return done(error) if error $.ajax url: "#{protocolName}://fake-host" success: (data) -> @@ -74,9 +81,25 @@ describe 'protocol module', -> error: (xhr, errorType, error) -> done(error) + it 'sets Access-Control-Allow-Origin', (done) -> + handler = (request, callback) -> callback(text) + protocol.registerStringProtocol protocolName, handler, (error) -> + return done(error) if error + $.ajax + url: "#{protocolName}://fake-host" + success: (data, status, request) -> + assert.equal data, text + assert.equal( + request.getResponseHeader('Access-Control-Allow-Origin'), + '*') + done() + error: (xhr, errorType, error) -> + done(error) + it 'sends object as response', (done) -> handler = (request, callback) -> callback(data: text, mimeType: 'text/html') protocol.registerStringProtocol protocolName, handler, (error) -> + return done(error) if error $.ajax url: "#{protocolName}://fake-host" success: (data, statux, request) -> @@ -88,6 +111,7 @@ describe 'protocol module', -> it 'fails when sending object other than string', (done) -> handler = (request, callback) -> callback(new Date) protocol.registerBufferProtocol protocolName, handler, (error) -> + return done(error) if error $.ajax url: "#{protocolName}://fake-host" success: (data) -> @@ -102,6 +126,7 @@ describe 'protocol module', -> it 'sends Buffer as response', (done) -> handler = (request, callback) -> callback(buffer) protocol.registerBufferProtocol protocolName, handler, (error) -> + return done(error) if error $.ajax url: "#{protocolName}://fake-host" success: (data) -> @@ -110,9 +135,25 @@ describe 'protocol module', -> error: (xhr, errorType, error) -> done(error) + it 'sets Access-Control-Allow-Origin', (done) -> + handler = (request, callback) -> callback(buffer) + protocol.registerBufferProtocol protocolName, handler, (error) -> + return done(error) if error + $.ajax + url: "#{protocolName}://fake-host" + success: (data, status, request) -> + assert.equal data, text + assert.equal( + request.getResponseHeader('Access-Control-Allow-Origin'), + '*') + 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) -> + return done(error) if error $.ajax url: "#{protocolName}://fake-host" success: (data, statux, request) -> @@ -124,6 +165,7 @@ describe 'protocol module', -> it 'fails when sending string', (done) -> handler = (request, callback) -> callback(text) protocol.registerBufferProtocol protocolName, handler, (error) -> + return done(error) if error $.ajax url: "#{protocolName}://fake-host" success: (data) -> @@ -142,6 +184,7 @@ describe 'protocol module', -> it 'sends file path as response', (done) -> handler = (request, callback) -> callback(filePath) protocol.registerFileProtocol protocolName, handler, (error) -> + return done(error) if error $.ajax url: "#{protocolName}://fake-host" success: (data) -> @@ -150,9 +193,25 @@ describe 'protocol module', -> error: (xhr, errorType, error) -> done(error) + it 'sets Access-Control-Allow-Origin', (done) -> + handler = (request, callback) -> callback(filePath) + protocol.registerFileProtocol protocolName, handler, (error) -> + return done(error) if error + $.ajax + url: "#{protocolName}://fake-host" + success: (data, status, request) -> + assert.equal data, String(fileContent) + assert.equal( + request.getResponseHeader('Access-Control-Allow-Origin'), + '*') + done() + error: (xhr, errorType, error) -> + done(error) + it 'sends object as response', (done) -> handler = (request, callback) -> callback(path: filePath) protocol.registerFileProtocol protocolName, handler, (error) -> + return done(error) if error $.ajax url: "#{protocolName}://fake-host" success: (data, statux, request) -> @@ -164,6 +223,7 @@ describe 'protocol module', -> it 'can send normal file', (done) -> handler = (request, callback) -> callback(normalPath) protocol.registerFileProtocol protocolName, handler, (error) -> + return done(error) if error $.ajax url: "#{protocolName}://fake-host" success: (data) -> @@ -176,6 +236,7 @@ describe 'protocol module', -> fakeFilePath = path.join __dirname, 'fixtures', 'asar', 'a.asar', 'not-exist' handler = (request, callback) -> callback(fakeFilePath) protocol.registerBufferProtocol protocolName, handler, (error) -> + return done(error) if error $.ajax url: "#{protocolName}://fake-host" success: (data) -> @@ -187,6 +248,7 @@ describe 'protocol module', -> it 'fails when sending unsupported content', (done) -> handler = (request, callback) -> callback(new Date) protocol.registerBufferProtocol protocolName, handler, (error) -> + return done(error) if error $.ajax url: "#{protocolName}://fake-host" success: (data) -> @@ -206,6 +268,7 @@ describe 'protocol module', -> url = "http://127.0.0.1:#{port}" handler = (request, callback) -> callback({url}) protocol.registerHttpProtocol protocolName, handler, (error) -> + return done(error) if error $.ajax url: "#{protocolName}://fake-host" success: (data) -> @@ -217,6 +280,7 @@ describe 'protocol module', -> it 'fails when sending invalid url', (done) -> handler = (request, callback) -> callback({url: 'url'}) protocol.registerHttpProtocol protocolName, handler, (error) -> + return done(error) if error $.ajax url: "#{protocolName}://fake-host" success: (data) -> @@ -228,6 +292,7 @@ describe 'protocol module', -> it 'fails when sending unsupported content', (done) -> handler = (request, callback) -> callback(new Date) protocol.registerHttpProtocol protocolName, handler, (error) -> + return done(error) if error $.ajax url: "#{protocolName}://fake-host" success: (data) -> @@ -285,9 +350,12 @@ describe 'protocol module', -> it 'does not crash when handler is called twice', (done) -> doubleHandler = (request, callback) -> - callback(text) - callback() + try + callback(text) + callback() + catch protocol.interceptStringProtocol 'http', doubleHandler, (error) -> + return done(error) if error $.ajax url: 'http://fake-host' success: (data) -> @@ -297,7 +365,11 @@ describe 'protocol module', -> done(error) it 'sends error when callback is called with nothing', (done) -> + # Flaky on Travis. + return done() if process.env.TRAVIS is 'true' + protocol.interceptBufferProtocol 'http', emptyHandler, (error) -> + return done(error) if error $.ajax url: 'http://fake-host' success: (data) -> @@ -310,6 +382,7 @@ describe 'protocol module', -> it 'can intercept http protocol', (done) -> handler = (request, callback) -> callback(text) protocol.interceptStringProtocol 'http', handler, (error) -> + return done(error) if error $.ajax url: 'http://fake-host' success: (data) -> @@ -322,6 +395,7 @@ describe 'protocol module', -> handler = (request, callback) -> callback({mimeType: 'application/json', data: '{"value": 1}'}) protocol.interceptStringProtocol 'http', handler, (error) -> + return done(error) if error $.ajax url: 'http://fake-host' success: (data) -> @@ -335,6 +409,7 @@ describe 'protocol module', -> it 'can intercept http protocol', (done) -> handler = (request, callback) -> callback(new Buffer(text)) protocol.interceptBufferProtocol 'http', handler, (error) -> + return done(error) if error $.ajax url: 'http://fake-host' success: (data) -> diff --git a/spec/api-screen-spec.coffee b/spec/api-screen-spec.coffee index 341524fca81b..59ef49631cbc 100644 --- a/spec/api-screen-spec.coffee +++ b/spec/api-screen-spec.coffee @@ -1,5 +1,6 @@ assert = require 'assert' -screen = require 'screen' + +{screen} = require 'electron' describe 'screen module', -> describe 'screen.getCursorScreenPoint()', -> diff --git a/spec/api-session-spec.coffee b/spec/api-session-spec.coffee index 9e083d27c0f7..98e47e357a58 100644 --- a/spec/api-session-spec.coffee +++ b/spec/api-session-spec.coffee @@ -1,10 +1,10 @@ assert = require 'assert' -remote = require 'remote' http = require 'http' path = require 'path' fs = require 'fs' -app = remote.require 'app' -BrowserWindow = remote.require 'browser-window' + +{ipcRenderer, remote} = require 'electron' +{app, ipcMain, session, BrowserWindow} = remote describe 'session module', -> @timeout 10000 @@ -23,7 +23,7 @@ describe 'session module', -> server.listen 0, '127.0.0.1', -> {port} = server.address() - w.loadUrl "#{url}:#{port}" + w.loadURL "#{url}:#{port}" w.webContents.on 'did-finish-load', -> w.webContents.session.cookies.get {url: url}, (error, list) -> return done(error) if error @@ -35,9 +35,9 @@ describe 'session module', -> done('Can not find cookie') it 'should over-write the existent cookie', (done) -> - app.defaultSession.cookies.set {url: url, name: '1', value: '1'}, (error) -> + session.defaultSession.cookies.set {url: url, name: '1', value: '1'}, (error) -> return done(error) if error - app.defaultSession.cookies.get {url: url}, (error, list) -> + session.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' @@ -47,11 +47,11 @@ describe 'session module', -> done('Can not find cookie') it 'should remove cookies', (done) -> - app.defaultSession.cookies.set {url: url, name: '2', value: '2'}, (error) -> + session.defaultSession.cookies.set {url: url, name: '2', value: '2'}, (error) -> return done(error) if error - app.defaultSession.cookies.remove {url: url, name: '2'}, (error) -> + session.defaultSession.cookies.remove {url: url, name: '2'}, (error) -> return done(error) if error - app.defaultSession.cookies.get {url: url}, (error, list) -> + session.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') @@ -60,12 +60,11 @@ describe 'session module', -> 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' + ipcMain.on 'count', (event, count) -> + ipcMain.removeAllListeners 'count' assert not count done() - w.loadUrl 'file://' + path.join(fixtures, 'api', 'localstorage.html') + w.loadURL 'file://' + path.join(fixtures, 'api', 'localstorage.html') w.webContents.on 'did-finish-load', -> options = origin: "file://", @@ -78,7 +77,6 @@ describe 'session module', -> # A 5 MB mock pdf. mockPDF = new Buffer 1024 * 1024 * 5 contentDisposition = 'inline; filename="mock.pdf"' - ipc = require 'ipc' downloadFilePath = path.join fixtures, 'mock.pdf' downloadServer = http.createServer (req, res) -> res.writeHead 200, { @@ -89,31 +87,49 @@ describe 'session module', -> res.end mockPDF downloadServer.close() - it 'can download successfully', (done) -> + assertDownload = (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port) -> + assert.equal state, 'completed' + assert.equal filename, 'mock.pdf' + assert.equal url, "http://127.0.0.1:#{port}/" + assert.equal mimeType, 'application/pdf' + assert.equal receivedBytes, mockPDF.length + assert.equal totalBytes, mockPDF.length + assert.equal disposition, contentDisposition + assert fs.existsSync downloadFilePath + fs.unlinkSync downloadFilePath + + it 'can download using BrowserWindow.loadURL', (done) -> downloadServer.listen 0, '127.0.0.1', -> {port} = downloadServer.address() - ipc.sendSync 'set-download-option', false - w.loadUrl "#{url}:#{port}" - ipc.once 'download-done', (state, url, mimeType, receivedBytes, - totalBytes, disposition, filename) -> - assert.equal state, 'completed' - assert.equal filename, 'mock.pdf' - assert.equal url, "http://127.0.0.1:#{port}/" - assert.equal mimeType, 'application/pdf' - assert.equal receivedBytes, mockPDF.length - assert.equal totalBytes, mockPDF.length - assert.equal disposition, contentDisposition - assert fs.existsSync downloadFilePath - fs.unlinkSync downloadFilePath + ipcRenderer.sendSync 'set-download-option', false + w.loadURL "#{url}:#{port}" + ipcRenderer.once 'download-done', (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) -> + assertDownload event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port done() + it 'can download using WebView.downloadURL', (done) -> + downloadServer.listen 0, '127.0.0.1', -> + {port} = downloadServer.address() + ipcRenderer.sendSync 'set-download-option', false + + webview = new WebView + webview.src = "file://#{fixtures}/api/blank.html" + webview.addEventListener 'did-finish-load', -> + webview.downloadURL "#{url}:#{port}/" + + ipcRenderer.once 'download-done', (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) -> + assertDownload event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port + document.body.removeChild(webview) + done() + + document.body.appendChild webview + it 'can cancel download', (done) -> downloadServer.listen 0, '127.0.0.1', -> {port} = downloadServer.address() - ipc.sendSync 'set-download-option', true - w.loadUrl "#{url}:#{port}/" - ipc.once 'download-done', (state, url, mimeType, receivedBytes, - totalBytes, disposition, filename) -> + ipcRenderer.sendSync 'set-download-option', true + w.loadURL "#{url}:#{port}/" + ipcRenderer.once 'download-done', (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) -> assert.equal state, 'cancelled' assert.equal filename, 'mock.pdf' assert.equal mimeType, 'application/pdf' diff --git a/spec/asar-spec.coffee b/spec/asar-spec.coffee index 1e6ee6910369..479443292b44 100644 --- a/spec/asar-spec.coffee +++ b/spec/asar-spec.coffee @@ -3,6 +3,9 @@ child_process = require 'child_process' fs = require 'fs' path = require 'path' +{nativeImage, remote} = require 'electron' +{ipcMain, BrowserWindow} = remote.require 'electron' + describe 'asar package', -> fixtures = path.join __dirname, 'fixtures' @@ -389,6 +392,23 @@ describe 'asar package', -> done() child.send file + describe 'child_process.execFile', -> + return unless process.platform is 'darwin' + + {execFile, execFileSync} = require 'child_process' + echo = path.join fixtures, 'asar', 'echo.asar', 'echo' + + it 'executes binaries', (done) -> + child = execFile echo, ['test'], (error, stdout) -> + assert.equal error, null + assert.equal stdout, 'test\n' + done() + + # execFileSync makes the test flaky after a refresh. + xit 'execFileSync executes binaries', -> + output = execFileSync echo, ['test'] + assert.equal String(output), 'test\n' + describe 'internalModuleReadFile', -> internalModuleReadFile = process.binding('fs').internalModuleReadFile @@ -404,11 +424,45 @@ describe 'asar package', -> p = path.join fixtures, 'asar', 'unpack.asar', 'a.txt' assert.equal internalModuleReadFile(p).toString().trim(), 'a' + describe 'process.noAsar', -> + errorName = if process.platform is 'win32' then 'ENOENT' else 'ENOTDIR' + + beforeEach -> + process.noAsar = true + afterEach -> + process.noAsar = false + + it 'disables asar support in sync API', -> + file = path.join fixtures, 'asar', 'a.asar', 'file1' + dir = path.join fixtures, 'asar', 'a.asar', 'dir1' + assert.throws (-> fs.readFileSync file), new RegExp(errorName) + assert.throws (-> fs.lstatSync file), new RegExp(errorName) + assert.throws (-> fs.realpathSync file), new RegExp(errorName) + assert.throws (-> fs.readdirSync dir), new RegExp(errorName) + + it 'disables asar support in async API', (done) -> + file = path.join fixtures, 'asar', 'a.asar', 'file1' + dir = path.join fixtures, 'asar', 'a.asar', 'dir1' + fs.readFile file, (error) -> + assert.equal error.code, errorName + fs.lstat file, (error) -> + assert.equal error.code, errorName + fs.realpath file, (error) -> + assert.equal error.code, errorName + fs.readdir dir, (error) -> + assert.equal error.code, errorName + done() + + it 'treats *.asar as normal file', -> + originalFs = require 'original-fs' + asar = path.join fixtures, 'asar', 'a.asar' + content1 = fs.readFileSync asar + content2 = originalFs.readFileSync asar + assert.equal content1.compare(content2), 0 + assert.throws (-> fs.readdirSync asar), /ENOTDIR/ + describe 'asar protocol', -> url = require 'url' - remote = require 'remote' - ipc = remote.require 'ipc' - BrowserWindow = remote.require 'browser-window' it 'can request a file in package', (done) -> p = path.resolve fixtures, 'asar', 'a.asar', 'file1' @@ -445,26 +499,26 @@ describe 'asar package', -> it 'sets __dirname correctly', (done) -> after -> w.destroy() - ipc.removeAllListeners 'dirname' + ipcMain.removeAllListeners 'dirname' w = new BrowserWindow(show: false, width: 400, height: 400) p = path.resolve fixtures, 'asar', 'web.asar', 'index.html' u = url.format protocol: 'file', slashed: true, pathname: p - w.loadUrl u - ipc.once 'dirname', (event, dirname) -> + ipcMain.once 'dirname', (event, dirname) -> assert.equal dirname, path.dirname(p) done() + w.loadURL u it 'loads script tag in html', (done) -> after -> w.destroy() - ipc.removeAllListeners 'ping' + ipcMain.removeAllListeners 'ping' w = new BrowserWindow(show: false, width: 400, height: 400) p = path.resolve fixtures, 'asar', 'script.asar', 'index.html' u = url.format protocol: 'file', slashed: true, pathname: p - w.loadUrl u - ipc.once 'ping', (event, message) -> + w.loadURL u + ipcMain.once 'ping', (event, message) -> assert.equal message, 'pong' done() @@ -496,10 +550,10 @@ describe 'asar package', -> describe 'native-image', -> it 'reads image from asar archive', -> p = path.join fixtures, 'asar', 'logo.asar', 'logo.png' - logo = require('native-image').createFromPath p + logo = nativeImage.createFromPath p assert.deepEqual logo.getSize(), {width: 55, height: 55} it 'reads image from asar archive with unpacked files', -> p = path.join fixtures, 'asar', 'unpack.asar', 'atom.png' - logo = require('native-image').createFromPath p + logo = nativeImage.createFromPath p assert.deepEqual logo.getSize(), {width: 1024, height: 1024} diff --git a/spec/chromium-spec.coffee b/spec/chromium-spec.coffee index a782079026a7..211de346846b 100644 --- a/spec/chromium-spec.coffee +++ b/spec/chromium-spec.coffee @@ -3,11 +3,19 @@ http = require 'http' https = require 'https' path = require 'path' ws = require 'ws' -remote = require 'remote' + +{remote} = require 'electron' +{BrowserWindow} = remote.require 'electron' describe 'chromium feature', -> fixtures = path.resolve __dirname, 'fixtures' + listener = null + afterEach -> + if listener? + window.removeEventListener 'message', listener + listener = null + xdescribe 'heap snapshot', -> it 'does not crash', -> process.atomBinding('v8_util').takeHeapSnapshot() @@ -24,21 +32,18 @@ describe 'chromium feature', -> $.get "http://127.0.0.1:#{port}" describe 'document.hidden', -> - BrowserWindow = remote.require 'browser-window' - ipc = remote.require 'ipc' url = "file://#{fixtures}/pages/document-hidden.html" w = null afterEach -> w?.destroy() - ipc.removeAllListeners 'hidden' it 'is set correctly when window is not shown', (done) -> - ipc.once 'hidden', (event, hidden) -> - assert hidden - done() w = new BrowserWindow(show:false) - w.loadUrl url + w.webContents.on 'ipc-message', (event, args) -> + assert.deepEqual args, ['hidden', true] + done() + w.loadURL url describe 'navigator.webkitGetUserMedia', -> it 'calls its callbacks', (done) -> @@ -62,46 +67,52 @@ describe 'chromium feature', -> it 'accepts "node-integration" as feature', (done) -> listener = (event) -> - window.removeEventListener 'message', listener - b.close() assert.equal event.data, 'undefined' + b.close() done() window.addEventListener 'message', listener - b = window.open "file://#{fixtures}/pages/window-opener-node.html", '', 'node-integration=no,show=no' + b = window.open "file://#{fixtures}/pages/window-opener-node.html", '', 'nodeIntegration=no,show=no' it 'inherit options of parent window', (done) -> listener = (event) -> - window.removeEventListener 'message', listener + [width, height] = remote.getCurrentWindow().getSize() + assert.equal event.data, "size: #{width} #{height}" b.close() - size = remote.getCurrentWindow().getSize() - assert.equal event.data, "size: #{size.width} #{size.height}" done() window.addEventListener 'message', listener b = window.open "file://#{fixtures}/pages/window-open-size.html", '', 'show=no' + it 'does not override child options', (done) -> + size = {width: 350, height: 450} + listener = (event) -> + assert.equal event.data, "size: #{size.width} #{size.height}" + b.close() + done() + window.addEventListener 'message', listener + b = window.open "file://#{fixtures}/pages/window-open-size.html", '', "show=no,width=#{size.width},height=#{size.height}" + 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.once 'opener', (event, opener) -> - assert.equal opener, null - done() - BrowserWindow = remote.require 'browser-window' w = new BrowserWindow(show: false) - w.loadUrl url + w.webContents.on 'ipc-message', (event, args) -> + assert.deepEqual args, ['opener', null] + done() + w.loadURL url it 'is not null for window opened by window.open', (done) -> - ipc.once 'opener', (event, opener) -> + listener = (event) -> + assert.equal event.data, 'object' b.close() - done(if opener isnt null then undefined else opener) + done() + window.addEventListener 'message', listener b = window.open url, '', 'show=no' describe 'window.opener.postMessage', -> diff --git a/spec/fixtures/api/beforeunload-false.html b/spec/fixtures/api/beforeunload-false.html index b5a04781103a..e34a1945dd95 100644 --- a/spec/fixtures/api/beforeunload-false.html +++ b/spec/fixtures/api/beforeunload-false.html @@ -3,7 +3,7 @@ diff --git a/spec/fixtures/api/preload.html b/spec/fixtures/api/preload.html index ece481f92413..b0e42ebe2ea3 100644 --- a/spec/fixtures/api/preload.html +++ b/spec/fixtures/api/preload.html @@ -3,7 +3,7 @@ diff --git a/spec/fixtures/api/send-sync-message.html b/spec/fixtures/api/send-sync-message.html index d6fe83f907e1..80950740570a 100644 --- a/spec/fixtures/api/send-sync-message.html +++ b/spec/fixtures/api/send-sync-message.html @@ -1,8 +1,8 @@ diff --git a/spec/fixtures/asar/echo.asar b/spec/fixtures/asar/echo.asar new file mode 100644 index 000000000000..4d72f7a92a7b Binary files /dev/null and b/spec/fixtures/asar/echo.asar differ diff --git a/spec/fixtures/asar/script.asar b/spec/fixtures/asar/script.asar index 152045ad2a6b..7239786ec90e 100755 Binary files a/spec/fixtures/asar/script.asar and b/spec/fixtures/asar/script.asar differ diff --git a/spec/fixtures/asar/web.asar b/spec/fixtures/asar/web.asar index 0c7a788e759d..1e9db65b8128 100644 Binary files a/spec/fixtures/asar/web.asar and b/spec/fixtures/asar/web.asar differ diff --git a/spec/fixtures/module/preload-ipc.js b/spec/fixtures/module/preload-ipc.js index 9b6e0201248e..ed95055c1249 100644 --- a/spec/fixtures/module/preload-ipc.js +++ b/spec/fixtures/module/preload-ipc.js @@ -1,4 +1,4 @@ -var ipc = require('ipc'); -ipc.on('ping', function(message) { - ipc.sendToHost('pong', message); +var ipcRenderer = require('electron').ipcRenderer; +ipcRenderer.on('ping', function(event, message) { + ipcRenderer.sendToHost('pong', message); }); diff --git a/spec/fixtures/module/send-later.js b/spec/fixtures/module/send-later.js index fce96b84b787..13f02452db1c 100644 --- a/spec/fixtures/module/send-later.js +++ b/spec/fixtures/module/send-later.js @@ -1,4 +1,4 @@ -var ipc = require('ipc'); +var ipcRenderer = require('electron').ipcRenderer; window.onload = function() { - ipc.send('answer', typeof window.process); + ipcRenderer.send('answer', typeof window.process); } diff --git a/spec/fixtures/pages/basic-auth.html b/spec/fixtures/pages/basic-auth.html index aa95546a9c11..ec9383ca4d08 100644 --- a/spec/fixtures/pages/basic-auth.html +++ b/spec/fixtures/pages/basic-auth.html @@ -2,7 +2,7 @@ diff --git a/spec/fixtures/pages/beforeunload-false.html b/spec/fixtures/pages/beforeunload-false.html index 7ae4edf4ce29..0b71b07c834a 100644 --- a/spec/fixtures/pages/beforeunload-false.html +++ b/spec/fixtures/pages/beforeunload-false.html @@ -3,8 +3,8 @@ diff --git a/spec/fixtures/pages/history.html b/spec/fixtures/pages/history.html index b5029d638926..6100293fdac4 100644 --- a/spec/fixtures/pages/history.html +++ b/spec/fixtures/pages/history.html @@ -2,7 +2,7 @@ diff --git a/spec/fixtures/pages/ipc-message.html b/spec/fixtures/pages/ipc-message.html index 15bfef49c4da..f543c9abf082 100644 --- a/spec/fixtures/pages/ipc-message.html +++ b/spec/fixtures/pages/ipc-message.html @@ -1,7 +1,7 @@ diff --git a/spec/fixtures/pages/onkeyup.html b/spec/fixtures/pages/onkeyup.html index 99e6c3e98382..4e75dbb1e4ac 100644 --- a/spec/fixtures/pages/onkeyup.html +++ b/spec/fixtures/pages/onkeyup.html @@ -2,7 +2,7 @@ diff --git a/spec/fixtures/pages/onmouseup.html b/spec/fixtures/pages/onmouseup.html index 1fd38bc7211f..123825a28fbd 100644 --- a/spec/fixtures/pages/onmouseup.html +++ b/spec/fixtures/pages/onmouseup.html @@ -2,7 +2,7 @@ diff --git a/spec/fixtures/pages/save_page/index.html b/spec/fixtures/pages/save_page/index.html new file mode 100644 index 000000000000..829233bb86fb --- /dev/null +++ b/spec/fixtures/pages/save_page/index.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/spec/fixtures/pages/save_page/test.css b/spec/fixtures/pages/save_page/test.css new file mode 100644 index 000000000000..f7df9d4fdadb --- /dev/null +++ b/spec/fixtures/pages/save_page/test.css @@ -0,0 +1 @@ +h1 { } diff --git a/spec/fixtures/pages/save_page/test.js b/spec/fixtures/pages/save_page/test.js new file mode 100644 index 000000000000..3d79457383f5 --- /dev/null +++ b/spec/fixtures/pages/save_page/test.js @@ -0,0 +1 @@ +console.log('save_page'); diff --git a/spec/fixtures/pages/window-open-size.html b/spec/fixtures/pages/window-open-size.html index 7b06cfddf551..93b5039f7966 100644 --- a/spec/fixtures/pages/window-open-size.html +++ b/spec/fixtures/pages/window-open-size.html @@ -1,8 +1,8 @@ diff --git a/spec/fixtures/pages/window-opener.html b/spec/fixtures/pages/window-opener.html index 0b5ecd556c9b..58a8b6ea1ae9 100644 --- a/spec/fixtures/pages/window-opener.html +++ b/spec/fixtures/pages/window-opener.html @@ -1,7 +1,10 @@ diff --git a/spec/node-spec.coffee b/spec/node-spec.coffee index 969fc76f41ff..e6b2aa15821c 100644 --- a/spec/node-spec.coffee +++ b/spec/node-spec.coffee @@ -3,7 +3,8 @@ child_process = require 'child_process' fs = require 'fs' path = require 'path' os = require 'os' -remote = require 'remote' + +{remote} = require 'electron' describe 'node feature', -> fixtures = path.join __dirname, 'fixtures' diff --git a/spec/package.json b/spec/package.json index 8f43b711f196..38bd83796701 100644 --- a/spec/package.json +++ b/spec/package.json @@ -5,14 +5,16 @@ "version": "0.1.0", "devDependencies": { "basic-auth": "^1.0.0", - "ffi": "2.0.0", - "formidable": "1.0.16", + "multiparty": "4.1.2", "graceful-fs": "3.0.5", "mocha": "2.1.0", "q": "0.9.7", - "runas": "3.x", "temp": "0.8.1", "walkdir": "0.0.7", "ws": "0.7.2" + }, + "optionalDependencies": { + "ffi": "2.0.0", + "runas": "3.x" } } diff --git a/spec/static/index.html b/spec/static/index.html index 879d769860ed..ea86f6ee302d 100644 --- a/spec/static/index.html +++ b/spec/static/index.html @@ -14,22 +14,25 @@ process.throwDeprecation = true; // Check if we are running in CI. - var argv = require('remote').process.argv; + var electron = require ('electron'); + var remote = electron.remote; + var ipcRenderer = electron.ipcRenderer; + + var argv = remote.process.argv; var isCi = argv[2] == '--ci'; if (!isCi) { - var win = require('remote').getCurrentWindow(); + var win = remote.getCurrentWindow(); win.show(); win.focus(); } // Show DevTools. document.oncontextmenu = function(e) { - require('remote').getCurrentWindow().inspectElement(e.clientX, e.clientY); + remote.getCurrentWindow().inspectElement(e.clientX, e.clientY); } require('coffee-script/register'); // Supports .coffee tests. - var ipc = require('ipc'); // Rediret all output to browser. if (isCi) { @@ -37,11 +40,11 @@ return { log: function() { args = Array.prototype.slice.call(arguments); - ipc.send('console.log', args); + ipcRenderer.send('console.log', args); }, error: function() { args = Array.prototype.slice.call(arguments); - ipc.send('console.error', args); + ipcRenderer.send('console.error', args); }, } }); @@ -70,7 +73,7 @@ var runner = mocha.run(function() { Mocha.utils.highlightTags('code'); if (isCi) - ipc.send('process.exit', runner.failures); + ipcRenderer.send('process.exit', runner.failures); }); }); })(); diff --git a/spec/static/main.js b/spec/static/main.js index 5b10bb6d43d7..be3690cd9e7d 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -1,41 +1,44 @@ -var app = require('app'); -var ipc = require('ipc'); -var dialog = require('dialog'); -var path = require('path'); -var BrowserWindow = require('browser-window'); +const electron = require('electron'); +const app = electron.app; +const ipcMain = electron.ipcMain; +const dialog = electron.dialog; +const BrowserWindow = electron.BrowserWindow; + +const path = require('path'); var window = null; process.port = 0; // will be used by crash-reporter spec. app.commandLine.appendSwitch('js-flags', '--expose_gc'); app.commandLine.appendSwitch('ignore-certificate-errors'); +app.commandLine.appendSwitch('disable-renderer-backgrounding'); // 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) { +ipcMain.on('message', function(event, arg) { event.sender.send('message', arg); }); -ipc.on('console.log', function(event, args) { +ipcMain.on('console.log', function(event, args) { console.error.apply(console, args); }); -ipc.on('console.error', function(event, args) { +ipcMain.on('console.error', function(event, args) { console.error.apply(console, args); }); -ipc.on('process.exit', function(event, code) { +ipcMain.on('process.exit', function(event, code) { process.exit(code); }); -ipc.on('eval', function(event, script) { +ipcMain.on('eval', function(event, script) { event.returnValue = eval(script); }); -ipc.on('echo', function(event, msg) { +ipcMain.on('echo', function(event, msg) { event.returnValue = msg; }); @@ -53,7 +56,7 @@ app.on('window-all-closed', function() { app.on('ready', function() { // Test if using protocol module would crash. - require('protocol').registerStringProtocol('test-if-crashes', function() {}); + electron.protocol.registerStringProtocol('test-if-crashes', function() {}); window = new BrowserWindow({ title: 'Electron Tests', @@ -64,7 +67,7 @@ app.on('ready', function() { javascript: true // Test whether web-preferences crashes. }, }); - window.loadUrl('file://' + __dirname + '/index.html'); + window.loadURL('file://' + __dirname + '/index.html'); window.on('unresponsive', function() { var chosen = dialog.showMessageBox(window, { type: 'warning', @@ -78,14 +81,14 @@ app.on('ready', function() { // For session's download test, listen 'will-download' event in browser, and // reply the result to renderer for verifying var downloadFilePath = path.join(__dirname, '..', 'fixtures', 'mock.pdf'); - require('ipc').on('set-download-option', function(event, need_cancel) { + ipcMain.on('set-download-option', function(event, need_cancel) { window.webContents.session.once('will-download', function(e, item, webContents) { item.setSavePath(downloadFilePath); item.on('done', function(e, state) { window.webContents.send('download-done', state, - item.getUrl(), + item.getURL(), item.getMimeType(), item.getReceivedBytes(), item.getTotalBytes(), diff --git a/toolchain.gypi b/toolchain.gypi index 6977847106f7..23592d0473a7 100644 --- a/toolchain.gypi +++ b/toolchain.gypi @@ -9,6 +9,9 @@ 'sysroot%': '', 'variables': { + # The minimum OS X SDK version to use. + 'mac_sdk_min%': '10.10', + # Set ARM architecture version. 'arm_version%': 7, @@ -17,6 +20,7 @@ }, # Copy conditionally-set variables out one scope. + 'mac_sdk_min%': '<(mac_sdk_min)', 'arm_version%': '<(arm_version)', 'arm_neon%': '<(arm_neon)', @@ -35,6 +39,11 @@ 'source_root': ' [10, 6]""" + return map(int, re.findall(r'(\d+)', version_str)) + + +def main(): + parser = OptionParser() + parser.add_option("--verify", + action="store_true", dest="verify", default=False, + help="return the sdk argument and warn if it doesn't exist") + parser.add_option("--sdk_path", + action="store", type="string", dest="sdk_path", default="", + help="user-specified SDK path; bypasses verification") + parser.add_option("--print_sdk_path", + action="store_true", dest="print_sdk_path", default=False, + help="Additionaly print the path the SDK (appears first).") + options, args = parser.parse_args() + if len(args) != 1: + parser.error('Please specify a minimum SDK version') + min_sdk_version = args[0] + + job = subprocess.Popen(['xcode-select', '-print-path'], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + out, err = job.communicate() + if job.returncode != 0: + print >> sys.stderr, out + print >> sys.stderr, err + raise Exception(('Error %d running xcode-select, you might have to run ' + '|sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer| ' + 'if you are using Xcode 4.') % job.returncode) + # The Developer folder moved in Xcode 4.3. + xcode43_sdk_path = os.path.join( + out.rstrip(), 'Platforms/MacOSX.platform/Developer/SDKs') + if os.path.isdir(xcode43_sdk_path): + sdk_dir = xcode43_sdk_path + else: + sdk_dir = os.path.join(out.rstrip(), 'SDKs') + sdks = [re.findall('^MacOSX(10\.\d+)\.sdk$', s) for s in os.listdir(sdk_dir)] + sdks = [s[0] for s in sdks if s] # [['10.5'], ['10.6']] => ['10.5', '10.6'] + sdks = [s for s in sdks # ['10.5', '10.6'] => ['10.6'] + if parse_version(s) >= parse_version(min_sdk_version)] + if not sdks: + raise Exception('No %s+ SDK found' % min_sdk_version) + best_sdk = sorted(sdks, key=parse_version)[0] + + if options.verify and best_sdk != min_sdk_version and not options.sdk_path: + print >> sys.stderr, '' + print >> sys.stderr, ' vvvvvvv' + print >> sys.stderr, '' + print >> sys.stderr, \ + 'This build requires the %s SDK, but it was not found on your system.' \ + % min_sdk_version + print >> sys.stderr, \ + 'Either install it, or explicitly set mac_sdk in your GYP_DEFINES.' + print >> sys.stderr, '' + print >> sys.stderr, ' ^^^^^^^' + print >> sys.stderr, '' + return min_sdk_version + + if options.print_sdk_path: + print subprocess.check_output(['xcodebuild', '-version', '-sdk', + 'macosx' + best_sdk, 'Path']).strip() + + return best_sdk + + +if __name__ == '__main__': + if sys.platform != 'darwin': + raise Exception("This script only runs on Mac") + print main() + sys.exit(0) diff --git a/vendor/brightray b/vendor/brightray index 8e443520e695..9b4d052d2af7 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 8e443520e695674fd26585cfa24a0ec0b6140c27 +Subproject commit 9b4d052d2af716c340034ed7815d9d758cd7804d diff --git a/vendor/crashpad b/vendor/crashpad index e6a0d433b0ee..5b777419c303 160000 --- a/vendor/crashpad +++ b/vendor/crashpad @@ -1 +1 @@ -Subproject commit e6a0d433b0ee399eecce2bef671794771052ffdb +Subproject commit 5b777419c303d8aa7930239d8ef755475f1ede57 diff --git a/vendor/native_mate b/vendor/native_mate index b7387da0854b..5e70868fd0c0 160000 --- a/vendor/native_mate +++ b/vendor/native_mate @@ -1 +1 @@ -Subproject commit b7387da0854b20d376fdae0d93a01f83d080668d +Subproject commit 5e70868fd0c005dc2c43bea15ca6e93da0b68741 diff --git a/vendor/node b/vendor/node index d64246490d69..38d791843463 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit d64246490d697f387b888391b1aba65032703a0f +Subproject commit 38d791843463b19c623c97c1c550a4e3c5a406d4