diff --git a/.gitignore b/.gitignore index eb9aedb4e2f4..a92239801a45 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ /external_binaries/ /out/ /vendor/brightray/vendor/download/ +/vendor/debian_wheezy_amd64-sysroot/ /vendor/debian_wheezy_arm-sysroot/ /vendor/debian_wheezy_i386-sysroot/ /vendor/python_26/ diff --git a/.gitmodules b/.gitmodules index c6e53ed39a69..6164ed8b9706 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,21 +1,21 @@ [submodule "vendor/brightray"] path = vendor/brightray - url = https://github.com/atom/brightray.git + url = https://github.com/electron/brightray.git [submodule "vendor/node"] path = vendor/node - url = https://github.com/atom/node.git + url = https://github.com/electron/node.git [submodule "vendor/depot_tools"] path = vendor/depot_tools url = https://chromium.googlesource.com/chromium/tools/depot_tools.git [submodule "vendor/breakpad"] path = vendor/breakpad - url = https://github.com/atom/chromium-breakpad.git + url = https://github.com/electron/chromium-breakpad.git [submodule "vendor/native_mate"] path = vendor/native_mate url = https://github.com/zcbenz/native-mate.git [submodule "vendor/crashpad"] path = vendor/crashpad - url = https://github.com/atom/crashpad.git + url = https://github.com/electron/crashpad.git [submodule "vendor/requests"] path = vendor/requests url = https://github.com/kennethreitz/requests diff --git a/.node-version b/.node-version new file mode 100644 index 000000000000..339cbf236a73 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +v5.10.0 diff --git a/.travis.yml b/.travis.yml index 8b4343138994..75a5a26c6c71 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,8 +19,7 @@ matrix: - os: linux env: TARGET_ARCH=ia32 allow_failures: - - env: TARGET_ARCH=arm - - env: TARGET_ARCH=ia32 + - os: osx script: './script/cibuild' diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 444ce0b4c821..e4d4c531e5a3 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,24 +1,46 @@ -# Contributor Code of Conduct +# Contributor Covenant 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. +## Our Pledge -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. +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members 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 +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 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. +## Scope -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. -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. +## Enforcement -This Code of Conduct is adapted from the Contributor Covenant, version 1.3.0, available from http://contributor-covenant.org/version/1/3/0/ +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [electron@github.com](mailto:electron@github.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING-ko.md b/CONTRIBUTING-ko.md index 52887c8d38a7..fe3eb90b68cf 100644 --- a/CONTRIBUTING-ko.md +++ b/CONTRIBUTING-ko.md @@ -1,10 +1,10 @@ # Electron에 기여하기 -:+1::tada: 먼저, 이 프로젝트에 기여해주셔서 감사합니다! :tada::+1: +:+1::tada: 먼저, 기여에 관심을 가져주셔서 감사합니다! :tada::+1: -이 프로젝트는 기여자 규약 [행동강령](CODE_OF_CONDUCT.md)을 준수합니다. 따라서 이 +이 프로젝트는 기여자 규약인 [행동강령](CODE_OF_CONDUCT.md)을 준수합니다. 따라서 이 프로젝트의 개발에 참여하려면 이 규약을 지켜야 합니다. 받아들일 수 없는 행위를 발견했을 -경우 atom@github.com로 보고 하십시오. +경우 atom@github.com로 보고하세요. 다음 항목들은 Electron에 기여하는 가이드라인을 제시합니다. 참고로 이 항목들은 그저 가이드라인에 불과하며 규칙이 아닙니다. 따라서 스스로의 적절한 @@ -12,7 +12,7 @@ ## 이슈 제출 -* [여기](https://github.com/atom/electron/issues/new)에서 새로운 이슈를 만들 수 +* [여기](https://github.com/electron/electron/issues/new)에서 새로운 이슈를 만들 수 있습니다. 하지만 이슈를 작성하기 전에 아래의 항목들을 숙지하고 가능한한 이슈 보고에 대해 최대한 많은 정보와 자세한 설명을 포함해야 합니다. 가능하다면 다음 항목을 포함해야 합니다: @@ -23,18 +23,18 @@ * 추가로 다음 사항을 준수하면 이슈를 해결하는데 큰 도움이 됩니다: * 스크린샷 또는 GIF 애니메이션 이미지들 * 터미널에 출력된 에러의 내용 또는 개발자 도구, 알림창에 뜬 내용 - * [Cursory search](https://github.com/atom/electron/issues?utf8=✓&q=is%3Aissue+)를 + * [Cursory search](https://github.com/electron/electron/issues?utf8=✓&q=is%3Aissue+)를 통해 이미 비슷한 내용의 이슈가 등록되어있는지 확인 ## Pull Request 하기 * 가능한한 스크린샷과 GIF 애니메이션 이미지를 pull request에 추가 -* CoffeeScript, JavaScript, C++과 Python등 -[참조문서에 정의된 코딩스타일](/docs-translations/ko-KR/development/coding-style.md)을 +* JavaScript, C++과 Python등 +[참조 문서에 정의된 코딩스타일](/docs-translations/ko-KR/development/coding-style.md)을 준수 * [문서 스타일 가이드](/docs-translations/ko-KR/styleguide.md)에 따라 문서를 [Markdown](https://daringfireball.net/projects/markdown) 형식으로 작성. -* 짧은, 현재 시제 커밋 메시지 사용. [커밋 메시지 스타일 가이드](#Git-커밋-메시지)를 +* 짧은, 현재 시제 커밋 메시지 사용. [커밋 메시지 스타일 가이드](#git-커밋-메시지)를 참고하세요 ## 스타일 가이드 @@ -58,7 +58,7 @@ ### Git 커밋 메시지 * 현재 시제 사용 ("Added feature" 대신 "Add feature" 사용) -* 필수적 분위기(imperative mood) 사용 ("Moves cursor to..." 대신 "Move cursor to..." 사용) +* 명령법(imperative mood) 사용 ("Moves cursor to..." 대신 "Move cursor to..." 사용) * 첫 줄은 72자에 맞추거나 그 보다 적게 제한 * 자유롭게 필요에 따라 이슈나 PR링크를 참조 * 단순한 문서 변경일 경우 `[ci skip]`을 커밋 메시지에 추가 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 67c1323205e5..d6bc3cb2d67d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,7 @@ propose changes to this document in a pull request. ## Submitting Issues -* You can create an issue [here](https://github.com/atom/electron/issues/new), +* You can create an issue [here](https://github.com/electron/electron/issues/new), but before doing that please read the notes below and include as many details as possible with your report. If you can, please include: * The version of Electron you are using @@ -22,13 +22,13 @@ possible with your report. If you can, please include: * Other things that will help resolve your issue: * Screenshots and animated GIFs * Error output that appears in your terminal, dev tools or as an alert - * Perform a [cursory search](https://github.com/atom/electron/issues?utf8=✓&q=is%3Aissue+) + * Perform a [cursory search](https://github.com/electron/electron/issues?utf8=✓&q=is%3Aissue+) to see if a similar issue has already been submitted ## Submitting Pull Requests * Include screenshots and animated GIFs in your pull request whenever possible. -* Follow the CoffeeScript, JavaScript, C++ and Python [coding style defined in docs](/docs/development/coding-style.md). +* Follow the JavaScript, C++, and Python [coding style defined in docs](/docs/development/coding-style.md). * Write documentation in [Markdown](https://daringfireball.net/projects/markdown). See the [Documentation Styleguide](/docs/styleguide.md). * Use short, present tense commit messages. See [Commit Message Styleguide](#git-commit-messages). diff --git a/README-ko.md b/README-ko.md index b9085828d468..7cabe5ed4564 100644 --- a/README-ko.md +++ b/README-ko.md @@ -1,30 +1,30 @@ [![Electron Logo](http://electron.atom.io/images/electron-logo.svg)](http://electron.atom.io/) -[![Build Status](https://travis-ci.org/atom/electron.svg?branch=master)](https://travis-ci.org/atom/electron) -[![devDependency Status](https://david-dm.org/atom/electron/dev-status.svg)](https://david-dm.org/atom/electron#info=devDependencies) +[![Travis Build Status](https://travis-ci.org/electron/electron.svg?branch=master)](https://travis-ci.org/electron/electron) +[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/kvxe4byi7jcxbe26/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/electron) +[![devDependency Status](https://david-dm.org/electron/electron/dev-status.svg)](https://david-dm.org/electron/electron#info=devDependencies) [![Join the Electron Community on Slack](http://atom-slack.herokuapp.com/badge.svg)](http://atom-slack.herokuapp.com/) -### [Electron](https://github.com/atom/electron/) 한국어 참조문서 - -:zap: *프레임워크 이름이 Atom Shell에서 Electron으로 변경되었습니다* :zap: +### [Electron](https://github.com/electron/electron/) 한국어 참조 문서 Electron 프레임워크는 JavaScript, HTML 그리고 CSS를 사용하여 Cross-Platform 데스크톱 어플리케이션을 개발할 수 있도록 해주는 프레임워크입니다. [Node.js](https://nodejs.org/)와 [Chromium](http://www.chromium.org)을 기반으로 만들어졌으며 [Atom Editor](https://github.com/atom/atom)에 사용되고 있습니다. +더 많은 어플리케이션은 [이곳](http://electron.atom.io/apps)에서 확인하세요. Electron에 대한 중요한 알림을 받고 싶다면 Twitter에서 [@ElectronJS](https://twitter.com/electronjs)를 팔로우 하세요. -이 프로젝트는 기여자 규약 [행동강령](CODE_OF_CONDUCT.md)을 준수합니다. 따라서 이 +이 프로젝트는 기여자 규약인 [행동강령](CODE_OF_CONDUCT.md)을 준수합니다. 따라서 이 프로젝트의 개발에 참여하려면 이 규약을 지켜야 합니다. 받아들일 수 없는 행위를 발견했을 -경우 atom@github.com로 보고 하십시오. +경우 electron@github.com로 보고하세요. ## 다운로드 Linux, Windows, OS X 용으로 미리 빌드된 Electron 바이너리와 디버그 심볼이 준비되어 -있습니다. [releases](https://github.com/atom/electron/releases) 페이지에서 받아 볼 -수 있습니다. +있습니다. [releases](https://github.com/electron/electron/releases) 페이지에서 +받아 볼 수 있습니다. 또한 [`npm`](https://docs.npmjs.com/)을 통해 미리 빌드된 Electron 바이너리를 설치할 수도 있습니다: @@ -43,25 +43,26 @@ npm install electron-prebuilt --save-dev ## 참조 문서 -[Docs](https://github.com/atom/electron/tree/master/docs/README.md)에 개발 지침과 -API 레퍼런스가 있습니다. Electron을 빌드 하는 방법과 프로젝트에 기여하는법 또한 문서에 -포함되어 있으니 참고하시기 바랍니다. +[Docs](https://github.com/electron/electron/tree/master/docs-translations/ko-KR/README.md)에 +개발 지침과 API 레퍼런스가 있습니다. Electron을 빌드 하는 방법과 프로젝트에 기여하는법 +또한 문서에 포함되어 있으니 참고하시기 바랍니다. ## 참조 문서 (번역) -- [브라질 포르투갈어](https://github.com/atom/electron/tree/master/docs-translations/pt-BR) -- [한국어](https://github.com/atom/electron/tree/master/docs-translations/ko-KR) -- [일본어](https://github.com/atom/electron/tree/master/docs-translations/jp) -- [스페인어](https://github.com/atom/electron/tree/master/docs-translations/es) -- [중국어 간체](https://github.com/atom/electron/tree/master/docs-translations/zh-CN) -- [중국어 번체](https://github.com/atom/electron/tree/master/docs-translations/zh-TW) -- [우크라이나어](https://github.com/atom/electron/tree/master/docs-translations/uk-UA) -- [러시아어](https://github.com/atom/electron/tree/master/docs-translations/ru-RU) -- [프랑스어](https://github.com/atom/electron/tree/master/docs-translations/fr-FR) +- [브라질 포르투갈어](https://github.com/electron/electron/tree/master/docs-translations/pt-BR) +- [한국어](https://github.com/electron/electron/tree/master/docs-translations/ko-KR) +- [일본어](https://github.com/electron/electron/tree/master/docs-translations/jp) +- [스페인어](https://github.com/electron/electron/tree/master/docs-translations/es) +- [중국어 간체](https://github.com/electron/electron/tree/master/docs-translations/zh-CN) +- [중국어 번체](https://github.com/electron/electron/tree/master/docs-translations/zh-TW) +- [터키어](https://github.com/electron/electron/tree/master/docs-translations/tr-TR) +- [우크라이나어](https://github.com/electron/electron/tree/master/docs-translations/uk-UA) +- [러시아어](https://github.com/electron/electron/tree/master/docs-translations/ru-RU) +- [프랑스어](https://github.com/electron/electron/tree/master/docs-translations/fr-FR) ## 시작하기 -[`atom/electron-quick-start`](https://github.com/atom/electron-quick-start) +[`electron/electron-quick-start`](https://github.com/electron/electron-quick-start) 저장소를 클론하여 Electron을 간단히 접해볼 수 있습니다. ## 커뮤니티 @@ -71,9 +72,9 @@ API 레퍼런스가 있습니다. Electron을 빌드 하는 방법과 프로젝 - Atom 포럼의 [`electron`](http://discuss.atom.io/c/electron) 카테고리 - Freenode 채팅의 `#atom-shell` 채널 - Slack의 [`Atom`](http://atom-slack.herokuapp.com/) 채널 -- [`electron-br`](https://electron-br.slack.com) *(브라질 포르투갈어)* 커뮤니티 -- [`electron-kr`](http://www.meetup.com/electron-kr/) *(한국어)* 커뮤니티 +- [`electron-br`](https://electron-br.slack.com) *(브라질)* 커뮤니티 +- [`electron-kr`](http://www.meetup.com/electron-kr/) *(한국)* 커뮤니티 +- [`electron-jp`](https://electron-jp-slackin.herokuapp.com/) *(일본)* 커뮤니티 [awesome-electron](https://github.com/sindresorhus/awesome-electron) 프로젝트에 -커뮤니티가 운영중인 유용한 예제 어플리케이션과 도구, 리소스가 있으니 한번 참고해 보시기 -바랍니다. +커뮤니티가 운영중인 유용한 예시 어플리케이션과 도구, 리소스가 있으니 참고하기 바랍니다. diff --git a/README.md b/README.md index 0080585b54cd..b1e0d6b1b9a9 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,26 @@ [![Electron Logo](http://electron.atom.io/images/electron-logo.svg)](http://electron.atom.io/) -[![Build Status](https://travis-ci.org/atom/electron.svg?branch=master)](https://travis-ci.org/atom/electron) -[![devDependency Status](https://david-dm.org/atom/electron/dev-status.svg)](https://david-dm.org/atom/electron#info=devDependencies) +[![Travis Build Status](https://travis-ci.org/electron/electron.svg?branch=master)](https://travis-ci.org/electron/electron) +[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/kvxe4byi7jcxbe26/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/electron) +[![devDependency Status](https://david-dm.org/electron/electron/dev-status.svg)](https://david-dm.org/electron/electron#info=devDependencies) [![Join the Electron Community on Slack](http://atom-slack.herokuapp.com/badge.svg)](http://atom-slack.herokuapp.com/) -:zap: *Formerly known as Atom Shell* :zap: - The Electron framework lets you write cross-platform desktop applications using JavaScript, HTML and CSS. It is based on [Node.js](https://nodejs.org/) and -[Chromium](http://www.chromium.org) and is used in the [Atom -editor](https://github.com/atom/atom). +[Chromium](http://www.chromium.org) and is used by the [Atom +editor](https://github.com/atom/atom) and many other [apps](http://electron.atom.io/apps). Follow [@ElectronJS](https://twitter.com/electronjs) on Twitter for important announcements. 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. +behavior to electron@github.com. ## Downloads 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. +be found on the [releases](https://github.com/electron/electron/releases) page. You can also use [`npm`](https://docs.npmjs.com/) to install prebuilt electron binaries: @@ -41,24 +40,25 @@ npm install electron-prebuilt --save-dev ## Documentation Guides and the API reference are located in the -[docs](https://github.com/atom/electron/tree/master/docs) directory. It also +[docs](https://github.com/electron/electron/tree/master/docs) directory. It also contains documents describing how to build and contribute to Electron. ## Documentation Translations -- [Brazilian Portuguese](https://github.com/atom/electron/tree/master/docs-translations/pt-BR) -- [Korean](https://github.com/atom/electron/tree/master/docs-translations/ko-KR) -- [Japanese](https://github.com/atom/electron/tree/master/docs-translations/jp) -- [Spanish](https://github.com/atom/electron/tree/master/docs-translations/es) -- [Simplified Chinese](https://github.com/atom/electron/tree/master/docs-translations/zh-CN) -- [Traditional Chinese](https://github.com/atom/electron/tree/master/docs-translations/zh-TW) -- [Ukrainian](https://github.com/atom/electron/tree/master/docs-translations/uk-UA) -- [Russian](https://github.com/atom/electron/tree/master/docs-translations/ru-RU) -- [French](https://github.com/atom/electron/tree/master/docs-translations/fr-FR) +- [Brazilian Portuguese](https://github.com/electron/electron/tree/master/docs-translations/pt-BR) +- [Korean](https://github.com/electron/electron/tree/master/docs-translations/ko-KR) +- [Japanese](https://github.com/electron/electron/tree/master/docs-translations/jp) +- [Spanish](https://github.com/electron/electron/tree/master/docs-translations/es) +- [Simplified Chinese](https://github.com/electron/electron/tree/master/docs-translations/zh-CN) +- [Traditional Chinese](https://github.com/electron/electron/tree/master/docs-translations/zh-TW) +- [Turkish](https://github.com/electron/electron/tree/master/docs-translations/tr-TR) +- [Ukrainian](https://github.com/electron/electron/tree/master/docs-translations/uk-UA) +- [Russian](https://github.com/electron/electron/tree/master/docs-translations/ru-RU) +- [French](https://github.com/electron/electron/tree/master/docs-translations/fr-FR) ## Quick Start -Clone and run the [`atom/electron-quick-start`](https://github.com/atom/electron-quick-start) +Clone and run the [`electron/electron-quick-start`](https://github.com/electron/electron-quick-start) repository to see a minimal Electron app in action. ## Community diff --git a/appveyor.yml b/appveyor.yml index 0fa0c0d9bddd..b093d2a2126e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,6 +2,8 @@ # http://www.appveyor.com/docs/appveyor-yml version: "{build}" +os: Visual Studio 2015 + init: - git config --global core.autocrlf input @@ -10,7 +12,7 @@ platform: - x64 install: - - cmd: SET PATH=C:\Program Files (x86)\MSBuild\12.0\bin\;%PATH% + - cmd: SET PATH=C:\Program Files (x86)\MSBuild\14.0\bin\;%PATH% - cmd: SET PATH=C:\python27;%PATH% - cmd: python script/cibuild diff --git a/atom/app/atom_content_client.cc b/atom/app/atom_content_client.cc index 25e9d0f1932a..40a6e1f5268d 100644 --- a/atom/app/atom_content_client.cc +++ b/atom/app/atom_content_client.cc @@ -89,11 +89,9 @@ content::PepperPluginInfo CreateWidevineCdmInfo(const base::FilePath& path, // Add the supported codecs as if they came from the component manifest. std::vector codecs; - codecs.push_back(kCdmSupportedCodecVorbis); codecs.push_back(kCdmSupportedCodecVp8); codecs.push_back(kCdmSupportedCodecVp9); #if defined(USE_PROPRIETARY_CODECS) - codecs.push_back(kCdmSupportedCodecAac); codecs.push_back(kCdmSupportedCodecAvc1); #endif // defined(USE_PROPRIETARY_CODECS) std::string codec_string = base::JoinString( @@ -126,7 +124,7 @@ void ConvertStringWithSeparatorToVector(std::vector* vec, void AddPepperFlashFromCommandLine( std::vector* plugins) { auto command_line = base::CommandLine::ForCurrentProcess(); - auto flash_path = command_line->GetSwitchValueNative( + base::FilePath flash_path = command_line->GetSwitchValuePath( switches::kPpapiFlashPath); if (flash_path.empty()) return; @@ -134,20 +132,19 @@ void AddPepperFlashFromCommandLine( auto flash_version = command_line->GetSwitchValueASCII( switches::kPpapiFlashVersion); - plugins->push_back( - CreatePepperFlashInfo(base::FilePath(flash_path), flash_version)); + plugins->push_back(CreatePepperFlashInfo(flash_path, flash_version)); } #if defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS) void AddWidevineCdmFromCommandLine( std::vector* plugins) { auto command_line = base::CommandLine::ForCurrentProcess(); - auto widevine_cdm_path = command_line->GetSwitchValueNative( + base::FilePath widevine_cdm_path = command_line->GetSwitchValuePath( switches::kWidevineCdmPath); if (widevine_cdm_path.empty()) return; - if (!base::PathExists(base::FilePath(widevine_cdm_path))) + if (!base::PathExists(widevine_cdm_path)) return; auto widevine_cdm_version = command_line->GetSwitchValueASCII( @@ -155,7 +152,7 @@ void AddWidevineCdmFromCommandLine( if (widevine_cdm_version.empty()) return; - plugins->push_back(CreateWidevineCdmInfo(base::FilePath(widevine_cdm_path), + plugins->push_back(CreateWidevineCdmInfo(widevine_cdm_path, widevine_cdm_version)); } #endif @@ -182,14 +179,8 @@ base::string16 AtomContentClient::GetLocalizedString(int message_id) const { void AtomContentClient::AddAdditionalSchemes( std::vector* standard_schemes, + std::vector* referrer_schemes, std::vector* savable_schemes) { - std::vector schemes; - ConvertStringWithSeparatorToVector(&schemes, ",", - switches::kRegisterStandardSchemes); - if (!schemes.empty()) { - for (const std::string& scheme : schemes) - standard_schemes->push_back({scheme.c_str(), url::SCHEME_WITHOUT_PORT}); - } standard_schemes->push_back({"chrome-extension", url::SCHEME_WITHOUT_PORT}); } diff --git a/atom/app/atom_content_client.h b/atom/app/atom_content_client.h index 33dbe19d990e..f31a14605723 100644 --- a/atom/app/atom_content_client.h +++ b/atom/app/atom_content_client.h @@ -25,6 +25,7 @@ class AtomContentClient : public brightray::ContentClient { base::string16 GetLocalizedString(int message_id) const override; void AddAdditionalSchemes( std::vector* standard_schemes, + std::vector* referrer_schemes, std::vector* savable_schemes) override; void AddPepperPlugins( std::vector* plugins) override; diff --git a/atom/app/atom_library_main.h b/atom/app/atom_library_main.h index 899861f610b3..e1603fa5942f 100644 --- a/atom/app/atom_library_main.h +++ b/atom/app/atom_library_main.h @@ -5,7 +5,7 @@ #ifndef ATOM_APP_ATOM_LIBRARY_MAIN_H_ #define ATOM_APP_ATOM_LIBRARY_MAIN_H_ -#include "base/basictypes.h" +#include "build/build_config.h" #if defined(OS_MACOSX) extern "C" { diff --git a/atom/app/atom_main.cc b/atom/app/atom_main.cc index 1ffab8d9225a..a3e702700265 100644 --- a/atom/app/atom_main.cc +++ b/atom/app/atom_main.cc @@ -15,8 +15,9 @@ #include "atom/app/atom_main_delegate.h" #include "atom/common/crash_reporter/win/crash_service_main.h" #include "base/environment.h" +#include "base/process/launch.h" #include "base/win/windows_version.h" -#include "content/public/app/startup_helper_win.h" +#include "content/public/app/sandbox_helper_win.h" #include "sandbox/win/src/sandbox_types.h" #include "ui/gfx/win/dpi.h" #elif defined(OS_LINUX) // defined(OS_WIN) @@ -34,7 +35,6 @@ 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) @@ -47,50 +47,6 @@ bool IsEnvSet(const char* name) { #endif } -bool IsRunAsNode() { - return IsEnvSet(kRunAsNode) || IsEnvSet(kOldRunAsNode); -} - -#if defined(OS_WIN) -// Win8.1 supports monitor-specific DPI scaling. -bool SetProcessDpiAwarenessWrapper(PROCESS_DPI_AWARENESS value) { - typedef HRESULT(WINAPI *SetProcessDpiAwarenessPtr)(PROCESS_DPI_AWARENESS); - SetProcessDpiAwarenessPtr set_process_dpi_awareness_func = - reinterpret_cast( - GetProcAddress(GetModuleHandleA("user32.dll"), - "SetProcessDpiAwarenessInternal")); - if (set_process_dpi_awareness_func) { - HRESULT hr = set_process_dpi_awareness_func(value); - if (SUCCEEDED(hr)) { - VLOG(1) << "SetProcessDpiAwareness succeeded."; - return true; - } else if (hr == E_ACCESSDENIED) { - LOG(ERROR) << "Access denied error from SetProcessDpiAwareness. " - "Function called twice, or manifest was used."; - } - } - return false; -} - -// This function works for Windows Vista through Win8. Win8.1 must use -// SetProcessDpiAwareness[Wrapper]. -BOOL SetProcessDPIAwareWrapper() { - typedef BOOL(WINAPI *SetProcessDPIAwarePtr)(VOID); - SetProcessDPIAwarePtr set_process_dpi_aware_func = - reinterpret_cast( - GetProcAddress(GetModuleHandleA("user32.dll"), - "SetProcessDPIAware")); - return set_process_dpi_aware_func && - set_process_dpi_aware_func(); -} - -void EnableHighDPISupport() { - if (!SetProcessDpiAwarenessWrapper(PROCESS_SYSTEM_DPI_AWARE)) { - SetProcessDPIAwareWrapper(); - } -} -#endif - } // namespace #if defined(OS_WIN) @@ -98,14 +54,11 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { int argc = 0; wchar_t** wargv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); - // Make output work in console if we are not in cygiwn. - if (!IsEnvSet("TERM") && !IsEnvSet("ELECTRON_NO_ATTACH_CONSOLE")) { - AttachConsole(ATTACH_PARENT_PROCESS); + bool run_as_node = IsEnvSet(kRunAsNode); - FILE* dontcare; - freopen_s(&dontcare, "CON", "w", stdout); - freopen_s(&dontcare, "CON", "w", stderr); - } + // Make sure the output is printed to console. + if (run_as_node || !IsEnvSet("ELECTRON_NO_ATTACH_CONSOLE")) + base::RouteStdioToConsole(false); // Convert argv to to UTF8 char** argv = new char*[argc]; @@ -141,12 +94,12 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { } } - if (IsRunAsNode()) { + if (run_as_node) { // Now that argv conversion is done, we can finally start. base::AtExitManager atexit_manager; base::i18n::InitializeICU(); return atom::NodeMain(argc, argv); - } else if (IsEnvSet("ATOM_SHELL_INTERNAL_CRASH_SERVICE")) { + } else if (IsEnvSet("ELECTRON_INTERNAL_CRASH_SERVICE")) { return crash_service::Main(cmd); } @@ -154,23 +107,18 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { content::InitializeSandboxInfo(&sandbox_info); atom::AtomMainDelegate delegate; - // We don't want to set DPI awareness on pre-Win7 because we don't support - // DirectWrite there. GDI fonts are kerned very badly, so better to leave - // DPI-unaware and at effective 1.0. See also ShouldUseDirectWrite(). - if (base::win::GetVersion() >= base::win::VERSION_WIN7) - EnableHighDPISupport(); - content::ContentMainParams params(&delegate); params.instance = instance; params.sandbox_info = &sandbox_info; atom::AtomCommandLine::Init(argc, argv); + atom::AtomCommandLine::InitW(argc, wargv); return content::ContentMain(params); } #elif defined(OS_LINUX) // defined(OS_WIN) int main(int argc, const char* argv[]) { - if (IsRunAsNode()) { + if (IsEnvSet(kRunAsNode)) { base::i18n::InitializeICU(); base::AtExitManager atexit_manager; return atom::NodeMain(argc, const_cast(argv)); @@ -187,7 +135,7 @@ int main(int argc, const char* argv[]) { #else // defined(OS_LINUX) int main(int argc, const char* argv[]) { - if (IsRunAsNode()) { + if (IsEnvSet(kRunAsNode)) { return AtomInitializeICUandStartNode(argc, const_cast(argv)); } diff --git a/atom/app/atom_main_delegate.cc b/atom/app/atom_main_delegate.cc index 698da4f4def7..f4e0b6044481 100644 --- a/atom/app/atom_main_delegate.cc +++ b/atom/app/atom_main_delegate.cc @@ -9,6 +9,7 @@ #include "atom/app/atom_content_client.h" #include "atom/browser/atom_browser_client.h" +#include "atom/browser/relauncher.h" #include "atom/common/google_api_key.h" #include "atom/renderer/atom_renderer_client.h" #include "atom/utility/atom_content_utility_client.h" @@ -25,11 +26,20 @@ namespace atom { namespace { +const char* kRelauncherProcess = "relauncher"; + bool IsBrowserProcess(base::CommandLine* cmd) { std::string process_type = cmd->GetSwitchValueASCII(switches::kProcessType); return process_type.empty(); } +#if defined(OS_WIN) +void InvalidParameterHandler(const wchar_t*, const wchar_t*, const wchar_t*, + unsigned int, uintptr_t) { + // noop. +} +#endif + } // namespace AtomMainDelegate::AtomMainDelegate() { @@ -61,7 +71,7 @@ bool AtomMainDelegate::BasicStartupComplete(int* exit_code) { #endif // !defined(OS_WIN) // Only enable logging when --enable-logging is specified. - scoped_ptr env(base::Environment::Create()); + std::unique_ptr env(base::Environment::Create()); if (!command_line->HasSwitch(switches::kEnableLogging) && !env->HasVar("ELECTRON_ENABLE_LOGGING")) { settings.logging_dest = logging::LOG_NONE; @@ -83,6 +93,15 @@ bool AtomMainDelegate::BasicStartupComplete(int* exit_code) { chrome::RegisterPathProvider(); +#if defined(OS_MACOSX) + SetUpBundleOverrides(); +#endif + +#if defined(OS_WIN) + // Ignore invalid parameter errors. + _set_invalid_parameter_handler(InvalidParameterHandler); +#endif + return brightray::MainDelegate::BasicStartupComplete(exit_code); } @@ -90,7 +109,7 @@ void AtomMainDelegate::PreSandboxStartup() { brightray::MainDelegate::PreSandboxStartup(); // Set google API key. - scoped_ptr env(base::Environment::Create()); + std::unique_ptr env(base::Environment::Create()); if (!env->HasVar("GOOGLE_API_KEY")) env->SetVar("GOOGLE_API_KEY", GOOGLEAPIS_API_KEY); @@ -98,10 +117,6 @@ void AtomMainDelegate::PreSandboxStartup() { std::string process_type = command_line->GetSwitchValueASCII( switches::kProcessType); - if (process_type == switches::kUtilityProcess) { - AtomContentUtilityClient::PreSandboxStartup(); - } - // Only append arguments for browser process. if (!IsBrowserProcess(command_line)) return; @@ -134,8 +149,29 @@ content::ContentUtilityClient* AtomMainDelegate::CreateContentUtilityClient() { return utility_client_.get(); } -scoped_ptr AtomMainDelegate::CreateContentClient() { - return scoped_ptr(new AtomContentClient).Pass(); +int AtomMainDelegate::RunProcess( + const std::string& process_type, + const content::MainFunctionParams& main_function_params) { + if (process_type == kRelauncherProcess) + return relauncher::RelauncherMain(main_function_params); + else + return -1; +} + +#if defined(OS_MACOSX) +bool AtomMainDelegate::ShouldSendMachPort(const std::string& process_type) { + return process_type != kRelauncherProcess; +} + +bool AtomMainDelegate::DelaySandboxInitialization( + const std::string& process_type) { + return process_type == kRelauncherProcess; +} +#endif + +std::unique_ptr +AtomMainDelegate::CreateContentClient() { + return std::unique_ptr(new AtomContentClient); } } // namespace atom diff --git a/atom/app/atom_main_delegate.h b/atom/app/atom_main_delegate.h index 5f4369302f5f..58a380bd7d5a 100644 --- a/atom/app/atom_main_delegate.h +++ b/atom/app/atom_main_delegate.h @@ -5,6 +5,8 @@ #ifndef ATOM_APP_ATOM_MAIN_DELEGATE_H_ #define ATOM_APP_ATOM_MAIN_DELEGATE_H_ +#include + #include "brightray/common/main_delegate.h" #include "brightray/common/content_client.h" @@ -22,19 +24,30 @@ class AtomMainDelegate : public brightray::MainDelegate { content::ContentBrowserClient* CreateContentBrowserClient() override; content::ContentRendererClient* CreateContentRendererClient() override; content::ContentUtilityClient* CreateContentUtilityClient() override; + int RunProcess( + const std::string& process_type, + const content::MainFunctionParams& main_function_params) override; +#if defined(OS_MACOSX) + bool ShouldSendMachPort(const std::string& process_type) override; + bool DelaySandboxInitialization(const std::string& process_type) override; +#endif // brightray::MainDelegate: - scoped_ptr CreateContentClient() override; + std::unique_ptr CreateContentClient() override; #if defined(OS_MACOSX) void OverrideChildProcessPath() override; void OverrideFrameworkBundlePath() override; #endif private: +#if defined(OS_MACOSX) + void SetUpBundleOverrides(); +#endif + brightray::ContentClient content_client_; - scoped_ptr browser_client_; - scoped_ptr renderer_client_; - scoped_ptr utility_client_; + std::unique_ptr browser_client_; + std::unique_ptr renderer_client_; + std::unique_ptr utility_client_; DISALLOW_COPY_AND_ASSIGN(AtomMainDelegate); }; diff --git a/atom/app/atom_main_delegate_mac.mm b/atom/app/atom_main_delegate_mac.mm index 33a8dea4aabb..ea5ab320a4d0 100644 --- a/atom/app/atom_main_delegate_mac.mm +++ b/atom/app/atom_main_delegate_mac.mm @@ -7,7 +7,10 @@ #include "base/mac/bundle_locations.h" #include "base/files/file_path.h" #include "base/files/file_util.h" +#include "base/mac/foundation_util.h" +#include "base/mac/scoped_nsautorelease_pool.h" #include "base/path_service.h" +#include "base/strings/sys_string_conversions.h" #include "brightray/common/application_info.h" #include "brightray/common/mac/main_application_bundle.h" #include "content/public/common/content_paths.h" @@ -48,4 +51,15 @@ void AtomMainDelegate::OverrideChildProcessPath() { PathService::Override(content::CHILD_PROCESS_EXE, helper_path); } +void AtomMainDelegate::SetUpBundleOverrides() { + base::mac::ScopedNSAutoreleasePool pool; + NSBundle* bundle = brightray::MainApplicationBundle(); + std::string base_bundle_id = + base::SysNSStringToUTF8([bundle bundleIdentifier]); + NSString* team_id = [bundle objectForInfoDictionaryKey:@"ElectronTeamID"]; + if (team_id) + base_bundle_id = base::SysNSStringToUTF8(team_id) + "." + base_bundle_id; + base::mac::SetBaseBundleID(base_bundle_id.c_str()); +} + } // namespace atom diff --git a/atom/app/node_main.cc b/atom/app/node_main.cc index b946ae28ff94..6f71d39f5740 100644 --- a/atom/app/node_main.cc +++ b/atom/app/node_main.cc @@ -7,8 +7,9 @@ #include "atom/app/uv_task_runner.h" #include "atom/browser/javascript_environment.h" #include "atom/browser/node_debugger.h" -#include "base/command_line.h" #include "atom/common/node_includes.h" +#include "base/command_line.h" +#include "base/feature_list.h" #include "base/thread_task_runner_handle.h" #include "gin/array_buffer.h" #include "gin/public/isolate_holder.h" @@ -27,6 +28,11 @@ int NodeMain(int argc, char *argv[]) { scoped_refptr uv_task_runner(new UvTaskRunner(loop)); base::ThreadTaskRunnerHandle handle(uv_task_runner); + // Initialize feature list. + std::unique_ptr feature_list(new base::FeatureList); + feature_list->InitializeFromCommandLine("", ""); + base::FeatureList::SetInstance(std::move(feature_list)); + gin::V8Initializer::LoadV8Snapshot(); gin::V8Initializer::LoadV8Natives(); JavascriptEnvironment gin_env; diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index e54d7fee1769..5d4aafca6999 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -14,17 +14,22 @@ #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/browser.h" #include "atom/browser/login_handler.h" +#include "atom/browser/relauncher.h" +#include "atom/common/atom_command_line.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/native_mate_converters/image_converter.h" +#include "atom/common/native_mate_converters/net_converter.h" +#include "atom/common/native_mate_converters/value_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/files/file_util.h" #include "base/path_service.h" +#include "base/strings/string_util.h" #include "brightray/browser/brightray_paths.h" #include "chrome/common/chrome_paths.h" #include "content/public/browser/client_certificate_delegate.h" @@ -39,7 +44,6 @@ #if defined(OS_WIN) #include "base/strings/utf_string_conversions.h" -#include "ui/base/win/shell.h" #endif using atom::Browser; @@ -132,19 +136,20 @@ void OnClientCertificateSelected( std::shared_ptr delegate, mate::Arguments* args) { mate::Dictionary cert_data; - if (!(args->Length() == 1 && args->GetNext(&cert_data))) { + if (!args->GetNext(&cert_data)) { args->ThrowError(); return; } - std::string encoded_data; - cert_data.Get("data", &encoded_data); + v8::Local data; + if (!cert_data.Get("data", &data)) + return; - auto certs = - net::X509Certificate::CreateCertificateListFromBytes( - encoded_data.data(), encoded_data.size(), - net::X509Certificate::FORMAT_AUTO); - delegate->ContinueWithCertificate(certs[0].get()); + auto certs = net::X509Certificate::CreateCertificateListFromBytes( + node::Buffer::Data(data), node::Buffer::Length(data), + net::X509Certificate::FORMAT_AUTO); + if (certs.size() > 0) + delegate->ContinueWithCertificate(certs[0].get()); } void PassLoginInformation(scoped_refptr login_handler, @@ -156,12 +161,46 @@ void PassLoginInformation(scoped_refptr login_handler, login_handler->CancelAuth(); } +#if defined(USE_NSS_CERTS) +int ImportIntoCertStore( + CertificateManagerModel* model, + const base::DictionaryValue& options) { + std::string file_data, cert_path; + base::string16 password; + net::CertificateList imported_certs; + int rv = -1; + options.GetString("certificate", &cert_path); + options.GetString("password", &password); + + if (!cert_path.empty()) { + if (base::ReadFileToString(base::FilePath(cert_path), &file_data)) { + auto module = model->cert_db()->GetPublicModule(); + rv = model->ImportFromPKCS12(module, + file_data, + password, + true, + &imported_certs); + if (imported_certs.size() > 1) { + auto it = imported_certs.begin(); + ++it; // skip first which would be the client certificate. + for (; it != imported_certs.end(); ++it) + rv &= model->SetCertTrust(it->get(), + net::CA_CERT, + net::NSSCertDatabase::TRUSTED_SSL); + } + } + } + return rv; +} +#endif + } // namespace -App::App() { +App::App(v8::Isolate* isolate) { static_cast(AtomBrowserClient::Get())->set_delegate(this); Browser::Get()->AddObserver(this); content::GpuDataManager::GetInstance()->AddObserver(this); + Init(isolate); } App::~App() { @@ -213,6 +252,15 @@ void App::OnFinishLaunching() { Emit("ready"); } +#if defined(OS_MACOSX) +void App::OnContinueUserActivity( + bool* prevent_default, + const std::string& type, + const base::DictionaryValue& user_info) { + *prevent_default = Emit("continue-activity", type, user_info); +} +#endif + void App::OnLogin(LoginHandler* login_handler) { v8::Locker locker(isolate()); v8::HandleScope handle_scope(isolate()); @@ -228,9 +276,25 @@ void App::OnLogin(LoginHandler* login_handler) { login_handler->CancelAuth(); } +void App::OnCreateWindow(const GURL& target_url, + const std::string& frame_name, + WindowOpenDisposition disposition, + int render_process_id, + int render_frame_id) { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + content::RenderFrameHost* rfh = + content::RenderFrameHost::FromID(render_process_id, render_frame_id); + content::WebContents* web_contents = + content::WebContents::FromRenderFrameHost(rfh); + if (web_contents) { + auto api_web_contents = WebContents::CreateFrom(isolate(), web_contents); + api_web_contents->OnCreateWindow(target_url, frame_name, disposition); + } +} + void App::AllowCertificateError( - int pid, - int fid, + content::WebContents* web_contents, int cert_error, const net::SSLInfo& ssl_info, const GURL& request_url, @@ -240,9 +304,6 @@ void App::AllowCertificateError( bool expired_previous_decision, const base::Callback& callback, content::CertificateRequestResultType* request) { - auto rfh = content::RenderFrameHost::FromID(pid, fid); - auto web_contents = content::WebContents::FromRenderFrameHost(rfh); - v8::Locker locker(isolate()); v8::HandleScope handle_scope(isolate()); bool prevent_default = Emit("certificate-error", @@ -260,7 +321,7 @@ void App::AllowCertificateError( void App::SelectClientCertificate( content::WebContents* web_contents, net::SSLCertRequestInfo* cert_request_info, - scoped_ptr delegate) { + std::unique_ptr delegate) { std::shared_ptr shared_delegate(delegate.release()); bool prevent_default = @@ -296,37 +357,30 @@ base::FilePath App::GetPath(mate::Arguments* args, const std::string& name) { void App::SetPath(mate::Arguments* args, const std::string& name, const base::FilePath& path) { + if (!path.IsAbsolute()) { + args->ThrowError("path must be absolute"); + return; + } + bool succeed = false; int key = GetPathConstant(name); if (key >= 0) - succeed = PathService::Override(key, path); + succeed = PathService::OverrideAndCreateIfNeeded(key, path, true, false); if (!succeed) args->ThrowError("Failed to set path"); } void App::SetDesktopName(const std::string& desktop_name) { #if defined(OS_LINUX) - scoped_ptr env(base::Environment::Create()); + std::unique_ptr env(base::Environment::Create()); env->SetVar("CHROME_DESKTOP", desktop_name); #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(""); } -#if defined(OS_WIN) -bool App::IsAeroGlassEnabled() { - return ui::win::IsAeroGlassEnabled(); -} -#endif - bool App::MakeSingleInstance( const ProcessSingleton::NotificationCallback& callback) { if (process_singleton_.get()) @@ -349,10 +403,90 @@ bool App::MakeSingleInstance( } } -mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder( - v8::Isolate* isolate) { +void App::ReleaseSingleInstance() { + if (process_singleton_.get()) { + process_singleton_->Cleanup(); + process_singleton_.reset(); + } +} + +bool App::Relaunch(mate::Arguments* js_args) { + // Parse parameters. + bool override_argv = false; + base::FilePath exec_path; + relauncher::StringVector args; + + mate::Dictionary options; + if (js_args->GetNext(&options)) { + if (options.Get("execPath", &exec_path) | options.Get("args", &args)) + override_argv = true; + } + + if (!override_argv) { +#if defined(OS_WIN) + const relauncher::StringVector& argv = atom::AtomCommandLine::wargv(); +#else + const relauncher::StringVector& argv = atom::AtomCommandLine::argv(); +#endif + return relauncher::RelaunchApp(argv); + } + + relauncher::StringVector argv; + argv.reserve(1 + args.size()); + + if (exec_path.empty()) { + base::FilePath current_exe_path; + PathService::Get(base::FILE_EXE, ¤t_exe_path); + argv.push_back(current_exe_path.value()); + } else { + argv.push_back(exec_path.value()); + } + + argv.insert(argv.end(), args.begin(), args.end()); + + return relauncher::RelaunchApp(argv); +} + +#if defined(USE_NSS_CERTS) +void App::ImportCertificate( + const base::DictionaryValue& options, + const net::CompletionCallback& callback) { + auto browser_context = AtomBrowserMainParts::Get()->browser_context(); + if (!certificate_manager_model_) { + std::unique_ptr copy = options.CreateDeepCopy(); + CertificateManagerModel::Create(browser_context, + base::Bind(&App::OnCertificateManagerModelCreated, + base::Unretained(this), + base::Passed(©), + callback)); + return; + } + + int rv = ImportIntoCertStore(certificate_manager_model_.get(), options); + callback.Run(rv); +} + +void App::OnCertificateManagerModelCreated( + std::unique_ptr options, + const net::CompletionCallback& callback, + std::unique_ptr model) { + certificate_manager_model_ = std::move(model); + int rv = ImportIntoCertStore(certificate_manager_model_.get(), + *(options.get())); + callback.Run(rv); +} +#endif + +// static +mate::Handle App::Create(v8::Isolate* isolate) { + return mate::CreateHandle(isolate, new App(isolate)); +} + +// static +void App::BuildPrototype( + v8::Isolate* isolate, v8::Local prototype) { auto browser = base::Unretained(Browser::Get()); - return mate::ObjectTemplateBuilder(isolate) + mate::ObjectTemplateBuilder(isolate, prototype) .SetMethod("quit", base::Bind(&Browser::Quit, browser)) .SetMethod("exit", base::Bind(&Browser::Exit, browser)) .SetMethod("focus", base::Bind(&Browser::Focus, browser)) @@ -367,27 +501,34 @@ mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder( base::Bind(&Browser::ClearRecentDocuments, browser)) .SetMethod("setAppUserModelId", base::Bind(&Browser::SetAppUserModelID, browser)) + .SetMethod("isDefaultProtocolClient", + base::Bind(&Browser::IsDefaultProtocolClient, browser)) + .SetMethod("setAsDefaultProtocolClient", + base::Bind(&Browser::SetAsDefaultProtocolClient, browser)) + .SetMethod("removeAsDefaultProtocolClient", + base::Bind(&Browser::RemoveAsDefaultProtocolClient, browser)) #if defined(OS_MACOSX) .SetMethod("hide", base::Bind(&Browser::Hide, browser)) .SetMethod("show", base::Bind(&Browser::Show, browser)) + .SetMethod("setUserActivity", + base::Bind(&Browser::SetUserActivity, browser)) + .SetMethod("getCurrentActivityType", + base::Bind(&Browser::GetCurrentActivityType, browser)) #endif #if defined(OS_WIN) .SetMethod("setUserTasks", base::Bind(&Browser::SetUserTasks, browser)) - .SetMethod("isAeroGlassEnabled", &App::IsAeroGlassEnabled) #endif .SetMethod("setPath", &App::SetPath) .SetMethod("getPath", &App::GetPath) .SetMethod("setDesktopName", &App::SetDesktopName) - .SetMethod("allowNTLMCredentialsForAllDomains", - &App::AllowNTLMCredentialsForAllDomains) .SetMethod("getLocale", &App::GetLocale) - .SetMethod("makeSingleInstance", &App::MakeSingleInstance); -} - -// static -mate::Handle App::Create(v8::Isolate* isolate) { - return CreateHandle(isolate, new App); +#if defined(USE_NSS_CERTS) + .SetMethod("importCertificate", &App::ImportCertificate) +#endif + .SetMethod("makeSingleInstance", &App::MakeSingleInstance) + .SetMethod("releaseSingleInstance", &App::ReleaseSingleInstance) + .SetMethod("relaunch", &App::Relaunch); } } // namespace api @@ -400,8 +541,8 @@ 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 || + if (base::EndsWith(switch_string, "-path", + base::CompareCase::INSENSITIVE_ASCII) || switch_string == switches::kLogNetLog) { base::FilePath path; args->GetNext(&path); @@ -447,6 +588,8 @@ void Initialize(v8::Local exports, v8::Local unused, dict.SetMethod("dockBounce", &DockBounce); dict.SetMethod("dockCancelBounce", base::Bind(&Browser::DockCancelBounce, browser)); + dict.SetMethod("dockDownloadFinished", + base::Bind(&Browser::DockDownloadFinished, browser)); dict.SetMethod("dockSetBadgeText", base::Bind(&Browser::DockSetBadgeText, browser)); dict.SetMethod("dockGetBadgeText", diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index c59b9154df11..e2bcb4583dda 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -14,6 +14,11 @@ #include "chrome/browser/process_singleton.h" #include "content/public/browser/gpu_data_manager_observer.h" #include "native_mate/handle.h" +#include "net/base/completion_callback.h" + +#if defined(USE_NSS_CERTS) +#include "chrome/browser/certificate_manager_model.h" +#endif namespace base { class FilePath; @@ -28,15 +33,32 @@ namespace atom { namespace api { class App : public AtomBrowserClient::Delegate, - public mate::EventEmitter, + public mate::EventEmitter, public BrowserObserver, public content::GpuDataManagerObserver { public: static mate::Handle Create(v8::Isolate* isolate); + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + + // Called when window with disposition needs to be created. + void OnCreateWindow(const GURL& target_url, + const std::string& frame_name, + WindowOpenDisposition disposition, + int render_process_id, + int render_frame_id); + +#if defined(USE_NSS_CERTS) + void OnCertificateManagerModelCreated( + std::unique_ptr options, + const net::CompletionCallback& callback, + std::unique_ptr model); +#endif + protected: - App(); - virtual ~App(); + explicit App(v8::Isolate* isolate); + ~App() override; // BrowserObserver: void OnBeforeQuit(bool* prevent_default) override; @@ -49,11 +71,16 @@ class App : public AtomBrowserClient::Delegate, void OnWillFinishLaunching() override; void OnFinishLaunching() override; void OnLogin(LoginHandler* login_handler) override; +#if defined(OS_MACOSX) + void OnContinueUserActivity( + bool* prevent_default, + const std::string& type, + const base::DictionaryValue& user_info) override; +#endif // content::ContentBrowserClient: void AllowCertificateError( - int render_process_id, - int render_frame_id, + content::WebContents* web_contents, int cert_error, const net::SSLInfo& ssl_info, const GURL& request_url, @@ -66,15 +93,11 @@ class App : public AtomBrowserClient::Delegate, void SelectClientCertificate( content::WebContents* web_contents, net::SSLCertRequestInfo* cert_request_info, - scoped_ptr delegate) override; + std::unique_ptr delegate) override; // content::GpuDataManagerObserver: void OnGpuProcessCrashed(base::TerminationStatus exit_code) override; - // mate::Wrappable: - mate::ObjectTemplateBuilder GetObjectTemplateBuilder( - v8::Isolate* isolate) override; - private: // Get/Set the pre-defined path in PathService. base::FilePath GetPath(mate::Arguments* args, const std::string& name); @@ -83,16 +106,22 @@ class App : public AtomBrowserClient::Delegate, const base::FilePath& path); void SetDesktopName(const std::string& desktop_name); - void AllowNTLMCredentialsForAllDomains(bool should_allow); + std::string GetLocale(); bool MakeSingleInstance( const ProcessSingleton::NotificationCallback& callback); - std::string GetLocale(); + void ReleaseSingleInstance(); + bool Relaunch(mate::Arguments* args); -#if defined(OS_WIN) - bool IsAeroGlassEnabled(); +#if defined(USE_NSS_CERTS) + void ImportCertificate(const base::DictionaryValue& options, + const net::CompletionCallback& callback); #endif - scoped_ptr process_singleton_; + std::unique_ptr process_singleton_; + +#if defined(USE_NSS_CERTS) + std::unique_ptr certificate_manager_model_; +#endif 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 1a02a54d4533..cdf3406ae788 100644 --- a/atom/browser/api/atom_api_auto_updater.cc +++ b/atom/browser/api/atom_api_auto_updater.cc @@ -34,8 +34,9 @@ namespace atom { namespace api { -AutoUpdater::AutoUpdater() { +AutoUpdater::AutoUpdater(v8::Isolate* isolate) { auto_updater::AutoUpdater::SetDelegate(this); + Init(isolate); } AutoUpdater::~AutoUpdater() { @@ -78,14 +79,6 @@ void AutoUpdater::OnWindowAllClosed() { QuitAndInstall(); } -mate::ObjectTemplateBuilder AutoUpdater::GetObjectTemplateBuilder( - v8::Isolate* isolate) { - return mate::ObjectTemplateBuilder(isolate) - .SetMethod("setFeedURL", &auto_updater::AutoUpdater::SetFeedURL) - .SetMethod("checkForUpdates", &auto_updater::AutoUpdater::CheckForUpdates) - .SetMethod("quitAndInstall", &AutoUpdater::QuitAndInstall); -} - void AutoUpdater::QuitAndInstall() { // If we don't have any window then quitAndInstall immediately. WindowList* window_list = WindowList::GetInstance(); @@ -102,7 +95,16 @@ void AutoUpdater::QuitAndInstall() { // static mate::Handle AutoUpdater::Create(v8::Isolate* isolate) { - return CreateHandle(isolate, new AutoUpdater); + return mate::CreateHandle(isolate, new AutoUpdater(isolate)); +} + +// static +void AutoUpdater::BuildPrototype( + v8::Isolate* isolate, v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .SetMethod("setFeedURL", &auto_updater::AutoUpdater::SetFeedURL) + .SetMethod("checkForUpdates", &auto_updater::AutoUpdater::CheckForUpdates) + .SetMethod("quitAndInstall", &AutoUpdater::QuitAndInstall); } } // namespace api diff --git a/atom/browser/api/atom_api_auto_updater.h b/atom/browser/api/atom_api_auto_updater.h index 95b91041e9e3..857647258adf 100644 --- a/atom/browser/api/atom_api_auto_updater.h +++ b/atom/browser/api/atom_api_auto_updater.h @@ -16,15 +16,18 @@ namespace atom { namespace api { -class AutoUpdater : public mate::EventEmitter, +class AutoUpdater : public mate::EventEmitter, public auto_updater::Delegate, public WindowListObserver { public: static mate::Handle Create(v8::Isolate* isolate); + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + protected: - AutoUpdater(); - virtual ~AutoUpdater(); + explicit AutoUpdater(v8::Isolate* isolate); + ~AutoUpdater() override; // Delegate implementations. void OnError(const std::string& error) override; @@ -39,10 +42,6 @@ class AutoUpdater : public mate::EventEmitter, // WindowListObserver: void OnWindowAllClosed() override; - // mate::Wrappable implementations: - mate::ObjectTemplateBuilder GetObjectTemplateBuilder( - v8::Isolate* isolate) override; - private: void QuitAndInstall(); diff --git a/atom/browser/api/atom_api_content_tracing.cc b/atom/browser/api/atom_api_content_tracing.cc index f29946d1f462..937a87ea2131 100644 --- a/atom/browser/api/atom_api_content_tracing.cc +++ b/atom/browser/api/atom_api_content_tracing.cc @@ -53,13 +53,7 @@ scoped_refptr GetTraceDataSink( void StopRecording(const base::FilePath& path, const CompletionCallback& callback) { - TracingController::GetInstance()->DisableRecording( - GetTraceDataSink(path, callback)); -} - -void CaptureMonitoringSnapshot(const base::FilePath& path, - const CompletionCallback& callback) { - TracingController::GetInstance()->CaptureMonitoringSnapshot( + TracingController::GetInstance()->StopTracing( GetTraceDataSink(path, callback)); } @@ -70,13 +64,8 @@ void Initialize(v8::Local exports, v8::Local unused, dict.SetMethod("getCategories", base::Bind( &TracingController::GetCategories, controller)); dict.SetMethod("startRecording", base::Bind( - &TracingController::EnableRecording, controller)); + &TracingController::StartTracing, controller)); dict.SetMethod("stopRecording", &StopRecording); - dict.SetMethod("startMonitoring", base::Bind( - &TracingController::EnableMonitoring, controller)); - dict.SetMethod("stopMonitoring", base::Bind( - &TracingController::DisableMonitoring, controller)); - dict.SetMethod("captureMonitoringSnapshot", &CaptureMonitoringSnapshot); dict.SetMethod("getTraceBufferUsage", base::Bind( &TracingController::GetTraceBufferUsage, controller)); dict.SetMethod("setWatchEvent", base::Bind( diff --git a/atom/browser/api/atom_api_cookies.cc b/atom/browser/api/atom_api_cookies.cc index 6323e5110c18..6ee15a6cba90 100644 --- a/atom/browser/api/atom_api_cookies.cc +++ b/atom/browser/api/atom_api_cookies.cc @@ -47,7 +47,7 @@ struct Converter { dict.Set("secure", val.IsSecure()); dict.Set("httpOnly", val.IsHttpOnly()); dict.Set("session", !val.IsPersistent()); - if (!val.IsPersistent()) + if (val.IsPersistent()) dict.Set("expirationDate", val.ExpiryDate().ToDoubleT()); return dict.GetHandle(); } @@ -112,7 +112,7 @@ void RunCallbackInUI(const base::Closure& callback) { } // Remove cookies from |list| not matching |filter|, and pass it to |callback|. -void FilterCookies(scoped_ptr filter, +void FilterCookies(std::unique_ptr filter, const Cookies::GetCallback& callback, const net::CookieList& list) { net::CookieList result; @@ -125,7 +125,7 @@ void FilterCookies(scoped_ptr filter, // Receives cookies matching |filter| in IO thread. void GetCookiesOnIO(scoped_refptr getter, - scoped_ptr filter, + std::unique_ptr filter, const Cookies::GetCallback& callback) { std::string url; filter->GetString("url", &url); @@ -133,12 +133,12 @@ void GetCookiesOnIO(scoped_refptr getter, auto filtered_callback = base::Bind(FilterCookies, base::Passed(&filter), callback); - net::CookieMonster* monster = GetCookieStore(getter)->GetCookieMonster(); // Empty url will match all url cookies. if (url.empty()) - monster->GetAllCookiesAsync(filtered_callback); + GetCookieStore(getter)->GetAllCookiesAsync(filtered_callback); else - monster->GetAllCookiesForURLAsync(GURL(url), filtered_callback); + GetCookieStore(getter)->GetAllCookiesForURLAsync(GURL(url), + filtered_callback); } // Removes cookie with |url| and |name| in IO thread. @@ -157,12 +157,14 @@ void OnSetCookie(const Cookies::SetCallback& callback, bool success) { // Sets cookie with |details| in IO thread. void SetCookieOnIO(scoped_refptr getter, - scoped_ptr details, + std::unique_ptr details, const Cookies::SetCallback& callback) { std::string url, name, value, domain, path; bool secure = false; bool http_only = false; + double creation_date; double expiration_date; + double last_access_date; details->GetString("url", &url); details->GetString("name", &name); details->GetString("value", &value); @@ -171,6 +173,13 @@ void SetCookieOnIO(scoped_refptr getter, details->GetBoolean("secure", &secure); details->GetBoolean("httpOnly", &http_only); + base::Time creation_time; + if (details->GetDouble("creationDate", &creation_date)) { + creation_time = (creation_date == 0) ? + base::Time::UnixEpoch() : + base::Time::FromDoubleT(creation_date); + } + base::Time expiration_time; if (details->GetDouble("expirationDate", &expiration_date)) { expiration_time = (expiration_date == 0) ? @@ -178,15 +187,26 @@ void SetCookieOnIO(scoped_refptr getter, base::Time::FromDoubleT(expiration_date); } - GetCookieStore(getter)->GetCookieMonster()->SetCookieWithDetailsAsync( - GURL(url), name, value, domain, path, expiration_time, secure, http_only, - false, net::COOKIE_PRIORITY_DEFAULT, base::Bind(OnSetCookie, callback)); + base::Time last_access_time; + if (details->GetDouble("lastAccessDate", &last_access_date)) { + last_access_time = (last_access_date == 0) ? + base::Time::UnixEpoch() : + base::Time::FromDoubleT(last_access_date); + } + + GetCookieStore(getter)->SetCookieWithDetailsAsync( + GURL(url), name, value, domain, path, creation_time, + expiration_time, last_access_time, secure, http_only, + net::CookieSameSite::DEFAULT_MODE, false, + net::COOKIE_PRIORITY_DEFAULT, base::Bind(OnSetCookie, callback)); } } // namespace -Cookies::Cookies(content::BrowserContext* browser_context) - : request_context_getter_(browser_context->GetRequestContext()) { +Cookies::Cookies(v8::Isolate* isolate, + content::BrowserContext* browser_context) + : request_context_getter_(browser_context->GetRequestContext()) { + Init(isolate); } Cookies::~Cookies() { @@ -194,7 +214,7 @@ Cookies::~Cookies() { void Cookies::Get(const base::DictionaryValue& filter, const GetCallback& callback) { - scoped_ptr copied(filter.CreateDeepCopy()); + std::unique_ptr copied(filter.CreateDeepCopy()); auto getter = make_scoped_refptr(request_context_getter_); content::BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, @@ -211,7 +231,7 @@ void Cookies::Remove(const GURL& url, const std::string& name, void Cookies::Set(const base::DictionaryValue& details, const SetCallback& callback) { - scoped_ptr copied(details.CreateDeepCopy()); + std::unique_ptr copied(details.CreateDeepCopy()); auto getter = make_scoped_refptr(request_context_getter_); content::BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, @@ -222,7 +242,7 @@ void Cookies::Set(const base::DictionaryValue& details, mate::Handle Cookies::Create( v8::Isolate* isolate, content::BrowserContext* browser_context) { - return mate::CreateHandle(isolate, new Cookies(browser_context)); + return mate::CreateHandle(isolate, new Cookies(isolate, browser_context)); } // static diff --git a/atom/browser/api/atom_api_cookies.h b/atom/browser/api/atom_api_cookies.h index 302fd1b25110..33fee56960f4 100644 --- a/atom/browser/api/atom_api_cookies.h +++ b/atom/browser/api/atom_api_cookies.h @@ -46,8 +46,8 @@ class Cookies : public mate::TrackableObject { v8::Local prototype); protected: - explicit Cookies(content::BrowserContext* browser_context); - ~Cookies(); + Cookies(v8::Isolate* isolate, content::BrowserContext* browser_context); + ~Cookies() override; void Get(const base::DictionaryValue& filter, const GetCallback& callback); void Remove(const GURL& url, const std::string& name, diff --git a/atom/browser/api/atom_api_debugger.cc b/atom/browser/api/atom_api_debugger.cc index eab60311f3dd..48b7a6f70c0b 100644 --- a/atom/browser/api/atom_api_debugger.cc +++ b/atom/browser/api/atom_api_debugger.cc @@ -31,9 +31,10 @@ WrapDebuggerCallback g_wrap_debugger; } // namespace -Debugger::Debugger(content::WebContents* web_contents) +Debugger::Debugger(v8::Isolate* isolate, content::WebContents* web_contents) : web_contents_(web_contents), previous_request_id_(0) { + Init(isolate); } Debugger::~Debugger() { @@ -51,7 +52,7 @@ void Debugger::DispatchProtocolMessage(DevToolsAgentHost* agent_host, const std::string& message) { DCHECK(agent_host == agent_host_.get()); - scoped_ptr parsed_message(base::JSONReader::Read(message)); + std::unique_ptr parsed_message(base::JSONReader::Read(message)); if (!parsed_message->IsType(base::Value::TYPE_DICTIONARY)) return; @@ -150,7 +151,8 @@ void Debugger::SendCommand(mate::Arguments* args) { mate::Handle Debugger::Create( v8::Isolate* isolate, content::WebContents* web_contents) { - auto handle = mate::CreateHandle(isolate, new Debugger(web_contents)); + auto handle = mate::CreateHandle( + isolate, new Debugger(isolate, web_contents)); g_wrap_debugger.Run(handle.ToV8()); return handle; } @@ -165,16 +167,8 @@ void Debugger::BuildPrototype(v8::Isolate* isolate, .SetMethod("sendCommand", &Debugger::SendCommand); } -void ClearWrapDebugger() { - g_wrap_debugger.Reset(); -} - void SetWrapDebugger(const WrapDebuggerCallback& callback) { g_wrap_debugger = callback; - - // Cleanup the wrapper on exit. - atom::AtomBrowserMainParts::Get()->RegisterDestructionCallback( - base::Bind(ClearWrapDebugger)); } } // namespace api diff --git a/atom/browser/api/atom_api_debugger.h b/atom/browser/api/atom_api_debugger.h index 5454108e8b09..1e97fa89abd0 100644 --- a/atom/browser/api/atom_api_debugger.h +++ b/atom/browser/api/atom_api_debugger.h @@ -42,8 +42,8 @@ class Debugger: public mate::TrackableObject, v8::Local prototype); protected: - explicit Debugger(content::WebContents* web_contents); - ~Debugger(); + Debugger(v8::Isolate* isolate, content::WebContents* web_contents); + ~Debugger() override; // content::DevToolsAgentHostClient: void AgentHostClosed(content::DevToolsAgentHost* agent_host, diff --git a/atom/browser/api/atom_api_desktop_capturer.cc b/atom/browser/api/atom_api_desktop_capturer.cc index ceb69deca452..3cb29a9124d3 100644 --- a/atom/browser/api/atom_api_desktop_capturer.cc +++ b/atom/browser/api/atom_api_desktop_capturer.cc @@ -5,7 +5,6 @@ #include "atom/browser/api/atom_api_desktop_capturer.h" #include "atom/common/api/atom_api_native_image.h" -#include "atom/common/node_includes.h" #include "atom/common/native_mate_converters/gfx_converter.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/media/desktop_media_list.h" @@ -14,6 +13,8 @@ #include "third_party/webrtc/modules/desktop_capture/screen_capturer.h" #include "third_party/webrtc/modules/desktop_capture/window_capturer.h" +#include "atom/common/node_includes.h" + namespace mate { template<> @@ -37,7 +38,8 @@ namespace atom { namespace api { -DesktopCapturer::DesktopCapturer() { +DesktopCapturer::DesktopCapturer(v8::Isolate* isolate) { + Init(isolate); } DesktopCapturer::~DesktopCapturer() { @@ -59,12 +61,12 @@ void DesktopCapturer::StartHandling(bool capture_window, options.set_disable_effects(false); #endif - scoped_ptr screen_capturer( + std::unique_ptr screen_capturer( capture_screen ? webrtc::ScreenCapturer::Create(options) : nullptr); - scoped_ptr window_capturer( + std::unique_ptr window_capturer( capture_window ? webrtc::WindowCapturer::Create(options) : nullptr); - media_list_.reset(new NativeDesktopMediaList(screen_capturer.Pass(), - window_capturer.Pass())); + media_list_.reset(new NativeDesktopMediaList( + std::move(screen_capturer), std::move(window_capturer))); media_list_->SetThumbnailSize(thumbnail_size); media_list_->StartUpdating(this); @@ -87,19 +89,19 @@ void DesktopCapturer::OnSourceThumbnailChanged(int index) { bool DesktopCapturer::OnRefreshFinished() { Emit("finished", media_list_->GetSources()); - media_list_.reset(); return false; } -mate::ObjectTemplateBuilder DesktopCapturer::GetObjectTemplateBuilder( - v8::Isolate* isolate) { - return mate::ObjectTemplateBuilder(isolate) - .SetMethod("startHandling", &DesktopCapturer::StartHandling); -} - // static mate::Handle DesktopCapturer::Create(v8::Isolate* isolate) { - return mate::CreateHandle(isolate, new DesktopCapturer); + return mate::CreateHandle(isolate, new DesktopCapturer(isolate)); +} + +// static +void DesktopCapturer::BuildPrototype( + v8::Isolate* isolate, v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .SetMethod("startHandling", &DesktopCapturer::StartHandling); } } // namespace api diff --git a/atom/browser/api/atom_api_desktop_capturer.h b/atom/browser/api/atom_api_desktop_capturer.h index c22c8a44835f..4d1755e06827 100644 --- a/atom/browser/api/atom_api_desktop_capturer.h +++ b/atom/browser/api/atom_api_desktop_capturer.h @@ -14,18 +14,21 @@ namespace atom { namespace api { -class DesktopCapturer: public mate::EventEmitter, +class DesktopCapturer: public mate::EventEmitter, public DesktopMediaListObserver { public: static mate::Handle Create(v8::Isolate* isolate); + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + void StartHandling(bool capture_window, bool capture_screen, const gfx::Size& thumbnail_size); protected: - DesktopCapturer(); - ~DesktopCapturer(); + explicit DesktopCapturer(v8::Isolate* isolate); + ~DesktopCapturer() override; // DesktopMediaListObserver overrides. void OnSourceAdded(int index) override; @@ -36,11 +39,7 @@ class DesktopCapturer: public mate::EventEmitter, bool OnRefreshFinished() override; private: - // mate::Wrappable: - mate::ObjectTemplateBuilder GetObjectTemplateBuilder( - v8::Isolate* isolate) override; - - scoped_ptr media_list_; + std::unique_ptr media_list_; DISALLOW_COPY_AND_ASSIGN(DesktopCapturer); }; diff --git a/atom/browser/api/atom_api_dialog.cc b/atom/browser/api/atom_api_dialog.cc index 0a544c56468c..5d853e2d5900 100644 --- a/atom/browser/api/atom_api_dialog.cc +++ b/atom/browser/api/atom_api_dialog.cc @@ -67,6 +67,7 @@ void ShowMessageBox(int type, } void ShowOpenDialog(const std::string& title, + const std::string& button_label, const base::FilePath& default_path, const file_dialog::Filters& filters, int properties, @@ -77,17 +78,18 @@ void ShowOpenDialog(const std::string& title, if (mate::Converter::FromV8(args->isolate(), peek, &callback)) { - file_dialog::ShowOpenDialog(window, title, default_path, filters, - properties, callback); + file_dialog::ShowOpenDialog(window, title, button_label, default_path, + filters, properties, callback); } else { std::vector paths; - if (file_dialog::ShowOpenDialog(window, title, default_path, filters, - properties, &paths)) + if (file_dialog::ShowOpenDialog(window, title, button_label, default_path, + filters, properties, &paths)) args->Return(paths); } } void ShowSaveDialog(const std::string& title, + const std::string& button_label, const base::FilePath& default_path, const file_dialog::Filters& filters, atom::NativeWindow* window, @@ -97,11 +99,12 @@ void ShowSaveDialog(const std::string& title, if (mate::Converter::FromV8(args->isolate(), peek, &callback)) { - file_dialog::ShowSaveDialog(window, title, default_path, filters, callback); + file_dialog::ShowSaveDialog(window, title, button_label, default_path, + filters, callback); } else { base::FilePath path; - if (file_dialog::ShowSaveDialog(window, title, default_path, filters, - &path)) + if (file_dialog::ShowSaveDialog(window, title, button_label, default_path, + filters, &path)) args->Return(path); } } diff --git a/atom/browser/api/atom_api_download_item.cc b/atom/browser/api/atom_api_download_item.cc index 5a8befc9d1db..96826a250f57 100644 --- a/atom/browser/api/atom_api_download_item.cc +++ b/atom/browser/api/atom_api_download_item.cc @@ -53,13 +53,15 @@ namespace { using WrapDownloadItemCallback = base::Callback)>; WrapDownloadItemCallback g_wrap_download_item; -std::map>> g_download_item_objects; +std::map>> g_download_item_objects; } // namespace -DownloadItem::DownloadItem(content::DownloadItem* download_item) +DownloadItem::DownloadItem(v8::Isolate* isolate, + content::DownloadItem* download_item) : download_item_(download_item) { download_item_->AddObserver(this); + Init(isolate); AttachAsUserData(download_item); } @@ -106,11 +108,11 @@ void DownloadItem::Cancel() { download_item_->Remove(); } -int64 DownloadItem::GetReceivedBytes() const { +int64_t DownloadItem::GetReceivedBytes() const { return download_item_->GetReceivedBytes(); } -int64 DownloadItem::GetTotalBytes() const { +int64_t DownloadItem::GetTotalBytes() const { return download_item_->GetTotalBytes(); } @@ -173,7 +175,7 @@ mate::Handle DownloadItem::Create( if (existing) return mate::CreateHandle(isolate, static_cast(existing)); - auto handle = mate::CreateHandle(isolate, new DownloadItem(item)); + auto handle = mate::CreateHandle(isolate, new DownloadItem(isolate, item)); g_wrap_download_item.Run(handle.ToV8()); // Reference this object in case it got garbage collected. @@ -182,16 +184,8 @@ mate::Handle DownloadItem::Create( return handle; } -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 diff --git a/atom/browser/api/atom_api_download_item.h b/atom/browser/api/atom_api_download_item.h index 5806c0181768..bc7ddd821bb9 100644 --- a/atom/browser/api/atom_api_download_item.h +++ b/atom/browser/api/atom_api_download_item.h @@ -23,15 +23,14 @@ class DownloadItem : public mate::TrackableObject, static mate::Handle Create(v8::Isolate* isolate, content::DownloadItem* item); - // mate::TrackableObject: static void BuildPrototype(v8::Isolate* isolate, v8::Local prototype); void Pause(); void Resume(); void Cancel(); - int64 GetReceivedBytes() const; - int64 GetTotalBytes() const; + int64_t GetReceivedBytes() const; + int64_t GetTotalBytes() const; std::string GetMimeType() const; bool HasUserGesture() const; std::string GetFilename() const; @@ -41,7 +40,7 @@ class DownloadItem : public mate::TrackableObject, base::FilePath GetSavePath() const; protected: - explicit DownloadItem(content::DownloadItem* download_item); + DownloadItem(v8::Isolate* isolate, content::DownloadItem* download_item); ~DownloadItem(); // Override content::DownloadItem::Observer methods diff --git a/atom/browser/api/atom_api_global_shortcut.cc b/atom/browser/api/atom_api_global_shortcut.cc index f5a03e4abf90..2b1e5d6591a0 100644 --- a/atom/browser/api/atom_api_global_shortcut.cc +++ b/atom/browser/api/atom_api_global_shortcut.cc @@ -19,7 +19,8 @@ namespace atom { namespace api { -GlobalShortcut::GlobalShortcut() { +GlobalShortcut::GlobalShortcut(v8::Isolate* isolate) { + Init(isolate); } GlobalShortcut::~GlobalShortcut() { @@ -66,20 +67,21 @@ void GlobalShortcut::UnregisterAll() { GlobalShortcutListener::GetInstance()->UnregisterAccelerators(this); } -mate::ObjectTemplateBuilder GlobalShortcut::GetObjectTemplateBuilder( - v8::Isolate* isolate) { - return mate::ObjectTemplateBuilder(isolate) +// static +mate::Handle GlobalShortcut::Create(v8::Isolate* isolate) { + return mate::CreateHandle(isolate, new GlobalShortcut(isolate)); +} + +// static +void GlobalShortcut::BuildPrototype( + v8::Isolate* isolate, v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) .SetMethod("register", &GlobalShortcut::Register) .SetMethod("isRegistered", &GlobalShortcut::IsRegistered) .SetMethod("unregister", &GlobalShortcut::Unregister) .SetMethod("unregisterAll", &GlobalShortcut::UnregisterAll); } -// static -mate::Handle GlobalShortcut::Create(v8::Isolate* isolate) { - return CreateHandle(isolate, new GlobalShortcut); -} - } // namespace api } // namespace atom diff --git a/atom/browser/api/atom_api_global_shortcut.h b/atom/browser/api/atom_api_global_shortcut.h index d7057b000320..41eb42f85569 100644 --- a/atom/browser/api/atom_api_global_shortcut.h +++ b/atom/browser/api/atom_api_global_shortcut.h @@ -23,13 +23,12 @@ class GlobalShortcut : public extensions::GlobalShortcutListener::Observer, public: static mate::Handle Create(v8::Isolate* isolate); - protected: - GlobalShortcut(); - ~GlobalShortcut() override; + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); - // mate::Wrappable implementations: - mate::ObjectTemplateBuilder GetObjectTemplateBuilder( - v8::Isolate* isolate) override; + protected: + explicit GlobalShortcut(v8::Isolate* isolate); + ~GlobalShortcut() override; private: typedef std::map AcceleratorCallbackMap; diff --git a/atom/browser/api/atom_api_menu.cc b/atom/browser/api/atom_api_menu.cc index e40ba17f464f..996c71739bc4 100644 --- a/atom/browser/api/atom_api_menu.cc +++ b/atom/browser/api/atom_api_menu.cc @@ -19,7 +19,7 @@ namespace atom { namespace api { -Menu::Menu() +Menu::Menu(v8::Isolate* isolate) : model_(new AtomMenuModel(this)), parent_(NULL) { } @@ -28,7 +28,7 @@ Menu::~Menu() { } void Menu::AfterInit(v8::Isolate* isolate) { - mate::Dictionary wrappable(isolate, GetWrapper(isolate)); + mate::Dictionary wrappable(isolate, GetWrapper()); mate::Dictionary delegate; if (!wrappable.Get("delegate", &delegate)) return; diff --git a/atom/browser/api/atom_api_menu.h b/atom/browser/api/atom_api_menu.h index 1ae708863a73..9ba4d7a754c4 100644 --- a/atom/browser/api/atom_api_menu.h +++ b/atom/browser/api/atom_api_menu.h @@ -20,7 +20,7 @@ namespace api { class Menu : public mate::TrackableObject, public AtomMenuModel::Delegate { public: - static mate::Wrappable* Create(); + static mate::WrappableBase* Create(v8::Isolate* isolate); static void BuildPrototype(v8::Isolate* isolate, v8::Local prototype); @@ -36,7 +36,7 @@ class Menu : public mate::TrackableObject, AtomMenuModel* model() const { return model_.get(); } protected: - Menu(); + explicit Menu(v8::Isolate* isolate); ~Menu() override; // mate::Wrappable: @@ -55,7 +55,7 @@ class Menu : public mate::TrackableObject, int x = -1, int y = -1, int positioning_item = 0) = 0; - scoped_ptr model_; + std::unique_ptr model_; Menu* parent_; private: diff --git a/atom/browser/api/atom_api_menu_mac.h b/atom/browser/api/atom_api_menu_mac.h index 85227fa2a9d9..293e8ec4edae 100644 --- a/atom/browser/api/atom_api_menu_mac.h +++ b/atom/browser/api/atom_api_menu_mac.h @@ -17,7 +17,7 @@ namespace api { class MenuMac : public Menu { protected: - MenuMac(); + explicit MenuMac(v8::Isolate* isolate); void PopupAt(Window* window, int x, int y, int positioning_item = 0) override; diff --git a/atom/browser/api/atom_api_menu_mac.mm b/atom/browser/api/atom_api_menu_mac.mm index 71c677b0476e..3b91dac62a7f 100644 --- a/atom/browser/api/atom_api_menu_mac.mm +++ b/atom/browser/api/atom_api_menu_mac.mm @@ -7,6 +7,8 @@ #include "atom/browser/native_window.h" #include "base/message_loop/message_loop.h" #include "base/strings/sys_string_conversions.h" +#include "brightray/browser/inspectable_web_contents.h" +#include "brightray/browser/inspectable_web_contents_view.h" #include "content/public/browser/web_contents.h" #include "atom/common/node_includes.h" @@ -15,21 +17,22 @@ namespace atom { namespace api { -MenuMac::MenuMac() { +MenuMac::MenuMac(v8::Isolate* isolate) : Menu(isolate) { } void MenuMac::PopupAt(Window* window, int x, int y, int positioning_item) { NativeWindow* native_window = window->window(); if (!native_window) return; - content::WebContents* web_contents = native_window->web_contents(); + brightray::InspectableWebContents* web_contents = + native_window->inspectable_web_contents(); if (!web_contents) return; base::scoped_nsobject menu_controller( [[AtomMenuController alloc] initWithModel:model_.get()]); NSMenu* menu = [menu_controller menu]; - NSView* view = web_contents->GetContentNativeView(); + NSView* view = web_contents->GetView()->GetNativeView(); // Which menu item to show. NSMenuItem* item = nil; @@ -46,6 +49,23 @@ void MenuMac::PopupAt(Window* window, int x, int y, int positioning_item) { position = NSMakePoint(x, [view frame].size.height - y); } + // If no preferred item is specified, try to show all of the menu items. + if (!positioning_item) { + CGFloat windowBottom = CGRectGetMinY([view window].frame); + CGFloat lowestMenuPoint = windowBottom + position.y - [menu size].height; + CGFloat screenBottom = CGRectGetMinY([view window].screen.frame); + CGFloat distanceFromBottom = lowestMenuPoint - screenBottom; + if (distanceFromBottom < 0) + position.y = position.y - distanceFromBottom + 4; + } + + // Place the menu left of cursor if it is overflowing off right of screen. + CGFloat windowLeft = CGRectGetMinX([view window].frame); + CGFloat rightmostMenuPoint = windowLeft + position.x + [menu size].width; + CGFloat screenRight = CGRectGetMaxX([view window].screen.frame); + if (rightmostMenuPoint > screenRight) + position.x = position.x - [menu size].width; + // Show the menu. [menu popUpMenuPositioningItem:item atLocation:position inView:view]; } @@ -68,8 +88,8 @@ void Menu::SendActionToFirstResponder(const std::string& action) { } // static -mate::Wrappable* Menu::Create() { - return new MenuMac(); +mate::WrappableBase* Menu::Create(v8::Isolate* isolate) { + return new MenuMac(isolate); } } // namespace api diff --git a/atom/browser/api/atom_api_menu_views.cc b/atom/browser/api/atom_api_menu_views.cc index 4a3a97dd906e..9340bc76732e 100644 --- a/atom/browser/api/atom_api_menu_views.cc +++ b/atom/browser/api/atom_api_menu_views.cc @@ -13,7 +13,7 @@ namespace atom { namespace api { -MenuViews::MenuViews() { +MenuViews::MenuViews(v8::Isolate* isolate) : Menu(isolate) { } void MenuViews::PopupAt(Window* window, int x, int y, int positioning_item) { @@ -30,7 +30,7 @@ void MenuViews::PopupAt(Window* window, int x, int y, int positioning_item) { // (-1, -1) means showing on mouse location. gfx::Point location; if (x == -1 || y == -1) { - location = gfx::Screen::GetNativeScreen()->GetCursorScreenPoint(); + location = gfx::Screen::GetScreen()->GetCursorScreenPoint(); } else { gfx::Point origin = view->GetViewBounds().origin(); location = gfx::Point(origin.x() + x, origin.y() + y); @@ -49,8 +49,8 @@ void MenuViews::PopupAt(Window* window, int x, int y, int positioning_item) { } // static -mate::Wrappable* Menu::Create() { - return new MenuViews(); +mate::WrappableBase* Menu::Create(v8::Isolate* isolate) { + return new MenuViews(isolate); } } // namespace api diff --git a/atom/browser/api/atom_api_menu_views.h b/atom/browser/api/atom_api_menu_views.h index e4d17c77ca65..e1daa4904028 100644 --- a/atom/browser/api/atom_api_menu_views.h +++ b/atom/browser/api/atom_api_menu_views.h @@ -14,7 +14,7 @@ namespace api { class MenuViews : public Menu { public: - MenuViews(); + explicit MenuViews(v8::Isolate* isolate); protected: void PopupAt(Window* window, int x, int y, int positioning_item = 0) override; diff --git a/atom/browser/api/atom_api_power_monitor.cc b/atom/browser/api/atom_api_power_monitor.cc index 31b35e10cea8..32c50c696c7f 100644 --- a/atom/browser/api/atom_api_power_monitor.cc +++ b/atom/browser/api/atom_api_power_monitor.cc @@ -14,8 +14,9 @@ namespace atom { namespace api { -PowerMonitor::PowerMonitor() { +PowerMonitor::PowerMonitor(v8::Isolate* isolate) { base::PowerMonitor::Get()->AddObserver(this); + Init(isolate); } PowerMonitor::~PowerMonitor() { @@ -46,7 +47,13 @@ v8::Local PowerMonitor::Create(v8::Isolate* isolate) { return v8::Null(isolate); } - return CreateHandle(isolate, new PowerMonitor).ToV8(); + return mate::CreateHandle(isolate, new PowerMonitor(isolate)).ToV8(); +} + +// static +void PowerMonitor::BuildPrototype( + v8::Isolate* isolate, v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype); } } // namespace api diff --git a/atom/browser/api/atom_api_power_monitor.h b/atom/browser/api/atom_api_power_monitor.h index 8fb52eeec95e..50d4bb466ca9 100644 --- a/atom/browser/api/atom_api_power_monitor.h +++ b/atom/browser/api/atom_api_power_monitor.h @@ -19,8 +19,11 @@ class PowerMonitor : public mate::TrackableObject, public: static v8::Local Create(v8::Isolate* isolate); + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + protected: - PowerMonitor(); + explicit PowerMonitor(v8::Isolate* isolate); ~PowerMonitor() override; // base::PowerObserver implementations: diff --git a/atom/browser/api/atom_api_power_save_blocker.cc b/atom/browser/api/atom_api_power_save_blocker.cc index 58983e6c846a..67c9b6dd83c2 100644 --- a/atom/browser/api/atom_api_power_save_blocker.cc +++ b/atom/browser/api/atom_api_power_save_blocker.cc @@ -37,9 +37,10 @@ namespace atom { namespace api { -PowerSaveBlocker::PowerSaveBlocker() +PowerSaveBlocker::PowerSaveBlocker(v8::Isolate* isolate) : current_blocker_type_( - content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension) { + content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension) { + Init(isolate); } PowerSaveBlocker::~PowerSaveBlocker() { @@ -69,7 +70,7 @@ void PowerSaveBlocker::UpdatePowerSaveBlocker() { } if (!power_save_blocker_ || new_blocker_type != current_blocker_type_) { - scoped_ptr new_blocker = + std::unique_ptr new_blocker = content::PowerSaveBlocker::Create( new_blocker_type, content::PowerSaveBlocker::kReasonOther, @@ -97,17 +98,18 @@ bool PowerSaveBlocker::IsStarted(int id) { return power_save_blocker_types_.find(id) != power_save_blocker_types_.end(); } -mate::ObjectTemplateBuilder PowerSaveBlocker::GetObjectTemplateBuilder( - v8::Isolate* isolate) { - return mate::ObjectTemplateBuilder(isolate) - .SetMethod("start", &PowerSaveBlocker::Start) - .SetMethod("stop", &PowerSaveBlocker::Stop) - .SetMethod("isStarted", &PowerSaveBlocker::IsStarted); +// static +mate::Handle PowerSaveBlocker::Create(v8::Isolate* isolate) { + return mate::CreateHandle(isolate, new PowerSaveBlocker(isolate)); } // static -mate::Handle PowerSaveBlocker::Create(v8::Isolate* isolate) { - return CreateHandle(isolate, new PowerSaveBlocker); +void PowerSaveBlocker::BuildPrototype( + v8::Isolate* isolate, v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .SetMethod("start", &PowerSaveBlocker::Start) + .SetMethod("stop", &PowerSaveBlocker::Stop) + .SetMethod("isStarted", &PowerSaveBlocker::IsStarted); } } // namespace api diff --git a/atom/browser/api/atom_api_power_save_blocker.h b/atom/browser/api/atom_api_power_save_blocker.h index a698d746ceb0..a20b493e75bf 100644 --- a/atom/browser/api/atom_api_power_save_blocker.h +++ b/atom/browser/api/atom_api_power_save_blocker.h @@ -24,13 +24,12 @@ class PowerSaveBlocker : public mate::TrackableObject { public: static mate::Handle Create(v8::Isolate* isolate); - protected: - PowerSaveBlocker(); - ~PowerSaveBlocker() override; + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); - // mate::Wrappable implementations: - mate::ObjectTemplateBuilder GetObjectTemplateBuilder( - v8::Isolate* isolate) override; + protected: + explicit PowerSaveBlocker(v8::Isolate* isolate); + ~PowerSaveBlocker() override; private: void UpdatePowerSaveBlocker(); @@ -38,7 +37,7 @@ class PowerSaveBlocker : public mate::TrackableObject { bool Stop(int id); bool IsStarted(int id); - scoped_ptr power_save_blocker_; + std::unique_ptr power_save_blocker_; // Currnet blocker type used by |power_save_blocker_| content::PowerSaveBlocker::PowerSaveBlockerType current_blocker_type_; diff --git a/atom/browser/api/atom_api_protocol.cc b/atom/browser/api/atom_api_protocol.cc index 09da9c71cadb..226f689d32de 100644 --- a/atom/browser/api/atom_api_protocol.cc +++ b/atom/browser/api/atom_api_protocol.cc @@ -15,6 +15,7 @@ #include "atom/common/native_mate_converters/net_converter.h" #include "atom/common/node_includes.h" #include "native_mate/dictionary.h" +#include "url/url_util.h" using content::BrowserThread; @@ -22,42 +23,11 @@ namespace atom { namespace api { -Protocol::Protocol(AtomBrowserContext* browser_context) +Protocol::Protocol(v8::Isolate* isolate, AtomBrowserContext* browser_context) : request_context_getter_(browser_context->GetRequestContext()), job_factory_(browser_context->job_factory()) { CHECK(job_factory_); -} - -mate::ObjectTemplateBuilder Protocol::GetObjectTemplateBuilder( - v8::Isolate* isolate) { - return mate::ObjectTemplateBuilder(isolate) - .SetMethod("registerStandardSchemes", &Protocol::RegisterStandardSchemes) - .SetMethod("registerServiceWorkerSchemes", - &Protocol::RegisterServiceWorkerSchemes) - .SetMethod("registerStringProtocol", - &Protocol::RegisterProtocol) - .SetMethod("registerBufferProtocol", - &Protocol::RegisterProtocol) - .SetMethod("registerFileProtocol", - &Protocol::RegisterProtocol) - .SetMethod("registerHttpProtocol", - &Protocol::RegisterProtocol) - .SetMethod("unregisterProtocol", &Protocol::UnregisterProtocol) - .SetMethod("isProtocolHandled", &Protocol::IsProtocolHandled) - .SetMethod("interceptStringProtocol", - &Protocol::InterceptProtocol) - .SetMethod("interceptBufferProtocol", - &Protocol::InterceptProtocol) - .SetMethod("interceptFileProtocol", - &Protocol::InterceptProtocol) - .SetMethod("interceptHttpProtocol", - &Protocol::InterceptProtocol) - .SetMethod("uninterceptProtocol", &Protocol::UninterceptProtocol); -} - -void Protocol::RegisterStandardSchemes( - const std::vector& schemes) { - atom::AtomBrowserClient::SetCustomSchemes(schemes); + Init(isolate); } void Protocol::RegisterServiceWorkerSchemes( @@ -150,7 +120,34 @@ std::string Protocol::ErrorCodeToString(ProtocolError error) { // static mate::Handle Protocol::Create( v8::Isolate* isolate, AtomBrowserContext* browser_context) { - return mate::CreateHandle(isolate, new Protocol(browser_context)); + return mate::CreateHandle(isolate, new Protocol(isolate, browser_context)); +} + +// static +void Protocol::BuildPrototype( + v8::Isolate* isolate, v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .SetMethod("registerServiceWorkerSchemes", + &Protocol::RegisterServiceWorkerSchemes) + .SetMethod("registerStringProtocol", + &Protocol::RegisterProtocol) + .SetMethod("registerBufferProtocol", + &Protocol::RegisterProtocol) + .SetMethod("registerFileProtocol", + &Protocol::RegisterProtocol) + .SetMethod("registerHttpProtocol", + &Protocol::RegisterProtocol) + .SetMethod("unregisterProtocol", &Protocol::UnregisterProtocol) + .SetMethod("isProtocolHandled", &Protocol::IsProtocolHandled) + .SetMethod("interceptStringProtocol", + &Protocol::InterceptProtocol) + .SetMethod("interceptBufferProtocol", + &Protocol::InterceptProtocol) + .SetMethod("interceptFileProtocol", + &Protocol::InterceptProtocol) + .SetMethod("interceptHttpProtocol", + &Protocol::InterceptProtocol) + .SetMethod("uninterceptProtocol", &Protocol::UninterceptProtocol); } } // namespace api @@ -159,13 +156,24 @@ mate::Handle Protocol::Create( namespace { +void RegisterStandardSchemes( + const std::vector& schemes) { + for (const auto& scheme : schemes) + url::AddStandardScheme(scheme.c_str(), url::SCHEME_WITHOUT_PORT); +} + +mate::Handle CreateProtocol(v8::Isolate* isolate) { + auto browser_context = static_cast( + atom::AtomBrowserMainParts::Get()->browser_context()); + return atom::api::Protocol::Create(isolate, browser_context); +} + void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { v8::Isolate* isolate = context->GetIsolate(); mate::Dictionary dict(isolate, exports); - auto browser_context = static_cast( - atom::AtomBrowserMainParts::Get()->browser_context()); - dict.Set("protocol", atom::api::Protocol::Create(isolate, browser_context)); + dict.SetMethod("createProtocolObject", base::Bind(&CreateProtocol, isolate)); + dict.SetMethod("registerStandardSchemes", &RegisterStandardSchemes); } } // namespace diff --git a/atom/browser/api/atom_api_protocol.h b/atom/browser/api/atom_api_protocol.h index 8aef406fbc38..d33e63fa9cff 100644 --- a/atom/browser/api/atom_api_protocol.h +++ b/atom/browser/api/atom_api_protocol.h @@ -30,7 +30,7 @@ class AtomURLRequestJobFactory; namespace api { -class Protocol : public mate::Wrappable { +class Protocol : public mate::Wrappable { public: using Handler = base::Callback)>; @@ -40,12 +40,11 @@ class Protocol : public mate::Wrappable { static mate::Handle Create( v8::Isolate* isolate, AtomBrowserContext* browser_context); - protected: - explicit Protocol(AtomBrowserContext* browser_context); + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); - // mate::Wrappable implementations: - virtual mate::ObjectTemplateBuilder GetObjectTemplateBuilder( - v8::Isolate* isolate); + protected: + Protocol(v8::Isolate* isolate, AtomBrowserContext* browser_context); private: // Possible errors. @@ -89,9 +88,6 @@ class Protocol : public mate::Wrappable { DISALLOW_COPY_AND_ASSIGN(CustomProtocolHandler); }; - // Register schemes to standard scheme list. - void RegisterStandardSchemes(const std::vector& schemes); - // Register schemes that can handle service worker. void RegisterServiceWorkerSchemes(const std::vector& schemes); @@ -114,10 +110,10 @@ class Protocol : public mate::Wrappable { const Handler& handler) { if (job_factory_->IsHandledProtocol(scheme)) return PROTOCOL_REGISTERED; - scoped_ptr> protocol_handler( + std::unique_ptr> protocol_handler( new CustomProtocolHandler( isolate(), request_context_getter_, handler)); - if (job_factory_->SetProtocolHandler(scheme, protocol_handler.Pass())) + if (job_factory_->SetProtocolHandler(scheme, std::move(protocol_handler))) return PROTOCOL_OK; else return PROTOCOL_FAIL; @@ -156,12 +152,12 @@ class Protocol : public mate::Wrappable { return PROTOCOL_FAIL; if (ContainsKey(original_protocols_, scheme)) return PROTOCOL_INTERCEPTED; - scoped_ptr> protocol_handler( + std::unique_ptr> protocol_handler( new CustomProtocolHandler( isolate(), request_context_getter_, handler)); original_protocols_.set( scheme, - job_factory_->ReplaceProtocol(scheme, protocol_handler.Pass())); + job_factory_->ReplaceProtocol(scheme, std::move(protocol_handler))); return PROTOCOL_OK; } @@ -180,7 +176,7 @@ class Protocol : public mate::Wrappable { // Map that stores the original protocols of schemes. using OriginalProtocolsMap = base::ScopedPtrHashMap< std::string, - scoped_ptr>; + std::unique_ptr>; OriginalProtocolsMap original_protocols_; AtomURLRequestJobFactory* job_factory_; // weak ref diff --git a/atom/browser/api/atom_api_render_process_preferences.cc b/atom/browser/api/atom_api_render_process_preferences.cc new file mode 100644 index 000000000000..59ae07b45c75 --- /dev/null +++ b/atom/browser/api/atom_api_render_process_preferences.cc @@ -0,0 +1,88 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/api/atom_api_render_process_preferences.h" + +#include "atom/browser/atom_browser_client.h" +#include "atom/browser/native_window.h" +#include "atom/browser/window_list.h" +#include "atom/common/native_mate_converters/value_converter.h" +#include "atom/common/node_includes.h" +#include "content/public/browser/render_process_host.h" +#include "native_mate/dictionary.h" +#include "native_mate/object_template_builder.h" + +namespace atom { + +namespace api { + +namespace { + +bool IsBrowserWindow(content::RenderProcessHost* process) { + content::WebContents* web_contents = + static_cast(AtomBrowserClient::Get())-> + GetWebContentsFromProcessID(process->GetID()); + if (!web_contents) + return false; + + NativeWindow* window = NativeWindow::FromWebContents(web_contents); + if (!window) + return false; + + return true; +} + +} // namespace + +RenderProcessPreferences::RenderProcessPreferences( + v8::Isolate* isolate, + const atom::RenderProcessPreferences::Predicate& predicate) + : preferences_(predicate) { + Init(isolate); +} + +RenderProcessPreferences::~RenderProcessPreferences() { +} + +int RenderProcessPreferences::AddEntry(const base::DictionaryValue& entry) { + return preferences_.AddEntry(entry); +} + +void RenderProcessPreferences::RemoveEntry(int id) { + preferences_.RemoveEntry(id); +} + +// static +void RenderProcessPreferences::BuildPrototype( + v8::Isolate* isolate, v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .SetMethod("addEntry", &RenderProcessPreferences::AddEntry) + .SetMethod("removeEntry", &RenderProcessPreferences::RemoveEntry); +} + +// static +mate::Handle +RenderProcessPreferences::ForAllBrowserWindow(v8::Isolate* isolate) { + return mate::CreateHandle( + isolate, + new RenderProcessPreferences(isolate, base::Bind(&IsBrowserWindow))); +} + +} // namespace api + +} // namespace atom + +namespace { + +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { + mate::Dictionary dict(context->GetIsolate(), exports); + dict.SetMethod("forAllBrowserWindow", + &atom::api::RenderProcessPreferences::ForAllBrowserWindow); +} + +} // namespace + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_render_process_preferences, + Initialize) diff --git a/atom/browser/api/atom_api_render_process_preferences.h b/atom/browser/api/atom_api_render_process_preferences.h new file mode 100644 index 000000000000..a305f1361b2d --- /dev/null +++ b/atom/browser/api/atom_api_render_process_preferences.h @@ -0,0 +1,44 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_API_ATOM_API_RENDER_PROCESS_PREFERENCES_H_ +#define ATOM_BROWSER_API_ATOM_API_RENDER_PROCESS_PREFERENCES_H_ + +#include "atom/browser/render_process_preferences.h" +#include "native_mate/handle.h" +#include "native_mate/wrappable.h" + +namespace atom { + +namespace api { + +class RenderProcessPreferences + : public mate::Wrappable { + public: + static mate::Handle + ForAllBrowserWindow(v8::Isolate* isolate); + + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + + int AddEntry(const base::DictionaryValue& entry); + void RemoveEntry(int id); + + protected: + RenderProcessPreferences( + v8::Isolate* isolate, + const atom::RenderProcessPreferences::Predicate& predicate); + ~RenderProcessPreferences() override; + + private: + atom::RenderProcessPreferences preferences_; + + DISALLOW_COPY_AND_ASSIGN(RenderProcessPreferences); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_ATOM_API_RENDER_PROCESS_PREFERENCES_H_ diff --git a/atom/browser/api/atom_api_screen.cc b/atom/browser/api/atom_api_screen.cc index 407a71f0cc41..425d906e286e 100644 --- a/atom/browser/api/atom_api_screen.cc +++ b/atom/browser/api/atom_api_screen.cc @@ -47,9 +47,10 @@ std::vector MetricsToArray(uint32_t metrics) { } // namespace -Screen::Screen(gfx::Screen* screen) : screen_(screen) { - displays_ = screen_->GetAllDisplays(); +Screen::Screen(v8::Isolate* isolate, gfx::Screen* screen) + : screen_(screen) { screen_->AddObserver(this); + Init(isolate); } Screen::~Screen() { @@ -65,7 +66,7 @@ gfx::Display Screen::GetPrimaryDisplay() { } std::vector Screen::GetAllDisplays() { - return displays_; + return screen_->GetAllDisplays(); } gfx::Display Screen::GetDisplayNearestPoint(const gfx::Point& point) { @@ -77,39 +78,18 @@ gfx::Display Screen::GetDisplayMatching(const gfx::Rect& match_rect) { } void Screen::OnDisplayAdded(const gfx::Display& new_display) { - displays_.push_back(new_display); Emit("display-added", new_display); } void Screen::OnDisplayRemoved(const gfx::Display& old_display) { - auto iter = FindById(&displays_, old_display.id()); - if (iter == displays_.end()) - return; - - displays_.erase(iter); Emit("display-removed", old_display); } void Screen::OnDisplayMetricsChanged(const gfx::Display& display, uint32_t changed_metrics) { - auto iter = FindById(&displays_, display.id()); - if (iter == displays_.end()) - return; - - *iter = display; Emit("display-metrics-changed", display, MetricsToArray(changed_metrics)); } -mate::ObjectTemplateBuilder Screen::GetObjectTemplateBuilder( - v8::Isolate* isolate) { - return mate::ObjectTemplateBuilder(isolate) - .SetMethod("getCursorScreenPoint", &Screen::GetCursorScreenPoint) - .SetMethod("getPrimaryDisplay", &Screen::GetPrimaryDisplay) - .SetMethod("getAllDisplays", &Screen::GetAllDisplays) - .SetMethod("getDisplayNearestPoint", &Screen::GetDisplayNearestPoint) - .SetMethod("getDisplayMatching", &Screen::GetDisplayMatching); -} - // static v8::Local Screen::Create(v8::Isolate* isolate) { if (!Browser::Get()->is_ready()) { @@ -119,14 +99,25 @@ v8::Local Screen::Create(v8::Isolate* isolate) { return v8::Null(isolate); } - gfx::Screen* screen = gfx::Screen::GetNativeScreen(); + gfx::Screen* screen = gfx::Screen::GetScreen(); if (!screen) { isolate->ThrowException(v8::Exception::Error(mate::StringToV8( isolate, "Failed to get screen information"))); return v8::Null(isolate); } - return mate::CreateHandle(isolate, new Screen(screen)).ToV8(); + return mate::CreateHandle(isolate, new Screen(isolate, screen)).ToV8(); +} + +// static +void Screen::BuildPrototype( + v8::Isolate* isolate, v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .SetMethod("getCursorScreenPoint", &Screen::GetCursorScreenPoint) + .SetMethod("getPrimaryDisplay", &Screen::GetPrimaryDisplay) + .SetMethod("getAllDisplays", &Screen::GetAllDisplays) + .SetMethod("getDisplayNearestPoint", &Screen::GetDisplayNearestPoint) + .SetMethod("getDisplayMatching", &Screen::GetDisplayMatching); } } // namespace api diff --git a/atom/browser/api/atom_api_screen.h b/atom/browser/api/atom_api_screen.h index f724130fa7fc..c17b61288525 100644 --- a/atom/browser/api/atom_api_screen.h +++ b/atom/browser/api/atom_api_screen.h @@ -21,14 +21,17 @@ namespace atom { namespace api { -class Screen : public mate::EventEmitter, +class Screen : public mate::EventEmitter, public gfx::DisplayObserver { public: static v8::Local Create(v8::Isolate* isolate); + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + protected: - explicit Screen(gfx::Screen* screen); - virtual ~Screen(); + Screen(v8::Isolate* isolate, gfx::Screen* screen); + ~Screen() override; gfx::Point GetCursorScreenPoint(); gfx::Display GetPrimaryDisplay(); @@ -42,13 +45,8 @@ class Screen : public mate::EventEmitter, void OnDisplayMetricsChanged(const gfx::Display& display, uint32_t changed_metrics) override; - // mate::Wrappable: - mate::ObjectTemplateBuilder GetObjectTemplateBuilder( - v8::Isolate* isolate) override; - private: gfx::Screen* screen_; - std::vector displays_; DISALLOW_COPY_AND_ASSIGN(Screen); }; diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index e5c5198f0341..15aa2afe9cf3 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -9,9 +9,7 @@ #include "atom/browser/api/atom_api_cookies.h" #include "atom/browser/api/atom_api_download_item.h" -#include "atom/browser/api/atom_api_web_contents.h" #include "atom/browser/api/atom_api_web_request.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/atom_permission_manager.h" @@ -23,12 +21,13 @@ #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/guid.h" +#include "components/prefs/pref_service.h" #include "base/strings/string_number_conversions.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 "brightray/browser/net/devtools_network_controller_handle.h" #include "chrome/common/pref_names.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/storage_partition.h" @@ -37,6 +36,8 @@ #include "net/base/load_flags.h" #include "net/disk_cache/disk_cache.h" #include "net/dns/host_cache.h" +#include "net/http/http_auth_handler_factory.h" +#include "net/http/http_auth_preferences.h" #include "net/proxy/proxy_service.h" #include "net/proxy/proxy_config_service_fixed.h" #include "net/url_request/url_request_context.h" @@ -49,12 +50,12 @@ namespace { struct ClearStorageDataOptions { GURL origin; - uint32 storage_types = StoragePartition::REMOVE_DATA_MASK_ALL; - uint32 quota_types = StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL; + uint32_t storage_types = StoragePartition::REMOVE_DATA_MASK_ALL; + uint32_t quota_types = StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL; }; -uint32 GetStorageMask(const std::vector& storage_types) { - uint32 storage_mask = 0; +uint32_t GetStorageMask(const std::vector& storage_types) { + uint32_t storage_mask = 0; for (const auto& it : storage_types) { auto type = base::ToLowerASCII(it); if (type == "appcache") @@ -77,8 +78,8 @@ uint32 GetStorageMask(const std::vector& storage_types) { return storage_mask; } -uint32 GetQuotaMask(const std::vector& quota_types) { - uint32 quota_mask = 0; +uint32_t GetQuotaMask(const std::vector& quota_types) { + uint32_t quota_mask = 0; for (const auto& it : quota_types) { auto type = base::ToLowerASCII(it); if (type == "temporary") @@ -191,7 +192,7 @@ class ResolveProxyHelper { // Start the request. int result = proxy_service->ResolveProxy( - url, net::LOAD_NORMAL, &proxy_info_, completion_callback, + url, "GET", net::LOAD_NORMAL, &proxy_info_, completion_callback, &pac_req_, nullptr, net::BoundNetLog()); // Completed synchronously. @@ -285,15 +286,30 @@ void ClearHostResolverCacheInIO( } } +void AllowNTLMCredentialsForDomainsInIO( + const scoped_refptr& context_getter, + const std::string& domains) { + auto request_context = context_getter->GetURLRequestContext(); + auto auth_handler = request_context->http_auth_handler_factory(); + if (auth_handler) { + auto auth_preferences = const_cast( + auth_handler->http_auth_preferences()); + if (auth_preferences) + auth_preferences->set_server_whitelist(domains); + } +} + } // namespace -Session::Session(AtomBrowserContext* browser_context) - : browser_context_(browser_context) { - AttachAsUserData(browser_context); - +Session::Session(v8::Isolate* isolate, AtomBrowserContext* browser_context) + : devtools_network_emulation_client_id_(base::GenerateGUID()), + browser_context_(browser_context) { // Observe DownloadManger to get download notifications. content::BrowserContext::GetDownloadManager(browser_context)-> AddObserver(this); + + Init(isolate); + AttachAsUserData(browser_context); } Session::~Session() { @@ -303,13 +319,15 @@ Session::~Session() { void Session::OnDownloadCreated(content::DownloadManager* manager, content::DownloadItem* item) { - auto web_contents = item->GetWebContents(); - if (SavePageHandler::IsSavePageTypes(item->GetMimeType())) + if (item->IsSavePackageDownload()) return; + + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); bool prevent_default = Emit( "will-download", DownloadItem::Create(isolate(), item), - api::WebContents::CreateFrom(isolate(), web_contents)); + item->GetWebContents()); if (prevent_default) { item->Cancel(true); item->Remove(); @@ -366,7 +384,7 @@ void Session::SetDownloadPath(const base::FilePath& path) { } void Session::EnableNetworkEmulation(const mate::Dictionary& options) { - scoped_ptr conditions; + std::unique_ptr conditions; bool offline = false; double latency, download_throughput, upload_throughput; if (options.Get("offline", &offline) && offline) { @@ -381,25 +399,19 @@ void Session::EnableNetworkEmulation(const mate::Dictionary& options) { 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))); + browser_context_->network_controller_handle()->SetNetworkState( + devtools_network_emulation_client_id_, std::move(conditions)); + browser_context_->network_delegate()->SetDevToolsNetworkEmulationClientId( + devtools_network_emulation_client_id_); } 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))); + std::unique_ptr conditions; + browser_context_->network_controller_handle()->SetNetworkState( + devtools_network_emulation_client_id_, std::move(conditions)); + browser_context_->network_delegate()->SetDevToolsNetworkEmulationClientId( + std::string()); } void Session::SetCertVerifyProc(v8::Local val, @@ -435,6 +447,13 @@ void Session::ClearHostResolverCache(mate::Arguments* args) { callback)); } +void Session::AllowNTLMCredentialsForDomains(const std::string& domains) { + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&AllowNTLMCredentialsForDomainsInIO, + make_scoped_refptr(browser_context_->GetRequestContext()), + domains)); +} + v8::Local Session::Cookies(v8::Isolate* isolate) { if (cookies_.IsEmpty()) { auto handle = atom::api::Cookies::Create(isolate, browser_context()); @@ -458,7 +477,8 @@ mate::Handle Session::CreateFrom( if (existing) return mate::CreateHandle(isolate, static_cast(existing)); - auto handle = mate::CreateHandle(isolate, new Session(browser_context)); + auto handle = mate::CreateHandle( + isolate, new Session(isolate, browser_context)); g_wrap_session.Run(handle.ToV8()); return handle; } @@ -489,20 +509,14 @@ void Session::BuildPrototype(v8::Isolate* isolate, .SetMethod("setPermissionRequestHandler", &Session::SetPermissionRequestHandler) .SetMethod("clearHostResolverCache", &Session::ClearHostResolverCache) + .SetMethod("allowNTLMCredentialsForDomains", + &Session::AllowNTLMCredentialsForDomains) .SetProperty("cookies", &Session::Cookies) .SetProperty("webRequest", &Session::WebRequest); } -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 diff --git a/atom/browser/api/atom_api_session.h b/atom/browser/api/atom_api_session.h index 02d8ba5cdec0..0cebf09ea1f3 100644 --- a/atom/browser/api/atom_api_session.h +++ b/atom/browser/api/atom_api_session.h @@ -58,7 +58,7 @@ class Session: public mate::TrackableObject, v8::Local prototype); protected: - explicit Session(AtomBrowserContext* browser_context); + Session(v8::Isolate* isolate, AtomBrowserContext* browser_context); ~Session(); // content::DownloadManager::Observer: @@ -79,6 +79,7 @@ class Session: public mate::TrackableObject, void SetPermissionRequestHandler(v8::Local val, mate::Arguments* args); void ClearHostResolverCache(mate::Arguments* args); + void AllowNTLMCredentialsForDomains(const std::string& domains); v8::Local Cookies(v8::Isolate* isolate); v8::Local WebRequest(v8::Isolate* isolate); @@ -86,6 +87,9 @@ class Session: public mate::TrackableObject, v8::Global cookies_; v8::Global web_request_; + // The X-DevTools-Emulate-Network-Conditions-Client-Id. + std::string devtools_network_emulation_client_id_; + scoped_refptr browser_context_; DISALLOW_COPY_AND_ASSIGN(Session); diff --git a/atom/browser/api/atom_api_system_preferences.cc b/atom/browser/api/atom_api_system_preferences.cc new file mode 100644 index 000000000000..2b11aad25278 --- /dev/null +++ b/atom/browser/api/atom_api_system_preferences.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/api/atom_api_system_preferences.h" + +#include "atom/common/native_mate_converters/callback.h" +#include "atom/common/native_mate_converters/value_converter.h" +#include "atom/common/node_includes.h" +#include "native_mate/dictionary.h" + +#if defined(OS_WIN) +#include "ui/base/win/shell.h" +#endif + +namespace atom { + +namespace api { + +SystemPreferences::SystemPreferences(v8::Isolate* isolate) { + Init(isolate); +} + +SystemPreferences::~SystemPreferences() { +} + +#if defined(OS_WIN) +bool SystemPreferences::IsAeroGlassEnabled() { + return ui::win::IsAeroGlassEnabled(); +} +#endif + +#if !defined(OS_MACOSX) +bool SystemPreferences::IsDarkMode() { + return false; +} +#endif + +// static +mate::Handle SystemPreferences::Create( + v8::Isolate* isolate) { + return mate::CreateHandle(isolate, new SystemPreferences(isolate)); +} + +// static +void SystemPreferences::BuildPrototype( + v8::Isolate* isolate, v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) +#if defined(OS_WIN) + .SetMethod("isAeroGlassEnabled", &SystemPreferences::IsAeroGlassEnabled) +#elif defined(OS_MACOSX) + .SetMethod("subscribeNotification", + &SystemPreferences::SubscribeNotification) + .SetMethod("unsubscribeNotification", + &SystemPreferences::UnsubscribeNotification) + .SetMethod("getUserDefault", &SystemPreferences::GetUserDefault) +#endif + .SetMethod("isDarkMode", &SystemPreferences::IsDarkMode); +} + +} // namespace api + +} // namespace atom + +namespace { + +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { + v8::Isolate* isolate = context->GetIsolate(); + mate::Dictionary dict(isolate, exports); + dict.Set("systemPreferences", atom::api::SystemPreferences::Create(isolate)); +} + +} // namespace + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_system_preferences, Initialize); diff --git a/atom/browser/api/atom_api_system_preferences.h b/atom/browser/api/atom_api_system_preferences.h new file mode 100644 index 000000000000..7779ce007816 --- /dev/null +++ b/atom/browser/api/atom_api_system_preferences.h @@ -0,0 +1,57 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_API_ATOM_API_SYSTEM_PREFERENCES_H_ +#define ATOM_BROWSER_API_ATOM_API_SYSTEM_PREFERENCES_H_ + +#include + +#include "atom/browser/api/event_emitter.h" +#include "base/callback.h" +#include "native_mate/handle.h" + +namespace base { +class DictionaryValue; +} + +namespace atom { + +namespace api { + +class SystemPreferences : public mate::EventEmitter { + public: + static mate::Handle Create(v8::Isolate* isolate); + + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + +#if defined(OS_MACOSX) + using NotificationCallback = base::Callback< + void(const std::string&, const base::DictionaryValue&)>; +#endif + +#if defined(OS_WIN) + bool IsAeroGlassEnabled(); +#elif defined(OS_MACOSX) + int SubscribeNotification(const std::string& name, + const NotificationCallback& callback); + void UnsubscribeNotification(int id); + v8::Local GetUserDefault(const std::string& name, + const std::string& type); +#endif + bool IsDarkMode(); + + protected: + explicit SystemPreferences(v8::Isolate* isolate); + ~SystemPreferences() override; + + private: + DISALLOW_COPY_AND_ASSIGN(SystemPreferences); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_ATOM_API_SYSTEM_PREFERENCES_H_ diff --git a/atom/browser/api/atom_api_system_preferences_mac.mm b/atom/browser/api/atom_api_system_preferences_mac.mm new file mode 100644 index 000000000000..72011500bd04 --- /dev/null +++ b/atom/browser/api/atom_api_system_preferences_mac.mm @@ -0,0 +1,102 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/api/atom_api_system_preferences.h" + +#include + +#import + +#include "atom/browser/mac/dict_util.h" +#include "atom/common/native_mate_converters/value_converter.h" +#include "atom/common/native_mate_converters/gurl_converter.h" +#include "base/strings/sys_string_conversions.h" +#include "base/values.h" +#include "net/base/mac/url_conversions.h" + +namespace atom { + +namespace api { + +namespace { + +int g_next_id = 0; + +// The map to convert |id| to |int|. +std::map g_id_map; + +} // namespace + +int SystemPreferences::SubscribeNotification( + const std::string& name, const NotificationCallback& callback) { + int request_id = g_next_id++; + __block NotificationCallback copied_callback = callback; + g_id_map[request_id] = [[NSDistributedNotificationCenter defaultCenter] + addObserverForName:base::SysUTF8ToNSString(name) + object:nil + queue:nil + usingBlock:^(NSNotification* notification) { + std::unique_ptr user_info = + NSDictionaryToDictionaryValue(notification.userInfo); + if (user_info) { + copied_callback.Run( + base::SysNSStringToUTF8(notification.name), + *user_info); + } else { + copied_callback.Run( + base::SysNSStringToUTF8(notification.name), + base::DictionaryValue()); + } + } + ]; + return request_id; +} + +void SystemPreferences::UnsubscribeNotification(int request_id) { + auto iter = g_id_map.find(request_id); + if (iter != g_id_map.end()) { + id observer = iter->second; + [[NSDistributedNotificationCenter defaultCenter] removeObserver:observer]; + g_id_map.erase(iter); + } +} + +v8::Local SystemPreferences::GetUserDefault( + const std::string& name, const std::string& type) { + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + NSString* key = base::SysUTF8ToNSString(name); + if (type == "string") { + return mate::StringToV8(isolate(), + base::SysNSStringToUTF8([defaults stringForKey:key])); + } else if (type == "boolean") { + return v8::Boolean::New(isolate(), [defaults boolForKey:key]); + } else if (type == "float") { + return v8::Number::New(isolate(), [defaults floatForKey:key]); + } else if (type == "integer") { + return v8::Integer::New(isolate(), [defaults integerForKey:key]); + } else if (type == "double") { + return v8::Number::New(isolate(), [defaults doubleForKey:key]); + } else if (type == "url") { + return mate::ConvertToV8(isolate(), + net::GURLWithNSURL([defaults URLForKey:key])); + } else if (type == "array") { + return mate::ConvertToV8(isolate(), + *NSArrayToListValue([defaults arrayForKey:key])); + } else if (type == "dictionary") { + return mate::ConvertToV8(isolate(), + *NSDictionaryToDictionaryValue([defaults dictionaryForKey:key])); + } else { + return v8::Undefined(isolate()); + } +} + +bool SystemPreferences::IsDarkMode() { + NSString* mode = [[NSUserDefaults standardUserDefaults] + stringForKey:@"AppleInterfaceStyle"]; + return [mode isEqualToString:@"Dark"]; +} + +} // namespace api + +} // namespace atom diff --git a/atom/browser/api/atom_api_tray.cc b/atom/browser/api/atom_api_tray.cc index 5f351f485049..c84e8a1d66b6 100644 --- a/atom/browser/api/atom_api_tray.cc +++ b/atom/browser/api/atom_api_tray.cc @@ -9,6 +9,7 @@ #include "atom/browser/api/atom_api_menu.h" #include "atom/browser/browser.h" #include "atom/browser/ui/tray_icon.h" +#include "atom/common/api/atom_api_native_image.h" #include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/image_converter.h" #include "atom/common/native_mate_converters/string16_converter.h" @@ -22,9 +23,9 @@ namespace atom { namespace api { -Tray::Tray(const gfx::Image& image) +Tray::Tray(v8::Isolate* isolate, mate::Handle image) : tray_icon_(TrayIcon::Create()) { - tray_icon_->SetImage(image); + SetImage(isolate, image); tray_icon_->AddObserver(this); } @@ -32,13 +33,14 @@ Tray::~Tray() { } // static -mate::Wrappable* Tray::New(v8::Isolate* isolate, const gfx::Image& image) { +mate::WrappableBase* Tray::New(v8::Isolate* isolate, + mate::Handle image) { if (!Browser::Get()->is_ready()) { isolate->ThrowException(v8::Exception::Error(mate::StringToV8( isolate, "Cannot create Tray before app is ready"))); return nullptr; } - return new Tray(image); + return new Tray(isolate, image); } void Tray::OnClicked(const gfx::Rect& bounds, int modifiers) { @@ -94,29 +96,38 @@ void Tray::OnDragEnded() { Emit("drag-end"); } -void Tray::SetImage(mate::Arguments* args, const gfx::Image& image) { - tray_icon_->SetImage(image); +void Tray::SetImage(v8::Isolate* isolate, mate::Handle image) { +#if defined(OS_WIN) + tray_icon_->SetImage(image->GetHICON(GetSystemMetrics(SM_CXSMICON))); +#else + tray_icon_->SetImage(image->image()); +#endif } -void Tray::SetPressedImage(mate::Arguments* args, const gfx::Image& image) { - tray_icon_->SetPressedImage(image); +void Tray::SetPressedImage(v8::Isolate* isolate, + mate::Handle image) { +#if defined(OS_WIN) + tray_icon_->SetPressedImage(image->GetHICON(GetSystemMetrics(SM_CXSMICON))); +#else + tray_icon_->SetPressedImage(image->image()); +#endif } -void Tray::SetToolTip(mate::Arguments* args, const std::string& tool_tip) { +void Tray::SetToolTip(const std::string& tool_tip) { tray_icon_->SetToolTip(tool_tip); } -void Tray::SetTitle(mate::Arguments* args, const std::string& title) { +void Tray::SetTitle(const std::string& title) { tray_icon_->SetTitle(title); } -void Tray::SetHighlightMode(mate::Arguments* args, bool highlight) { +void Tray::SetHighlightMode(bool highlight) { tray_icon_->SetHighlightMode(highlight); } void Tray::DisplayBalloon(mate::Arguments* args, const mate::Dictionary& options) { - gfx::Image icon; + mate::Handle icon; options.Get("icon", &icon); base::string16 title, content; if (!options.Get("title", &title) || @@ -125,7 +136,14 @@ void Tray::DisplayBalloon(mate::Arguments* args, return; } - tray_icon_->DisplayBalloon(icon, title, content); +#if defined(OS_WIN) + tray_icon_->DisplayBalloon( + icon.IsEmpty() ? NULL : icon->GetHICON(GetSystemMetrics(SM_CXSMICON)), + title, content); +#else + tray_icon_->DisplayBalloon( + icon.IsEmpty() ? gfx::Image() : icon->image(), title, content); +#endif } void Tray::PopUpContextMenu(mate::Arguments* args) { @@ -136,7 +154,8 @@ void Tray::PopUpContextMenu(mate::Arguments* args) { tray_icon_->PopUpContextMenu(pos, menu.IsEmpty() ? nullptr : menu->model()); } -void Tray::SetContextMenu(mate::Arguments* args, Menu* menu) { +void Tray::SetContextMenu(v8::Isolate* isolate, mate::Handle menu) { + menu_.Reset(isolate, menu.ToV8()); tray_icon_->SetContextMenu(menu->model()); } @@ -162,7 +181,7 @@ void Tray::BuildPrototype(v8::Isolate* isolate, .SetMethod("setHighlightMode", &Tray::SetHighlightMode) .SetMethod("displayBalloon", &Tray::DisplayBalloon) .SetMethod("popUpContextMenu", &Tray::PopUpContextMenu) - .SetMethod("_setContextMenu", &Tray::SetContextMenu); + .SetMethod("setContextMenu", &Tray::SetContextMenu); } } // namespace api diff --git a/atom/browser/api/atom_api_tray.h b/atom/browser/api/atom_api_tray.h index 0e0d153ad0d3..a6c329567ca3 100644 --- a/atom/browser/api/atom_api_tray.h +++ b/atom/browser/api/atom_api_tray.h @@ -11,6 +11,7 @@ #include "atom/browser/api/trackable_object.h" #include "atom/browser/ui/tray_icon_observer.h" #include "base/memory/scoped_ptr.h" +#include "native_mate/handle.h" namespace gfx { class Image; @@ -28,17 +29,19 @@ class TrayIcon; namespace api { class Menu; +class NativeImage; class Tray : public mate::TrackableObject, public TrayIconObserver { public: - static mate::Wrappable* New(v8::Isolate* isolate, const gfx::Image& image); + static mate::WrappableBase* New( + v8::Isolate* isolate, mate::Handle image); static void BuildPrototype(v8::Isolate* isolate, v8::Local prototype); protected: - explicit Tray(const gfx::Image& image); + Tray(v8::Isolate* isolate, mate::Handle image); ~Tray() override; // TrayIconObserver: @@ -54,19 +57,20 @@ class Tray : public mate::TrackableObject, void OnDragExited() override; void OnDragEnded() override; - 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); - void SetTitle(mate::Arguments* args, const std::string& title); - void SetHighlightMode(mate::Arguments* args, bool highlight); + void SetImage(v8::Isolate* isolate, mate::Handle image); + void SetPressedImage(v8::Isolate* isolate, mate::Handle image); + void SetToolTip(const std::string& tool_tip); + void SetTitle(const std::string& title); + void SetHighlightMode(bool highlight); void DisplayBalloon(mate::Arguments* args, const mate::Dictionary& options); void PopUpContextMenu(mate::Arguments* args); - void SetContextMenu(mate::Arguments* args, Menu* menu); + void SetContextMenu(v8::Isolate* isolate, mate::Handle menu); private: v8::Local ModifiersToObject(v8::Isolate* isolate, int modifiers); - scoped_ptr tray_icon_; + v8::Global menu_; + std::unique_ptr tray_icon_; DISALLOW_COPY_AND_ASSIGN(Tray); }; diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 4935adbc75ad..9d93d980be4a 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -13,12 +13,17 @@ #include "atom/browser/atom_browser_client.h" #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" +#include "atom/browser/atom_security_state_model_client.h" +#include "atom/browser/lib/bluetooth_chooser.h" #include "atom/browser/native_window.h" +#include "atom/browser/net/atom_network_delegate.h" #include "atom/browser/web_contents_permission_helper.h" #include "atom/browser/web_contents_preferences.h" #include "atom/browser/web_view_guest_delegate.h" #include "atom/common/api/api_messages.h" #include "atom/common/api/event_emitter_caller.h" +#include "atom/common/color_util.h" +#include "atom/common/mouse_util.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" @@ -28,13 +33,14 @@ #include "atom/common/native_mate_converters/image_converter.h" #include "atom/common/native_mate_converters/string16_converter.h" #include "atom/common/native_mate_converters/value_converter.h" -#include "atom/common/mouse_util.h" +#include "atom/common/options_switches.h" #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/browser/renderer_host/render_widget_host_impl.h" #include "content/common/view_messages.h" #include "content/public/browser/favicon_status.h" #include "content/public/browser/native_web_keyboard_event.h" @@ -44,6 +50,7 @@ #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/resource_request_details.h" #include "content/public/browser/service_worker_context.h" @@ -57,6 +64,7 @@ #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 "third_party/WebKit/public/web/WebFindOptions.h" #include "ui/base/l10n/l10n_util.h" #include "atom/common/node_includes.h" @@ -138,7 +146,7 @@ struct Converter { net::HttpResponseHeaders* headers) { base::DictionaryValue response_headers; if (headers) { - void* iter = nullptr; + size_t iter = 0; std::string key; std::string value; while (headers->EnumerateHeaderLines(&iter, &key, &value)) { @@ -148,9 +156,9 @@ struct Converter { if (response_headers.GetList(key, &values)) values->AppendString(value); } else { - scoped_ptr values(new base::ListValue()); + std::unique_ptr values(new base::ListValue()); values->AppendString(value); - response_headers.Set(key, values.Pass()); + response_headers.Set(key, std::move(values)); } } } @@ -209,17 +217,27 @@ content::ServiceWorkerContext* GetServiceWorkerContext( } // namespace -WebContents::WebContents(content::WebContents* web_contents) +WebContents::WebContents(v8::Isolate* isolate, + content::WebContents* web_contents) : content::WebContentsObserver(web_contents), - type_(REMOTE) { - AttachAsUserData(web_contents); + embedder_(nullptr), + type_(REMOTE), + request_id_(0), + background_throttling_(true) { web_contents->SetUserAgentOverride(GetBrowserContext()->GetUserAgent()); + + Init(isolate); + AttachAsUserData(web_contents); } WebContents::WebContents(v8::Isolate* isolate, const mate::Dictionary& options) : embedder_(nullptr), - request_id_(0) { + request_id_(0), + background_throttling_(true) { + // Read options. + options.Get("backgroundThrottling", &background_throttling_); + // Whether it is a guest WebContents. bool is_guest = false; options.Get("isGuest", &is_guest); @@ -244,8 +262,9 @@ WebContents::WebContents(v8::Isolate* isolate, content::WebContents* web_contents; if (is_guest) { - content::SiteInstance* site_instance = content::SiteInstance::CreateForURL( - session->browser_context(), GURL("chrome-guest://fake-host")); + scoped_refptr site_instance = + content::SiteInstance::CreateForURL( + session->browser_context(), GURL("chrome-guest://fake-host")); content::WebContents::CreateParams params( session->browser_context(), site_instance); guest_delegate_.reset(new WebViewGuestDelegate); @@ -257,8 +276,7 @@ WebContents::WebContents(v8::Isolate* isolate, } Observe(web_contents); - AttachAsUserData(web_contents); - InitWithWebContents(web_contents); + InitWithWebContents(web_contents, session->browser_context()); managed_web_contents()->GetView()->SetDelegate(this); @@ -267,6 +285,8 @@ WebContents::WebContents(v8::Isolate* isolate, // Intialize permission helper. WebContentsPermissionHelper::CreateForWebContents(web_contents); + // Intialize security state client. + AtomSecurityStateModelClient::CreateForWebContents(web_contents); web_contents->SetUserAgentOverride(GetBrowserContext()->GetUserAgent()); @@ -284,6 +304,9 @@ WebContents::WebContents(v8::Isolate* isolate, if (owner_window) SetOwnerWindow(owner_window); } + + Init(isolate); + AttachAsUserData(web_contents); } WebContents::~WebContents() { @@ -303,9 +326,9 @@ WebContents::~WebContents() { } bool WebContents::AddMessageToConsole(content::WebContents* source, - int32 level, + int32_t level, const base::string16& message, - int32 line_no, + int32_t line_no, const base::string16& source_id) { if (type_ == BROWSER_WINDOW) { return false; @@ -315,20 +338,13 @@ bool WebContents::AddMessageToConsole(content::WebContents* source, } } -bool WebContents::ShouldCreateWebContents( - content::WebContents* web_contents, - int route_id, - int main_frame_route_id, - WindowContainerType window_container_type, - const std::string& frame_name, - const GURL& target_url, - const std::string& partition_id, - content::SessionStorageNamespace* session_storage_namespace) { +void WebContents::OnCreateWindow(const GURL& target_url, + const std::string& frame_name, + WindowOpenDisposition disposition) { if (type_ == BROWSER_WINDOW) - Emit("-new-window", target_url, frame_name, NEW_FOREGROUND_TAB); + Emit("-new-window", target_url, frame_name, disposition); else - Emit("new-window", target_url, frame_name, NEW_FOREGROUND_TAB); - return false; + Emit("new-window", target_url, frame_name, disposition); } content::WebContents* WebContents::OpenURLFromTab( @@ -365,7 +381,7 @@ void WebContents::MoveContents(content::WebContents* source, void WebContents::CloseContents(content::WebContents* source) { Emit("close"); - if (type_ == BROWSER_WINDOW) + if (type_ == BROWSER_WINDOW && owner_window()) owner_window()->CloseContents(source); } @@ -380,14 +396,12 @@ bool WebContents::IsPopupOrPanel(const content::WebContents* source) const { void WebContents::HandleKeyboardEvent( content::WebContents* source, const content::NativeWebKeyboardEvent& event) { - if (event.windowsKeyCode == ui::VKEY_ESCAPE && is_html_fullscreen()) { - // Escape exits tabbed fullscreen mode. - ExitFullscreenModeForTab(source); - } else if (type_ == BROWSER_WINDOW) { - owner_window()->HandleKeyboardEvent(source, event); - } else if (type_ == WEB_VIEW && guest_delegate_) { + if (type_ == WEB_VIEW && embedder_) { // Send the unhandled keyboard events back to the embedder. - guest_delegate_->HandleKeyboardEvent(source, event); + embedder_->HandleKeyboardEvent(source, event); + } else { + // Go to the default keyboard handling. + CommonWebContentsDelegate::HandleKeyboardEvent(source, event); } } @@ -416,22 +430,24 @@ void WebContents::ExitFullscreenModeForTab(content::WebContents* source) { void WebContents::RendererUnresponsive(content::WebContents* source) { Emit("unresponsive"); - if (type_ == BROWSER_WINDOW) + if (type_ == BROWSER_WINDOW && owner_window()) owner_window()->RendererUnresponsive(source); } void WebContents::RendererResponsive(content::WebContents* source) { Emit("responsive"); - if (type_ == BROWSER_WINDOW) + if (type_ == BROWSER_WINDOW && owner_window()) owner_window()->RendererResponsive(source); } bool WebContents::HandleContextMenu(const content::ContextMenuParams& params) { - if (!params.custom_context.is_pepper_menu) - return false; + if (params.custom_context.is_pepper_menu) { + Emit("pepper-context-menu", std::make_pair(params, web_contents())); + web_contents()->NotifyContextMenuClosed(params.custom_context); + } else { + Emit("context-menu", std::make_pair(params, web_contents())); + } - Emit("pepper-context-menu", std::make_pair(params, web_contents())); - web_contents()->NotifyContextMenuClosed(params.custom_context); return true; } @@ -454,6 +470,7 @@ void WebContents::FindReply(content::WebContents* web_contents, result.Set("requestId", request_id); result.Set("selectionArea", selection_rect); result.Set("finalUpdate", final_update); + result.Set("activeMatchOrdinal", active_match_ordinal); Emit("found-in-page", result); } else if (final_update) { result.Set("requestId", request_id); @@ -463,6 +480,13 @@ void WebContents::FindReply(content::WebContents* web_contents, } } +bool WebContents::CheckMediaAccessPermission( + content::WebContents* web_contents, + const GURL& security_origin, + content::MediaStreamType type) { + return true; +} + void WebContents::RequestMediaAccessPermission( content::WebContents* web_contents, const content::MediaStreamRequest& request, @@ -481,6 +505,14 @@ void WebContents::RequestToLockMouse( permission_helper->RequestPointerLockPermission(user_gesture); } +std::unique_ptr WebContents::RunBluetoothChooser( + content::RenderFrameHost* frame, + const content::BluetoothChooser::EventHandler& event_handler) { + std::unique_ptr bluetooth_chooser( + new BluetoothChooser(this, event_handler)); + return std::move(bluetooth_chooser); +} + 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. @@ -502,11 +534,11 @@ void WebContents::PluginCrashed(const base::FilePath& plugin_path, Emit("plugin-crashed", info.name, info.version); } -void WebContents::MediaStartedPlaying() { +void WebContents::MediaStartedPlaying(const MediaPlayerId& id) { Emit("media-started-playing"); } -void WebContents::MediaPaused() { +void WebContents::MediaStoppedPlaying(const MediaPlayerId& id) { Emit("media-paused"); } @@ -536,18 +568,21 @@ void WebContents::DidFinishLoad(content::RenderFrameHost* render_frame_host, void WebContents::DidFailProvisionalLoad( content::RenderFrameHost* render_frame_host, const GURL& url, - int error_code, - const base::string16& error_description, + int code, + const base::string16& description, bool was_ignored_by_handler) { - Emit("did-fail-provisional-load", error_code, error_description, url); + bool is_main_frame = !render_frame_host->GetParent(); + Emit("did-fail-provisional-load", code, description, url, is_main_frame); + Emit("did-fail-load", code, description, url, is_main_frame); } void WebContents::DidFailLoad(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); + bool is_main_frame = !render_frame_host->GetParent(); + Emit("did-fail-load", error_code, error_description, url, is_main_frame); } void WebContents::DidStartLoading() { @@ -567,7 +602,8 @@ void WebContents::DidGetResourceResponseStart( details.http_response_code, details.method, details.referrer, - details.headers.get()); + details.headers.get(), + ResourceTypeToString(details.resource_type)); } void WebContents::DidGetRedirectForResourceRequest( @@ -613,6 +649,10 @@ void WebContents::DidUpdateFaviconURL( Emit("page-favicon-updated", unique_urls); } +void WebContents::DevToolsReloadPage() { + Emit("devtools-reload-page"); +} + void WebContents::DevToolsFocused() { Emit("devtools-focused"); } @@ -624,6 +664,11 @@ void WebContents::DevToolsOpened() { isolate(), managed_web_contents()->GetDevToolsWebContents()); devtools_web_contents_.Reset(isolate(), handle.ToV8()); + // Set inspected tabID. + base::FundamentalValue tab_id(ID()); + managed_web_contents()->CallClientFunction( + "DevToolsAPI.setInspectedTabId", &tab_id, nullptr, nullptr); + // Inherit owner window in devtools. if (owner_window()) handle->SetOwnerWindow(managed_web_contents()->GetDevToolsWebContents(), @@ -703,7 +748,8 @@ void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) { Emit("did-fail-load", static_cast(net::ERR_INVALID_URL), net::ErrorToShortString(net::ERR_INVALID_URL), - url.possibly_invalid_spec()); + url.possibly_invalid_spec(), + true); return; } @@ -726,6 +772,25 @@ void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) { params.should_clear_history_list = true; params.override_user_agent = content::NavigationController::UA_OVERRIDE_TRUE; web_contents()->GetController().LoadURLWithParams(params); + + // Set the background color of RenderWidgetHostView. + // We have to call it right after LoadURL because the RenderViewHost is only + // created after loading a page. + const auto view = web_contents()->GetRenderWidgetHostView(); + WebContentsPreferences* web_preferences = + WebContentsPreferences::FromWebContents(web_contents()); + std::string color_name; + if (web_preferences->web_preferences()->GetString(options::kBackgroundColor, + &color_name)) { + view->SetBackgroundColor(ParseHexColor(color_name)); + } else { + view->SetBackgroundColor(SK_ColorTRANSPARENT); + } + + // For the same reason we can only disable hidden here. + const auto host = static_cast( + view->GetRenderWidgetHost()); + host->disable_hidden_ = !background_throttling_; } void WebContents::DownloadURL(const GURL& url) { @@ -749,6 +814,14 @@ bool WebContents::IsLoading() const { return web_contents()->IsLoading(); } +bool WebContents::IsLoadingMainFrame() const { + // Comparing site instances works because Electron always creates a new site + // instance when navigating, regardless of origin. See AtomBrowserClient. + return (web_contents()->GetLastCommittedURL().is_empty() || + web_contents()->GetSiteInstance() != + web_contents()->GetPendingSiteInstance()) && IsLoading(); +} + bool WebContents::IsWaitingForResponse() const { return web_contents()->IsWaitingForResponse(); } @@ -805,14 +878,20 @@ void WebContents::OpenDevTools(mate::Arguments* args) { if (type_ == REMOTE) return; - bool detach = false; - if (type_ == WEB_VIEW) { - detach = true; + std::string state; + if (type_ == WEB_VIEW || !owner_window()) { + state = "detach"; } else if (args && args->Length() == 1) { + bool detach = false; mate::Dictionary options; - args->GetNext(&options) && options.Get("detach", &detach); + if (args->GetNext(&options)) { + options.Get("mode", &state); + options.Get("detach", &detach); + if (state.empty() && detach) + state = "detach"; + } } - managed_web_contents()->SetCanDock(!detach); + managed_web_contents()->SetDockState(state); managed_web_contents()->ShowDevTools(); } @@ -991,8 +1070,8 @@ void WebContents::ReplaceMisspelling(const base::string16& word) { web_contents()->ReplaceMisspelling(word); } -uint32 WebContents::FindInPage(mate::Arguments* args) { - uint32 request_id = GetNextRequestId(); +uint32_t WebContents::FindInPage(mate::Arguments* args) { + uint32_t request_id = GetNextRequestId(); base::string16 search_text; blink::WebFindOptions options; if (!args->GetNext(&search_text) || search_text.empty()) { @@ -1018,9 +1097,10 @@ void WebContents::TabTraverse(bool reverse) { web_contents()->FocusThroughTabTraversal(reverse); } -bool WebContents::SendIPCMessage(const base::string16& channel, +bool WebContents::SendIPCMessage(bool all_frames, + const base::string16& channel, const base::ListValue& args) { - return Send(new AtomViewMsg_Message(routing_id(), channel, args)); + return Send(new AtomViewMsg_Message(routing_id(), all_frames, channel, args)); } void WebContents::SendInputEvent(v8::Isolate* isolate, @@ -1061,9 +1141,9 @@ void WebContents::BeginFrameSubscription( const FrameSubscriber::FrameCaptureCallback& callback) { const auto view = web_contents()->GetRenderWidgetHostView(); if (view) { - scoped_ptr frame_subscriber(new FrameSubscriber( - isolate(), view->GetVisibleViewportSize(), callback)); - view->BeginFrameSubscription(frame_subscriber.Pass()); + std::unique_ptr frame_subscriber(new FrameSubscriber( + isolate(), view, callback)); + view->BeginFrameSubscription(std::move(frame_subscriber)); } } @@ -1091,11 +1171,6 @@ void WebContents::SetSize(const SetSizeParams& params) { guest_delegate_->SetSize(params); } -void WebContents::SetAllowTransparency(bool allow) { - if (guest_delegate_) - guest_delegate_->SetAllowTransparency(allow); -} - bool WebContents::IsGuest() const { return type_ == WEB_VIEW; } @@ -1113,6 +1188,10 @@ v8::Local WebContents::GetOwnerBrowserWindow() { return v8::Null(isolate()); } +int32_t WebContents::ID() const { + return weak_map_id(); +} + v8::Local WebContents::Session(v8::Isolate* isolate) { return v8::Local::New(isolate, session_); } @@ -1150,6 +1229,7 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, .SetMethod("_getURL", &WebContents::GetURL) .SetMethod("getTitle", &WebContents::GetTitle) .SetMethod("isLoading", &WebContents::IsLoading) + .SetMethod("isLoadingMainFrame", &WebContents::IsLoadingMainFrame) .SetMethod("isWaitingForResponse", &WebContents::IsWaitingForResponse) .SetMethod("_stop", &WebContents::Stop) .SetMethod("_goBack", &WebContents::GoBack) @@ -1193,7 +1273,6 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, &WebContents::BeginFrameSubscription) .SetMethod("endFrameSubscription", &WebContents::EndFrameSubscription) .SetMethod("setSize", &WebContents::SetSize) - .SetMethod("setAllowTransparency", &WebContents::SetAllowTransparency) .SetMethod("isGuest", &WebContents::IsGuest) .SetMethod("getWebPreferences", &WebContents::GetWebPreferences) .SetMethod("getOwnerBrowserWindow", &WebContents::GetOwnerBrowserWindow) @@ -1205,6 +1284,7 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, .SetMethod("_printToPDF", &WebContents::PrintToPDF) .SetMethod("addWorkSpace", &WebContents::AddWorkSpace) .SetMethod("removeWorkSpace", &WebContents::RemoveWorkSpace) + .SetProperty("id", &WebContents::ID) .SetProperty("session", &WebContents::Session) .SetProperty("hostWebContents", &WebContents::HostWebContents) .SetProperty("devToolsWebContents", &WebContents::DevToolsWebContents) @@ -1237,7 +1317,8 @@ mate::Handle WebContents::CreateFrom( return mate::CreateHandle(isolate, static_cast(existing)); // Otherwise create a new WebContents wrapper object. - auto handle = mate::CreateHandle(isolate, new WebContents(web_contents)); + auto handle = mate::CreateHandle( + isolate, new WebContents(isolate, web_contents)); g_wrap_web_contents.Run(handle.ToV8()); return handle; } @@ -1250,16 +1331,8 @@ mate::Handle WebContents::Create( return handle; } -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 @@ -1275,6 +1348,8 @@ 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("fromId", + &mate::TrackableObject::FromWeakMapID); } } // namespace diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index b4f29090dbd3..85c43f1486aa 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -55,6 +55,9 @@ class WebContents : public mate::TrackableObject, static mate::Handle Create( v8::Isolate* isolate, const mate::Dictionary& options); + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + int GetID() const; bool Equal(const WebContents* web_contents) const; void LoadURL(const GURL& url, const mate::Dictionary& options); @@ -62,6 +65,7 @@ class WebContents : public mate::TrackableObject, GURL GetURL() const; base::string16 GetTitle() const; bool IsLoading() const; + bool IsLoadingMainFrame() const; bool IsWaitingForResponse() const; void Stop(); void ReloadIgnoringCache(); @@ -110,7 +114,7 @@ class WebContents : public mate::TrackableObject, void Unselect(); void Replace(const base::string16& word); void ReplaceMisspelling(const base::string16& word); - uint32 FindInPage(mate::Arguments* args); + uint32_t FindInPage(mate::Arguments* args); void StopFindInPage(content::StopFindAction action); // Focus. @@ -118,7 +122,8 @@ class WebContents : public mate::TrackableObject, void TabTraverse(bool reverse); // Send messages to browser. - bool SendIPCMessage(const base::string16& channel, + bool SendIPCMessage(bool all_frames, + const base::string16& channel, const base::ListValue& args); // Send WebInputEvent to the page. @@ -131,7 +136,6 @@ class WebContents : public mate::TrackableObject, // Methods for creating . void SetSize(const SetSizeParams& params); - void SetAllowTransparency(bool allow); bool IsGuest() const; // Callback triggered on permission response. @@ -139,6 +143,11 @@ class WebContents : public mate::TrackableObject, const GURL& origin, bool allowed); + // Create window with the given disposition. + void OnCreateWindow(const GURL& target_url, + const std::string& frame_name, + WindowOpenDisposition disposition); + // Returns the web preferences of current WebContents. v8::Local GetWebPreferences(v8::Isolate* isolate); @@ -146,35 +155,23 @@ class WebContents : public mate::TrackableObject, v8::Local GetOwnerBrowserWindow(); // Properties. + int32_t ID() const; v8::Local Session(v8::Isolate* isolate); content::WebContents* HostWebContents(); v8::Local DevToolsWebContents(v8::Isolate* isolate); v8::Local Debugger(v8::Isolate* isolate); - // mate::TrackableObject: - static void BuildPrototype(v8::Isolate* isolate, - v8::Local prototype); - protected: - explicit WebContents(content::WebContents* web_contents); + WebContents(v8::Isolate* isolate, content::WebContents* web_contents); WebContents(v8::Isolate* isolate, const mate::Dictionary& options); ~WebContents(); // content::WebContentsDelegate: bool AddMessageToConsole(content::WebContents* source, - int32 level, + int32_t level, const base::string16& message, - int32 line_no, + int32_t line_no, const base::string16& source_id) override; - bool ShouldCreateWebContents( - content::WebContents* web_contents, - int route_id, - int main_frame_route_id, - WindowContainerType window_container_type, - const std::string& frame_name, - const GURL& target_url, - const std::string& partition_id, - content::SessionStorageNamespace* session_storage_namespace) override; content::WebContents* OpenURLFromTab( content::WebContents* source, const content::OpenURLParams& params) override; @@ -202,6 +199,10 @@ class WebContents : public mate::TrackableObject, const gfx::Rect& selection_rect, int active_match_ordinal, bool final_update) override; + bool CheckMediaAccessPermission( + content::WebContents* web_contents, + const GURL& security_origin, + content::MediaStreamType type) override; void RequestMediaAccessPermission( content::WebContents* web_contents, const content::MediaStreamRequest& request, @@ -210,6 +211,9 @@ class WebContents : public mate::TrackableObject, content::WebContents* web_contents, bool user_gesture, bool last_unlocked_by_target) override; + std::unique_ptr RunBluetoothChooser( + content::RenderFrameHost* frame, + const content::BluetoothChooser::EventHandler& handler) override; // content::WebContentsObserver: void BeforeUnloadFired(const base::TimeTicks& proceed_time) override; @@ -248,10 +252,13 @@ class WebContents : public mate::TrackableObject, const std::vector& urls) override; void PluginCrashed(const base::FilePath& plugin_path, base::ProcessId plugin_pid) override; - void MediaStartedPlaying() override; - void MediaPaused() override; + void MediaStartedPlaying(const MediaPlayerId& id) override; + void MediaStoppedPlaying(const MediaPlayerId& id) override; void DidChangeThemeColor(SkColor theme_color) override; + // brightray::InspectableWebContentsDelegate: + void DevToolsReloadPage() override; + // brightray::InspectableWebContentsViewDelegate: void DevToolsFocused() override; void DevToolsOpened() override; @@ -266,7 +273,7 @@ class WebContents : public mate::TrackableObject, AtomBrowserContext* GetBrowserContext() const; - uint32 GetNextRequestId() { + uint32_t GetNextRequestId() { return ++request_id_; } @@ -286,7 +293,7 @@ class WebContents : public mate::TrackableObject, v8::Global devtools_web_contents_; v8::Global debugger_; - scoped_ptr guest_delegate_; + std::unique_ptr guest_delegate_; // The host webcontents that may contain this webcontents. WebContents* embedder_; @@ -295,7 +302,10 @@ class WebContents : public mate::TrackableObject, Type type_; // Request id used for findInPage request. - uint32 request_id_; + uint32_t request_id_; + + // Whether background throttling is disabled. + bool background_throttling_; DISALLOW_COPY_AND_ASSIGN(WebContents); }; diff --git a/atom/browser/api/atom_api_web_request.cc b/atom/browser/api/atom_api_web_request.cc index a987369ed82d..867901df065b 100644 --- a/atom/browser/api/atom_api_web_request.cc +++ b/atom/browser/api/atom_api_web_request.cc @@ -36,8 +36,10 @@ namespace atom { namespace api { -WebRequest::WebRequest(AtomBrowserContext* browser_context) +WebRequest::WebRequest(v8::Isolate* isolate, + AtomBrowserContext* browser_context) : browser_context_(browser_context) { + Init(isolate); } WebRequest::~WebRequest() { @@ -81,7 +83,7 @@ void WebRequest::SetListener(Method method, Event type, mate::Arguments* args) { mate::Handle WebRequest::Create( v8::Isolate* isolate, AtomBrowserContext* browser_context) { - return mate::CreateHandle(isolate, new WebRequest(browser_context)); + return mate::CreateHandle(isolate, new WebRequest(isolate, browser_context)); } // static diff --git a/atom/browser/api/atom_api_web_request.h b/atom/browser/api/atom_api_web_request.h index 9a6e17a04605..edcdcc5339e3 100644 --- a/atom/browser/api/atom_api_web_request.h +++ b/atom/browser/api/atom_api_web_request.h @@ -21,13 +21,12 @@ class WebRequest : public mate::TrackableObject { static mate::Handle Create(v8::Isolate* isolate, AtomBrowserContext* browser_context); - // mate::TrackableObject: static void BuildPrototype(v8::Isolate* isolate, v8::Local prototype); protected: - explicit WebRequest(AtomBrowserContext* browser_context); - ~WebRequest(); + WebRequest(v8::Isolate* isolate, AtomBrowserContext* browser_context); + ~WebRequest() override; // C++ can not distinguish overloaded member function. template diff --git a/atom/browser/api/atom_api_web_view_manager.cc b/atom/browser/api/atom_api_web_view_manager.cc index e57c5ffb6bb4..1586c3a10dde 100644 --- a/atom/browser/api/atom_api_web_view_manager.cc +++ b/atom/browser/api/atom_api_web_view_manager.cc @@ -2,9 +2,9 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#include "atom/browser/api/atom_api_web_contents.h" #include "atom/browser/web_contents_preferences.h" #include "atom/browser/web_view_manager.h" +#include "atom/common/native_mate_converters/content_converter.h" #include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/node_includes.h" #include "content/public/browser/browser_context.h" @@ -12,40 +12,14 @@ using atom::WebContentsPreferences; -namespace mate { - -template<> -struct Converter { - static bool FromV8(v8::Isolate* isolate, v8::Local val, - content::WebContents** out) { - atom::api::WebContents* contents; - if (!Converter::FromV8(isolate, val, &contents)) - return false; - *out = contents->web_contents(); - return true; - } -}; - -} // namespace mate - namespace { -atom::WebViewManager* GetWebViewManager(content::WebContents* web_contents) { - auto context = web_contents->GetBrowserContext(); - if (context) { - auto manager = context->GetGuestManager(); - return static_cast(manager); - } else { - return nullptr; - } -} - void AddGuest(int guest_instance_id, int element_instance_id, content::WebContents* embedder, content::WebContents* guest_web_contents, const base::DictionaryValue& options) { - auto manager = GetWebViewManager(embedder); + auto manager = atom::WebViewManager::GetWebViewManager(embedder); if (manager) manager->AddGuest(guest_instance_id, element_instance_id, embedder, guest_web_contents); @@ -54,7 +28,7 @@ void AddGuest(int guest_instance_id, } void RemoveGuest(content::WebContents* embedder, int guest_instance_id) { - auto manager = GetWebViewManager(embedder); + auto manager = atom::WebViewManager::GetWebViewManager(embedder); if (manager) manager->RemoveGuest(guest_instance_id); } diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index ea1d95b79ca0..ac3c510193ed 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -21,8 +21,11 @@ #include "native_mate/dictionary.h" #include "ui/gfx/geometry/rect.h" -#if defined(OS_WIN) +#if defined(TOOLKIT_VIEWS) #include "atom/browser/native_window_views.h" +#endif + +#if defined(OS_WIN) #include "atom/browser/ui/win/taskbar_host.h" #endif @@ -61,52 +64,6 @@ 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); - } - } -} - // Converts binary data to Buffer. v8::Local ToBuffer(v8::Isolate* isolate, void* val, int size) { auto buffer = node::Buffer::Copy(isolate, static_cast(val), size); @@ -120,21 +77,14 @@ v8::Local ToBuffer(v8::Isolate* isolate, void* val, int size) { Window::Window(v8::Isolate* isolate, const mate::Dictionary& options) { - // 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(options::kWebPreferences, &web_preferences); - // Be compatible with old options which are now in web_preferences. + // Copy the backgroundColor to webContents. v8::Local 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); + if (options.Get(options::kBackgroundColor, &value)) + web_preferences.Set(options::kBackgroundColor, value); // Creates the WebContents used by BrowserWindow. auto web_contents = WebContents::Create(isolate, web_preferences); @@ -142,7 +92,7 @@ Window::Window(v8::Isolate* isolate, const mate::Dictionary& options) { api_web_contents_ = web_contents.get(); // Keep a copy of the options for later use. - mate::Dictionary(isolate, web_contents->GetWrapper(isolate)).Set( + mate::Dictionary(isolate, web_contents->GetWrapper()).Set( "browserWindowOptions", options); // Creates BrowserWindow. @@ -152,6 +102,13 @@ Window::Window(v8::Isolate* isolate, const mate::Dictionary& options) { window_->InitFromOptions(options); window_->AddObserver(this); AttachAsUserData(window_.get()); + +#if defined(TOOLKIT_VIEWS) + // Sets the window icon. + mate::Handle icon; + if (options.Get(options::kIcon, &icon)) + SetIcon(icon); +#endif } Window::~Window() { @@ -191,6 +148,14 @@ void Window::OnWindowFocus() { Emit("focus"); } +void Window::OnWindowShow() { + Emit("show"); +} + +void Window::OnWindowHide() { + Emit("hide"); +} + void Window::OnWindowMaximize() { Emit("maximize"); } @@ -235,6 +200,10 @@ void Window::OnWindowScrollTouchEnd() { Emit("scroll-touch-end"); } +void Window::OnWindowSwipe(const std::string& direction) { + Emit("swipe", direction); +} + void Window::OnWindowEnterHtmlFullScreen() { Emit("enter-html-full-screen"); } @@ -266,7 +235,7 @@ void Window::OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) { #endif // static -mate::Wrappable* Window::New(v8::Isolate* isolate, mate::Arguments* args) { +mate::WrappableBase* 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"))); @@ -294,6 +263,10 @@ void Window::Focus() { window_->Focus(true); } +void Window::Blur() { + window_->Focus(false); +} + bool Window::IsFocused() { return window_->IsFocused(); } @@ -408,6 +381,12 @@ std::vector Window::GetMaximumSize() { return result; } +void Window::SetSheetOffset(double offsetY, mate::Arguments* args) { + double offsetX = 0.0; + args->GetNext(&offsetX); + window_->SetSheetOffset(offsetX, offsetY); +} + void Window::SetResizable(bool resizable) { window_->SetResizable(resizable); } @@ -650,6 +629,19 @@ void Window::ShowDefinitionForSelection() { } #endif +#if defined(TOOLKIT_VIEWS) +void Window::SetIcon(mate::Handle icon) { +#if defined(OS_WIN) + static_cast(window_.get())->SetIcon( + icon->GetHICON(GetSystemMetrics(SM_CXSMICON)), + icon->GetHICON(GetSystemMetrics(SM_CXICON))); +#elif defined(USE_X11) + static_cast(window_.get())->SetIcon( + icon->image().AsImageSkia()); +#endif +} +#endif + void Window::SetAspectRatio(double aspect_ratio, mate::Arguments* args) { gfx::Size extra_size; args->GetNext(&extra_size); @@ -688,6 +680,7 @@ void Window::BuildPrototype(v8::Isolate* isolate, .MakeDestroyable() .SetMethod("close", &Window::Close) .SetMethod("focus", &Window::Focus) + .SetMethod("blur", &Window::Blur) .SetMethod("isFocused", &Window::IsFocused) .SetMethod("show", &Window::Show) .SetMethod("showInactive", &Window::ShowInactive) @@ -713,6 +706,7 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("getMinimumSize", &Window::GetMinimumSize) .SetMethod("setMaximumSize", &Window::SetMaximumSize) .SetMethod("getMaximumSize", &Window::GetMaximumSize) + .SetMethod("setSheetOffset", &Window::SetSheetOffset) .SetMethod("setResizable", &Window::SetResizable) .SetMethod("isResizable", &Window::IsResizable) .SetMethod("setMovable", &Window::SetMovable) @@ -769,6 +763,9 @@ void Window::BuildPrototype(v8::Isolate* isolate, #if defined(OS_MACOSX) .SetMethod("showDefinitionForSelection", &Window::ShowDefinitionForSelection) +#endif +#if defined(TOOLKIT_VIEWS) + .SetMethod("setIcon", &Window::SetIcon) #endif .SetProperty("id", &Window::ID) .SetProperty("webContents", &Window::WebContents); @@ -779,7 +776,7 @@ v8::Local Window::From(v8::Isolate* isolate, NativeWindow* native_window) { auto existing = TrackableObject::FromWrappedClass(isolate, native_window); if (existing) - return existing->GetWrapper(isolate); + return existing->GetWrapper(); else return v8::Null(isolate); } diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 641124f4dfd2..e698eaaf78cc 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -14,6 +14,7 @@ #include "atom/browser/api/trackable_object.h" #include "atom/browser/native_window.h" #include "atom/browser/native_window_observer.h" +#include "atom/common/api/atom_api_native_image.h" #include "native_mate/handle.h" class GURL; @@ -38,7 +39,7 @@ class WebContents; class Window : public mate::TrackableObject, public NativeWindowObserver { public: - static mate::Wrappable* New(v8::Isolate* isolate, mate::Arguments* args); + static mate::WrappableBase* New(v8::Isolate* isolate, mate::Arguments* args); static void BuildPrototype(v8::Isolate* isolate, v8::Local prototype); @@ -51,13 +52,15 @@ class Window : public mate::TrackableObject, protected: Window(v8::Isolate* isolate, const mate::Dictionary& options); - virtual ~Window(); + ~Window() override; // NativeWindowObserver: void WillCloseWindow(bool* prevent_default) override; void OnWindowClosed() override; void OnWindowBlur() override; void OnWindowFocus() override; + void OnWindowShow() override; + void OnWindowHide() override; void OnWindowMaximize() override; void OnWindowUnmaximize() override; void OnWindowMinimize() override; @@ -67,6 +70,7 @@ class Window : public mate::TrackableObject, void OnWindowMoved() override; void OnWindowScrollTouchBegin() override; void OnWindowScrollTouchEnd() override; + void OnWindowSwipe(const std::string& direction) override; void OnWindowEnterFullScreen() override; void OnWindowLeaveFullScreen() override; void OnWindowEnterHtmlFullScreen() override; @@ -83,6 +87,7 @@ class Window : public mate::TrackableObject, // APIs for NativeWindow. void Close(); void Focus(); + void Blur(); bool IsFocused(); void Show(); void ShowInactive(); @@ -106,6 +111,7 @@ class Window : public mate::TrackableObject, std::vector GetMinimumSize(); void SetMaximumSize(int width, int height); std::vector GetMaximumSize(); + void SetSheetOffset(double offsetY, mate::Arguments* args); void SetResizable(bool resizable); bool IsResizable(); void SetMovable(bool movable); @@ -167,6 +173,10 @@ class Window : public mate::TrackableObject, void ShowDefinitionForSelection(); #endif +#if defined(TOOLKIT_VIEWS) + void SetIcon(mate::Handle icon); +#endif + void SetVisibleOnAllWorkspaces(bool visible); bool IsVisibleOnAllWorkspaces(); @@ -183,7 +193,7 @@ class Window : public mate::TrackableObject, api::WebContents* api_web_contents_; - scoped_ptr window_; + std::unique_ptr window_; DISALLOW_COPY_AND_ASSIGN(Window); }; diff --git a/atom/browser/api/event.cc b/atom/browser/api/event.cc index 5c87292ea52e..2554e4ad0dab 100644 --- a/atom/browser/api/event.cc +++ b/atom/browser/api/event.cc @@ -11,31 +11,15 @@ namespace mate { -namespace { - -v8::Persistent template_; - -} // namespace - -Event::Event() +Event::Event(v8::Isolate* isolate) : sender_(NULL), message_(NULL) { + Init(isolate); } Event::~Event() { } -ObjectTemplateBuilder Event::GetObjectTemplateBuilder(v8::Isolate* isolate) { - if (template_.IsEmpty()) - template_.Reset(isolate, ObjectTemplateBuilder(isolate) - .SetMethod("preventDefault", &Event::PreventDefault) - .SetMethod("sendReply", &Event::SendReply) - .Build()); - - return ObjectTemplateBuilder( - isolate, v8::Local::New(isolate, template_)); -} - void Event::SetSenderAndMessage(content::WebContents* sender, IPC::Message* message) { DCHECK(!sender_); @@ -52,7 +36,7 @@ void Event::WebContentsDestroyed() { } void Event::PreventDefault(v8::Isolate* isolate) { - GetWrapper(isolate)->Set(StringToV8(isolate, "defaultPrevented"), + GetWrapper()->Set(StringToV8(isolate, "defaultPrevented"), v8::True(isolate)); } @@ -61,12 +45,23 @@ bool Event::SendReply(const base::string16& json) { return false; AtomViewHostMsg_Message_Sync::WriteReplyParams(message_, json); - return sender_->Send(message_); + bool success = sender_->Send(message_); + message_ = NULL; + sender_ = NULL; + return success; } // static Handle Event::Create(v8::Isolate* isolate) { - return CreateHandle(isolate, new Event); + return mate::CreateHandle(isolate, new Event(isolate)); +} + +// static +void Event::BuildPrototype( + v8::Isolate* isolate, v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .SetMethod("preventDefault", &Event::PreventDefault) + .SetMethod("sendReply", &Event::SendReply); } } // namespace mate diff --git a/atom/browser/api/event.h b/atom/browser/api/event.h index 5cdc08324b72..81db638a0dc2 100644 --- a/atom/browser/api/event.h +++ b/atom/browser/api/event.h @@ -15,11 +15,14 @@ class Message; namespace mate { -class Event : public Wrappable, +class Event : public Wrappable, public content::WebContentsObserver { public: static Handle Create(v8::Isolate* isolate); + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + // Pass the sender and message to be replied. void SetSenderAndMessage(content::WebContents* sender, IPC::Message* message); @@ -30,11 +33,8 @@ class Event : public Wrappable, bool SendReply(const base::string16& json); protected: - Event(); - virtual ~Event(); - - // Wrappable implementations: - ObjectTemplateBuilder GetObjectTemplateBuilder(v8::Isolate* isolate) override; + explicit Event(v8::Isolate* isolate); + ~Event() override; // content::WebContentsObserver implementations: void WebContentsDestroyed() override; diff --git a/atom/browser/api/event_emitter.cc b/atom/browser/api/event_emitter.cc index be7018dafa44..7e392fddee34 100644 --- a/atom/browser/api/event_emitter.cc +++ b/atom/browser/api/event_emitter.cc @@ -34,11 +34,13 @@ v8::Local CreateEventObject(v8::Isolate* isolate) { } // namespace -EventEmitter::EventEmitter() { -} +namespace internal { -v8::Local EventEmitter::CreateJSEvent( - v8::Isolate* isolate, content::WebContents* sender, IPC::Message* message) { +v8::Local CreateJSEvent( + v8::Isolate* isolate, + v8::Local object, + content::WebContents* sender, + IPC::Message* message) { v8::Local event; bool use_native_event = sender && message; @@ -49,16 +51,20 @@ v8::Local EventEmitter::CreateJSEvent( } else { event = CreateEventObject(isolate); } - mate::Dictionary(isolate, event).Set("sender", GetWrapper(isolate)); + mate::Dictionary(isolate, event).Set("sender", object); return event; } -v8::Local EventEmitter::CreateCustomEvent( - v8::Isolate* isolate, v8::Local custom_event) { +v8::Local CreateCustomEvent( + v8::Isolate* isolate, + v8::Local object, + v8::Local custom_event) { v8::Local event = CreateEventObject(isolate); (void)event->SetPrototype(custom_event->CreationContext(), custom_event); - mate::Dictionary(isolate, event).Set("sender", GetWrapper(isolate)); + mate::Dictionary(isolate, event).Set("sender", object); return event; } +} // namespace internal + } // namespace mate diff --git a/atom/browser/api/event_emitter.h b/atom/browser/api/event_emitter.h index 42816d42a45b..99f6ed46e48f 100644 --- a/atom/browser/api/event_emitter.h +++ b/atom/browser/api/event_emitter.h @@ -20,17 +20,38 @@ class Message; namespace mate { +namespace internal { + +v8::Local CreateJSEvent(v8::Isolate* isolate, + v8::Local object, + content::WebContents* sender, + IPC::Message* message); +v8::Local CreateCustomEvent( + v8::Isolate* isolate, + v8::Local object, + v8::Local event); + +} // namespace internal + // Provide helperers to emit event in JavaScript. -class EventEmitter : public Wrappable { +template +class EventEmitter : public Wrappable { public: typedef std::vector> ValueArray; + // Make the convinient methods visible: + // https://isocpp.org/wiki/faq/templates#nondependent-name-lookup-members + v8::Local GetWrapper() { return Wrappable::GetWrapper(); } + v8::Isolate* isolate() const { return Wrappable::isolate(); } + // this.emit(name, event, args...); template bool EmitCustomEvent(const base::StringPiece& name, v8::Local event, const Args&... args) { - return EmitWithEvent(name, CreateCustomEvent(isolate(), event), args...); + return EmitWithEvent( + name, + internal::CreateCustomEvent(isolate(), GetWrapper(), event), args...); } // this.emit(name, new Event(), args...); @@ -47,12 +68,13 @@ class EventEmitter : public Wrappable { const Args&... args) { v8::Locker locker(isolate()); v8::HandleScope handle_scope(isolate()); - v8::Local event = CreateJSEvent(isolate(), sender, message); + v8::Local event = internal::CreateJSEvent( + isolate(), GetWrapper(), sender, message); return EmitWithEvent(name, event, args...); } protected: - EventEmitter(); + EventEmitter() {} private: // this.emit(name, event, args...); @@ -62,17 +84,11 @@ class EventEmitter : public Wrappable { const Args&... args) { v8::Locker locker(isolate()); v8::HandleScope handle_scope(isolate()); - EmitEvent(isolate(), GetWrapper(isolate()), name, event, args...); + EmitEvent(isolate(), GetWrapper(), name, event, args...); return event->Get( StringToV8(isolate(), "defaultPrevented"))->BooleanValue(); } - v8::Local CreateJSEvent(v8::Isolate* isolate, - content::WebContents* sender, - IPC::Message* message); - v8::Local CreateCustomEvent( - v8::Isolate* isolate, v8::Local event); - DISALLOW_COPY_AND_ASSIGN(EventEmitter); }; diff --git a/atom/browser/api/frame_subscriber.cc b/atom/browser/api/frame_subscriber.cc index b2b049ce8b74..f81a8bea8b1a 100644 --- a/atom/browser/api/frame_subscriber.cc +++ b/atom/browser/api/frame_subscriber.cc @@ -4,19 +4,18 @@ #include "atom/browser/api/frame_subscriber.h" -#include "atom/common/node_includes.h" #include "base/bind.h" -#include "media/base/video_frame.h" -#include "media/base/yuv_convert.h" +#include "atom/common/node_includes.h" +#include "content/public/browser/render_widget_host.h" namespace atom { namespace api { FrameSubscriber::FrameSubscriber(v8::Isolate* isolate, - const gfx::Size& size, + content::RenderWidgetHostView* view, const FrameCaptureCallback& callback) - : isolate_(isolate), size_(size), callback_(callback), weak_factory_(this) { + : isolate_(isolate), view_(view), callback_(callback), weak_factory_(this) { } bool FrameSubscriber::ShouldCaptureFrame( @@ -24,39 +23,39 @@ bool FrameSubscriber::ShouldCaptureFrame( base::TimeTicks present_time, scoped_refptr* storage, DeliverFrameCallback* callback) { - *storage = media::VideoFrame::CreateFrame( - media::PIXEL_FORMAT_YV12, - size_, gfx::Rect(size_), size_, base::TimeDelta()); - *callback = base::Bind(&FrameSubscriber::OnFrameDelivered, - weak_factory_.GetWeakPtr(), *storage); - return true; + const auto host = view_ ? view_->GetRenderWidgetHost() : nullptr; + if (!view_ || !host) + return false; + + const auto size = view_->GetVisibleViewportSize(); + + host->CopyFromBackingStore( + gfx::Rect(size), + size, + base::Bind(&FrameSubscriber::OnFrameDelivered, + weak_factory_.GetWeakPtr(), callback_), + kBGRA_8888_SkColorType); + + return false; } -void FrameSubscriber::OnFrameDelivered( - scoped_refptr frame, base::TimeTicks, bool result) { - if (!result) +void FrameSubscriber::OnFrameDelivered(const FrameCaptureCallback& callback, + const SkBitmap& bitmap, content::ReadbackResponse response) { + if (bitmap.computeSize64() == 0) return; v8::Locker locker(isolate_); v8::HandleScope handle_scope(isolate_); - gfx::Rect rect = frame->visible_rect(); - size_t rgb_arr_size = rect.width() * rect.height() * 4; + size_t rgb_arr_size = bitmap.width() * bitmap.height() * + bitmap.bytesPerPixel(); v8::MaybeLocal buffer = node::Buffer::New(isolate_, rgb_arr_size); if (buffer.IsEmpty()) return; - // Convert a frame of YUV to 32 bit ARGB. - media::ConvertYUVToRGB32(frame->data(media::VideoFrame::kYPlane), - frame->data(media::VideoFrame::kUPlane), - frame->data(media::VideoFrame::kVPlane), - reinterpret_cast( - node::Buffer::Data(buffer.ToLocalChecked())), - rect.width(), rect.height(), - frame->stride(media::VideoFrame::kYPlane), - frame->stride(media::VideoFrame::kUVPlane), - rect.width() * 4, - media::YV12); + bitmap.copyPixelsTo( + reinterpret_cast(node::Buffer::Data(buffer.ToLocalChecked())), + rgb_arr_size); callback_.Run(buffer.ToLocalChecked()); } diff --git a/atom/browser/api/frame_subscriber.h b/atom/browser/api/frame_subscriber.h index 089c4922d797..a803d75dff20 100644 --- a/atom/browser/api/frame_subscriber.h +++ b/atom/browser/api/frame_subscriber.h @@ -7,7 +7,10 @@ #include "base/callback.h" #include "base/memory/weak_ptr.h" +#include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/render_widget_host_view_frame_subscriber.h" +#include "content/public/browser/readback_types.h" +#include "third_party/skia/include/core/SkBitmap.h" #include "ui/gfx/geometry/size.h" #include "v8/include/v8.h" @@ -20,7 +23,7 @@ class FrameSubscriber : public content::RenderWidgetHostViewFrameSubscriber { using FrameCaptureCallback = base::Callback)>; FrameSubscriber(v8::Isolate* isolate, - const gfx::Size& size, + content::RenderWidgetHostView* view, const FrameCaptureCallback& callback); bool ShouldCaptureFrame(const gfx::Rect& damage_rect, @@ -29,11 +32,11 @@ class FrameSubscriber : public content::RenderWidgetHostViewFrameSubscriber { DeliverFrameCallback* callback) override; private: - void OnFrameDelivered( - scoped_refptr frame, base::TimeTicks, bool); + void OnFrameDelivered(const FrameCaptureCallback& callback, + const SkBitmap& bitmap, content::ReadbackResponse response); v8::Isolate* isolate_; - gfx::Size size_; + content::RenderWidgetHostView* view_; FrameCaptureCallback callback_; base::WeakPtrFactory weak_factory_; diff --git a/atom/browser/api/lib/app.js b/atom/browser/api/lib/app.js deleted file mode 100644 index f7c9cd975837..000000000000 --- a/atom/browser/api/lib/app.js +++ /dev/null @@ -1,123 +0,0 @@ -const deprecate = require('electron').deprecate; -const session = require('electron').session; -const Menu = require('electron').Menu; -const EventEmitter = require('events').EventEmitter; - -const bindings = process.atomBinding('app'); -const downloadItemBindings = process.atomBinding('download_item'); -const app = bindings.app; - -var slice = [].slice; - -app.__proto__ = EventEmitter.prototype; - -app.setApplicationMenu = function(menu) { - return Menu.setApplicationMenu(menu); -}; - -app.getApplicationMenu = function() { - return Menu.getApplicationMenu(); -}; - -app.commandLine = { - appendSwitch: bindings.appendSwitch, - appendArgument: bindings.appendArgument -}; - -if (process.platform === 'darwin') { - app.dock = { - bounce: function(type) { - if (type == null) { - type = 'informational'; - } - return bindings.dockBounce(type); - }, - cancelBounce: bindings.dockCancelBounce, - setBadge: bindings.dockSetBadgeText, - getBadge: bindings.dockGetBadgeText, - hide: bindings.dockHide, - show: bindings.dockShow, - setMenu: bindings.dockSetMenu, - setIcon: bindings.dockSetIcon - }; -} - -var appPath = null; - -app.setAppPath = function(path) { - return appPath = path; -}; - -app.getAppPath = function() { - return appPath; -}; - -// Routes the events to webContents. -var ref1 = ['login', 'certificate-error', 'select-client-certificate']; -var fn = function(name) { - return app.on(name, function() { - var args, event, webContents; - event = arguments[0], webContents = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; - return webContents.emit.apply(webContents, [name, event].concat(slice.call(args))); - }); -}; -var i, len; -for (i = 0, len = ref1.length; i < len; i++) { - fn(ref1[i]); -} - -// Deprecated. - -app.getHomeDir = deprecate('app.getHomeDir', 'app.getPath', function() { - return this.getPath('home'); -}); - -app.getDataPath = deprecate('app.getDataPath', 'app.getPath', function() { - return this.getPath('userData'); -}); - -app.setDataPath = deprecate('app.setDataPath', 'app.setPath', function(path) { - return this.setPath('userData', path); -}); - -app.resolveProxy = deprecate('app.resolveProxy', 'session.defaultSession.resolveProxy', function(url, callback) { - return session.defaultSession.resolveProxy(url, callback); -}); - -deprecate.rename(app, 'terminate', 'quit'); - -deprecate.event(app, 'finish-launching', 'ready', function() { - - // give default app a chance to setup default menu. - return setImmediate((function(_this) { - return function() { - return _this.emit('finish-launching'); - }; - })(this)); -}); - -deprecate.event(app, 'activate-with-no-open-windows', 'activate', function(event, hasVisibleWindows) { - if (!hasVisibleWindows) { - return this.emit('activate-with-no-open-windows', event); - } -}); - -deprecate.event(app, 'select-certificate', 'select-client-certificate'); - -// Wrappers for native classes. -var wrapDownloadItem = function(downloadItem) { - - // downloadItem is an EventEmitter. - downloadItem.__proto__ = EventEmitter.prototype; - - // Deprecated. - deprecate.property(downloadItem, 'url', 'getURL'); - deprecate.property(downloadItem, 'filename', 'getFilename'); - deprecate.property(downloadItem, 'mimeType', 'getMimeType'); - return deprecate.rename(downloadItem, 'getUrl', 'getURL'); -}; - -downloadItemBindings._setWrapDownloadItem(wrapDownloadItem); - -// Only one App object pemitted. -module.exports = app; diff --git a/atom/browser/api/lib/auto-updater.js b/atom/browser/api/lib/auto-updater.js deleted file mode 100644 index 9cc1fada0675..000000000000 --- a/atom/browser/api/lib/auto-updater.js +++ /dev/null @@ -1,7 +0,0 @@ -const deprecate = require('electron').deprecate; -const autoUpdater = process.platform === 'win32' ? require('./auto-updater/auto-updater-win') : require('./auto-updater/auto-updater-native'); - -// Deprecated. -deprecate.rename(autoUpdater, 'setFeedUrl', 'setFeedURL'); - -module.exports = autoUpdater; diff --git a/atom/browser/api/lib/auto-updater/auto-updater-native.js b/atom/browser/api/lib/auto-updater/auto-updater-native.js deleted file mode 100644 index 20c69cdb5a2b..000000000000 --- a/atom/browser/api/lib/auto-updater/auto-updater-native.js +++ /dev/null @@ -1,6 +0,0 @@ -const EventEmitter = require('events').EventEmitter; -const autoUpdater = process.atomBinding('auto_updater').autoUpdater; - -autoUpdater.__proto__ = EventEmitter.prototype; - -module.exports = autoUpdater; diff --git a/atom/browser/api/lib/auto-updater/auto-updater-win.js b/atom/browser/api/lib/auto-updater/auto-updater-win.js deleted file mode 100644 index 1270f8f2bdb4..000000000000 --- a/atom/browser/api/lib/auto-updater/auto-updater-win.js +++ /dev/null @@ -1,63 +0,0 @@ -'use strict'; - -const app = require('electron').app; -const EventEmitter = require('events').EventEmitter; -const squirrelUpdate = require('./squirrel-update-win'); -const util = require('util'); - -function AutoUpdater() { - EventEmitter.call(this); -} - -util.inherits(AutoUpdater, EventEmitter); - -AutoUpdater.prototype.quitAndInstall = function() { - squirrelUpdate.processStart(); - return app.quit(); -}; - -AutoUpdater.prototype.setFeedURL = function(updateURL) { - return this.updateURL = updateURL; -}; - -AutoUpdater.prototype.checkForUpdates = function() { - if (!this.updateURL) { - return this.emitError('Update URL is not set'); - } - if (!squirrelUpdate.supported()) { - return this.emitError('Can not find Squirrel'); - } - this.emit('checking-for-update'); - return squirrelUpdate.download(this.updateURL, (function(_this) { - return function(error, update) { - if (error != null) { - return _this.emitError(error); - } - if (update == null) { - return _this.emit('update-not-available'); - } - _this.emit('update-available'); - return squirrelUpdate.update(_this.updateURL, function(error) { - var date, releaseNotes, version; - if (error != null) { - return _this.emitError(error); - } - releaseNotes = update.releaseNotes, version = update.version; - - // Following information is not available on Windows, so fake them. - date = new Date; - return _this.emit('update-downloaded', {}, releaseNotes, version, date, _this.updateURL, function() { - return _this.quitAndInstall(); - }); - }); - }; - })(this)); -}; - -// Private: Emit both error object and message, this is to keep compatibility -// with Old APIs. -AutoUpdater.prototype.emitError = function(message) { - return this.emit('error', new Error(message), message); -}; - -module.exports = new AutoUpdater; diff --git a/atom/browser/api/lib/auto-updater/squirrel-update-win.js b/atom/browser/api/lib/auto-updater/squirrel-update-win.js deleted file mode 100644 index fbab354b2643..000000000000 --- a/atom/browser/api/lib/auto-updater/squirrel-update-win.js +++ /dev/null @@ -1,98 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const spawn = require('child_process').spawn; - -// i.e. my-app/app-0.1.13/ -const appFolder = path.dirname(process.execPath); - -// i.e. my-app/Update.exe -const updateExe = path.resolve(appFolder, '..', 'Update.exe'); - -const exeName = path.basename(process.execPath); - -// Spawn a command and invoke the callback when it completes with an error -// and the output from standard out. -var spawnUpdate = function(args, detached, callback) { - var error, errorEmitted, spawnedProcess, stderr, stdout; - try { - spawnedProcess = spawn(updateExe, args, { - detached: detached - }); - } catch (error1) { - error = error1; - - // Shouldn't happen, but still guard it. - process.nextTick(function() { - return callback(error); - }); - return; - } - stdout = ''; - stderr = ''; - spawnedProcess.stdout.on('data', function(data) { - return stdout += data; - }); - spawnedProcess.stderr.on('data', function(data) { - return stderr += data; - }); - errorEmitted = false; - spawnedProcess.on('error', function(error) { - errorEmitted = true; - return callback(error); - }); - return spawnedProcess.on('exit', function(code, signal) { - - // We may have already emitted an error. - if (errorEmitted) { - return; - } - - // Process terminated with error. - if (code !== 0) { - return callback("Command failed: " + (signal != null ? signal : code) + "\n" + stderr); - } - - // Success. - return callback(null, stdout); - }); -}; - -// Start an instance of the installed app. -exports.processStart = function() { - return spawnUpdate(['--processStart', exeName], true, function() {}); -}; - -// Download the releases specified by the URL and write new results to stdout. -exports.download = function(updateURL, callback) { - return spawnUpdate(['--download', updateURL], false, function(error, stdout) { - var json, ref, ref1, update; - if (error != null) { - return callback(error); - } - try { - // Last line of output is the JSON details about the releases - json = stdout.trim().split('\n').pop(); - update = (ref = JSON.parse(json)) != null ? (ref1 = ref.releasesToApply) != null ? typeof ref1.pop === "function" ? ref1.pop() : void 0 : void 0 : void 0; - } catch (jsonError) { - return callback("Invalid result:\n" + stdout); - } - return callback(null, update); - }); -}; - - -// Update the application to the latest remote version specified by URL. -exports.update = function(updateURL, callback) { - return spawnUpdate(['--update', updateURL], false, callback); -}; - - -// Is the Update.exe installed with the current application? -exports.supported = function() { - try { - fs.accessSync(updateExe, fs.R_OK); - return true; - } catch (error) { - return false; - } -}; diff --git a/atom/browser/api/lib/browser-window.js b/atom/browser/api/lib/browser-window.js deleted file mode 100644 index a9649f853d75..000000000000 --- a/atom/browser/api/lib/browser-window.js +++ /dev/null @@ -1,243 +0,0 @@ -const ipcMain = require('electron').ipcMain; -const deprecate = require('electron').deprecate; -const EventEmitter = require('events').EventEmitter; -const BrowserWindow = process.atomBinding('window').BrowserWindow; - -BrowserWindow.prototype.__proto__ = EventEmitter.prototype; - -BrowserWindow.prototype._init = function() { - - // avoid recursive require. - var app, menu; - app = require('electron').app; - - // Simulate the application menu on platforms other than OS X. - if (process.platform !== 'darwin') { - menu = app.getApplicationMenu(); - if (menu != null) { - this.setMenu(menu); - } - } - - // Make new windows requested by links behave like "window.open" - this.webContents.on('-new-window', function(event, url, frameName) { - var options; - options = { - show: true, - width: 800, - height: 600 - }; - return ipcMain.emit('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', event, url, frameName, options); - }); - - // window.resizeTo(...) - // window.moveTo(...) - this.webContents.on('move', (function(_this) { - return function(event, size) { - return _this.setBounds(size); - }; - })(this)); - - // Hide the auto-hide menu when webContents is focused. - this.webContents.on('activate', (function(_this) { - return function() { - if (process.platform !== 'darwin' && _this.isMenuBarAutoHide() && _this.isMenuBarVisible()) { - return _this.setMenuBarVisibility(false); - } - }; - })(this)); - - // Forward the crashed event. - this.webContents.on('crashed', (function(_this) { - return function() { - return _this.emit('crashed'); - }; - })(this)); - - // Change window title to page title. - this.webContents.on('page-title-updated', (event, title) => { - // The page-title-updated event is not emitted immediately (see #3645), so - // when the callback is called the BrowserWindow might have been closed. - if (this.isDestroyed()) - return; - // Route the event to BrowserWindow. - this.emit('page-title-updated', event, title); - if (!event.defaultPrevented) - this.setTitle(title); - }); - - // Sometimes the webContents doesn't get focus when window is shown, so we have - // to force focusing on webContents in this case. The safest way is to focus it - // when we first start to load URL, if we do it earlier it won't have effect, - // if we do it later we might move focus in the page. - // Though this hack is only needed on OS X when the app is launched from - // Finder, we still do it on all platforms in case of other bugs we don't know. - this.webContents.once('load-url', function() { - return this.focus(); - }); - - // Redirect focus/blur event to app instance too. - this.on('blur', (function(_this) { - return function(event) { - return app.emit('browser-window-blur', event, _this); - }; - })(this)); - this.on('focus', (function(_this) { - return function(event) { - return app.emit('browser-window-focus', event, _this); - }; - })(this)); - - // Notify the creation of the window. - app.emit('browser-window-created', {}, this); - - // Be compatible with old APIs. - this.webContents.on('devtools-focused', (function(_this) { - return function() { - return _this.emit('devtools-focused'); - }; - })(this)); - this.webContents.on('devtools-opened', (function(_this) { - return function() { - return _this.emit('devtools-opened'); - }; - })(this)); - this.webContents.on('devtools-closed', (function(_this) { - return function() { - return _this.emit('devtools-closed'); - }; - })(this)); - return Object.defineProperty(this, 'devToolsWebContents', { - enumerable: true, - configurable: false, - get: function() { - return this.webContents.devToolsWebContents; - } - }); -}; - -BrowserWindow.getFocusedWindow = function() { - var i, len, window, windows; - windows = BrowserWindow.getAllWindows(); - for (i = 0, len = windows.length; i < len; i++) { - window = windows[i]; - if (window.isFocused()) { - return window; - } - } - return null; -}; - -BrowserWindow.fromWebContents = function(webContents) { - var i, len, ref1, window, windows; - windows = BrowserWindow.getAllWindows(); - for (i = 0, len = windows.length; i < len; i++) { - window = windows[i]; - if ((ref1 = window.webContents) != null ? ref1.equal(webContents) : void 0) { - return window; - } - } -}; - -BrowserWindow.fromDevToolsWebContents = function(webContents) { - var i, len, ref1, window, windows; - windows = BrowserWindow.getAllWindows(); - for (i = 0, len = windows.length; i < len; i++) { - window = windows[i]; - if ((ref1 = window.devToolsWebContents) != null ? ref1.equal(webContents) : void 0) { - return window; - } - } -}; - -// Helpers. - -BrowserWindow.prototype.loadURL = function() { - return this.webContents.loadURL.apply(this.webContents, arguments); -}; - -BrowserWindow.prototype.getURL = function() { - return this.webContents.getURL(); -}; - -BrowserWindow.prototype.reload = function() { - return this.webContents.reload.apply(this.webContents, arguments); -}; - -BrowserWindow.prototype.send = function() { - return this.webContents.send.apply(this.webContents, arguments); -}; - -BrowserWindow.prototype.openDevTools = function() { - return this.webContents.openDevTools.apply(this.webContents, arguments); -}; - -BrowserWindow.prototype.closeDevTools = function() { - return this.webContents.closeDevTools(); -}; - -BrowserWindow.prototype.isDevToolsOpened = function() { - return this.webContents.isDevToolsOpened(); -}; - -BrowserWindow.prototype.isDevToolsFocused = function() { - return this.webContents.isDevToolsFocused(); -}; - -BrowserWindow.prototype.toggleDevTools = function() { - return this.webContents.toggleDevTools(); -}; - -BrowserWindow.prototype.inspectElement = function() { - return this.webContents.inspectElement.apply(this.webContents, arguments); -}; - -BrowserWindow.prototype.inspectServiceWorker = function() { - return this.webContents.inspectServiceWorker(); -}; - -// Deprecated. - -deprecate.member(BrowserWindow, 'undo', 'webContents'); - -deprecate.member(BrowserWindow, 'redo', 'webContents'); - -deprecate.member(BrowserWindow, 'cut', 'webContents'); - -deprecate.member(BrowserWindow, 'copy', 'webContents'); - -deprecate.member(BrowserWindow, 'paste', 'webContents'); - -deprecate.member(BrowserWindow, 'selectAll', 'webContents'); - -deprecate.member(BrowserWindow, 'reloadIgnoringCache', 'webContents'); - -deprecate.member(BrowserWindow, 'isLoading', 'webContents'); - -deprecate.member(BrowserWindow, 'isWaitingForResponse', 'webContents'); - -deprecate.member(BrowserWindow, 'stop', 'webContents'); - -deprecate.member(BrowserWindow, 'isCrashed', 'webContents'); - -deprecate.member(BrowserWindow, 'print', 'webContents'); - -deprecate.member(BrowserWindow, 'printToPDF', 'webContents'); - -deprecate.rename(BrowserWindow, 'restart', 'reload'); - -deprecate.rename(BrowserWindow, 'loadUrl', 'loadURL'); - -deprecate.rename(BrowserWindow, 'getUrl', 'getURL'); - -BrowserWindow.prototype.executeJavaScriptInDevTools = deprecate('executeJavaScriptInDevTools', 'devToolsWebContents.executeJavaScript', function(code) { - var ref1; - return (ref1 = this.devToolsWebContents) != null ? ref1.executeJavaScript(code) : void 0; -}); - -BrowserWindow.prototype.getPageTitle = deprecate('getPageTitle', 'webContents.getTitle', function() { - var ref1; - return (ref1 = this.webContents) != null ? ref1.getTitle() : void 0; -}); - -module.exports = BrowserWindow; diff --git a/atom/browser/api/lib/content-tracing.js b/atom/browser/api/lib/content-tracing.js deleted file mode 100644 index b00c5666df59..000000000000 --- a/atom/browser/api/lib/content-tracing.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = process.atomBinding('content_tracing'); diff --git a/atom/browser/api/lib/dialog.js b/atom/browser/api/lib/dialog.js deleted file mode 100644 index 102aaa3897df..000000000000 --- a/atom/browser/api/lib/dialog.js +++ /dev/null @@ -1,170 +0,0 @@ -const app = require('electron').app; -const BrowserWindow = require('electron').BrowserWindow; -const binding = process.atomBinding('dialog'); -const v8Util = process.atomBinding('v8_util'); - -var slice = [].slice; -var includes = [].includes; - -var fileDialogProperties = { - openFile: 1 << 0, - openDirectory: 1 << 1, - multiSelections: 1 << 2, - createDirectory: 1 << 3 -}; - -var messageBoxTypes = ['none', 'info', 'warning', 'error', 'question']; - -var messageBoxOptions = { - noLink: 1 << 0 -}; - -var parseArgs = function(window, options, callback) { - if (!(window === null || (window != null ? window.constructor : void 0) === BrowserWindow)) { - // Shift. - callback = options; - options = window; - window = null; - } - if ((callback == null) && typeof options === 'function') { - // Shift. - callback = options; - options = null; - } - return [window, options, callback]; -}; - -var checkAppInitialized = function() { - if (!app.isReady()) { - throw new Error('dialog module can only be used after app is ready'); - } -}; - -module.exports = { - showOpenDialog: function() { - var args, callback, options, prop, properties, ref1, value, window, wrappedCallback; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - checkAppInitialized(); - ref1 = parseArgs.apply(null, args), window = ref1[0], options = ref1[1], callback = ref1[2]; - if (options == null) { - options = { - title: 'Open', - properties: ['openFile'] - }; - } - if (options.properties == null) { - options.properties = ['openFile']; - } - if (!Array.isArray(options.properties)) { - throw new TypeError('Properties need to be array'); - } - properties = 0; - for (prop in fileDialogProperties) { - value = fileDialogProperties[prop]; - if (includes.call(options.properties, prop)) { - properties |= value; - } - } - if (options.title == null) { - options.title = ''; - } - if (options.defaultPath == null) { - options.defaultPath = ''; - } - if (options.filters == null) { - options.filters = []; - } - wrappedCallback = typeof callback === 'function' ? function(success, result) { - return callback(success ? result : void 0); - } : null; - return binding.showOpenDialog(String(options.title), String(options.defaultPath), options.filters, properties, window, wrappedCallback); - }, - showSaveDialog: function() { - var args, callback, options, ref1, window, wrappedCallback; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - checkAppInitialized(); - ref1 = parseArgs.apply(null, args), window = ref1[0], options = ref1[1], callback = ref1[2]; - if (options == null) { - options = { - title: 'Save' - }; - } - if (options.title == null) { - options.title = ''; - } - if (options.defaultPath == null) { - options.defaultPath = ''; - } - if (options.filters == null) { - options.filters = []; - } - wrappedCallback = typeof callback === 'function' ? function(success, result) { - return callback(success ? result : void 0); - } : null; - return binding.showSaveDialog(String(options.title), String(options.defaultPath), options.filters, window, wrappedCallback); - }, - showMessageBox: function() { - var args, callback, flags, i, j, len, messageBoxType, options, ref1, ref2, ref3, text, window; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - checkAppInitialized(); - ref1 = parseArgs.apply(null, args), window = ref1[0], options = ref1[1], callback = ref1[2]; - if (options == null) { - options = { - type: 'none' - }; - } - if (options.type == null) { - options.type = 'none'; - } - messageBoxType = messageBoxTypes.indexOf(options.type); - if (!(messageBoxType > -1)) { - throw new TypeError('Invalid message box type'); - } - if (!Array.isArray(options.buttons)) { - throw new TypeError('Buttons need to be array'); - } - if (options.title == null) { - options.title = ''; - } - if (options.message == null) { - options.message = ''; - } - if (options.detail == null) { - options.detail = ''; - } - if (options.icon == null) { - options.icon = null; - } - if (options.defaultId == null) { - options.defaultId = -1; - } - - // Choose a default button to get selected when dialog is cancelled. - if (options.cancelId == null) { - options.cancelId = 0; - ref2 = options.buttons; - for (i = j = 0, len = ref2.length; j < len; i = ++j) { - text = ref2[i]; - if ((ref3 = text.toLowerCase()) === 'cancel' || ref3 === 'no') { - options.cancelId = i; - break; - } - } - } - flags = options.noLink ? messageBoxOptions.noLink : 0; - return binding.showMessageBox(messageBoxType, options.buttons, options.defaultId, options.cancelId, flags, options.title, options.message, options.detail, options.icon, window, callback); - }, - showErrorBox: function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return binding.showErrorBox.apply(binding, args); - } -}; - -// Mark standard asynchronous functions. -var ref1 = ['showMessageBox', 'showOpenDialog', 'showSaveDialog']; -var j, len, api; -for (j = 0, len = ref1.length; j < len; j++) { - api = ref1[j]; - v8Util.setHiddenValue(module.exports[api], 'asynchronous', true); -} diff --git a/atom/browser/api/lib/exports/electron.js b/atom/browser/api/lib/exports/electron.js deleted file mode 100644 index 7f97fcdbc4cf..000000000000 --- a/atom/browser/api/lib/exports/electron.js +++ /dev/null @@ -1,112 +0,0 @@ -const common = require('../../../../common/api/lib/exports/electron'); - - -// Import common modules. -common.defineProperties(exports); - -Object.defineProperties(exports, { - - // Browser side modules, please sort with alphabet order. - app: { - enumerable: true, - get: function() { - return require('../app'); - } - }, - autoUpdater: { - enumerable: true, - get: function() { - return require('../auto-updater'); - } - }, - BrowserWindow: { - enumerable: true, - get: function() { - return require('../browser-window'); - } - }, - contentTracing: { - enumerable: true, - get: function() { - return require('../content-tracing'); - } - }, - dialog: { - enumerable: true, - get: function() { - return require('../dialog'); - } - }, - ipcMain: { - enumerable: true, - get: function() { - return require('../ipc-main'); - } - }, - globalShortcut: { - enumerable: true, - get: function() { - return require('../global-shortcut'); - } - }, - Menu: { - enumerable: true, - get: function() { - return require('../menu'); - } - }, - MenuItem: { - enumerable: true, - get: function() { - return require('../menu-item'); - } - }, - powerMonitor: { - enumerable: true, - get: function() { - return require('../power-monitor'); - } - }, - powerSaveBlocker: { - enumerable: true, - get: function() { - return require('../power-save-blocker'); - } - }, - protocol: { - enumerable: true, - get: function() { - return require('../protocol'); - } - }, - screen: { - enumerable: true, - get: function() { - return require('../screen'); - } - }, - session: { - enumerable: true, - get: function() { - return require('../session'); - } - }, - Tray: { - enumerable: true, - get: function() { - return require('../tray'); - } - }, - - // The internal modules, invisible unless you know their names. - NavigationController: { - get: function() { - return require('../navigation-controller'); - } - }, - webContents: { - get: function() { - return require('../web-contents'); - } - } -}); diff --git a/atom/browser/api/lib/global-shortcut.js b/atom/browser/api/lib/global-shortcut.js deleted file mode 100644 index daca6c232796..000000000000 --- a/atom/browser/api/lib/global-shortcut.js +++ /dev/null @@ -1,5 +0,0 @@ -var globalShortcut; - -globalShortcut = process.atomBinding('global_shortcut').globalShortcut; - -module.exports = globalShortcut; diff --git a/atom/browser/api/lib/ipc-main.js b/atom/browser/api/lib/ipc-main.js deleted file mode 100644 index e253e03eaabe..000000000000 --- a/atom/browser/api/lib/ipc-main.js +++ /dev/null @@ -1,3 +0,0 @@ -const EventEmitter = require('events').EventEmitter; - -module.exports = new EventEmitter; diff --git a/atom/browser/api/lib/ipc.js b/atom/browser/api/lib/ipc.js deleted file mode 100644 index 6e9715154234..000000000000 --- a/atom/browser/api/lib/ipc.js +++ /dev/null @@ -1,7 +0,0 @@ -const deprecate = require('electron').deprecate; -const ipcMain = require('electron').ipcMain; - -// This module is deprecated, we mirror everything from ipcMain. -deprecate.warn('ipc module', 'require("electron").ipcMain'); - -module.exports = ipcMain; diff --git a/atom/browser/api/lib/menu-item.js b/atom/browser/api/lib/menu-item.js deleted file mode 100644 index d58a0fad2c70..000000000000 --- a/atom/browser/api/lib/menu-item.js +++ /dev/null @@ -1,102 +0,0 @@ -var MenuItem, methodInBrowserWindow, nextCommandId, rolesMap; - -nextCommandId = 0; - -// Maps role to methods of webContents -rolesMap = { - undo: 'undo', - redo: 'redo', - cut: 'cut', - copy: 'copy', - paste: 'paste', - selectall: 'selectAll', - minimize: 'minimize', - close: 'close' -}; - -// Maps methods that should be called directly on the BrowserWindow instance -methodInBrowserWindow = { - minimize: true, - close: true -}; - -MenuItem = (function() { - MenuItem.types = ['normal', 'separator', 'submenu', 'checkbox', 'radio']; - - function MenuItem(options) { - var click, ref; - const Menu = require('electron').Menu; - click = options.click, this.selector = options.selector, this.type = options.type, this.role = options.role, this.label = options.label, this.sublabel = options.sublabel, this.accelerator = options.accelerator, this.icon = options.icon, this.enabled = options.enabled, this.visible = options.visible, this.checked = options.checked, this.submenu = options.submenu; - if ((this.submenu != null) && this.submenu.constructor !== Menu) { - this.submenu = Menu.buildFromTemplate(this.submenu); - } - if ((this.type == null) && (this.submenu != null)) { - this.type = 'submenu'; - } - if (this.type === 'submenu' && ((ref = this.submenu) != null ? ref.constructor : void 0) !== Menu) { - throw new Error('Invalid submenu'); - } - this.overrideReadOnlyProperty('type', 'normal'); - this.overrideReadOnlyProperty('role'); - this.overrideReadOnlyProperty('accelerator'); - this.overrideReadOnlyProperty('icon'); - this.overrideReadOnlyProperty('submenu'); - this.overrideProperty('label', ''); - this.overrideProperty('sublabel', ''); - this.overrideProperty('enabled', true); - this.overrideProperty('visible', true); - this.overrideProperty('checked', false); - if (MenuItem.types.indexOf(this.type) === -1) { - throw new Error("Unknown menu type " + this.type); - } - this.commandId = ++nextCommandId; - this.click = (function(_this) { - return function(focusedWindow) { - - // Manually flip the checked flags when clicked. - var methodName, ref1, ref2; - if ((ref1 = _this.type) === 'checkbox' || ref1 === 'radio') { - _this.checked = !_this.checked; - } - if (_this.role && rolesMap[_this.role] && process.platform !== 'darwin' && (focusedWindow != null)) { - methodName = rolesMap[_this.role]; - if (methodInBrowserWindow[methodName]) { - return focusedWindow[methodName](); - } else { - return (ref2 = focusedWindow.webContents) != null ? ref2[methodName]() : void 0; - } - } else if (typeof click === 'function') { - return click(_this, focusedWindow); - } else if (typeof _this.selector === 'string') { - return Menu.sendActionToFirstResponder(_this.selector); - } - }; - })(this); - } - - MenuItem.prototype.overrideProperty = function(name, defaultValue) { - if (defaultValue == null) { - defaultValue = null; - } - return this[name] != null ? this[name] : this[name] = defaultValue; - }; - - MenuItem.prototype.overrideReadOnlyProperty = function(name, defaultValue) { - if (defaultValue == null) { - defaultValue = null; - } - if (this[name] == null) { - this[name] = defaultValue; - } - return Object.defineProperty(this, name, { - enumerable: true, - writable: false, - value: this[name] - }); - }; - - return MenuItem; - -})(); - -module.exports = MenuItem; diff --git a/atom/browser/api/lib/menu.js b/atom/browser/api/lib/menu.js deleted file mode 100644 index 62c771af6c6d..000000000000 --- a/atom/browser/api/lib/menu.js +++ /dev/null @@ -1,335 +0,0 @@ -const BrowserWindow = require('electron').BrowserWindow; -const MenuItem = require('electron').MenuItem; -const EventEmitter = require('events').EventEmitter; -const v8Util = process.atomBinding('v8_util'); -const bindings = process.atomBinding('menu'); - -// Automatically generated radio menu item's group id. -var nextGroupId = 0; - -// Search between seperators to find a radio menu item and return its group id, -// otherwise generate a group id. -var generateGroupId = function(items, pos) { - var i, item, j, k, ref1, ref2, ref3; - if (pos > 0) { - for (i = j = ref1 = pos - 1; ref1 <= 0 ? j <= 0 : j >= 0; i = ref1 <= 0 ? ++j : --j) { - item = items[i]; - if (item.type === 'radio') { - return item.groupId; - } - if (item.type === 'separator') { - break; - } - } - } else if (pos < items.length) { - for (i = k = ref2 = pos, ref3 = items.length - 1; ref2 <= ref3 ? k <= ref3 : k >= ref3; i = ref2 <= ref3 ? ++k : --k) { - item = items[i]; - if (item.type === 'radio') { - return item.groupId; - } - if (item.type === 'separator') { - break; - } - } - } - return ++nextGroupId; -}; - -// Returns the index of item according to |id|. -var indexOfItemById = function(items, id) { - var i, item, j, len; - for (i = j = 0, len = items.length; j < len; i = ++j) { - item = items[i]; - if (item.id === id) { - return i; - } - } - return -1; -}; - -// Returns the index of where to insert the item according to |position|. -var indexToInsertByPosition = function(items, position) { - var id, insertIndex, query, ref1; - if (!position) { - return items.length; - } - ref1 = position.split('='), query = ref1[0], id = ref1[1]; - insertIndex = indexOfItemById(items, id); - if (insertIndex === -1 && query !== 'endof') { - console.warn("Item with id '" + id + "' is not found"); - return items.length; - } - switch (query) { - case 'after': - insertIndex++; - break; - case 'endof': - - // If the |id| doesn't exist, then create a new group with the |id|. - if (insertIndex === -1) { - items.push({ - id: id, - type: 'separator' - }); - insertIndex = items.length - 1; - } - - // Find the end of the group. - insertIndex++; - while (insertIndex < items.length && items[insertIndex].type !== 'separator') { - insertIndex++; - } - } - return insertIndex; -}; - -const Menu = bindings.Menu; - -Menu.prototype.__proto__ = EventEmitter.prototype; - -Menu.prototype._init = function() { - this.commandsMap = {}; - this.groupsMap = {}; - this.items = []; - return this.delegate = { - isCommandIdChecked: (function(_this) { - return function(commandId) { - var ref1; - return (ref1 = _this.commandsMap[commandId]) != null ? ref1.checked : void 0; - }; - })(this), - isCommandIdEnabled: (function(_this) { - return function(commandId) { - var ref1; - return (ref1 = _this.commandsMap[commandId]) != null ? ref1.enabled : void 0; - }; - })(this), - isCommandIdVisible: (function(_this) { - return function(commandId) { - var ref1; - return (ref1 = _this.commandsMap[commandId]) != null ? ref1.visible : void 0; - }; - })(this), - getAcceleratorForCommandId: (function(_this) { - return function(commandId) { - var ref1; - return (ref1 = _this.commandsMap[commandId]) != null ? ref1.accelerator : void 0; - }; - })(this), - getIconForCommandId: (function(_this) { - return function(commandId) { - var ref1; - return (ref1 = _this.commandsMap[commandId]) != null ? ref1.icon : void 0; - }; - })(this), - executeCommand: (function(_this) { - return function(commandId) { - var ref1; - return (ref1 = _this.commandsMap[commandId]) != null ? ref1.click(BrowserWindow.getFocusedWindow()) : void 0; - }; - })(this), - menuWillShow: (function(_this) { - return function() { - - // Make sure radio groups have at least one menu item seleted. - var checked, group, id, j, len, radioItem, ref1, results; - ref1 = _this.groupsMap; - results = []; - for (id in ref1) { - group = ref1[id]; - checked = false; - for (j = 0, len = group.length; j < len; j++) { - radioItem = group[j]; - if (!radioItem.checked) { - continue; - } - checked = true; - break; - } - if (!checked) { - results.push(v8Util.setHiddenValue(group[0], 'checked', true)); - } else { - results.push(void 0); - } - } - return results; - }; - })(this) - }; -}; - -Menu.prototype.popup = function(window, x, y, positioningItem) { - if (typeof window != 'object' || window.constructor !== BrowserWindow) { - // Shift. - positioningItem = y; - y = x; - x = window; - window = BrowserWindow.getFocusedWindow(); - } - - // Default parameters. - if (typeof x !== 'number') x = -1; - if (typeof y !== 'number') y = -1; - if (typeof positioningItem !== 'number') positioningItem = 0; - - this.popupAt(window, x, y, positioningItem); -}; - -Menu.prototype.append = function(item) { - return this.insert(this.getItemCount(), item); -}; - -Menu.prototype.insert = function(pos, item) { - var base, name; - if ((item != null ? item.constructor : void 0) !== MenuItem) { - throw new TypeError('Invalid item'); - } - switch (item.type) { - case 'normal': - this.insertItem(pos, item.commandId, item.label); - break; - case 'checkbox': - this.insertCheckItem(pos, item.commandId, item.label); - break; - case 'separator': - this.insertSeparator(pos); - break; - case 'submenu': - this.insertSubMenu(pos, item.commandId, item.label, item.submenu); - break; - case 'radio': - // Grouping radio menu items. - item.overrideReadOnlyProperty('groupId', generateGroupId(this.items, pos)); - if ((base = this.groupsMap)[name = item.groupId] == null) { - base[name] = []; - } - this.groupsMap[item.groupId].push(item); - - // Setting a radio menu item should flip other items in the group. - v8Util.setHiddenValue(item, 'checked', item.checked); - Object.defineProperty(item, 'checked', { - enumerable: true, - get: function() { - return v8Util.getHiddenValue(item, 'checked'); - }, - set: (function(_this) { - return function() { - var j, len, otherItem, ref1; - ref1 = _this.groupsMap[item.groupId]; - for (j = 0, len = ref1.length; j < len; j++) { - otherItem = ref1[j]; - if (otherItem !== item) { - v8Util.setHiddenValue(otherItem, 'checked', false); - } - } - return v8Util.setHiddenValue(item, 'checked', true); - }; - })(this) - }); - this.insertRadioItem(pos, item.commandId, item.label, item.groupId); - } - if (item.sublabel != null) { - this.setSublabel(pos, item.sublabel); - } - if (item.icon != null) { - this.setIcon(pos, item.icon); - } - if (item.role != null) { - this.setRole(pos, item.role); - } - - // Make menu accessable to items. - item.overrideReadOnlyProperty('menu', this); - - // Remember the items. - this.items.splice(pos, 0, item); - return this.commandsMap[item.commandId] = item; -}; - - -// Force menuWillShow to be called -Menu.prototype._callMenuWillShow = function() { - var item, j, len, ref1, ref2, results; - if ((ref1 = this.delegate) != null) { - ref1.menuWillShow(); - } - ref2 = this.items; - results = []; - for (j = 0, len = ref2.length; j < len; j++) { - item = ref2[j]; - if (item.submenu != null) { - results.push(item.submenu._callMenuWillShow()); - } - } - return results; -}; - -var applicationMenu = null; - -Menu.setApplicationMenu = function(menu) { - var j, len, results, w, windows; - if (!(menu === null || menu.constructor === Menu)) { - throw new TypeError('Invalid menu'); - } - - // Keep a reference. - applicationMenu = menu; - if (process.platform === 'darwin') { - if (menu === null) { - return; - } - menu._callMenuWillShow(); - return bindings.setApplicationMenu(menu); - } else { - windows = BrowserWindow.getAllWindows(); - results = []; - for (j = 0, len = windows.length; j < len; j++) { - w = windows[j]; - results.push(w.setMenu(menu)); - } - return results; - } -}; - -Menu.getApplicationMenu = function() { - return applicationMenu; -}; - -Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder; - -Menu.buildFromTemplate = function(template) { - var insertIndex, item, j, k, key, len, len1, menu, menuItem, positionedTemplate, value; - if (!Array.isArray(template)) { - throw new TypeError('Invalid template for Menu'); - } - positionedTemplate = []; - insertIndex = 0; - for (j = 0, len = template.length; j < len; j++) { - item = template[j]; - if (item.position) { - insertIndex = indexToInsertByPosition(positionedTemplate, item.position); - } else { - // If no |position| is specified, insert after last item. - insertIndex++; - } - positionedTemplate.splice(insertIndex, 0, item); - } - menu = new Menu; - for (k = 0, len1 = positionedTemplate.length; k < len1; k++) { - item = positionedTemplate[k]; - if (typeof item !== 'object') { - throw new TypeError('Invalid template for MenuItem'); - } - menuItem = new MenuItem(item); - for (key in item) { - value = item[key]; - if (menuItem[key] == null) { - menuItem[key] = value; - } - } - menu.append(menuItem); - } - return menu; -}; - -module.exports = Menu; diff --git a/atom/browser/api/lib/navigation-controller.js b/atom/browser/api/lib/navigation-controller.js deleted file mode 100644 index 80756eb13e49..000000000000 --- a/atom/browser/api/lib/navigation-controller.js +++ /dev/null @@ -1,188 +0,0 @@ -const ipcMain = require('electron').ipcMain; - -var slice = [].slice; - -// The history operation in renderer is redirected to browser. -ipcMain.on('ATOM_SHELL_NAVIGATION_CONTROLLER', function() { - var args, event, method, ref; - event = arguments[0], method = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; - return (ref = event.sender)[method].apply(ref, args); -}); - -ipcMain.on('ATOM_SHELL_SYNC_NAVIGATION_CONTROLLER', function() { - var args, event, method, ref; - event = arguments[0], method = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; - return event.returnValue = (ref = event.sender)[method].apply(ref, args); -}); - -// JavaScript implementation of Chromium's NavigationController. -// Instead of relying on Chromium for history control, we compeletely do history -// control on user land, and only rely on WebContents.loadURL for navigation. -// This helps us avoid Chromium's various optimizations so we can ensure renderer -// process is restarted everytime. -var NavigationController = (function() { - function NavigationController(webContents) { - this.webContents = webContents; - this.clearHistory(); - - // webContents may have already navigated to a page. - if (this.webContents._getURL()) { - this.currentIndex++; - this.history.push(this.webContents._getURL()); - } - this.webContents.on('navigation-entry-commited', (function(_this) { - return function(event, url, inPage, replaceEntry) { - var currentEntry; - if (_this.inPageIndex > -1 && !inPage) { - - // Navigated to a new page, clear in-page mark. - _this.inPageIndex = -1; - } else if (_this.inPageIndex === -1 && inPage) { - - // Started in-page navigations. - _this.inPageIndex = _this.currentIndex; - } - if (_this.pendingIndex >= 0) { - - // Go to index. - _this.currentIndex = _this.pendingIndex; - _this.pendingIndex = -1; - return _this.history[_this.currentIndex] = url; - } else if (replaceEntry) { - - // Non-user initialized navigation. - return _this.history[_this.currentIndex] = url; - } else { - - // Normal navigation. Clear history. - _this.history = _this.history.slice(0, _this.currentIndex + 1); - currentEntry = _this.history[_this.currentIndex]; - if ((currentEntry != null ? currentEntry.url : void 0) !== url) { - _this.currentIndex++; - return _this.history.push(url); - } - } - }; - })(this)); - } - - NavigationController.prototype.loadURL = function(url, options) { - if (options == null) { - options = {}; - } - this.pendingIndex = -1; - this.webContents._loadURL(url, options); - return this.webContents.emit('load-url', url, options); - }; - - NavigationController.prototype.getURL = function() { - if (this.currentIndex === -1) { - return ''; - } else { - return this.history[this.currentIndex]; - } - }; - - NavigationController.prototype.stop = function() { - this.pendingIndex = -1; - return this.webContents._stop(); - }; - - NavigationController.prototype.reload = function() { - this.pendingIndex = this.currentIndex; - return this.webContents._loadURL(this.getURL(), {}); - }; - - NavigationController.prototype.reloadIgnoringCache = function() { - this.pendingIndex = this.currentIndex; - return this.webContents._loadURL(this.getURL(), { - extraHeaders: "pragma: no-cache\n" - }); - }; - - NavigationController.prototype.canGoBack = function() { - return this.getActiveIndex() > 0; - }; - - NavigationController.prototype.canGoForward = function() { - return this.getActiveIndex() < this.history.length - 1; - }; - - NavigationController.prototype.canGoToIndex = function(index) { - return index >= 0 && index < this.history.length; - }; - - NavigationController.prototype.canGoToOffset = function(offset) { - return this.canGoToIndex(this.currentIndex + offset); - }; - - NavigationController.prototype.clearHistory = function() { - this.history = []; - this.currentIndex = -1; - this.pendingIndex = -1; - return this.inPageIndex = -1; - }; - - NavigationController.prototype.goBack = function() { - if (!this.canGoBack()) { - return; - } - this.pendingIndex = this.getActiveIndex() - 1; - if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) { - return this.webContents._goBack(); - } else { - return this.webContents._loadURL(this.history[this.pendingIndex], {}); - } - }; - - NavigationController.prototype.goForward = function() { - if (!this.canGoForward()) { - return; - } - this.pendingIndex = this.getActiveIndex() + 1; - if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) { - return this.webContents._goForward(); - } else { - return this.webContents._loadURL(this.history[this.pendingIndex], {}); - } - }; - - NavigationController.prototype.goToIndex = function(index) { - if (!this.canGoToIndex(index)) { - return; - } - this.pendingIndex = index; - return this.webContents._loadURL(this.history[this.pendingIndex], {}); - }; - - NavigationController.prototype.goToOffset = function(offset) { - var pendingIndex; - if (!this.canGoToOffset(offset)) { - return; - } - pendingIndex = this.currentIndex + offset; - if (this.inPageIndex > -1 && pendingIndex >= this.inPageIndex) { - this.pendingIndex = pendingIndex; - return this.webContents._goToOffset(offset); - } else { - return this.goToIndex(pendingIndex); - } - }; - - NavigationController.prototype.getActiveIndex = function() { - if (this.pendingIndex === -1) { - return this.currentIndex; - } else { - return this.pendingIndex; - } - }; - - NavigationController.prototype.length = function() { - return this.history.length; - }; - - return NavigationController; - -})(); - -module.exports = NavigationController; diff --git a/atom/browser/api/lib/power-monitor.js b/atom/browser/api/lib/power-monitor.js deleted file mode 100644 index 239eb3b4d47a..000000000000 --- a/atom/browser/api/lib/power-monitor.js +++ /dev/null @@ -1,6 +0,0 @@ -const EventEmitter = require('events').EventEmitter; -const powerMonitor = process.atomBinding('power_monitor').powerMonitor; - -powerMonitor.__proto__ = EventEmitter.prototype; - -module.exports = powerMonitor; diff --git a/atom/browser/api/lib/power-save-blocker.js b/atom/browser/api/lib/power-save-blocker.js deleted file mode 100644 index c44e3e2b63a2..000000000000 --- a/atom/browser/api/lib/power-save-blocker.js +++ /dev/null @@ -1,5 +0,0 @@ -var powerSaveBlocker; - -powerSaveBlocker = process.atomBinding('power_save_blocker').powerSaveBlocker; - -module.exports = powerSaveBlocker; diff --git a/atom/browser/api/lib/protocol.js b/atom/browser/api/lib/protocol.js deleted file mode 100644 index 41cb48db09b9..000000000000 --- a/atom/browser/api/lib/protocol.js +++ /dev/null @@ -1,31 +0,0 @@ -const app = require('electron').app; - -if (!app.isReady()) { - throw new Error('Can not initialize protocol module before app is ready'); -} - -const protocol = process.atomBinding('protocol').protocol; - -// Warn about removed APIs. -var logAndThrow = function(callback, message) { - console.error(message); - if (callback) { - return callback(new Error(message)); - } else { - throw new Error(message); - } -}; - -protocol.registerProtocol = function(scheme, handler, callback) { - return logAndThrow(callback, 'registerProtocol API has been replaced by the register[File/Http/Buffer/String]Protocol API family, please switch to the new APIs.'); -}; - -protocol.isHandledProtocol = function(scheme, callback) { - return logAndThrow(callback, 'isHandledProtocol API has been replaced by isProtocolHandled.'); -}; - -protocol.interceptProtocol = function(scheme, handler, callback) { - return logAndThrow(callback, 'interceptProtocol API has been replaced by the intercept[File/Http/Buffer/String]Protocol API family, please switch to the new APIs.'); -}; - -module.exports = protocol; diff --git a/atom/browser/api/lib/screen.js b/atom/browser/api/lib/screen.js deleted file mode 100644 index 04965278a3c8..000000000000 --- a/atom/browser/api/lib/screen.js +++ /dev/null @@ -1,6 +0,0 @@ -const EventEmitter = require('events').EventEmitter; -const screen = process.atomBinding('screen').screen; - -screen.__proto__ = EventEmitter.prototype; - -module.exports = screen; diff --git a/atom/browser/api/lib/session.js b/atom/browser/api/lib/session.js deleted file mode 100644 index 8662759192dd..000000000000 --- a/atom/browser/api/lib/session.js +++ /dev/null @@ -1,33 +0,0 @@ -const EventEmitter = require('events').EventEmitter; -const bindings = process.atomBinding('session'); -const PERSIST_PREFIX = 'persist:'; - -// Returns the Session from |partition| string. -exports.fromPartition = function(partition) { - if (partition == null) { - partition = ''; - } - if (partition === '') { - return exports.defaultSession; - } - if (partition.startsWith(PERSIST_PREFIX)) { - return bindings.fromPartition(partition.substr(PERSIST_PREFIX.length), false); - } else { - return bindings.fromPartition(partition, true); - } -}; - -// Returns the default session. -Object.defineProperty(exports, 'defaultSession', { - enumerable: true, - get: function() { - return bindings.fromPartition('', false); - } -}); - -var wrapSession = function(session) { - // session is an EventEmitter. - return session.__proto__ = EventEmitter.prototype; -}; - -bindings._setWrapSession(wrapSession); diff --git a/atom/browser/api/lib/tray.js b/atom/browser/api/lib/tray.js deleted file mode 100644 index 342683552f62..000000000000 --- a/atom/browser/api/lib/tray.js +++ /dev/null @@ -1,23 +0,0 @@ -const deprecate = require('electron').deprecate; -const EventEmitter = require('events').EventEmitter; -const Tray = process.atomBinding('tray').Tray; - -Tray.prototype.__proto__ = EventEmitter.prototype; - -Tray.prototype._init = function() { - // Deprecated. - deprecate.rename(this, 'popContextMenu', 'popUpContextMenu'); - deprecate.event(this, 'clicked', 'click'); - deprecate.event(this, 'double-clicked', 'double-click'); - deprecate.event(this, 'right-clicked', 'right-click'); - return deprecate.event(this, 'balloon-clicked', 'balloon-click'); -}; - -Tray.prototype.setContextMenu = function(menu) { - this._setContextMenu(menu); - - // Keep a strong reference of menu. - return this.menu = menu; -}; - -module.exports = Tray; diff --git a/atom/browser/api/lib/web-contents.js b/atom/browser/api/lib/web-contents.js deleted file mode 100644 index e0c16999c72a..000000000000 --- a/atom/browser/api/lib/web-contents.js +++ /dev/null @@ -1,236 +0,0 @@ -'use strict'; - -const EventEmitter = require('events').EventEmitter; -const deprecate = require('electron').deprecate; -const ipcMain = require('electron').ipcMain; -const NavigationController = require('electron').NavigationController; -const Menu = require('electron').Menu; - -const binding = process.atomBinding('web_contents'); -const debuggerBinding = process.atomBinding('debugger'); - -let slice = [].slice; -let nextId = 0; - -let getNextId = function() { - return ++nextId; -}; - -let PDFPageSize = { - A5: { - custom_display_name: "A5", - height_microns: 210000, - name: "ISO_A5", - width_microns: 148000 - }, - A4: { - custom_display_name: "A4", - height_microns: 297000, - name: "ISO_A4", - is_default: "true", - width_microns: 210000 - }, - A3: { - custom_display_name: "A3", - height_microns: 420000, - name: "ISO_A3", - width_microns: 297000 - }, - Legal: { - custom_display_name: "Legal", - height_microns: 355600, - name: "NA_LEGAL", - width_microns: 215900 - }, - Letter: { - custom_display_name: "Letter", - height_microns: 279400, - name: "NA_LETTER", - width_microns: 215900 - }, - Tabloid: { - height_microns: 431800, - name: "NA_LEDGER", - width_microns: 279400, - custom_display_name: "Tabloid" - } -}; - -// Following methods are mapped to webFrame. -const webFrameMethods = [ - 'executeJavaScript', - 'insertText', - 'setZoomFactor', - 'setZoomLevel', - 'setZoomLevelLimits' -]; - -let wrapWebContents = function(webContents) { - // webContents is an EventEmitter. - var controller, method, name, ref1; - webContents.__proto__ = EventEmitter.prototype; - - // Every remote callback from renderer process would add a listenter to the - // render-view-deleted event, so ignore the listenters warning. - webContents.setMaxListeners(0); - - // WebContents::send(channel, args..) - webContents.send = function() { - var args = 2 <= arguments.length ? slice.call(arguments, 1) : []; - var channel = arguments[0]; - if (channel == null) { - throw new Error('Missing required channel argument'); - } - return this._send(channel, slice.call(args)); - }; - - // The navigation controller. - controller = new NavigationController(webContents); - ref1 = NavigationController.prototype; - for (name in ref1) { - method = ref1[name]; - if (method instanceof Function) { - (function(name, method) { - return webContents[name] = function() { - return method.apply(controller, arguments); - }; - })(name, method); - } - } - - // Mapping webFrame methods. - for (let method of webFrameMethods) { - webContents[method] = function() { - let args = Array.prototype.slice.call(arguments); - this.send('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', method, args); - }; - } - - // Make sure webContents.executeJavaScript would run the code only when the - // webContents has been loaded. - const executeJavaScript = webContents.executeJavaScript; - webContents.executeJavaScript = function(code, hasUserGesture) { - if (this.getURL() && !this.isLoading()) - return executeJavaScript.call(this, code, hasUserGesture); - else - return this.once('did-finish-load', executeJavaScript.bind(this, code, hasUserGesture)); - }; - - // Dispatch IPC messages to the ipc module. - webContents.on('ipc-message', function(event, packed) { - var args, channel; - channel = packed[0], args = 2 <= packed.length ? slice.call(packed, 1) : []; - return ipcMain.emit.apply(ipcMain, [channel, event].concat(slice.call(args))); - }); - webContents.on('ipc-message-sync', function(event, packed) { - var args, channel; - channel = packed[0], args = 2 <= packed.length ? slice.call(packed, 1) : []; - Object.defineProperty(event, 'returnValue', { - set: function(value) { - return event.sendReply(JSON.stringify(value)); - } - }); - return ipcMain.emit.apply(ipcMain, [channel, event].concat(slice.call(args))); - }); - - // Handle context menu action request from pepper plugin. - webContents.on('pepper-context-menu', function(event, params) { - var menu; - menu = Menu.buildFromTemplate(params.menu); - return menu.popup(params.x, params.y); - }); - - // This error occurs when host could not be found. - webContents.on('did-fail-provisional-load', function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - - // Calling loadURL during this event might cause crash, so delay the event - // until next tick. - return setImmediate((function(_this) { - return function() { - return _this.emit.apply(_this, ['did-fail-load'].concat(slice.call(args))); - }; - })(this)); - }); - - // Delays the page-title-updated event to next tick. - webContents.on('-page-title-updated', function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return setImmediate((function(_this) { - return function() { - return _this.emit.apply(_this, ['page-title-updated'].concat(slice.call(args))); - }; - })(this)); - }); - - // Deprecated. - deprecate.rename(webContents, 'loadUrl', 'loadURL'); - deprecate.rename(webContents, 'getUrl', 'getURL'); - deprecate.event(webContents, 'page-title-set', 'page-title-updated', function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return this.emit.apply(this, ['page-title-set'].concat(slice.call(args))); - }); - return webContents.printToPDF = function(options, callback) { - var printingSetting; - printingSetting = { - pageRage: [], - mediaSize: {}, - landscape: false, - color: 2, - headerFooterEnabled: false, - marginsType: 0, - isFirstRequest: false, - requestID: getNextId(), - previewModifiable: true, - printToPDF: true, - printWithCloudPrint: false, - printWithPrivet: false, - printWithExtension: false, - deviceName: "Save as PDF", - generateDraftData: true, - fitToPageEnabled: false, - duplex: 0, - copies: 1, - collate: true, - shouldPrintBackgrounds: false, - shouldPrintSelectionOnly: false - }; - if (options.landscape) { - printingSetting.landscape = options.landscape; - } - if (options.marginsType) { - printingSetting.marginsType = options.marginsType; - } - if (options.printSelectionOnly) { - printingSetting.shouldPrintSelectionOnly = options.printSelectionOnly; - } - if (options.printBackground) { - printingSetting.shouldPrintBackgrounds = options.printBackground; - } - if (options.pageSize && PDFPageSize[options.pageSize]) { - printingSetting.mediaSize = PDFPageSize[options.pageSize]; - } else { - printingSetting.mediaSize = PDFPageSize['A4']; - } - return this._printToPDF(printingSetting, callback); - }; -}; - -// Wrapper for native class. -let wrapDebugger = function(webContentsDebugger) { - // debugger is an EventEmitter. - webContentsDebugger.__proto__ = EventEmitter.prototype; -}; - -binding._setWrapWebContents(wrapWebContents); -debuggerBinding._setWrapDebugger(wrapDebugger); - -module.exports.create = function(options) { - if (options == null) { - options = {}; - } - return binding.create(options); -}; diff --git a/atom/browser/api/save_page_handler.cc b/atom/browser/api/save_page_handler.cc index 1e5bc094cf63..e07ec3ac0abb 100644 --- a/atom/browser/api/save_page_handler.cc +++ b/atom/browser/api/save_page_handler.cc @@ -73,11 +73,6 @@ void SavePageHandler::Destroy(content::DownloadItem* item) { 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 index dd1692a8badc..951a9414e03f 100644 --- a/atom/browser/api/save_page_handler.h +++ b/atom/browser/api/save_page_handler.h @@ -37,8 +37,6 @@ class SavePageHandler : public content::DownloadManager::Observer, 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); diff --git a/atom/browser/api/trackable_object.cc b/atom/browser/api/trackable_object.cc index 77a936cde02c..5148df627c8d 100644 --- a/atom/browser/api/trackable_object.cc +++ b/atom/browser/api/trackable_object.cc @@ -37,15 +37,6 @@ TrackableObjectBase::~TrackableObjectBase() { cleanup_.Run(); } -void TrackableObjectBase::AfterInit(v8::Isolate* isolate) { - if (wrapped_) - AttachAsUserData(wrapped_); -} - -void TrackableObjectBase::MarkDestroyed() { - GetWrapper(isolate())->SetAlignedPointerInInternalField(0, nullptr); -} - base::Closure TrackableObjectBase::GetDestroyClosure() { return base::Bind(&TrackableObjectBase::Destroy, weak_factory_.GetWeakPtr()); } diff --git a/atom/browser/api/trackable_object.h b/atom/browser/api/trackable_object.h index 7c4ed03fe052..bbed54754002 100644 --- a/atom/browser/api/trackable_object.h +++ b/atom/browser/api/trackable_object.h @@ -8,7 +8,7 @@ #include #include "atom/browser/api/event_emitter.h" -#include "atom/common/id_weak_map.h" +#include "atom/common/key_weak_map.h" #include "base/bind.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" @@ -21,7 +21,7 @@ class SupportsUserData; namespace mate { // Users should use TrackableObject instead. -class TrackableObjectBase : public mate::EventEmitter { +class TrackableObjectBase { public: TrackableObjectBase(); @@ -32,13 +32,7 @@ class TrackableObjectBase : public mate::EventEmitter { void AttachAsUserData(base::SupportsUserData* wrapped); protected: - ~TrackableObjectBase() override; - - // mate::Wrappable: - void AfterInit(v8::Isolate* isolate) override; - - // Mark the JS object as destroyed. - void MarkDestroyed(); + virtual ~TrackableObjectBase(); // Returns a closure that can destroy the native class. base::Closure GetDestroyClosure(); @@ -65,8 +59,14 @@ class TrackableObjectBase : public mate::EventEmitter { // All instances of TrackableObject will be kept in a weak map and can be got // from its ID. template -class TrackableObject : public TrackableObjectBase { +class TrackableObject : public TrackableObjectBase, + public mate::EventEmitter { public: + // Mark the JS object as destroyed. + void MarkDestroyed() { + Wrappable::GetWrapper()->SetAlignedPointerInInternalField(0, nullptr); + } + // Finds out the TrackableObject from its ID in weak map. static T* FromWeakMapID(v8::Isolate* isolate, int32_t id) { if (!weak_map_) @@ -106,50 +106,33 @@ class TrackableObject : public TrackableObjectBase { protected: TrackableObject() {} + ~TrackableObject() override { RemoveFromWeakMap(); } void AfterInit(v8::Isolate* isolate) override { if (!weak_map_) { - weak_map_.reset(new atom::IDWeakMap); - RegisterDestructionCallback( - base::Bind(&TrackableObject::ReleaseAllWeakReferences)); + weak_map_ = new atom::KeyWeakMap; } - weak_map_id_ = weak_map_->Add(isolate, GetWrapper(isolate)); - TrackableObjectBase::AfterInit(isolate); + weak_map_id_ = ++next_id_; + weak_map_->Set(isolate, weak_map_id_, Wrappable::GetWrapper()); + if (wrapped_) + AttachAsUserData(wrapped_); } 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_; + static int32_t next_id_; + static atom::KeyWeakMap* weak_map_; // leaked on purpose DISALLOW_COPY_AND_ASSIGN(TrackableObject); }; template -v8::Persistent TrackableObject::template_; +int32_t TrackableObject::next_id_ = 0; template -scoped_ptr TrackableObject::weak_map_; +atom::KeyWeakMap* TrackableObject::weak_map_ = nullptr; } // namespace mate diff --git a/atom/browser/atom_access_token_store.cc b/atom/browser/atom_access_token_store.cc index adf2f5061cb0..ed87ea979920 100644 --- a/atom/browser/atom_access_token_store.cc +++ b/atom/browser/atom_access_token_store.cc @@ -32,18 +32,18 @@ AtomAccessTokenStore::~AtomAccessTokenStore() { } void AtomAccessTokenStore::LoadAccessTokens( - const LoadAccessTokensCallbackType& callback) { - AccessTokenSet access_token_set; + const LoadAccessTokensCallback& callback) { + AccessTokenMap access_token_map; - // Equivelent to access_token_set[kGeolocationProviderURL]. + // Equivelent to access_token_map[kGeolocationProviderURL]. // Somehow base::string16 is causing compilation errors when used in a pair // of std::map on Linux, this can work around it. std::pair token_pair; token_pair.first = GURL(kGeolocationProviderURL); - access_token_set.insert(token_pair); + access_token_map.insert(token_pair); auto browser_context = AtomBrowserMainParts::Get()->browser_context(); - callback.Run(access_token_set, browser_context->url_request_context_getter()); + callback.Run(access_token_map, browser_context->url_request_context_getter()); } void AtomAccessTokenStore::SaveAccessToken(const GURL& server_url, diff --git a/atom/browser/atom_access_token_store.h b/atom/browser/atom_access_token_store.h index f2b734a20695..54bc2cee3c29 100644 --- a/atom/browser/atom_access_token_store.h +++ b/atom/browser/atom_access_token_store.h @@ -18,7 +18,7 @@ class AtomAccessTokenStore : public content::AccessTokenStore { // content::AccessTokenStore: void LoadAccessTokens( - const LoadAccessTokensCallbackType& callback) override; + const LoadAccessTokensCallback& callback) override; void SaveAccessToken(const GURL& server_url, const base::string16& access_token) override; diff --git a/atom/browser/atom_browser_client.cc b/atom/browser/atom_browser_client.cc index 1fe435673db6..e3cb9c5c8c89 100644 --- a/atom/browser/atom_browser_client.cc +++ b/atom/browser/atom_browser_client.cc @@ -8,6 +8,7 @@ #include #endif +#include "atom/browser/api/atom_api_app.h" #include "atom/browser/atom_access_token_store.h" #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" @@ -36,7 +37,6 @@ #include "content/public/browser/site_instance.h" #include "content/public/browser/web_contents.h" #include "content/public/common/web_preferences.h" -#include "net/cert/x509_certificate.h" #include "net/ssl/ssl_cert_request_info.h" #include "ppapi/host/ppapi_host.h" #include "ui/base/l10n/l10n_util.h" @@ -46,37 +46,13 @@ namespace atom { namespace { -// The default routing id of WebContents. -// In Electron each RenderProcessHost only has one WebContents, so this ID is -// same for every WebContents. -int kDefaultRoutingID = 2; - // Next navigation should not restart renderer process. bool g_suppress_renderer_process_restart = false; -// Custom schemes to be registered to standard. -std::string g_custom_schemes = ""; // Custom schemes to be registered to handle service worker. std::string g_custom_service_worker_schemes = ""; -scoped_refptr ImportCertFromFile( - const base::FilePath& path) { - if (path.empty()) - return nullptr; - - std::string cert_data; - if (!base::ReadFileToString(path, &cert_data)) - return nullptr; - - net::CertificateList certs = - net::X509Certificate::CreateCertificateListFromBytes( - cert_data.data(), cert_data.size(), - net::X509Certificate::FORMAT_AUTO); - - if (certs.empty()) - return nullptr; - - return certs[0]; +void Noop(scoped_refptr) { } } // namespace @@ -86,11 +62,6 @@ void AtomBrowserClient::SuppressRendererProcessRestartForOnce() { g_suppress_renderer_process_restart = true; } -void AtomBrowserClient::SetCustomSchemes( - const std::vector& schemes) { - g_custom_schemes = base::JoinString(schemes, ","); -} - void AtomBrowserClient::SetCustomServiceWorkerSchemes( const std::vector& schemes) { g_custom_service_worker_schemes = base::JoinString(schemes, ","); @@ -102,6 +73,17 @@ AtomBrowserClient::AtomBrowserClient() : delegate_(nullptr) { AtomBrowserClient::~AtomBrowserClient() { } +content::WebContents* AtomBrowserClient::GetWebContentsFromProcessID( + int process_id) { + // If the process is a pending process, we should use the old one. + if (ContainsKey(pending_processes_, process_id)) + process_id = pending_processes_[process_id]; + + // Certain render process will be created with no associated render view, + // for example: ServiceWorker. + return WebContentsPreferences::GetWebContentsFromProcessID(process_id); +} + void AtomBrowserClient::RenderProcessWillLaunch( content::RenderProcessHost* host) { int process_id = host->GetID(); @@ -161,7 +143,16 @@ void AtomBrowserClient::OverrideSiteInstanceForNavigation( if (url.SchemeIs(url::kJavaScriptScheme)) return; - *new_instance = content::SiteInstance::CreateForURL(browser_context, url); + scoped_refptr site_instance = + content::SiteInstance::CreateForURL(browser_context, url); + *new_instance = site_instance.get(); + + // Make sure the |site_instance| is not freed when this function returns. + // FIXME(zcbenz): We should adjust OverrideSiteInstanceForNavigation's + // interface to solve this. + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&Noop, base::RetainedRef(site_instance))); // Remember the original renderer process of the pending renderer process. auto current_process = current_instance->GetProcess(); @@ -178,11 +169,6 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches( if (process_type != "renderer") return; - // The registered standard schemes. - if (!g_custom_schemes.empty()) - command_line->AppendSwitchASCII(switches::kRegisterStandardSchemes, - g_custom_schemes); - // The registered service worker schemes. if (!g_custom_service_worker_schemes.empty()) command_line->AppendSwitchASCII(switches::kRegisterServiceWorkerSchemes, @@ -197,20 +183,7 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches( } #endif - // If the process is a pending process, we should use the old one. - if (ContainsKey(pending_processes_, process_id)) - process_id = pending_processes_[process_id]; - - - // Certain render process will be created with no associated render view, - // for example: ServiceWorker. - auto rvh = content::RenderViewHost::FromID(process_id, kDefaultRoutingID); - if (!rvh) - return; - - // Get the WebContents of the render process. - content::WebContents* web_contents = - content::WebContents::FromRenderViewHost(rvh); + content::WebContents* web_contents = GetWebContentsFromProcessID(process_id); if (!web_contents) return; @@ -230,8 +203,7 @@ content::QuotaPermissionContext* } void AtomBrowserClient::AllowCertificateError( - int render_process_id, - int render_frame_id, + content::WebContents* web_contents, int cert_error, const net::SSLInfo& ssl_info, const GURL& request_url, @@ -243,7 +215,7 @@ void AtomBrowserClient::AllowCertificateError( content::CertificateRequestResultType* request) { if (delegate_) { delegate_->AllowCertificateError( - render_process_id, render_frame_id, cert_error, ssl_info, request_url, + web_contents, cert_error, ssl_info, request_url, resource_type, overridable, strict_enforcement, expired_previous_decision, callback, request); } @@ -252,20 +224,10 @@ void AtomBrowserClient::AllowCertificateError( void AtomBrowserClient::SelectClientCertificate( content::WebContents* web_contents, net::SSLCertRequestInfo* cert_request_info, - scoped_ptr delegate) { - // --client-certificate=`path` - auto cmd = base::CommandLine::ForCurrentProcess(); - if (cmd->HasSwitch(switches::kClientCertificate)) { - auto cert_path = cmd->GetSwitchValuePath(switches::kClientCertificate); - auto certificate = ImportCertFromFile(cert_path); - if (certificate.get()) - delegate->ContinueWithCertificate(certificate.get()); - return; - } - + std::unique_ptr delegate) { if (!cert_request_info->client_certs.empty() && delegate_) { delegate_->SelectClientCertificate( - web_contents, cert_request_info, delegate.Pass()); + web_contents, cert_request_info, std::move(delegate)); } } @@ -276,6 +238,39 @@ void AtomBrowserClient::ResourceDispatcherHostCreated() { resource_dispatcher_host_delegate_.get()); } +bool AtomBrowserClient::CanCreateWindow( + const GURL& opener_url, + const GURL& opener_top_level_frame_url, + const GURL& source_origin, + WindowContainerType container_type, + const std::string& frame_name, + const GURL& target_url, + const content::Referrer& referrer, + WindowOpenDisposition disposition, + const blink::WebWindowFeatures& features, + bool user_gesture, + bool opener_suppressed, + content::ResourceContext* context, + int render_process_id, + int opener_render_view_id, + int opener_render_frame_id, + bool* no_javascript_access) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + + if (delegate_) { + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, + base::Bind(&api::App::OnCreateWindow, + base::Unretained(static_cast(delegate_)), + target_url, + frame_name, + disposition, + render_process_id, + opener_render_frame_id)); + } + + return false; +} + brightray::BrowserMainParts* AtomBrowserClient::OverrideCreateBrowserMainParts( const content::MainFunctionParams&) { v8::V8::Initialize(); // Init V8 before creating main parts. @@ -285,8 +280,8 @@ brightray::BrowserMainParts* AtomBrowserClient::OverrideCreateBrowserMainParts( void AtomBrowserClient::WebNotificationAllowed( int render_process_id, const base::Callback& callback) { - content::WebContents* web_contents = content::WebContents::FromRenderViewHost( - content::RenderViewHost::FromID(render_process_id, kDefaultRoutingID)); + content::WebContents* web_contents = + WebContentsPreferences::GetWebContentsFromProcessID(render_process_id); if (!web_contents) { callback.Run(false); return; diff --git a/atom/browser/atom_browser_client.h b/atom/browser/atom_browser_client.h index 4af66cc041ae..cf1a4cc438b6 100644 --- a/atom/browser/atom_browser_client.h +++ b/atom/browser/atom_browser_client.h @@ -34,10 +34,12 @@ class AtomBrowserClient : public brightray::BrowserClient, using Delegate = content::ContentBrowserClient; void set_delegate(Delegate* delegate) { delegate_ = delegate; } + // Returns the WebContents for pending render processes. + content::WebContents* GetWebContentsFromProcessID(int process_id); + // Don't force renderer process to restart for once. static void SuppressRendererProcessRestartForOnce(); - // Custom schemes to be registered to standard. - static void SetCustomSchemes(const std::vector& schemes); + // Custom schemes to be registered to handle service worker. static void SetCustomServiceWorkerSchemes( const std::vector& schemes); @@ -61,8 +63,7 @@ class AtomBrowserClient : public brightray::BrowserClient, void DidCreatePpapiPlugin(content::BrowserPpapiHost* browser_host) override; content::QuotaPermissionContext* CreateQuotaPermissionContext() override; void AllowCertificateError( - int render_process_id, - int render_frame_id, + content::WebContents* web_contents, int cert_error, const net::SSLInfo& ssl_info, const GURL& request_url, @@ -75,8 +76,24 @@ class AtomBrowserClient : public brightray::BrowserClient, void SelectClientCertificate( content::WebContents* web_contents, net::SSLCertRequestInfo* cert_request_info, - scoped_ptr delegate) override; + std::unique_ptr delegate) override; void ResourceDispatcherHostCreated() override; + bool CanCreateWindow(const GURL& opener_url, + const GURL& opener_top_level_frame_url, + const GURL& source_origin, + WindowContainerType container_type, + const std::string& frame_name, + const GURL& target_url, + const content::Referrer& referrer, + WindowOpenDisposition disposition, + const blink::WebWindowFeatures& features, + bool user_gesture, + bool opener_suppressed, + content::ResourceContext* context, + int render_process_id, + int opener_render_view_id, + int opener_render_frame_id, + bool* no_javascript_access) override; // brightray::BrowserClient: brightray::BrowserMainParts* OverrideCreateBrowserMainParts( @@ -92,7 +109,7 @@ class AtomBrowserClient : public brightray::BrowserClient, // pending_render_process => current_render_process. std::map pending_processes_; - scoped_ptr + std::unique_ptr resource_dispatcher_host_delegate_; Delegate* delegate_; diff --git a/atom/browser/atom_browser_context.cc b/atom/browser/atom_browser_context.cc index be004c8f6db8..6f28cf6df7da 100644 --- a/atom/browser/atom_browser_context.cc +++ b/atom/browser/atom_browser_context.cc @@ -21,7 +21,7 @@ #include "base/command_line.h" #include "base/files/file_path.h" #include "base/path_service.h" -#include "base/prefs/pref_registry_simple.h" +#include "components/prefs/pref_registry_simple.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/threading/sequenced_worker_pool.h" @@ -46,7 +46,7 @@ namespace { class NoCacheBackend : public net::HttpCache::BackendFactory { int CreateBackend(net::NetLog* net_log, - scoped_ptr* backend, + std::unique_ptr* backend, const net::CompletionCallback& callback) override { return net::ERR_FAILED; } @@ -65,10 +65,9 @@ std::string RemoveWhitespace(const std::string& str) { AtomBrowserContext::AtomBrowserContext(const std::string& partition, bool in_memory) : brightray::BrowserContext(partition, in_memory), - cert_verifier_(nullptr), + cert_verifier_(new AtomCertVerifier), job_factory_(new AtomURLRequestJobFactory), - network_delegate_(new AtomNetworkDelegate), - allow_ntlm_everywhere_(false) { + network_delegate_(new AtomNetworkDelegate) { } AtomBrowserContext::~AtomBrowserContext() { @@ -95,11 +94,11 @@ std::string AtomBrowserContext::GetUserAgent() { return content::BuildUserAgentFromProduct(user_agent); } -scoped_ptr +std::unique_ptr AtomBrowserContext::CreateURLRequestJobFactory( content::ProtocolHandlerMap* handlers, content::URLRequestInterceptorScopedVector* interceptors) { - scoped_ptr job_factory(job_factory_); + std::unique_ptr job_factory(job_factory_); for (auto& it : *handlers) { job_factory->SetProtocolHandler(it.first, @@ -134,14 +133,15 @@ AtomBrowserContext::CreateURLRequestJobFactory( new net::FtpNetworkLayer(host_resolver)))); // Set up interceptors in the reverse order. - scoped_ptr top_job_factory = job_factory.Pass(); + std::unique_ptr top_job_factory = + std::move(job_factory); content::URLRequestInterceptorScopedVector::reverse_iterator it; for (it = interceptors->rbegin(); it != interceptors->rend(); ++it) top_job_factory.reset(new net::URLRequestInterceptingJobFactory( - top_job_factory.Pass(), make_scoped_ptr(*it))); + std::move(top_job_factory), make_scoped_ptr(*it))); interceptors->weak_clear(); - return top_job_factory.Pass(); + return top_job_factory; } net::HttpCache::BackendFactory* @@ -176,9 +176,7 @@ content::PermissionManager* AtomBrowserContext::GetPermissionManager() { return permission_manager_.get(); } -scoped_ptr AtomBrowserContext::CreateCertVerifier() { - DCHECK(!cert_verifier_); - cert_verifier_ = new AtomCertVerifier; +std::unique_ptr AtomBrowserContext::CreateCertVerifier() { return make_scoped_ptr(cert_verifier_); } @@ -193,16 +191,7 @@ void AtomBrowserContext::RegisterPrefs(PrefRegistrySimple* pref_registry) { PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS, &download_dir); pref_registry->RegisterFilePathPref(prefs::kDownloadDefaultDirectory, download_dir); -} - -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; + pref_registry->RegisterDictionaryPref(prefs::kDevToolsFileSystemPaths); } } // namespace atom diff --git a/atom/browser/atom_browser_context.h b/atom/browser/atom_browser_context.h index d959adbc753a..028f5d1e79c4 100644 --- a/atom/browser/atom_browser_context.h +++ b/atom/browser/atom_browser_context.h @@ -26,14 +26,13 @@ class AtomBrowserContext : public brightray::BrowserContext { // brightray::URLRequestContextGetter::Delegate: net::NetworkDelegate* CreateNetworkDelegate() override; std::string GetUserAgent() override; - scoped_ptr CreateURLRequestJobFactory( + std::unique_ptr CreateURLRequestJobFactory( content::ProtocolHandlerMap* handlers, content::URLRequestInterceptorScopedVector* interceptors) override; net::HttpCache::BackendFactory* CreateHttpCacheBackendFactory( const base::FilePath& base_path) override; - scoped_ptr CreateCertVerifier() override; + std::unique_ptr CreateCertVerifier() override; net::SSLConfigService* CreateSSLConfigService() override; - bool AllowNTLMCredentialsForDomain(const GURL& auth_origin) override; // content::BrowserContext: content::DownloadManagerDelegate* GetDownloadManagerDelegate() override; @@ -43,8 +42,6 @@ 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_; } @@ -52,17 +49,15 @@ class AtomBrowserContext : public brightray::BrowserContext { AtomNetworkDelegate* network_delegate() const { return network_delegate_; } private: - scoped_ptr download_manager_delegate_; - scoped_ptr guest_manager_; - scoped_ptr permission_manager_; + std::unique_ptr download_manager_delegate_; + std::unique_ptr guest_manager_; + std::unique_ptr permission_manager_; // Managed by brightray::BrowserContext. AtomCertVerifier* cert_verifier_; AtomURLRequestJobFactory* job_factory_; AtomNetworkDelegate* network_delegate_; - 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 a046f34287ee..335e0576fcbc 100644 --- a/atom/browser/atom_browser_main_parts.cc +++ b/atom/browser/atom_browser_main_parts.cc @@ -46,6 +46,14 @@ AtomBrowserMainParts::AtomBrowserMainParts() } AtomBrowserMainParts::~AtomBrowserMainParts() { + // Leak the JavascriptEnvironment on exit. + // This is to work around the bug that V8 would be waiting for background + // tasks to finish on exit, while somehow it waits forever in Electron, more + // about this can be found at https://github.com/electron/electron/issues/4767. + // On the other handle there is actually no need to gracefully shutdown V8 + // on exit in the main process, we already ensured all necessary resources get + // cleaned up, and it would make quitting faster. + ignore_result(js_env_.release()); } // static @@ -98,17 +106,21 @@ void AtomBrowserMainParts::PostEarlyInitialization() { node_debugger_.reset(new NodeDebugger(js_env_->isolate())); // Create the global environment. - global_env = node_bindings_->CreateEnvironment(js_env_->context()); + node::Environment* env = + node_bindings_->CreateEnvironment(js_env_->context()); // Make sure node can get correct environment when debugging. if (node_debugger_->IsRunning()) - global_env->AssignToContext(v8::Debug::GetDebugContext()); + env->AssignToContext(v8::Debug::GetDebugContext()); // Add atom-shell extended APIs. - atom_bindings_->BindTo(js_env_->isolate(), global_env->process_object()); + atom_bindings_->BindTo(js_env_->isolate(), env->process_object()); // Load everything. - node_bindings_->LoadEnvironment(global_env); + node_bindings_->LoadEnvironment(env); + + // Wrap the uv loop with global env. + node_bindings_->set_uv_env(env); } void AtomBrowserMainParts::PreMainMessageLoopRun() { @@ -124,9 +136,8 @@ void AtomBrowserMainParts::PreMainMessageLoopRun() { // Start idle gc. gc_timer_.Start( FROM_HERE, base::TimeDelta::FromMinutes(1), - base::Bind(base::IgnoreResult(&v8::Isolate::IdleNotification), - base::Unretained(js_env_->isolate()), - 1000)); + base::Bind(&v8::Isolate::LowMemoryNotification, + base::Unretained(js_env_->isolate()))); brightray::BrowserMainParts::PreMainMessageLoopRun(); bridge_task_runner_->MessageLoopIsReady(); @@ -172,14 +183,6 @@ void AtomBrowserMainParts::PostMainMessageLoopRun() { ++iter; callback.Run(); } - - // Destroy JavaScript environment immediately after running destruction - // callbacks. - gc_timer_.Stop(); - node_debugger_.reset(); - atom_bindings_.reset(); - node_bindings_.reset(); - js_env_.reset(); } } // namespace atom diff --git a/atom/browser/atom_browser_main_parts.h b/atom/browser/atom_browser_main_parts.h index c1c0c89c6786..0d8619f6865a 100644 --- a/atom/browser/atom_browser_main_parts.h +++ b/atom/browser/atom_browser_main_parts.h @@ -31,7 +31,7 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts { static AtomBrowserMainParts* Get(); - // Sets the exit code, will fail if the the message loop is not ready. + // Sets the exit code, will fail if the message loop is not ready. bool SetExitCode(int code); // Gets the exit code @@ -68,7 +68,7 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts { #endif // A fake BrowserProcess object that used to feed the source code from chrome. - scoped_ptr fake_browser_process_; + std::unique_ptr fake_browser_process_; // The gin::PerIsolateData requires a task runner to create, so we feed it // with a task runner that will post all work to main loop. @@ -77,11 +77,11 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts { // Pointer to exit code. int* exit_code_; - scoped_ptr browser_; - scoped_ptr js_env_; - scoped_ptr node_bindings_; - scoped_ptr atom_bindings_; - scoped_ptr node_debugger_; + std::unique_ptr browser_; + std::unique_ptr js_env_; + std::unique_ptr node_bindings_; + std::unique_ptr atom_bindings_; + std::unique_ptr node_debugger_; base::Timer gc_timer_; diff --git a/atom/browser/atom_download_manager_delegate.cc b/atom/browser/atom_download_manager_delegate.cc index f5bdbbd8598d..6d7e3c7172d8 100644 --- a/atom/browser/atom_download_manager_delegate.cc +++ b/atom/browser/atom_download_manager_delegate.cc @@ -12,7 +12,7 @@ #include "atom/browser/ui/file_dialog.h" #include "base/bind.h" #include "base/files/file_util.h" -#include "base/prefs/pref_service.h" +#include "components/prefs/pref_service.h" #include "chrome/common/pref_names.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" @@ -60,7 +60,7 @@ void AtomDownloadManagerDelegate::CreateDownloadPath( } void AtomDownloadManagerDelegate::OnDownloadPathGenerated( - uint32 download_id, + uint32_t download_id, const content::DownloadTargetCallback& callback, const base::FilePath& default_path) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); @@ -70,12 +70,15 @@ void AtomDownloadManagerDelegate::OnDownloadPathGenerated( return; NativeWindow* window = nullptr; - auto relay = NativeWindowRelay::FromWebContents(item->GetWebContents()); + content::WebContents* web_contents = item->GetWebContents(); + auto relay = web_contents ? NativeWindowRelay::FromWebContents(web_contents) + : nullptr; if (relay) window = relay->window.get(); base::FilePath path; - if (file_dialog::ShowSaveDialog(window, item->GetURL().spec(), default_path, + if (file_dialog::ShowSaveDialog(window, item->GetURL().spec(), + "", default_path, file_dialog::Filters(), &path)) { // Remember the last selected download directory. AtomBrowserContext* browser_context = static_cast( @@ -160,7 +163,7 @@ bool AtomDownloadManagerDelegate::ShouldOpenDownload( void AtomDownloadManagerDelegate::GetNextId( const content::DownloadIdCallback& callback) { - static uint32 next_id = content::DownloadItem::kInvalidId + 1; + static uint32_t next_id = content::DownloadItem::kInvalidId + 1; callback.Run(next_id++); } diff --git a/atom/browser/atom_download_manager_delegate.h b/atom/browser/atom_download_manager_delegate.h index 2df3a7d45a6b..5ea3d50d5aee 100644 --- a/atom/browser/atom_download_manager_delegate.h +++ b/atom/browser/atom_download_manager_delegate.h @@ -31,7 +31,7 @@ class AtomDownloadManagerDelegate : public content::DownloadManagerDelegate { const std::string& mime_type, const base::FilePath& path, const CreateDownloadPathCallback& callback); - void OnDownloadPathGenerated(uint32 download_id, + void OnDownloadPathGenerated(uint32_t download_id, const content::DownloadTargetCallback& callback, const base::FilePath& default_path); diff --git a/atom/browser/atom_javascript_dialog_manager.cc b/atom/browser/atom_javascript_dialog_manager.cc index c21d1fb11816..eaf311ba1cdb 100644 --- a/atom/browser/atom_javascript_dialog_manager.cc +++ b/atom/browser/atom_javascript_dialog_manager.cc @@ -13,7 +13,6 @@ namespace atom { void AtomJavaScriptDialogManager::RunJavaScriptDialog( content::WebContents* web_contents, const GURL& origin_url, - const std::string& accept_lang, content::JavaScriptMessageType javascript_message_type, const base::string16& message_text, const base::string16& default_prompt_text, @@ -24,12 +23,10 @@ void AtomJavaScriptDialogManager::RunJavaScriptDialog( void AtomJavaScriptDialogManager::RunBeforeUnloadDialog( content::WebContents* web_contents, - const base::string16& message_text, bool is_reload, const DialogClosedCallback& callback) { - bool prevent_reload = message_text.empty() || - message_text == base::ASCIIToUTF16("false"); - callback.Run(!prevent_reload, message_text); + // FIXME(zcbenz): the |message_text| is removed, figure out what should we do. + callback.Run(false, base::ASCIIToUTF16("This should not be displayed")); } } // namespace atom diff --git a/atom/browser/atom_javascript_dialog_manager.h b/atom/browser/atom_javascript_dialog_manager.h index c0a0dccf0fa9..3844e41f9d2f 100644 --- a/atom/browser/atom_javascript_dialog_manager.h +++ b/atom/browser/atom_javascript_dialog_manager.h @@ -17,7 +17,6 @@ class AtomJavaScriptDialogManager : public content::JavaScriptDialogManager { void RunJavaScriptDialog( content::WebContents* web_contents, const GURL& origin_url, - const std::string& accept_lang, content::JavaScriptMessageType javascript_message_type, const base::string16& message_text, const base::string16& default_prompt_text, @@ -25,7 +24,6 @@ class AtomJavaScriptDialogManager : public content::JavaScriptDialogManager { bool* did_suppress_message) override; void RunBeforeUnloadDialog( content::WebContents* web_contents, - const base::string16& message_text, bool is_reload, const DialogClosedCallback& callback) override; void CancelActiveAndPendingDialogs( diff --git a/atom/browser/atom_permission_manager.cc b/atom/browser/atom_permission_manager.cc index e7fdaaffea71..7fc5fa0d0378 100644 --- a/atom/browser/atom_permission_manager.cc +++ b/atom/browser/atom_permission_manager.cc @@ -4,6 +4,9 @@ #include "atom/browser/atom_permission_manager.h" +#include + +#include "atom/browser/web_contents_preferences.h" #include "content/public/browser/child_process_security_policy.h" #include "content/public/browser/permission_type.h" #include "content/public/browser/render_frame_host.h" @@ -15,17 +18,12 @@ namespace atom { namespace { -// Must be kept in sync with atom_browser_client.cc -int kDefaultRoutingID = 2; - bool WebContentsDestroyed(int process_id) { - auto rvh = content::RenderViewHost::FromID(process_id, kDefaultRoutingID); - if (rvh) { - auto contents = content::WebContents::FromRenderViewHost(rvh); - return contents->IsBeingDestroyed(); - } - - return true; + auto contents = + WebContentsPreferences::GetWebContentsFromProcessID(process_id); + if (!contents) + return true; + return contents->IsBeingDestroyed(); } } // namespace @@ -42,7 +40,7 @@ void AtomPermissionManager::SetPermissionRequestHandler( if (handler.is_null() && !pending_requests_.empty()) { for (const auto& request : pending_requests_) { if (!WebContentsDestroyed(request.second.render_process_id)) - request.second.callback.Run(content::PERMISSION_STATUS_DENIED); + request.second.callback.Run(blink::mojom::PermissionStatus::DENIED); } pending_requests_.clear(); } @@ -53,7 +51,6 @@ int AtomPermissionManager::RequestPermission( content::PermissionType permission, content::RenderFrameHost* render_frame_host, const GURL& requesting_origin, - bool user_gesture, const ResponseCallback& response_callback) { int process_id = render_frame_host->GetProcess()->GetID(); @@ -76,7 +73,26 @@ int AtomPermissionManager::RequestPermission( return request_id_; } - response_callback.Run(content::PERMISSION_STATUS_GRANTED); + response_callback.Run(blink::mojom::PermissionStatus::GRANTED); + return kNoPendingOperation; +} + +int AtomPermissionManager::RequestPermissions( + const std::vector& permissions, + content::RenderFrameHost* render_frame_host, + const GURL& requesting_origin, + const base::Callback&)>& callback) { + // FIXME(zcbenz): Just ignore multiple permissions request for now. + std::vector permissionStatuses; + for (auto permission : permissions) { + if (permission == content::PermissionType::MIDI_SYSEX) { + content::ChildProcessSecurityPolicy::GetInstance()-> + GrantSendMidiSysExMessage(render_frame_host->GetProcess()->GetID()); + } + permissionStatuses.push_back(blink::mojom::PermissionStatus::GRANTED); + } + callback.Run(permissionStatuses); return kNoPendingOperation; } @@ -84,7 +100,7 @@ void AtomPermissionManager::OnPermissionResponse( int request_id, const GURL& origin, const ResponseCallback& callback, - content::PermissionStatus status) { + blink::mojom::PermissionStatus status) { auto request = pending_requests_.find(request_id); if (request != pending_requests_.end()) { if (!WebContentsDestroyed(request->second.render_process_id)) @@ -97,7 +113,7 @@ void AtomPermissionManager::CancelPermissionRequest(int request_id) { auto request = pending_requests_.find(request_id); if (request != pending_requests_.end()) { if (!WebContentsDestroyed(request->second.render_process_id)) - request->second.callback.Run(content::PERMISSION_STATUS_DENIED); + request->second.callback.Run(blink::mojom::PermissionStatus::DENIED); pending_requests_.erase(request); } } @@ -108,11 +124,11 @@ void AtomPermissionManager::ResetPermission( const GURL& embedding_origin) { } -content::PermissionStatus AtomPermissionManager::GetPermissionStatus( +blink::mojom::PermissionStatus AtomPermissionManager::GetPermissionStatus( content::PermissionType permission, const GURL& requesting_origin, const GURL& embedding_origin) { - return content::PERMISSION_STATUS_GRANTED; + return blink::mojom::PermissionStatus::GRANTED; } void AtomPermissionManager::RegisterPermissionUsage( diff --git a/atom/browser/atom_permission_manager.h b/atom/browser/atom_permission_manager.h index 4bebbbc84f29..d0fcdc60768f 100644 --- a/atom/browser/atom_permission_manager.h +++ b/atom/browser/atom_permission_manager.h @@ -6,6 +6,7 @@ #define ATOM_BROWSER_ATOM_PERMISSION_MANAGER_H_ #include +#include #include "base/callback.h" #include "content/public/browser/permission_manager.h" @@ -22,7 +23,7 @@ class AtomPermissionManager : public content::PermissionManager { ~AtomPermissionManager() override; using ResponseCallback = - base::Callback; + base::Callback; using RequestHandler = base::Callback& permissions, + content::RenderFrameHost* render_frame_host, + const GURL& requesting_origin, + const base::Callback&)>& callback) override; protected: void OnPermissionResponse(int request_id, const GURL& url, const ResponseCallback& callback, - content::PermissionStatus status); + blink::mojom::PermissionStatus status); // content::PermissionManager: void CancelPermissionRequest(int request_id) override; void ResetPermission(content::PermissionType permission, const GURL& requesting_origin, const GURL& embedding_origin) override; - content::PermissionStatus GetPermissionStatus( + blink::mojom::PermissionStatus GetPermissionStatus( content::PermissionType permission, const GURL& requesting_origin, const GURL& embedding_origin) override; @@ -61,7 +67,8 @@ class AtomPermissionManager : public content::PermissionManager { content::PermissionType permission, const GURL& requesting_origin, const GURL& embedding_origin, - const base::Callback& callback) override; + const base::Callback& callback) + override; void UnsubscribePermissionStatusChange(int subscription_id) override; private: diff --git a/atom/browser/atom_resource_dispatcher_host_delegate.cc b/atom/browser/atom_resource_dispatcher_host_delegate.cc index 4d969786c19a..59ac258ea13a 100644 --- a/atom/browser/atom_resource_dispatcher_host_delegate.cc +++ b/atom/browser/atom_resource_dispatcher_host_delegate.cc @@ -5,6 +5,7 @@ #include "atom/browser/atom_resource_dispatcher_host_delegate.h" #include "atom/browser/login_handler.h" +#include "atom/browser/web_contents_permission_helper.h" #include "atom/common/platform_util.h" #include "content/public/browser/browser_thread.h" #include "net/base/escape.h" @@ -14,20 +15,46 @@ using content::BrowserThread; namespace atom { +namespace { + +void OnOpenExternal(const GURL& escaped_url, + bool allowed) { + if (allowed) + platform_util::OpenExternal(escaped_url, true); +} + +void HandleExternalProtocolInUI( + const GURL& url, + const content::ResourceRequestInfo::WebContentsGetter& web_contents_getter, + bool has_user_gesture) { + content::WebContents* web_contents = web_contents_getter.Run(); + if (!web_contents) + return; + + GURL escaped_url(net::EscapeExternalHandlerValue(url.spec())); + auto callback = base::Bind(&OnOpenExternal, escaped_url); + auto permission_helper = + WebContentsPermissionHelper::FromWebContents(web_contents); + permission_helper->RequestOpenExternalPermission(callback, has_user_gesture); +} + +} // namespace + AtomResourceDispatcherHostDelegate::AtomResourceDispatcherHostDelegate() { } bool AtomResourceDispatcherHostDelegate::HandleExternalProtocol( const GURL& url, - int render_process_id, - int render_view_id, + int child_id, + const content::ResourceRequestInfo::WebContentsGetter& web_contents_getter, 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, true)); + base::Bind(&HandleExternalProtocolInUI, + url, + web_contents_getter, + has_user_gesture)); return true; } diff --git a/atom/browser/atom_resource_dispatcher_host_delegate.h b/atom/browser/atom_resource_dispatcher_host_delegate.h index a90b366bc75b..408b83c92d90 100644 --- a/atom/browser/atom_resource_dispatcher_host_delegate.h +++ b/atom/browser/atom_resource_dispatcher_host_delegate.h @@ -15,12 +15,13 @@ class AtomResourceDispatcherHostDelegate AtomResourceDispatcherHostDelegate(); // content::ResourceDispatcherHostDelegate: - bool HandleExternalProtocol(const GURL& url, - int render_process_id, - int render_view_id, - bool is_main_frame, - ui::PageTransition transition, - bool has_user_gesture) override; + bool HandleExternalProtocol( + const GURL& url, + int child_id, + const content::ResourceRequestInfo::WebContentsGetter&, + bool is_main_frame, + ui::PageTransition transition, + bool has_user_gesture) override; content::ResourceDispatcherHostLoginDelegate* CreateLoginDelegate( net::AuthChallengeInfo* auth_info, net::URLRequest* request) override; diff --git a/atom/browser/atom_security_state_model_client.cc b/atom/browser/atom_security_state_model_client.cc new file mode 100644 index 000000000000..911849c5c2d1 --- /dev/null +++ b/atom/browser/atom_security_state_model_client.cc @@ -0,0 +1,105 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/atom_security_state_model_client.h" + +#include "content/public/browser/cert_store.h" +#include "content/public/browser/navigation_entry.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/origin_util.h" +#include "content/public/common/ssl_status.h" +#include "net/cert/x509_certificate.h" + +DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::AtomSecurityStateModelClient); + +using security_state::SecurityStateModel; + +namespace atom { + +namespace { + +SecurityStateModel::SecurityLevel GetSecurityLevelForSecurityStyle( + content::SecurityStyle style) { + switch (style) { + case content::SECURITY_STYLE_UNKNOWN: + return SecurityStateModel::NONE; + case content::SECURITY_STYLE_UNAUTHENTICATED: + return SecurityStateModel::NONE; + case content::SECURITY_STYLE_AUTHENTICATION_BROKEN: + return SecurityStateModel::SECURITY_ERROR; + case content::SECURITY_STYLE_WARNING: + return SecurityStateModel::SECURITY_WARNING; + case content::SECURITY_STYLE_AUTHENTICATED: + return SecurityStateModel::SECURE; + } + return SecurityStateModel::NONE; +} + +} // namespace + +AtomSecurityStateModelClient::AtomSecurityStateModelClient( + content::WebContents* web_contents) + : web_contents_(web_contents), + security_state_model_(new SecurityStateModel()) { + security_state_model_->SetClient(this); +} + +AtomSecurityStateModelClient::~AtomSecurityStateModelClient() { +} + +const SecurityStateModel::SecurityInfo& +AtomSecurityStateModelClient::GetSecurityInfo() const { + return security_state_model_->GetSecurityInfo(); +} + +bool AtomSecurityStateModelClient::RetrieveCert( + scoped_refptr* cert) { + content::NavigationEntry* entry = + web_contents_->GetController().GetVisibleEntry(); + if (!entry) + return false; + return content::CertStore::GetInstance()->RetrieveCert( + entry->GetSSL().cert_id, cert); +} + +bool AtomSecurityStateModelClient::UsedPolicyInstalledCertificate() { + return false; +} + +bool AtomSecurityStateModelClient::IsOriginSecure(const GURL& url) { + return content::IsOriginSecure(url); +} + +void AtomSecurityStateModelClient::GetVisibleSecurityState( + SecurityStateModel::VisibleSecurityState* state) { + content::NavigationEntry* entry = + web_contents_->GetController().GetVisibleEntry(); + if (!entry || + entry->GetSSL().security_style == content::SECURITY_STYLE_UNKNOWN) { + *state = SecurityStateModel::VisibleSecurityState(); + return; + } + + state->initialized = true; + state->url = entry->GetURL(); + const content::SSLStatus& ssl = entry->GetSSL(); + state->initial_security_level = + GetSecurityLevelForSecurityStyle(ssl.security_style); + state->cert_id = ssl.cert_id; + state->cert_status = ssl.cert_status; + state->connection_status = ssl.connection_status; + state->security_bits = ssl.security_bits; + state->sct_verify_statuses.clear(); + for (const auto& sct : ssl.signed_certificate_timestamp_ids) + state->sct_verify_statuses.push_back(sct.status); + state->displayed_mixed_content = + (ssl.content_status & content::SSLStatus::DISPLAYED_INSECURE_CONTENT) + ? true + : false; + state->ran_mixed_content = + (ssl.content_status & content::SSLStatus::RAN_INSECURE_CONTENT) ? true + : false; +} + +} // namespace atom diff --git a/atom/browser/atom_security_state_model_client.h b/atom/browser/atom_security_state_model_client.h new file mode 100644 index 000000000000..0dd3aa66c125 --- /dev/null +++ b/atom/browser/atom_security_state_model_client.h @@ -0,0 +1,42 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_ATOM_SECURITY_STATE_MODEL_CLIENT_H_ +#define ATOM_BROWSER_ATOM_SECURITY_STATE_MODEL_CLIENT_H_ + +#include "components/security_state/security_state_model.h" +#include "components/security_state/security_state_model_client.h" +#include "content/public/browser/web_contents_user_data.h" + +namespace atom { + +class AtomSecurityStateModelClient + : public security_state::SecurityStateModelClient, + public content::WebContentsUserData { + public: + ~AtomSecurityStateModelClient() override; + + const security_state::SecurityStateModel::SecurityInfo& + GetSecurityInfo() const; + + // security_state::SecurityStateModelClient: + void GetVisibleSecurityState( + security_state::SecurityStateModel::VisibleSecurityState* state) override; + bool RetrieveCert(scoped_refptr* cert) override; + bool UsedPolicyInstalledCertificate() override; + bool IsOriginSecure(const GURL& url) override; + + private: + explicit AtomSecurityStateModelClient(content::WebContents* web_contents); + friend class content::WebContentsUserData; + + content::WebContents* web_contents_; + std::unique_ptr security_state_model_; + + DISALLOW_COPY_AND_ASSIGN(AtomSecurityStateModelClient); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_ATOM_SECURITY_STATE_MODEL_CLIENT_H_ diff --git a/atom/browser/atom_speech_recognition_manager_delegate.h b/atom/browser/atom_speech_recognition_manager_delegate.h index ec31e227bafc..4c78e0eead10 100644 --- a/atom/browser/atom_speech_recognition_manager_delegate.h +++ b/atom/browser/atom_speech_recognition_manager_delegate.h @@ -7,6 +7,7 @@ #include +#include "base/macros.h" #include "content/public/browser/speech_recognition_event_listener.h" #include "content/public/browser/speech_recognition_manager_delegate.h" diff --git a/atom/browser/auto_updater.h b/atom/browser/auto_updater.h index 9e479d4220de..d13c6f0c3305 100644 --- a/atom/browser/auto_updater.h +++ b/atom/browser/auto_updater.h @@ -7,7 +7,8 @@ #include -#include "base/basictypes.h" +#include "base/macros.h" +#include "build/build_config.h" namespace base { class Time; diff --git a/atom/browser/auto_updater_mac.mm b/atom/browser/auto_updater_mac.mm index a55cdd281265..ab996dda90ab 100644 --- a/atom/browser/auto_updater_mac.mm +++ b/atom/browser/auto_updater_mac.mm @@ -22,6 +22,12 @@ SQRLUpdater* g_updater = nil; } // namespace +namespace { + +bool g_update_available = false; + +} + // static void AutoUpdater::SetFeedURL(const std::string& feed) { if (g_updater == nil) { @@ -69,6 +75,7 @@ void AutoUpdater::CheckForUpdates() { take:1] subscribeNext:^(SQRLDownloadedUpdate *downloadedUpdate) { if (downloadedUpdate) { + g_update_available = true; SQRLUpdate* update = downloadedUpdate.update; // There is a new update that has been downloaded. delegate->OnUpdateDownloaded( @@ -77,23 +84,32 @@ void AutoUpdater::CheckForUpdates() { base::Time::FromDoubleT(update.releaseDate.timeIntervalSince1970), base::SysNSStringToUTF8(update.updateURL.absoluteString)); } else { + g_update_available = false; // When the completed event is sent with no update, then we know there // is no update available. delegate->OnUpdateNotAvailable(); } } error:^(NSError *error) { - delegate->OnError(base::SysNSStringToUTF8( + NSString* failureString = error.localizedFailureReason ? [NSString stringWithFormat:@"%@: %@", - error.localizedDescription, error.localizedFailureReason])); + error.localizedDescription, + error.localizedFailureReason] : + [NSString stringWithString:error.localizedDescription]; + delegate->OnError(base::SysNSStringToUTF8(failureString)); }]; } void AutoUpdater::QuitAndInstall() { - [[g_updater relaunchToInstallUpdate] subscribeError:^(NSError* error) { - Delegate* delegate = AutoUpdater::GetDelegate(); + Delegate* delegate = AutoUpdater::GetDelegate(); + if (g_update_available) { + [[g_updater relaunchToInstallUpdate] subscribeError:^(NSError* error) { + if (delegate) + delegate->OnError(base::SysNSStringToUTF8(error.localizedDescription)); + }]; + } else { if (delegate) - delegate->OnError(base::SysNSStringToUTF8(error.localizedDescription)); - }]; + delegate->OnError("No update available, can't quit and install"); + } } } // namespace auto_updater diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 7a2c22ea9d23..093209ef7c47 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -9,12 +9,16 @@ #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/native_window.h" #include "atom/browser/window_list.h" +#include "base/files/file_util.h" #include "base/message_loop/message_loop.h" +#include "base/path_service.h" +#include "brightray/browser/brightray_paths.h" namespace atom { Browser::Browser() : is_quiting_(false), + is_exiting_(false), is_ready_(false), is_shutdown_(false) { WindowList::AddObserver(this); @@ -49,9 +53,12 @@ void Browser::Exit(int code) { // Message loop is not ready, quit directly. exit(code); } else { - // Prepare to quit when all windows have been closed.. + // Prepare to quit when all windows have been closed. is_quiting_ = true; + // Remember this caller so that we don't emit unrelated events. + is_exiting_ = true; + // Must destroy windows before quitting, otherwise bad things can happen. atom::WindowList* window_list = atom::WindowList::GetInstance(); if (window_list->size() == 0) { @@ -135,6 +142,11 @@ void Browser::WillFinishLaunching() { } void Browser::DidFinishLaunching() { + // Make sure the userData directory is created. + base::FilePath user_data; + if (PathService::Get(brightray::DIR_USER_DATA, &user_data)) + base::CreateDirectoryAndGetError(user_data, nullptr); + is_ready_ = true; FOR_EACH_OBSERVER(BrowserObserver, observers_, OnFinishLaunching()); } @@ -175,7 +187,9 @@ void Browser::OnWindowCloseCancelled(NativeWindow* window) { } void Browser::OnWindowAllClosed() { - if (is_quiting_) + if (is_exiting_) + Shutdown(); + else if (is_quiting_) NotifyAndShutdown(); else FOR_EACH_OBSERVER(BrowserObserver, observers_, OnWindowAllClosed()); diff --git a/atom/browser/browser.h b/atom/browser/browser.h index 2c44eaa107bc..18d0c97c93ac 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -8,12 +8,13 @@ #include #include -#include "base/basictypes.h" +#include "base/macros.h" #include "base/compiler_specific.h" #include "base/observer_list.h" #include "base/strings/string16.h" #include "atom/browser/browser_observer.h" #include "atom/browser/window_list_observer.h" +#include "native_mate/arguments.h" #if defined(OS_WIN) #include "base/files/file_path.h" @@ -76,6 +77,15 @@ class Browser : public WindowListObserver { // Set the application user model ID. void SetAppUserModelID(const base::string16& name); + // Remove the default protocol handler registry key + bool RemoveAsDefaultProtocolClient(const std::string& protocol); + + // Set as default handler for a protocol. + bool SetAsDefaultProtocolClient(const std::string& protocol); + + // Query the current state of default handler for a protocol. + bool IsDefaultProtocolClient(const std::string& protocol); + #if defined(OS_MACOSX) // Hide the application. void Hide(); @@ -83,6 +93,18 @@ class Browser : public WindowListObserver { // Show the application. void Show(); + // Creates an activity and sets it as the one currently in use. + void SetUserActivity(const std::string& type, + const base::DictionaryValue& user_info, + mate::Arguments* args); + + // Returns the type name of the current user activity. + std::string GetCurrentActivityType(); + + // Resumes an activity via hand-off. + bool ContinueUserActivity(const std::string& type, + const base::DictionaryValue& user_info); + // Bounce the dock icon. enum BounceType { BOUNCE_CRITICAL = 0, @@ -91,6 +113,9 @@ class Browser : public WindowListObserver { int DockBounce(BounceType type); void DockCancelBounce(int request_id); + // Bounce the Downloads stack. + void DockDownloadFinished(const std::string& filePath); + // Set/Get dock's badge text. void DockSetBadgeText(const std::string& label); std::string DockGetBadgeText(); @@ -177,10 +202,13 @@ class Browser : public WindowListObserver { // Observers of the browser. base::ObserverList observers_; + // Whether `app.exit()` has been called + bool is_exiting_; + // Whether "ready" event has been emitted. bool is_ready_; - // The browse is being shutdown. + // The browser is being shutdown. bool is_shutdown_; std::string version_override_; diff --git a/atom/browser/browser_linux.cc b/atom/browser/browser_linux.cc index 25cb9a0a2385..d994bb4109bb 100644 --- a/atom/browser/browser_linux.cc +++ b/atom/browser/browser_linux.cc @@ -34,6 +34,18 @@ void Browser::ClearRecentDocuments() { void Browser::SetAppUserModelID(const base::string16& name) { } +bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol) { + return false; +} + +bool Browser::SetAsDefaultProtocolClient(const std::string& protocol) { + return false; +} + +bool Browser::IsDefaultProtocolClient(const std::string& protocol) { + return false; +} + std::string Browser::GetExecutableFileVersion() const { return brightray::GetApplicationVersion(); } diff --git a/atom/browser/browser_mac.mm b/atom/browser/browser_mac.mm index bc0be22dcae8..4561eab8c950 100644 --- a/atom/browser/browser_mac.mm +++ b/atom/browser/browser_mac.mm @@ -6,11 +6,15 @@ #include "atom/browser/mac/atom_application.h" #include "atom/browser/mac/atom_application_delegate.h" +#include "atom/browser/mac/dict_util.h" #include "atom/browser/native_window.h" #include "atom/browser/window_list.h" +#include "base/mac/bundle_locations.h" #include "base/mac/foundation_util.h" #include "base/strings/sys_string_conversions.h" #include "brightray/common/application_info.h" +#include "net/base/mac/url_conversions.h" +#include "url/gurl.h" namespace atom { @@ -40,9 +44,103 @@ void Browser::ClearRecentDocuments() { [[NSDocumentController sharedDocumentController] clearRecentDocuments:nil]; } +bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol) { + NSString* identifier = [base::mac::MainBundle() bundleIdentifier]; + if (!identifier) + return false; + + if (!Browser::IsDefaultProtocolClient(protocol)) + return false; + + NSString* protocol_ns = [NSString stringWithUTF8String:protocol.c_str()]; + CFStringRef protocol_cf = base::mac::NSToCFCast(protocol_ns); + CFArrayRef bundleList = LSCopyAllHandlersForURLScheme(protocol_cf); + if (!bundleList) { + return false; + } + // On Mac OS X, we can't query the default, but the handlers list seems to put + // Apple's defaults first, so we'll use the first option that isn't our bundle + CFStringRef other = nil; + for (CFIndex i = 0; i < CFArrayGetCount(bundleList); i++) { + other = (CFStringRef)CFArrayGetValueAtIndex(bundleList, i); + if (![identifier isEqualToString: (__bridge NSString *)other]) { + break; + } + } + + OSStatus return_code = LSSetDefaultHandlerForURLScheme(protocol_cf, other); + return return_code == noErr; +} + +bool Browser::SetAsDefaultProtocolClient(const std::string& protocol) { + if (protocol.empty()) + return false; + + NSString* identifier = [base::mac::MainBundle() bundleIdentifier]; + if (!identifier) + return false; + + NSString* protocol_ns = [NSString stringWithUTF8String:protocol.c_str()]; + OSStatus return_code = + LSSetDefaultHandlerForURLScheme(base::mac::NSToCFCast(protocol_ns), + base::mac::NSToCFCast(identifier)); + return return_code == noErr; +} + +bool Browser::IsDefaultProtocolClient(const std::string& protocol) { + if (protocol.empty()) + return false; + + NSString* identifier = [base::mac::MainBundle() bundleIdentifier]; + if (!identifier) + return false; + + NSString* protocol_ns = [NSString stringWithUTF8String:protocol.c_str()]; + + CFStringRef bundle = + LSCopyDefaultHandlerForURLScheme(base::mac::NSToCFCast(protocol_ns)); + NSString* bundleId = static_cast( + base::mac::CFTypeRefToNSObjectAutorelease(bundle)); + if (!bundleId) + return false; + + // Ensure the comparison is case-insensitive + // as LS does not persist the case of the bundle id. + NSComparisonResult result = + [bundleId caseInsensitiveCompare:identifier]; + return result == NSOrderedSame; +} + void Browser::SetAppUserModelID(const base::string16& name) { } +void Browser::SetUserActivity(const std::string& type, + const base::DictionaryValue& user_info, + mate::Arguments* args) { + std::string url_string; + args->GetNext(&url_string); + + [[AtomApplication sharedApplication] + setCurrentActivity:base::SysUTF8ToNSString(type) + withUserInfo:DictionaryValueToNSDictionary(user_info) + withWebpageURL:net::NSURLWithGURL(GURL(url_string))]; +} + +std::string Browser::GetCurrentActivityType() { + NSUserActivity* userActivity = + [[AtomApplication sharedApplication] getCurrentActivity]; + return base::SysNSStringToUTF8(userActivity.activityType); +} + +bool Browser::ContinueUserActivity(const std::string& type, + const base::DictionaryValue& user_info) { + bool prevent_default = false; + FOR_EACH_OBSERVER(BrowserObserver, + observers_, + OnContinueUserActivity(&prevent_default, type, user_info)); + return prevent_default; +} + std::string Browser::GetExecutableFileVersion() const { return brightray::GetApplicationVersion(); } @@ -65,6 +163,12 @@ void Browser::DockSetBadgeText(const std::string& label) { [tile setBadgeLabel:base::SysUTF8ToNSString(label)]; } +void Browser::DockDownloadFinished(const std::string& filePath) { + [[NSDistributedNotificationCenter defaultCenter] + postNotificationName: @"com.apple.DownloadFileFinished" + object: base::SysUTF8ToNSString(filePath)]; +} + std::string Browser::DockGetBadgeText() { NSDockTile *tile = [[AtomApplication sharedApplication] dockTile]; return base::SysNSStringToUTF8([tile badgeLabel]); diff --git a/atom/browser/browser_observer.h b/atom/browser/browser_observer.h index f6d76bc13fb3..c8b0082beb7e 100644 --- a/atom/browser/browser_observer.h +++ b/atom/browser/browser_observer.h @@ -7,6 +7,12 @@ #include +#include "build/build_config.h" + +namespace base { +class DictionaryValue; +} + namespace atom { class LoginHandler; @@ -45,6 +51,14 @@ class BrowserObserver { // The browser requests HTTP login. virtual void OnLogin(LoginHandler* login_handler) {} +#if defined(OS_MACOSX) + // The browser wants to resume a user activity via handoff. (OS X only) + virtual void OnContinueUserActivity( + bool* prevent_default, + const std::string& type, + const base::DictionaryValue& user_info) {} +#endif + protected: virtual ~BrowserObserver() {} }; diff --git a/atom/browser/browser_win.cc b/atom/browser/browser_win.cc index fdf4bd8c3bbe..345e2bcf67ce 100644 --- a/atom/browser/browser_win.cc +++ b/atom/browser/browser_win.cc @@ -19,6 +19,7 @@ #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/win/win_util.h" +#include "base/win/registry.h" #include "base/win/windows_version.h" #include "atom/common/atom_version.h" @@ -125,6 +126,149 @@ void Browser::SetUserTasks(const std::vector& tasks) { destinations->CommitList(); } +bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol) { + if (protocol.empty()) + return false; + + base::FilePath path; + if (!PathService::Get(base::FILE_EXE, &path)) { + LOG(ERROR) << "Error getting app exe path"; + return false; + } + + // Main Registry Key + HKEY root = HKEY_CURRENT_USER; + std::string keyPathStr = "Software\\Classes\\" + protocol; + std::wstring keyPath = std::wstring(keyPathStr.begin(), keyPathStr.end()); + + // Command Key + std::string cmdPathStr = keyPathStr + "\\shell\\open\\command"; + std::wstring cmdPath = std::wstring(cmdPathStr.begin(), cmdPathStr.end()); + + base::win::RegKey key; + base::win::RegKey commandKey; + if (FAILED(key.Open(root, keyPath.c_str(), KEY_ALL_ACCESS))) + // Key doesn't even exist, we can confirm that it is not set + return true; + + if (FAILED(commandKey.Open(root, cmdPath.c_str(), KEY_ALL_ACCESS))) + // Key doesn't even exist, we can confirm that it is not set + return true; + + std::wstring keyVal; + if (FAILED(commandKey.ReadValue(L"", &keyVal))) + // Default value not set, we can confirm that it is not set + return true; + + std::wstring exePath(path.value()); + std::wstring exe = L"\"" + exePath + L"\" \"%1\""; + if (keyVal == exe) { + // Let's kill the key + if (FAILED(key.DeleteKey(L"shell"))) + return false; + + return true; + } else { + return true; + } +} + +bool Browser::SetAsDefaultProtocolClient(const std::string& protocol) { + // HKEY_CLASSES_ROOT + // $PROTOCOL + // (Default) = "URL:$NAME" + // URL Protocol = "" + // shell + // open + // command + // (Default) = "$COMMAND" "%1" + // + // However, the "HKEY_CLASSES_ROOT" key can only be written by the + // Administrator user. So, we instead write to "HKEY_CURRENT_USER\ + // Software\Classes", which is inherited by "HKEY_CLASSES_ROOT" + // anyway, and can be written by unprivileged users. + + if (protocol.empty()) + return false; + + base::FilePath path; + if (!PathService::Get(base::FILE_EXE, &path)) { + LOG(ERROR) << "Error getting app exe path"; + return false; + } + + // Main Registry Key + HKEY root = HKEY_CURRENT_USER; + std::string keyPathStr = "Software\\Classes\\" + protocol; + std::wstring keyPath = std::wstring(keyPathStr.begin(), keyPathStr.end()); + std::string urlDeclStr = "URL:" + protocol; + std::wstring urlDecl = std::wstring(urlDeclStr.begin(), urlDeclStr.end()); + + // Command Key + std::string cmdPathStr = keyPathStr + "\\shell\\open\\command"; + std::wstring cmdPath = std::wstring(cmdPathStr.begin(), cmdPathStr.end()); + + // Executable Path + std::wstring exePath(path.value()); + std::wstring exe = L"\"" + exePath + L"\" \"%1\""; + + // Write information to registry + base::win::RegKey key(root, keyPath.c_str(), KEY_ALL_ACCESS); + if (FAILED(key.WriteValue(L"URL Protocol", L"")) || + FAILED(key.WriteValue(L"", urlDecl.c_str()))) + return false; + + base::win::RegKey commandKey(root, cmdPath.c_str(), KEY_ALL_ACCESS); + if (FAILED(commandKey.WriteValue(L"", exe.c_str()))) + return false; + + return true; +} + +bool Browser::IsDefaultProtocolClient(const std::string& protocol) { + if (protocol.empty()) + return false; + + base::FilePath path; + if (!PathService::Get(base::FILE_EXE, &path)) { + LOG(ERROR) << "Error getting app exe path"; + return false; + } + + // Main Registry Key + HKEY root = HKEY_CURRENT_USER; + std::string keyPathStr = "Software\\Classes\\" + protocol; + std::wstring keyPath = std::wstring(keyPathStr.begin(), keyPathStr.end()); + + // Command Key + std::string cmdPathStr = keyPathStr + "\\shell\\open\\command"; + std::wstring cmdPath = std::wstring(cmdPathStr.begin(), cmdPathStr.end()); + + base::win::RegKey key; + base::win::RegKey commandKey; + if (FAILED(key.Open(root, keyPath.c_str(), KEY_ALL_ACCESS))) + // Key doesn't exist, we can confirm that it is not set + return false; + + if (FAILED(commandKey.Open(root, cmdPath.c_str(), KEY_ALL_ACCESS))) + // Key doesn't exist, we can confirm that it is not set + return false; + + std::wstring keyVal; + if (FAILED(commandKey.ReadValue(L"", &keyVal))) + // Default value not set, we can confirm that it is not set + return false; + + std::wstring exePath(path.value()); + std::wstring exe = L"\"" + exePath + L"\" \"%1\""; + if (keyVal == exe) { + // Default value is the same as current file path + return true; + } else { + return false; + } +} + PCWSTR Browser::GetAppUserModelID() { if (app_user_model_id_.empty()) { SetAppUserModelID(base::ReplaceStringPlaceholders( @@ -137,7 +281,7 @@ PCWSTR Browser::GetAppUserModelID() { std::string Browser::GetExecutableFileVersion() const { base::FilePath path; if (PathService::Get(base::FILE_EXE, &path)) { - scoped_ptr version_info( + std::unique_ptr version_info( FileVersionInfo::CreateFileVersionInfo(path)); return base::UTF16ToUTF8(version_info->product_version()); } @@ -148,7 +292,7 @@ std::string Browser::GetExecutableFileVersion() const { std::string Browser::GetExecutableFileProductName() const { base::FilePath path; if (PathService::Get(base::FILE_EXE, &path)) { - scoped_ptr version_info( + std::unique_ptr version_info( FileVersionInfo::CreateFileVersionInfo(path)); return base::UTF16ToUTF8(version_info->product_name()); } diff --git a/atom/browser/common_web_contents_delegate.cc b/atom/browser/common_web_contents_delegate.cc index 1fd6f782de58..c275d4aee74e 100644 --- a/atom/browser/common_web_contents_delegate.cc +++ b/atom/browser/common_web_contents_delegate.cc @@ -4,37 +4,42 @@ #include "atom/browser/common_web_contents_delegate.h" +#include #include #include +#include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_javascript_dialog_manager.h" +#include "atom/browser/atom_security_state_model_client.h" #include "atom/browser/native_window.h" #include "atom/browser/ui/file_dialog.h" #include "atom/browser/web_dialog_helper.h" +#include "atom/common/atom_constants.h" #include "base/files/file_util.h" +#include "components/prefs/pref_service.h" +#include "components/prefs/scoped_user_pref_update.h" #include "chrome/browser/printing/print_preview_message_handler.h" #include "chrome/browser/printing/print_view_manager_basic.h" #include "chrome/browser/ui/browser_dialogs.h" +#include "chrome/common/pref_names.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/child_process_security_policy.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host.h" +#include "content/public/browser/security_style_explanation.h" +#include "content/public/browser/security_style_explanations.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; +using security_state::SecurityStateModel; namespace atom { namespace { +const char kRootName[] = ""; + struct FileSystem { FileSystem() { } @@ -52,14 +57,14 @@ struct FileSystem { }; std::string RegisterFileSystem(content::WebContents* web_contents, - const base::FilePath& path, - std::string* registered_name) { + const base::FilePath& path) { auto isolated_context = storage::IsolatedContext::GetInstance(); + std::string root_name(kRootName); std::string file_system_id = isolated_context->RegisterFileSystemForPath( storage::kFileSystemTypeNativeLocal, std::string(), path, - registered_name); + &root_name); content::ChildProcessSecurityPolicy* policy = content::ChildProcessSecurityPolicy::GetInstance(); @@ -79,13 +84,12 @@ std::string RegisterFileSystem(content::WebContents* web_contents, FileSystem CreateFileSystemStruct( content::WebContents* web_contents, const std::string& file_system_id, - const std::string& registered_name, const std::string& file_system_path) { const GURL origin = web_contents->GetURL().GetOrigin(); std::string file_system_name = storage::GetIsolatedFileSystemName(origin, file_system_id); std::string root_url = storage::GetIsolatedFileSystemRootURIString( - origin, file_system_id, registered_name); + origin, file_system_id, kRootName); return FileSystem(file_system_name, root_url, file_system_path); } @@ -113,18 +117,66 @@ void AppendToFile(const base::FilePath& path, base::AppendToFile(path, content.data(), content.size()); } +PrefService* GetPrefService(content::WebContents* web_contents) { + auto context = web_contents->GetBrowserContext(); + return static_cast(context)->prefs(); +} + +std::set GetAddedFileSystemPaths( + content::WebContents* web_contents) { + auto pref_service = GetPrefService(web_contents); + const base::DictionaryValue* file_system_paths_value = + pref_service->GetDictionary(prefs::kDevToolsFileSystemPaths); + std::set result; + if (file_system_paths_value) { + base::DictionaryValue::Iterator it(*file_system_paths_value); + for (; !it.IsAtEnd(); it.Advance()) { + result.insert(it.key()); + } + } + return result; +} + +bool IsDevToolsFileSystemAdded( + content::WebContents* web_contents, + const std::string& file_system_path) { + auto file_system_paths = GetAddedFileSystemPaths(web_contents); + return file_system_paths.find(file_system_path) != file_system_paths.end(); +} + +content::SecurityStyle SecurityLevelToSecurityStyle( + SecurityStateModel::SecurityLevel security_level) { + switch (security_level) { + case SecurityStateModel::NONE: + return content::SECURITY_STYLE_UNAUTHENTICATED; + case SecurityStateModel::SECURITY_WARNING: + case SecurityStateModel::SECURITY_POLICY_WARNING: + return content::SECURITY_STYLE_WARNING; + case SecurityStateModel::EV_SECURE: + case SecurityStateModel::SECURE: + return content::SECURITY_STYLE_AUTHENTICATED; + case SecurityStateModel::SECURITY_ERROR: + return content::SECURITY_STYLE_AUTHENTICATION_BROKEN; + } + + return content::SECURITY_STYLE_UNKNOWN; +} + } // namespace CommonWebContentsDelegate::CommonWebContentsDelegate() : html_fullscreen_(false), - native_fullscreen_(false) { + native_fullscreen_(false), + devtools_file_system_indexer_(new DevToolsFileSystemIndexer) { } CommonWebContentsDelegate::~CommonWebContentsDelegate() { } void CommonWebContentsDelegate::InitWithWebContents( - content::WebContents* web_contents) { + content::WebContents* web_contents, + AtomBrowserContext* browser_context) { + browser_context_ = browser_context; web_contents->SetDelegate(this); printing::PrintViewManagerBasic::CreateForWebContents(web_contents); @@ -173,8 +225,6 @@ content::WebContents* CommonWebContentsDelegate::OpenURLFromTab( load_url_params.should_replace_current_entry = params.should_replace_current_entry; load_url_params.is_renderer_initiated = params.is_renderer_initiated; - load_url_params.transferred_global_request_id = - params.transferred_global_request_id; load_url_params.should_clear_history_list = true; source->GetController().LoadURLWithParams(load_url_params); @@ -223,7 +273,7 @@ void CommonWebContentsDelegate::EnterFullscreenModeForTab( return; SetHtmlApiFullscreen(true); owner_window_->NotifyWindowEnterHtmlFullScreen(); - source->GetRenderViewHost()->WasResized(); + source->GetRenderViewHost()->GetWidget()->WasResized(); } void CommonWebContentsDelegate::ExitFullscreenModeForTab( @@ -232,7 +282,7 @@ void CommonWebContentsDelegate::ExitFullscreenModeForTab( return; SetHtmlApiFullscreen(false); owner_window_->NotifyWindowLeaveHtmlFullScreen(); - source->GetRenderViewHost()->WasResized(); + source->GetRenderViewHost()->GetWidget()->WasResized(); } bool CommonWebContentsDelegate::IsFullscreenForTabOrPending( @@ -240,6 +290,90 @@ bool CommonWebContentsDelegate::IsFullscreenForTabOrPending( return html_fullscreen_; } +content::SecurityStyle CommonWebContentsDelegate::GetSecurityStyle( + content::WebContents* web_contents, + content::SecurityStyleExplanations* explanations) { + auto model_client = + AtomSecurityStateModelClient::FromWebContents(web_contents); + + const SecurityStateModel::SecurityInfo& security_info = + model_client->GetSecurityInfo(); + + const content::SecurityStyle security_style = + SecurityLevelToSecurityStyle(security_info.security_level); + + explanations->ran_insecure_content_style = + SecurityLevelToSecurityStyle( + SecurityStateModel::kRanInsecureContentLevel); + explanations->displayed_insecure_content_style = + SecurityLevelToSecurityStyle( + SecurityStateModel::kDisplayedInsecureContentLevel); + + explanations->scheme_is_cryptographic = security_info.scheme_is_cryptographic; + if (!security_info.scheme_is_cryptographic) + return security_style; + + if (security_info.sha1_deprecation_status == + SecurityStateModel::DEPRECATED_SHA1_MAJOR) { + explanations->broken_explanations.push_back( + content::SecurityStyleExplanation( + kSHA1Certificate, + kSHA1MajorDescription, + security_info.cert_id)); + } else if (security_info.sha1_deprecation_status == + SecurityStateModel::DEPRECATED_SHA1_MINOR) { + explanations->unauthenticated_explanations.push_back( + content::SecurityStyleExplanation( + kSHA1Certificate, + kSHA1MinorDescription, + security_info.cert_id)); + } + + explanations->ran_insecure_content = + security_info.mixed_content_status == + SecurityStateModel::RAN_MIXED_CONTENT || + security_info.mixed_content_status == + SecurityStateModel::RAN_AND_DISPLAYED_MIXED_CONTENT; + explanations->displayed_insecure_content = + security_info.mixed_content_status == + SecurityStateModel::DISPLAYED_MIXED_CONTENT || + security_info.mixed_content_status == + SecurityStateModel::RAN_AND_DISPLAYED_MIXED_CONTENT; + + if (net::IsCertStatusError(security_info.cert_status)) { + std::string error_string = net::ErrorToString( + net::MapCertStatusToNetError(security_info.cert_status)); + + content::SecurityStyleExplanation explanation( + kCertificateError, + "There are issues with the site's certificate chain " + error_string, + security_info.cert_id); + + if (net::IsCertStatusMinorError(security_info.cert_status)) + explanations->unauthenticated_explanations.push_back(explanation); + else + explanations->broken_explanations.push_back(explanation); + } else { + if (security_info.sha1_deprecation_status == + SecurityStateModel::NO_DEPRECATED_SHA1) { + explanations->secure_explanations.push_back( + content::SecurityStyleExplanation( + kValidCertificate, + kValidCertificateDescription, + security_info.cert_id)); + } + } + + if (security_info.is_secure_protocol_and_ciphersuite) { + explanations->secure_explanations.push_back( + content::SecurityStyleExplanation( + kSecureProtocol, + kSecureProtocolDescription)); + } + + return security_style; +} + void CommonWebContentsDelegate::DevToolsSaveToFile( const std::string& url, const std::string& content, bool save_as) { base::FilePath path; @@ -249,7 +383,7 @@ void CommonWebContentsDelegate::DevToolsSaveToFile( } else { file_dialog::Filters filters; base::FilePath default_path(base::FilePath::FromUTF8Unsafe(url)); - if (!file_dialog::ShowSaveDialog(owner_window(), url, default_path, + if (!file_dialog::ShowSaveDialog(owner_window(), url, "", default_path, filters, &path)) { base::StringValue url_value(url); web_contents_->CallClientFunction( @@ -279,6 +413,34 @@ void CommonWebContentsDelegate::DevToolsAppendToFile( base::Unretained(this), url)); } +void CommonWebContentsDelegate::DevToolsRequestFileSystems() { + auto file_system_paths = GetAddedFileSystemPaths(GetDevToolsWebContents()); + if (file_system_paths.empty()) { + base::ListValue empty_file_system_value; + web_contents_->CallClientFunction("DevToolsAPI.fileSystemsLoaded", + &empty_file_system_value, + nullptr, nullptr); + return; + } + + std::vector file_systems; + for (auto file_system_path : file_system_paths) { + base::FilePath path = base::FilePath::FromUTF8Unsafe(file_system_path); + std::string file_system_id = RegisterFileSystem(GetDevToolsWebContents(), + path); + FileSystem file_system = CreateFileSystemStruct(GetDevToolsWebContents(), + file_system_id, + file_system_path); + file_systems.push_back(file_system); + } + + base::ListValue file_system_value; + for (size_t i = 0; i < file_systems.size(); ++i) + file_system_value.Append(CreateFileSystemValue(file_systems[i])); + web_contents_->CallClientFunction("DevToolsAPI.fileSystemsLoaded", + &file_system_value, nullptr, nullptr); +} + void CommonWebContentsDelegate::DevToolsAddFileSystem( const base::FilePath& file_system_path) { base::FilePath path = file_system_path; @@ -287,39 +449,32 @@ void CommonWebContentsDelegate::DevToolsAddFileSystem( base::FilePath default_path; std::vector paths; int flag = file_dialog::FILE_DIALOG_OPEN_DIRECTORY; - if (!file_dialog::ShowOpenDialog(owner_window(), "", default_path, + if (!file_dialog::ShowOpenDialog(owner_window(), "", "", default_path, filters, flag, &paths)) return; path = paths[0]; } - std::string registered_name; std::string file_system_id = RegisterFileSystem(GetDevToolsWebContents(), - path, - ®istered_name); - - WorkspaceMap::iterator it = saved_paths_.find(file_system_id); - if (it != saved_paths_.end()) + path); + if (IsDevToolsFileSystemAdded(GetDevToolsWebContents(), path.AsUTF8Unsafe())) return; - saved_paths_[file_system_id] = path; - FileSystem file_system = CreateFileSystemStruct(GetDevToolsWebContents(), - file_system_id, - registered_name, - path.AsUTF8Unsafe()); + file_system_id, + path.AsUTF8Unsafe()); + std::unique_ptr file_system_value( + CreateFileSystemValue(file_system)); - scoped_ptr error_string_value( - new base::StringValue(std::string())); - scoped_ptr file_system_value; - if (!file_system.file_system_path.empty()) - file_system_value.reset(CreateFileSystemValue(file_system)); - web_contents_->CallClientFunction( - "DevToolsAPI.fileSystemAdded", - error_string_value.get(), - file_system_value.get(), - nullptr); + auto pref_service = GetPrefService(GetDevToolsWebContents()); + DictionaryPrefUpdate update(pref_service, prefs::kDevToolsFileSystemPaths); + update.Get()->SetWithoutPathExpansion( + path.AsUTF8Unsafe(), base::Value::CreateNullValue()); + + web_contents_->CallClientFunction("DevToolsAPI.fileSystemAdded", + file_system_value.get(), + nullptr, nullptr); } void CommonWebContentsDelegate::DevToolsRemoveFileSystem( @@ -327,21 +482,73 @@ void CommonWebContentsDelegate::DevToolsRemoveFileSystem( if (!web_contents_) return; + std::string path = file_system_path.AsUTF8Unsafe(); storage::IsolatedContext::GetInstance()-> RevokeFileSystemByPath(file_system_path); - for (auto it = saved_paths_.begin(); it != saved_paths_.end(); ++it) - if (it->second == file_system_path) { - saved_paths_.erase(it); - break; - } + auto pref_service = GetPrefService(GetDevToolsWebContents()); + DictionaryPrefUpdate update(pref_service, prefs::kDevToolsFileSystemPaths); + update.Get()->RemoveWithoutPathExpansion(path, nullptr); - base::StringValue file_system_path_value(file_system_path.AsUTF8Unsafe()); - web_contents_->CallClientFunction( - "DevToolsAPI.fileSystemRemoved", - &file_system_path_value, - nullptr, - nullptr); + base::StringValue file_system_path_value(path); + web_contents_->CallClientFunction("DevToolsAPI.fileSystemRemoved", + &file_system_path_value, + nullptr, nullptr); +} + +void CommonWebContentsDelegate::DevToolsIndexPath( + int request_id, + const std::string& file_system_path) { + if (!IsDevToolsFileSystemAdded(GetDevToolsWebContents(), file_system_path)) { + OnDevToolsIndexingDone(request_id, file_system_path); + return; + } + if (devtools_indexing_jobs_.count(request_id) != 0) + return; + devtools_indexing_jobs_[request_id] = + scoped_refptr( + devtools_file_system_indexer_->IndexPath( + file_system_path, + base::Bind( + &CommonWebContentsDelegate::OnDevToolsIndexingWorkCalculated, + base::Unretained(this), + request_id, + file_system_path), + base::Bind(&CommonWebContentsDelegate::OnDevToolsIndexingWorked, + base::Unretained(this), + request_id, + file_system_path), + base::Bind(&CommonWebContentsDelegate::OnDevToolsIndexingDone, + base::Unretained(this), + request_id, + file_system_path))); +} + +void CommonWebContentsDelegate::DevToolsStopIndexing(int request_id) { + auto it = devtools_indexing_jobs_.find(request_id); + if (it == devtools_indexing_jobs_.end()) + return; + it->second->Stop(); + devtools_indexing_jobs_.erase(it); +} + +void CommonWebContentsDelegate::DevToolsSearchInPath( + int request_id, + const std::string& file_system_path, + const std::string& query) { + if (!IsDevToolsFileSystemAdded(GetDevToolsWebContents(), file_system_path)) { + OnDevToolsSearchCompleted(request_id, + file_system_path, + std::vector()); + return; + } + devtools_file_system_indexer_->SearchInPath( + file_system_path, + query, + base::Bind(&CommonWebContentsDelegate::OnDevToolsSearchCompleted, + base::Unretained(this), + request_id, + file_system_path)); } void CommonWebContentsDelegate::OnDevToolsSaveToFile( @@ -360,22 +567,60 @@ 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(); +void CommonWebContentsDelegate::OnDevToolsIndexingWorkCalculated( + int request_id, + const std::string& file_system_path, + int total_work) { + base::FundamentalValue request_id_value(request_id); + base::StringValue file_system_path_value(file_system_path); + base::FundamentalValue total_work_value(total_work); + web_contents_->CallClientFunction("DevToolsAPI.indexingTotalWorkCalculated", + &request_id_value, + &file_system_path_value, + &total_work_value); } -#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); +void CommonWebContentsDelegate::OnDevToolsIndexingWorked( + int request_id, + const std::string& file_system_path, + int worked) { + base::FundamentalValue request_id_value(request_id); + base::StringValue file_system_path_value(file_system_path); + base::FundamentalValue worked_value(worked); + web_contents_->CallClientFunction("DevToolsAPI.indexingWorked", + &request_id_value, + &file_system_path_value, + &worked_value); +} + +void CommonWebContentsDelegate::OnDevToolsIndexingDone( + int request_id, + const std::string& file_system_path) { + devtools_indexing_jobs_.erase(request_id); + base::FundamentalValue request_id_value(request_id); + base::StringValue file_system_path_value(file_system_path); + web_contents_->CallClientFunction("DevToolsAPI.indexingDone", + &request_id_value, + &file_system_path_value, + nullptr); +} + +void CommonWebContentsDelegate::OnDevToolsSearchCompleted( + int request_id, + const std::string& file_system_path, + const std::vector& file_paths) { + base::ListValue file_paths_value; + for (std::vector::const_iterator it(file_paths.begin()); + it != file_paths.end(); ++it) { + file_paths_value.AppendString(*it); + } + base::FundamentalValue request_id_value(request_id); + base::StringValue file_system_path_value(file_system_path); + web_contents_->CallClientFunction("DevToolsAPI.searchCompleted", + &request_id_value, + &file_system_path_value, + &file_paths_value); } -#endif void CommonWebContentsDelegate::SetHtmlApiFullscreen(bool enter_fullscreen) { // Window is already in fullscreen mode, save the state. diff --git a/atom/browser/common_web_contents_delegate.h b/atom/browser/common_web_contents_delegate.h index 83ef2850bd96..1746d63b2082 100644 --- a/atom/browser/common_web_contents_delegate.h +++ b/atom/browser/common_web_contents_delegate.h @@ -12,10 +12,14 @@ #include "brightray/browser/inspectable_web_contents_impl.h" #include "brightray/browser/inspectable_web_contents_delegate.h" #include "brightray/browser/inspectable_web_contents_view_delegate.h" +#include "brightray/browser/devtools_file_system_indexer.h" #include "content/public/browser/web_contents_delegate.h" +using brightray::DevToolsFileSystemIndexer; + namespace atom { +class AtomBrowserContext; class AtomJavaScriptDialogManager; class NativeWindow; class WebDialogHelper; @@ -30,7 +34,8 @@ class CommonWebContentsDelegate // Creates a InspectableWebContents object and takes onwership of // |web_contents|. - void InitWithWebContents(content::WebContents* web_contents); + void InitWithWebContents(content::WebContents* web_contents, + AtomBrowserContext* browser_context); // Set the window as owner window. void SetOwnerWindow(NativeWindow* owner_window); @@ -76,6 +81,12 @@ class CommonWebContentsDelegate void ExitFullscreenModeForTab(content::WebContents* source) override; bool IsFullscreenForTabOrPending( const content::WebContents* source) const override; + content::SecurityStyle GetSecurityStyle( + content::WebContents* web_contents, + content::SecurityStyleExplanations* explanations) override; + void HandleKeyboardEvent( + content::WebContents* source, + const content::NativeWebKeyboardEvent& event) override; // brightray::InspectableWebContentsDelegate: void DevToolsSaveToFile(const std::string& url, @@ -83,9 +94,16 @@ class CommonWebContentsDelegate bool save_as) override; void DevToolsAppendToFile(const std::string& url, const std::string& content) override; + void DevToolsRequestFileSystems() override; void DevToolsAddFileSystem(const base::FilePath& path) override; void DevToolsRemoveFileSystem( const base::FilePath& file_system_path) override; + void DevToolsIndexPath(int request_id, + const std::string& file_system_path) override; + void DevToolsStopIndexing(int request_id) override; + void DevToolsSearchInPath(int request_id, + const std::string& file_system_path, + const std::string& query) override; // brightray::InspectableWebContentsViewDelegate: #if defined(TOOLKIT_VIEWS) @@ -103,6 +121,19 @@ class CommonWebContentsDelegate // Callback for when DevToolsAppendToFile has completed. void OnDevToolsAppendToFile(const std::string& url); + // + void OnDevToolsIndexingWorkCalculated(int request_id, + const std::string& file_system_path, + int total_work); + void OnDevToolsIndexingWorked(int request_id, + const std::string& file_system_path, + int worked); + void OnDevToolsIndexingDone(int request_id, + const std::string& file_system_path); + void OnDevToolsSearchCompleted(int request_id, + const std::string& file_system_path, + const std::vector& file_paths); + // Set fullscreen mode triggered by html api. void SetHtmlApiFullscreen(bool enter_fullscreen); @@ -115,23 +146,29 @@ class CommonWebContentsDelegate // Whether window is fullscreened by window api. bool native_fullscreen_; - scoped_ptr web_dialog_helper_; - scoped_ptr dialog_manager_; + std::unique_ptr web_dialog_helper_; + std::unique_ptr dialog_manager_; + scoped_refptr devtools_file_system_indexer_; + + // Make sure BrowserContext is alwasys destroyed after WebContents. + scoped_refptr browser_context_; // The stored InspectableWebContents object. // Notice that web_contents_ must be placed after dialog_manager_, so we can // make sure web_contents_ is destroyed before dialog_manager_, otherwise a // crash would happen. - scoped_ptr web_contents_; + std::unique_ptr web_contents_; // Maps url to file path, used by the file requests sent from devtools. typedef std::map PathsMap; PathsMap saved_files_; - // Maps file system id to file path, used by the file system requests - // sent from devtools. - typedef std::map WorkspaceMap; - WorkspaceMap saved_paths_; + // Map id to index job, used for file system indexing requests from devtools. + typedef std::map< + int, + scoped_refptr> + DevToolsIndexingJobsMap; + DevToolsIndexingJobsMap devtools_indexing_jobs_; DISALLOW_COPY_AND_ASSIGN(CommonWebContentsDelegate); }; diff --git a/atom/browser/common_web_contents_delegate_mac.mm b/atom/browser/common_web_contents_delegate_mac.mm new file mode 100644 index 000000000000..69117f190532 --- /dev/null +++ b/atom/browser/common_web_contents_delegate_mac.mm @@ -0,0 +1,39 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/common_web_contents_delegate.h" + +#import + +#include "content/public/browser/native_web_keyboard_event.h" +#include "ui/events/keycodes/keyboard_codes.h" + +namespace atom { + +void CommonWebContentsDelegate::HandleKeyboardEvent( + content::WebContents* source, + const content::NativeWebKeyboardEvent& event) { + if (event.skip_in_browser || + event.type == content::NativeWebKeyboardEvent::Char) + return; + + // Escape exits tabbed fullscreen mode. + if (event.windowsKeyCode == ui::VKEY_ESCAPE && is_html_fullscreen()) + ExitFullscreenModeForTab(source); + + BOOL handled = [[NSApp mainMenu] performKeyEquivalent:event.os_event]; + if (!handled && event.os_event.window) { + // Handle the cmd+~ shortcut. + if ((event.os_event.modifierFlags & NSCommandKeyMask) /* cmd */ && + (event.os_event.keyCode == 50 /* ~ */)) { + if (event.os_event.modifierFlags & NSShiftKeyMask) { + [NSApp sendAction:@selector(_cycleWindowsReversed:) to:nil from:nil]; + } else { + [NSApp sendAction:@selector(_cycleWindows:) to:nil from:nil]; + } + } + } +} + +} // namespace atom diff --git a/atom/browser/common_web_contents_delegate_views.cc b/atom/browser/common_web_contents_delegate_views.cc new file mode 100644 index 000000000000..d70884c45c2a --- /dev/null +++ b/atom/browser/common_web_contents_delegate_views.cc @@ -0,0 +1,45 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/common_web_contents_delegate.h" + +#include "atom/browser/native_window_views.h" +#include "base/strings/string_util.h" +#include "content/public/browser/native_web_keyboard_event.h" +#include "ui/events/keycodes/keyboard_codes.h" + +#if defined(USE_X11) +#include "atom/browser/browser.h" +#endif + +namespace atom { + +void CommonWebContentsDelegate::HandleKeyboardEvent( + content::WebContents* source, + const content::NativeWebKeyboardEvent& event) { + // Escape exits tabbed fullscreen mode. + if (event.windowsKeyCode == ui::VKEY_ESCAPE && is_html_fullscreen()) + ExitFullscreenModeForTab(source); + + // Let the NativeWindow handle other parts. + if (owner_window()) + owner_window()->HandleKeyboardEvent(source, event); +} + +gfx::ImageSkia CommonWebContentsDelegate::GetDevToolsWindowIcon() { + if (!owner_window()) + return gfx::ImageSkia(); + return static_cast(static_cast( + owner_window()))->GetWindowAppIcon(); +} + +#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 + +} // namespace atom diff --git a/atom/browser/default_app/default_app.js b/atom/browser/default_app/default_app.js deleted file mode 100644 index ebcca2a5d9a5..000000000000 --- a/atom/browser/default_app/default_app.js +++ /dev/null @@ -1,23 +0,0 @@ -const electron = require('electron'); -const app = electron.app; -const BrowserWindow = electron.BrowserWindow; - -var mainWindow = null; - -// Quit when all windows are closed. -app.on('window-all-closed', function() { - app.quit(); -}); - -exports.load = function(appUrl) { - app.on('ready', function() { - mainWindow = new BrowserWindow({ - width: 800, - height: 600, - autoHideMenuBar: true, - useContentSize: true, - }); - mainWindow.loadURL(appUrl); - mainWindow.focus(); - }); -}; diff --git a/atom/browser/default_app/index.html b/atom/browser/default_app/index.html deleted file mode 100644 index 80c347eff6dd..000000000000 --- a/atom/browser/default_app/index.html +++ /dev/null @@ -1,159 +0,0 @@ - - - Electron - - - - - -

- -

- -
- -

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

- - - -

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

- -

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

- -

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

- -
- Drag your app here to run it -
- -
- - - - diff --git a/atom/browser/default_app/main.js b/atom/browser/default_app/main.js deleted file mode 100644 index 6ff26e9ea679..000000000000 --- a/atom/browser/default_app/main.js +++ /dev/null @@ -1,312 +0,0 @@ -const electron = require('electron'); -const app = electron.app; -const dialog = electron.dialog; -const shell = electron.shell; -const Menu = electron.Menu; - -const fs = require('fs'); -const path = require('path'); -const url = require('url'); - -// Quit when all windows are closed and no other one is listening to this. -app.on('window-all-closed', function() { - if (app.listeners('window-all-closed').length == 1) - app.quit(); -}); - -// Parse command line options. -var argv = process.argv.slice(1); -var option = { file: null, help: null, version: null, webdriver: null, modules: [] }; -for (var i = 0; i < argv.length; i++) { - if (argv[i] == '--version' || argv[i] == '-v') { - option.version = true; - break; - } else if (argv[i].match(/^--app=/)) { - option.file = argv[i].split('=')[1]; - break; - } else if (argv[i] == '--help' || argv[i] == '-h') { - option.help = true; - break; - } else if (argv[i] == '--test-type=webdriver') { - option.webdriver = true; - } else if (argv[i] == '--require' || argv[i] == '-r') { - option.modules.push(argv[++i]); - continue; - } else if (argv[i][0] == '-') { - continue; - } else { - option.file = argv[i]; - break; - } -} - -// Create default menu. -app.once('ready', function() { - if (Menu.getApplicationMenu()) - return; - - var template = [ - { - label: 'Edit', - submenu: [ - { - label: 'Undo', - accelerator: 'CmdOrCtrl+Z', - role: 'undo' - }, - { - label: 'Redo', - accelerator: 'Shift+CmdOrCtrl+Z', - role: 'redo' - }, - { - type: 'separator' - }, - { - label: 'Cut', - accelerator: 'CmdOrCtrl+X', - role: 'cut' - }, - { - label: 'Copy', - accelerator: 'CmdOrCtrl+C', - role: 'copy' - }, - { - label: 'Paste', - accelerator: 'CmdOrCtrl+V', - role: 'paste' - }, - { - label: 'Select All', - accelerator: 'CmdOrCtrl+A', - role: 'selectall' - }, - ] - }, - { - label: 'View', - submenu: [ - { - label: 'Reload', - accelerator: 'CmdOrCtrl+R', - click: function(item, focusedWindow) { - if (focusedWindow) - focusedWindow.reload(); - } - }, - { - label: 'Toggle Full Screen', - accelerator: (function() { - if (process.platform == 'darwin') - return 'Ctrl+Command+F'; - else - return 'F11'; - })(), - click: function(item, focusedWindow) { - if (focusedWindow) - focusedWindow.setFullScreen(!focusedWindow.isFullScreen()); - } - }, - { - label: 'Toggle Developer Tools', - accelerator: (function() { - if (process.platform == 'darwin') - return 'Alt+Command+I'; - else - return 'Ctrl+Shift+I'; - })(), - click: function(item, focusedWindow) { - if (focusedWindow) - focusedWindow.toggleDevTools(); - } - }, - ] - }, - { - label: 'Window', - role: 'window', - submenu: [ - { - label: 'Minimize', - accelerator: 'CmdOrCtrl+M', - role: 'minimize' - }, - { - label: 'Close', - accelerator: 'CmdOrCtrl+W', - role: 'close' - }, - ] - }, - { - label: 'Help', - role: 'help', - submenu: [ - { - label: 'Learn More', - click: function() { - shell.openExternal('http://electron.atom.io'); - } - }, - { - label: 'Documentation', - click: function() { - shell.openExternal( - `https://github.com/atom/electron/tree/v${process.versions.electron}/docs#readme` - ); - } - }, - { - label: 'Community Discussions', - click: function() { - shell.openExternal('https://discuss.atom.io/c/electron'); - } - }, - { - label: 'Search Issues', - click: function() { - shell.openExternal('https://github.com/atom/electron/issues'); - } - } - ] - }, - ]; - - if (process.platform == 'darwin') { - template.unshift({ - label: 'Electron', - submenu: [ - { - label: 'About Electron', - role: 'about' - }, - { - type: 'separator' - }, - { - label: 'Services', - role: 'services', - submenu: [] - }, - { - type: 'separator' - }, - { - label: 'Hide Electron', - accelerator: 'Command+H', - role: 'hide' - }, - { - label: 'Hide Others', - accelerator: 'Command+Alt+H', - role: 'hideothers' - }, - { - label: 'Show All', - role: 'unhide' - }, - { - type: 'separator' - }, - { - label: 'Quit', - accelerator: 'Command+Q', - click: function() { app.quit(); } - }, - ] - }); - template[3].submenu.push( - { - type: 'separator' - }, - { - label: 'Bring All to Front', - role: 'front' - } - ); - } - - var menu = Menu.buildFromTemplate(template); - Menu.setApplicationMenu(menu); -}); - -if (option.modules.length > 0) { - require('module')._preloadModules(option.modules); -} - -function loadApplicationPackage(packagePath) { - try { - // Override app name and version. - packagePath = path.resolve(packagePath); - var packageJsonPath = path.join(packagePath, 'package.json'); - if (fs.existsSync(packageJsonPath)) { - var packageJson = JSON.parse(fs.readFileSync(packageJsonPath)); - if (packageJson.version) - app.setVersion(packageJson.version); - if (packageJson.productName) - app.setName(packageJson.productName); - else if (packageJson.name) - app.setName(packageJson.name); - app.setPath('userData', path.join(app.getPath('appData'), app.getName())); - app.setPath('userCache', path.join(app.getPath('cache'), app.getName())); - app.setAppPath(packagePath); - } - - // Run the app. - require('module')._load(packagePath, module, true); - } catch(e) { - if (e.code == 'MODULE_NOT_FOUND') { - app.focus(); - dialog.showErrorBox( - 'Error opening app', - 'The app provided is not a valid Electron app, please read the docs on how to write one:\n' + - `https://github.com/atom/electron/tree/v${process.versions.electron}/docs\n\n${e.toString()}` - ); - process.exit(1); - } else { - console.error('App threw an error when running', e); - throw e; - } - } -} - -function loadApplicationByUrl(appUrl) { - require('./default_app').load(appUrl); -} - -// Start the specified app if there is one specified in command line, otherwise -// start the default app. -if (option.file && !option.webdriver) { - var file = option.file; - var protocol = url.parse(file).protocol; - var extension = path.extname(file); - if (protocol === 'http:' || protocol === 'https:' || protocol === 'file:') { - loadApplicationByUrl(file); - } else if (extension === '.html' || extension === '.htm') { - loadApplicationByUrl('file://' + path.resolve(file)); - } else { - loadApplicationPackage(file); - } -} else if (option.version) { - console.log('v' + process.versions.electron); - process.exit(0); -} else if (option.help) { - var helpMessage = "Electron v" + process.versions.electron + " - Cross Platform Desktop Application Shell\n\n"; - helpMessage += "Usage: electron [options] [path]\n\n"; - helpMessage += "A path to an Electron application may be specified.\n"; - helpMessage += "The path must be one of the following:\n\n"; - helpMessage += " - index.js file.\n"; - helpMessage += " - Folder containing a package.json file.\n"; - helpMessage += " - Folder containing an index.js file.\n"; - helpMessage += " - .html/.htm file.\n"; - helpMessage += " - http://, https://, or file:// URL.\n"; - helpMessage += "\nOptions:\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 { - loadApplicationByUrl('file://' + __dirname + '/index.html'); -} diff --git a/atom/browser/javascript_environment.cc b/atom/browser/javascript_environment.cc index dc27cedee56f..970132b47c15 100644 --- a/atom/browser/javascript_environment.cc +++ b/atom/browser/javascript_environment.cc @@ -37,6 +37,7 @@ bool JavascriptEnvironment::Initialize() { v8::V8::SetFlagsFromString(js_flags.c_str(), js_flags.size()); gin::IsolateHolder::Initialize(gin::IsolateHolder::kNonStrictMode, + gin::IsolateHolder::kStableV8Extras, gin::ArrayBufferAllocator::SharedInstance()); return true; } diff --git a/atom/browser/javascript_environment.h b/atom/browser/javascript_environment.h index 20f1667c3b8e..07cd602cf00d 100644 --- a/atom/browser/javascript_environment.h +++ b/atom/browser/javascript_environment.h @@ -5,7 +5,7 @@ #ifndef ATOM_BROWSER_JAVASCRIPT_ENVIRONMENT_H_ #define ATOM_BROWSER_JAVASCRIPT_ENVIRONMENT_H_ -#include "base/basictypes.h" +#include "base/macros.h" #include "gin/public/isolate_holder.h" namespace atom { diff --git a/atom/browser/lib/bluetooth_chooser.cc b/atom/browser/lib/bluetooth_chooser.cc new file mode 100644 index 000000000000..2ed21bd333fd --- /dev/null +++ b/atom/browser/lib/bluetooth_chooser.cc @@ -0,0 +1,107 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/lib/bluetooth_chooser.h" +#include "atom/common/native_mate_converters/callback.h" +#include "atom/common/native_mate_converters/string16_converter.h" +#include "native_mate/dictionary.h" + +namespace mate { + +template<> +struct Converter { + static v8::Local ToV8( + v8::Isolate* isolate, const atom::BluetoothChooser::DeviceInfo& val) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.Set("deviceName", val.device_name); + dict.Set("deviceId", val.device_id); + return mate::ConvertToV8(isolate, dict); + } +}; + +} // namespace mate + +namespace atom { + +namespace { + +const int kMaxScanRetries = 5; + +void OnDeviceChosen( + const content::BluetoothChooser::EventHandler& handler, + const std::string& device_id) { + if (device_id.empty()) { + handler.Run(content::BluetoothChooser::Event::CANCELLED, device_id); + } else { + handler.Run(content::BluetoothChooser::Event::SELECTED, device_id); + } +} + +} // namespace + +BluetoothChooser::BluetoothChooser( + api::WebContents* contents, + const EventHandler& event_handler) + : api_web_contents_(contents), + event_handler_(event_handler), + num_retries_(0) { +} + +BluetoothChooser::~BluetoothChooser() { +} + +void BluetoothChooser::SetAdapterPresence(AdapterPresence presence) { + switch (presence) { + case AdapterPresence::ABSENT: + case AdapterPresence::POWERED_OFF: + event_handler_.Run(Event::CANCELLED, ""); + break; + case AdapterPresence::POWERED_ON: + break; + } +} + +void BluetoothChooser::ShowDiscoveryState(DiscoveryState state) { + switch (state) { + case DiscoveryState::FAILED_TO_START: + event_handler_.Run(Event::CANCELLED, ""); + break; + case DiscoveryState::IDLE: + if (device_list_.empty()) { + auto event = ++num_retries_ > kMaxScanRetries ? Event::CANCELLED + : Event::RESCAN; + event_handler_.Run(event, ""); + } else { + bool prevent_default = + api_web_contents_->Emit("select-bluetooth-device", + device_list_, + base::Bind(&OnDeviceChosen, + event_handler_)); + if (!prevent_default) { + auto device_id = device_list_[0].device_id; + event_handler_.Run(Event::SELECTED, device_id); + } + } + break; + case DiscoveryState::DISCOVERING: + break; + } +} + +void BluetoothChooser::AddDevice(const std::string& device_id, + const base::string16& device_name) { + DeviceInfo info = {device_id, device_name}; + device_list_.push_back(info); +} + +void BluetoothChooser::RemoveDevice(const std::string& device_id) { + for (auto it = device_list_.begin(); it != device_list_.end(); ++it) { + if (it->device_id == device_id) { + device_list_.erase(it); + return; + } + } +} + +} // namespace atom diff --git a/atom/browser/lib/bluetooth_chooser.h b/atom/browser/lib/bluetooth_chooser.h new file mode 100644 index 000000000000..615dfcb8c663 --- /dev/null +++ b/atom/browser/lib/bluetooth_chooser.h @@ -0,0 +1,45 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_LIB_BLUETOOTH_CHOOSER_H_ +#define ATOM_BROWSER_LIB_BLUETOOTH_CHOOSER_H_ + +#include +#include + +#include "atom/browser/api/atom_api_web_contents.h" +#include "content/public/browser/bluetooth_chooser.h" + +namespace atom { + +class BluetoothChooser : public content::BluetoothChooser { + public: + struct DeviceInfo { + std::string device_id; + base::string16 device_name; + }; + + explicit BluetoothChooser(api::WebContents* contents, + const EventHandler& handler); + ~BluetoothChooser() override; + + // content::BluetoothChooser: + void SetAdapterPresence(AdapterPresence presence) override; + void ShowDiscoveryState(DiscoveryState state) override; + void AddDevice(const std::string& device_id, + const base::string16& device_name) override; + void RemoveDevice(const std::string& device_id) override; + + private: + std::vector device_list_; + api::WebContents* api_web_contents_; + EventHandler event_handler_; + int num_retries_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothChooser); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_LIB_BLUETOOTH_CHOOSER_H_ diff --git a/atom/browser/lib/chrome-extension.js b/atom/browser/lib/chrome-extension.js deleted file mode 100644 index fcbb8c7f15fd..000000000000 --- a/atom/browser/lib/chrome-extension.js +++ /dev/null @@ -1,145 +0,0 @@ -const electron = require('electron'); -const app = electron.app; -const fs = require('fs'); -const path = require('path'); -const url = require('url'); - -// Mapping between hostname and file path. -var hostPathMap = {}; -var hostPathMapNextKey = 0; - -var getHostForPath = function(path) { - var key; - key = "extension-" + (++hostPathMapNextKey); - hostPathMap[key] = path; - return key; -}; - -var getPathForHost = function(host) { - return hostPathMap[host]; -}; - -// Cache extensionInfo. -var extensionInfoMap = {}; - -var getExtensionInfoFromPath = function(srcDirectory) { - var manifest, page; - manifest = JSON.parse(fs.readFileSync(path.join(srcDirectory, 'manifest.json'))); - if (extensionInfoMap[manifest.name] == null) { - - // We can not use 'file://' directly because all resources in the extension - // will be treated as relative to the root in Chrome. - page = url.format({ - protocol: 'chrome-extension', - slashes: true, - hostname: getHostForPath(srcDirectory), - pathname: manifest.devtools_page - }); - extensionInfoMap[manifest.name] = { - startPage: page, - name: manifest.name, - srcDirectory: srcDirectory, - exposeExperimentalAPIs: true - }; - return extensionInfoMap[manifest.name]; - } -}; - -// The loaded extensions cache and its persistent path. -var loadedExtensions = null; -var loadedExtensionsPath = null; - -app.on('will-quit', function() { - try { - loadedExtensions = Object.keys(extensionInfoMap).map(function(key) { - return extensionInfoMap[key].srcDirectory; - }); - if (loadedExtensions.length > 0) { - try { - fs.mkdirSync(path.dirname(loadedExtensionsPath)); - } catch (error) { - // Ignore error - } - fs.writeFileSync(loadedExtensionsPath, JSON.stringify(loadedExtensions)); - } else { - fs.unlinkSync(loadedExtensionsPath); - } - } catch (error) { - // Ignore error - } -}); - -// We can not use protocol or BrowserWindow until app is ready. -app.once('ready', function() { - var BrowserWindow, chromeExtensionHandler, i, init, len, protocol, srcDirectory; - protocol = electron.protocol, BrowserWindow = electron.BrowserWindow; - - // Load persisted extensions. - loadedExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions'); - try { - loadedExtensions = JSON.parse(fs.readFileSync(loadedExtensionsPath)); - if (!Array.isArray(loadedExtensions)) { - loadedExtensions = []; - } - - // Preheat the extensionInfo cache. - for (i = 0, len = loadedExtensions.length; i < len; i++) { - srcDirectory = loadedExtensions[i]; - getExtensionInfoFromPath(srcDirectory); - } - } catch (error) { - // Ignore error - } - - // The chrome-extension: can map a extension URL request to real file path. - chromeExtensionHandler = function(request, callback) { - var directory, parsed; - parsed = url.parse(request.url); - if (!(parsed.hostname && (parsed.path != null))) { - return callback(); - } - if (!/extension-\d+/.test(parsed.hostname)) { - return callback(); - } - directory = getPathForHost(parsed.hostname); - if (directory == null) { - return callback(); - } - return callback(path.join(directory, parsed.path)); - }; - protocol.registerFileProtocol('chrome-extension', chromeExtensionHandler, function(error) { - if (error) { - return console.error('Unable to register chrome-extension protocol'); - } - }); - BrowserWindow.prototype._loadDevToolsExtensions = function(extensionInfoArray) { - var ref; - return (ref = this.devToolsWebContents) != null ? ref.executeJavaScript("DevToolsAPI.addExtensions(" + (JSON.stringify(extensionInfoArray)) + ");") : void 0; - }; - BrowserWindow.addDevToolsExtension = function(srcDirectory) { - var extensionInfo, j, len1, ref, window; - extensionInfo = getExtensionInfoFromPath(srcDirectory); - if (extensionInfo) { - ref = BrowserWindow.getAllWindows(); - for (j = 0, len1 = ref.length; j < len1; j++) { - window = ref[j]; - window._loadDevToolsExtensions([extensionInfo]); - } - return extensionInfo.name; - } - }; - BrowserWindow.removeDevToolsExtension = function(name) { - return delete extensionInfoMap[name]; - }; - - // Load persistented extensions when devtools is opened. - init = BrowserWindow.prototype._init; - return BrowserWindow.prototype._init = function() { - init.call(this); - return this.on('devtools-opened', function() { - return this._loadDevToolsExtensions(Object.keys(extensionInfoMap).map(function(key) { - return extensionInfoMap[key]; - })); - }); - }; -}); diff --git a/atom/browser/lib/desktop-capturer.js b/atom/browser/lib/desktop-capturer.js deleted file mode 100644 index a4154f0c5b1c..000000000000 --- a/atom/browser/lib/desktop-capturer.js +++ /dev/null @@ -1,75 +0,0 @@ -const ipcMain = require('electron').ipcMain; -const desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer; - -var deepEqual = function(opt1, opt2) { - return JSON.stringify(opt1) === JSON.stringify(opt2); -}; - -// A queue for holding all requests from renderer process. -var requestsQueue = []; - -ipcMain.on('ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', function(event, captureWindow, captureScreen, thumbnailSize, id) { - var request; - request = { - id: id, - options: { - captureWindow: captureWindow, - captureScreen: captureScreen, - thumbnailSize: thumbnailSize - }, - webContents: event.sender - }; - requestsQueue.push(request); - if (requestsQueue.length === 1) { - desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize); - } - - // If the WebContents is destroyed before receiving result, just remove the - // reference from requestsQueue to make the module not send the result to it. - return event.sender.once('destroyed', function() { - return request.webContents = null; - }); -}); - -desktopCapturer.emit = function(event, name, sources) { - // Receiving sources result from main process, now send them back to renderer. - var captureScreen, captureWindow, handledRequest, i, len, ref, ref1, ref2, request, result, source, thumbnailSize, unhandledRequestsQueue; - handledRequest = requestsQueue.shift(0); - result = (function() { - var i, len, results; - results = []; - for (i = 0, len = sources.length; i < len; i++) { - source = sources[i]; - results.push({ - id: source.id, - name: source.name, - thumbnail: source.thumbnail.toDataUrl() - }); - } - return results; - })(); - if ((ref = handledRequest.webContents) != null) { - ref.send("ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_" + handledRequest.id, result); - } - - // Check the queue to see whether there is other same request. If has, handle - // it for reducing redunplicated `desktopCaptuer.startHandling` calls. - unhandledRequestsQueue = []; - for (i = 0, len = requestsQueue.length; i < len; i++) { - request = requestsQueue[i]; - if (deepEqual(handledRequest.options, request.options)) { - if ((ref1 = request.webContents) != null) { - ref1.send("ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_" + request.id, result); - } - } else { - unhandledRequestsQueue.push(request); - } - } - requestsQueue = unhandledRequestsQueue; - - // If the requestsQueue is not empty, start a new request handling. - if (requestsQueue.length > 0) { - ref2 = requestsQueue[0].options, captureWindow = ref2.captureWindow, captureScreen = ref2.captureScreen, thumbnailSize = ref2.thumbnailSize; - return desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize); - } -}; diff --git a/atom/browser/lib/guest-view-manager.js b/atom/browser/lib/guest-view-manager.js deleted file mode 100644 index b41b9b3a0f5c..000000000000 --- a/atom/browser/lib/guest-view-manager.js +++ /dev/null @@ -1,247 +0,0 @@ -const ipcMain = require('electron').ipcMain; -const webContents = require('electron').webContents; - -var slice = [].slice; - -// Doesn't exist in early initialization. -var webViewManager = null; - -var supportedWebViewEvents = [ - 'load-commit', - 'did-finish-load', - 'did-fail-load', - 'did-frame-finish-load', - 'did-start-loading', - 'did-stop-loading', - 'did-get-response-details', - 'did-get-redirect-request', - 'dom-ready', - 'console-message', - 'devtools-opened', - 'devtools-closed', - 'devtools-focused', - 'new-window', - 'will-navigate', - 'did-navigate', - 'did-navigate-in-page', - 'close', - 'crashed', - 'gpu-crashed', - 'plugin-crashed', - 'destroyed', - 'page-title-updated', - 'page-favicon-updated', - 'enter-html-full-screen', - 'leave-html-full-screen', - 'media-started-playing', - 'media-paused', - 'found-in-page', - 'did-change-theme-color' -]; - -var nextInstanceId = 0; -var guestInstances = {}; -var embedderElementsMap = {}; -var reverseEmbedderElementsMap = {}; - -// Moves the last element of array to the first one. -var moveLastToFirst = function(list) { - return list.unshift(list.pop()); -}; - -// Generate guestInstanceId. -var getNextInstanceId = function() { - return ++nextInstanceId; -}; - -// Create a new guest instance. -var createGuest = function(embedder, params) { - var destroy, destroyEvents, event, fn, guest, i, id, j, len, len1, listeners; - if (webViewManager == null) { - webViewManager = process.atomBinding('web_view_manager'); - } - id = getNextInstanceId(embedder); - guest = webContents.create({ - isGuest: true, - partition: params.partition, - embedder: embedder - }); - guestInstances[id] = { - guest: guest, - embedder: embedder - }; - - // Destroy guest when the embedder is gone or navigated. - destroyEvents = ['will-destroy', 'crashed', 'did-navigate']; - destroy = function() { - if (guestInstances[id] != null) { - return destroyGuest(embedder, id); - } - }; - for (i = 0, len = destroyEvents.length; i < len; i++) { - event = destroyEvents[i]; - embedder.once(event, destroy); - - // Users might also listen to the crashed event, so We must ensure the guest - // is destroyed before users' listener gets called. It is done by moving our - // listener to the first one in queue. - listeners = embedder._events[event]; - if (Array.isArray(listeners)) { - moveLastToFirst(listeners); - } - } - guest.once('destroyed', function() { - var j, len1, results; - results = []; - for (j = 0, len1 = destroyEvents.length; j < len1; j++) { - event = destroyEvents[j]; - results.push(embedder.removeListener(event, destroy)); - } - return results; - }); - - // Init guest web view after attached. - guest.once('did-attach', function() { - var opts; - params = this.attachParams; - delete this.attachParams; - this.viewInstanceId = params.instanceId; - this.setSize({ - normal: { - width: params.elementWidth, - height: params.elementHeight - }, - enableAutoSize: params.autosize, - min: { - width: params.minwidth, - height: params.minheight - }, - max: { - width: params.maxwidth, - height: params.maxheight - } - }); - if (params.src) { - opts = {}; - if (params.httpreferrer) { - opts.httpReferrer = params.httpreferrer; - } - if (params.useragent) { - opts.userAgent = params.useragent; - } - this.loadURL(params.src, opts); - } - if (params.allowtransparency != null) { - this.setAllowTransparency(params.allowtransparency); - } - return guest.allowPopups = params.allowpopups; - }); - - // Dispatch events to embedder. - fn = function(event) { - return guest.on(event, function() { - var args = 2 <= arguments.length ? slice.call(arguments, 1) : []; - return embedder.send.apply(embedder, ["ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-" + guest.viewInstanceId, event].concat(slice.call(args))); - }); - }; - for (j = 0, len1 = supportedWebViewEvents.length; j < len1; j++) { - event = supportedWebViewEvents[j]; - fn(event); - } - - // Dispatch guest's IPC messages to embedder. - guest.on('ipc-message-host', function(_, packed) { - var args, channel; - channel = packed[0], args = 2 <= packed.length ? slice.call(packed, 1) : []; - return embedder.send.apply(embedder, ["ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-" + guest.viewInstanceId, channel].concat(slice.call(args))); - }); - - // Autosize. - guest.on('size-changed', function() { - var args = 2 <= arguments.length ? slice.call(arguments, 1) : []; - return embedder.send.apply(embedder, ["ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-" + guest.viewInstanceId].concat(slice.call(args))); - }); - return id; -}; - -// Attach the guest to an element of embedder. -var attachGuest = function(embedder, elementInstanceId, guestInstanceId, params) { - var guest, key, oldGuestInstanceId, ref1, webPreferences; - guest = guestInstances[guestInstanceId].guest; - - // Destroy the old guest when attaching. - key = (embedder.getId()) + "-" + elementInstanceId; - oldGuestInstanceId = embedderElementsMap[key]; - if (oldGuestInstanceId != null) { - - // Reattachment to the same guest is not currently supported. - if (oldGuestInstanceId === guestInstanceId) { - return; - } - if (guestInstances[oldGuestInstanceId] == null) { - return; - } - destroyGuest(embedder, oldGuestInstanceId); - } - webPreferences = { - guestInstanceId: guestInstanceId, - nodeIntegration: (ref1 = params.nodeintegration) != null ? ref1 : false, - plugins: params.plugins, - webSecurity: !params.disablewebsecurity, - blinkFeatures: params.blinkfeatures - }; - if (params.preload) { - webPreferences.preloadURL = params.preload; - } - webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences); - guest.attachParams = params; - embedderElementsMap[key] = guestInstanceId; - return reverseEmbedderElementsMap[guestInstanceId] = key; -}; - -// Destroy an existing guest instance. -var destroyGuest = function(embedder, id) { - var key; - webViewManager.removeGuest(embedder, id); - guestInstances[id].guest.destroy(); - delete guestInstances[id]; - key = reverseEmbedderElementsMap[id]; - if (key != null) { - delete reverseEmbedderElementsMap[id]; - return delete embedderElementsMap[key]; - } -}; - -ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', function(event, params, requestId) { - return event.sender.send("ATOM_SHELL_RESPONSE_" + requestId, createGuest(event.sender, params)); -}); - -ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_ATTACH_GUEST', function(event, elementInstanceId, guestInstanceId, params) { - return attachGuest(event.sender, elementInstanceId, guestInstanceId, params); -}); - -ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', function(event, id) { - return destroyGuest(event.sender, id); -}); - -ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_SET_SIZE', function(event, id, params) { - var ref1; - return (ref1 = guestInstances[id]) != null ? ref1.guest.setSize(params) : void 0; -}); - -ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_SET_ALLOW_TRANSPARENCY', function(event, id, allowtransparency) { - var ref1; - return (ref1 = guestInstances[id]) != null ? ref1.guest.setAllowTransparency(allowtransparency) : void 0; -}); - -// Returns WebContents from its guest id. -exports.getGuest = function(id) { - var ref1; - return (ref1 = guestInstances[id]) != null ? ref1.guest : void 0; -}; - -// Returns the embedder of the guest. -exports.getEmbedder = function(id) { - var ref1; - return (ref1 = guestInstances[id]) != null ? ref1.embedder : void 0; -}; diff --git a/atom/browser/lib/guest-window-manager.js b/atom/browser/lib/guest-window-manager.js deleted file mode 100644 index 50c6a62eb0ca..000000000000 --- a/atom/browser/lib/guest-window-manager.js +++ /dev/null @@ -1,124 +0,0 @@ -const ipcMain = require('electron').ipcMain; -const BrowserWindow = require('electron').BrowserWindow; - -var hasProp = {}.hasOwnProperty; -var slice = [].slice; -var frameToGuest = {}; - -// Copy attribute of |parent| to |child| if it is not defined in |child|. -var mergeOptions = function(child, parent) { - var key, value; - for (key in parent) { - if (!hasProp.call(parent, key)) continue; - value = parent[key]; - if (!(key in child)) { - if (typeof value === 'object') { - child[key] = mergeOptions({}, value); - } else { - child[key] = value; - } - } - } - return child; -}; - -// Merge |options| with the |embedder|'s window's options. -var mergeBrowserWindowOptions = function(embedder, options) { - if (embedder.browserWindowOptions != null) { - - // Inherit the original options if it is a BrowserWindow. - mergeOptions(options, embedder.browserWindowOptions); - } else { - - // Or only inherit web-preferences if it is a webview. - if (options.webPreferences == null) { - options.webPreferences = {}; - } - mergeOptions(options.webPreferences, embedder.getWebPreferences()); - } - return options; -}; - -// Create a new guest created by |embedder| with |options|. -var createGuest = function(embedder, url, frameName, options) { - var closedByEmbedder, closedByUser, guest, guestId, ref1; - guest = frameToGuest[frameName]; - if (frameName && (guest != null)) { - guest.loadURL(url); - return guest.id; - } - - // Remember the embedder window's id. - if (options.webPreferences == null) { - options.webPreferences = {}; - } - options.webPreferences.openerId = (ref1 = BrowserWindow.fromWebContents(embedder)) != null ? ref1.id : void 0; - guest = new BrowserWindow(options); - guest.loadURL(url); - - // When |embedder| is destroyed we should also destroy attached guest, and if - // guest is closed by user then we should prevent |embedder| from double - // closing guest. - guestId = guest.id; - closedByEmbedder = function() { - guest.removeListener('closed', closedByUser); - return guest.destroy(); - }; - closedByUser = function() { - embedder.send("ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_" + guestId); - return embedder.removeListener('render-view-deleted', closedByEmbedder); - }; - embedder.once('render-view-deleted', closedByEmbedder); - guest.once('closed', closedByUser); - if (frameName) { - frameToGuest[frameName] = guest; - guest.frameName = frameName; - guest.once('closed', function() { - return delete frameToGuest[frameName]; - }); - } - return guest.id; -}; - -// Routed window.open messages. -ipcMain.on('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', function() { - var args, event, frameName, options, url; - event = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; - url = args[0], frameName = args[1], options = args[2]; - options = mergeBrowserWindowOptions(event.sender, options); - event.sender.emit('new-window', event, url, frameName, 'new-window', options); - if ((event.sender.isGuest() && !event.sender.allowPopups) || event.defaultPrevented) { - return event.returnValue = null; - } else { - return event.returnValue = createGuest(event.sender, url, frameName, options); - } -}); - -ipcMain.on('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', function(event, guestId) { - var ref1; - return (ref1 = BrowserWindow.fromId(guestId)) != null ? ref1.destroy() : void 0; -}); - -ipcMain.on('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function() { - var args, guestId, method, ref1; - guestId = arguments[1], method = arguments[2], args = 4 <= arguments.length ? slice.call(arguments, 3) : []; - return (ref1 = BrowserWindow.fromId(guestId)) != null ? ref1[method].apply(ref1, args) : void 0; -}); - -ipcMain.on('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function(event, guestId, message, targetOrigin, sourceOrigin) { - var guestContents, ref1, ref2, sourceId; - sourceId = (ref1 = BrowserWindow.fromWebContents(event.sender)) != null ? ref1.id : void 0; - if (sourceId == null) { - return; - } - guestContents = (ref2 = BrowserWindow.fromId(guestId)) != null ? ref2.webContents : void 0; - if ((guestContents != null ? guestContents.getURL().indexOf(targetOrigin) : void 0) === 0 || targetOrigin === '*') { - return guestContents != null ? guestContents.send('ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin) : void 0; - } -}); - -ipcMain.on('ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', function() { - var args, guestId, method, ref1, ref2; - guestId = arguments[1], method = arguments[2], args = 4 <= arguments.length ? slice.call(arguments, 3) : []; - return (ref1 = BrowserWindow.fromId(guestId)) != null ? (ref2 = ref1.webContents) != null ? ref2[method].apply(ref2, args) : void 0 : void 0; -}); diff --git a/atom/browser/lib/init.js b/atom/browser/lib/init.js deleted file mode 100644 index 4769faee0a39..000000000000 --- a/atom/browser/lib/init.js +++ /dev/null @@ -1,158 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const util = require('util'); -const Module = require('module'); -const v8 = require('v8'); - -var slice = [].slice; - -// We modified the original process.argv to let node.js load the atom.js, -// we need to restore it here. -process.argv.splice(1, 1); - -// Clear search paths. -require(path.resolve(__dirname, '..', '..', 'common', 'lib', 'reset-search-paths')); - -// Import common settings. -require(path.resolve(__dirname, '..', '..', 'common', 'lib', 'init')); - -var globalPaths = Module.globalPaths; - -if (!process.env.ELECTRON_HIDE_INTERNAL_MODULES) { - globalPaths.push(path.resolve(__dirname, '..', 'api', 'lib')); -} - -// Expose public APIs. -globalPaths.push(path.resolve(__dirname, '..', 'api', 'lib', 'exports')); - -if (process.platform === 'win32') { - // Redirect node's console to use our own implementations, since node can not - // handle console output when running as GUI program. - var consoleLog = function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return process.log(util.format.apply(util, args) + "\n"); - }; - var streamWrite = function(chunk, encoding, callback) { - if (Buffer.isBuffer(chunk)) { - chunk = chunk.toString(encoding); - } - process.log(chunk); - if (callback) { - callback(); - } - return true; - }; - console.log = console.error = console.warn = consoleLog; - process.stdout.write = process.stderr.write = streamWrite; - - // Always returns EOF for stdin stream. - var Readable = require('stream').Readable; - var stdin = new Readable; - stdin.push(null); - process.__defineGetter__('stdin', function() { - return stdin; - }); -} - -// Don't quit on fatal error. -process.on('uncaughtException', function(error) { - - // Do nothing if the user has a custom uncaught exception handler. - var dialog, message, ref, stack; - if (process.listeners('uncaughtException').length > 1) { - return; - } - - // Show error in GUI. - dialog = require('electron').dialog; - stack = (ref = error.stack) != null ? ref : error.name + ": " + error.message; - message = "Uncaught Exception:\n" + stack; - return dialog.showErrorBox('A JavaScript error occurred in the main process', message); -}); - -// Emit 'exit' event on quit. -var app = require('electron').app; - -app.on('quit', function(event, exitCode) { - return process.emit('exit', exitCode); -}); - -// Map process.exit to app.exit, which quits gracefully. -process.exit = app.exit; - -// Load the RPC server. -require('./rpc-server'); - -// Load the guest view manager. -require('./guest-view-manager'); - -require('./guest-window-manager'); - -// Now we try to load app's package.json. -var packageJson = null; -var searchPaths = ['app', 'app.asar', 'default_app']; -var i, len, packagePath; -for (i = 0, len = searchPaths.length; i < len; i++) { - packagePath = searchPaths[i]; - try { - packagePath = path.join(process.resourcesPath, packagePath); - packageJson = JSON.parse(fs.readFileSync(path.join(packagePath, 'package.json'))); - break; - } catch (error) { - continue; - } -} - -if (packageJson == null) { - process.nextTick(function() { - return process.exit(1); - }); - throw new Error("Unable to find a valid app"); -} - -// Set application's version. -if (packageJson.version != null) { - app.setVersion(packageJson.version); -} - -// Set application's name. -if (packageJson.productName != null) { - app.setName(packageJson.productName); -} else if (packageJson.name != null) { - app.setName(packageJson.name); -} - -// Set application's desktop name. -if (packageJson.desktopName != null) { - app.setDesktopName(packageJson.desktopName); -} else { - app.setDesktopName((app.getName()) + ".desktop"); -} - -// Set v8 flags -if (packageJson.v8Flags != null) { - v8.setFlagsFromString(packageJson.v8Flags); -} - -// Chrome 42 disables NPAPI plugins by default, reenable them here -app.commandLine.appendSwitch('enable-npapi'); - -// Set the user path according to application's name. -app.setPath('userData', path.join(app.getPath('appData'), app.getName())); - -app.setPath('userCache', path.join(app.getPath('cache'), app.getName())); - -app.setAppPath(packagePath); - -// Load the chrome extension support. -require('./chrome-extension'); - -// Load internal desktop-capturer module. -require('./desktop-capturer'); - -// Set main startup script of the app. -var mainStartupScript = packageJson.main || 'index.js'; - -// Finally load app's main.js and transfer control to C++. -Module._load(path.join(packagePath, mainStartupScript), Module, true); diff --git a/atom/browser/lib/rpc-server.js b/atom/browser/lib/rpc-server.js deleted file mode 100644 index 20ee8fbdeae1..000000000000 --- a/atom/browser/lib/rpc-server.js +++ /dev/null @@ -1,365 +0,0 @@ -'use strict'; - -const electron = require('electron'); -const ipcMain = electron.ipcMain; -const objectsRegistry = require('./objects-registry'); -const v8Util = process.atomBinding('v8_util'); -const IDWeakMap = process.atomBinding('id_weak_map').IDWeakMap; - -// The internal properties of Function. -const FUNCTION_PROPERTIES = [ - 'length', 'name', 'arguments', 'caller', 'prototype', -]; - -// The remote functions in renderer processes. -// (webContentsId) => {id: Function} -let rendererFunctions = {}; - -// Return the description of object's members: -let getObjectMemebers = function(object) { - let names = Object.getOwnPropertyNames(object); - // For Function, we should not override following properties even though they - // are "own" properties. - if (typeof object === 'function') { - names = names.filter((name) => { - return !FUNCTION_PROPERTIES.includes(name); - }); - } - // Map properties to descriptors. - return names.map((name) => { - let descriptor = Object.getOwnPropertyDescriptor(object, name); - let member = {name, enumerable: descriptor.enumerable, writable: false}; - if (descriptor.get === undefined && typeof object[name] === 'function') { - member.type = 'method'; - } else { - if (descriptor.set || descriptor.writable) - member.writable = true; - member.type = 'get'; - } - return member; - }); -}; - -// Return the description of object's prototype. -let getObjectPrototype = function(object) { - let proto = Object.getPrototypeOf(object); - if (proto === null || proto === Object.prototype) - return null; - return { - members: getObjectMemebers(proto), - proto: getObjectPrototype(proto), - }; -}; - -// Convert a real value into meta data. -var valueToMeta = function(sender, value, optimizeSimpleObject) { - var el, i, len, meta; - if (optimizeSimpleObject == null) { - optimizeSimpleObject = false; - } - meta = { - type: typeof value - }; - if (Buffer.isBuffer(value)) { - meta.type = 'buffer'; - } - if (value === null) { - meta.type = 'value'; - } - if (Array.isArray(value)) { - meta.type = 'array'; - } - if (value instanceof Error) { - meta.type = 'error'; - } - if (value instanceof Date) { - meta.type = 'date'; - } - if ((value != null ? value.constructor.name : void 0) === 'Promise') { - meta.type = 'promise'; - } - - // Treat simple objects as value. - if (optimizeSimpleObject && meta.type === 'object' && v8Util.getHiddenValue(value, 'simple')) { - meta.type = 'value'; - } - - // Treat the arguments object as array. - if (meta.type === 'object' && (value.hasOwnProperty('callee')) && (value.length != null)) { - meta.type = 'array'; - } - if (meta.type === 'array') { - meta.members = []; - for (i = 0, len = value.length; i < len; i++) { - el = value[i]; - meta.members.push(valueToMeta(sender, el)); - } - } else if (meta.type === 'object' || meta.type === 'function') { - meta.name = value.constructor.name; - - // Reference the original value if it's an object, because when it's - // passed to renderer we would assume the renderer keeps a reference of - // it. - meta.id = objectsRegistry.add(sender, value); - meta.members = getObjectMemebers(value); - meta.proto = getObjectPrototype(value); - } else if (meta.type === 'buffer') { - meta.value = Array.prototype.slice.call(value, 0); - } else if (meta.type === 'promise') { - meta.then = valueToMeta(sender, value.then.bind(value)); - } else if (meta.type === 'error') { - meta.members = plainObjectToMeta(value); - - // Error.name is not part of own properties. - meta.members.push({ - name: 'name', - value: value.name - }); - } else if (meta.type === 'date') { - meta.value = value.getTime(); - } else { - meta.type = 'value'; - meta.value = value; - } - return meta; -}; - -// Convert object to meta by value. -var plainObjectToMeta = function(obj) { - return Object.getOwnPropertyNames(obj).map(function(name) { - return { - name: name, - value: obj[name] - }; - }); -}; - -// Convert Error into meta data. -var exceptionToMeta = function(error) { - return { - type: 'exception', - message: error.message, - stack: error.stack || error - }; -}; - -// Convert array of meta data from renderer into array of real values. -var unwrapArgs = function(sender, args) { - var metaToValue; - metaToValue = function(meta) { - var i, len, member, ref, returnValue; - switch (meta.type) { - case 'value': - return meta.value; - case 'remote-object': - return objectsRegistry.get(meta.id); - case 'array': - return unwrapArgs(sender, meta.value); - case 'buffer': - return new Buffer(meta.value); - case 'date': - return new Date(meta.value); - case 'promise': - return Promise.resolve({ - then: metaToValue(meta.then) - }); - case 'object': { - let ret = {}; - Object.defineProperty(ret.constructor, 'name', { value: meta.name }); - - ref = meta.members; - for (i = 0, len = ref.length; i < len; i++) { - member = ref[i]; - ret[member.name] = metaToValue(member.value); - } - return ret; - } - case 'function-with-return-value': - returnValue = metaToValue(meta.value); - return function() { - return returnValue; - }; - case 'function': { - // Cache the callbacks in renderer. - let webContentsId = sender.getId(); - let callbacks = rendererFunctions[webContentsId]; - if (!callbacks) { - callbacks = rendererFunctions[webContentsId] = new IDWeakMap; - sender.once('render-view-deleted', function(event, id) { - callbacks.clear(); - delete rendererFunctions[id]; - }); - } - - if (callbacks.has(meta.id)) - return callbacks.get(meta.id); - - let callIntoRenderer = function(...args) { - if ((webContentsId in rendererFunctions) && !sender.isDestroyed()) - sender.send('ATOM_RENDERER_CALLBACK', meta.id, valueToMeta(sender, args)); - else - throw new Error(`Attempting to call a function in a renderer window that has been closed or released. Function provided here: ${meta.location}.`); - }; - v8Util.setDestructor(callIntoRenderer, function() { - if ((webContentsId in rendererFunctions) && !sender.isDestroyed()) - sender.send('ATOM_RENDERER_RELEASE_CALLBACK', meta.id); - }); - callbacks.set(meta.id, callIntoRenderer); - return callIntoRenderer; - } - default: - throw new TypeError("Unknown type: " + meta.type); - } - }; - return args.map(metaToValue); -}; - -// Call a function and send reply asynchronously if it's a an asynchronous -// style function and the caller didn't pass a callback. -var callFunction = function(event, func, caller, args) { - var funcMarkedAsync, funcName, funcPassedCallback, ref, ret; - funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous'); - funcPassedCallback = typeof args[args.length - 1] === 'function'; - try { - if (funcMarkedAsync && !funcPassedCallback) { - args.push(function(ret) { - return event.returnValue = valueToMeta(event.sender, ret, true); - }); - return func.apply(caller, args); - } else { - ret = func.apply(caller, args); - return event.returnValue = valueToMeta(event.sender, ret, true); - } - } catch (error) { - // Catch functions thrown further down in function invocation and wrap - // them with the function name so it's easier to trace things like - // `Error processing argument -1.` - funcName = (ref = func.name) != null ? ref : "anonymous"; - throw new Error("Could not call remote function `" + funcName + "`. Check that the function signature is correct. Underlying error: " + error.message); - } -}; - -ipcMain.on('ATOM_BROWSER_REQUIRE', function(event, module) { - try { - return event.returnValue = valueToMeta(event.sender, process.mainModule.require(module)); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_GET_BUILTIN', function(event, module) { - try { - return event.returnValue = valueToMeta(event.sender, electron[module]); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_GLOBAL', function(event, name) { - try { - return event.returnValue = valueToMeta(event.sender, global[name]); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_CURRENT_WINDOW', function(event) { - try { - return event.returnValue = valueToMeta(event.sender, event.sender.getOwnerBrowserWindow()); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_CURRENT_WEB_CONTENTS', function(event) { - return event.returnValue = valueToMeta(event.sender, event.sender); -}); - -ipcMain.on('ATOM_BROWSER_CONSTRUCTOR', function(event, id, args) { - try { - args = unwrapArgs(event.sender, args); - let constructor = objectsRegistry.get(id); - - // Call new with array of arguments. - // http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible - let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args))); - return event.returnValue = valueToMeta(event.sender, obj); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_FUNCTION_CALL', function(event, id, args) { - try { - args = unwrapArgs(event.sender, args); - let func = objectsRegistry.get(id); - return callFunction(event, func, global, args); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_MEMBER_CONSTRUCTOR', function(event, id, method, args) { - try { - args = unwrapArgs(event.sender, args); - let constructor = objectsRegistry.get(id)[method]; - - // Call new with array of arguments. - let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args))); - return event.returnValue = valueToMeta(event.sender, obj); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_MEMBER_CALL', function(event, id, method, args) { - try { - args = unwrapArgs(event.sender, args); - let obj = objectsRegistry.get(id); - return callFunction(event, obj[method], obj, args); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_MEMBER_SET', function(event, id, name, value) { - try { - let obj = objectsRegistry.get(id); - obj[name] = value; - return event.returnValue = null; - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_MEMBER_GET', function(event, id, name) { - try { - let obj = objectsRegistry.get(id); - return event.returnValue = valueToMeta(event.sender, obj[name]); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_DEREFERENCE', function(event, id) { - return objectsRegistry.remove(event.sender.getId(), id); -}); - -ipcMain.on('ATOM_BROWSER_GUEST_WEB_CONTENTS', function(event, guestInstanceId) { - try { - let guestViewManager = require('./guest-view-manager'); - return event.returnValue = valueToMeta(event.sender, guestViewManager.getGuest(guestInstanceId)); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function(event, guestInstanceId, method, ...args) { - try { - let guestViewManager = require('./guest-view-manager'); - let guest = guestViewManager.getGuest(guestInstanceId); - return guest[method].apply(guest, args); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); diff --git a/atom/browser/login_handler.cc b/atom/browser/login_handler.cc index 7a1a77cc2b13..1a6c6947b617 100644 --- a/atom/browser/login_handler.cc +++ b/atom/browser/login_handler.cc @@ -37,10 +37,11 @@ LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info, 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))); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&Browser::RequestLogin, + base::Unretained(Browser::Get()), + base::RetainedRef(make_scoped_refptr(this)))); } LoginHandler::~LoginHandler() { diff --git a/atom/browser/mac/atom_application.h b/atom/browser/mac/atom_application.h index 9fafb054b09a..73baf9e7d980 100644 --- a/atom/browser/mac/atom_application.h +++ b/atom/browser/mac/atom_application.h @@ -3,11 +3,13 @@ // found in the LICENSE file. #import "base/mac/scoped_sending_event.h" +#import "base/mac/scoped_nsobject.h" @interface AtomApplication : NSApplication { @private BOOL handlingSendEvent_; + base::scoped_nsobject currentActivity_; } + (AtomApplication*)sharedApplication; @@ -18,4 +20,9 @@ // CrAppControlProtocol: - (void)setHandlingSendEvent:(BOOL)handlingSendEvent; +- (NSUserActivity*)getCurrentActivity; +- (void)setCurrentActivity:(NSString*)type + withUserInfo:(NSDictionary*)userInfo + withWebpageURL:(NSURL*)webpageURL; + @end diff --git a/atom/browser/mac/atom_application.mm b/atom/browser/mac/atom_application.mm index cc9c6accc83d..ea64af3a04d2 100644 --- a/atom/browser/mac/atom_application.mm +++ b/atom/browser/mac/atom_application.mm @@ -28,6 +28,20 @@ handlingSendEvent_ = handlingSendEvent; } +- (void)setCurrentActivity:(NSString*)type + withUserInfo:(NSDictionary*)userInfo + withWebpageURL:(NSURL*)webpageURL { + currentActivity_ = base::scoped_nsobject( + [[NSUserActivity alloc] initWithActivityType:type]); + [currentActivity_ setUserInfo:userInfo]; + [currentActivity_ setWebpageURL:webpageURL]; + [currentActivity_ becomeCurrent]; +} + +- (NSUserActivity*)getCurrentActivity { + return currentActivity_.get(); +} + - (void)awakeFromNib { [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self diff --git a/atom/browser/mac/atom_application_delegate.mm b/atom/browser/mac/atom_application_delegate.mm index 7662162ab618..84caae9d5ee3 100644 --- a/atom/browser/mac/atom_application_delegate.mm +++ b/atom/browser/mac/atom_application_delegate.mm @@ -6,7 +6,9 @@ #import "atom/browser/mac/atom_application.h" #include "atom/browser/browser.h" +#include "atom/browser/mac/dict_util.h" #include "base/strings/sys_string_conversions.h" +#include "base/values.h" @implementation AtomApplicationDelegate @@ -59,4 +61,17 @@ return flag; } +- (BOOL)application:(NSApplication*)sender +continueUserActivity:(NSUserActivity*)userActivity + restorationHandler:(void (^)(NSArray*restorableObjects))restorationHandler { + std::string activity_type(base::SysNSStringToUTF8(userActivity.activityType)); + std::unique_ptr user_info = + atom::NSDictionaryToDictionaryValue(userActivity.userInfo); + if (!user_info) + return NO; + + atom::Browser* browser = atom::Browser::Get(); + return browser->ContinueUserActivity(activity_type, *user_info) ? YES : NO; +} + @end diff --git a/atom/browser/mac/dict_util.h b/atom/browser/mac/dict_util.h new file mode 100644 index 000000000000..3ffd8ba51006 --- /dev/null +++ b/atom/browser/mac/dict_util.h @@ -0,0 +1,28 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_MAC_DICT_UTIL_H_ +#define ATOM_BROWSER_MAC_DICT_UTIL_H_ + +#import + +#include "base/memory/scoped_ptr.h" + +namespace base { +class ListValue; +class DictionaryValue; +} + +namespace atom { + +NSDictionary* DictionaryValueToNSDictionary(const base::DictionaryValue& value); + +std::unique_ptr NSDictionaryToDictionaryValue( + NSDictionary* dict); + +std::unique_ptr NSArrayToListValue(NSArray* arr); + +} // namespace atom + +#endif // ATOM_BROWSER_MAC_DICT_UTIL_H_ diff --git a/atom/browser/mac/dict_util.mm b/atom/browser/mac/dict_util.mm new file mode 100644 index 000000000000..8692f001f6a8 --- /dev/null +++ b/atom/browser/mac/dict_util.mm @@ -0,0 +1,114 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/mac/dict_util.h" + +#include "base/json/json_writer.h" +#include "base/strings/sys_string_conversions.h" +#include "base/values.h" + +namespace atom { + +std::unique_ptr NSArrayToListValue(NSArray* arr) { + if (!arr) + return nullptr; + + std::unique_ptr result(new base::ListValue); + for (id value in arr) { + if ([value isKindOfClass:[NSString class]]) { + result->AppendString(base::SysNSStringToUTF8(value)); + } else if ([value isKindOfClass:[NSNumber class]]) { + const char* objc_type = [value objCType]; + if (strcmp(objc_type, @encode(BOOL)) == 0 || + strcmp(objc_type, @encode(char)) == 0) + result->AppendBoolean([value boolValue]); + else if (strcmp(objc_type, @encode(double)) == 0 || + strcmp(objc_type, @encode(float)) == 0) + result->AppendDouble([value doubleValue]); + else + result->AppendInteger([value intValue]); + } else if ([value isKindOfClass:[NSArray class]]) { + std::unique_ptr sub_arr = NSArrayToListValue(value); + if (sub_arr) + result->Append(std::move(sub_arr)); + else + result->Append(base::Value::CreateNullValue()); + } else if ([value isKindOfClass:[NSDictionary class]]) { + std::unique_ptr sub_dict = + NSDictionaryToDictionaryValue(value); + if (sub_dict) + result->Append(std::move(sub_dict)); + else + result->Append(base::Value::CreateNullValue()); + } else { + result->AppendString(base::SysNSStringToUTF8([value description])); + } + } + + return result; +} + +NSDictionary* DictionaryValueToNSDictionary(const base::DictionaryValue& value) { + std::string json; + if (!base::JSONWriter::Write(value, &json)) + return nil; + NSData* jsonData = [NSData dataWithBytes:json.c_str() length:json.length()]; + id obj = [NSJSONSerialization JSONObjectWithData:jsonData + options:0 + error:nil]; + if (![obj isKindOfClass:[NSDictionary class]]) + return nil; + return obj; +} + +std::unique_ptr NSDictionaryToDictionaryValue( + NSDictionary* dict) { + if (!dict) + return nullptr; + + std::unique_ptr result(new base::DictionaryValue); + for (id key in dict) { + std::string str_key = base::SysNSStringToUTF8( + [key isKindOfClass:[NSString class]] ? key : [key description]); + + id value = [dict objectForKey:key]; + if ([value isKindOfClass:[NSString class]]) { + result->SetStringWithoutPathExpansion( + str_key, base::SysNSStringToUTF8(value)); + } else if ([value isKindOfClass:[NSNumber class]]) { + const char* objc_type = [value objCType]; + if (strcmp(objc_type, @encode(BOOL)) == 0 || + strcmp(objc_type, @encode(char)) == 0) + result->SetBooleanWithoutPathExpansion(str_key, [value boolValue]); + else if (strcmp(objc_type, @encode(double)) == 0 || + strcmp(objc_type, @encode(float)) == 0) + result->SetDoubleWithoutPathExpansion(str_key, [value doubleValue]); + else + result->SetIntegerWithoutPathExpansion(str_key, [value intValue]); + } else if ([value isKindOfClass:[NSArray class]]) { + std::unique_ptr sub_arr = NSArrayToListValue(value); + if (sub_arr) + result->SetWithoutPathExpansion(str_key, std::move(sub_arr)); + else + result->SetWithoutPathExpansion(str_key, + base::Value::CreateNullValue()); + } else if ([value isKindOfClass:[NSDictionary class]]) { + std::unique_ptr sub_dict = + NSDictionaryToDictionaryValue(value); + if (sub_dict) + result->SetWithoutPathExpansion(str_key, std::move(sub_dict)); + else + result->SetWithoutPathExpansion(str_key, + base::Value::CreateNullValue()); + } else { + result->SetStringWithoutPathExpansion( + str_key, + base::SysNSStringToUTF8([value description])); + } + } + + return result; +} + +} // namespace atom diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index c25534f7f18e..c22159fc25db 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -12,12 +12,11 @@ #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/window_list.h" #include "atom/common/api/api_messages.h" -#include "atom/common/native_mate_converters/image_converter.h" #include "atom/common/native_mate_converters/file_path_converter.h" #include "atom/common/options_switches.h" #include "base/files/file_util.h" #include "base/json/json_writer.h" -#include "base/prefs/pref_service.h" +#include "components/prefs/pref_service.h" #include "base/message_loop/message_loop.h" #include "base/strings/utf_string_conversions.h" #include "brightray/browser/inspectable_web_contents.h" @@ -27,6 +26,7 @@ #include "content/public/browser/plugin_service.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/common/content_switches.h" #include "ipc/ipc_message_macros.h" @@ -48,11 +48,12 @@ NativeWindow::NativeWindow( 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), has_dialog_attached_(false), + sheet_offset_x_(0.0), + sheet_offset_y_(0.0), aspect_ratio_(0.0), inspectable_web_contents_(inspectable_web_contents), weak_factory_(this) { @@ -64,9 +65,6 @@ NativeWindow::NativeWindow( // mode. ui::GpuSwitchingManager::SetTransparent(transparent_); - // Read icon before window is created. - options.Get(options::kIcon, &icon_); - WindowList::AddWindow(this); } @@ -115,6 +113,12 @@ void NativeWindow::InitFromOptions(const mate::Dictionary& options) { } else { SetSizeConstraints(size_constraints); } +#if defined(USE_X11) + bool resizable; + if (options.Get(options::kResizable, &resizable)) { + SetResizable(resizable); + } +#endif #if defined(OS_WIN) || defined(USE_X11) bool closable; if (options.Get(options::kClosable, &closable)) { @@ -133,12 +137,20 @@ void NativeWindow::InitFromOptions(const mate::Dictionary& options) { if (options.Get(options::kAlwaysOnTop, &top) && top) { SetAlwaysOnTop(true); } -#if defined(OS_MACOSX) || defined(OS_WIN) - bool fullscreen; - if (options.Get(options::kFullscreen, &fullscreen) && fullscreen) { + bool fullscreenable = true; + bool fullscreen = false; + if (options.Get(options::kFullscreen, &fullscreen) && !fullscreen) { + // Disable fullscreen button if 'fullscreen' is specified to false. + #if defined(OS_MACOSX) + fullscreenable = false; + #endif + } + // Overriden by 'fullscreenable'. + options.Get(options::kFullScreenable, &fullscreenable); + SetFullScreenable(fullscreenable); + if (fullscreen) { SetFullScreen(true); } -#endif bool skip; if (options.Get(options::kSkipTaskbar, &skip) && skip) { SetSkipTaskbar(skip); @@ -150,6 +162,9 @@ void NativeWindow::InitFromOptions(const mate::Dictionary& options) { std::string color; if (options.Get(options::kBackgroundColor, &color)) { SetBackgroundColor(color); + } else if (!transparent()) { + // For normal window, use white as default background. + SetBackgroundColor("#FFFF"); } std::string title("Electron"); options.Get(options::kTitle, &title); @@ -239,6 +254,19 @@ gfx::Size NativeWindow::GetMaximumSize() { return GetSizeConstraints().GetMaximumSize(); } +void NativeWindow::SetSheetOffset(const double offsetX, const double offsetY) { + sheet_offset_x_ = offsetX; + sheet_offset_y_ = offsetY; +} + +double NativeWindow::GetSheetOffsetX() { + return sheet_offset_x_; +} + +double NativeWindow::GetSheetOffsetY() { + return sheet_offset_y_; +} + void NativeWindow::SetRepresentedFilename(const std::string& filename) { } @@ -264,15 +292,15 @@ bool NativeWindow::HasModalDialog() { } void NativeWindow::FocusOnWebView() { - web_contents()->GetRenderViewHost()->Focus(); + web_contents()->GetRenderViewHost()->GetWidget()->Focus(); } void NativeWindow::BlurWebView() { - web_contents()->GetRenderViewHost()->Blur(); + web_contents()->GetRenderViewHost()->GetWidget()->Blur(); } bool NativeWindow::IsWebViewFocused() { - auto host_view = web_contents()->GetRenderViewHost()->GetView(); + auto host_view = web_contents()->GetRenderViewHost()->GetWidget()->GetView(); return host_view && host_view->HasFocus(); } @@ -294,9 +322,9 @@ void NativeWindow::CapturePage(const gfx::Rect& rect, // current system, increase the requested bitmap size to capture it all. gfx::Size bitmap_size = view_size; const gfx::NativeView native_view = view->GetNativeView(); - gfx::Screen* const screen = gfx::Screen::GetScreenFor(native_view); const float scale = - screen->GetDisplayNearestWindow(native_view).device_scale_factor(); + gfx::Screen::GetScreen()->GetDisplayNearestWindow(native_view) + .device_scale_factor(); if (scale > 1.0f) bitmap_size = gfx::ScaleToCeiledSize(view_size, scale); @@ -388,7 +416,7 @@ void NativeWindow::RendererUnresponsive(content::WebContents* source) { // responsive event soon. This could happen after the whole application had // blocked for a while. // Also notice that when closing this event would be ignored because we have - // explicity started a close timeout counter. This is on purpose because we + // explicitly started a close timeout counter. This is on purpose because we // don't want the unresponsive event to be sent too early when user is closing // the window. ScheduleUnresponsiveEvent(50); @@ -417,6 +445,14 @@ void NativeWindow::NotifyWindowFocus() { FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowFocus()); } +void NativeWindow::NotifyWindowShow() { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowShow()); +} + +void NativeWindow::NotifyWindowHide() { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowHide()); +} + void NativeWindow::NotifyWindowMaximize() { FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowMaximize()); } @@ -460,6 +496,11 @@ void NativeWindow::NotifyWindowScrollTouchEnd() { OnWindowScrollTouchEnd()); } +void NativeWindow::NotifyWindowSwipe(const std::string& direction) { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, + OnWindowSwipe(direction)); +} + void NativeWindow::NotifyWindowLeaveFullScreen() { FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowLeaveFullScreen()); @@ -489,9 +530,9 @@ void NativeWindow::NotifyWindowMessage( } #endif -scoped_ptr NativeWindow::DraggableRegionsToSkRegion( +std::unique_ptr NativeWindow::DraggableRegionsToSkRegion( const std::vector& regions) { - scoped_ptr sk_region(new SkRegion); + std::unique_ptr sk_region(new SkRegion); for (const DraggableRegion& region : regions) { sk_region->op( region.bounds.x(), @@ -500,7 +541,7 @@ scoped_ptr NativeWindow::DraggableRegionsToSkRegion( region.bounds.bottom(), region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op); } - return sk_region.Pass(); + return sk_region; } void NativeWindow::RenderViewCreated( @@ -536,7 +577,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_ && !force_using_draggable_region_) + if (has_frame_) return; draggable_region_ = DraggableRegionsToSkRegion(regions); } @@ -569,27 +610,4 @@ void NativeWindow::OnCapturePageDone(const CapturePageCallback& callback, callback.Run(bitmap); } -SkColor NativeWindow::ParseHexColor(const std::string& name) { - auto color = name.substr(1); - unsigned length = color.size(); - SkColor result = (length != 8 ? 0xFF000000 : 0x00000000); - unsigned value = 0; - if (length != 3 && length != 6 && length != 8) - return result; - for (unsigned i = 0; i < length; ++i) { - if (!base::IsHexDigit(color[i])) - return result; - value <<= 4; - value |= (color[i] < 'A' ? color[i] - '0' : (color[i] - 'A' + 10) & 0xF); - } - if (length == 6 || length == 8) { - result |= value; - return result; - } - result |= (value & 0xF00) << 12 | (value & 0xF00) << 8 - | (value & 0xF0) << 8 | (value & 0xF0) << 4 - | (value & 0xF) << 4 | (value & 0xF); - return result; -} - } // namespace atom diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 09fae6c6bcfe..0846fbde4a9c 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -123,6 +123,9 @@ class NativeWindow : public base::SupportsUserData, virtual gfx::Size GetMinimumSize(); virtual void SetMaximumSize(const gfx::Size& size); virtual gfx::Size GetMaximumSize(); + virtual void SetSheetOffset(const double offsetX, const double offsetY); + virtual double GetSheetOffsetX(); + virtual double GetSheetOffsetY(); virtual void SetResizable(bool resizable) = 0; virtual bool IsResizable() = 0; virtual void SetMovable(bool movable) = 0; @@ -188,7 +191,7 @@ class NativeWindow : public base::SupportsUserData, // Set the aspect ratio when resizing window. double GetAspectRatio(); gfx::Size GetAspectRatioExtraSize(); - void SetAspectRatio(double aspect_ratio, const gfx::Size& extra_size); + virtual void SetAspectRatio(double aspect_ratio, const gfx::Size& extra_size); base::WeakPtr GetWeakPtr() { return weak_factory_.GetWeakPtr(); @@ -210,6 +213,8 @@ class NativeWindow : public base::SupportsUserData, void NotifyWindowClosed(); void NotifyWindowBlur(); void NotifyWindowFocus(); + void NotifyWindowShow(); + void NotifyWindowHide(); void NotifyWindowMaximize(); void NotifyWindowUnmaximize(); void NotifyWindowMinimize(); @@ -219,6 +224,7 @@ class NativeWindow : public base::SupportsUserData, void NotifyWindowMoved(); void NotifyWindowScrollTouchBegin(); void NotifyWindowScrollTouchEnd(); + void NotifyWindowSwipe(const std::string& direction); void NotifyWindowEnterFullScreen(); void NotifyWindowLeaveFullScreen(); void NotifyWindowEnterHtmlFullScreen(); @@ -241,17 +247,11 @@ class NativeWindow : public base::SupportsUserData, } bool has_frame() const { return has_frame_; } + void set_has_frame(bool has_frame) { has_frame_ = has_frame; } + bool transparent() const { return transparent_; } SkRegion* draggable_region() const { return draggable_region_.get(); } bool enable_larger_than_screen() const { return enable_larger_than_screen_; } - gfx::ImageSkia icon() const { return icon_; } - - 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; @@ -263,7 +263,7 @@ class NativeWindow : public base::SupportsUserData, // Convert draggable regions in raw format to SkRegion format. Caller is // responsible for deleting the returned SkRegion instance. - scoped_ptr DraggableRegionsToSkRegion( + std::unique_ptr DraggableRegionsToSkRegion( const std::vector& regions); // Converts between content size to window size. @@ -279,9 +279,6 @@ class NativeWindow : public base::SupportsUserData, void BeforeUnloadDialogCancelled() override; bool OnMessageReceived(const IPC::Message& message) override; - // Parse hex color like "#FFF" or "#EFEFEF" - SkColor ParseHexColor(const std::string& name); - private: // Schedule a notification unresponsive event. void ScheduleUnresponsiveEvent(int ms); @@ -297,15 +294,12 @@ class NativeWindow : public base::SupportsUserData, // 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_; // For custom drag, the whole window is non-draggable and the draggable region // has to been explicitly provided. - scoped_ptr draggable_region_; // used in custom drag. + std::unique_ptr draggable_region_; // used in custom drag. // Minimum and maximum size, stored as content size. extensions::SizeConstraints size_constraints_; @@ -313,9 +307,6 @@ class NativeWindow : public base::SupportsUserData, // Whether window can be resized larger than screen. bool enable_larger_than_screen_; - // Window icon. - gfx::ImageSkia icon_; - // The windows has been closed. bool is_closed_; @@ -326,6 +317,11 @@ class NativeWindow : public base::SupportsUserData, // it should be cancelled when we can prove that the window is responsive. base::CancelableClosure window_unresposive_closure_; + // Used to display sheets at the appropriate horizontal and vertical offsets + // on OS X. + double sheet_offset_x_; + double sheet_offset_y_; + // Used to maintain the aspect ratio of a view which is inside of the // content view. double aspect_ratio_; diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 93d03e4c157c..c1694c3c784a 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -49,6 +49,8 @@ class NativeWindowMac : public NativeWindow { void SetResizable(bool resizable) override; bool IsResizable() override; void SetMovable(bool movable) override; + void SetAspectRatio(double aspect_ratio, const gfx::Size& extra_size) + override; bool IsMovable() override; void SetMinimizable(bool minimizable) override; bool IsMinimizable() override; @@ -91,16 +93,15 @@ class NativeWindowMac : public NativeWindow { UpdateDraggableRegionViews(draggable_regions_); } + // Set the attribute of NSWindow while work around a bug of zoom button. + void SetStyleMask(bool on, NSUInteger flag); + void SetCollectionBehavior(bool on, NSUInteger flag); + bool should_hide_native_toolbar_in_fullscreen() const { return should_hide_native_toolbar_in_fullscreen_; } protected: - // NativeWindow: - void HandleKeyboardEvent( - 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( @@ -120,10 +121,6 @@ class NativeWindowMac : public NativeWindow { // whehter we can drag. void UpdateDraggableRegionViews(const std::vector& regions); - // Set the attribute of NSWindow while work around a bug of zo0m button. - void SetStyleMask(bool on, NSUInteger flag); - void SetCollectionBehavior(bool on, NSUInteger flag); - base::scoped_nsobject window_; base::scoped_nsobject window_delegate_; @@ -142,6 +139,10 @@ class NativeWindowMac : public NativeWindow { // The presentation options before entering kiosk mode. NSApplicationPresentationOptions kiosk_options_; + // Force showing the buttons for frameless window. + bool force_show_buttons_; + + // Whether to hide the native toolbar under fullscreen mode. bool should_hide_native_toolbar_in_fullscreen_; DISALLOW_COPY_AND_ASSIGN(NativeWindowMac); diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 9d9f2a2392d1..8bc4052f8538 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -6,18 +6,21 @@ #include +#include "atom/browser/window_list.h" +#include "atom/common/color_util.h" #include "atom/common/draggable_region.h" #include "atom/common/options_switches.h" #include "base/mac/mac_util.h" +#include "base/mac/scoped_cftyperef.h" #include "base/strings/sys_string_conversions.h" #include "brightray/browser/inspectable_web_contents.h" #include "brightray/browser/inspectable_web_contents_view.h" #include "content/public/browser/browser_accessibility_state.h" -#include "content/public/browser/native_web_keyboard_event.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host_view.h" #include "native_mate/dictionary.h" +#include "skia/ext/skia_utils_mac.h" #include "ui/gfx/skia_util.h" namespace { @@ -79,6 +82,21 @@ bool ScopedDisableResize::disable_resize_ = false; return self; } +- (void)windowDidChangeOcclusionState:(NSNotification *)notification { + // notification.object is the window that changed its state. + // It's safe to use self.window instead if you don't assign one delegate to many windows + NSWindow *window = notification.object; + + // check occlusion binary flag + if (window.occlusionState & NSWindowOcclusionStateVisible) { + // The app is visible + shell_->NotifyWindowShow(); + } else { + // The app is not visible + shell_->NotifyWindowHide(); + } +} + - (void)windowDidBecomeMain:(NSNotification*)notification { content::WebContents* web_contents = shell_->web_contents(); if (!web_contents) @@ -124,22 +142,9 @@ bool ScopedDisableResize::disable_resize_ = false; newSize.width = roundf((frameSize.height - extraHeightPlusFrame) * aspectRatio + extraWidthPlusFrame); - - // If the new width is less than the frame size use it as the primary - // constraint. This ensures that the value returned by this method will - // never be larger than the users requested window size. - if (newSize.width <= frameSize.width) { - newSize.height = - roundf((newSize.width - extraWidthPlusFrame) / aspectRatio + - extraHeightPlusFrame); - } else { - newSize.height = - roundf((frameSize.width - extraWidthPlusFrame) / aspectRatio + - extraHeightPlusFrame); - newSize.width = - roundf((newSize.height - extraHeightPlusFrame) * aspectRatio + - extraWidthPlusFrame); - } + newSize.height = + roundf((newSize.width - extraWidthPlusFrame) / aspectRatio + + extraHeightPlusFrame); } return newSize; @@ -196,10 +201,22 @@ bool ScopedDisableResize::disable_resize_ = false; [[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]); [toolbar setShowsBaselineSeparator:NO]; [window setToolbar:toolbar]; + + // Set window style to hide the toolbar, otherwise the toolbar will show in + // fullscreen mode. + shell_->SetStyleMask(true, NSFullSizeContentViewWindowMask); } } +- (void)windowWillExitFullScreen:(NSNotification*)notification { + // Turn off the style for toolbar. + if (shell_->should_hide_native_toolbar_in_fullscreen()) + shell_->SetStyleMask(false, NSFullSizeContentViewWindowMask); +} + - (void)windowDidExitFullScreen:(NSNotification*)notification { + // For certain versions of OS X the fullscreen button will automatically show + // after exiting fullscreen mode. if (!shell_->has_frame()) { NSWindow* window = shell_->GetNativeWindow(); [[window standardWindowButton:NSWindowFullScreenButton] setHidden:YES]; @@ -225,6 +242,15 @@ bool ScopedDisableResize::disable_resize_ = false; return NO; } +- (NSRect)window:(NSWindow*)window + willPositionSheet:(NSWindow*)sheet usingRect:(NSRect)rect { + NSView* view = window.contentView; + + rect.origin.x = shell_->GetSheetOffsetX(); + rect.origin.y = view.frame.size.height - shell_->GetSheetOffsetY(); + return rect; +} + @end @interface AtomNSWindow : NSWindow { @@ -252,6 +278,18 @@ bool ScopedDisableResize::disable_resize_ = false; // NSWindow overrides. +- (void)swipeWithEvent:(NSEvent *)event { + if (event.deltaY == 1.0) { + shell_->NotifyWindowSwipe("up"); + } else if (event.deltaX == -1.0) { + shell_->NotifyWindowSwipe("right"); + } else if (event.deltaY == -1.0) { + shell_->NotifyWindowSwipe("down"); + } else if (event.deltaX == 1.0) { + shell_->NotifyWindowSwipe("left"); + } +} + - (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen*)screen { // Resizing is disabled. if (ScopedDisableResize::IsResizeDisabled()) @@ -354,6 +392,7 @@ NativeWindowMac::NativeWindowMac( : NativeWindow(web_contents, options), is_kiosk_(false), attention_request_id_(0), + force_show_buttons_(false), should_hide_native_toolbar_in_fullscreen_(false) { int width = 800, height = 600; options.Get(options::kWidth, &width); @@ -401,22 +440,17 @@ NativeWindowMac::NativeWindowMac( if (closable) { styleMask |= NSClosableWindowMask; } + if ((titleBarStyle == "hidden") || (titleBarStyle == "hidden-inset")) { + // The window without titlebar is treated the same with frameless window. + set_has_frame(false); + force_show_buttons_ = true; + } if (!useStandardWindow || transparent() || !has_frame()) { styleMask |= NSTexturedBackgroundWindowMask; } if (resizable) { styleMask |= NSResizableWindowMask; } - if ((titleBarStyle == "hidden") || (titleBarStyle == "hidden-inset")) { - styleMask |= NSFullSizeContentViewWindowMask; - styleMask |= NSUnifiedTitleAndToolbarWindowMask; - } - // We capture this because we need to access the option later when - // entering/exiting fullscreen and since the options dict is only passed to - // the constructor but not stored, let’s store this option this way. - if (titleBarStyle == "hidden-inset") { - should_hide_native_toolbar_in_fullscreen_ = true; - } window_.reset([[AtomNSWindow alloc] initWithContentRect:cocoa_bounds @@ -453,17 +487,14 @@ NativeWindowMac::NativeWindowMac( [window_ setReleasedWhenClosed:NO]; // Hide the title bar. - if ((titleBarStyle == "hidden") || (titleBarStyle == "hidden-inset")) { + if (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); + base::scoped_nsobject toolbar( + [[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]); + [toolbar setShowsBaselineSeparator:NO]; + [window_ setToolbar:toolbar]; + should_hide_native_toolbar_in_fullscreen_ = true; } // On OS X the initial window size doesn't include window frame. @@ -482,19 +513,9 @@ NativeWindowMac::NativeWindowMac( options.Get(options::kDisableAutoHideCursor, &disableAutoHideCursor); [window_ setDisableAutoHideCursor:disableAutoHideCursor]; - // Disable fullscreen button when 'fullscreenable' is false or 'fullscreen' - // is specified to false. - bool fullscreenable = true; - options.Get(options::kFullScreenable, &fullscreenable); - bool fullscreen = false; - if (options.Get(options::kFullscreen, &fullscreen) && !fullscreen) - fullscreenable = false; - SetFullScreenable(fullscreenable); - - // Disable zoom button if window is not resizable - if (!maximizable) { + // Disable zoom button if window is not resizable. + if (!maximizable) SetMaximizable(false); - } NSView* view = inspectable_web_contents()->GetView()->GetNativeView(); [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; @@ -531,6 +552,11 @@ NativeWindowMac::~NativeWindowMac() { } void NativeWindowMac::Close() { + if (!IsClosable()) { + WindowList::WindowCloseCancelled(this); + return; + } + [window_ performClose:nil]; } @@ -575,15 +601,30 @@ bool NativeWindowMac::IsVisible() { } void NativeWindowMac::Maximize() { + if (IsMaximized()) + return; + [window_ zoom:nil]; } void NativeWindowMac::Unmaximize() { + if (!IsMaximized()) + return; + [window_ zoom:nil]; } bool NativeWindowMac::IsMaximized() { - return [window_ isZoomed]; + if (([window_ styleMask] & NSResizableWindowMask) != 0) { + return [window_ isZoomed]; + } else { + NSRect rectScreen = [[NSScreen mainScreen] visibleFrame]; + NSRect rectWindow = [window_ frame]; + return (rectScreen.origin.x == rectWindow.origin.x && + rectScreen.origin.y == rectWindow.origin.y && + rectScreen.size.width == rectWindow.size.width && + rectScreen.size.height == rectWindow.size.height); + } } void NativeWindowMac::Minimize() { @@ -671,6 +712,17 @@ bool NativeWindowMac::IsResizable() { return [window_ styleMask] & NSResizableWindowMask; } +void NativeWindowMac::SetAspectRatio(double aspect_ratio, + const gfx::Size& extra_size) { + NativeWindow::SetAspectRatio(aspect_ratio, extra_size); + + // Reset the behaviour to default if aspect_ratio is set to 0 or less. + if (aspect_ratio > 0.0) + [window_ setAspectRatio:NSMakeSize(aspect_ratio, 1.0)]; + else + [window_ setResizeIncrements:NSMakeSize(1.0, 1.0)]; +} + void NativeWindowMac::SetMovable(bool movable) { [window_ setMovable:movable]; } @@ -778,12 +830,14 @@ bool NativeWindowMac::IsKiosk() { } void NativeWindowMac::SetBackgroundColor(const std::string& color_name) { - SkColor background_color = NativeWindow::ParseHexColor(color_name); - NSColor *color = [NSColor colorWithCalibratedRed:SkColorGetR(background_color) - green:SkColorGetG(background_color) - blue:SkColorGetB(background_color) - alpha:SkColorGetA(background_color)/255.0f]; - [window_ setBackgroundColor:color]; + SkColor color = ParseHexColor(color_name); + base::ScopedCFTypeRef cgcolor = + skia::CGColorCreateFromSkColor(color); + [[[window_ contentView] layer] setBackgroundColor:cgcolor]; + + const auto view = web_contents()->GetRenderWidgetHostView(); + if (view) + view->SetBackgroundColor(color); } void NativeWindowMac::SetHasShadow(bool has_shadow) { @@ -884,45 +938,14 @@ bool NativeWindowMac::IsVisibleOnAllWorkspaces() { return collectionBehavior & NSWindowCollectionBehaviorCanJoinAllSpaces; } -void NativeWindowMac::HandleKeyboardEvent( - content::WebContents*, - const content::NativeWebKeyboardEvent& event) { - if (event.skip_in_browser || - event.type == content::NativeWebKeyboardEvent::Char) - return; - - BOOL handled = [[NSApp mainMenu] performKeyEquivalent:event.os_event]; - if (!handled && event.os_event.window) { - // Handle the cmd+~ shortcut. - if ((event.os_event.modifierFlags & NSCommandKeyMask) /* cmd */ && - (event.os_event.keyCode == 50 /* ~ */)) { - // Switch to next visible window. - NSArray* windows = [NSApp windows]; - NSIndexSet* indexes = [windows indexesOfObjectsPassingTest: - ^BOOL(id window, NSUInteger idx, BOOL* stop) { - return [window isVisible]; - }]; - if ([indexes count] == 0) - return; - NSUInteger current = [windows indexOfObject:event.os_event.window]; - if (current == NSNotFound) // Some faked event. - return; - NSUInteger next = [indexes indexGreaterThanIndex:current]; - if (next == NSNotFound) - next = [indexes firstIndex]; - [[windows objectAtIndex:next] makeKeyAndOrderFront:nil]; - } - } -} - 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); + std::unique_ptr draggable(DraggableRegionsToSkRegion(regions)); + std::unique_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()) { @@ -979,6 +1002,10 @@ void NativeWindowMac::InstallView() { [view setFrame:[content_view_ bounds]]; [content_view_ addSubview:view]; + if (force_show_buttons_) + return; + + // Hide the window buttons. [[window_ standardWindowButton:NSWindowZoomButton] setHidden:YES]; [[window_ standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES]; [[window_ standardWindowButton:NSWindowCloseButton] setHidden:YES]; @@ -997,7 +1024,7 @@ void NativeWindowMac::UninstallView() { void NativeWindowMac::UpdateDraggableRegionViews( const std::vector& regions) { - if (has_frame() && !force_using_draggable_region()) + if (has_frame()) return; // All ControlRegionViews should be added as children of the WebContentsView, diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index 4af181085a08..cfbae95bda16 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -42,6 +42,12 @@ class NativeWindowObserver { // Called when window gains focus. virtual void OnWindowFocus() {} + // Called when window is shown. + virtual void OnWindowShow() {} + + // Called when window is hidden. + virtual void OnWindowHide() {} + // Called when window state changed. virtual void OnWindowMaximize() {} virtual void OnWindowUnmaximize() {} @@ -52,6 +58,7 @@ class NativeWindowObserver { virtual void OnWindowMoved() {} virtual void OnWindowScrollTouchBegin() {} virtual void OnWindowScrollTouchEnd() {} + virtual void OnWindowSwipe(const std::string& direction) {} virtual void OnWindowEnterFullScreen() {} virtual void OnWindowLeaveFullScreen() {} virtual void OnWindowEnterHtmlFullScreen() {} diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 97a2ee05a331..e699892d4ac5 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -9,11 +9,15 @@ #include "atom/browser/ui/views/menu_bar.h" #include "atom/browser/ui/views/menu_layout.h" +#include "atom/browser/window_list.h" +#include "atom/common/color_util.h" #include "atom/common/draggable_region.h" +#include "atom/common/native_mate_converters/image_converter.h" #include "atom/common/options_switches.h" #include "base/strings/utf_string_conversions.h" #include "brightray/browser/inspectable_web_contents.h" #include "brightray/browser/inspectable_web_contents_view.h" +#include "content/public/browser/browser_thread.h" #include "content/public/browser/native_web_keyboard_event.h" #include "native_mate/dictionary.h" #include "ui/aura/window_tree_host.h" @@ -38,6 +42,7 @@ #include "chrome/browser/ui/libgtk2ui/unity_service.h" #include "ui/base/x/x11_util.h" #include "ui/gfx/x/x11_types.h" +#include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h" #include "ui/views/window/native_frame_view.h" #elif defined(OS_WIN) #include "atom/browser/ui/views/win_frame_view.h" @@ -117,7 +122,8 @@ NativeWindowViews::NativeWindowViews( movable_(true), resizable_(true), maximizable_(true), - minimizable_(true) { + minimizable_(true), + fullscreenable_(true) { options.Get(options::kTitle, &title_); options.Get(options::kAutoHideMenuBar, &menu_bar_autohide_); @@ -270,7 +276,6 @@ NativeWindowViews::NativeWindowViews( use_content_size_) size = ContentSizeToWindowSize(size); - window_->UpdateWindowIcon(); window_->CenterWindow(size); Layout(); } @@ -280,6 +285,11 @@ NativeWindowViews::~NativeWindowViews() { } void NativeWindowViews::Close() { + if (!IsClosable()) { + WindowList::WindowCloseCancelled(this); + return; + } + window_->Close(); } @@ -301,6 +311,8 @@ bool NativeWindowViews::IsFocused() { void NativeWindowViews::Show() { window_->native_widget_private()->ShowWithWindowState(GetRestoredState()); + NotifyWindowShow(); + #if defined(USE_X11) if (global_menu_bar_) global_menu_bar_->OnWindowMapped(); @@ -310,6 +322,8 @@ void NativeWindowViews::Show() { void NativeWindowViews::ShowInactive() { window_->ShowInactive(); + NotifyWindowShow(); + #if defined(USE_X11) if (global_menu_bar_) global_menu_bar_->OnWindowMapped(); @@ -319,6 +333,8 @@ void NativeWindowViews::ShowInactive() { void NativeWindowViews::Hide() { window_->Hide(); + NotifyWindowHide(); + #if defined(USE_X11) if (global_menu_bar_) global_menu_bar_->OnWindowUnmapped(); @@ -362,6 +378,9 @@ bool NativeWindowViews::IsMinimized() { } void NativeWindowViews::SetFullScreen(bool fullscreen) { + if (!IsFullScreenable()) + return; + #if defined(OS_WIN) // There is no native fullscreen state on Windows. if (fullscreen) { @@ -505,11 +524,12 @@ bool NativeWindowViews::IsMaximizable() { #endif } -void NativeWindowViews::SetFullScreenable(bool maximizable) { +void NativeWindowViews::SetFullScreenable(bool fullscreenable) { + fullscreenable_ = fullscreenable; } bool NativeWindowViews::IsFullScreenable() { - return true; + return fullscreenable_; } void NativeWindowViews::SetClosable(bool closable) { @@ -603,14 +623,16 @@ bool NativeWindowViews::IsKiosk() { void NativeWindowViews::SetBackgroundColor(const std::string& color_name) { // web views' background color. - SkColor background_color = NativeWindow::ParseHexColor(color_name); + 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); + GetAcceleratedWidget(), + GCLP_HBRBACKGROUND, + reinterpret_cast(brush)); if (previous_brush) DeleteObject((HBRUSH)previous_brush); #endif @@ -763,15 +785,38 @@ gfx::AcceleratedWidget NativeWindowViews::GetAcceleratedWidget() { return GetNativeWindow()->GetHost()->GetAcceleratedWidget(); } +#if defined(OS_WIN) +void NativeWindowViews::SetIcon(HICON window_icon, HICON app_icon) { + // We are responsible for storing the images. + window_icon_ = base::win::ScopedHICON(CopyIcon(window_icon)); + app_icon_ = base::win::ScopedHICON(CopyIcon(app_icon)); + + HWND hwnd = GetAcceleratedWidget(); + SendMessage(hwnd, WM_SETICON, ICON_SMALL, + reinterpret_cast(window_icon_.get())); + SendMessage(hwnd, WM_SETICON, ICON_BIG, + reinterpret_cast(app_icon_.get())); +} +#elif defined(USE_X11) +void NativeWindowViews::SetIcon(const gfx::ImageSkia& icon) { + views::DesktopWindowTreeHostX11* tree_host = + views::DesktopWindowTreeHostX11::GetHostForXID(GetAcceleratedWidget()); + static_cast(tree_host)->SetWindowIcons( + icon, icon); +} +#endif + void NativeWindowViews::OnWidgetActivationChanged( views::Widget* widget, bool active) { if (widget != window_.get()) return; - if (active) - NotifyWindowFocus(); - else - NotifyWindowBlur(); + // Post the notification to next tick. + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(active ? &NativeWindow::NotifyWindowFocus : + &NativeWindow::NotifyWindowBlur, + GetWeakPtr())); if (active && inspectable_web_contents() && !inspectable_web_contents()->IsDevToolsViewShowing()) @@ -825,14 +870,6 @@ bool NativeWindowViews::ShouldHandleSystemCommands() const { return true; } -gfx::ImageSkia NativeWindowViews::GetWindowAppIcon() { - return icon(); -} - -gfx::ImageSkia NativeWindowViews::GetWindowIcon() { - return GetWindowAppIcon(); -} - views::Widget* NativeWindowViews::GetWidget() { return window_.get(); } diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index 934f2aa00c93..909c5b6fd4ba 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -17,6 +17,7 @@ #if defined(OS_WIN) #include "atom/browser/ui/win/message_handler_delegate.h" #include "atom/browser/ui/win/taskbar_host.h" +#include "base/win/scoped_gdi_object.h" #endif namespace views { @@ -104,6 +105,12 @@ class NativeWindowViews : public NativeWindow, gfx::AcceleratedWidget GetAcceleratedWidget() override; +#if defined(OS_WIN) + void SetIcon(HICON small_icon, HICON app_icon); +#elif defined(USE_X11) + void SetIcon(const gfx::ImageSkia& icon); +#endif + views::Widget* widget() const { return window_.get(); } #if defined(OS_WIN) @@ -125,8 +132,6 @@ class NativeWindowViews : public NativeWindow, bool CanMinimize() const override; base::string16 GetWindowTitle() const override; bool ShouldHandleSystemCommands() const override; - gfx::ImageSkia GetWindowAppIcon() override; - gfx::ImageSkia GetWindowIcon() override; views::Widget* GetWidget() override; const views::Widget* GetWidget() const override; views::View* GetContentsView() override; @@ -145,7 +150,6 @@ class NativeWindowViews : public NativeWindow, // MessageHandlerDelegate: bool PreHandleMSG( UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result) override; - void HandleSizeEvent(WPARAM w_param, LPARAM l_param); #endif @@ -167,19 +171,19 @@ class NativeWindowViews : public NativeWindow, // Returns the restore state for the window. ui::WindowShowState GetRestoredState(); - scoped_ptr window_; + std::unique_ptr window_; views::View* web_view_; // Managed by inspectable_web_contents_. - scoped_ptr menu_bar_; + std::unique_ptr menu_bar_; bool menu_bar_autohide_; bool menu_bar_visible_; bool menu_bar_alt_pressed_; #if defined(USE_X11) - scoped_ptr global_menu_bar_; + std::unique_ptr global_menu_bar_; // Handles window state events. - scoped_ptr window_state_watcher_; + std::unique_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 @@ -199,10 +203,17 @@ class NativeWindowViews : public NativeWindow, // In charge of running taskbar related APIs. TaskbarHost taskbar_host_; + + // If true we have enabled a11y + bool enabled_a11y_support_; + + // The icons of window and taskbar. + base::win::ScopedHICON window_icon_; + base::win::ScopedHICON app_icon_; #endif // Handles unhandled keyboard messages coming back from the renderer process. - scoped_ptr keyboard_event_handler_; + std::unique_ptr keyboard_event_handler_; // Map from accelerator to menu item's command id. accelerator_util::AcceleratorTable accelerator_table_; @@ -212,6 +223,7 @@ class NativeWindowViews : public NativeWindow, bool resizable_; bool maximizable_; bool minimizable_; + bool fullscreenable_; std::string title_; gfx::Size widget_size_; diff --git a/atom/browser/native_window_views_win.cc b/atom/browser/native_window_views_win.cc index e5ed1975f80f..038ab105222d 100644 --- a/atom/browser/native_window_views_win.cc +++ b/atom/browser/native_window_views_win.cc @@ -84,6 +84,25 @@ bool NativeWindowViews::PreHandleMSG( 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 (enabled_a11y_support_) return false; + + if (obj_id == OBJID_CLIENT) { + const auto axState = content::BrowserAccessibilityState::GetInstance(); + if (axState && !axState->IsAccessibleBrowser()) { + axState->OnScreenReaderDetected(); + enabled_a11y_support_ = true; + } + } + + return false; + } case WM_COMMAND: // Handle thumbar button click message. if (HIWORD(w_param) == THBN_CLICKED) diff --git a/atom/browser/net/asar/url_request_asar_job.cc b/atom/browser/net/asar/url_request_asar_job.cc index d926d1111722..39e55a35cbce 100644 --- a/atom/browser/net/asar/url_request_asar_job.cc +++ b/atom/browser/net/asar/url_request_asar_job.cc @@ -44,6 +44,7 @@ URLRequestAsarJob::URLRequestAsarJob( : net::URLRequestJob(request, network_delegate), type_(TYPE_ERROR), remaining_bytes_(0), + range_parse_result_(net::OK), weak_ptr_factory_(this) {} URLRequestAsarJob::~URLRequestAsarJob() {} @@ -99,7 +100,7 @@ void URLRequestAsarJob::InitializeFileJob( void URLRequestAsarJob::Start() { if (type_ == TYPE_ASAR) { - remaining_bytes_ = static_cast(file_info_.size); + remaining_bytes_ = static_cast(file_info_.size); int flags = base::File::FLAG_OPEN | base::File::FLAG_READ | @@ -131,18 +132,14 @@ void URLRequestAsarJob::Kill() { URLRequestJob::Kill(); } -bool URLRequestAsarJob::ReadRawData(net::IOBuffer* dest, - int dest_size, - int* bytes_read) { +int URLRequestAsarJob::ReadRawData(net::IOBuffer* dest, int dest_size) { if (remaining_bytes_ < dest_size) dest_size = static_cast(remaining_bytes_); // If we should copy zero bytes because |remaining_bytes_| is zero, short // circuit here. - if (!dest_size) { - *bytes_read = 0; - return true; - } + if (!dest_size) + return 0; int rv = stream_->Read(dest, dest_size, @@ -150,20 +147,11 @@ bool URLRequestAsarJob::ReadRawData(net::IOBuffer* dest, weak_ptr_factory_.GetWeakPtr(), make_scoped_refptr(dest))); if (rv >= 0) { - // Data is immediately available. - *bytes_read = rv; remaining_bytes_ -= rv; DCHECK_GE(remaining_bytes_, 0); - return true; } - // Otherwise, a read error occured. We may just need to wait... - if (rv == net::ERR_IO_PENDING) { - SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); - } else { - NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, rv)); - } - return false; + return rv; } bool URLRequestAsarJob::IsRedirectResponse(GURL* location, @@ -214,15 +202,16 @@ void URLRequestAsarJob::SetExtraRequestHeaders( const net::HttpRequestHeaders& headers) { std::string range_header; if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) { - // We only care about "Range" header here. + // This job only cares about the Range header. This method stashes the value + // for later use in DidOpen(), which is responsible for some of the range + // validation as well. NotifyStartError is not legal to call here since + // the job has not started. std::vector ranges; if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) { if (ranges.size() == 1) { byte_range_ = ranges[0]; } else { - NotifyDone(net::URLRequestStatus( - net::URLRequestStatus::FAILED, - net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); + range_parse_result_ = net::ERR_REQUEST_RANGE_NOT_SATISFIABLE; } } } @@ -274,7 +263,14 @@ void URLRequestAsarJob::DidFetchMetaInfo(const FileMetaInfo* meta_info) { void URLRequestAsarJob::DidOpen(int result) { if (result != net::OK) { - NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result)); + NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED, + result)); + return; + } + + if (range_parse_result_ != net::OK) { + NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED, + range_parse_result_)); return; } @@ -289,8 +285,9 @@ void URLRequestAsarJob::DidOpen(int result) { } } else { if (!byte_range_.ComputeBounds(meta_info_.file_size)) { - NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, - net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); + NotifyStartError( + net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); return; } @@ -315,17 +312,19 @@ void URLRequestAsarJob::DidOpen(int result) { } } -void URLRequestAsarJob::DidSeek(int64 result) { +void URLRequestAsarJob::DidSeek(int64_t result) { if (type_ == TYPE_ASAR) { - if (result != static_cast(file_info_.offset)) { - NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, - net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); + if (result != static_cast(file_info_.offset)) { + NotifyStartError( + net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); return; } } else { if (result != byte_range_.first_byte_position()) { - NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, - net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); + NotifyStartError( + net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); return; } } @@ -334,21 +333,14 @@ void URLRequestAsarJob::DidSeek(int64 result) { } void URLRequestAsarJob::DidRead(scoped_refptr buf, int result) { - if (result > 0) { - SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status + if (result >= 0) { remaining_bytes_ -= result; DCHECK_GE(remaining_bytes_, 0); } buf = NULL; - if (result == 0) { - NotifyDone(net::URLRequestStatus()); - } else if (result < 0) { - NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result)); - } - - NotifyReadComplete(result); + ReadRawDataComplete(result); } } // namespace asar diff --git a/atom/browser/net/asar/url_request_asar_job.h b/atom/browser/net/asar/url_request_asar_job.h index 29d1afc521d6..56c519823c0e 100644 --- a/atom/browser/net/asar/url_request_asar_job.h +++ b/atom/browser/net/asar/url_request_asar_job.h @@ -54,9 +54,7 @@ class URLRequestAsarJob : public net::URLRequestJob { // net::URLRequestJob: void Start() override; void Kill() override; - bool ReadRawData(net::IOBuffer* buf, - int buf_size, - int* bytes_read) override; + int ReadRawData(net::IOBuffer* buf, int buf_size) override; bool IsRedirectResponse(GURL* location, int* http_status_code) override; net::Filter* SetupFilter() const override; bool GetMimeType(std::string* mime_type) const override; @@ -72,7 +70,7 @@ class URLRequestAsarJob : public net::URLRequestJob { FileMetaInfo(); // Size of the file. - int64 file_size; + int64_t file_size; // Mime type associated with the file. std::string mime_type; // Result returned from GetMimeTypeFromFile(), i.e. flag showing whether @@ -97,7 +95,7 @@ class URLRequestAsarJob : public net::URLRequestJob { // Callback after seeking to the beginning of |byte_range_| in the file // on a background thread. - void DidSeek(int64 result); + void DidSeek(int64_t result); // Callback after data is asynchronously read from the file into |buf|. void DidRead(scoped_refptr buf, int result); @@ -114,12 +112,14 @@ class URLRequestAsarJob : public net::URLRequestJob { base::FilePath file_path_; Archive::FileInfo file_info_; - scoped_ptr stream_; + std::unique_ptr stream_; FileMetaInfo meta_info_; scoped_refptr file_task_runner_; net::HttpByteRange byte_range_; - int64 remaining_bytes_; + int64_t remaining_bytes_; + + net::Error range_parse_result_; base::WeakPtrFactory weak_ptr_factory_; diff --git a/atom/browser/net/atom_cert_verifier.cc b/atom/browser/net/atom_cert_verifier.cc index 3633d805fb5b..adfba7060c90 100644 --- a/atom/browser/net/atom_cert_verifier.cc +++ b/atom/browser/net/atom_cert_verifier.cc @@ -48,7 +48,7 @@ int AtomCertVerifier::Verify( net::CRLSet* crl_set, net::CertVerifyResult* verify_result, const net::CompletionCallback& callback, - scoped_ptr* out_req, + std::unique_ptr* out_req, const net::BoundNetLog& net_log) { DCHECK_CURRENTLY_ON(BrowserThread::IO); diff --git a/atom/browser/net/atom_cert_verifier.h b/atom/browser/net/atom_cert_verifier.h index 796ae2849bda..e00ba260ec3f 100644 --- a/atom/browser/net/atom_cert_verifier.h +++ b/atom/browser/net/atom_cert_verifier.h @@ -34,14 +34,14 @@ class AtomCertVerifier : public net::CertVerifier { net::CRLSet* crl_set, net::CertVerifyResult* verify_result, const net::CompletionCallback& callback, - scoped_ptr* out_req, + std::unique_ptr* out_req, const net::BoundNetLog& net_log) override; bool SupportsOCSPStapling() override; private: base::Lock lock_; VerifyProc verify_proc_; - scoped_ptr default_cert_verifier_; + std::unique_ptr default_cert_verifier_; DISALLOW_COPY_AND_ASSIGN(AtomCertVerifier); }; diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index f993138ccc58..fd0c52b3554f 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -4,21 +4,20 @@ #include "atom/browser/net/atom_network_delegate.h" -#include +#include #include "atom/common/native_mate_converters/net_converter.h" #include "base/stl_util.h" #include "base/strings/string_util.h" +#include "brightray/browser/net/devtools_network_transaction.h" #include "content/public/browser/browser_thread.h" -#include "content/public/browser/resource_request_info.h" #include "net/url_request/url_request.h" +using brightray::DevToolsNetworkTransaction; using content::BrowserThread; namespace atom { -namespace { - const char* ResourceTypeToString(content::ResourceType type) { switch (type) { case content::RESOURCE_TYPE_MAIN_FRAME: @@ -40,14 +39,19 @@ const char* ResourceTypeToString(content::ResourceType type) { } } +namespace { + +using ResponseHeadersContainer = + std::pair*, const std::string&>; + void RunSimpleListener(const AtomNetworkDelegate::SimpleListener& listener, - scoped_ptr details) { + std::unique_ptr details) { return listener.Run(*(details.get())); } void RunResponseListener( const AtomNetworkDelegate::ResponseListener& listener, - scoped_ptr details, + std::unique_ptr details, const AtomNetworkDelegate::ResponseCallback& callback) { return listener.Run(*(details.get()), callback); } @@ -75,19 +79,19 @@ void ToDictionary(base::DictionaryValue* details, net::URLRequest* request) { details->SetString("resourceType", info ? ResourceTypeToString(info->GetResourceType()) : "other"); - scoped_ptr list(new base::ListValue); + std::unique_ptr list(new base::ListValue); GetUploadData(list.get(), request); if (!list->empty()) - details->Set("uploadData", list.Pass()); + details->Set("uploadData", std::move(list)); } void ToDictionary(base::DictionaryValue* details, const net::HttpRequestHeaders& headers) { - scoped_ptr dict(new base::DictionaryValue); + std::unique_ptr dict(new base::DictionaryValue); net::HttpRequestHeaders::Iterator it(headers); while (it.GetNext()) dict->SetString(it.name(), it.value()); - details->Set("requestHeaders", dict.Pass()); + details->Set("requestHeaders", std::move(dict)); } void ToDictionary(base::DictionaryValue* details, @@ -95,8 +99,8 @@ void ToDictionary(base::DictionaryValue* details, if (!headers) return; - scoped_ptr dict(new base::DictionaryValue); - void* iter = nullptr; + std::unique_ptr dict(new base::DictionaryValue); + size_t iter = 0; std::string key; std::string value; while (headers->EnumerateHeaderLines(&iter, &key, &value)) { @@ -105,12 +109,12 @@ void ToDictionary(base::DictionaryValue* details, if (dict->GetList(key, &values)) values->AppendString(value); } else { - scoped_ptr values(new base::ListValue); + std::unique_ptr values(new base::ListValue); values->AppendString(value); - dict->Set(key, values.Pass()); + dict->Set(key, std::move(values)); } } - details->Set("responseHeaders", dict.Pass()); + details->Set("responseHeaders", std::move(dict)); details->SetString("statusLine", headers->GetStatusLine()); details->SetInteger("statusCode", headers->response_code()); } @@ -170,10 +174,15 @@ void ReadFromResponseObject(const base::DictionaryValue& response, } void ReadFromResponseObject(const base::DictionaryValue& response, - scoped_refptr* headers) { + const ResponseHeadersContainer& container) { const base::DictionaryValue* dict; + std::string status_line; + if (!response.GetString("statusLine", &status_line)) + status_line = container.second; if (response.GetDictionary("responseHeaders", &dict)) { + auto headers = container.first; *headers = new net::HttpResponseHeaders(""); + (*headers)->ReplaceStatusLine(status_line); for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { @@ -218,6 +227,12 @@ void AtomNetworkDelegate::SetResponseListenerInIO( response_listeners_[type] = { patterns, callback }; } +void AtomNetworkDelegate::SetDevToolsNetworkEmulationClientId( + const std::string& client_id) { + base::AutoLock auto_lock(lock_); + client_id_ = client_id; +} + int AtomNetworkDelegate::OnBeforeURLRequest( net::URLRequest* request, const net::CompletionCallback& callback, @@ -233,6 +248,16 @@ int AtomNetworkDelegate::OnBeforeSendHeaders( net::URLRequest* request, const net::CompletionCallback& callback, net::HttpRequestHeaders* headers) { + std::string client_id; + { + base::AutoLock auto_lock(lock_); + client_id = client_id_; + } + + if (!client_id.empty()) + headers->SetHeader( + DevToolsNetworkTransaction::kDevToolsEmulateNetworkConditionsClientId, + client_id); if (!ContainsKey(response_listeners_, kOnBeforeSendHeaders)) return brightray::NetworkDelegate::OnBeforeSendHeaders( request, callback, headers); @@ -263,7 +288,8 @@ int AtomNetworkDelegate::OnHeadersReceived( request, callback, original, override, allowed); return HandleResponseEvent( - kOnHeadersReceived, request, callback, override, original); + kOnHeadersReceived, request, callback, + std::make_pair(override, original->GetStatusLine()), original); } void AtomNetworkDelegate::OnBeforeRedirect(net::URLRequest* request, @@ -343,7 +369,7 @@ int AtomNetworkDelegate::HandleResponseEvent( if (!MatchesFilterCondition(request, info.url_patterns)) return net::OK; - scoped_ptr details(new base::DictionaryValue); + std::unique_ptr details(new base::DictionaryValue); FillDetailsObject(details.get(), request, args...); // The |request| could be destroyed before the |callback| is called. @@ -366,7 +392,7 @@ void AtomNetworkDelegate::HandleSimpleEvent( if (!MatchesFilterCondition(request, info.url_patterns)) return; - scoped_ptr details(new base::DictionaryValue); + std::unique_ptr details(new base::DictionaryValue); FillDetailsObject(details.get(), request, args...); BrowserThread::PostTask( @@ -376,7 +402,7 @@ void AtomNetworkDelegate::HandleSimpleEvent( template void AtomNetworkDelegate::OnListenerResultInIO( - uint64_t id, T out, scoped_ptr response) { + uint64_t id, T out, std::unique_ptr response) { // The request has been destroyed. if (!ContainsKey(callbacks_, id)) return; @@ -391,7 +417,7 @@ void AtomNetworkDelegate::OnListenerResultInIO( template void AtomNetworkDelegate::OnListenerResultInUI( uint64_t id, T out, const base::DictionaryValue& response) { - scoped_ptr copy = response.CreateDeepCopy(); + std::unique_ptr copy = response.CreateDeepCopy(); BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&AtomNetworkDelegate::OnListenerResultInIO, diff --git a/atom/browser/net/atom_network_delegate.h b/atom/browser/net/atom_network_delegate.h index 4f55f7c09863..62653df0baeb 100644 --- a/atom/browser/net/atom_network_delegate.h +++ b/atom/browser/net/atom_network_delegate.h @@ -7,14 +7,17 @@ #include #include +#include #include "brightray/browser/network_delegate.h" #include "base/callback.h" +#include "base/synchronization/lock.h" #include "base/values.h" #include "extensions/common/url_pattern.h" #include "net/base/net_errors.h" #include "net/http/http_request_headers.h" #include "net/http/http_response_headers.h" +#include "content/public/browser/resource_request_info.h" namespace extensions { class URLPattern; @@ -24,6 +27,8 @@ namespace atom { using URLPatterns = std::set; +const char* ResourceTypeToString(content::ResourceType type); + class AtomNetworkDelegate : public brightray::NetworkDelegate { public: using ResponseCallback = base::Callback; @@ -65,6 +70,8 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { const URLPatterns& patterns, const ResponseListener& callback); + void SetDevToolsNetworkEmulationClientId(const std::string& client_id); + protected: // net::NetworkDelegate: int OnBeforeURLRequest(net::URLRequest* request, @@ -104,7 +111,7 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { // Deal with the results of Listener. template void OnListenerResultInIO( - uint64_t id, T out, scoped_ptr response); + uint64_t id, T out, std::unique_ptr response); template void OnListenerResultInUI( uint64_t id, T out, const base::DictionaryValue& response); @@ -113,6 +120,11 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { std::map response_listeners_; std::map callbacks_; + base::Lock lock_; + + // Client id for devtools network emulation. + std::string client_id_; + DISALLOW_COPY_AND_ASSIGN(AtomNetworkDelegate); }; diff --git a/atom/browser/net/atom_ssl_config_service.cc b/atom/browser/net/atom_ssl_config_service.cc index 2b3143e93cb4..c306a8a39175 100644 --- a/atom/browser/net/atom_ssl_config_service.cc +++ b/atom/browser/net/atom_ssl_config_service.cc @@ -18,8 +18,8 @@ namespace atom { namespace { -uint16 GetSSLProtocolVersion(const std::string& version_string) { - uint16 version = 0; // Invalid +uint16_t GetSSLProtocolVersion(const std::string& version_string) { + uint16_t version = 0; // Invalid if (version_string == "tls1") version = net::SSL_PROTOCOL_VERSION_TLS1; else if (version_string == "tls1.1") @@ -29,13 +29,13 @@ uint16 GetSSLProtocolVersion(const std::string& version_string) { return version; } -std::vector ParseCipherSuites( +std::vector ParseCipherSuites( const std::vector& cipher_strings) { - std::vector cipher_suites; + std::vector cipher_suites; cipher_suites.reserve(cipher_strings.size()); for (auto& cipher_string : cipher_strings) { - uint16 cipher_suite = 0; + uint16_t cipher_suite = 0; if (!net::ParseSSLCipherString(cipher_string, &cipher_suite)) { LOG(ERROR) << "Ignoring unrecognised cipher suite : " << cipher_string; diff --git a/atom/browser/net/atom_url_request_job_factory.cc b/atom/browser/net/atom_url_request_job_factory.cc index dbd8b4160cfd..aff2565814b5 100644 --- a/atom/browser/net/atom_url_request_job_factory.cc +++ b/atom/browser/net/atom_url_request_job_factory.cc @@ -23,7 +23,8 @@ AtomURLRequestJobFactory::~AtomURLRequestJobFactory() { } bool AtomURLRequestJobFactory::SetProtocolHandler( - const std::string& scheme, scoped_ptr protocol_handler) { + const std::string& scheme, + std::unique_ptr protocol_handler) { if (!protocol_handler) { ProtocolHandlerMap::iterator it = protocol_handler_map_.find(scheme); if (it == protocol_handler_map_.end()) @@ -40,8 +41,9 @@ bool AtomURLRequestJobFactory::SetProtocolHandler( return true; } -scoped_ptr AtomURLRequestJobFactory::ReplaceProtocol( - const std::string& scheme, scoped_ptr protocol_handler) { +std::unique_ptr AtomURLRequestJobFactory::ReplaceProtocol( + const std::string& scheme, + std::unique_ptr protocol_handler) { if (!ContainsKey(protocol_handler_map_, scheme)) return nullptr; ProtocolHandler* original_protocol_handler = protocol_handler_map_[scheme]; diff --git a/atom/browser/net/atom_url_request_job_factory.h b/atom/browser/net/atom_url_request_job_factory.h index dde36225b7af..e3dbd7754240 100644 --- a/atom/browser/net/atom_url_request_job_factory.h +++ b/atom/browser/net/atom_url_request_job_factory.h @@ -24,13 +24,14 @@ class AtomURLRequestJobFactory : public net::URLRequestJobFactory { // Sets the ProtocolHandler for a scheme. Returns true on success, false on // failure (a ProtocolHandler already exists for |scheme|). On success, // URLRequestJobFactory takes ownership of |protocol_handler|. - bool SetProtocolHandler( - const std::string& scheme, scoped_ptr protocol_handler); + bool SetProtocolHandler(const std::string& scheme, + std::unique_ptr protocol_handler); // Intercepts the ProtocolHandler for a scheme. Returns the original protocol // handler on success, otherwise returns NULL. - scoped_ptr ReplaceProtocol( - const std::string& scheme, scoped_ptr protocol_handler); + std::unique_ptr ReplaceProtocol( + const std::string& scheme, + std::unique_ptr protocol_handler); // Returns the protocol handler registered with scheme. ProtocolHandler* GetProtocolHandler(const std::string& scheme) const; diff --git a/atom/browser/net/js_asker.cc b/atom/browser/net/js_asker.cc index 8f0d1d2b9577..8362c1add1c6 100644 --- a/atom/browser/net/js_asker.cc +++ b/atom/browser/net/js_asker.cc @@ -16,7 +16,9 @@ namespace internal { namespace { // The callback which is passed to |handler|. -void HandlerCallback(const ResponseCallback& callback, mate::Arguments* args) { +void HandlerCallback(const BeforeStartCallback& before_start, + const ResponseCallback& callback, + mate::Arguments* args) { // If there is no argument passed then we failed. v8::Local value; if (!args->GetNext(&value)) { @@ -26,10 +28,13 @@ void HandlerCallback(const ResponseCallback& callback, mate::Arguments* args) { return; } + // Give the job a chance to parse V8 value. + before_start.Run(args->isolate(), value); + // Pass whatever user passed to the actaul request job. V8ValueConverter converter; v8::Local context = args->isolate()->GetCurrentContext(); - scoped_ptr options(converter.FromV8Value(value, context)); + std::unique_ptr options(converter.FromV8Value(value, context)); content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind(callback, true, base::Passed(&options))); @@ -40,15 +45,17 @@ void HandlerCallback(const ResponseCallback& callback, mate::Arguments* args) { void AskForOptions(v8::Isolate* isolate, const JavaScriptHandler& handler, net::URLRequest* request, + const BeforeStartCallback& before_start, const ResponseCallback& callback) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); v8::Locker locker(isolate); v8::HandleScope handle_scope(isolate); v8::Local context = isolate->GetCurrentContext(); v8::Context::Scope context_scope(context); - handler.Run(request, - mate::ConvertToV8(isolate, - base::Bind(&HandlerCallback, callback))); + handler.Run( + request, + mate::ConvertToV8(isolate, + base::Bind(&HandlerCallback, before_start, callback))); } bool IsErrorOptions(base::Value* value, int* error) { diff --git a/atom/browser/net/js_asker.h b/atom/browser/net/js_asker.h index 8ec245ee8c4f..6301f4de2c2a 100644 --- a/atom/browser/net/js_asker.h +++ b/atom/browser/net/js_asker.h @@ -23,13 +23,16 @@ using JavaScriptHandler = namespace internal { +using BeforeStartCallback = + base::Callback)>; using ResponseCallback = - base::Callback options)>; + base::Callback options)>; // Ask handler for options in UI thread. void AskForOptions(v8::Isolate* isolate, const JavaScriptHandler& handler, net::URLRequest* request, + const BeforeStartCallback& before_start, const ResponseCallback& callback); // Test whether the |options| means an error. @@ -54,7 +57,8 @@ class JsAsker : public RequestJob { } // Subclass should do initailze work here. - virtual void StartAsync(scoped_ptr options) = 0; + virtual void BeforeStartInUI(v8::Isolate*, v8::Local) {} + virtual void StartAsync(std::unique_ptr options) = 0; net::URLRequestContextGetter* request_context_getter() const { return request_context_getter_; @@ -69,6 +73,8 @@ class JsAsker : public RequestJob { isolate_, handler_, RequestJob::request(), + base::Bind(&JsAsker::BeforeStartInUI, + weak_factory_.GetWeakPtr()), base::Bind(&JsAsker::OnResponse, weak_factory_.GetWeakPtr()))); } @@ -78,10 +84,10 @@ class JsAsker : public RequestJob { // Called when the JS handler has sent the response, we need to decide whether // to start, or fail the job. - void OnResponse(bool success, scoped_ptr value) { + void OnResponse(bool success, std::unique_ptr value) { int error = net::ERR_NOT_IMPLEMENTED; if (success && value && !internal::IsErrorOptions(value.get(), &error)) { - StartAsync(value.Pass()); + StartAsync(std::move(value)); } else { RequestJob::NotifyStartError( net::URLRequestStatus(net::URLRequestStatus::FAILED, error)); diff --git a/atom/browser/net/url_request_async_asar_job.cc b/atom/browser/net/url_request_async_asar_job.cc index 3578f3b7971c..1234bccf4303 100644 --- a/atom/browser/net/url_request_async_asar_job.cc +++ b/atom/browser/net/url_request_async_asar_job.cc @@ -16,7 +16,7 @@ URLRequestAsyncAsarJob::URLRequestAsyncAsarJob( : JsAsker(request, network_delegate) { } -void URLRequestAsyncAsarJob::StartAsync(scoped_ptr options) { +void URLRequestAsyncAsarJob::StartAsync(std::unique_ptr options) { base::FilePath::StringType file_path; if (options->IsType(base::Value::TYPE_DICTIONARY)) { static_cast(options.get())->GetString( diff --git a/atom/browser/net/url_request_async_asar_job.h b/atom/browser/net/url_request_async_asar_job.h index d65142f0bdbb..032f3d992413 100644 --- a/atom/browser/net/url_request_async_asar_job.h +++ b/atom/browser/net/url_request_async_asar_job.h @@ -16,7 +16,7 @@ class URLRequestAsyncAsarJob : public JsAsker { URLRequestAsyncAsarJob(net::URLRequest*, net::NetworkDelegate*); // JsAsker: - void StartAsync(scoped_ptr options) override; + void StartAsync(std::unique_ptr options) override; // URLRequestJob: void GetResponseInfo(net::HttpResponseInfo* info) override; diff --git a/atom/browser/net/url_request_buffer_job.cc b/atom/browser/net/url_request_buffer_job.cc index aa273bf81693..c713099c76a1 100644 --- a/atom/browser/net/url_request_buffer_job.cc +++ b/atom/browser/net/url_request_buffer_job.cc @@ -8,17 +8,31 @@ #include "atom/common/atom_constants.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "net/base/mime_util.h" #include "net/base/net_errors.h" namespace atom { +namespace { + +std::string GetExtFromURL(const GURL& url) { + std::string spec = url.spec(); + size_t index = spec.find_last_of('.'); + if (index == std::string::npos || index == spec.size()) + return std::string(); + return spec.substr(index + 1, spec.size() - index - 1); +} + +} // namespace + URLRequestBufferJob::URLRequestBufferJob( net::URLRequest* request, net::NetworkDelegate* network_delegate) : JsAsker(request, network_delegate), status_code_(net::HTTP_NOT_IMPLEMENTED) { } -void URLRequestBufferJob::StartAsync(scoped_ptr options) { +void URLRequestBufferJob::StartAsync(std::unique_ptr options) { const base::BinaryValue* binary = nullptr; if (options->IsType(base::Value::TYPE_DICTIONARY)) { base::DictionaryValue* dict = @@ -30,6 +44,15 @@ void URLRequestBufferJob::StartAsync(scoped_ptr options) { options->GetAsBinary(&binary); } + if (mime_type_.empty()) { + std::string ext = GetExtFromURL(request()->url()); +#if defined(OS_WIN) + net::GetWellKnownMimeTypeFromExtension(base::UTF8ToUTF16(ext), &mime_type_); +#else + net::GetWellKnownMimeTypeFromExtension(ext, &mime_type_); +#endif + } + if (!binary) { NotifyStartError(net::URLRequestStatus( net::URLRequestStatus::FAILED, net::ERR_NOT_IMPLEMENTED)); diff --git a/atom/browser/net/url_request_buffer_job.h b/atom/browser/net/url_request_buffer_job.h index ab8de7e8f030..356ca9347f6f 100644 --- a/atom/browser/net/url_request_buffer_job.h +++ b/atom/browser/net/url_request_buffer_job.h @@ -19,7 +19,7 @@ class URLRequestBufferJob : public JsAsker { URLRequestBufferJob(net::URLRequest*, net::NetworkDelegate*); // JsAsker: - void StartAsync(scoped_ptr options) override; + void StartAsync(std::unique_ptr options) override; // URLRequestJob: void GetResponseInfo(net::HttpResponseInfo* info) override; diff --git a/atom/browser/net/url_request_fetch_job.cc b/atom/browser/net/url_request_fetch_job.cc index 6f4182901752..8279d09d5dad 100644 --- a/atom/browser/net/url_request_fetch_job.cc +++ b/atom/browser/net/url_request_fetch_job.cc @@ -8,15 +8,14 @@ #include #include "base/strings/string_util.h" -#include "base/thread_task_runner_handle.h" +#include "native_mate/dictionary.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/http/http_response_headers.h" #include "net/url_request/url_fetcher.h" #include "net/url_request/url_fetcher_response_writer.h" -#include "net/url_request/url_request_context.h" -#include "net/url_request/url_request_context_builder.h" -#include "net/url_request/url_request_status.h" + +using content::BrowserThread; namespace atom { @@ -60,7 +59,7 @@ class ResponsePiper : public net::URLFetcherResponseWriter { job_->HeadersCompleted(); first_write_ = false; } - return job_->DataAvailable(buffer, num_bytes); + return job_->DataAvailable(buffer, num_bytes, callback); } int Finish(const net::CompletionCallback& callback) override { return net::OK; @@ -78,10 +77,30 @@ class ResponsePiper : public net::URLFetcherResponseWriter { URLRequestFetchJob::URLRequestFetchJob( net::URLRequest* request, net::NetworkDelegate* network_delegate) : JsAsker(request, network_delegate), - pending_buffer_size_(0) { + pending_buffer_size_(0), + write_num_bytes_(0) { } -void URLRequestFetchJob::StartAsync(scoped_ptr options) { +void URLRequestFetchJob::BeforeStartInUI( + v8::Isolate* isolate, v8::Local value) { + mate::Dictionary options; + if (!mate::ConvertFromV8(isolate, value, &options)) + return; + + // When |session| is set to |null| we use a new request context for fetch job. + // TODO(zcbenz): Handle the case when it is not null. + v8::Local session; + if (options.Get("session", &session) && session->IsNull()) { + // We have to create the URLRequestContextGetter on UI thread. + url_request_context_getter_ = new brightray::URLRequestContextGetter( + this, nullptr, nullptr, base::FilePath(), true, + BrowserThread::UnsafeGetMessageLoopForThread(BrowserThread::IO), + BrowserThread::UnsafeGetMessageLoopForThread(BrowserThread::FILE), + nullptr, content::URLRequestInterceptorScopedVector()); + } +} + +void URLRequestFetchJob::StartAsync(std::unique_ptr options) { if (!options->IsType(base::Value::TYPE_DICTIONARY)) { NotifyStartError(net::URLRequestStatus( net::URLRequestStatus::FAILED, net::ERR_NOT_IMPLEMENTED)); @@ -89,14 +108,12 @@ void URLRequestFetchJob::StartAsync(scoped_ptr options) { } std::string url, method, referrer; - base::Value* session = nullptr; base::DictionaryValue* upload_data = nullptr; base::DictionaryValue* dict = static_cast(options.get()); dict->GetString("url", &url); dict->GetString("method", &method); dict->GetString("referrer", &referrer); - dict->Get("session", &session); dict->GetDictionary("uploadData", &upload_data); // Check if URL is valid. @@ -117,9 +134,9 @@ void URLRequestFetchJob::StartAsync(scoped_ptr options) { fetcher_ = net::URLFetcher::Create(formated_url, request_type, this); fetcher_->SaveResponseWithWriter(make_scoped_ptr(new ResponsePiper(this))); - // When |session| is set to |null| we use a new request context for fetch job. - if (session && session->IsType(base::Value::TYPE_NULL)) - fetcher_->SetRequestContext(CreateRequestContext()); + // A request context getter is passed by the user. + if (url_request_context_getter_) + fetcher_->SetRequestContext(url_request_context_getter_.get()); else fetcher_->SetRequestContext(request_context_getter()); @@ -144,44 +161,30 @@ void URLRequestFetchJob::StartAsync(scoped_ptr options) { fetcher_->Start(); } -net::URLRequestContextGetter* URLRequestFetchJob::CreateRequestContext() { - if (!url_request_context_getter_.get()) { - auto task_runner = base::ThreadTaskRunnerHandle::Get(); - net::URLRequestContextBuilder builder; - builder.set_proxy_service(net::ProxyService::CreateDirect()); - request_context_ = builder.Build(); - url_request_context_getter_ = new net::TrivialURLRequestContextGetter( - request_context_.get(), task_runner); - } - return url_request_context_getter_.get(); -} - void URLRequestFetchJob::HeadersCompleted() { response_info_.reset(new net::HttpResponseInfo); response_info_->headers = fetcher_->GetResponseHeaders(); NotifyHeadersComplete(); } -int URLRequestFetchJob::DataAvailable(net::IOBuffer* buffer, int num_bytes) { - // Clear the IO_PENDING status. - SetStatus(net::URLRequestStatus()); - // Do nothing if pending_buffer_ is empty, i.e. there's no ReadRawData() - // operation waiting for IO completion. - if (!pending_buffer_.get()) +int URLRequestFetchJob::DataAvailable(net::IOBuffer* buffer, + int num_bytes, + const net::CompletionCallback& callback) { + // When pending_buffer_ is empty, there's no ReadRawData() operation waiting + // for IO completion, we have to save the parameters until the request is + // ready to read data. + if (!pending_buffer_.get()) { + write_buffer_ = buffer; + write_num_bytes_ = num_bytes; + write_callback_ = callback; return net::ERR_IO_PENDING; + } - // pending_buffer_ is set to the IOBuffer instance provided to ReadRawData() - // by URLRequestJob. - - int bytes_read = std::min(num_bytes, pending_buffer_size_); - memcpy(pending_buffer_->data(), buffer->data(), bytes_read); - - // Clear the buffers before notifying the read is complete, so that it is - // safe for the observer to read. - pending_buffer_ = nullptr; - pending_buffer_size_ = 0; - - NotifyReadComplete(bytes_read); + // Write data to the pending buffer and clear them after the writing. + int bytes_read = BufferCopy(buffer, num_bytes, + pending_buffer_.get(), pending_buffer_size_); + ClearPendingBuffer(); + ReadRawDataComplete(bytes_read); return bytes_read; } @@ -190,18 +193,27 @@ void URLRequestFetchJob::Kill() { fetcher_.reset(); } -bool URLRequestFetchJob::ReadRawData(net::IOBuffer* dest, - int dest_size, - int* bytes_read) { +int URLRequestFetchJob::ReadRawData(net::IOBuffer* dest, int dest_size) { if (GetResponseCode() == 204) { - *bytes_read = 0; request()->set_received_response_content_length(prefilter_bytes_read()); - return true; + return net::OK; } - pending_buffer_ = dest; - pending_buffer_size_ = dest_size; - SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); - return false; + + // When write_buffer_ is empty, there is no data valable yet, we have to save + // the dest buffer util DataAvailable. + if (!write_buffer_.get()) { + pending_buffer_ = dest; + pending_buffer_size_ = dest_size; + return net::ERR_IO_PENDING; + } + + // Read from the write buffer and clear them after reading. + int bytes_read = BufferCopy(write_buffer_.get(), write_num_bytes_, + dest, dest_size); + net::CompletionCallback write_callback = write_callback_; + ClearWriteBuffer(); + write_callback.Run(bytes_read); + return bytes_read; } bool URLRequestFetchJob::GetMimeType(std::string* mime_type) const { @@ -232,11 +244,31 @@ void URLRequestFetchJob::OnURLFetchComplete(const net::URLFetcher* source) { return; } + ClearPendingBuffer(); + ClearWriteBuffer(); + + if (fetcher_->GetStatus().is_success()) + ReadRawDataComplete(0); + else + NotifyStartError(fetcher_->GetStatus()); +} + +int URLRequestFetchJob::BufferCopy(net::IOBuffer* source, int num_bytes, + net::IOBuffer* target, int target_size) { + int bytes_written = std::min(num_bytes, target_size); + memcpy(target->data(), source->data(), bytes_written); + return bytes_written; +} + +void URLRequestFetchJob::ClearPendingBuffer() { pending_buffer_ = nullptr; pending_buffer_size_ = 0; - NotifyDone(fetcher_->GetStatus()); - if (fetcher_->GetStatus().is_success()) - NotifyReadComplete(0); +} + +void URLRequestFetchJob::ClearWriteBuffer() { + write_buffer_ = nullptr; + write_num_bytes_ = 0; + write_callback_.Reset(); } } // namespace atom diff --git a/atom/browser/net/url_request_fetch_job.h b/atom/browser/net/url_request_fetch_job.h index 399f78ae3963..906ba68d3965 100644 --- a/atom/browser/net/url_request_fetch_job.h +++ b/atom/browser/net/url_request_fetch_job.h @@ -8,32 +8,31 @@ #include #include "atom/browser/net/js_asker.h" -#include "net/url_request/url_request_context_getter.h" +#include "browser/url_request_context_getter.h" #include "net/url_request/url_fetcher_delegate.h" -#include "net/url_request/url_request_job.h" namespace atom { -class AtomBrowserContext; - class URLRequestFetchJob : public JsAsker, - public net::URLFetcherDelegate { + public net::URLFetcherDelegate, + public brightray::URLRequestContextGetter::Delegate { public: URLRequestFetchJob(net::URLRequest*, net::NetworkDelegate*); // Called by response writer. void HeadersCompleted(); - int DataAvailable(net::IOBuffer* buffer, int num_bytes); + int DataAvailable(net::IOBuffer* buffer, + int num_bytes, + const net::CompletionCallback& callback); protected: // JsAsker: - void StartAsync(scoped_ptr options) override; + void BeforeStartInUI(v8::Isolate*, v8::Local) override; + void StartAsync(std::unique_ptr options) override; // net::URLRequestJob: void Kill() override; - bool ReadRawData(net::IOBuffer* buf, - int buf_size, - int* bytes_read) override; + int ReadRawData(net::IOBuffer* buf, int buf_size) override; bool GetMimeType(std::string* mime_type) const override; void GetResponseInfo(net::HttpResponseInfo* info) override; int GetResponseCode() const override; @@ -42,15 +41,23 @@ class URLRequestFetchJob : public JsAsker, void OnURLFetchComplete(const net::URLFetcher* source) override; private: - // Create a independent request context. - net::URLRequestContextGetter* CreateRequestContext(); + int BufferCopy(net::IOBuffer* source, int num_bytes, + net::IOBuffer* target, int target_size); + void ClearPendingBuffer(); + void ClearWriteBuffer(); - scoped_ptr request_context_; scoped_refptr url_request_context_getter_; - scoped_ptr fetcher_; + std::unique_ptr fetcher_; + std::unique_ptr response_info_; + + // Saved arguments passed to ReadRawData. scoped_refptr pending_buffer_; int pending_buffer_size_; - scoped_ptr response_info_; + + // Saved arguments passed to DataAvailable. + scoped_refptr write_buffer_; + int write_num_bytes_; + net::CompletionCallback write_callback_; DISALLOW_COPY_AND_ASSIGN(URLRequestFetchJob); }; diff --git a/atom/browser/net/url_request_string_job.cc b/atom/browser/net/url_request_string_job.cc index 606781142da0..59945e66db07 100644 --- a/atom/browser/net/url_request_string_job.cc +++ b/atom/browser/net/url_request_string_job.cc @@ -16,7 +16,7 @@ URLRequestStringJob::URLRequestStringJob( : JsAsker(request, network_delegate) { } -void URLRequestStringJob::StartAsync(scoped_ptr options) { +void URLRequestStringJob::StartAsync(std::unique_ptr options) { if (options->IsType(base::Value::TYPE_DICTIONARY)) { base::DictionaryValue* dict = static_cast(options.get()); diff --git a/atom/browser/net/url_request_string_job.h b/atom/browser/net/url_request_string_job.h index e40f0d93dab7..474473cca87d 100644 --- a/atom/browser/net/url_request_string_job.h +++ b/atom/browser/net/url_request_string_job.h @@ -17,7 +17,7 @@ class URLRequestStringJob : public JsAsker { URLRequestStringJob(net::URLRequest*, net::NetworkDelegate*); // JsAsker: - void StartAsync(scoped_ptr options) override; + void StartAsync(std::unique_ptr options) override; // URLRequestJob: void GetResponseInfo(net::HttpResponseInfo* info) override; diff --git a/atom/browser/node_debugger.cc b/atom/browser/node_debugger.cc index 2cfcdb222aea..55025dd69f72 100644 --- a/atom/browser/node_debugger.cc +++ b/atom/browser/node_debugger.cc @@ -145,14 +145,14 @@ void NodeDebugger::DebugMessageHandler(const v8::Debug::Message& message) { void NodeDebugger::DidAccept( net::test_server::StreamListenSocket* server, - scoped_ptr socket) { + std::unique_ptr socket) { // Only accept one session. if (accepted_socket_) { socket->Send(std::string("Remote debugging session already active"), true); return; } - accepted_socket_ = socket.Pass(); + accepted_socket_ = std::move(socket); SendConnectMessage(); } diff --git a/atom/browser/node_debugger.h b/atom/browser/node_debugger.h index aedf7b2c0310..f708de6329f3 100644 --- a/atom/browser/node_debugger.h +++ b/atom/browser/node_debugger.h @@ -38,7 +38,7 @@ class NodeDebugger : public net::test_server::StreamListenSocket::Delegate { // net::test_server::StreamListenSocket::Delegate: void DidAccept( net::test_server::StreamListenSocket* server, - scoped_ptr socket) override; + std::unique_ptr socket) override; void DidRead(net::test_server::StreamListenSocket* socket, const char* data, int len) override; @@ -49,8 +49,8 @@ class NodeDebugger : public net::test_server::StreamListenSocket::Delegate { uv_async_t weak_up_ui_handle_; base::Thread thread_; - scoped_ptr server_; - scoped_ptr accepted_socket_; + std::unique_ptr server_; + std::unique_ptr accepted_socket_; std::string buffer_; int content_length_; diff --git a/atom/browser/relauncher.cc b/atom/browser/relauncher.cc new file mode 100644 index 000000000000..9610d80693b1 --- /dev/null +++ b/atom/browser/relauncher.cc @@ -0,0 +1,192 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/relauncher.h" + +#include +#include + +#include "atom/common/atom_command_line.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/process/launch.h" +#include "content/public/common/content_paths.h" +#include "content/public/common/content_switches.h" +#include "content/public/common/main_function_params.h" + +#if defined(OS_POSIX) +#include "base/posix/eintr_wrapper.h" +#endif + +namespace relauncher { + +namespace internal { + +#if defined(OS_POSIX) +const int kRelauncherSyncFD = STDERR_FILENO + 1; +#endif + +const CharType* kRelauncherTypeArg = FILE_PATH_LITERAL("--type=relauncher"); +const CharType* kRelauncherArgSeparator = FILE_PATH_LITERAL("---"); + +} // namespace internal + +bool RelaunchApp(const StringVector& argv) { + // Use the currently-running application's helper process. The automatic + // update feature is careful to leave the currently-running version alone, + // so this is safe even if the relaunch is the result of an update having + // been applied. In fact, it's safer than using the updated version of the + // helper process, because there's no guarantee that the updated version's + // relauncher implementation will be compatible with the running version's. + base::FilePath child_path; + if (!PathService::Get(content::CHILD_PROCESS_EXE, &child_path)) { + LOG(ERROR) << "No CHILD_PROCESS_EXE"; + return false; + } + + StringVector relauncher_args; + return RelaunchAppWithHelper(child_path, relauncher_args, argv); +} + +bool RelaunchAppWithHelper(const base::FilePath& helper, + const StringVector& relauncher_args, + const StringVector& argv) { + StringVector relaunch_argv; + relaunch_argv.push_back(helper.value()); + relaunch_argv.push_back(internal::kRelauncherTypeArg); + + relaunch_argv.insert(relaunch_argv.end(), + relauncher_args.begin(), relauncher_args.end()); + + relaunch_argv.push_back(internal::kRelauncherArgSeparator); + + relaunch_argv.insert(relaunch_argv.end(), argv.begin(), argv.end()); + +#if defined(OS_POSIX) + int pipe_fds[2]; + if (HANDLE_EINTR(pipe(pipe_fds)) != 0) { + PLOG(ERROR) << "pipe"; + return false; + } + + // The parent process will only use pipe_read_fd as the read side of the + // pipe. It can close the write side as soon as the relauncher process has + // forked off. The relauncher process will only use pipe_write_fd as the + // write side of the pipe. In that process, the read side will be closed by + // base::LaunchApp because it won't be present in fd_map, and the write side + // will be remapped to kRelauncherSyncFD by fd_map. + base::ScopedFD pipe_read_fd(pipe_fds[0]); + base::ScopedFD pipe_write_fd(pipe_fds[1]); + + // Make sure kRelauncherSyncFD is a safe value. base::LaunchProcess will + // preserve these three FDs in forked processes, so kRelauncherSyncFD should + // not conflict with them. + static_assert(internal::kRelauncherSyncFD != STDIN_FILENO && + internal::kRelauncherSyncFD != STDOUT_FILENO && + internal::kRelauncherSyncFD != STDERR_FILENO, + "kRelauncherSyncFD must not conflict with stdio fds"); + + base::FileHandleMappingVector fd_map; + fd_map.push_back( + std::make_pair(pipe_write_fd.get(), internal::kRelauncherSyncFD)); +#endif + + base::LaunchOptions options; +#if defined(OS_POSIX) + options.fds_to_remap = &fd_map; + base::Process process = base::LaunchProcess(relaunch_argv, options); +#elif defined(OS_WIN) + base::Process process = base::LaunchProcess( + internal::ArgvToCommandLineString(relaunch_argv), options); +#endif + if (!process.IsValid()) { + LOG(ERROR) << "base::LaunchProcess failed"; + return false; + } + + // The relauncher process is now starting up, or has started up. The + // original parent process continues. + +#if defined(OS_WIN) + // Synchronize with the relauncher process. + StringType name = internal::GetWaitEventName(process.Pid()); + HANDLE wait_event = ::CreateEventW(NULL, TRUE, FALSE, name.c_str()); + if (wait_event != NULL) { + WaitForSingleObject(wait_event, 1000); + CloseHandle(wait_event); + } +#elif defined(OS_POSIX) + pipe_write_fd.reset(); // close(pipe_fds[1]); + + // Synchronize with the relauncher process. + char read_char; + int read_result = HANDLE_EINTR(read(pipe_read_fd.get(), &read_char, 1)); + if (read_result != 1) { + if (read_result < 0) { + PLOG(ERROR) << "read"; + } else { + LOG(ERROR) << "read: unexpected result " << read_result; + } + return false; + } + + // Since a byte has been successfully read from the relauncher process, it's + // guaranteed to have set up its kqueue monitoring this process for exit. + // It's safe to exit now. +#endif + return true; +} + +int RelauncherMain(const content::MainFunctionParams& main_parameters) { +#if defined(OS_WIN) + const StringVector& argv = atom::AtomCommandLine::wargv(); +#else + const StringVector& argv = atom::AtomCommandLine::argv(); +#endif + + if (argv.size() < 4 || argv[1] != internal::kRelauncherTypeArg) { + LOG(ERROR) << "relauncher process invoked with unexpected arguments"; + return 1; + } + + internal::RelauncherSynchronizeWithParent(); + + // Figure out what to execute, what arguments to pass it, and whether to + // start it in the background. + bool in_relauncher_args = false; + StringType relaunch_executable; + StringVector relauncher_args; + StringVector launch_argv; + for (size_t argv_index = 2; argv_index < argv.size(); ++argv_index) { + const StringType& arg(argv[argv_index]); + if (!in_relauncher_args) { + if (arg == internal::kRelauncherArgSeparator) { + in_relauncher_args = true; + } else { + relauncher_args.push_back(arg); + } + } else { + launch_argv.push_back(arg); + } + } + + if (launch_argv.empty()) { + LOG(ERROR) << "nothing to relaunch"; + return 1; + } + + if (internal::LaunchProgram(relauncher_args, launch_argv) != 0) { + LOG(ERROR) << "failed to launch program"; + return 1; + } + + // The application should have relaunched (or is in the process of + // relaunching). From this point on, only clean-up tasks should occur, and + // failures are tolerable. + + return 0; +} + +} // namespace relauncher diff --git a/atom/browser/relauncher.h b/atom/browser/relauncher.h new file mode 100644 index 000000000000..10f2e5128795 --- /dev/null +++ b/atom/browser/relauncher.h @@ -0,0 +1,122 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_RELAUNCHER_H_ +#define ATOM_BROWSER_RELAUNCHER_H_ + +// relauncher implements main browser application relaunches across platforms. +// When a browser wants to relaunch itself, it can't simply fork off a new +// process and exec a new browser from within. That leaves open a window +// during which two browser applications might be running concurrently. If +// that happens, each will wind up with a distinct Dock icon, which is +// especially bad if the user expected the Dock icon to be persistent by +// choosing Keep in Dock from the icon's contextual menu. +// +// relauncher approaches this problem by introducing an intermediate +// process (the "relauncher") in between the original browser ("parent") and +// replacement browser ("relaunched"). The helper executable is used for the +// relauncher process; because it's an LSUIElement, it doesn't get a Dock +// icon and isn't visible as a running application at all. The parent will +// start a relauncher process, giving it the "writer" side of a pipe that it +// retains the "reader" end of. When the relauncher starts up, it will +// establish a kqueue to wait for the parent to exit, and will then write to +// the pipe. The parent, upon reading from the pipe, is free to exit. When the +// relauncher is notified via its kqueue that the parent has exited, it +// proceeds, launching the relaunched process. The handshake to synchronize +// the parent with the relauncher is necessary to avoid races: the relauncher +// needs to be sure that it's monitoring the parent and not some other process +// in light of PID reuse, so the parent must remain alive long enough for the +// relauncher to set up its kqueue. + +#include +#include + +#include "base/command_line.h" + +#if defined(OS_WIN) +#include "base/process/process_handle.h" +#endif + +namespace content { +struct MainFunctionParams; +} + +namespace relauncher { + +using CharType = base::CommandLine::CharType; +using StringType = base::CommandLine::StringType; +using StringVector = base::CommandLine::StringVector; + +// Relaunches the application using the helper application associated with the +// currently running instance of Chrome in the parent browser process as the +// executable for the relauncher process. |args| is an argv-style vector of +// command line arguments of the form normally passed to execv. args[0] is +// also the path to the relaunched process. Because the relauncher process +// will ultimately launch the relaunched process via Launch Services, args[0] +// may be either a pathname to an executable file or a pathname to an .app +// bundle directory. The caller should exit soon after RelaunchApp returns +// successfully. Returns true on success, although some failures can occur +// after this function returns true if, for example, they occur within the +// relauncher process. Returns false when the relaunch definitely failed. +bool RelaunchApp(const StringVector& argv); + +// Identical to RelaunchApp, but uses |helper| as the path to the relauncher +// process, and allows additional arguments to be supplied to the relauncher +// process in relauncher_args. Unlike args[0], |helper| must be a pathname to +// an executable file. The helper path given must be from the same version of +// Chrome as the running parent browser process, as there are no guarantees +// that the parent and relauncher processes from different versions will be +// able to communicate with one another. This variant can be useful to +// relaunch the same version of Chrome from another location, using that +// location's helper. +bool RelaunchAppWithHelper(const base::FilePath& helper, + const StringVector& relauncher_args, + const StringVector& args); + +// The entry point from ChromeMain into the relauncher process. +int RelauncherMain(const content::MainFunctionParams& main_parameters); + +namespace internal { + +#if defined(OS_POSIX) +// The "magic" file descriptor that the relauncher process' write side of the +// pipe shows up on. Chosen to avoid conflicting with stdin, stdout, and +// stderr. +extern const int kRelauncherSyncFD; +#endif + +// The "type" argument identifying a relauncher process ("--type=relauncher"). +extern const CharType* kRelauncherTypeArg; + +// The argument separating arguments intended for the relauncher process from +// those intended for the relaunched process. "---" is chosen instead of "--" +// because CommandLine interprets "--" as meaning "end of switches", but +// for many purposes, the relauncher process' CommandLine ought to interpret +// arguments intended for the relaunched process, to get the correct settings +// for such things as logging and the user-data-dir in case it affects crash +// reporting. +extern const CharType* kRelauncherArgSeparator; + +#if defined(OS_WIN) +StringType GetWaitEventName(base::ProcessId pid); + +StringType ArgvToCommandLineString(const StringVector& argv); +#endif + +// In the relauncher process, performs the necessary synchronization steps +// with the parent by setting up a kqueue to watch for it to exit, writing a +// byte to the pipe, and then waiting for the exit notification on the kqueue. +// If anything fails, this logs a message and returns immediately. In those +// situations, it can be assumed that something went wrong with the parent +// process and the best recovery approach is to attempt relaunch anyway. +void RelauncherSynchronizeWithParent(); + +int LaunchProgram(const StringVector& relauncher_args, + const StringVector& argv); + +} // namespace internal + +} // namespace relauncher + +#endif // ATOM_BROWSER_RELAUNCHER_H_ diff --git a/atom/browser/relauncher_linux.cc b/atom/browser/relauncher_linux.cc new file mode 100644 index 000000000000..2fbbd47faf03 --- /dev/null +++ b/atom/browser/relauncher_linux.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/relauncher.h" + +#include +#include +#include + +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/launch.h" + +namespace relauncher { + +namespace internal { + +void RelauncherSynchronizeWithParent() { + base::ScopedFD relauncher_sync_fd(kRelauncherSyncFD); + + // Don't execute signal handlers of SIGUSR2. + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR2); + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { + PLOG(ERROR) << "sigprocmask"; + return; + } + + // Create a signalfd that watches for SIGUSR2. + int usr2_fd = signalfd(-1, &mask, 0); + if (usr2_fd < 0) { + PLOG(ERROR) << "signalfd"; + return; + } + + // Send SIGUSR2 to current process when parent process ends. + if (HANDLE_EINTR(prctl(PR_SET_PDEATHSIG, SIGUSR2)) != 0) { + PLOG(ERROR) << "prctl"; + return; + } + + // Write a '\0' character to the pipe. + if (HANDLE_EINTR(write(relauncher_sync_fd.get(), "", 1)) != 1) { + PLOG(ERROR) << "write"; + return; + } + + // Wait the SIGUSR2 signal to happen. + struct signalfd_siginfo si; + HANDLE_EINTR(read(usr2_fd, &si, sizeof(si))); +} + +int LaunchProgram(const StringVector& relauncher_args, + const StringVector& argv) { + base::LaunchOptions options; + options.allow_new_privs = true; + options.new_process_group = true; // detach + base::Process process = base::LaunchProcess(argv, options); + return process.IsValid() ? 0 : 1; +} + +} // namespace internal + +} // namespace relauncher diff --git a/atom/browser/relauncher_mac.cc b/atom/browser/relauncher_mac.cc new file mode 100644 index 000000000000..ef26f8441df6 --- /dev/null +++ b/atom/browser/relauncher_mac.cc @@ -0,0 +1,90 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/relauncher.h" + +#include +#include +#include +#include + +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/process/launch.h" +#include "base/mac/mac_logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/strings/sys_string_conversions.h" + +namespace relauncher { + +namespace internal { + +void RelauncherSynchronizeWithParent() { + base::ScopedFD relauncher_sync_fd(kRelauncherSyncFD); + + int parent_pid = getppid(); + + // PID 1 identifies init. launchd, that is. launchd never starts the + // relauncher process directly, having this parent_pid means that the parent + // already exited and launchd "inherited" the relauncher as its child. + // There's no reason to synchronize with launchd. + if (parent_pid == 1) { + LOG(ERROR) << "unexpected parent_pid"; + return; + } + + // Set up a kqueue to monitor the parent process for exit. + base::ScopedFD kq(kqueue()); + if (!kq.is_valid()) { + PLOG(ERROR) << "kqueue"; + return; + } + + struct kevent change = { 0 }; + EV_SET(&change, parent_pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); + if (kevent(kq.get(), &change, 1, NULL, 0, NULL) == -1) { + PLOG(ERROR) << "kevent (add)"; + return; + } + + // Write a '\0' character to the pipe. + if (HANDLE_EINTR(write(relauncher_sync_fd.get(), "", 1)) != 1) { + PLOG(ERROR) << "write"; + return; + } + + // Up until now, the parent process was blocked in a read waiting for the + // write above to complete. The parent process is now free to exit. Wait for + // that to happen. + struct kevent event; + int events = kevent(kq.get(), NULL, 0, &event, 1, NULL); + if (events != 1) { + if (events < 0) { + PLOG(ERROR) << "kevent (monitor)"; + } else { + LOG(ERROR) << "kevent (monitor): unexpected result " << events; + } + return; + } + + if (event.filter != EVFILT_PROC || + event.fflags != NOTE_EXIT || + event.ident != static_cast(parent_pid)) { + LOG(ERROR) << "kevent (monitor): unexpected event, filter " << event.filter + << ", fflags " << event.fflags << ", ident " << event.ident; + return; + } +} + +int LaunchProgram(const StringVector& relauncher_args, + const StringVector& argv) { + base::LaunchOptions options; + options.new_process_group = true; // detach + base::Process process = base::LaunchProcess(argv, options); + return process.IsValid() ? 0 : 1; +} + +} // namespace internal + +} // namespace relauncher diff --git a/atom/browser/relauncher_win.cc b/atom/browser/relauncher_win.cc new file mode 100644 index 000000000000..1aebb515ecf0 --- /dev/null +++ b/atom/browser/relauncher_win.cc @@ -0,0 +1,128 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/relauncher.h" + +#include + +#include "base/process/launch.h" +#include "base/strings/stringprintf.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/win_utils.h" +#include "ui/base/win/shell.h" + +namespace relauncher { + +namespace internal { + +namespace { + +const CharType* kWaitEventName = L"ElectronRelauncherWaitEvent"; + +HANDLE GetParentProcessHandle(base::ProcessHandle handle) { + NtQueryInformationProcessFunction NtQueryInformationProcess = nullptr; + ResolveNTFunctionPtr("NtQueryInformationProcess", &NtQueryInformationProcess); + if (!NtQueryInformationProcess) { + LOG(ERROR) << "Unable to get NtQueryInformationProcess"; + return NULL; + } + + PROCESS_BASIC_INFORMATION pbi; + LONG status = NtQueryInformationProcess( + handle, ProcessBasicInformation, + &pbi, sizeof(PROCESS_BASIC_INFORMATION), NULL); + if (!NT_SUCCESS(status)) { + LOG(ERROR) << "NtQueryInformationProcess failed"; + return NULL; + } + + return ::OpenProcess(PROCESS_ALL_ACCESS, TRUE, + pbi.InheritedFromUniqueProcessId); +} + +StringType AddQuoteForArg(const StringType& arg) { + // We follow the quoting rules of CommandLineToArgvW. + // http://msdn.microsoft.com/en-us/library/17w5ykft.aspx + std::wstring quotable_chars(L" \\\""); + if (arg.find_first_of(quotable_chars) == std::wstring::npos) { + // No quoting necessary. + return arg; + } + + std::wstring out; + out.push_back(L'"'); + for (size_t i = 0; i < arg.size(); ++i) { + if (arg[i] == '\\') { + // Find the extent of this run of backslashes. + size_t start = i, end = start + 1; + for (; end < arg.size() && arg[end] == '\\'; ++end) {} + size_t backslash_count = end - start; + + // Backslashes are escapes only if the run is followed by a double quote. + // Since we also will end the string with a double quote, we escape for + // either a double quote or the end of the string. + if (end == arg.size() || arg[end] == '"') { + // To quote, we need to output 2x as many backslashes. + backslash_count *= 2; + } + for (size_t j = 0; j < backslash_count; ++j) + out.push_back('\\'); + + // Advance i to one before the end to balance i++ in loop. + i = end - 1; + } else if (arg[i] == '"') { + out.push_back('\\'); + out.push_back('"'); + } else { + out.push_back(arg[i]); + } + } + out.push_back('"'); + + return out; +} + +} // namespace + +StringType GetWaitEventName(base::ProcessId pid) { + return base::StringPrintf(L"%s-%d", kWaitEventName, static_cast(pid)); +} + +StringType ArgvToCommandLineString(const StringVector& argv) { + StringType command_line; + for (const StringType& arg : argv) { + if (!command_line.empty()) + command_line += L' '; + command_line += AddQuoteForArg(arg); + } + return command_line; +} + +void RelauncherSynchronizeWithParent() { + base::Process process = base::Process::Current(); + base::win::ScopedHandle parent_process( + GetParentProcessHandle(process.Handle())); + + // Notify the parent process that it can quit now. + StringType name = internal::GetWaitEventName(process.Pid()); + base::win::ScopedHandle wait_event( + ::CreateEventW(NULL, TRUE, FALSE, name.c_str())); + ::SetEvent(wait_event.Get()); + + // Wait for parent process to quit. + WaitForSingleObject(parent_process.Get(), INFINITE); +} + +int LaunchProgram(const StringVector& relauncher_args, + const StringVector& argv) { + base::LaunchOptions options; + base::Process process = + base::LaunchProcess(ArgvToCommandLineString(argv), options); + return process.IsValid() ? 0 : 1; +} + +} // namespace internal + +} // namespace relauncher diff --git a/atom/browser/render_process_preferences.cc b/atom/browser/render_process_preferences.cc new file mode 100644 index 000000000000..d109c8714f70 --- /dev/null +++ b/atom/browser/render_process_preferences.cc @@ -0,0 +1,63 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/render_process_preferences.h" + +#include "atom/common/api/api_messages.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_types.h" +#include "content/public/browser/render_process_host.h" + +namespace atom { + +RenderProcessPreferences::RenderProcessPreferences(const Predicate& predicate) + : predicate_(predicate), + next_id_(0), + cache_needs_update_(true) { + registrar_.Add(this, + content::NOTIFICATION_RENDERER_PROCESS_CREATED, + content::NotificationService::AllBrowserContextsAndSources()); +} + +RenderProcessPreferences::~RenderProcessPreferences() { +} + +int RenderProcessPreferences::AddEntry(const base::DictionaryValue& entry) { + int id = ++next_id_; + entries_[id] = entry.CreateDeepCopy(); + cache_needs_update_ = true; + return id; +} + +void RenderProcessPreferences::RemoveEntry(int id) { + cache_needs_update_ = true; + entries_.erase(id); +} + +void RenderProcessPreferences::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + DCHECK_EQ(type, content::NOTIFICATION_RENDERER_PROCESS_CREATED); + content::RenderProcessHost* process = + content::Source(source).ptr(); + + if (!predicate_.Run(process)) + return; + + UpdateCache(); + process->Send(new AtomMsg_UpdatePreferences(cached_entries_)); +} + +void RenderProcessPreferences::UpdateCache() { + if (!cache_needs_update_) + return; + + cached_entries_.Clear(); + for (const auto& iter : entries_) + cached_entries_.Append(iter.second->CreateDeepCopy()); + cache_needs_update_ = false; +} + +} // namespace atom diff --git a/atom/browser/render_process_preferences.h b/atom/browser/render_process_preferences.h new file mode 100644 index 000000000000..77bf176f492c --- /dev/null +++ b/atom/browser/render_process_preferences.h @@ -0,0 +1,61 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_RENDER_PROCESS_PREFERENCES_H_ +#define ATOM_BROWSER_RENDER_PROCESS_PREFERENCES_H_ + +#include +#include + +#include "base/callback.h" +#include "base/values.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" + +namespace content { +class RenderProcessHost; +} + +namespace atom { + +// Sets user preferences for render processes. +class RenderProcessPreferences : public content::NotificationObserver { + public: + using Predicate = base::Callback; + + // The |predicate| is used to determine whether to set preferences for a + // render process. + explicit RenderProcessPreferences(const Predicate& predicate); + virtual ~RenderProcessPreferences(); + + int AddEntry(const base::DictionaryValue& entry); + void RemoveEntry(int id); + + private: + // content::NotificationObserver: + void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) override; + + void UpdateCache(); + + // Manages our notification registrations. + content::NotificationRegistrar registrar_; + + Predicate predicate_; + + int next_id_; + std::unordered_map> entries_; + + // We need to convert the |entries_| to ListValue for multiple times, this + // caches is only updated when we are sending messages. + bool cache_needs_update_; + base::ListValue cached_entries_; + + DISALLOW_COPY_AND_ASSIGN(RenderProcessPreferences); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_RENDER_PROCESS_PREFERENCES_H_ diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index e1a5694d27ad..28eddfa61235 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleIconFile - atom.icns + electron.icns CFBundleVersion - 0.36.8 + 1.2.1 CFBundleShortVersionString - 0.36.8 + 1.2.1 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/atom/browser/resources/mac/atom.icns b/atom/browser/resources/mac/electron.icns similarity index 100% rename from atom/browser/resources/mac/atom.icns rename to atom/browser/resources/mac/electron.icns diff --git a/atom/browser/resources/win/atom.manifest b/atom/browser/resources/win/atom.manifest index 84970c717288..64c07ded17b0 100644 --- a/atom/browser/resources/win/atom.manifest +++ b/atom/browser/resources/win/atom.manifest @@ -30,4 +30,11 @@ + + + true + true + + + diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index ff125b3edf72..7e386d319d9d 100644 --- a/atom/browser/resources/win/atom.rc +++ b/atom/browser/resources/win/atom.rc @@ -56,8 +56,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,36,8,0 - PRODUCTVERSION 0,36,8,0 + FILEVERSION 1,2,1,0 + PRODUCTVERSION 1,2,1,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "0.36.8" + VALUE "FileVersion", "1.2.1" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "0.36.8" + VALUE "ProductVersion", "1.2.1" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/browser/ui/accelerator_util.cc b/atom/browser/ui/accelerator_util.cc index 39138d8e49b5..f683c99c637f 100644 --- a/atom/browser/ui/accelerator_util.cc +++ b/atom/browser/ui/accelerator_util.cc @@ -18,13 +18,12 @@ namespace accelerator_util { -bool StringToAccelerator(const std::string& description, +bool StringToAccelerator(const std::string& shortcut, ui::Accelerator* accelerator) { - if (!base::IsStringASCII(description)) { + if (!base::IsStringASCII(shortcut)) { LOG(ERROR) << "The accelerator string can only contain ASCII characters"; return false; } - std::string shortcut(base::ToLowerASCII(description)); std::vector tokens = base::SplitString( shortcut, "+", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); @@ -33,95 +32,35 @@ bool StringToAccelerator(const std::string& description, int modifiers = ui::EF_NONE; ui::KeyboardCode key = ui::VKEY_UNKNOWN; for (size_t i = 0; i < tokens.size(); i++) { - // We use straight comparing instead of map because the accelerator tends - // to be correct and usually only uses few special tokens. - if (tokens[i].size() == 1) { - bool shifted = false; - key = atom::KeyboardCodeFromCharCode(tokens[i][0], &shifted); - if (shifted) + bool shifted = false; + ui::KeyboardCode code = atom::KeyboardCodeFromStr(tokens[i], &shifted); + if (shifted) + modifiers |= ui::EF_SHIFT_DOWN; + switch (code) { + // The token can be a modifier. + case ui::VKEY_SHIFT: modifiers |= ui::EF_SHIFT_DOWN; - } else if (tokens[i] == "ctrl" || tokens[i] == "control") { - modifiers |= ui::EF_CONTROL_DOWN; - } else if (tokens[i] == "super") { - modifiers |= ui::EF_COMMAND_DOWN; -#if defined(OS_MACOSX) - } else if (tokens[i] == "cmd" || tokens[i] == "command") { - modifiers |= ui::EF_COMMAND_DOWN; -#endif - } else if (tokens[i] == "commandorcontrol" || tokens[i] == "cmdorctrl") { -#if defined(OS_MACOSX) - modifiers |= ui::EF_COMMAND_DOWN; -#else - modifiers |= ui::EF_CONTROL_DOWN; -#endif - } else if (tokens[i] == "alt") { - modifiers |= ui::EF_ALT_DOWN; - } else if (tokens[i] == "shift") { - modifiers |= ui::EF_SHIFT_DOWN; - } else if (tokens[i] == "plus") { - modifiers |= ui::EF_SHIFT_DOWN; - key = ui::VKEY_OEM_PLUS; - } else if (tokens[i] == "tab") { - key = ui::VKEY_TAB; - } else if (tokens[i] == "space") { - key = ui::VKEY_SPACE; - } else if (tokens[i] == "backspace") { - key = ui::VKEY_BACK; - } else if (tokens[i] == "delete") { - key = ui::VKEY_DELETE; - } else if (tokens[i] == "insert") { - key = ui::VKEY_INSERT; - } else if (tokens[i] == "enter" || tokens[i] == "return") { - key = ui::VKEY_RETURN; - } else if (tokens[i] == "up") { - key = ui::VKEY_UP; - } else if (tokens[i] == "down") { - key = ui::VKEY_DOWN; - } else if (tokens[i] == "left") { - key = ui::VKEY_LEFT; - } else if (tokens[i] == "right") { - key = ui::VKEY_RIGHT; - } else if (tokens[i] == "home") { - key = ui::VKEY_HOME; - } else if (tokens[i] == "end") { - key = ui::VKEY_END; - } else if (tokens[i] == "pageup") { - key = ui::VKEY_PRIOR; - } else if (tokens[i] == "pagedown") { - key = ui::VKEY_NEXT; - } else if (tokens[i] == "esc" || tokens[i] == "escape") { - key = ui::VKEY_ESCAPE; - } else if (tokens[i] == "volumemute") { - key = ui::VKEY_VOLUME_MUTE; - } else if (tokens[i] == "volumeup") { - key = ui::VKEY_VOLUME_UP; - } else if (tokens[i] == "volumedown") { - key = ui::VKEY_VOLUME_DOWN; - } else if (tokens[i] == "medianexttrack") { - key = ui::VKEY_MEDIA_NEXT_TRACK; - } else if (tokens[i] == "mediaprevioustrack") { - key = ui::VKEY_MEDIA_PREV_TRACK; - } else if (tokens[i] == "mediastop") { - key = ui::VKEY_MEDIA_STOP; - } else if (tokens[i] == "mediaplaypause") { - key = ui::VKEY_MEDIA_PLAY_PAUSE; - } else if (tokens[i].size() > 1 && tokens[i][0] == 'f') { - // F1 - F24. - int n; - if (base::StringToInt(tokens[i].c_str() + 1, &n) && n > 0 && n < 25) { - key = static_cast(ui::VKEY_F1 + n - 1); - } else { - LOG(WARNING) << tokens[i] << "is not available on keyboard"; - return false; - } - } else { - LOG(WARNING) << "Invalid accelerator token: " << tokens[i]; - return false; + break; + case ui::VKEY_CONTROL: + modifiers |= ui::EF_CONTROL_DOWN; + break; + case ui::VKEY_MENU: + modifiers |= ui::EF_ALT_DOWN; + break; + case ui::VKEY_COMMAND: + modifiers |= ui::EF_COMMAND_DOWN; + break; + case ui::VKEY_ALTGR: + modifiers |= ui::EF_ALTGR_DOWN; + break; + // Or it is a normal key. + default: + key = code; } } if (key == ui::VKEY_UNKNOWN) { - LOG(WARNING) << "The accelerator doesn't contain a valid key"; + LOG(WARNING) << shortcut << " doesn't contain a valid key"; return false; } diff --git a/atom/browser/ui/accelerator_util_mac.mm b/atom/browser/ui/accelerator_util_mac.mm index 2075b1041f60..e2448833ab0d 100644 --- a/atom/browser/ui/accelerator_util_mac.mm +++ b/atom/browser/ui/accelerator_util_mac.mm @@ -13,12 +13,6 @@ namespace accelerator_util { void SetPlatformAccelerator(ui::Accelerator* accelerator) { unichar character; unichar characterIgnoringModifiers; - ui::MacKeyCodeForWindowsKeyCode(accelerator->key_code(), - 0, - &character, - &characterIgnoringModifiers); - NSString* characters = - [[[NSString alloc] initWithCharacters:&character length:1] autorelease]; NSUInteger modifiers = (accelerator->IsCtrlDown() ? NSControlKeyMask : 0) | @@ -26,9 +20,21 @@ void SetPlatformAccelerator(ui::Accelerator* accelerator) { (accelerator->IsAltDown() ? NSAlternateKeyMask : 0) | (accelerator->IsShiftDown() ? NSShiftKeyMask : 0); - scoped_ptr platform_accelerator( + ui::MacKeyCodeForWindowsKeyCode(accelerator->key_code(), + modifiers, + &character, + &characterIgnoringModifiers); + + if (character != characterIgnoringModifiers) { + modifiers ^= NSShiftKeyMask; + } + + NSString* characters = + [[[NSString alloc] initWithCharacters:&character length:1] autorelease]; + + std::unique_ptr platform_accelerator( new ui::PlatformAcceleratorCocoa(characters, modifiers)); - accelerator->set_platform_accelerator(platform_accelerator.Pass()); + accelerator->set_platform_accelerator(std::move(platform_accelerator)); } } // namespace accelerator_util diff --git a/atom/browser/ui/cocoa/atom_menu_controller.mm b/atom/browser/ui/cocoa/atom_menu_controller.mm index 9c8c99da9aa6..24098914b7cb 100644 --- a/atom/browser/ui/cocoa/atom_menu_controller.mm +++ b/atom/browser/ui/cocoa/atom_menu_controller.mm @@ -32,9 +32,12 @@ Role kRolesMap[] = { { @selector(cut:), "cut" }, { @selector(copy:), "copy" }, { @selector(paste:), "paste" }, + { @selector(delete:), "delete" }, + { @selector(pasteAndMatchStyle:), "paste-and-match-style" }, { @selector(selectAll:), "selectall" }, { @selector(performMiniaturize:), "minimize" }, { @selector(performClose:), "close" }, + { @selector(performZoom:), "zoom" }, }; } // namespace diff --git a/atom/browser/ui/file_dialog.h b/atom/browser/ui/file_dialog.h index 51d7f5ee9d32..b857648161b5 100644 --- a/atom/browser/ui/file_dialog.h +++ b/atom/browser/ui/file_dialog.h @@ -37,6 +37,7 @@ typedef base::Callback(iter->data)); + base::FilePath path = AddExtensionForFilename( + static_cast(iter->data)); g_free(iter->data); paths.push_back(path); } @@ -135,11 +141,13 @@ class FileChooserDialog { private: void AddFilters(const Filters& filters); + base::FilePath AddExtensionForFilename(const gchar* filename) const; atom::NativeWindow::DialogScope dialog_scope_; GtkWidget* dialog_; + Filters filters_; SaveDialogCallback save_callback_; OpenDialogCallback open_callback_; @@ -147,7 +155,7 @@ class FileChooserDialog { }; void FileChooserDialog::OnFileDialogResponse(GtkWidget* widget, int response) { - gtk_widget_hide_all(dialog_); + gtk_widget_hide(dialog_); if (!save_callback_.is_null()) { if (response == GTK_RESPONSE_ACCEPT) @@ -169,7 +177,7 @@ void FileChooserDialog::AddFilters(const Filters& filters) { GtkFileFilter* gtk_filter = gtk_file_filter_new(); for (size_t j = 0; j < filter.second.size(); ++j) { - scoped_ptr file_extension( + std::unique_ptr file_extension( new std::string("." + filter.second[j])); gtk_file_filter_add_custom( gtk_filter, @@ -184,10 +192,35 @@ void FileChooserDialog::AddFilters(const Filters& filters) { } } +base::FilePath FileChooserDialog::AddExtensionForFilename( + const gchar* filename) const { + base::FilePath path(filename); + GtkFileFilter* selected_filter = + gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog_)); + if (!selected_filter) + return path; + + GSList* filters = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(dialog_)); + int i = g_slist_index(filters, selected_filter); + g_slist_free(filters); + if (i >= filters_.size()) + return path; + + const auto& extensions = filters_[i].second; + for (const auto& extension : extensions) { + if (extension == "*" || path.MatchesExtension("." + extension)) + return path; + } + + return path.ReplaceExtension(extensions[0]); +} + + } // namespace bool ShowOpenDialog(atom::NativeWindow* parent_window, const std::string& title, + const std::string& button_label, const base::FilePath& default_path, const Filters& filters, int properties, @@ -195,8 +228,8 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window, GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN; if (properties & FILE_DIALOG_OPEN_DIRECTORY) action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; - FileChooserDialog open_dialog(action, parent_window, title, default_path, - filters); + FileChooserDialog open_dialog(action, parent_window, title, button_label, + default_path, filters); if (properties & FILE_DIALOG_MULTI_SELECTIONS) gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(open_dialog.dialog()), TRUE); @@ -213,6 +246,7 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window, void ShowOpenDialog(atom::NativeWindow* parent_window, const std::string& title, + const std::string& button_label, const base::FilePath& default_path, const Filters& filters, int properties, @@ -221,7 +255,7 @@ void ShowOpenDialog(atom::NativeWindow* parent_window, if (properties & FILE_DIALOG_OPEN_DIRECTORY) action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; FileChooserDialog* open_dialog = new FileChooserDialog( - action, parent_window, title, default_path, filters); + action, parent_window, title, button_label, default_path, filters); if (properties & FILE_DIALOG_MULTI_SELECTIONS) gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(open_dialog->dialog()), TRUE); @@ -231,11 +265,12 @@ void ShowOpenDialog(atom::NativeWindow* parent_window, bool ShowSaveDialog(atom::NativeWindow* parent_window, const std::string& title, + const std::string& button_label, const base::FilePath& default_path, const Filters& filters, base::FilePath* path) { FileChooserDialog save_dialog(GTK_FILE_CHOOSER_ACTION_SAVE, parent_window, - title, default_path, filters); + title, button_label, default_path, filters); gtk_widget_show_all(save_dialog.dialog()); int response = gtk_dialog_run(GTK_DIALOG(save_dialog.dialog())); if (response == GTK_RESPONSE_ACCEPT) { @@ -248,12 +283,13 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, void ShowSaveDialog(atom::NativeWindow* parent_window, const std::string& title, + const std::string& button_label, const base::FilePath& default_path, const Filters& filters, const SaveDialogCallback& callback) { FileChooserDialog* save_dialog = new FileChooserDialog( - GTK_FILE_CHOOSER_ACTION_SAVE, parent_window, title, default_path, - filters); + GTK_FILE_CHOOSER_ACTION_SAVE, parent_window, title, button_label, + default_path, filters); save_dialog->RunSaveAsynchronous(callback); } diff --git a/atom/browser/ui/file_dialog_mac.mm b/atom/browser/ui/file_dialog_mac.mm index 49662c69b3b8..d674817a2a41 100644 --- a/atom/browser/ui/file_dialog_mac.mm +++ b/atom/browser/ui/file_dialog_mac.mm @@ -45,11 +45,15 @@ void SetAllowedFileTypes(NSSavePanel* dialog, const Filters& filters) { void SetupDialog(NSSavePanel* dialog, const std::string& title, + const std::string& button_label, const base::FilePath& default_path, const Filters& filters) { if (!title.empty()) [dialog setTitle:base::SysUTF8ToNSString(title)]; + if (!button_label.empty()) + [dialog setPrompt:base::SysUTF8ToNSString(button_label)]; + NSString* default_dir = nil; NSString* default_filename = nil; if (!default_path.empty()) { @@ -114,6 +118,7 @@ void ReadDialogPaths(NSOpenPanel* dialog, std::vector* paths) { bool ShowOpenDialog(atom::NativeWindow* parent_window, const std::string& title, + const std::string& button_label, const base::FilePath& default_path, const Filters& filters, int properties, @@ -121,7 +126,7 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window, DCHECK(paths); NSOpenPanel* dialog = [NSOpenPanel openPanel]; - SetupDialog(dialog, title, default_path, filters); + SetupDialog(dialog, title, button_label, default_path, filters); SetupDialogForProperties(dialog, properties); int chosen = RunModalDialog(dialog, parent_window); @@ -134,13 +139,14 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window, void ShowOpenDialog(atom::NativeWindow* parent_window, const std::string& title, + const std::string& button_label, const base::FilePath& default_path, const Filters& filters, int properties, const OpenDialogCallback& c) { NSOpenPanel* dialog = [NSOpenPanel openPanel]; - SetupDialog(dialog, title, default_path, filters); + SetupDialog(dialog, title, button_label, default_path, filters); SetupDialogForProperties(dialog, properties); // Duplicate the callback object here since c is a reference and gcd would @@ -162,13 +168,14 @@ void ShowOpenDialog(atom::NativeWindow* parent_window, bool ShowSaveDialog(atom::NativeWindow* parent_window, const std::string& title, + const std::string& button_label, const base::FilePath& default_path, const Filters& filters, base::FilePath* path) { DCHECK(path); NSSavePanel* dialog = [NSSavePanel savePanel]; - SetupDialog(dialog, title, default_path, filters); + SetupDialog(dialog, title, button_label, default_path, filters); int chosen = RunModalDialog(dialog, parent_window); if (chosen == NSFileHandlingPanelCancelButton || ![[dialog URL] isFileURL]) @@ -180,12 +187,13 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, void ShowSaveDialog(atom::NativeWindow* parent_window, const std::string& title, + const std::string& button_label, const base::FilePath& default_path, const Filters& filters, const SaveDialogCallback& c) { NSSavePanel* dialog = [NSSavePanel savePanel]; - SetupDialog(dialog, title, default_path, filters); + SetupDialog(dialog, title, button_label, default_path, filters); __block SaveDialogCallback callback = c; diff --git a/atom/browser/ui/file_dialog_win.cc b/atom/browser/ui/file_dialog_win.cc index 6577e4c08404..5314b63a45a8 100644 --- a/atom/browser/ui/file_dialog_win.cc +++ b/atom/browser/ui/file_dialog_win.cc @@ -63,7 +63,9 @@ void ConvertFilters(const Filters& filters, template class FileDialog { public: - FileDialog(const base::FilePath& default_path, const std::string& title, + FileDialog(const base::FilePath& default_path, + const std::string& title, + const std::string& button_label, const Filters& filters, int options) { std::wstring file_part; if (!IsDirectory(default_path)) @@ -79,6 +81,9 @@ class FileDialog { if (!title.empty()) GetPtr()->SetTitle(base::UTF8ToUTF16(title).c_str()); + if (!button_label.empty()) + GetPtr()->SetOkButtonLabel(base::UTF8ToUTF16(button_label).c_str()); + // By default, *.* will be added to the file name if file type is "*.*". In // Electron, we disable it to make a better experience. // @@ -129,7 +134,7 @@ class FileDialog { GetPtr()->SetFolder(folder_item); } - scoped_ptr dialog_; + std::unique_ptr dialog_; DISALLOW_COPY_AND_ASSIGN(FileDialog); }; @@ -140,7 +145,7 @@ struct RunState { }; bool CreateDialogThread(RunState* run_state) { - scoped_ptr thread( + std::unique_ptr thread( new base::Thread(ATOM_PRODUCT_NAME "FileDialogThread")); thread->init_com_with_mta(false); if (!thread->Start()) @@ -154,13 +159,14 @@ bool CreateDialogThread(RunState* run_state) { void RunOpenDialogInNewThread(const RunState& run_state, atom::NativeWindow* parent, const std::string& title, + const std::string& button_label, const base::FilePath& default_path, const Filters& filters, int properties, const OpenDialogCallback& callback) { std::vector paths; - bool result = ShowOpenDialog(parent, title, default_path, filters, properties, - &paths); + bool result = ShowOpenDialog(parent, title, button_label, default_path, + filters, properties, &paths); run_state.ui_message_loop->PostTask(FROM_HERE, base::Bind(callback, result, paths)); run_state.ui_message_loop->DeleteSoon(FROM_HERE, run_state.dialog_thread); @@ -169,11 +175,13 @@ void RunOpenDialogInNewThread(const RunState& run_state, void RunSaveDialogInNewThread(const RunState& run_state, atom::NativeWindow* parent, const std::string& title, + const std::string& button_label, const base::FilePath& default_path, const Filters& filters, const SaveDialogCallback& callback) { base::FilePath path; - bool result = ShowSaveDialog(parent, title, default_path, filters, &path); + bool result = ShowSaveDialog(parent, title, button_label, default_path, + filters, &path); run_state.ui_message_loop->PostTask(FROM_HERE, base::Bind(callback, result, path)); run_state.ui_message_loop->DeleteSoon(FROM_HERE, run_state.dialog_thread); @@ -183,6 +191,7 @@ void RunSaveDialogInNewThread(const RunState& run_state, bool ShowOpenDialog(atom::NativeWindow* parent_window, const std::string& title, + const std::string& button_label, const base::FilePath& default_path, const Filters& filters, int properties, @@ -194,7 +203,7 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window, options |= FOS_ALLOWMULTISELECT; FileDialog open_dialog( - default_path, title, filters, options); + default_path, title, button_label, filters, options); if (!open_dialog.Show(parent_window)) return false; @@ -230,6 +239,7 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window, void ShowOpenDialog(atom::NativeWindow* parent, const std::string& title, + const std::string& button_label, const base::FilePath& default_path, const Filters& filters, int properties, @@ -243,16 +253,17 @@ void ShowOpenDialog(atom::NativeWindow* parent, run_state.dialog_thread->message_loop()->PostTask( FROM_HERE, base::Bind(&RunOpenDialogInNewThread, run_state, parent, title, - default_path, filters, properties, callback)); + button_label, default_path, filters, properties, callback)); } bool ShowSaveDialog(atom::NativeWindow* parent_window, const std::string& title, + const std::string& button_label, const base::FilePath& default_path, const Filters& filters, base::FilePath* path) { FileDialog save_dialog( - default_path, title, filters, + default_path, title, button_label, filters, FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST | FOS_OVERWRITEPROMPT); if (!save_dialog.Show(parent_window)) return false; @@ -268,6 +279,7 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, void ShowSaveDialog(atom::NativeWindow* parent, const std::string& title, + const std::string& button_label, const base::FilePath& default_path, const Filters& filters, const SaveDialogCallback& callback) { @@ -280,7 +292,7 @@ void ShowSaveDialog(atom::NativeWindow* parent, run_state.dialog_thread->message_loop()->PostTask( FROM_HERE, base::Bind(&RunSaveDialogInNewThread, run_state, parent, title, - default_path, filters, callback)); + button_label, default_path, filters, callback)); } } // namespace file_dialog diff --git a/atom/browser/ui/message_box_gtk.cc b/atom/browser/ui/message_box_gtk.cc index 9615e958b275..b09ccdd48279 100644 --- a/atom/browser/ui/message_box_gtk.cc +++ b/atom/browser/ui/message_box_gtk.cc @@ -53,9 +53,16 @@ class GtkMessageBox { // Set dialog's icon. if (!icon.isNull()) { GdkPixbuf* pixbuf = libgtk2ui::GdkPixbufFromSkBitmap(*icon.bitmap()); - GtkWidget* image = gtk_image_new_from_pixbuf(pixbuf); + GtkIconSource* iconsource = gtk_icon_source_new(); + GtkIconSet* iconset = gtk_icon_set_new(); + gtk_icon_source_set_pixbuf(iconsource, pixbuf); + gtk_icon_set_add_source(iconset, iconsource); + GtkWidget* image = gtk_image_new_from_icon_set(iconset, + GTK_ICON_SIZE_DIALOG); gtk_message_dialog_set_image(GTK_MESSAGE_DIALOG(dialog_), image); gtk_widget_show(image); + gtk_icon_source_free(iconsource); + gtk_icon_set_unref(iconset); g_object_unref(pixbuf); } @@ -149,7 +156,7 @@ class GtkMessageBox { }; void GtkMessageBox::OnResponseDialog(GtkWidget* widget, int response) { - gtk_widget_hide_all(dialog_); + gtk_widget_hide(dialog_); if (response < 0) callback_.Run(cancel_id_); diff --git a/atom/browser/ui/message_box_mac.mm b/atom/browser/ui/message_box_mac.mm index 9380f01e384d..d553a6a7dfbf 100644 --- a/atom/browser/ui/message_box_mac.mm +++ b/atom/browser/ui/message_box_mac.mm @@ -96,7 +96,7 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window, } if (!icon.isNull()) { - NSImage* image = gfx::SkBitmapToNSImageWithColorSpace( + NSImage* image = skia::SkBitmapToNSImageWithColorSpace( *icon.bitmap(), base::mac::GetGenericRGBColorSpace()); [alert setIcon:image]; } diff --git a/atom/browser/ui/message_box_win.cc b/atom/browser/ui/message_box_win.cc index 2847ae21a164..b966ef92a8fd 100644 --- a/atom/browser/ui/message_box_win.cc +++ b/atom/browser/ui/message_box_win.cc @@ -64,7 +64,8 @@ void MapToCommonID(const std::vector& buttons, (*button_flags) |= common.button; } else { // It is a custom button. - dialog_buttons->push_back({i + kIDStart, buttons[i].c_str()}); + dialog_buttons->push_back( + {static_cast(i + kIDStart), buttons[i].c_str()}); } } } @@ -102,9 +103,9 @@ int ShowMessageBoxUTF16(HWND parent, base::win::ScopedHICON hicon; if (!icon.isNull()) { - hicon.Set(IconUtil::CreateHICONFromSkBitmap(*icon.bitmap())); + hicon = IconUtil::CreateHICONFromSkBitmap(*icon.bitmap()); config.dwFlags |= TDF_USE_HICON_MAIN; - config.hMainIcon = hicon.Get(); + config.hMainIcon = hicon.get(); } else { // Show icon according to dialog's type. switch (type) { @@ -135,7 +136,8 @@ int ShowMessageBoxUTF16(HWND parent, std::vector dialog_buttons; if (options & MESSAGE_BOX_NO_LINK) { for (size_t i = 0; i < buttons.size(); ++i) - dialog_buttons.push_back({i + kIDStart, buttons[i].c_str()}); + dialog_buttons.push_back( + {static_cast(i + kIDStart), buttons[i].c_str()}); } else { MapToCommonID(buttons, &id_map, &config.dwCommonButtons, &dialog_buttons); } @@ -220,7 +222,7 @@ void ShowMessageBox(NativeWindow* parent, const std::string& detail, const gfx::ImageSkia& icon, const MessageBoxCallback& callback) { - scoped_ptr thread( + std::unique_ptr thread( new base::Thread(ATOM_PRODUCT_NAME "MessageBoxThread")); thread->init_com_with_mta(false); if (!thread->Start()) { diff --git a/atom/browser/ui/tray_icon.cc b/atom/browser/ui/tray_icon.cc index 60923c2ad0a6..dcdb90ac159c 100644 --- a/atom/browser/ui/tray_icon.cc +++ b/atom/browser/ui/tray_icon.cc @@ -12,7 +12,7 @@ TrayIcon::TrayIcon() { TrayIcon::~TrayIcon() { } -void TrayIcon::SetPressedImage(const gfx::Image& image) { +void TrayIcon::SetPressedImage(ImageType image) { } void TrayIcon::SetTitle(const std::string& title) { @@ -21,7 +21,7 @@ void TrayIcon::SetTitle(const std::string& title) { void TrayIcon::SetHighlightMode(bool highlight) { } -void TrayIcon::DisplayBalloon(const gfx::Image& icon, +void TrayIcon::DisplayBalloon(ImageType icon, const base::string16& title, const base::string16& contents) { } diff --git a/atom/browser/ui/tray_icon.h b/atom/browser/ui/tray_icon.h index c80ff08d6a52..dd018821cd3a 100644 --- a/atom/browser/ui/tray_icon.h +++ b/atom/browser/ui/tray_icon.h @@ -19,13 +19,19 @@ class TrayIcon { public: static TrayIcon* Create(); +#if defined(OS_WIN) + using ImageType = HICON; +#else + using ImageType = const gfx::Image&; +#endif + virtual ~TrayIcon(); // Sets the image associated with this status icon. - virtual void SetImage(const gfx::Image& image) = 0; + virtual void SetImage(ImageType image) = 0; // Sets the image associated with this status icon when pressed. - virtual void SetPressedImage(const gfx::Image& image); + virtual void SetPressedImage(ImageType image); // Sets the hover text for this status icon. This is also used as the label // for the menu item which is created as a replacement for the status icon @@ -43,7 +49,7 @@ class TrayIcon { // Displays a notification balloon with the specified contents. // Depending on the platform it might not appear by the icon tray. - virtual void DisplayBalloon(const gfx::Image& icon, + virtual void DisplayBalloon(ImageType icon, const base::string16& title, const base::string16& contents); diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index d2a2fe83460e..c3fa3f3b9272 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -27,7 +27,6 @@ const CGFloat kVerticalTitleMargin = 2; BOOL inMouseEventSequence_; base::scoped_nsobject image_; base::scoped_nsobject alternateImage_; - base::scoped_nsobject image_view_; base::scoped_nsobject title_; base::scoped_nsobject statusItem_; } @@ -44,15 +43,6 @@ const CGFloat kVerticalTitleMargin = 2; inMouseEventSequence_ = NO; if ((self = [super initWithFrame: CGRectZero])) { - // Setup the image view. - image_view_.reset([[NSImageView alloc] initWithFrame: CGRectZero]); - [image_view_ setImageScaling:NSImageScaleNone]; - [image_view_ setImageAlignment:NSImageAlignCenter]; - [self addSubview:image_view_]; - - // Unregister image_view_ as a dragged destination, allows its parent view - // (StatusItemView) handle dragging events. - [image_view_ unregisterDraggedTypes]; [self registerForDraggedTypes: @[NSFilenamesPboardType]]; // Create the status item. @@ -69,7 +59,6 @@ const CGFloat kVerticalTitleMargin = 2; - (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]; } @@ -85,28 +74,44 @@ const CGFloat kVerticalTitleMargin = 2; // | icon | title | /// ---------------- - // Draw background. BOOL highlight = [self shouldHighlight]; + BOOL highlightContent = highlight | [self isDarkMode]; CGFloat thickness = [[statusItem_ statusBar] thickness]; + + // Draw the system bar background. [statusItem_ drawStatusBarBackgroundInRect:self.bounds withHighlight:highlight]; - // Make use of NSImageView to draw the image, which can correctly draw - // template image under dark menu bar. - if (inMouseEventSequence_ && alternateImage_ && - [image_view_ image] != alternateImage_.get()) { - [image_view_ setImage:alternateImage_]; - } else if ([image_view_ image] != image_.get()) { - [image_view_ setImage:image_]; + // Determine which image to use. + NSImage* image = image_.get(); + if (inMouseEventSequence_ && alternateImage_) { + image = alternateImage_.get(); + } + // Apply the higlight color if the image is a template image. When this moves + // to using the new [NSStatusItem button] API, this should work automagically. + if ([image isTemplate] == YES) { + NSImage* imageWithColor = [[image copy] autorelease]; + [imageWithColor lockFocus]; + [[self colorWithHighlight: highlightContent] set]; + CGRect imageBounds = CGRectMake(0,0, image.size.width, image.size.height); + NSRectFillUsingOperation(imageBounds, NSCompositeSourceAtop); + [imageWithColor unlockFocus]; + image = imageWithColor; } + // Draw the image + [image drawInRect: CGRectMake( + roundf(([self iconWidth] - image.size.width) / 2), + roundf((thickness - image.size.height) / 2), + image.size.width, + image.size.height + )]; + if (title_) { - // Highlight the text when icon is highlighted or in dark mode. - highlight |= [self isDarkMode]; // Draw title. NSRect titleDrawRect = NSMakeRect( [self iconWidth], -kVerticalTitleMargin, [self titleWidth], thickness); [title_ drawInRect:titleDrawRect - withAttributes:[self titleAttributesWithHighlight:highlight]]; + withAttributes:[self titleAttributesWithHighlight:highlightContent]]; } } @@ -157,14 +162,16 @@ const CGFloat kVerticalTitleMargin = 2; return [attributes size].width; } -- (NSDictionary*)titleAttributesWithHighlight:(BOOL)highlight { - NSFont* font = [NSFont menuBarFontOfSize:0]; - NSColor* foregroundColor = highlight ? +- (NSColor*)colorWithHighlight:(BOOL)highlight { + return highlight ? [NSColor whiteColor] : [NSColor colorWithRed:0.265625 green:0.25390625 blue:0.234375 alpha:1.0]; +} + +- (NSDictionary*)titleAttributesWithHighlight:(BOOL)highlight { return @{ - NSFontAttributeName: font, - NSForegroundColorAttributeName: foregroundColor + NSFontAttributeName:[NSFont menuBarFontOfSize:0], + NSForegroundColorAttributeName:[self colorWithHighlight: highlight] }; } diff --git a/atom/browser/ui/tray_icon_gtk.h b/atom/browser/ui/tray_icon_gtk.h index 2be3259f218d..cfeb8ab3d0bf 100644 --- a/atom/browser/ui/tray_icon_gtk.h +++ b/atom/browser/ui/tray_icon_gtk.h @@ -32,7 +32,7 @@ class TrayIconGtk : public TrayIcon, void OnClick() override; bool HasClickAction() override; - scoped_ptr icon_; + std::unique_ptr icon_; DISALLOW_COPY_AND_ASSIGN(TrayIconGtk); }; diff --git a/atom/browser/ui/views/global_menu_bar_x11.h b/atom/browser/ui/views/global_menu_bar_x11.h index d9e818ce3eb6..89b2680cabeb 100644 --- a/atom/browser/ui/views/global_menu_bar_x11.h +++ b/atom/browser/ui/views/global_menu_bar_x11.h @@ -7,7 +7,7 @@ #include -#include "base/basictypes.h" +#include "base/macros.h" #include "base/compiler_specific.h" #include "ui/base/glib/glib_signal.h" #include "ui/gfx/native_widget_types.h" @@ -40,7 +40,7 @@ class GlobalMenuBarX11 { explicit GlobalMenuBarX11(NativeWindowViews* window); virtual ~GlobalMenuBarX11(); - // Creates the object path for DbusemenuServer which is attached to |xid|. + // Creates the object path for DbusmenuServer which is attached to |xid|. static std::string GetPathForWindow(gfx::AcceleratedWidget xid); void SetMenu(ui::MenuModel* menu_model); diff --git a/atom/browser/ui/views/menu_bar.cc b/atom/browser/ui/views/menu_bar.cc index ba0a542c1e8d..872375ccbbd1 100644 --- a/atom/browser/ui/views/menu_bar.cc +++ b/atom/browser/ui/views/menu_bar.cc @@ -104,7 +104,7 @@ int MenuBar::GetAcceleratorIndex(base::char16 key) { void MenuBar::ActivateAccelerator(base::char16 key) { int i = GetAcceleratorIndex(key); if (i != -1) - static_cast(child_at(i))->Activate(); + static_cast(child_at(i))->Activate(nullptr); } int MenuBar::GetItemCount() const { @@ -141,22 +141,22 @@ const char* MenuBar::GetClassName() const { void MenuBar::ButtonPressed(views::Button* sender, const ui::Event& event) { } -void MenuBar::OnMenuButtonClicked(views::View* source, - const gfx::Point& point) { +void MenuBar::OnMenuButtonClicked(views::MenuButton* source, + const gfx::Point& point, + const ui::Event* event) { // Hide the accelerator when a submenu is activated. SetAcceleratorVisibility(false); if (!menu_model_) return; - views::MenuButton* button = static_cast(source); - int id = button->tag(); + int id = source->tag(); ui::MenuModel::ItemType type = menu_model_->GetTypeAt(id); if (type != ui::MenuModel::TYPE_SUBMENU) return; MenuDelegate menu_delegate(this); - menu_delegate.RunMenu(menu_model_->GetSubmenuModelAt(id), button); + menu_delegate.RunMenu(menu_model_->GetSubmenuModelAt(id), source); } } // namespace atom diff --git a/atom/browser/ui/views/menu_bar.h b/atom/browser/ui/views/menu_bar.h index 9d77cfdf2a22..9f38a5981c7f 100644 --- a/atom/browser/ui/views/menu_bar.h +++ b/atom/browser/ui/views/menu_bar.h @@ -57,8 +57,10 @@ class MenuBar : public views::View, void ButtonPressed(views::Button* sender, const ui::Event& event) override; // views::MenuButtonListener: - void OnMenuButtonClicked(views::View* source, - const gfx::Point& point) override; + void OnMenuButtonClicked(views::MenuButton* source, + const gfx::Point& point, + const ui::Event* event) override; + private: SkColor background_color_; diff --git a/atom/browser/ui/views/menu_delegate.cc b/atom/browser/ui/views/menu_delegate.cc index f0ecf13b36b6..a107a696250b 100644 --- a/atom/browser/ui/views/menu_delegate.cc +++ b/atom/browser/ui/views/menu_delegate.cc @@ -112,7 +112,7 @@ views::MenuItemView* MenuDelegate::GetSiblingMenu( content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, base::Bind(base::IgnoreResult(&views::MenuButton::Activate), - base::Unretained(button))); + base::Unretained(button), nullptr)); } return nullptr; diff --git a/atom/browser/ui/views/menu_delegate.h b/atom/browser/ui/views/menu_delegate.h index 211ddb57bc39..ad7093b48f72 100644 --- a/atom/browser/ui/views/menu_delegate.h +++ b/atom/browser/ui/views/menu_delegate.h @@ -52,8 +52,8 @@ class MenuDelegate : public views::MenuDelegate { private: MenuBar* menu_bar_; int id_; - scoped_ptr adapter_; - scoped_ptr menu_runner_; + std::unique_ptr adapter_; + std::unique_ptr menu_runner_; DISALLOW_COPY_AND_ASSIGN(MenuDelegate); }; diff --git a/atom/browser/ui/views/submenu_button.cc b/atom/browser/ui/views/submenu_button.cc index 7f413d519c2b..ca06d7d627ef 100644 --- a/atom/browser/ui/views/submenu_button.cc +++ b/atom/browser/ui/views/submenu_button.cc @@ -26,7 +26,7 @@ base::string16 FilterAccelerator(const base::string16& label) { SubmenuButton::SubmenuButton(views::ButtonListener* listener, const base::string16& title, views::MenuButtonListener* menu_button_listener) - : views::MenuButton(listener, FilterAccelerator(title), + : views::MenuButton(FilterAccelerator(title), menu_button_listener, false), accelerator_(0), show_underline_(false), @@ -37,7 +37,7 @@ SubmenuButton::SubmenuButton(views::ButtonListener* listener, underline_color_(SK_ColorBLACK) { #if defined(OS_LINUX) // Dont' use native style border. - SetBorder(CreateDefaultBorder().Pass()); + SetBorder(std::move(CreateDefaultBorder())); #endif if (GetUnderlinePosition(title, &accelerator_, &underline_start_, diff --git a/atom/browser/ui/win/notify_icon.cc b/atom/browser/ui/win/notify_icon.cc index 1ac29f1360cb..1cc616216c6e 100644 --- a/atom/browser/ui/win/notify_icon.cc +++ b/atom/browser/ui/win/notify_icon.cc @@ -9,7 +9,6 @@ #include "base/strings/utf_string_conversions.h" #include "base/win/windows_version.h" #include "third_party/skia/include/core/SkBitmap.h" -#include "ui/gfx/icon_util.h" #include "ui/gfx/image/image.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" @@ -80,7 +79,7 @@ void NotifyIcon::ResetIcon() { InitIconData(&icon_data); icon_data.uFlags |= NIF_MESSAGE; icon_data.uCallbackMessage = message_id_; - icon_data.hIcon = icon_.Get(); + icon_data.hIcon = icon_.get(); // If we have an image, then set the NIF_ICON flag, which tells // Shell_NotifyIcon() to set the image for the status icon it creates. if (icon_data.hIcon) @@ -91,19 +90,20 @@ void NotifyIcon::ResetIcon() { LOG(WARNING) << "Unable to re-create status tray icon."; } -void NotifyIcon::SetImage(const gfx::Image& image) { +void NotifyIcon::SetImage(HICON image) { + icon_ = base::win::ScopedHICON(CopyIcon(image)); + // Create the icon. NOTIFYICONDATA icon_data; InitIconData(&icon_data); icon_data.uFlags |= NIF_ICON; - icon_.Set(IconUtil::CreateHICONFromSkBitmap(image.AsBitmap())); - icon_data.hIcon = icon_.Get(); + icon_data.hIcon = image; BOOL result = Shell_NotifyIcon(NIM_MODIFY, &icon_data); if (!result) LOG(WARNING) << "Error setting status tray icon image"; } -void NotifyIcon::SetPressedImage(const gfx::Image& image) { +void NotifyIcon::SetPressedImage(HICON image) { // Ignore pressed images, since the standard on Windows is to not highlight // pressed status icons. } @@ -119,7 +119,7 @@ void NotifyIcon::SetToolTip(const std::string& tool_tip) { LOG(WARNING) << "Unable to set tooltip for status tray icon"; } -void NotifyIcon::DisplayBalloon(const gfx::Image& icon, +void NotifyIcon::DisplayBalloon(HICON icon, const base::string16& title, const base::string16& contents) { NOTIFYICONDATA icon_data; @@ -129,13 +129,8 @@ void NotifyIcon::DisplayBalloon(const gfx::Image& icon, 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(); - if (!icon.IsEmpty() && win_version != base::win::VERSION_PRE_XP) { - balloon_icon_.Set(IconUtil::CreateHICONFromSkBitmap(icon.AsBitmap())); - icon_data.hBalloonIcon = balloon_icon_.Get(); - icon_data.dwInfoFlags = NIIF_USER | NIIF_LARGE_ICON; - } + icon_data.hBalloonIcon = icon; + icon_data.dwInfoFlags = NIIF_USER | NIIF_LARGE_ICON; BOOL result = Shell_NotifyIcon(NIM_MODIFY, &icon_data); if (!result) @@ -155,7 +150,7 @@ void NotifyIcon::PopUpContextMenu(const gfx::Point& pos, // Show menu at mouse's position by default. gfx::Rect rect(pos, gfx::Size()); if (pos.IsOrigin()) - rect.set_origin(gfx::Screen::GetNativeScreen()->GetCursorScreenPoint()); + rect.set_origin(gfx::Screen::GetScreen()->GetCursorScreenPoint()); views::MenuRunner menu_runner( menu_model, diff --git a/atom/browser/ui/win/notify_icon.h b/atom/browser/ui/win/notify_icon.h index 23608c7c7ab6..95e9945a17f8 100644 --- a/atom/browser/ui/win/notify_icon.h +++ b/atom/browser/ui/win/notify_icon.h @@ -11,7 +11,7 @@ #include #include "atom/browser/ui/tray_icon.h" -#include "base/basictypes.h" +#include "base/macros.h" #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" #include "base/win/scoped_gdi_object.h" @@ -45,10 +45,10 @@ class NotifyIcon : public TrayIcon { UINT message_id() const { return message_id_; } // Overridden from TrayIcon: - void SetImage(const gfx::Image& image) override; - void SetPressedImage(const gfx::Image& image) override; + void SetImage(HICON image) override; + void SetPressedImage(HICON image) override; void SetToolTip(const std::string& tool_tip) override; - void DisplayBalloon(const gfx::Image& icon, + void DisplayBalloon(HICON icon, const base::string16& title, const base::string16& contents) override; void PopUpContextMenu(const gfx::Point& pos, @@ -73,9 +73,6 @@ class NotifyIcon : public TrayIcon { // The currently-displayed icon for the window. base::win::ScopedHICON icon_; - // The currently-displayed icon for the notification balloon. - base::win::ScopedHICON balloon_icon_; - // The context menu. ui::SimpleMenuModel* menu_model_; diff --git a/atom/browser/ui/win/notify_icon_host.cc b/atom/browser/ui/win/notify_icon_host.cc index a0d4287ff614..1f0b35b09fd2 100644 --- a/atom/browser/ui/win/notify_icon_host.cc +++ b/atom/browser/ui/win/notify_icon_host.cc @@ -15,6 +15,7 @@ #include "base/win/win_util.h" #include "base/win/wrapped_window_proc.h" #include "ui/events/event_constants.h" +#include "ui/events/win/system_event_state_lookup.h" #include "ui/gfx/win/hwnd_util.h" namespace atom { @@ -35,11 +36,11 @@ bool IsWinPressed() { int GetKeyboardModifers() { int modifiers = ui::EF_NONE; - if (base::win::IsShiftPressed()) + if (ui::win::IsShiftPressed()) modifiers |= ui::EF_SHIFT_DOWN; - if (base::win::IsCtrlPressed()) + if (ui::win::IsCtrlPressed()) modifiers |= ui::EF_CONTROL_DOWN; - if (base::win::IsAltPressed()) + if (ui::win::IsAltPressed()) modifiers |= ui::EF_ALT_DOWN; if (IsWinPressed()) modifiers |= ui::EF_COMMAND_DOWN; diff --git a/atom/browser/ui/win/notify_icon_host.h b/atom/browser/ui/win/notify_icon_host.h index 6797d4f6a54c..773b3112d470 100644 --- a/atom/browser/ui/win/notify_icon_host.h +++ b/atom/browser/ui/win/notify_icon_host.h @@ -9,8 +9,7 @@ #include -#include "base/compiler_specific.h" -#include "base/memory/scoped_ptr.h" +#include "base/macros.h" namespace atom { diff --git a/atom/browser/ui/win/taskbar_host.cc b/atom/browser/ui/win/taskbar_host.cc index 0d250829110f..f7841cfa856c 100644 --- a/atom/browser/ui/win/taskbar_host.cc +++ b/atom/browser/ui/win/taskbar_host.cc @@ -91,7 +91,7 @@ bool TaskbarHost::SetThumbarButtons( if (!button.icon.IsEmpty()) { thumb_button.dwMask |= THB_ICON; icons[i] = IconUtil::CreateHICONFromSkBitmap(button.icon.AsBitmap()); - thumb_button.hIcon = icons[i].Get(); + thumb_button.hIcon = icons[i].get(); } // Set tooltip. @@ -138,8 +138,8 @@ bool TaskbarHost::SetOverlayIcon( base::win::ScopedHICON icon( IconUtil::CreateHICONFromSkBitmap(overlay.AsBitmap())); - return SUCCEEDED( - taskbar_->SetOverlayIcon(window, icon, base::UTF8ToUTF16(text).c_str())); + return SUCCEEDED(taskbar_->SetOverlayIcon( + window, icon.get(), base::UTF8ToUTF16(text).c_str())); } bool TaskbarHost::HandleThumbarButtonEvent(int button_id) { diff --git a/atom/browser/ui/x/x_window_utils.cc b/atom/browser/ui/x/x_window_utils.cc index db83753bb376..48e8bc977322 100644 --- a/atom/browser/ui/x/x_window_utils.cc +++ b/atom/browser/ui/x/x_window_utils.cc @@ -51,7 +51,7 @@ void SetWindowType(::Window xwindow, const std::string& type) { } bool ShouldUseGlobalMenuBar() { - scoped_ptr env(base::Environment::Create()); + std::unique_ptr env(base::Environment::Create()); if (env->HasVar("ELECTRON_FORCE_WINDOW_MENU_BAR")) return false; @@ -61,7 +61,7 @@ bool ShouldUseGlobalMenuBar() { dbus::ObjectProxy* object_proxy = bus->GetObjectProxy(DBUS_SERVICE_DBUS, dbus::ObjectPath(DBUS_PATH_DBUS)); dbus::MethodCall method_call(DBUS_INTERFACE_DBUS, "ListNames"); - scoped_ptr response(object_proxy->CallMethodAndBlock( + std::unique_ptr response(object_proxy->CallMethodAndBlock( &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); if (!response) { bus->ShutdownAndBlock(); diff --git a/atom/browser/web_contents_permission_helper.cc b/atom/browser/web_contents_permission_helper.cc index d018e1d09dbc..73405b571934 100644 --- a/atom/browser/web_contents_permission_helper.cc +++ b/atom/browser/web_contents_permission_helper.cc @@ -34,8 +34,8 @@ void OnPointerLockResponse(content::WebContents* web_contents, bool allowed) { } void OnPermissionResponse(const base::Callback& callback, - content::PermissionStatus status) { - if (status == content::PERMISSION_STATUS_GRANTED) + blink::mojom::PermissionStatus status) { + if (status == blink::mojom::PermissionStatus::GRANTED) callback.Run(true); else callback.Run(false); @@ -60,7 +60,7 @@ void WebContentsPermissionHelper::RequestPermission( web_contents_->GetBrowserContext()->GetPermissionManager()); auto origin = web_contents_->GetLastCommittedURL(); permission_manager->RequestPermission( - permission, rfh, origin, user_gesture, + permission, rfh, origin, base::Bind(&OnPermissionResponse, callback)); } @@ -91,4 +91,12 @@ void WebContentsPermissionHelper::RequestPointerLockPermission( user_gesture); } +void WebContentsPermissionHelper::RequestOpenExternalPermission( + const base::Callback& callback, + bool user_gesture) { + RequestPermission((content::PermissionType)(PermissionType::OPEN_EXTERNAL), + callback, + user_gesture); +} + } // namespace atom diff --git a/atom/browser/web_contents_permission_helper.h b/atom/browser/web_contents_permission_helper.h index 90ae6dff56f5..89da64b75833 100644 --- a/atom/browser/web_contents_permission_helper.h +++ b/atom/browser/web_contents_permission_helper.h @@ -19,7 +19,8 @@ class WebContentsPermissionHelper enum class PermissionType { POINTER_LOCK = static_cast(content::PermissionType::NUM) + 1, - FULLSCREEN + FULLSCREEN, + OPEN_EXTERNAL, }; void RequestFullscreenPermission( @@ -30,6 +31,9 @@ class WebContentsPermissionHelper void RequestWebNotificationPermission( const base::Callback& callback); void RequestPointerLockPermission(bool user_gesture); + void RequestOpenExternalPermission( + const base::Callback& callback, + bool user_gesture); private: explicit WebContentsPermissionHelper(content::WebContents* web_contents); diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index fb67d2516eb2..a32e23de68f5 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -4,12 +4,17 @@ #include "atom/browser/web_contents_preferences.h" +#include #include +#include +#include "atom/browser/native_window.h" +#include "atom/browser/web_view_manager.h" #include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/options_switches.h" #include "base/command_line.h" #include "base/strings/string_number_conversions.h" +#include "content/public/browser/render_process_host.h" #include "content/public/common/content_switches.h" #include "content/public/common/web_preferences.h" #include "native_mate/dictionary.h" @@ -23,9 +28,13 @@ DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::WebContentsPreferences); namespace atom { +// static +std::vector WebContentsPreferences::instances_; + WebContentsPreferences::WebContentsPreferences( content::WebContents* web_contents, - const mate::Dictionary& web_preferences) { + const mate::Dictionary& web_preferences) + : web_contents_(web_contents) { v8::Isolate* isolate = web_preferences.isolate(); mate::Dictionary copied(isolate, web_preferences.GetHandle()->Clone()); // Following fields should not be stored. @@ -35,15 +44,31 @@ WebContentsPreferences::WebContentsPreferences( mate::ConvertFromV8(isolate, copied.GetHandle(), &web_preferences_); web_contents->SetUserData(UserDataKey(), this); + + instances_.push_back(this); } WebContentsPreferences::~WebContentsPreferences() { + instances_.erase( + std::remove(instances_.begin(), instances_.end(), this), + instances_.end()); } void WebContentsPreferences::Merge(const base::DictionaryValue& extend) { web_preferences_.MergeDictionary(&extend); } +// static +content::WebContents* WebContentsPreferences::GetWebContentsFromProcessID( + int process_id) { + for (WebContentsPreferences* preferences : instances_) { + content::WebContents* web_contents = preferences->web_contents_; + if (web_contents->GetRenderProcessHost()->GetID() == process_id) + return web_contents; + } + return nullptr; +} + // static void WebContentsPreferences::AppendExtraCommandLineSwitches( content::WebContents* web_contents, base::CommandLine* command_line) { @@ -74,11 +99,6 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( // Check if we have node integration specified. bool node_integration = true; web_preferences.GetBoolean(options::kNodeIntegration, &node_integration); - // Be compatible with old API of "node-integration" option. - std::string old_token; - if (web_preferences.GetString(options::kNodeIntegration, &old_token) && - old_token != "disable") - node_integration = true; command_line->AppendSwitchASCII(switches::kNodeIntegration, node_integration ? "true" : "false"); @@ -98,6 +118,11 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( LOG(ERROR) << "preload url must be file:// protocol."; } + // --background-color. + std::string color; + if (web_preferences.GetString(options::kBackgroundColor, &color)) + command_line->AppendSwitchASCII(switches::kBackgroundColor, color); + // The zoom factor. double zoom_factor = 1.0; if (web_preferences.GetDouble(options::kZoomFactor, &zoom_factor) && @@ -106,22 +131,59 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( base::DoubleToString(zoom_factor)); // --guest-instance-id, which is used to identify guest WebContents. - int guest_instance_id; + int guest_instance_id = 0; if (web_preferences.GetInteger(options::kGuestInstanceID, &guest_instance_id)) - command_line->AppendSwitchASCII(switches::kGuestInstanceID, - base::IntToString(guest_instance_id)); + command_line->AppendSwitchASCII(switches::kGuestInstanceID, + base::IntToString(guest_instance_id)); // Pass the opener's window id. int opener_id; if (web_preferences.GetInteger(options::kOpenerID, &opener_id)) - command_line->AppendSwitchASCII(switches::kOpenerID, - base::IntToString(opener_id)); + command_line->AppendSwitchASCII(switches::kOpenerID, + base::IntToString(opener_id)); + +#if defined(OS_MACOSX) + // Enable scroll bounce. + bool scroll_bounce; + if (web_preferences.GetBoolean(options::kScrollBounce, &scroll_bounce) && + scroll_bounce) + command_line->AppendSwitch(switches::kScrollBounce); +#endif + + // Custom command line switches. + const base::ListValue* args; + if (web_preferences.GetList("commandLineSwitches", &args)) { + for (size_t i = 0; i < args->GetSize(); ++i) { + std::string arg; + if (args->GetString(i, &arg) && !arg.empty()) + command_line->AppendSwitch(arg); + } + } // Enable blink features. std::string blink_features; if (web_preferences.GetString(options::kBlinkFeatures, &blink_features)) - command_line->AppendSwitchASCII(::switches::kEnableBlinkFeatures, - blink_features); + command_line->AppendSwitchASCII(::switches::kEnableBlinkFeatures, + blink_features); + + // The initial visibility state. + NativeWindow* window = NativeWindow::FromWebContents(web_contents); + + // Use embedder window for webviews + if (guest_instance_id && !window) { + auto manager = WebViewManager::GetWebViewManager(web_contents); + if (manager) { + auto embedder = manager->GetEmbedder(guest_instance_id); + if (embedder) + window = NativeWindow::FromWebContents(embedder); + } + } + + if (window) { + bool visible = window->IsVisible() && !window->IsMinimized(); + if (!visible) // Default state is visible. + command_line->AppendSwitch("hidden-page"); + } } // static @@ -140,8 +202,6 @@ void WebContentsPreferences::OverrideWebkitPrefs( 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("webSecurity", &b)) { prefs->web_security_enabled = b; prefs->allow_displaying_insecure_content = !b; diff --git a/atom/browser/web_contents_preferences.h b/atom/browser/web_contents_preferences.h index 8b04f9ee24e6..daf1f6e84de5 100644 --- a/atom/browser/web_contents_preferences.h +++ b/atom/browser/web_contents_preferences.h @@ -5,6 +5,8 @@ #ifndef ATOM_BROWSER_WEB_CONTENTS_PREFERENCES_H_ #define ATOM_BROWSER_WEB_CONTENTS_PREFERENCES_H_ +#include + #include "base/values.h" #include "content/public/browser/web_contents_user_data.h" @@ -26,6 +28,10 @@ namespace atom { class WebContentsPreferences : public content::WebContentsUserData { public: + // Get WebContents according to process ID. + // FIXME(zcbenz): This method does not belong here. + static content::WebContents* GetWebContentsFromProcessID(int process_id); + // Append command paramters according to |web_contents|'s preferences. static void AppendExtraCommandLineSwitches( content::WebContents* web_contents, base::CommandLine* command_line); @@ -47,6 +53,9 @@ class WebContentsPreferences private: friend class content::WebContentsUserData; + static std::vector instances_; + + content::WebContents* web_contents_; base::DictionaryValue web_preferences_; DISALLOW_COPY_AND_ASSIGN(WebContentsPreferences); diff --git a/atom/browser/web_dialog_helper.cc b/atom/browser/web_dialog_helper.cc index c3d2a1d0f23f..2d2a9e5db80d 100644 --- a/atom/browser/web_dialog_helper.cc +++ b/atom/browser/web_dialog_helper.cc @@ -13,7 +13,7 @@ #include "base/bind.h" #include "base/files/file_enumerator.h" #include "base/files/file_path.h" -#include "base/prefs/pref_service.h" +#include "components/prefs/pref_service.h" #include "base/strings/utf_string_conversions.h" #include "chrome/common/pref_names.h" #include "content/public/browser/render_view_host.h" @@ -84,6 +84,7 @@ void WebDialogHelper::RunFileChooser(content::WebContents* web_contents, base::FilePath path; if (file_dialog::ShowSaveDialog(window_, base::UTF16ToUTF8(params.title), + "", params.default_file_name, filters, &path)) { @@ -114,6 +115,7 @@ void WebDialogHelper::RunFileChooser(content::WebContents* web_contents, prefs::kSelectFileLastDirectory).Append(params.default_file_name); if (file_dialog::ShowOpenDialog(window_, base::UTF16ToUTF8(params.title), + "", default_file_path, filters, flags, diff --git a/atom/browser/web_view_guest_delegate.cc b/atom/browser/web_view_guest_delegate.cc index 8e1810c4a39b..11f8219bf088 100644 --- a/atom/browser/web_view_guest_delegate.cc +++ b/atom/browser/web_view_guest_delegate.cc @@ -9,6 +9,7 @@ #include "content/public/browser/guest_host.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host.h" #include "content/public/browser/render_widget_host_view.h" namespace atom { @@ -21,8 +22,7 @@ const int kDefaultHeight = 300; } // namespace WebViewGuestDelegate::WebViewGuestDelegate() - : guest_opaque_(true), - guest_host_(nullptr), + : guest_host_(nullptr), auto_size_enabled_(false), is_full_page_plugin_(false), api_web_contents_(nullptr) { @@ -95,41 +95,6 @@ void WebViewGuestDelegate::SetSize(const SetSizeParams& params) { auto_size_enabled_ = enable_auto_size; } -void WebViewGuestDelegate::SetAllowTransparency(bool allow) { - if (guest_opaque_ != allow) - return; - - auto render_view_host = web_contents()->GetRenderViewHost(); - guest_opaque_ = !allow; - if (!render_view_host->GetView()) - return; - - if (guest_opaque_) { - render_view_host->GetView()->SetBackgroundColorToDefault(); - } else { - render_view_host->GetView()->SetBackgroundColor(SK_ColorTRANSPARENT); - } -} - -void WebViewGuestDelegate::HandleKeyboardEvent( - content::WebContents* source, - const content::NativeWebKeyboardEvent& event) { - if (embedder_web_contents_) - embedder_web_contents_->GetDelegate()->HandleKeyboardEvent(source, event); -} - -void WebViewGuestDelegate::RenderViewReady() { - // We don't want to accidentally set the opacity of an interstitial page. - // WebContents::GetRenderWidgetHostView will return the RWHV of an - // interstitial page if one is showing at this time. We only want opacity - // to apply to web pages. - auto render_view_host_view = web_contents()->GetRenderViewHost()->GetView(); - if (guest_opaque_) - render_view_host_view->SetBackgroundColorToDefault(); - else - render_view_host_view->SetBackgroundColor(SK_ColorTRANSPARENT); -} - void WebViewGuestDelegate::DidCommitProvisionalLoadForFrame( content::RenderFrameHost* render_frame_host, const GURL& url, ui::PageTransition transition_type) { diff --git a/atom/browser/web_view_guest_delegate.h b/atom/browser/web_view_guest_delegate.h index 65e0bcde1916..d65eb1463586 100644 --- a/atom/browser/web_view_guest_delegate.h +++ b/atom/browser/web_view_guest_delegate.h @@ -8,10 +8,6 @@ #include "content/public/browser/browser_plugin_guest_delegate.h" #include "content/public/browser/web_contents_observer.h" -namespace content { -struct NativeWebKeyboardEvent; -} - namespace atom { namespace api { @@ -28,10 +24,10 @@ struct SetSizeParams { SetSizeParams() {} ~SetSizeParams() {} - scoped_ptr enable_auto_size; - scoped_ptr min_size; - scoped_ptr max_size; - scoped_ptr normal_size; + std::unique_ptr enable_auto_size; + std::unique_ptr min_size; + std::unique_ptr max_size; + std::unique_ptr normal_size; }; class WebViewGuestDelegate : public content::BrowserPluginGuestDelegate, @@ -49,16 +45,8 @@ class WebViewGuestDelegate : public content::BrowserPluginGuestDelegate, // and normal sizes. void SetSize(const SetSizeParams& params); - // Sets the transparency of the guest. - void SetAllowTransparency(bool allow); - - // Transfer the keyboard event to embedder. - void HandleKeyboardEvent(content::WebContents* source, - const content::NativeWebKeyboardEvent& event); - protected: // content::WebContentsObserver: - void RenderViewReady() override; void DidCommitProvisionalLoadForFrame( content::RenderFrameHost* render_frame_host, const GURL& url, ui::PageTransition transition_type) override; @@ -85,9 +73,6 @@ class WebViewGuestDelegate : public content::BrowserPluginGuestDelegate, // Returns the default size of the guestview. gfx::Size GetDefaultSize() const; - // Stores whether the contents of the guest can be transparent. - bool guest_opaque_; - // The WebContents that attaches this guest view. content::WebContents* embedder_web_contents_; diff --git a/atom/browser/web_view_manager.cc b/atom/browser/web_view_manager.cc index d404c1a43680..815e60a166ca 100644 --- a/atom/browser/web_view_manager.cc +++ b/atom/browser/web_view_manager.cc @@ -42,6 +42,13 @@ void WebViewManager::RemoveGuest(int guest_instance_id) { } } +content::WebContents* WebViewManager::GetEmbedder(int guest_instance_id) { + if (ContainsKey(web_contents_embedder_map_, guest_instance_id)) + return web_contents_embedder_map_[guest_instance_id].embedder; + else + return nullptr; +} + content::WebContents* WebViewManager::GetGuestByInstanceID( int owner_process_id, int element_instance_id) { @@ -65,4 +72,16 @@ bool WebViewManager::ForEachGuest(content::WebContents* embedder_web_contents, return false; } +// static +WebViewManager* WebViewManager::GetWebViewManager( + content::WebContents* web_contents) { + auto context = web_contents->GetBrowserContext(); + if (context) { + auto manager = context->GetGuestManager(); + return static_cast(manager); + } else { + return nullptr; + } +} + } // namespace atom diff --git a/atom/browser/web_view_manager.h b/atom/browser/web_view_manager.h index ff9a8ecba2ab..eb2ba8ad42c8 100644 --- a/atom/browser/web_view_manager.h +++ b/atom/browser/web_view_manager.h @@ -21,6 +21,9 @@ class WebViewManager : public content::BrowserPluginGuestManager { content::WebContents* embedder, content::WebContents* web_contents); void RemoveGuest(int guest_instance_id); + content::WebContents* GetEmbedder(int guest_instance_id); + + static WebViewManager* GetWebViewManager(content::WebContents* web_contents); protected: // content::BrowserPluginGuestManager: diff --git a/atom/browser/window_list.h b/atom/browser/window_list.h index bfb9a2b0aecc..3dd87b2c34c2 100644 --- a/atom/browser/window_list.h +++ b/atom/browser/window_list.h @@ -7,7 +7,7 @@ #include -#include "base/basictypes.h" +#include "base/macros.h" #include "base/lazy_instance.h" #include "base/observer_list.h" diff --git a/atom/common/api/api_messages.h b/atom/common/api/api_messages.h index eeb26614847b..ab27d5a2516e 100644 --- a/atom/common/api/api_messages.h +++ b/atom/common/api/api_messages.h @@ -30,10 +30,14 @@ IPC_SYNC_MESSAGE_ROUTED2_1(AtomViewHostMsg_Message_Sync, base::ListValue /* arguments */, base::string16 /* result (in JSON) */) -IPC_MESSAGE_ROUTED2(AtomViewMsg_Message, +IPC_MESSAGE_ROUTED3(AtomViewMsg_Message, + bool /* send_to_all */, base::string16 /* channel */, base::ListValue /* arguments */) // Sent by the renderer when the draggable regions are updated. IPC_MESSAGE_ROUTED1(AtomViewHostMsg_UpdateDraggableRegions, std::vector /* regions */) + +// Update renderer process preferences. +IPC_MESSAGE_CONTROL1(AtomMsg_UpdatePreferences, base::ListValue) diff --git a/atom/common/api/atom_api_asar.cc b/atom/common/api/atom_api_asar.cc index 7aee71fc3294..97c2e47fde0d 100644 --- a/atom/common/api/atom_api_asar.cc +++ b/atom/common/api/atom_api_asar.cc @@ -18,21 +18,39 @@ namespace { -v8::Persistent template_; - -class Archive : public mate::Wrappable { +class Archive : public mate::Wrappable { public: static v8::Local Create(v8::Isolate* isolate, const base::FilePath& path) { - scoped_ptr archive(new asar::Archive(path)); + std::unique_ptr archive(new asar::Archive(path)); if (!archive->Init()) return v8::False(isolate); - return (new Archive(archive.Pass()))->GetWrapper(isolate); + return (new Archive(isolate, std::move(archive)))->GetWrapper(); + } + + static void BuildPrototype( + v8::Isolate* isolate, v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .SetProperty("path", &Archive::GetPath) + .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); } protected: - explicit Archive(scoped_ptr archive) - : archive_(archive.Pass()) {} + Archive(v8::Isolate* isolate, std::unique_ptr archive) + : archive_(std::move(archive)) { + Init(isolate); + } + + // Returns the path of the file. + base::FilePath GetPath() { + return archive_->path(); + } // Reads the offset and size of file. v8::Local GetFileInfo(v8::Isolate* isolate, @@ -101,26 +119,8 @@ class Archive : public mate::Wrappable { archive_.reset(); } - // mate::Wrappable: - mate::ObjectTemplateBuilder GetObjectTemplateBuilder(v8::Isolate* isolate) { - 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: - scoped_ptr archive_; + std::unique_ptr archive_; DISALLOW_COPY_AND_ASSIGN(Archive); }; @@ -129,9 +129,11 @@ void InitAsarSupport(v8::Isolate* isolate, v8::Local process, v8::Local require) { // Evaluate asar_init.coffee. + const char* asar_init_native = reinterpret_cast( + static_cast(node::asar_init_native)); v8::Local asar_init = v8::Script::Compile(v8::String::NewFromUtf8( isolate, - node::asar_init_native, + asar_init_native, v8::String::kNormalString, sizeof(node::asar_init_native) -1)); v8::Local result = asar_init->Run(); @@ -141,9 +143,11 @@ void InitAsarSupport(v8::Isolate* isolate, v8::Local, std::string)> init; if (mate::ConvertFromV8(isolate, result, &init)) { + const char* asar_native = reinterpret_cast( + static_cast(node::asar_native)); init.Run(process, require, - std::string(node::asar_native, sizeof(node::asar_native) - 1)); + std::string(asar_native, sizeof(node::asar_native) - 1)); } } diff --git a/atom/common/api/atom_api_clipboard.cc b/atom/common/api/atom_api_clipboard.cc index 5186e22c8d9e..cb413800be77 100644 --- a/atom/common/api/atom_api_clipboard.cc +++ b/atom/common/api/atom_api_clipboard.cc @@ -109,8 +109,8 @@ base::string16 ReadHtml(mate::Arguments* args) { base::string16 data; base::string16 html; std::string url; - uint32 start; - uint32 end; + uint32_t start; + uint32_t end; ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); clipboard->ReadHTML(GetClipboardType(args), &html, &url, &start, &end); data = html.substr(start, end - start); @@ -146,13 +146,19 @@ void Initialize(v8::Local exports, v8::Local unused, dict.SetMethod("write", &Write); dict.SetMethod("readText", &ReadText); dict.SetMethod("writeText", &WriteText); + dict.SetMethod("readRTF", &ReadRtf); + dict.SetMethod("writeRTF", &WriteRtf); + dict.SetMethod("readHTML", &ReadHtml); + dict.SetMethod("writeHTML", &WriteHtml); + dict.SetMethod("readImage", &ReadImage); + dict.SetMethod("writeImage", &WriteImage); + dict.SetMethod("clear", &Clear); + + // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings dict.SetMethod("readRtf", &ReadRtf); dict.SetMethod("writeRtf", &WriteRtf); dict.SetMethod("readHtml", &ReadHtml); dict.SetMethod("writeHtml", &WriteHtml); - dict.SetMethod("readImage", &ReadImage); - dict.SetMethod("writeImage", &WriteImage); - dict.SetMethod("clear", &Clear); } } // namespace diff --git a/atom/common/api/atom_api_id_weak_map.cc b/atom/common/api/atom_api_id_weak_map.cc deleted file mode 100644 index f32e33682dff..000000000000 --- a/atom/common/api/atom_api_id_weak_map.cc +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2015 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "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 deleted file mode 100644 index 0cf656f455bc..000000000000 --- a/atom/common/api/atom_api_id_weak_map.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2015 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef ATOM_COMMON_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_key_weak_map.h b/atom/common/api/atom_api_key_weak_map.h new file mode 100644 index 000000000000..b13338badd5b --- /dev/null +++ b/atom/common/api/atom_api_key_weak_map.h @@ -0,0 +1,65 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_API_ATOM_API_KEY_WEAK_MAP_H_ +#define ATOM_COMMON_API_ATOM_API_KEY_WEAK_MAP_H_ + +#include "atom/common/key_weak_map.h" +#include "native_mate/object_template_builder.h" +#include "native_mate/handle.h" + +namespace atom { + +namespace api { + +template +class KeyWeakMap : public mate::Wrappable> { + public: + static mate::Handle> Create(v8::Isolate* isolate) { + return mate::CreateHandle(isolate, new KeyWeakMap(isolate)); + } + + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .SetMethod("set", &KeyWeakMap::Set) + .SetMethod("get", &KeyWeakMap::Get) + .SetMethod("has", &KeyWeakMap::Has) + .SetMethod("remove", &KeyWeakMap::Remove); + } + + protected: + explicit KeyWeakMap(v8::Isolate* isolate) { + mate::Wrappable>::Init(isolate); + } + ~KeyWeakMap() override {} + + private: + // API for KeyWeakMap. + void Set(v8::Isolate* isolate, const K& key, v8::Local object) { + key_weak_map_.Set(isolate, key, object); + } + + v8::Local Get(v8::Isolate* isolate, const K& key) { + return key_weak_map_.Get(isolate, key).ToLocalChecked(); + } + + bool Has(const K& key) { + return key_weak_map_.Has(key); + } + + void Remove(const K& key) { + key_weak_map_.Remove(key); + } + + atom::KeyWeakMap key_weak_map_; + + DISALLOW_COPY_AND_ASSIGN(KeyWeakMap); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_COMMON_API_ATOM_API_KEY_WEAK_MAP_H_ diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index a810069e71b9..c0b51ba2af28 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -13,6 +13,7 @@ #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/node_includes.h" #include "base/base64.h" +#include "base/files/file_util.h" #include "base/strings/string_util.h" #include "base/strings/pattern.h" #include "native_mate/dictionary.h" @@ -62,7 +63,7 @@ float GetScaleFactorFromPath(const base::FilePath& path) { // We don't try to convert string to float here because it is very very // expensive. - for (unsigned i = 0; i < arraysize(kScaleFactorPairs); ++i) { + for (unsigned i = 0; i < node::arraysize(kScaleFactorPairs); ++i) { if (base::EndsWith(filename, kScaleFactorPairs[i].name, base::CompareCase::INSENSITIVE_ASCII)) return kScaleFactorPairs[i].scale; @@ -75,7 +76,7 @@ bool AddImageSkiaRep(gfx::ImageSkia* image, const unsigned char* data, size_t size, double scale_factor) { - scoped_ptr decoded(new SkBitmap()); + std::unique_ptr decoded(new SkBitmap()); // Try PNG first. if (!gfx::PNGCodec::Decode(data, size, decoded.get())) @@ -119,6 +120,20 @@ bool PopulateImageSkiaRepsFromPath(gfx::ImageSkia* image, return succeed; } +base::FilePath NormalizePath(const base::FilePath& path) { + if (!path.ReferencesParent()) { + return path; + } + + base::FilePath absolute_path = MakeAbsoluteFilePath(path); + // MakeAbsoluteFilePath returns an empty path on failures so use original path + if (absolute_path.empty()) { + return path; + } else { + return absolute_path; + } +} + #if defined(OS_MACOSX) bool IsTemplateFilename(const base::FilePath& path) { return (base::MatchPattern(path.value(), "*Template.*") || @@ -127,7 +142,7 @@ bool IsTemplateFilename(const base::FilePath& path) { #endif #if defined(OS_WIN) -bool ReadImageSkiaFromICO(gfx::ImageSkia* image, const base::FilePath& path) { +base::win::ScopedHICON ReadICOFromPath(int size, const base::FilePath& path) { // If file is in asar archive, we extract it to a temp file so LoadImage can // load it. base::FilePath asar_path, relative_path; @@ -140,46 +155,58 @@ bool ReadImageSkiaFromICO(gfx::ImageSkia* image, const base::FilePath& path) { } // Load the icon from file. - base::win::ScopedHICON icon(static_cast( - LoadImage(NULL, image_path.value().c_str(), IMAGE_ICON, 0, 0, - LR_DEFAULTSIZE | LR_LOADFROMFILE))); - if (!icon) - return false; + return base::win::ScopedHICON(static_cast( + LoadImage(NULL, image_path.value().c_str(), IMAGE_ICON, size, size, + LR_LOADFROMFILE))); +} +void ReadImageSkiaFromICO(gfx::ImageSkia* image, HICON icon) { // Convert the icon from the Windows specific HICON to gfx::ImageSkia. - scoped_ptr bitmap(IconUtil::CreateSkBitmapFromHICON(icon)); + std::unique_ptr bitmap(IconUtil::CreateSkBitmapFromHICON(icon)); image->AddRepresentation(gfx::ImageSkiaRep(*bitmap, 1.0f)); - return true; } #endif -v8::Persistent template_; - } // namespace -NativeImage::NativeImage() {} +NativeImage::NativeImage(v8::Isolate* isolate, const gfx::Image& image) + : image_(image) { + Init(isolate); +} -NativeImage::NativeImage(const gfx::Image& image) : image_(image) {} +#if defined(OS_WIN) +NativeImage::NativeImage(v8::Isolate* isolate, const base::FilePath& hicon_path) + : hicon_path_(hicon_path) { + // Use the 256x256 icon as fallback icon. + gfx::ImageSkia image_skia; + ReadImageSkiaFromICO(&image_skia, GetHICON(256)); + image_ = gfx::Image(image_skia); + Init(isolate); +} +#endif NativeImage::~NativeImage() {} -mate::ObjectTemplateBuilder NativeImage::GetObjectTemplateBuilder( - v8::Isolate* isolate) { - if (template_.IsEmpty()) - template_.Reset(isolate, mate::ObjectTemplateBuilder(isolate) - .SetMethod("toPng", &NativeImage::ToPNG) - .SetMethod("toJpeg", &NativeImage::ToJPEG) - .SetMethod("toDataURL", &NativeImage::ToDataURL) - .SetMethod("toDataUrl", &NativeImage::ToDataURL) // deprecated. - .SetMethod("isEmpty", &NativeImage::IsEmpty) - .SetMethod("getSize", &NativeImage::GetSize) - .SetMethod("setTemplateImage", &NativeImage::SetTemplateImage) - .SetMethod("isTemplateImage", &NativeImage::IsTemplateImage) - .Build()); +#if defined(OS_WIN) +HICON NativeImage::GetHICON(int size) { + auto iter = hicons_.find(size); + if (iter != hicons_.end()) + return iter->second.get(); - return mate::ObjectTemplateBuilder( - isolate, v8::Local::New(isolate, template_)); + // First try loading the icon with specified size. + if (!hicon_path_.empty()) { + hicons_[size] = std::move(ReadICOFromPath(size, hicon_path_)); + return hicons_[size].get(); + } + + // Then convert the image to ICO. + if (image_.IsEmpty()) + return NULL; + hicons_[size] = std::move( + IconUtil::CreateHICONFromSkBitmap(image_.AsBitmap())); + return hicons_[size].get(); } +#endif v8::Local NativeImage::ToPNG(v8::Isolate* isolate) { scoped_refptr png = image_.As1xPNGBytes(); @@ -206,6 +233,20 @@ std::string NativeImage::ToDataURL() { return data_url; } +v8::Local NativeImage::GetNativeHandle(v8::Isolate* isolate, + mate::Arguments* args) { +#if defined(OS_MACOSX) + NSImage* ptr = image_.AsNSImage(); + return node::Buffer::Copy( + isolate, + reinterpret_cast(ptr), + sizeof(void*)).ToLocalChecked(); +#else + args->ThrowError("Not implemented"); + return v8::Undefined(isolate); +#endif +} + bool NativeImage::IsEmpty() { return image_.IsEmpty(); } @@ -225,13 +266,13 @@ bool NativeImage::IsTemplateImage() { // static mate::Handle NativeImage::CreateEmpty(v8::Isolate* isolate) { - return mate::CreateHandle(isolate, new NativeImage); + return mate::CreateHandle(isolate, new NativeImage(isolate, gfx::Image())); } // static mate::Handle NativeImage::Create( v8::Isolate* isolate, const gfx::Image& image) { - return mate::CreateHandle(isolate, new NativeImage(image)); + return mate::CreateHandle(isolate, new NativeImage(isolate, image)); } // static @@ -253,18 +294,19 @@ mate::Handle NativeImage::CreateFromJPEG( // static mate::Handle NativeImage::CreateFromPath( v8::Isolate* isolate, const base::FilePath& path) { - gfx::ImageSkia image_skia; - if (path.MatchesExtension(FILE_PATH_LITERAL(".ico"))) { + base::FilePath image_path = NormalizePath(path); #if defined(OS_WIN) - ReadImageSkiaFromICO(&image_skia, path); -#endif - } else { - PopulateImageSkiaRepsFromPath(&image_skia, path); + if (image_path.MatchesExtension(FILE_PATH_LITERAL(".ico"))) { + return mate::CreateHandle(isolate, + new NativeImage(isolate, image_path)); } +#endif + gfx::ImageSkia image_skia; + PopulateImageSkiaRepsFromPath(&image_skia, image_path); gfx::Image image(image_skia); mate::Handle handle = Create(isolate, image); #if defined(OS_MACOSX) - if (IsTemplateFilename(path)) + if (IsTemplateFilename(image_path)) handle->SetTemplateImage(true); #endif return handle; @@ -298,10 +340,53 @@ mate::Handle NativeImage::CreateFromDataURL( return CreateEmpty(isolate); } +// static +void NativeImage::BuildPrototype( + v8::Isolate* isolate, v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .SetMethod("toPng", &NativeImage::ToPNG) + .SetMethod("toJpeg", &NativeImage::ToJPEG) + .SetMethod("getNativeHandle", &NativeImage::GetNativeHandle) + .SetMethod("toDataURL", &NativeImage::ToDataURL) + .SetMethod("isEmpty", &NativeImage::IsEmpty) + .SetMethod("getSize", &NativeImage::GetSize) + .SetMethod("setTemplateImage", &NativeImage::SetTemplateImage) + .SetMethod("isTemplateImage", &NativeImage::IsTemplateImage); +} + } // namespace api } // namespace atom +namespace mate { + +v8::Local Converter>::ToV8( + v8::Isolate* isolate, + const mate::Handle& val) { + return val.ToV8(); +} + +bool Converter>::FromV8( + v8::Isolate* isolate, v8::Local val, + mate::Handle* out) { + // Try converting from file path. + base::FilePath path; + if (ConvertFromV8(isolate, val, &path)) { + *out = atom::api::NativeImage::CreateFromPath(isolate, path); + // Should throw when failed to initialize from path. + return !(*out)->image().IsEmpty(); + } + + WrappableBase* wrapper = static_cast(internal::FromV8Impl( + isolate, val)); + if (!wrapper) + return false; + + *out = CreateHandle(isolate, static_cast(wrapper)); + return true; +} + +} // namespace mate namespace { diff --git a/atom/common/api/atom_api_native_image.h b/atom/common/api/atom_api_native_image.h index 1f0fe946ba51..de4db4c0f382 100644 --- a/atom/common/api/atom_api_native_image.h +++ b/atom/common/api/atom_api_native_image.h @@ -5,12 +5,18 @@ #ifndef ATOM_COMMON_API_ATOM_API_NATIVE_IMAGE_H_ #define ATOM_COMMON_API_ATOM_API_NATIVE_IMAGE_H_ +#include #include #include "native_mate/handle.h" #include "native_mate/wrappable.h" #include "ui/gfx/image/image.h" +#if defined(OS_WIN) +#include "base/files/file_path.h" +#include "base/win/scoped_gdi_object.h" +#endif + class GURL; namespace base { @@ -29,7 +35,7 @@ namespace atom { namespace api { -class NativeImage : public mate::Wrappable { +class NativeImage : public mate::Wrappable { public: static mate::Handle CreateEmpty(v8::Isolate* isolate); static mate::Handle Create( @@ -45,22 +51,28 @@ class NativeImage : public mate::Wrappable { static mate::Handle CreateFromDataURL( v8::Isolate* isolate, const GURL& url); - // The default constructor should only be used by image_converter.cc. - NativeImage(); + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + +#if defined(OS_WIN) + HICON GetHICON(int size); +#endif const gfx::Image& image() const { return image_; } protected: - explicit NativeImage(const gfx::Image& image); - virtual ~NativeImage(); - - // mate::Wrappable: - mate::ObjectTemplateBuilder GetObjectTemplateBuilder( - v8::Isolate* isolate) override; + NativeImage(v8::Isolate* isolate, const gfx::Image& image); +#if defined(OS_WIN) + NativeImage(v8::Isolate* isolate, const base::FilePath& hicon_path); +#endif + ~NativeImage() override; private: v8::Local ToPNG(v8::Isolate* isolate); v8::Local ToJPEG(v8::Isolate* isolate, int quality); + v8::Local GetNativeHandle( + v8::Isolate* isolate, + mate::Arguments* args); std::string ToDataURL(); bool IsEmpty(); gfx::Size GetSize(); @@ -70,6 +82,11 @@ class NativeImage : public mate::Wrappable { // Determine if the image is a template image. bool IsTemplateImage(); +#if defined(OS_WIN) + base::FilePath hicon_path_; + std::map hicons_; +#endif + gfx::Image image_; DISALLOW_COPY_AND_ASSIGN(NativeImage); @@ -79,4 +96,19 @@ class NativeImage : public mate::Wrappable { } // namespace atom +namespace mate { + +// A custom converter that allows converting path to NativeImage. +template<> +struct Converter> { + static v8::Local ToV8( + v8::Isolate* isolate, + const mate::Handle& val); + static bool FromV8(v8::Isolate* isolate, v8::Local val, + mate::Handle* out); +}; + +} // namespace mate + + #endif // ATOM_COMMON_API_ATOM_API_NATIVE_IMAGE_H_ diff --git a/atom/common/api/atom_api_v8_util.cc b/atom/common/api/atom_api_v8_util.cc index f50d3485eba6..7b7655c6cd2e 100644 --- a/atom/common/api/atom_api_v8_util.cc +++ b/atom/common/api/atom_api_v8_util.cc @@ -3,40 +3,91 @@ // found in the LICENSE file. #include +#include -#include "atom/common/api/object_life_monitor.h" +#include "atom/common/api/atom_api_key_weak_map.h" +#include "atom/common/api/remote_callback_freer.h" +#include "atom/common/api/remote_object_freer.h" +#include "atom/common/native_mate_converters/content_converter.h" #include "atom/common/node_includes.h" +#include "base/hash.h" #include "native_mate/dictionary.h" #include "v8/include/v8-profiler.h" +namespace std { + +// The hash function used by DoubleIDWeakMap. +template +struct hash> { + std::size_t operator()(std::pair value) const { + return base::HashInts(value.first, value.second); + } +}; + +} // namespace std + +namespace mate { + +template +struct Converter> { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + std::pair* out) { + if (!val->IsArray()) + return false; + + v8::Local array(v8::Local::Cast(val)); + if (array->Length() != 2) + return false; + return Converter::FromV8(isolate, array->Get(0), &out->first) && + Converter::FromV8(isolate, array->Get(1), &out->second); + } +}; + +} // namespace mate + namespace { -v8::Local GetHiddenValue(v8::Local object, +v8::Local GetHiddenValue(v8::Isolate* isolate, + v8::Local object, v8::Local key) { - return object->GetHiddenValue(key); + v8::Local context = isolate->GetCurrentContext(); + v8::Local privateKey = v8::Private::ForApi(isolate, key); + v8::Local value; + v8::Maybe result = object->HasPrivate(context, privateKey); + if (!(result.IsJust() && result.FromJust())) + return v8::Local(); + if (object->GetPrivate(context, privateKey).ToLocal(&value)) + return value; + return v8::Local(); } -void SetHiddenValue(v8::Local object, +void SetHiddenValue(v8::Isolate* isolate, + v8::Local object, v8::Local key, v8::Local value) { - object->SetHiddenValue(key, value); + if (value.IsEmpty()) + return; + v8::Local context = isolate->GetCurrentContext(); + v8::Local privateKey = v8::Private::ForApi(isolate, key); + object->SetPrivate(context, privateKey, value); } -void DeleteHiddenValue(v8::Local object, +void DeleteHiddenValue(v8::Isolate* isolate, + v8::Local object, v8::Local key) { - object->DeleteHiddenValue(key); + v8::Local context = isolate->GetCurrentContext(); + v8::Local privateKey = v8::Private::ForApi(isolate, key); + // Actually deleting the value would make force the object into + // dictionary mode which is unnecessarily slow. Instead, we replace + // the hidden value with "undefined". + object->SetPrivate(context, privateKey, v8::Undefined(isolate)); } int32_t GetObjectHash(v8::Local object) { return object->GetIdentityHash(); } -void SetDestructor(v8::Isolate* isolate, - v8::Local object, - v8::Local callback) { - atom::ObjectLifeMonitor::BindTo(isolate, object, callback); -} - void TakeHeapSnapshot(v8::Isolate* isolate) { isolate->GetHeapProfiler()->TakeHeapSnapshot(); } @@ -48,8 +99,12 @@ void Initialize(v8::Local exports, v8::Local unused, dict.SetMethod("setHiddenValue", &SetHiddenValue); dict.SetMethod("deleteHiddenValue", &DeleteHiddenValue); dict.SetMethod("getObjectHash", &GetObjectHash); - dict.SetMethod("setDestructor", &SetDestructor); dict.SetMethod("takeHeapSnapshot", &TakeHeapSnapshot); + dict.SetMethod("setRemoteCallbackFreer", &atom::RemoteCallbackFreer::BindTo); + dict.SetMethod("setRemoteObjectFreer", &atom::RemoteObjectFreer::BindTo); + dict.SetMethod("createIDWeakMap", &atom::api::KeyWeakMap::Create); + dict.SetMethod("createDoubleIDWeakMap", + &atom::api::KeyWeakMap>::Create); } } // namespace diff --git a/atom/common/api/atom_bindings.cc b/atom/common/api/atom_bindings.cc index a000f6fc743a..f567492b52ba 100644 --- a/atom/common/api/atom_bindings.cc +++ b/atom/common/api/atom_bindings.cc @@ -33,6 +33,46 @@ void Hang() { base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1)); } +v8::Local GetProcessMemoryInfo(v8::Isolate* isolate) { + std::unique_ptr metrics( + base::ProcessMetrics::CreateCurrentProcessMetrics()); + + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.Set("workingSetSize", + static_cast(metrics->GetWorkingSetSize() >> 10)); + dict.Set("peakWorkingSetSize", + static_cast(metrics->GetPeakWorkingSetSize() >> 10)); + + size_t private_bytes, shared_bytes; + if (metrics->GetMemoryBytes(&private_bytes, &shared_bytes)) { + dict.Set("privateBytes", static_cast(private_bytes >> 10)); + dict.Set("sharedBytes", static_cast(shared_bytes >> 10)); + } + + return dict.GetHandle(); +} + +v8::Local GetSystemMemoryInfo(v8::Isolate* isolate, + mate::Arguments* args) { + base::SystemMemoryInfoKB mem_info; + if (!base::GetSystemMemoryInfo(&mem_info)) { + args->ThrowError("Unable to retrieve system memory information"); + return v8::Undefined(isolate); + } + + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.Set("total", mem_info.total); + dict.Set("free", mem_info.free); + + // NB: These return bogus values on OS X +#if !defined(OS_MACOSX) + dict.Set("swapTotal", mem_info.swap_total); + dict.Set("swapFree", mem_info.swap_free); +#endif + + return dict.GetHandle(); +} + // Called when there is a fatal error in V8, we just crash the process here so // we can get the stack trace. void FatalErrorCallback(const char* location, const char* message) { @@ -63,6 +103,8 @@ void AtomBindings::BindTo(v8::Isolate* isolate, dict.SetMethod("crash", &Crash); dict.SetMethod("hang", &Hang); dict.SetMethod("log", &Log); + dict.SetMethod("getProcessMemoryInfo", &GetProcessMemoryInfo); + dict.SetMethod("getSystemMemoryInfo", &GetSystemMemoryInfo); #if defined(OS_POSIX) dict.SetMethod("setFdLimit", &base::SetFdLimit); #endif @@ -98,24 +140,17 @@ void AtomBindings::OnCallNextTick(uv_async_t* handle) { self->pending_next_ticks_.begin(); it != self->pending_next_ticks_.end(); ++it) { node::Environment* env = *it; + // KickNextTick, copied from node.cc: + node::Environment::AsyncCallbackScope callback_scope(env); + if (callback_scope.in_makecallback()) + continue; node::Environment::TickInfo* tick_info = env->tick_info(); - - v8::Context::Scope context_scope(env->context()); - if (tick_info->in_tick()) - continue; - - if (tick_info->length() == 0) { + if (tick_info->length() == 0) env->isolate()->RunMicrotasks(); - } - - if (tick_info->length() == 0) { + v8::Local process = env->process_object(); + if (tick_info->length() == 0) tick_info->set_index(0); - continue; - } - - tick_info->set_in_tick(true); - env->tick_callback_function()->Call(env->process_object(), 0, NULL); - tick_info->set_in_tick(false); + env->tick_callback_function()->Call(process, 0, nullptr).IsEmpty(); } self->pending_next_ticks_.clear(); diff --git a/atom/common/api/atom_bindings.h b/atom/common/api/atom_bindings.h index b3536c234030..9460145d2391 100644 --- a/atom/common/api/atom_bindings.h +++ b/atom/common/api/atom_bindings.h @@ -7,6 +7,7 @@ #include +#include "base/macros.h" #include "base/strings/string16.h" #include "v8/include/v8.h" #include "vendor/node/deps/uv/include/uv.h" diff --git a/atom/common/api/event_emitter_caller.cc b/atom/common/api/event_emitter_caller.cc index 4b44553d3740..271ea705e09c 100644 --- a/atom/common/api/event_emitter_caller.cc +++ b/atom/common/api/event_emitter_caller.cc @@ -7,7 +7,6 @@ #include "atom/common/api/locker.h" #include "atom/common/node_includes.h" #include "base/memory/scoped_ptr.h" -#include "third_party/WebKit/public/web/WebScopedMicrotaskSuppression.h" namespace mate { @@ -17,9 +16,11 @@ v8::Local CallEmitWithArgs(v8::Isolate* isolate, v8::Local obj, ValueVector* args) { // Perform microtask checkpoint after running JavaScript. - scoped_ptr script_scope( + std::unique_ptr script_scope( Locker::IsBrowserProcess() ? - nullptr : new blink::WebScopedRunV8Script); + nullptr : + new v8::MicrotasksScope(isolate, + v8::MicrotasksScope::kRunMicrotasks)); // 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.js b/atom/common/api/lib/callbacks-registry.js deleted file mode 100644 index 5b528aa3a6c3..000000000000 --- a/atom/common/api/lib/callbacks-registry.js +++ /dev/null @@ -1,66 +0,0 @@ -'use strict'; - -var slice = [].slice; - -const v8Util = process.atomBinding('v8_util'); - -class CallbacksRegistry { - constructor() { - this.nextId = 0; - this.callbacks = {}; - } - - add(callback) { - // The callback is already added. - var filenameAndLine, id, location, match, ref, regexp, stackString; - id = v8Util.getHiddenValue(callback, 'callbackId'); - if (id != null) { - return id; - } - id = ++this.nextId; - - // Capture the location of the function and put it in the ID string, - // so that release errors can be tracked down easily. - regexp = /at (.*)/gi; - stackString = (new Error).stack; - while ((match = regexp.exec(stackString)) !== null) { - location = match[1]; - if (location.indexOf('(native)') !== -1) { - continue; - } - if (location.indexOf('atom.asar') !== -1) { - continue; - } - ref = /([^\/^\)]*)\)?$/gi.exec(location); - filenameAndLine = ref[1]; - break; - } - this.callbacks[id] = callback; - v8Util.setHiddenValue(callback, 'callbackId', id); - v8Util.setHiddenValue(callback, 'location', filenameAndLine); - return id; - } - - get(id) { - var ref; - return (ref = this.callbacks[id]) != null ? ref : function() {}; - } - - call() { - var args, id, ref; - id = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; - return (ref = this.get(id)).call.apply(ref, [global].concat(slice.call(args))); - } - - apply() { - var args, id, ref; - id = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; - return (ref = this.get(id)).apply.apply(ref, [global].concat(slice.call(args))); - } - - remove(id) { - return delete this.callbacks[id]; - } -} - -module.exports = CallbacksRegistry; diff --git a/atom/common/api/lib/crash-reporter.js b/atom/common/api/lib/crash-reporter.js deleted file mode 100644 index e3a327118fdd..000000000000 --- a/atom/common/api/lib/crash-reporter.js +++ /dev/null @@ -1,94 +0,0 @@ -const os = require('os'); -const path = require('path'); -const spawn = require('child_process').spawn; -const electron = require('electron'); -const binding = process.atomBinding('crash_reporter'); - -var CrashReporter = (function() { - function CrashReporter() {} - - CrashReporter.prototype.start = function(options) { - var app, args, autoSubmit, companyName, deprecate, env, extra, ignoreSystemCrashHandler, start, submitURL; - if (options == null) { - options = {}; - } - this.productName = options.productName, companyName = options.companyName, submitURL = options.submitURL, autoSubmit = options.autoSubmit, ignoreSystemCrashHandler = options.ignoreSystemCrashHandler, extra = options.extra; - - // Deprecated. - deprecate = electron.deprecate; - if (options.submitUrl) { - if (submitURL == null) { - submitURL = options.submitUrl; - } - deprecate.warn('submitUrl', 'submitURL'); - } - app = (process.type === 'browser' ? electron : electron.remote).app; - if (this.productName == null) { - this.productName = app.getName(); - } - if (autoSubmit == null) { - autoSubmit = true; - } - if (ignoreSystemCrashHandler == null) { - ignoreSystemCrashHandler = false; - } - if (extra == null) { - extra = {}; - } - if (extra._productName == null) { - extra._productName = this.productName; - } - if (extra._companyName == null) { - extra._companyName = companyName; - } - if (extra._version == null) { - extra._version = app.getVersion(); - } - if (companyName == null) { - deprecate.log('companyName is now a required option to crashReporter.start'); - return; - } - if (submitURL == null) { - deprecate.log('submitURL is now a required option to crashReporter.start'); - return; - } - start = (function(_this) { - return function() { - return binding.start(_this.productName, companyName, submitURL, autoSubmit, ignoreSystemCrashHandler, extra); - }; - })(this); - if (process.platform === 'win32') { - args = ["--reporter-url=" + submitURL, "--application-name=" + this.productName, "--v=1"]; - env = { - ATOM_SHELL_INTERNAL_CRASH_SERVICE: 1 - }; - spawn(process.execPath, args, { - env: env, - detached: true - }); - } - return start(); - }; - - CrashReporter.prototype.getLastCrashReport = function() { - var reports; - reports = this.getUploadedReports(); - if (reports.length > 0) { - return reports[0]; - } else { - return null; - } - }; - - CrashReporter.prototype.getUploadedReports = function() { - var log, tmpdir; - tmpdir = process.platform === 'win32' ? os.tmpdir() : '/tmp'; - log = process.platform === 'darwin' ? path.join(tmpdir, this.productName + " Crashes") : path.join(tmpdir, this.productName + " Crashes", 'uploads.log'); - return binding._getUploadedReports(log); - }; - - return CrashReporter; - -})(); - -module.exports = new CrashReporter; diff --git a/atom/common/api/lib/deprecate.js b/atom/common/api/lib/deprecate.js deleted file mode 100644 index 852d544deea9..000000000000 --- a/atom/common/api/lib/deprecate.js +++ /dev/null @@ -1,114 +0,0 @@ -// Deprecate a method. -var deprecate, - slice = [].slice; - -deprecate = function(oldName, newName, fn) { - var warned; - warned = false; - return function() { - if (!(warned || process.noDeprecation)) { - warned = true; - deprecate.warn(oldName, newName); - } - return fn.apply(this, arguments); - }; -}; - -// The method is renamed. -deprecate.rename = function(object, oldName, newName) { - var newMethod, warned; - warned = false; - newMethod = function() { - if (!(warned || process.noDeprecation)) { - warned = true; - deprecate.warn(oldName, newName); - } - return this[newName].apply(this, arguments); - }; - if (typeof object === 'function') { - return object.prototype[oldName] = newMethod; - } else { - return object[oldName] = newMethod; - } -}; - -// Forward the method to member. -deprecate.member = function(object, method, member) { - var warned; - warned = false; - return object.prototype[method] = function() { - if (!(warned || process.noDeprecation)) { - warned = true; - deprecate.warn(method, member + "." + method); - } - return this[member][method].apply(this[member], arguments); - }; -}; - -// Deprecate a property. -deprecate.property = function(object, property, method) { - return Object.defineProperty(object, property, { - get: function() { - var warned; - warned = false; - if (!(warned || process.noDeprecation)) { - warned = true; - deprecate.warn(property + " property", method + " method"); - } - return this[method](); - } - }); -}; - -// Deprecate an event. -deprecate.event = function(emitter, oldName, newName, fn) { - var warned; - warned = false; - return emitter.on(newName, function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - - // there is listeners for old API. - if (this.listenerCount(oldName) > 0) { - if (!(warned || process.noDeprecation)) { - warned = true; - deprecate.warn("'" + oldName + "' event", "'" + newName + "' event"); - } - if (fn != null) { - return fn.apply(this, arguments); - } else { - return this.emit.apply(this, [oldName].concat(slice.call(args))); - } - } - }); -}; - -// Print deprecation warning. -deprecate.warn = function(oldName, newName) { - return deprecate.log(oldName + " is deprecated. Use " + newName + " instead."); -}; - -var deprecationHandler = null; - -// Print deprecation message. -deprecate.log = function(message) { - if (typeof deprecationHandler === 'function') { - deprecationHandler(message); - } else if (process.throwDeprecation) { - throw new Error(message); - } else if (process.traceDeprecation) { - return console.trace(message); - } else { - return console.warn("(electron) " + message); - } -}; - -deprecate.setHandler = function(handler) { - deprecationHandler = handler; -}; - -deprecate.getHandler = function() { - return deprecationHandler; -}; - -module.exports = deprecate; diff --git a/atom/common/api/lib/deprecations.js b/atom/common/api/lib/deprecations.js deleted file mode 100644 index e4290c749474..000000000000 --- a/atom/common/api/lib/deprecations.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -const deprecate = require('electron').deprecate; - -exports.setHandler = function (deprecationHandler) { - deprecate.setHandler(deprecationHandler); -}; - -exports.getHandler = function () { - return deprecate.getHandler(); -}; diff --git a/atom/common/api/lib/exports/electron.js b/atom/common/api/lib/exports/electron.js deleted file mode 100644 index 51ab48ded833..000000000000 --- a/atom/common/api/lib/exports/electron.js +++ /dev/null @@ -1,65 +0,0 @@ -// Do not expose the internal modules to `require`. -const hideInternalModules = function() { - var globalPaths = require('module').globalPaths; - if (globalPaths.length === 3) { - - // Remove the "common/api/lib" and "browser-or-renderer/api/lib". - return globalPaths.splice(0, 2); - } -}; - -// Attaches properties to |exports|. -exports.defineProperties = function(exports) { - return Object.defineProperties(exports, { - hideInternalModules: { - enumerable: true, - value: hideInternalModules - }, - - // Common modules, please sort with alphabet order. - clipboard: { - - // Must be enumerable, otherwise it woulde be invisible to remote module. - enumerable: true, - get: function() { - return require('../clipboard'); - } - }, - crashReporter: { - enumerable: true, - get: function() { - return require('../crash-reporter'); - } - }, - deprecations: { - enumerable: true, - get: function() { - return require('../deprecations'); - } - }, - nativeImage: { - enumerable: true, - get: function() { - return require('../native-image'); - } - }, - shell: { - enumerable: true, - get: function() { - return require('../shell'); - } - }, - - // The internal modules, invisible unless you know their names. - CallbacksRegistry: { - get: function() { - return require('../callbacks-registry'); - } - }, - deprecate: { - get: function() { - return require('../deprecate'); - } - } - }); -}; diff --git a/atom/common/api/lib/native-image.js b/atom/common/api/lib/native-image.js deleted file mode 100644 index 6f9a9bd7d542..000000000000 --- a/atom/common/api/lib/native-image.js +++ /dev/null @@ -1,7 +0,0 @@ -const deprecate = require('electron').deprecate; -const nativeImage = process.atomBinding('native_image'); - -// Deprecated. -deprecate.rename(nativeImage, 'createFromDataUrl', 'createFromDataURL'); - -module.exports = nativeImage; diff --git a/atom/common/api/lib/shell.js b/atom/common/api/lib/shell.js deleted file mode 100644 index 08cc4e8eb41c..000000000000 --- a/atom/common/api/lib/shell.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = process.atomBinding('shell'); diff --git a/atom/common/api/locker.h b/atom/common/api/locker.h index 201217ff625a..e64ef1853ec5 100644 --- a/atom/common/api/locker.h +++ b/atom/common/api/locker.h @@ -5,7 +5,9 @@ #ifndef ATOM_COMMON_API_LOCKER_H_ #define ATOM_COMMON_API_LOCKER_H_ -#include "base/memory/scoped_ptr.h" +#include + +#include "base/macros.h" #include "v8/include/v8.h" namespace mate { @@ -24,7 +26,7 @@ class Locker { void* operator new(size_t size); void operator delete(void*, size_t); - scoped_ptr locker_; + std::unique_ptr locker_; DISALLOW_COPY_AND_ASSIGN(Locker); }; diff --git a/atom/common/api/object_life_monitor.cc b/atom/common/api/object_life_monitor.cc index 9b7c7fe6d05f..ffcc0d718420 100644 --- a/atom/common/api/object_life_monitor.cc +++ b/atom/common/api/object_life_monitor.cc @@ -10,47 +10,35 @@ namespace atom { -// static -void ObjectLifeMonitor::BindTo(v8::Isolate* isolate, - v8::Local target, - v8::Local destructor) { - new ObjectLifeMonitor(isolate, target, destructor); -} - ObjectLifeMonitor::ObjectLifeMonitor(v8::Isolate* isolate, - v8::Local target, - v8::Local destructor) + v8::Local target) : isolate_(isolate), context_(isolate, isolate->GetCurrentContext()), target_(isolate, target), - destructor_(isolate, destructor), weak_ptr_factory_(this) { target_.SetWeak(this, OnObjectGC, v8::WeakCallbackType::kParameter); } +ObjectLifeMonitor::~ObjectLifeMonitor() { + if (target_.IsEmpty()) + return; + target_.ClearWeak(); + target_.Reset(); +} + // static void ObjectLifeMonitor::OnObjectGC( const v8::WeakCallbackInfo& data) { - // Usually FirstWeakCallback should do nothing other than reset |object_| - // and then set a second weak callback to run later. We can sidestep that, - // because posting a task to the current message loop is all but free - but - // DO NOT add any more work to this method. The only acceptable place to add - // code is RunCallback. ObjectLifeMonitor* self = data.GetParameter(); self->target_.Reset(); - base::MessageLoop::current()->PostTask( - FROM_HERE, base::Bind(&ObjectLifeMonitor::RunCallback, - self->weak_ptr_factory_.GetWeakPtr())); + self->RunDestructor(); + data.SetSecondPassCallback(Free); } -void ObjectLifeMonitor::RunCallback() { - v8::HandleScope handle_scope(isolate_); - v8::Local context = v8::Local::New( - isolate_, context_); - v8::Context::Scope context_scope(context); - v8::Local::New(isolate_, destructor_)->Call( - context->Global(), 0, nullptr); - delete this; +// static +void ObjectLifeMonitor::Free( + const v8::WeakCallbackInfo& data) { + delete data.GetParameter(); } } // namespace atom diff --git a/atom/common/api/object_life_monitor.h b/atom/common/api/object_life_monitor.h index 90216d8227a5..59d5fdb5cffd 100644 --- a/atom/common/api/object_life_monitor.h +++ b/atom/common/api/object_life_monitor.h @@ -5,31 +5,26 @@ #ifndef ATOM_COMMON_API_OBJECT_LIFE_MONITOR_H_ #define ATOM_COMMON_API_OBJECT_LIFE_MONITOR_H_ -#include "base/basictypes.h" +#include "base/macros.h" #include "base/memory/weak_ptr.h" #include "v8/include/v8.h" namespace atom { class ObjectLifeMonitor { - public: - static void BindTo(v8::Isolate* isolate, - v8::Local target, - v8::Local destructor); + protected: + ObjectLifeMonitor(v8::Isolate* isolate, v8::Local target); + virtual ~ObjectLifeMonitor(); + + virtual void RunDestructor() = 0; private: - ObjectLifeMonitor(v8::Isolate* isolate, - v8::Local target, - v8::Local destructor); - static void OnObjectGC(const v8::WeakCallbackInfo& data); - - void RunCallback(); + static void Free(const v8::WeakCallbackInfo& data); v8::Isolate* isolate_; v8::Global context_; v8::Global target_; - v8::Global destructor_; base::WeakPtrFactory weak_ptr_factory_; diff --git a/atom/common/api/remote_callback_freer.cc b/atom/common/api/remote_callback_freer.cc new file mode 100644 index 000000000000..d1a185d51f39 --- /dev/null +++ b/atom/common/api/remote_callback_freer.cc @@ -0,0 +1,47 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/common/api/remote_callback_freer.h" + +#include "atom/common/api/api_messages.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" + +namespace atom { + +// static +void RemoteCallbackFreer::BindTo(v8::Isolate* isolate, + v8::Local target, + int object_id, + content::WebContents* web_contents) { + new RemoteCallbackFreer(isolate, target, object_id, web_contents); +} + +RemoteCallbackFreer::RemoteCallbackFreer(v8::Isolate* isolate, + v8::Local target, + int object_id, + content::WebContents* web_contents) + : ObjectLifeMonitor(isolate, target), + content::WebContentsObserver(web_contents), + object_id_(object_id) { +} + +RemoteCallbackFreer::~RemoteCallbackFreer() { +} + +void RemoteCallbackFreer::RunDestructor() { + base::string16 channel = + base::ASCIIToUTF16("ELECTRON_RENDERER_RELEASE_CALLBACK"); + base::ListValue args; + args.AppendInteger(object_id_); + Send(new AtomViewMsg_Message(routing_id(), false, channel, args)); + + Observe(nullptr); +} + +void RemoteCallbackFreer::RenderViewDeleted(content::RenderViewHost*) { + delete this; +} + +} // namespace atom diff --git a/atom/common/api/remote_callback_freer.h b/atom/common/api/remote_callback_freer.h new file mode 100644 index 000000000000..8fe80c8d4774 --- /dev/null +++ b/atom/common/api/remote_callback_freer.h @@ -0,0 +1,40 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_API_REMOTE_CALLBACK_FREER_H_ +#define ATOM_COMMON_API_REMOTE_CALLBACK_FREER_H_ +#include "atom/common/api/object_life_monitor.h" +#include "content/public/browser/web_contents_observer.h" + +namespace atom { + +class RemoteCallbackFreer : public ObjectLifeMonitor, + public content::WebContentsObserver { + public: + static void BindTo(v8::Isolate* isolate, + v8::Local target, + int object_id, + content::WebContents* web_conents); + + protected: + RemoteCallbackFreer(v8::Isolate* isolate, + v8::Local target, + int object_id, + content::WebContents* web_conents); + ~RemoteCallbackFreer() override; + + void RunDestructor() override; + + // content::WebContentsObserver: + void RenderViewDeleted(content::RenderViewHost*) override; + + private: + int object_id_; + + DISALLOW_COPY_AND_ASSIGN(RemoteCallbackFreer); +}; + +} // namespace atom + +#endif // ATOM_COMMON_API_REMOTE_CALLBACK_FREER_H_ diff --git a/atom/common/api/remote_object_freer.cc b/atom/common/api/remote_object_freer.cc new file mode 100644 index 000000000000..1762f1d1e068 --- /dev/null +++ b/atom/common/api/remote_object_freer.cc @@ -0,0 +1,63 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/common/api/remote_object_freer.h" + +#include "atom/common/api/api_messages.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "content/public/renderer/render_view.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" +#include "third_party/WebKit/public/web/WebView.h" + +using blink::WebLocalFrame; +using blink::WebView; + +namespace atom { + +namespace { + +content::RenderView* GetCurrentRenderView() { + WebLocalFrame* frame = WebLocalFrame::frameForCurrentContext(); + if (!frame) + return nullptr; + + WebView* view = frame->view(); + if (!view) + return nullptr; // can happen during closing. + + return content::RenderView::FromWebView(view); +} + +} // namespace + +// static +void RemoteObjectFreer::BindTo( + v8::Isolate* isolate, v8::Local target, int object_id) { + new RemoteObjectFreer(isolate, target, object_id); +} + +RemoteObjectFreer::RemoteObjectFreer( + v8::Isolate* isolate, v8::Local target, int object_id) + : ObjectLifeMonitor(isolate, target), + object_id_(object_id) { +} + +RemoteObjectFreer::~RemoteObjectFreer() { +} + +void RemoteObjectFreer::RunDestructor() { + content::RenderView* render_view = GetCurrentRenderView(); + if (!render_view) + return; + + base::string16 channel = base::ASCIIToUTF16("ipc-message"); + base::ListValue args; + args.AppendString("ELECTRON_BROWSER_DEREFERENCE"); + args.AppendInteger(object_id_); + render_view->Send( + new AtomViewHostMsg_Message(render_view->GetRoutingID(), channel, args)); +} + +} // namespace atom diff --git a/atom/common/api/remote_object_freer.h b/atom/common/api/remote_object_freer.h new file mode 100644 index 000000000000..c2b5d8b7d30b --- /dev/null +++ b/atom/common/api/remote_object_freer.h @@ -0,0 +1,32 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_API_REMOTE_OBJECT_FREER_H_ +#define ATOM_COMMON_API_REMOTE_OBJECT_FREER_H_ + +#include "atom/common/api/object_life_monitor.h" + +namespace atom { + +class RemoteObjectFreer : public ObjectLifeMonitor { + public: + static void BindTo( + v8::Isolate* isolate, v8::Local target, int object_id); + + protected: + RemoteObjectFreer( + v8::Isolate* isolate, v8::Local target, int object_id); + ~RemoteObjectFreer() override; + + void RunDestructor() override; + + private: + int object_id_; + + DISALLOW_COPY_AND_ASSIGN(RemoteObjectFreer); +}; + +} // namespace atom + +#endif // ATOM_COMMON_API_REMOTE_OBJECT_FREER_H_ diff --git a/atom/common/asar/archive.cc b/atom/common/asar/archive.cc index 35451410a8d9..c2306c3c8f38 100644 --- a/atom/common/asar/archive.cc +++ b/atom/common/asar/archive.cc @@ -4,10 +4,6 @@ #include "atom/common/asar/archive.h" -#if defined(OS_WIN) -#include -#endif - #include #include @@ -20,6 +16,10 @@ #include "base/strings/string_number_conversions.h" #include "base/values.h" +#if defined(OS_WIN) +#include "atom/node/osfhandle.h" +#endif + namespace asar { namespace { @@ -90,12 +90,12 @@ bool GetNodeFromPath(std::string path, } bool FillFileInfoWithNode(Archive::FileInfo* info, - uint32 header_size, + uint32_t header_size, const base::DictionaryValue* node) { int size; if (!node->GetInteger("size", &size)) return false; - info->size = static_cast(size); + info->size = static_cast(size); if (node->GetBoolean("unpacked", &info->unpacked) && info->unpacked) return true; @@ -118,7 +118,7 @@ Archive::Archive(const base::FilePath& path) : path_(path), file_(path_, base::File::FLAG_OPEN | base::File::FLAG_READ), #if defined(OS_WIN) - fd_(_open_osfhandle( + fd_(node::open_osfhandle( reinterpret_cast(file_.GetPlatformFile()), 0)), #elif defined(OS_POSIX) fd_(file_.GetPlatformFile()), @@ -131,7 +131,7 @@ Archive::Archive(const base::FilePath& path) Archive::~Archive() { #if defined(OS_WIN) if (fd_ != -1) { - _close(fd_); + node::close(fd_); // Don't close the handle since we already closed the fd. file_.TakePlatformFile(); } @@ -157,7 +157,7 @@ bool Archive::Init() { return false; } - uint32 size; + uint32_t size; if (!base::PickleIterator(base::Pickle(buf.data(), buf.size())).ReadUInt32( &size)) { LOG(ERROR) << "Failed to parse header size from " << path_.value(); @@ -180,7 +180,7 @@ bool Archive::Init() { std::string error; base::JSONReader reader; - scoped_ptr value(reader.ReadToValue(header)); + std::unique_ptr value(reader.ReadToValue(header)); if (!value || !value->IsType(base::Value::TYPE_DICTIONARY)) { LOG(ERROR) << "Failed to parse header: " << error; return false; @@ -283,7 +283,7 @@ bool Archive::CopyFileOut(const base::FilePath& path, base::FilePath* out) { return true; } - scoped_ptr temp_file(new ScopedTemporaryFile); + std::unique_ptr temp_file(new ScopedTemporaryFile); base::FilePath::StringType ext = path.Extension(); if (!temp_file->InitFromFile(&file_, ext, info.offset, info.size)) return false; @@ -296,7 +296,7 @@ bool Archive::CopyFileOut(const base::FilePath& path, base::FilePath* out) { #endif *out = temp_file->path(); - external_files_.set(path, temp_file.Pass()); + external_files_.set(path, std::move(temp_file)); return true; } diff --git a/atom/common/asar/archive.h b/atom/common/asar/archive.h index de5b9de605a3..5438776885f3 100644 --- a/atom/common/asar/archive.h +++ b/atom/common/asar/archive.h @@ -28,8 +28,8 @@ class Archive { FileInfo() : unpacked(false), executable(false), size(0), offset(0) {} bool unpacked; bool executable; - uint32 size; - uint64 offset; + uint32_t size; + uint64_t offset; }; struct Stats : public FileInfo { @@ -71,11 +71,11 @@ class Archive { base::FilePath path_; base::File file_; int fd_; - uint32 header_size_; - scoped_ptr header_; + uint32_t header_size_; + std::unique_ptr header_; // Cached external temporary files. - base::ScopedPtrHashMap> + base::ScopedPtrHashMap> external_files_; DISALLOW_COPY_AND_ASSIGN(Archive); diff --git a/atom/common/asar/scoped_temporary_file.cc b/atom/common/asar/scoped_temporary_file.cc index 6dd12782d8ee..8578d90d9074 100644 --- a/atom/common/asar/scoped_temporary_file.cc +++ b/atom/common/asar/scoped_temporary_file.cc @@ -51,7 +51,7 @@ bool ScopedTemporaryFile::Init(const base::FilePath::StringType& ext) { bool ScopedTemporaryFile::InitFromFile(base::File* src, const base::FilePath::StringType& ext, - uint64 offset, uint64 size) { + uint64_t offset, uint64_t size) { if (!src->IsValid()) return false; diff --git a/atom/common/asar/scoped_temporary_file.h b/atom/common/asar/scoped_temporary_file.h index 23660a239011..5931d9b87af7 100644 --- a/atom/common/asar/scoped_temporary_file.h +++ b/atom/common/asar/scoped_temporary_file.h @@ -28,7 +28,7 @@ class ScopedTemporaryFile { // Init an temporary file and fill it with content of |path|. bool InitFromFile(base::File* src, const base::FilePath::StringType& ext, - uint64 offset, uint64 size); + uint64_t offset, uint64_t size); base::FilePath path() const { return path_; } diff --git a/atom/common/atom_command_line.cc b/atom/common/atom_command_line.cc index 2ac62385aeac..08880ffe4a35 100644 --- a/atom/common/atom_command_line.cc +++ b/atom/common/atom_command_line.cc @@ -12,6 +12,11 @@ namespace atom { // static std::vector AtomCommandLine::argv_; +#if defined(OS_WIN) +// static +std::vector AtomCommandLine::wargv_; +#endif + // static void AtomCommandLine::Init(int argc, const char* const* argv) { // Hack around with the argv pointer. Used for process.title = "blah" @@ -21,6 +26,15 @@ void AtomCommandLine::Init(int argc, const char* const* argv) { } } +#if defined(OS_WIN) +// static +void AtomCommandLine::InitW(int argc, const wchar_t* const* argv) { + for (int i = 0; i < argc; ++i) { + wargv_.push_back(argv[i]); + } +} +#endif + #if defined(OS_LINUX) // static void AtomCommandLine::InitializeFromCommandLine() { diff --git a/atom/common/atom_command_line.h b/atom/common/atom_command_line.h index 7c8840f70751..a834ce925662 100644 --- a/atom/common/atom_command_line.h +++ b/atom/common/atom_command_line.h @@ -8,7 +8,8 @@ #include #include -#include "base/basictypes.h" +#include "base/macros.h" +#include "build/build_config.h" namespace atom { @@ -18,6 +19,11 @@ class AtomCommandLine { static void Init(int argc, const char* const* argv); static std::vector argv() { return argv_; } +#if defined(OS_WIN) + static void InitW(int argc, const wchar_t* const* argv); + static std::vector wargv() { return wargv_; } +#endif + #if defined(OS_LINUX) // On Linux the command line has to be read from base::CommandLine since // it is using zygote. @@ -27,6 +33,10 @@ class AtomCommandLine { private: static std::vector argv_; +#if defined(OS_WIN) + static std::vector wargv_; +#endif + DISALLOW_IMPLICIT_CONSTRUCTORS(AtomCommandLine); }; diff --git a/atom/common/atom_constants.cc b/atom/common/atom_constants.cc index dacda3c816c8..f66c947aa24c 100644 --- a/atom/common/atom_constants.cc +++ b/atom/common/atom_constants.cc @@ -6,6 +6,22 @@ namespace atom { -const char* kCORSHeader = "Access-Control-Allow-Origin: *"; +const char kCORSHeader[] = "Access-Control-Allow-Origin: *"; + +const char kSHA1Certificate[] = "SHA-1 Certificate"; +const char kSHA1MajorDescription[] = + "The certificate for this site expires in 2017 or later, " + "and the certificate chain contains a certificate signed using SHA-1."; +const char kSHA1MinorDescription[] = + "The certificate for this site expires in 2016, " + "and the certificate chain contains a certificate signed using SHA-1."; +const char kCertificateError[] = "Certificate Error"; +const char kValidCertificate[] = "Valid Certificate"; +const char kValidCertificateDescription[] = + "The connection to this site is using a valid, trusted server certificate."; +const char kSecureProtocol[] = "Secure TLS connection"; +const char kSecureProtocolDescription[] = + "The connection to this site is using a strong protocol version " + "and cipher suite."; } // namespace atom diff --git a/atom/common/atom_constants.h b/atom/common/atom_constants.h index e0d42e83eef9..b67b7b2e4ae5 100644 --- a/atom/common/atom_constants.h +++ b/atom/common/atom_constants.h @@ -8,7 +8,17 @@ namespace atom { // Header to ignore CORS. -extern const char* kCORSHeader; +extern const char kCORSHeader[]; + +// Strings describing Chrome security policy for DevTools security panel. +extern const char kSHA1Certificate[]; +extern const char kSHA1MajorDescription[]; +extern const char kSHA1MinorDescription[]; +extern const char kCertificateError[]; +extern const char kValidCertificate[]; +extern const char kValidCertificateDescription[]; +extern const char kSecureProtocol[]; +extern const char kSecureProtocolDescription[]; } // namespace atom diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index b464ebdae4fd..455bfacbfc11 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -5,9 +5,9 @@ #ifndef ATOM_VERSION_H #define ATOM_VERSION_H -#define ATOM_MAJOR_VERSION 0 -#define ATOM_MINOR_VERSION 36 -#define ATOM_PATCH_VERSION 8 +#define ATOM_MAJOR_VERSION 1 +#define ATOM_MINOR_VERSION 2 +#define ATOM_PATCH_VERSION 1 #define ATOM_VERSION_IS_RELEASE 1 diff --git a/atom/common/chrome_version.h b/atom/common/chrome_version.h index 37a5c64979e5..f84f6ac15516 100644 --- a/atom/common/chrome_version.h +++ b/atom/common/chrome_version.h @@ -8,7 +8,7 @@ #ifndef ATOM_COMMON_CHROME_VERSION_H_ #define ATOM_COMMON_CHROME_VERSION_H_ -#define CHROME_VERSION_STRING "47.0.2526.110" +#define CHROME_VERSION_STRING "51.0.2704.63" #define CHROME_VERSION "v" CHROME_VERSION_STRING #endif // ATOM_COMMON_CHROME_VERSION_H_ diff --git a/atom/common/color_util.cc b/atom/common/color_util.cc new file mode 100644 index 000000000000..a6640d9e08b6 --- /dev/null +++ b/atom/common/color_util.cc @@ -0,0 +1,48 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/common/color_util.h" + +#include + +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" + +namespace atom { + +SkColor ParseHexColor(const std::string& color_string) { + // Check the string for incorrect formatting. + if (color_string.empty() || color_string[0] != '#') + return SK_ColorWHITE; + + // Prepend FF if alpha channel is not specified. + std::string source = color_string.substr(1); + if (source.size() == 3) + source.insert(0, "F"); + else if (source.size() == 6) + source.insert(0, "FF"); + + // Convert the string from #FFF format to #FFFFFF format. + std::string formatted_color; + if (source.size() == 4) { + for (size_t i = 0; i < 4; ++i) { + formatted_color += source[i]; + formatted_color += source[i]; + } + } else if (source.size() == 8) { + formatted_color = source; + } else { + return SK_ColorWHITE; + } + + // Convert the string to an integer and make sure it is in the correct value + // range. + std::vector bytes; + if (!base::HexStringToBytes(formatted_color, &bytes)) + return SK_ColorWHITE; + + return SkColorSetARGB(bytes[0], bytes[1], bytes[2], bytes[3]); +} + +} // namespace atom diff --git a/atom/common/color_util.h b/atom/common/color_util.h new file mode 100644 index 000000000000..0f2bb9633a73 --- /dev/null +++ b/atom/common/color_util.h @@ -0,0 +1,19 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_COLOR_UTIL_H_ +#define ATOM_COMMON_COLOR_UTIL_H_ + +#include + +#include "third_party/skia/include/core/SkColor.h" + +namespace atom { + +// Parse hex color like "#FFF" or "#EFEFEF" +SkColor ParseHexColor(const std::string& name); + +} // namespace atom + +#endif // ATOM_COMMON_COLOR_UTIL_H_ diff --git a/atom/common/common_message_generator.h b/atom/common/common_message_generator.h index 832de1abf739..64206956863e 100644 --- a/atom/common/common_message_generator.h +++ b/atom/common/common_message_generator.h @@ -8,3 +8,4 @@ #include "chrome/common/print_messages.h" #include "chrome/common/tts_messages.h" #include "chrome/common/widevine_cdm_messages.h" +#include "chrome/common/chrome_utility_messages.h" diff --git a/atom/common/crash_reporter/crash_reporter.h b/atom/common/crash_reporter/crash_reporter.h index 98832fea45de..eebbe16dca82 100644 --- a/atom/common/crash_reporter/crash_reporter.h +++ b/atom/common/crash_reporter/crash_reporter.h @@ -10,7 +10,7 @@ #include #include -#include "base/basictypes.h" +#include "base/macros.h" namespace crash_reporter { diff --git a/atom/common/crash_reporter/crash_reporter_linux.h b/atom/common/crash_reporter/crash_reporter_linux.h index 165c288ab2b1..b74103ccbf76 100644 --- a/atom/common/crash_reporter/crash_reporter_linux.h +++ b/atom/common/crash_reporter/crash_reporter_linux.h @@ -47,7 +47,7 @@ class CrashReporterLinux : public CrashReporter { void* context, const bool succeeded); - scoped_ptr breakpad_; + std::unique_ptr breakpad_; CrashKeyStorage crash_keys_; uint64_t process_start_time_; diff --git a/atom/common/crash_reporter/crash_reporter_mac.h b/atom/common/crash_reporter/crash_reporter_mac.h index f03154359155..5556263cd350 100644 --- a/atom/common/crash_reporter/crash_reporter_mac.h +++ b/atom/common/crash_reporter/crash_reporter_mac.h @@ -45,7 +45,7 @@ class CrashReporterMac : public CrashReporter { std::vector GetUploadedReports( const std::string& path) override; - scoped_ptr simple_string_dictionary_; + std::unique_ptr simple_string_dictionary_; DISALLOW_COPY_AND_ASSIGN(CrashReporterMac); }; diff --git a/atom/common/crash_reporter/crash_reporter_mac.mm b/atom/common/crash_reporter/crash_reporter_mac.mm index 74ac70125b7d..ee2ce036048f 100644 --- a/atom/common/crash_reporter/crash_reporter_mac.mm +++ b/atom/common/crash_reporter/crash_reporter_mac.mm @@ -4,6 +4,8 @@ #include "atom/common/crash_reporter/crash_reporter_mac.h" +#include + #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/mac/bundle_locations.h" @@ -48,7 +50,8 @@ void CrashReporterMac::InitBreakpad(const std::string& product_name, if (crashpad_client.StartHandler(handler_path, database_path, submit_url, StringMap(), - std::vector())) { + std::vector(), + true)) { crashpad_client.UseHandler(); } } // @autoreleasepool @@ -72,7 +75,7 @@ void CrashReporterMac::InitBreakpad(const std::string& product_name, SetCrashKeyValue(upload_parameter.first, upload_parameter.second); } if (is_browser_) { - scoped_ptr database = + std::unique_ptr database = crashpad::CrashReportDatabase::Initialize(database_path); if (database) { database->GetSettings()->SetUploadsEnabled(auto_submit); @@ -98,7 +101,7 @@ CrashReporterMac::GetUploadedReports(const std::string& path) { return uploaded_reports; } // Load crashpad database. - scoped_ptr database = + std::unique_ptr database = crashpad::CrashReportDatabase::Initialize(file_path); DCHECK(database); diff --git a/atom/common/crash_reporter/crash_reporter_win.cc b/atom/common/crash_reporter/crash_reporter_win.cc index 939a02f090c5..4264f6af9db4 100644 --- a/atom/common/crash_reporter/crash_reporter_win.cc +++ b/atom/common/crash_reporter/crash_reporter_win.cc @@ -43,6 +43,10 @@ const MINIDUMP_TYPE kSmallDumpType = static_cast( const wchar_t kWaitEventFormat[] = L"$1CrashServiceWaitEvent"; const wchar_t kPipeNameFormat[] = L"\\\\.\\pipe\\$1 Crash Service"; +// Matches breakpad/src/client/windows/common/ipc_protocol.h. +const int kNameMaxLength = 64; +const int kValueMaxLength = 64; + typedef NTSTATUS (WINAPI* NtTerminateProcessPtr)(HANDLE ProcessHandle, NTSTATUS ExitStatus); char* g_real_terminate_process_stub = NULL; @@ -247,9 +251,18 @@ google_breakpad::CustomClientInfo* CrashReporterWin::GetCustomInfo( for (StringMap::const_iterator iter = upload_parameters_.begin(); iter != upload_parameters_.end(); ++iter) { - custom_info_entries_.push_back(google_breakpad::CustomInfoEntry( - base::UTF8ToWide(iter->first).c_str(), - base::UTF8ToWide(iter->second).c_str())); + // breakpad has hardcoded the length of name/value, and doesn't truncate + // the values itself, so we have to truncate them here otherwise weird + // things may happen. + std::wstring name = base::UTF8ToWide(iter->first); + std::wstring value = base::UTF8ToWide(iter->second); + if (name.length() > kNameMaxLength - 1) + name.resize(kNameMaxLength - 1); + if (value.length() > kValueMaxLength - 1) + value.resize(kValueMaxLength - 1); + + custom_info_entries_.push_back( + google_breakpad::CustomInfoEntry(name.c_str(), value.c_str())); } custom_info_.entries = &custom_info_entries_.front(); diff --git a/atom/common/crash_reporter/crash_reporter_win.h b/atom/common/crash_reporter/crash_reporter_win.h index 181c9eabd23c..0ab8f4bcfb19 100644 --- a/atom/common/crash_reporter/crash_reporter_win.h +++ b/atom/common/crash_reporter/crash_reporter_win.h @@ -62,7 +62,7 @@ class CrashReporterWin : public CrashReporter { google_breakpad::CustomClientInfo custom_info_; bool skip_system_crash_handler_; - scoped_ptr breakpad_; + std::unique_ptr breakpad_; DISALLOW_COPY_AND_ASSIGN(CrashReporterWin); }; diff --git a/atom/common/crash_reporter/linux/crash_dump_handler.h b/atom/common/crash_reporter/linux/crash_dump_handler.h index f600c9e0d1b4..f10c5212254e 100644 --- a/atom/common/crash_reporter/linux/crash_dump_handler.h +++ b/atom/common/crash_reporter/linux/crash_dump_handler.h @@ -6,7 +6,11 @@ #ifndef ATOM_COMMON_CRASH_REPORTER_LINUX_CRASH_DUMP_HANDLER_H_ #define ATOM_COMMON_CRASH_REPORTER_LINUX_CRASH_DUMP_HANDLER_H_ -#include "base/basictypes.h" +#include +#include +#include + +#include "base/macros.h" #include "vendor/breakpad/src/common/simple_string_dictionary.h" namespace crash_reporter { diff --git a/atom/common/crash_reporter/win/crash_service.cc b/atom/common/crash_reporter/win/crash_service.cc index 67e22381aef8..4563a9202a3e 100644 --- a/atom/common/crash_reporter/win/crash_service.cc +++ b/atom/common/crash_reporter/win/crash_service.cc @@ -86,7 +86,7 @@ bool WriteReportIDToFile(const std::wstring& dump_path, if (!file.is_open()) return false; - int64 seconds_since_epoch = + int64_t seconds_since_epoch = (base::Time::Now() - base::Time::UnixEpoch()).InSeconds(); std::wstring line = base::Int64ToString16(seconds_since_epoch); line += L','; @@ -211,7 +211,7 @@ bool CrashService::Initialize(const base::string16& application_name, std::wstring pipe_name = kTestPipeName; int max_reports = -1; - // The checkpoint file allows CrashReportSender to enforce the the maximum + // The checkpoint file allows CrashReportSender to enforce the maximum // reports per day quota. Does not seem to serve any other purpose. base::FilePath checkpoint_path = operating_dir.Append(kCheckPointFile); @@ -327,7 +327,7 @@ void CrashService::OnClientConnected(void* context, void CrashService::OnClientExited(void* context, const google_breakpad::ClientInfo* client_info) { - ProcessingLock lock; + ProcessingLock processing_lock; VLOG(1) << "client end. pid = " << client_info->pid(); CrashService* self = static_cast(context); ::InterlockedIncrement(&self->clients_terminated_); @@ -440,10 +440,12 @@ DWORD CrashService::AsyncSendDump(void* context) { // termination of the service object. base::AutoLock lock(info->self->sending_); VLOG(1) << "trying to send report for pid = " << info->pid; + std::map file_map; + file_map[L"upload_file_minidump"] = info->dump_path; google_breakpad::ReportResult send_result = info->self->sender_->SendCrashReport(info->self->reporter_url_, info->map, - info->dump_path, + file_map, &report_id); switch (send_result) { case google_breakpad::RESULT_FAILED: diff --git a/atom/common/crash_reporter/win/crash_service.h b/atom/common/crash_reporter/win/crash_service.h index 7195ec2a958c..c05e0d5bf6e4 100644 --- a/atom/common/crash_reporter/win/crash_service.h +++ b/atom/common/crash_reporter/win/crash_service.h @@ -7,7 +7,7 @@ #include -#include "base/basictypes.h" +#include "base/macros.h" #include "base/files/file_path.h" #include "base/synchronization/lock.h" diff --git a/atom/common/crash_reporter/win/crash_service_main.cc b/atom/common/crash_reporter/win/crash_service_main.cc index 56d46970b67f..c6325f090ade 100644 --- a/atom/common/crash_reporter/win/crash_service_main.cc +++ b/atom/common/crash_reporter/win/crash_service_main.cc @@ -20,6 +20,11 @@ const char kApplicationName[] = "application-name"; const wchar_t kPipeNameFormat[] = L"\\\\.\\pipe\\$1 Crash Service"; const wchar_t kStandardLogFile[] = L"operation_log.txt"; +void InvalidParameterHandler(const wchar_t*, const wchar_t*, const wchar_t*, + unsigned int, uintptr_t) { + // noop. +} + bool GetCrashServiceDirectory(const std::wstring& application_name, base::FilePath* dir) { base::FilePath temp_dir; @@ -37,6 +42,9 @@ bool GetCrashServiceDirectory(const std::wstring& application_name, } // namespace. int Main(const wchar_t* cmd) { + // Ignore invalid parameter errors. + _set_invalid_parameter_handler(InvalidParameterHandler); + // Initialize all Chromium things. base::AtExitManager exit_manager; base::CommandLine::Init(0, NULL); diff --git a/atom/common/id_weak_map.cc b/atom/common/id_weak_map.cc deleted file mode 100644 index a78dcbceba53..000000000000 --- a/atom/common/id_weak_map.cc +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2015 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "atom/common/id_weak_map.h" - -#include - -#include "native_mate/converter.h" - -namespace atom { - -namespace { - -struct ObjectKey { - ObjectKey(int id, IDWeakMap* map) : id(id), map(map) {} - int id; - IDWeakMap* map; -}; - -void OnObjectGC(const v8::WeakCallbackInfo& data) { - ObjectKey* key = data.GetParameter(); - key->map->Remove(key->id); - delete key; -} - -} // namespace - -IDWeakMap::IDWeakMap() : next_id_(0) { -} - -IDWeakMap::~IDWeakMap() { -} - -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; -} - -v8::MaybeLocal IDWeakMap::Get(v8::Isolate* isolate, int32_t id) { - auto iter = map_.find(id); - if (iter == map_.end()) - return v8::MaybeLocal(); - else - return v8::Local::New(isolate, *iter->second); -} - -bool IDWeakMap::Has(int32_t id) const { - return map_.find(id) != map_.end(); -} - -std::vector IDWeakMap::Keys() const { - std::vector keys; - keys.reserve(map_.size()); - for (const auto& iter : map_) - keys.emplace_back(iter.first); - return keys; -} - -std::vector> IDWeakMap::Values(v8::Isolate* isolate) { - std::vector> keys; - keys.reserve(map_.size()); - for (const auto& iter : map_) - keys.emplace_back(v8::Local::New(isolate, *iter.second)); - return keys; -} - -void IDWeakMap::Remove(int32_t id) { - auto iter = map_.find(id); - if (iter == map_.end()) - LOG(WARNING) << "Removing unexist object with ID " << id; - else - map_.erase(iter); -} - -void IDWeakMap::Clear() { - map_.clear(); -} - -int32_t IDWeakMap::GetNextID() { - return ++next_id_; -} - -} // namespace atom diff --git a/atom/common/id_weak_map.h b/atom/common/id_weak_map.h deleted file mode 100644 index 72c64c6ae5d4..000000000000 --- a/atom/common/id_weak_map.h +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2015 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef ATOM_COMMON_ID_WEAK_MAP_H_ -#define ATOM_COMMON_ID_WEAK_MAP_H_ - -#include -#include - -#include "base/memory/linked_ptr.h" -#include "v8/include/v8.h" - -namespace atom { - -// Like ES6's WeakMap, but the key is Integer and the value is Weak Pointer. -class IDWeakMap { - public: - IDWeakMap(); - ~IDWeakMap(); - - // 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); - - // Gets the object from WeakMap by its |id|. - v8::MaybeLocal Get(v8::Isolate* isolate, int32_t id); - - // Whethere there is an object with |id| in this WeakMap. - bool Has(int32_t id) const; - - // Returns IDs of all available objects. - std::vector Keys() const; - - // Returns all objects. - std::vector> Values(v8::Isolate* isolate); - - // Remove object with |id| in the WeakMap. - void Remove(int32_t key); - - // Clears the weak map. - void Clear(); - - private: - // Returns next available ID. - int32_t GetNextID(); - - // ID of next stored object. - int32_t next_id_; - - // Map of stored objects. - std::unordered_map>> map_; - - DISALLOW_COPY_AND_ASSIGN(IDWeakMap); -}; - -} // namespace atom - -#endif // ATOM_COMMON_ID_WEAK_MAP_H_ diff --git a/atom/common/key_weak_map.h b/atom/common/key_weak_map.h new file mode 100644 index 000000000000..bce34bfe0f6e --- /dev/null +++ b/atom/common/key_weak_map.h @@ -0,0 +1,96 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_KEY_WEAK_MAP_H_ +#define ATOM_COMMON_KEY_WEAK_MAP_H_ + +#include +#include +#include + +#include "base/memory/linked_ptr.h" +#include "v8/include/v8.h" + +namespace atom { + +namespace internal { + +} // namespace internal + +// Like ES6's WeakMap, but the key is Integer and the value is Weak Pointer. +template +class KeyWeakMap { + public: + // Records the key and self, used by SetWeak. + struct KeyObject { + K key; + KeyWeakMap* self; + }; + + KeyWeakMap() {} + virtual ~KeyWeakMap() { + for (const auto& p : map_) + p.second.second->ClearWeak(); + } + + // Sets the object to WeakMap with the given |key|. + void Set(v8::Isolate* isolate, const K& key, v8::Local object) { + auto value = make_linked_ptr(new v8::Global(isolate, object)); + KeyObject key_object = {key, this}; + auto& p = map_[key] = std::make_pair(key_object, value); + value->SetWeak(&(p.first), OnObjectGC, v8::WeakCallbackType::kParameter); + } + + // Gets the object from WeakMap by its |key|. + v8::MaybeLocal Get(v8::Isolate* isolate, const K& key) { + auto iter = map_.find(key); + if (iter == map_.end()) + return v8::MaybeLocal(); + else + return v8::Local::New(isolate, *(iter->second.second)); + } + + // Whethere there is an object with |key| in this WeakMap. + bool Has(const K& key) const { + return map_.find(key) != map_.end(); + } + + // Returns all objects. + std::vector> Values(v8::Isolate* isolate) { + std::vector> keys; + keys.reserve(map_.size()); + for (const auto& iter : map_) { + const auto& value = *(iter.second.second); + keys.emplace_back(v8::Local::New(isolate, value)); + } + return keys; + } + + // Remove object with |key| in the WeakMap. + void Remove(const K& key) { + auto iter = map_.find(key); + if (iter == map_.end()) + return; + + iter->second.second->ClearWeak(); + map_.erase(iter); + } + + private: + static void OnObjectGC( + const v8::WeakCallbackInfo::KeyObject>& data) { + KeyWeakMap::KeyObject* key_object = data.GetParameter(); + key_object->self->Remove(key_object->key); + } + + // Map of stored objects. + std::unordered_map< + K, std::pair>>> map_; + + DISALLOW_COPY_AND_ASSIGN(KeyWeakMap); +}; + +} // namespace atom + +#endif // ATOM_COMMON_KEY_WEAK_MAP_H_ diff --git a/atom/common/keyboard_util.cc b/atom/common/keyboard_util.cc index e5dedd84dedd..d860bc0c46c7 100644 --- a/atom/common/keyboard_util.cc +++ b/atom/common/keyboard_util.cc @@ -3,12 +3,19 @@ // found in the LICENSE file. #include + #include "atom/common/keyboard_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" namespace atom { -// Return key code of the char. +namespace { + +// Return key code of the char, and also determine whether the SHIFT key is +// pressed. ui::KeyboardCode KeyboardCodeFromCharCode(base::char16 c, bool* shifted) { + c = base::ToLowerASCII(c); *shifted = false; switch (c) { case 0x08: return ui::VKEY_BACK; @@ -72,28 +79,98 @@ ui::KeyboardCode KeyboardCodeFromCharCode(base::char16 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 key code represented by |str|. +ui::KeyboardCode KeyboardCodeFromKeyIdentifier(const std::string& s, + bool* shifted) { + std::string str = base::ToLowerASCII(s); + if (str == "ctrl" || str == "control") { + return ui::VKEY_CONTROL; + } else if (str == "super" || str == "cmd" || str == "command" || + str == "meta") { + return ui::VKEY_COMMAND; + } else if (str == "commandorcontrol" || str == "cmdorctrl") { +#if defined(OS_MACOSX) + return ui::VKEY_COMMAND; +#else + return ui::VKEY_CONTROL; +#endif + } else if (str == "alt" || str == "option") { + return ui::VKEY_MENU; + } else if (str == "shift") { + return ui::VKEY_SHIFT; + } else if (str == "altgr") { + return ui::VKEY_ALTGR; + } else if (str == "plus") { + *shifted = true; + return ui::VKEY_OEM_PLUS; + } else if (str == "tab") { + return ui::VKEY_TAB; + } else if (str == "space") { + return ui::VKEY_SPACE; + } else if (str == "backspace") { + return ui::VKEY_BACK; + } else if (str == "delete") { + return ui::VKEY_DELETE; + } else if (str == "insert") { + return ui::VKEY_INSERT; + } else if (str == "enter" || str == "return") { + return ui::VKEY_RETURN; + } else if (str == "up") { + return ui::VKEY_UP; + } else if (str == "down") { + return ui::VKEY_DOWN; + } else if (str == "left") { + return ui::VKEY_LEFT; + } else if (str == "right") { + return ui::VKEY_RIGHT; + } else if (str == "home") { + return ui::VKEY_HOME; + } else if (str == "end") { + return ui::VKEY_END; + } else if (str == "pageup") { + return ui::VKEY_PRIOR; + } else if (str == "pagedown") { + return ui::VKEY_NEXT; + } else if (str == "esc" || str == "escape") { + return ui::VKEY_ESCAPE; + } else if (str == "volumemute") { + return ui::VKEY_VOLUME_MUTE; + } else if (str == "volumeup") { + return ui::VKEY_VOLUME_UP; + } else if (str == "volumedown") { + return ui::VKEY_VOLUME_DOWN; + } else if (str == "medianexttrack") { + return ui::VKEY_MEDIA_NEXT_TRACK; + } else if (str == "mediaprevioustrack") { + return ui::VKEY_MEDIA_PREV_TRACK; + } else if (str == "mediastop") { + return ui::VKEY_MEDIA_STOP; + } else if (str == "mediaplaypause") { + return ui::VKEY_MEDIA_PLAY_PAUSE; + } else if (str == "printscreen") { + return ui::VKEY_SNAPSHOT; + } else if (str.size() > 1 && str[0] == 'f') { + // F1 - F24. + int n; + if (base::StringToInt(str.c_str() + 1, &n) && n > 0 && n < 25) { + return static_cast(ui::VKEY_F1 + n - 1); + } else { + LOG(WARNING) << str << "is not available on keyboard"; + return ui::VKEY_UNKNOWN; + } + } else { + LOG(WARNING) << "Invalid accelerator token: " << str; + return ui::VKEY_UNKNOWN; + } +} - return ui::VKEY_UNKNOWN; +} // namespace + +ui::KeyboardCode KeyboardCodeFromStr(const std::string& str, bool* shifted) { + if (str.size() == 1) + return KeyboardCodeFromCharCode(str[0], shifted); + else + return KeyboardCodeFromKeyIdentifier(str, shifted); } } // namespace atom diff --git a/atom/common/keyboard_util.h b/atom/common/keyboard_util.h index d3168d4447e0..c9d1b809e8f7 100644 --- a/atom/common/keyboard_util.h +++ b/atom/common/keyboard_util.h @@ -6,17 +6,14 @@ #define ATOM_COMMON_KEYBOARD_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 +// Return key code of the |str|, and also determine whether the SHIFT key is // pressed. -ui::KeyboardCode KeyboardCodeFromCharCode(base::char16 c, bool* shifted); - -// Return key code of the char from a string representation of the char -ui::KeyboardCode KeyboardCodeFromKeyIdentifier(const std::string& chr); +ui::KeyboardCode KeyboardCodeFromStr(const std::string& str, bool* shifted); } // namespace atom diff --git a/atom/common/lib/asar.js b/atom/common/lib/asar.js deleted file mode 100644 index f45e62b176de..000000000000 --- a/atom/common/lib/asar.js +++ /dev/null @@ -1,602 +0,0 @@ -(function () { - const asar = process.binding('atom_common_asar'); - const child_process = require('child_process'); - const path = require('path'); - const util = require('util'); - - var hasProp = {}.hasOwnProperty; - - // Cache asar archive objects. - var cachedArchives = {}; - - var getOrCreateArchive = function(p) { - var archive; - archive = cachedArchives[p]; - if (archive != null) { - return archive; - } - archive = asar.createArchive(p); - if (!archive) { - return false; - } - return cachedArchives[p] = archive; - }; - - // Clean cache on quit. - process.on('exit', function() { - var archive, p, results; - results = []; - for (p in cachedArchives) { - if (!hasProp.call(cachedArchives, p)) continue; - archive = cachedArchives[p]; - results.push(archive.destroy()); - } - return results; - }); - - // Separate asar package's path from full path. - var splitPath = function(p) { - var index; - - // shortcut to disable asar. - if (process.noAsar) { - return [false]; - } - - if (typeof p !== 'string') { - return [false]; - } - if (p.substr(-5) === '.asar') { - return [true, p, '']; - } - p = path.normalize(p); - index = p.lastIndexOf(".asar" + path.sep); - if (index === -1) { - return [false]; - } - return [true, p.substr(0, index + 5), p.substr(index + 6)]; - }; - - // Convert asar archive's Stats object to fs's Stats object. - var nextInode = 0; - - var uid = process.getuid != null ? process.getuid() : 0; - - var gid = process.getgid != null ? process.getgid() : 0; - - var fakeTime = new Date(); - - var asarStatsToFsStats = function(stats) { - return { - dev: 1, - ino: ++nextInode, - mode: 33188, - nlink: 1, - uid: uid, - gid: gid, - rdev: 0, - atime: stats.atime || fakeTime, - birthtime: stats.birthtime || fakeTime, - mtime: stats.mtime || fakeTime, - ctime: stats.ctime || fakeTime, - size: stats.size, - isFile: function() { - return stats.isFile; - }, - isDirectory: function() { - return stats.isDirectory; - }, - isSymbolicLink: function() { - return stats.isLink; - }, - isBlockDevice: function() { - return false; - }, - isCharacterDevice: function() { - return false; - }, - isFIFO: function() { - return false; - }, - isSocket: function() { - return false; - } - }; - }; - - // Create a ENOENT error. - var notFoundError = function(asarPath, filePath, callback) { - var error; - error = new Error("ENOENT, " + filePath + " not found in " + asarPath); - error.code = "ENOENT"; - error.errno = -2; - if (typeof callback !== 'function') { - throw error; - } - return process.nextTick(function() { - return callback(error); - }); - }; - - // Create a ENOTDIR error. - var notDirError = function(callback) { - var error; - error = new Error('ENOTDIR, not a directory'); - error.code = 'ENOTDIR'; - error.errno = -20; - if (typeof callback !== 'function') { - throw error; - } - return process.nextTick(function() { - return callback(error); - }); - }; - - // Create invalid archive error. - var invalidArchiveError = function(asarPath, callback) { - var error; - error = new Error("Invalid package " + asarPath); - if (typeof callback !== 'function') { - throw error; - } - return process.nextTick(function() { - return callback(error); - }); - }; - - // Override APIs that rely on passing file path instead of content to C++. - var overrideAPISync = function(module, name, arg) { - var old; - if (arg == null) { - arg = 0; - } - old = module[name]; - return module[name] = function() { - var archive, asarPath, filePath, isAsar, newPath, p, ref; - p = arguments[arg]; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return old.apply(this, arguments); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - invalidArchiveError(asarPath); - } - newPath = archive.copyFileOut(filePath); - if (!newPath) { - notFoundError(asarPath, filePath); - } - arguments[arg] = newPath; - return old.apply(this, arguments); - }; - }; - - var overrideAPI = function(module, name, arg) { - var old; - if (arg == null) { - arg = 0; - } - old = module[name]; - return module[name] = function() { - var archive, asarPath, callback, filePath, isAsar, newPath, p, ref; - p = arguments[arg]; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return old.apply(this, arguments); - } - callback = arguments[arguments.length - 1]; - if (typeof callback !== 'function') { - return overrideAPISync(module, name, arg); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - return invalidArchiveError(asarPath, callback); - } - newPath = archive.copyFileOut(filePath); - if (!newPath) { - return notFoundError(asarPath, filePath, callback); - } - arguments[arg] = newPath; - return old.apply(this, arguments); - }; - }; - - // Override fs APIs. - exports.wrapFsWithAsar = function(fs) { - var exists, existsSync, internalModuleReadFile, internalModuleStat, lstat, lstatSync, mkdir, mkdirSync, readFile, readFileSync, readdir, readdirSync, realpath, realpathSync, stat, statSync, statSyncNoException, logFDs, logASARAccess; - - logFDs = {}; - logASARAccess = function(asarPath, filePath, offset) { - if (!process.env.ELECTRON_LOG_ASAR_READS) { - return; - } - if (!logFDs[asarPath]) { - var logFilename, logPath; - const path = require('path'); - logFilename = path.basename(asarPath, '.asar') + '-access-log.txt'; - logPath = path.join(require('os').tmpdir(), logFilename); - logFDs[asarPath] = fs.openSync(logPath, 'a'); - console.log('Logging ' + asarPath + ' access to ' + logPath); - } - fs.writeSync(logFDs[asarPath], offset + ': ' + filePath + '\n'); - }; - - lstatSync = fs.lstatSync; - fs.lstatSync = function(p) { - var archive, asarPath, filePath, isAsar, ref, stats; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return lstatSync(p); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - invalidArchiveError(asarPath); - } - stats = archive.stat(filePath); - if (!stats) { - notFoundError(asarPath, filePath); - } - return asarStatsToFsStats(stats); - }; - lstat = fs.lstat; - fs.lstat = function(p, callback) { - var archive, asarPath, filePath, isAsar, ref, stats; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return lstat(p, callback); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - return invalidArchiveError(asarPath, callback); - } - stats = getOrCreateArchive(asarPath).stat(filePath); - if (!stats) { - return notFoundError(asarPath, filePath, callback); - } - return process.nextTick(function() { - return callback(null, asarStatsToFsStats(stats)); - }); - }; - statSync = fs.statSync; - fs.statSync = function(p) { - var isAsar = splitPath(p)[0]; - if (!isAsar) { - return statSync(p); - } - - // Do not distinguish links for now. - return fs.lstatSync(p); - }; - stat = fs.stat; - fs.stat = function(p, callback) { - var isAsar = splitPath(p)[0]; - if (!isAsar) { - return stat(p, callback); - } - - // Do not distinguish links for now. - return process.nextTick(function() { - return fs.lstat(p, callback); - }); - }; - statSyncNoException = fs.statSyncNoException; - fs.statSyncNoException = function(p) { - var archive, asarPath, filePath, isAsar, ref, stats; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return statSyncNoException(p); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - return false; - } - stats = archive.stat(filePath); - if (!stats) { - return false; - } - return asarStatsToFsStats(stats); - }; - realpathSync = fs.realpathSync; - fs.realpathSync = function(p) { - var archive, asarPath, filePath, isAsar, real, ref; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return realpathSync.apply(this, arguments); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - invalidArchiveError(asarPath); - } - real = archive.realpath(filePath); - if (real === false) { - notFoundError(asarPath, filePath); - } - return path.join(realpathSync(asarPath), real); - }; - realpath = fs.realpath; - fs.realpath = function(p, cache, callback) { - var archive, asarPath, filePath, isAsar, real, ref; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return realpath.apply(this, arguments); - } - if (typeof cache === 'function') { - callback = cache; - cache = void 0; - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - return invalidArchiveError(asarPath, callback); - } - real = archive.realpath(filePath); - if (real === false) { - return notFoundError(asarPath, filePath, callback); - } - return realpath(asarPath, function(err, p) { - if (err) { - return callback(err); - } - return callback(null, path.join(p, real)); - }); - }; - exists = fs.exists; - fs.exists = function(p, callback) { - var archive, asarPath, filePath, isAsar, ref; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return exists(p, callback); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - return invalidArchiveError(asarPath, callback); - } - return process.nextTick(function() { - return callback(archive.stat(filePath) !== false); - }); - }; - existsSync = fs.existsSync; - fs.existsSync = function(p) { - var archive, asarPath, filePath, isAsar, ref; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return existsSync(p); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - return false; - } - return archive.stat(filePath) !== false; - }; - readFile = fs.readFile; - fs.readFile = function(p, options, callback) { - var archive, asarPath, buffer, encoding, fd, filePath, info, isAsar, realPath, ref; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return readFile.apply(this, arguments); - } - if (typeof options === 'function') { - callback = options; - options = void 0; - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - return invalidArchiveError(asarPath, callback); - } - info = archive.getFileInfo(filePath); - if (!info) { - return notFoundError(asarPath, filePath, callback); - } - if (info.size === 0) { - return process.nextTick(function() { - return callback(null, new Buffer(0)); - }); - } - if (info.unpacked) { - realPath = archive.copyFileOut(filePath); - return fs.readFile(realPath, options, callback); - } - if (!options) { - options = { - encoding: null - }; - } else if (util.isString(options)) { - options = { - encoding: options - }; - } else if (!util.isObject(options)) { - throw new TypeError('Bad arguments'); - } - encoding = options.encoding; - buffer = new Buffer(info.size); - fd = archive.getFd(); - if (!(fd >= 0)) { - return notFoundError(asarPath, filePath, callback); - } - logASARAccess(asarPath, filePath, info.offset); - return fs.read(fd, buffer, 0, info.size, info.offset, function(error) { - return callback(error, encoding ? buffer.toString(encoding) : buffer); - }); - }; - readFileSync = fs.readFileSync; - fs.readFileSync = function(p, opts) { - // this allows v8 to optimize this function - var archive, asarPath, buffer, encoding, fd, filePath, info, isAsar, options, realPath, ref; - options = opts; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return readFileSync.apply(this, arguments); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - invalidArchiveError(asarPath); - } - info = archive.getFileInfo(filePath); - if (!info) { - notFoundError(asarPath, filePath); - } - if (info.size === 0) { - if (options) { - return ''; - } else { - return new Buffer(0); - } - } - if (info.unpacked) { - realPath = archive.copyFileOut(filePath); - return fs.readFileSync(realPath, options); - } - if (!options) { - options = { - encoding: null - }; - } else if (util.isString(options)) { - options = { - encoding: options - }; - } else if (!util.isObject(options)) { - throw new TypeError('Bad arguments'); - } - encoding = options.encoding; - buffer = new Buffer(info.size); - fd = archive.getFd(); - if (!(fd >= 0)) { - notFoundError(asarPath, filePath); - } - logASARAccess(asarPath, filePath, info.offset); - fs.readSync(fd, buffer, 0, info.size, info.offset); - if (encoding) { - return buffer.toString(encoding); - } else { - return buffer; - } - }; - readdir = fs.readdir; - fs.readdir = function(p, callback) { - var archive, asarPath, filePath, files, isAsar, ref; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return readdir.apply(this, arguments); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - return invalidArchiveError(asarPath, callback); - } - files = archive.readdir(filePath); - if (!files) { - return notFoundError(asarPath, filePath, callback); - } - return process.nextTick(function() { - return callback(null, files); - }); - }; - readdirSync = fs.readdirSync; - fs.readdirSync = function(p) { - var archive, asarPath, filePath, files, isAsar, ref; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return readdirSync.apply(this, arguments); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - invalidArchiveError(asarPath); - } - files = archive.readdir(filePath); - if (!files) { - notFoundError(asarPath, filePath); - } - return files; - }; - internalModuleReadFile = process.binding('fs').internalModuleReadFile; - process.binding('fs').internalModuleReadFile = function(p) { - var archive, asarPath, buffer, fd, filePath, info, isAsar, realPath, ref; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return internalModuleReadFile(p); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - return void 0; - } - info = archive.getFileInfo(filePath); - if (!info) { - return void 0; - } - if (info.size === 0) { - return ''; - } - if (info.unpacked) { - realPath = archive.copyFileOut(filePath); - return fs.readFileSync(realPath, { - encoding: 'utf8' - }); - } - buffer = new Buffer(info.size); - fd = archive.getFd(); - if (!(fd >= 0)) { - return void 0; - } - logASARAccess(asarPath, filePath, info.offset); - fs.readSync(fd, buffer, 0, info.size, info.offset); - return buffer.toString('utf8'); - }; - internalModuleStat = process.binding('fs').internalModuleStat; - process.binding('fs').internalModuleStat = function(p) { - var archive, asarPath, filePath, isAsar, ref, stats; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return internalModuleStat(p); - } - archive = getOrCreateArchive(asarPath); - - // -ENOENT - if (!archive) { - return -34; - } - stats = archive.stat(filePath); - - // -ENOENT - if (!stats) { - return -34; - } - if (stats.isDirectory) { - return 1; - } else { - return 0; - } - }; - - // Calling mkdir for directory inside asar archive should throw ENOTDIR - // error, but on Windows it throws ENOENT. - // This is to work around the recursive looping bug of mkdirp since it is - // widely used. - if (process.platform === 'win32') { - mkdir = fs.mkdir; - fs.mkdir = function(p, mode, callback) { - var filePath, isAsar, ref; - if (typeof mode === 'function') { - callback = mode; - } - ref = splitPath(p), isAsar = ref[0], filePath = ref[2]; - if (isAsar && filePath.length) { - return notDirError(callback); - } - return mkdir(p, mode, callback); - }; - mkdirSync = fs.mkdirSync; - fs.mkdirSync = function(p, mode) { - var filePath, isAsar, ref; - ref = splitPath(p), isAsar = ref[0], filePath = ref[2]; - if (isAsar && filePath.length) { - notDirError(); - } - return mkdirSync(p, mode); - }; - } - overrideAPI(fs, 'open'); - overrideAPI(child_process, 'execFile'); - overrideAPISync(process, 'dlopen', 1); - overrideAPISync(require('module')._extensions, '.node', 1); - overrideAPISync(fs, 'openSync'); - return overrideAPISync(child_process, 'execFileSync'); - }; -})(); diff --git a/atom/common/lib/asar_init.js b/atom/common/lib/asar_init.js deleted file mode 100644 index 49b57907c8f9..000000000000 --- a/atom/common/lib/asar_init.js +++ /dev/null @@ -1,14 +0,0 @@ -(function () { - return function(process, require, asarSource) { - // Make asar.coffee accessible via "require". - process.binding('natives').ATOM_SHELL_ASAR = asarSource; - - // Monkey-patch the fs module. - require('ATOM_SHELL_ASAR').wrapFsWithAsar(require('fs')); - - // Make graceful-fs work with asar. - var source = process.binding('natives'); - source['original-fs'] = source.fs; - return source['fs'] = "var src = '(function (exports, require, module, __filename, __dirname) { ' +\n process.binding('natives')['original-fs'] +\n ' });';\nvar vm = require('vm');\nvar fn = vm.runInThisContext(src, { filename: 'fs.js' });\nfn(exports, require, module);\nvar asar = require('ATOM_SHELL_ASAR');\nasar.wrapFsWithAsar(exports);"; - }; -})(); diff --git a/atom/common/lib/reset-search-paths.js b/atom/common/lib/reset-search-paths.js deleted file mode 100644 index abf671196735..000000000000 --- a/atom/common/lib/reset-search-paths.js +++ /dev/null @@ -1,36 +0,0 @@ -const path = require('path'); -const Module = require('module'); - -// Clear Node's global search paths. -Module.globalPaths.length = 0; - -// Clear current and parent(init.coffee)'s search paths. -module.paths = []; - -module.parent.paths = []; - -// Prevent Node from adding paths outside this app to search paths. -Module._nodeModulePaths = function(from) { - var dir, i, part, parts, paths, skipOutsidePaths, splitRe, tip; - from = path.resolve(from); - - // If "from" is outside the app then we do nothing. - skipOutsidePaths = from.startsWith(process.resourcesPath); - - // Following logoic is copied from module.js. - splitRe = process.platform === 'win32' ? /[\/\\]/ : /\//; - paths = []; - parts = from.split(splitRe); - for (tip = i = parts.length - 1; i >= 0; tip = i += -1) { - part = parts[tip]; - if (part === 'node_modules') { - continue; - } - dir = parts.slice(0, tip + 1).join(path.sep); - if (skipOutsidePaths && !dir.startsWith(process.resourcesPath)) { - break; - } - paths.push(path.join(dir, 'node_modules')); - } - return paths; -}; diff --git a/atom/common/native_mate_converters/blink_converter.cc b/atom/common/native_mate_converters/blink_converter.cc index fdb894b33462..4e37836c5a55 100644 --- a/atom/common/native_mate_converters/blink_converter.cc +++ b/atom/common/native_mate_converters/blink_converter.cc @@ -12,9 +12,11 @@ #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/WebCache.h" #include "third_party/WebKit/public/web/WebDeviceEmulationParams.h" #include "third_party/WebKit/public/web/WebFindOptions.h" #include "third_party/WebKit/public/web/WebInputEvent.h" +#include "ui/base/clipboard/clipboard.h" namespace { @@ -90,6 +92,8 @@ struct Converter { *out = blink::WebMouseEvent::Button::ButtonMiddle; else if (button == "right") *out = blink::WebMouseEvent::Button::ButtonRight; + else + return false; return true; } }; @@ -159,25 +163,22 @@ bool Converter::FromV8( return false; if (!ConvertFromV8(isolate, val, static_cast(out))) return false; - base::char16 code; - std::string identifier; - bool shifted = false; - if (dict.Get("keyCode", &code)) - out->windowsKeyCode = atom::KeyboardCodeFromCharCode(code, &shifted); - else if (dict.Get("keyCode", &identifier)) - out->windowsKeyCode = atom::KeyboardCodeFromKeyIdentifier( - base::ToLowerASCII(identifier)); + std::string str; + bool shifted = false; + if (dict.Get("keyCode", &str)) + out->windowsKeyCode = atom::KeyboardCodeFromStr(str, &shifted); else return false; if (shifted) out->modifiers |= blink::WebInputEvent::ShiftKey; out->setKeyIdentifierFromWindowsKeyCode(); - if (out->type == blink::WebInputEvent::Char || - out->type == blink::WebInputEvent::RawKeyDown) { - out->text[0] = code; - out->unmodifiedText[0] = code; + if ((out->type == blink::WebInputEvent::Char || + out->type == blink::WebInputEvent::RawKeyDown) && + str.size() == 1) { + out->text[0] = str[0]; + out->unmodifiedText[0] = str[0]; } return true; } @@ -203,7 +204,8 @@ bool Converter::FromV8( return false; if (!dict.Get("x", &out->x) || !dict.Get("y", &out->y)) return false; - dict.Get("button", &out->button); + if (!dict.Get("button", &out->button)) + out->button = blink::WebMouseEvent::Button::ButtonLeft; dict.Get("globalX", &out->globalX); dict.Get("globalY", &out->globalY); dict.Get("movementX", &out->movementX); @@ -299,4 +301,117 @@ bool Converter::FromV8( return true; } +// static +v8::Local Converter::ToV8( + v8::Isolate* isolate, const blink::WebContextMenuData::MediaType& in) { + switch (in) { + case blink::WebContextMenuData::MediaTypeImage: + return mate::StringToV8(isolate, "image"); + case blink::WebContextMenuData::MediaTypeVideo: + return mate::StringToV8(isolate, "video"); + case blink::WebContextMenuData::MediaTypeAudio: + return mate::StringToV8(isolate, "audio"); + case blink::WebContextMenuData::MediaTypeCanvas: + return mate::StringToV8(isolate, "canvas"); + case blink::WebContextMenuData::MediaTypeFile: + return mate::StringToV8(isolate, "file"); + case blink::WebContextMenuData::MediaTypePlugin: + return mate::StringToV8(isolate, "plugin"); + default: + return mate::StringToV8(isolate, "none"); + } +} + +// static +v8::Local Converter::ToV8( + v8::Isolate* isolate, + const blink::WebContextMenuData::InputFieldType& in) { + switch (in) { + case blink::WebContextMenuData::InputFieldTypePlainText: + return mate::StringToV8(isolate, "plainText"); + case blink::WebContextMenuData::InputFieldTypePassword: + return mate::StringToV8(isolate, "password"); + case blink::WebContextMenuData::InputFieldTypeOther: + return mate::StringToV8(isolate, "other"); + default: + return mate::StringToV8(isolate, "none"); + } +} + +v8::Local EditFlagsToV8(v8::Isolate* isolate, int editFlags) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.Set("canUndo", + !!(editFlags & blink::WebContextMenuData::CanUndo)); + dict.Set("canRedo", + !!(editFlags & blink::WebContextMenuData::CanRedo)); + dict.Set("canCut", + !!(editFlags & blink::WebContextMenuData::CanCut)); + dict.Set("canCopy", + !!(editFlags & blink::WebContextMenuData::CanCopy)); + + bool pasteFlag = false; + if (editFlags & blink::WebContextMenuData::CanPaste) { + std::vector types; + bool ignore; + ui::Clipboard::GetForCurrentThread()->ReadAvailableTypes( + ui::CLIPBOARD_TYPE_COPY_PASTE, &types, &ignore); + pasteFlag = !types.empty(); + } + dict.Set("canPaste", pasteFlag); + + dict.Set("canDelete", + !!(editFlags & blink::WebContextMenuData::CanDelete)); + dict.Set("canSelectAll", + !!(editFlags & blink::WebContextMenuData::CanSelectAll)); + + return mate::ConvertToV8(isolate, dict); +} + +v8::Local MediaFlagsToV8(v8::Isolate* isolate, int mediaFlags) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.Set("inError", + !!(mediaFlags & blink::WebContextMenuData::MediaInError)); + dict.Set("isPaused", + !!(mediaFlags & blink::WebContextMenuData::MediaPaused)); + dict.Set("isMuted", + !!(mediaFlags & blink::WebContextMenuData::MediaMuted)); + dict.Set("hasAudio", + !!(mediaFlags & blink::WebContextMenuData::MediaHasAudio)); + dict.Set("isLooping", + (mediaFlags & blink::WebContextMenuData::MediaLoop) != 0); + dict.Set("isControlsVisible", + (mediaFlags & blink::WebContextMenuData::MediaControls) != 0); + dict.Set("canToggleControls", + !!(mediaFlags & blink::WebContextMenuData::MediaCanToggleControls)); + dict.Set("canRotate", + !!(mediaFlags & blink::WebContextMenuData::MediaCanRotate)); + return mate::ConvertToV8(isolate, dict); +} + +v8::Local Converter::ToV8( + v8::Isolate* isolate, + const blink::WebCache::ResourceTypeStat& stat) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.Set("count", static_cast(stat.count)); + dict.Set("size", static_cast(stat.size)); + dict.Set("liveSize", static_cast(stat.liveSize)); + dict.Set("decodedSize", static_cast(stat.decodedSize)); + dict.Set("purgedSize", static_cast(stat.purgedSize)); + dict.Set("purgeableSize", static_cast(stat.purgeableSize)); + return dict.GetHandle(); +} + +v8::Local Converter::ToV8( + v8::Isolate* isolate, + const blink::WebCache::ResourceTypeStats& stats) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.Set("images", stats.images); + dict.Set("scripts", stats.scripts); + dict.Set("cssStyleSheets", stats.cssStyleSheets); + dict.Set("xslStyleSheets", stats.xslStyleSheets); + dict.Set("fonts", stats.fonts); + dict.Set("other", stats.other); + return dict.GetHandle(); +} + } // namespace mate diff --git a/atom/common/native_mate_converters/blink_converter.h b/atom/common/native_mate_converters/blink_converter.h index 6a3601929214..78275ab62ec9 100644 --- a/atom/common/native_mate_converters/blink_converter.h +++ b/atom/common/native_mate_converters/blink_converter.h @@ -6,6 +6,8 @@ #define ATOM_COMMON_NATIVE_MATE_CONVERTERS_BLINK_CONVERTER_H_ #include "native_mate/converter.h" +#include "third_party/WebKit/public/web/WebCache.h" +#include "third_party/WebKit/public/web/WebContextMenuData.h" namespace blink { class WebInputEvent; @@ -87,6 +89,35 @@ struct Converter { blink::WebFindOptions* out); }; +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const blink::WebContextMenuData::MediaType& in); +}; + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const blink::WebContextMenuData::InputFieldType& in); +}; + +template<> +struct Converter { + static v8::Local ToV8( + v8::Isolate* isolate, + const blink::WebCache::ResourceTypeStat& stat); +}; + +template<> +struct Converter { + static v8::Local ToV8( + v8::Isolate* isolate, + const blink::WebCache::ResourceTypeStats& stats); +}; + +v8::Local EditFlagsToV8(v8::Isolate* isolate, int editFlags); +v8::Local MediaFlagsToV8(v8::Isolate* isolate, int mediaFlags); + } // namespace mate #endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_BLINK_CONVERTER_H_ diff --git a/atom/common/native_mate_converters/callback.h b/atom/common/native_mate_converters/callback.h index 6ef8e74c735a..43a1baf25fa8 100644 --- a/atom/common/native_mate_converters/callback.h +++ b/atom/common/native_mate_converters/callback.h @@ -13,7 +13,6 @@ #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" namespace mate { @@ -49,9 +48,11 @@ struct V8FunctionInvoker(ArgTypes...)> { v8::EscapableHandleScope handle_scope(isolate); if (!function.IsAlive()) return v8::Null(isolate); - scoped_ptr script_scope( + std::unique_ptr script_scope( Locker::IsBrowserProcess() ? - nullptr : new blink::WebScopedRunV8Script); + nullptr : + new v8::MicrotasksScope(isolate, + v8::MicrotasksScope::kRunMicrotasks)); v8::Local holder = function.NewHandle(isolate); v8::Local context = holder->CreationContext(); v8::Context::Scope context_scope(context); @@ -70,9 +71,11 @@ struct V8FunctionInvoker { v8::HandleScope handle_scope(isolate); if (!function.IsAlive()) return; - scoped_ptr script_scope( + std::unique_ptr script_scope( Locker::IsBrowserProcess() ? - nullptr : new blink::WebScopedRunV8Script); + nullptr : + new v8::MicrotasksScope(isolate, + v8::MicrotasksScope::kRunMicrotasks)); v8::Local holder = function.NewHandle(isolate); v8::Local context = holder->CreationContext(); v8::Context::Scope context_scope(context); @@ -91,9 +94,11 @@ struct V8FunctionInvoker { ReturnType ret = ReturnType(); if (!function.IsAlive()) return ret; - scoped_ptr script_scope( + std::unique_ptr script_scope( Locker::IsBrowserProcess() ? - nullptr : new blink::WebScopedRunV8Script); + nullptr : + new v8::MicrotasksScope(isolate, + v8::MicrotasksScope::kRunMicrotasks)); v8::Local holder = function.NewHandle(isolate); v8::Local context = holder->CreationContext(); v8::Context::Scope context_scope(context); diff --git a/atom/common/native_mate_converters/content_converter.cc b/atom/common/native_mate_converters/content_converter.cc index f5d81d085bc1..824065882e82 100644 --- a/atom/common/native_mate_converters/content_converter.cc +++ b/atom/common/native_mate_converters/content_converter.cc @@ -9,8 +9,11 @@ #include "atom/browser/api/atom_api_web_contents.h" #include "atom/browser/web_contents_permission_helper.h" +#include "atom/common/native_mate_converters/blink_converter.h" #include "atom/common/native_mate_converters/callback.h" +#include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/string16_converter.h" +#include "atom/common/native_mate_converters/ui_base_types_converter.h" #include "content/public/browser/web_contents.h" #include "content/public/common/context_menu_params.h" #include "native_mate/dictionary.h" @@ -94,6 +97,23 @@ v8::Local Converter::ToV8( mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); dict.Set("x", params.x); dict.Set("y", params.y); + dict.Set("linkURL", params.link_url); + dict.Set("linkText", params.link_text); + dict.Set("pageURL", params.page_url); + dict.Set("frameURL", params.frame_url); + dict.Set("srcURL", params.src_url); + dict.Set("mediaType", params.media_type); + dict.Set("mediaFlags", MediaFlagsToV8(isolate, params.media_flags)); + dict.Set("hasImageContents", params.has_image_contents); + dict.Set("isEditable", params.is_editable); + dict.Set("editFlags", EditFlagsToV8(isolate, params.edit_flags)); + dict.Set("selectionText", params.selection_text); + dict.Set("titleText", params.title_text); + dict.Set("misspelledWord", params.misspelled_word); + dict.Set("frameCharset", params.frame_charset); + dict.Set("inputFieldType", params.input_field_type); + dict.Set("menuSourceType", params.source_type); + if (params.custom_context.is_pepper_menu) dict.Set("menu", MenuToV8(isolate, val.second, params.custom_context, params.custom_items)); @@ -101,18 +121,18 @@ v8::Local Converter::ToV8( } // static -bool Converter::FromV8( +bool Converter::FromV8( v8::Isolate* isolate, v8::Local val, - content::PermissionStatus* out) { + blink::mojom::PermissionStatus* out) { bool result; if (!ConvertFromV8(isolate, val, &result)) return false; if (result) - *out = content::PERMISSION_STATUS_GRANTED; + *out = blink::mojom::PermissionStatus::GRANTED; else - *out = content::PERMISSION_STATUS_DENIED; + *out = blink::mojom::PermissionStatus::DENIED; return true; } @@ -145,6 +165,8 @@ v8::Local Converter::ToV8( return StringToV8(isolate, "pointerLock"); else if (val == (content::PermissionType)(PermissionType::FULLSCREEN)) return StringToV8(isolate, "fullscreen"); + else if (val == (content::PermissionType)(PermissionType::OPEN_EXTERNAL)) + return StringToV8(isolate, "openExternal"); return StringToV8(isolate, "unknown"); } @@ -178,4 +200,17 @@ v8::Local Converter::ToV8( return atom::api::WebContents::CreateFrom(isolate, val).ToV8(); } +// static +bool Converter::FromV8( + v8::Isolate* isolate, + v8::Local val, + content::WebContents** out) { + atom::api::WebContents* web_contents = nullptr; + if (!ConvertFromV8(isolate, val, &web_contents) || !web_contents) + return false; + + *out = web_contents->web_contents(); + return true; +} + } // namespace mate diff --git a/atom/common/native_mate_converters/content_converter.h b/atom/common/native_mate_converters/content_converter.h index b1a42b6897ca..522961a7e96c 100644 --- a/atom/common/native_mate_converters/content_converter.h +++ b/atom/common/native_mate_converters/content_converter.h @@ -9,8 +9,8 @@ #include "content/public/browser/permission_type.h" #include "content/public/common/menu_item.h" -#include "content/public/common/permission_status.mojom.h" #include "content/public/common/stop_find_action.h" +#include "third_party/WebKit/public/platform/modules/permissions/permission_status.mojom.h" #include "native_mate/converter.h" namespace content { @@ -36,9 +36,9 @@ struct Converter { }; template<> -struct Converter { +struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Local val, - content::PermissionStatus* out); + blink::mojom::PermissionStatus* out); }; template<> @@ -57,6 +57,8 @@ template<> struct Converter { static v8::Local ToV8(v8::Isolate* isolate, content::WebContents* val); + static bool FromV8(v8::Isolate* isolate, v8::Local val, + content::WebContents** out); }; } // namespace mate diff --git a/atom/common/native_mate_converters/image_converter.cc b/atom/common/native_mate_converters/image_converter.cc index 550bb7b904a7..cfb1938a138f 100644 --- a/atom/common/native_mate_converters/image_converter.cc +++ b/atom/common/native_mate_converters/image_converter.cc @@ -28,16 +28,8 @@ bool Converter::FromV8(v8::Isolate* isolate, return true; Handle native_image; - if (!ConvertFromV8(isolate, val, &native_image)) { - // Try converting from file path. - base::FilePath path; - if (!Converter::FromV8(isolate, val, &path)) - return false; - - native_image = atom::api::NativeImage::CreateFromPath(isolate, path); - if (native_image->image().IsEmpty()) - return false; - } + if (!ConvertFromV8(isolate, val, &native_image)) + return false; *out = native_image->image(); return true; diff --git a/atom/common/native_mate_converters/net_converter.cc b/atom/common/native_mate_converters/net_converter.cc index 184f1c3fecff..772ae6e22087 100644 --- a/atom/common/native_mate_converters/net_converter.cc +++ b/atom/common/native_mate_converters/net_converter.cc @@ -25,16 +25,16 @@ namespace mate { // static v8::Local Converter::ToV8( v8::Isolate* isolate, const net::URLRequest* val) { - scoped_ptr dict(new base::DictionaryValue); + std::unique_ptr dict(new base::DictionaryValue); dict->SetString("method", val->method()); std::string url; if (!val->url_chain().empty()) url = val->url().spec(); dict->SetStringWithoutPathExpansion("url", url); dict->SetString("referrer", val->referrer()); - scoped_ptr list(new base::ListValue); + std::unique_ptr list(new base::ListValue); atom::GetUploadData(list.get(), val); if (!list->empty()) - dict->Set("uploadData", list.Pass()); + dict->Set("uploadData", std::move(list)); return mate::ConvertToV8(isolate, *(dict.get())); } @@ -74,25 +74,25 @@ void GetUploadData(base::ListValue* upload_data_list, const net::UploadDataStream* upload_data = request->get_upload(); if (!upload_data) return; - const ScopedVector* readers = + const std::vector>* readers = upload_data->GetElementReaders(); for (const auto& reader : *readers) { - scoped_ptr upload_data_dict( + std::unique_ptr upload_data_dict( new base::DictionaryValue); if (reader->AsBytesReader()) { const net::UploadBytesElementReader* bytes_reader = reader->AsBytesReader(); - scoped_ptr bytes( + std::unique_ptr bytes( base::BinaryValue::CreateWithCopiedBuffer(bytes_reader->bytes(), bytes_reader->length())); - upload_data_dict->Set("bytes", bytes.Pass()); + upload_data_dict->Set("bytes", std::move(bytes)); } else if (reader->AsFileReader()) { const net::UploadFileElementReader* file_reader = reader->AsFileReader(); auto file_path = file_reader->path().AsUTF8Unsafe(); upload_data_dict->SetStringWithoutPathExpansion("file", file_path); } - upload_data_list->Append(upload_data_dict.Pass()); + upload_data_list->Append(std::move(upload_data_dict)); } } diff --git a/atom/common/native_mate_converters/ui_base_types_converter.h b/atom/common/native_mate_converters/ui_base_types_converter.h new file mode 100644 index 000000000000..ad3390566c88 --- /dev/null +++ b/atom/common/native_mate_converters/ui_base_types_converter.h @@ -0,0 +1,34 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_UI_BASE_TYPES_CONVERTER_H_ +#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_UI_BASE_TYPES_CONVERTER_H_ + +#include "native_mate/converter.h" +#include "ui/base/ui_base_types.h" + +namespace mate { + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const ui::MenuSourceType& in) { + switch (in) { + case ui::MENU_SOURCE_MOUSE: + return mate::StringToV8(isolate, "mouse"); + case ui::MENU_SOURCE_KEYBOARD: + return mate::StringToV8(isolate, "keyboard"); + case ui::MENU_SOURCE_TOUCH: + return mate::StringToV8(isolate, "touch"); + case ui::MENU_SOURCE_TOUCH_EDIT_MENU: + return mate::StringToV8(isolate, "touchMenu"); + default: + return mate::StringToV8(isolate, "none"); + } + } +}; + +} // namespace mate + +#endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_UI_BASE_TYPES_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 7d3a1277cb8b..ff96b727e02a 100644 --- a/atom/common/native_mate_converters/v8_value_converter.cc +++ b/atom/common/native_mate_converters/v8_value_converter.cc @@ -76,15 +76,10 @@ class V8ValueConverter::FromV8ValueState { }; V8ValueConverter::V8ValueConverter() - : date_allowed_(false), - reg_exp_allowed_(false), + : reg_exp_allowed_(false), function_allowed_(false), strip_null_from_objects_(false) {} -void V8ValueConverter::SetDateAllowed(bool val) { - date_allowed_ = val; -} - void V8ValueConverter::SetRegExpAllowed(bool val) { reg_exp_allowed_ = val; } @@ -174,7 +169,7 @@ v8::Local V8ValueConverter::ToV8Array( CHECK(!child_v8.IsEmpty()); v8::TryCatch try_catch; - result->Set(static_cast(i), child_v8); + result->Set(static_cast(i), child_v8); if (try_catch.HasCaught()) LOG(ERROR) << "Setter for index " << i << " threw an exception."; } @@ -243,12 +238,17 @@ base::Value* V8ValueConverter::FromV8ValueImpl( return NULL; if (val->IsDate()) { - if (!date_allowed_) - // JSON.stringify would convert this to a string, but an object is more - // consistent within this class. - return FromV8Object(val->ToObject(), state, isolate); v8::Date* date = v8::Date::Cast(*val); - return new base::FundamentalValue(date->NumberValue() / 1000.0); + v8::Local toISOString = + date->Get(v8::String::NewFromUtf8(isolate, "toISOString")); + if (toISOString->IsFunction()) { + v8::Local result = + toISOString.As()->Call(val, 0, nullptr); + if (!result.IsEmpty()) { + v8::String::Utf8Value utf8(result->ToString()); + return new base::StringValue(std::string(*utf8, utf8.length())); + } + } } if (val->IsRegExp()) { @@ -288,7 +288,7 @@ base::Value* V8ValueConverter::FromV8Array( if (!state->UpdateAndCheckUniqueness(val)) return base::Value::CreateNullValue().release(); - scoped_ptr scope; + std::unique_ptr scope; // If val was created in a different context than our current one, change to // that context, but change back after val is converted. if (!val->CreationContext().IsEmpty() && @@ -298,7 +298,7 @@ base::Value* V8ValueConverter::FromV8Array( base::ListValue* result = new base::ListValue(); // Only fields with integer keys are carried over to the ListValue. - for (uint32 i = 0; i < val->Length(); ++i) { + for (uint32_t i = 0; i < val->Length(); ++i) { v8::TryCatch try_catch; v8::Local child_v8 = val->Get(i); if (try_catch.HasCaught()) { @@ -335,17 +335,17 @@ base::Value* V8ValueConverter::FromV8Object( if (!state->UpdateAndCheckUniqueness(val)) return base::Value::CreateNullValue().release(); - scoped_ptr scope; + std::unique_ptr scope; // If val was created in a different context than our current one, change to // that context, but change back after val is converted. if (!val->CreationContext().IsEmpty() && val->CreationContext() != isolate->GetCurrentContext()) scope.reset(new v8::Context::Scope(val->CreationContext())); - scoped_ptr result(new base::DictionaryValue()); + std::unique_ptr result(new base::DictionaryValue()); v8::Local property_names(val->GetOwnPropertyNames()); - for (uint32 i = 0; i < property_names->Length(); ++i) { + for (uint32_t i = 0; i < property_names->Length(); ++i) { v8::Local key(property_names->Get(i)); // Extend this test to cover more types as necessary and if sensible. @@ -371,7 +371,8 @@ base::Value* V8ValueConverter::FromV8Object( child_v8 = v8::Null(isolate); } - scoped_ptr child(FromV8ValueImpl(state, child_v8, isolate)); + std::unique_ptr child( + FromV8ValueImpl(state, child_v8, isolate)); if (!child.get()) // JSON.stringify skips properties whose values don't serialize, for // example undefined and functions. Emulate that behavior. diff --git a/atom/common/native_mate_converters/v8_value_converter.h b/atom/common/native_mate_converters/v8_value_converter.h index 2b695b43747b..632587022d14 100644 --- a/atom/common/native_mate_converters/v8_value_converter.h +++ b/atom/common/native_mate_converters/v8_value_converter.h @@ -5,7 +5,7 @@ #ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_V8_VALUE_CONVERTER_H_ #define ATOM_COMMON_NATIVE_MATE_CONVERTERS_V8_VALUE_CONVERTER_H_ -#include "base/basictypes.h" +#include "base/macros.h" #include "base/compiler_specific.h" #include "v8/include/v8.h" @@ -22,7 +22,6 @@ class V8ValueConverter { public: V8ValueConverter(); - void SetDateAllowed(bool val); void SetRegExpAllowed(bool val); void SetFunctionAllowed(bool val); void SetStripNullFromObjects(bool val); @@ -58,9 +57,6 @@ class V8ValueConverter { FromV8ValueState* state, v8::Isolate* isolate) const; - // If true, we will convert Date JavaScript objects to doubles. - bool date_allowed_; - // If true, we will convert RegExp JavaScript objects to string. bool reg_exp_allowed_; diff --git a/atom/common/native_mate_converters/value_converter.cc b/atom/common/native_mate_converters/value_converter.cc index c9c1a861ba26..c3c7ae0383c5 100644 --- a/atom/common/native_mate_converters/value_converter.cc +++ b/atom/common/native_mate_converters/value_converter.cc @@ -12,8 +12,8 @@ namespace mate { bool Converter::FromV8(v8::Isolate* isolate, v8::Local val, base::DictionaryValue* out) { - scoped_ptr converter(new atom::V8ValueConverter); - scoped_ptr value(converter->FromV8Value( + std::unique_ptr converter(new atom::V8ValueConverter); + std::unique_ptr value(converter->FromV8Value( val, isolate->GetCurrentContext())); if (value && value->IsType(base::Value::TYPE_DICTIONARY)) { out->Swap(static_cast(value.get())); @@ -26,15 +26,15 @@ bool Converter::FromV8(v8::Isolate* isolate, v8::Local Converter::ToV8( v8::Isolate* isolate, const base::DictionaryValue& val) { - scoped_ptr converter(new atom::V8ValueConverter); + std::unique_ptr converter(new atom::V8ValueConverter); return converter->ToV8Value(&val, isolate->GetCurrentContext()); } bool Converter::FromV8(v8::Isolate* isolate, v8::Local val, base::ListValue* out) { - scoped_ptr converter(new atom::V8ValueConverter); - scoped_ptr value(converter->FromV8Value( + std::unique_ptr converter(new atom::V8ValueConverter); + std::unique_ptr value(converter->FromV8Value( val, isolate->GetCurrentContext())); if (value->IsType(base::Value::TYPE_LIST)) { out->Swap(static_cast(value.get())); @@ -47,7 +47,7 @@ bool Converter::FromV8(v8::Isolate* isolate, v8::Local Converter::ToV8( v8::Isolate* isolate, const base::ListValue& val) { - scoped_ptr converter(new atom::V8ValueConverter); + std::unique_ptr converter(new atom::V8ValueConverter); return converter->ToV8Value(&val, isolate->GetCurrentContext()); } diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index 69e7906ffbbb..ed2ea01675a1 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -21,7 +21,6 @@ #include "content/public/browser/browser_thread.h" #include "content/public/common/content_paths.h" #include "native_mate/dictionary.h" -#include "third_party/WebKit/public/web/WebScopedMicrotaskSuppression.h" using content::BrowserThread; @@ -43,7 +42,9 @@ REFERENCE_MODULE(atom_browser_power_monitor); REFERENCE_MODULE(atom_browser_power_save_blocker); REFERENCE_MODULE(atom_browser_protocol); REFERENCE_MODULE(atom_browser_global_shortcut); +REFERENCE_MODULE(atom_browser_render_process_preferences); REFERENCE_MODULE(atom_browser_session); +REFERENCE_MODULE(atom_browser_system_preferences); REFERENCE_MODULE(atom_browser_tray); REFERENCE_MODULE(atom_browser_web_contents); REFERENCE_MODULE(atom_browser_web_view_manager); @@ -51,7 +52,6 @@ REFERENCE_MODULE(atom_browser_window); REFERENCE_MODULE(atom_common_asar); REFERENCE_MODULE(atom_common_clipboard); REFERENCE_MODULE(atom_common_crash_reporter); -REFERENCE_MODULE(atom_common_id_weak_map); REFERENCE_MODULE(atom_common_native_image); REFERENCE_MODULE(atom_common_screen); REFERENCE_MODULE(atom_common_shell); @@ -79,13 +79,13 @@ void UvNoOp(uv_async_t* handle) { // Convert the given vector to an array of C-strings. The strings in the // returned vector are only guaranteed valid so long as the vector of strings // is not modified. -scoped_ptr StringVectorToArgArray( +std::unique_ptr StringVectorToArgArray( const std::vector& vector) { - scoped_ptr array(new const char*[vector.size()]); + std::unique_ptr array(new const char*[vector.size()]); for (size_t i = 0; i < vector.size(); ++i) { array[i] = vector[i].c_str(); } - return array.Pass(); + return array; } base::FilePath GetResourcesPath(bool is_browser) { @@ -106,8 +106,6 @@ base::FilePath GetResourcesPath(bool is_browser) { } // namespace -node::Environment* global_env = nullptr; - NodeBindings::NodeBindings(bool is_browser) : is_browser_(is_browser), message_loop_(nullptr), @@ -148,7 +146,7 @@ void NodeBindings::Initialize() { #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()); + std::unique_ptr env(base::Environment::Create()); if (env->HasVar("ELECTRON_DEFAULT_ERROR_MODE")) SetErrorMode(0); #endif @@ -163,14 +161,13 @@ node::Environment* NodeBindings::CreateEnvironment( FILE_PATH_LITERAL("browser") : FILE_PATH_LITERAL("renderer"); base::FilePath resources_path = GetResourcesPath(is_browser_); base::FilePath script_path = - resources_path.Append(FILE_PATH_LITERAL("atom.asar")) + resources_path.Append(FILE_PATH_LITERAL("electron.asar")) .Append(process_type) - .Append(FILE_PATH_LITERAL("lib")) .Append(FILE_PATH_LITERAL("init.js")); std::string script_path_str = script_path.AsUTF8Unsafe(); args.insert(args.begin() + 1, script_path_str.c_str()); - scoped_ptr c_argv = StringVectorToArgArray(args); + std::unique_ptr c_argv = StringVectorToArgArray(args); node::Environment* env = node::CreateEnvironment( context->GetIsolate(), uv_default_loop(), context, args.size(), c_argv.get(), 0, nullptr); @@ -178,6 +175,9 @@ node::Environment* NodeBindings::CreateEnvironment( mate::Dictionary process(context->GetIsolate(), env->process_object()); process.Set("type", process_type); process.Set("resourcesPath", resources_path); + // Do not set DOM globals for renderer process. + if (!is_browser_) + process.Set("_noBrowserGlobals", resources_path); // The path to helper app. base::FilePath helper_exec_path; PathService::Get(content::CHILD_PROCESS_EXE, &helper_exec_path); @@ -215,10 +215,8 @@ void NodeBindings::RunMessageLoop() { void NodeBindings::UvRunOnce() { DCHECK(!is_browser_ || BrowserThread::CurrentlyOn(BrowserThread::UI)); - // By default the global env would be used unless user specified another one - // (this happens for renderer process, which wraps the uv loop with web page - // context). - node::Environment* env = uv_env() ? uv_env() : global_env; + node::Environment* env = uv_env(); + CHECK(env); // Use Locker in browser process. mate::Locker locker(env->isolate()); @@ -228,8 +226,10 @@ void NodeBindings::UvRunOnce() { v8::Context::Scope context_scope(env->context()); // Perform microtask checkpoint after running JavaScript. - scoped_ptr script_scope( - is_browser_ ? nullptr : new blink::WebScopedRunV8Script); + std::unique_ptr script_scope(is_browser_ ? + nullptr : + new v8::MicrotasksScope(env->isolate(), + v8::MicrotasksScope::kRunMicrotasks)); // Deal with uv events. int r = uv_run(uv_loop_, UV_RUN_NOWAIT); diff --git a/atom/common/node_bindings.h b/atom/common/node_bindings.h index 93ad77149168..16d512d3bed0 100644 --- a/atom/common/node_bindings.h +++ b/atom/common/node_bindings.h @@ -5,7 +5,7 @@ #ifndef ATOM_COMMON_NODE_BINDINGS_H_ #define ATOM_COMMON_NODE_BINDINGS_H_ -#include "base/basictypes.h" +#include "base/macros.h" #include "base/memory/weak_ptr.h" #include "v8/include/v8.h" #include "vendor/node/deps/uv/include/uv.h" diff --git a/atom/common/node_bindings_mac.cc b/atom/common/node_bindings_mac.cc index 877497d5a1e2..9e61d27f496b 100644 --- a/atom/common/node_bindings_mac.cc +++ b/atom/common/node_bindings_mac.cc @@ -5,6 +5,7 @@ #include "atom/common/node_bindings_mac.h" #include +#include #include #include #include @@ -14,13 +15,7 @@ namespace atom { NodeBindingsMac::NodeBindingsMac(bool is_browser) - : NodeBindings(is_browser), - kqueue_(kqueue()) { - // Add uv's backend fd to kqueue. - struct kevent ev; - EV_SET(&ev, uv_backend_fd(uv_loop_), EVFILT_READ, EV_ADD | EV_ENABLE, - 0, 0, 0); - kevent(kqueue_, &ev, 1, NULL, 0, NULL); + : NodeBindings(is_browser) { } NodeBindingsMac::~NodeBindingsMac() { @@ -44,19 +39,22 @@ void NodeBindingsMac::OnWatcherQueueChanged(uv_loop_t* loop) { } void NodeBindingsMac::PollEvents() { - struct timespec spec; + struct timeval tv; int timeout = uv_backend_timeout(uv_loop_); if (timeout != -1) { - spec.tv_sec = timeout / 1000; - spec.tv_nsec = (timeout % 1000) * 1000000; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; } + fd_set readset; + int fd = uv_backend_fd(uv_loop_); + FD_ZERO(&readset); + FD_SET(fd, &readset); + // Wait for new libuv events. int r; do { - struct kevent ev; - r = ::kevent(kqueue_, NULL, 0, &ev, 1, - timeout == -1 ? NULL : &spec); + r = select(fd + 1, &readset, NULL, NULL, timeout == -1 ? NULL : &tv); } while (r == -1 && errno == EINTR); } diff --git a/atom/common/node_bindings_mac.h b/atom/common/node_bindings_mac.h index 03152ada3ea6..96c79ec6f0df 100644 --- a/atom/common/node_bindings_mac.h +++ b/atom/common/node_bindings_mac.h @@ -23,9 +23,6 @@ class NodeBindingsMac : public NodeBindings { void PollEvents() override; - // Kqueue to poll for uv's backend fd. - int kqueue_; - DISALLOW_COPY_AND_ASSIGN(NodeBindingsMac); }; diff --git a/atom/common/node_includes.h b/atom/common/node_includes.h index 3876d8622913..3734a43e5f45 100644 --- a/atom/common/node_includes.h +++ b/atom/common/node_includes.h @@ -21,6 +21,7 @@ #undef CHECK_LT #undef DISALLOW_COPY_AND_ASSIGN #undef NO_RETURN +#undef arraysize #undef debug_string // This is defined in OS X 10.9 SDK in AssertMacros.h. #include "vendor/node/src/env.h" #include "vendor/node/src/env-inl.h" @@ -28,11 +29,4 @@ #include "vendor/node/src/node_buffer.h" #include "vendor/node/src/node_internals.h" -namespace atom { -// Defined in node_bindings.cc. -// For renderer it's created in atom_renderer_client.cc. -// For browser it's created in atom_browser_main_parts.cc. -extern node::Environment* global_env; -} - #endif // ATOM_COMMON_NODE_INCLUDES_H_ diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index ac63ea1b7276..de130eee13eb 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -103,6 +103,9 @@ const char kExperimentalCanvasFeatures[] = "experimentalCanvasFeatures"; // Opener window's ID. const char kOpenerID[] = "openerId"; +// Enable the rubber banding effect. +const char kScrollBounce[] = "scrollBounce"; + // Enable blink features. const char kBlinkFeatures[] = "blinkFeatures"; @@ -119,15 +122,9 @@ const char kPpapiFlashPath[] = "ppapi-flash-path"; // Ppapi Flash version. const char kPpapiFlashVersion[] = "ppapi-flash-version"; -// Path to client certificate. -const char kClientCertificate[] = "client-certificate"; - // Disable HTTP cache. const char kDisableHttpCache[] = "disable-http-cache"; -// Register schemes to standard. -const char kRegisterStandardSchemes[] = "register-standard-schemes"; - // Register schemes to handle service worker. const char kRegisterServiceWorkerSchemes[] = "register-service-worker-schemes"; @@ -142,12 +139,14 @@ const char kCipherSuiteBlacklist[] = "cipher-suite-blacklist"; 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 kOpenerID[] = "opener-id"; +const char kBackgroundColor[] = "background-color"; +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 kOpenerID[] = "opener-id"; +const char kScrollBounce[] = "scroll-bounce"; // Widevine options // Path to Widevine CDM binaries. diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index 3c198555a5c3..fadde79f18c8 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -56,6 +56,7 @@ extern const char kGuestInstanceID[]; extern const char kExperimentalFeatures[]; extern const char kExperimentalCanvasFeatures[]; extern const char kOpenerID[]; +extern const char kScrollBounce[]; extern const char kBlinkFeatures[]; } // namespace options @@ -68,20 +69,20 @@ 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 kRegisterServiceWorkerSchemes[]; extern const char kSSLVersionFallbackMin[]; extern const char kCipherSuiteBlacklist[]; extern const char kAppUserModelId[]; +extern const char kBackgroundColor[]; extern const char kZoomFactor[]; extern const char kPreloadScript[]; extern const char kPreloadURL[]; extern const char kNodeIntegration[]; extern const char kGuestInstanceID[]; extern const char kOpenerID[]; +extern const char kScrollBounce[]; extern const char kWidevineCdmPath[]; extern const char kWidevineCdmVersion[]; diff --git a/atom/common/platform_util_linux.cc b/atom/common/platform_util_linux.cc index 1e437b866cc0..9811c8760d06 100644 --- a/atom/common/platform_util_linux.cc +++ b/atom/common/platform_util_linux.cc @@ -13,7 +13,9 @@ namespace { -bool XDGUtil(const std::string& util, const std::string& arg) { +bool XDGUtil(const std::string& util, + const std::string& arg, + const bool wait_for_exit) { std::vector argv; argv.push_back(util); argv.push_back(arg); @@ -30,6 +32,11 @@ bool XDGUtil(const std::string& util, const std::string& arg) { if (!process.IsValid()) return false; + if (!wait_for_exit) { + base::EnsureProcessGetsReaped(process.Pid()); + return true; + } + int exit_code = -1; if (!process.WaitForExit(&exit_code)) return false; @@ -37,12 +44,12 @@ bool XDGUtil(const std::string& util, const std::string& arg) { return (exit_code == 0); } -bool XDGOpen(const std::string& path) { - return XDGUtil("xdg-open", path); +bool XDGOpen(const std::string& path, const bool wait_for_exit) { + return XDGUtil("xdg-open", path, wait_for_exit); } -bool XDGEmail(const std::string& email) { - return XDGUtil("xdg-email", email); +bool XDGEmail(const std::string& email, const bool wait_for_exit) { + return XDGUtil("xdg-email", email, wait_for_exit); } } // namespace @@ -57,22 +64,24 @@ void ShowItemInFolder(const base::FilePath& full_path) { if (!base::DirectoryExists(dir)) return; - XDGOpen(dir.value()); + XDGOpen(dir.value(), true); } void OpenItem(const base::FilePath& full_path) { - XDGOpen(full_path.value()); + XDGOpen(full_path.value(), true); } bool OpenExternal(const GURL& url, bool activate) { + // Don't wait for exit, since we don't want to wait for the browser/email + // client window to close before returning if (url.SchemeIs("mailto")) - return XDGEmail(url.spec()); + return XDGEmail(url.spec(), false); else - return XDGOpen(url.spec()); + return XDGOpen(url.spec(), false); } bool MoveItemToTrash(const base::FilePath& full_path) { - return XDGUtil("gvfs-trash", full_path.value()); + return XDGUtil("gvfs-trash", full_path.value(), true); } void Beep() { diff --git a/atom/node/osfhandle.cc b/atom/node/osfhandle.cc new file mode 100644 index 000000000000..33403dc52371 --- /dev/null +++ b/atom/node/osfhandle.cc @@ -0,0 +1,19 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "osfhandle.h" + +#include + +namespace node { + +int open_osfhandle(intptr_t osfhandle, int flags) { + return _open_osfhandle(osfhandle, flags); +} + +int close(int fd) { + return _close(fd); +} + +} // namespace node diff --git a/atom/node/osfhandle.h b/atom/node/osfhandle.h new file mode 100644 index 000000000000..6ebb2ab11e9d --- /dev/null +++ b/atom/node/osfhandle.h @@ -0,0 +1,28 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_NODE_OSFHANDLE_H_ +#define ATOM_NODE_OSFHANDLE_H_ + +#include + +#include "node_extern.h" + +namespace node { + +// The _open_osfhandle and _close functions on Windows are provided by the +// Visual C++ library, so the fd returned by them can only be used in the +// same instance of VC++ library. +// However Electron is linking with VC++ library statically, so electron.exe +// shares a different instance of VC++ library with node.exe. This results +// in fd created in electron.exe not usable in node.dll, so we have to ensure +// we always create fd in one instance of VC++ library. +// Followings wrappers are compiled in node.dll, and all code in electron.exe +// should call these wrappers instead of calling _open_osfhandle directly. +NODE_EXTERN int open_osfhandle(intptr_t osfhandle, int flags); +NODE_EXTERN int close(int fd); + +} // namespace node + +#endif // ATOM_NODE_OSFHANDLE_H_ diff --git a/atom/renderer/api/atom_api_spell_check_client.cc b/atom/renderer/api/atom_api_spell_check_client.cc index 25d1e30b0aac..08f36efce581 100644 --- a/atom/renderer/api/atom_api_spell_check_client.cc +++ b/atom/renderer/api/atom_api_spell_check_client.cc @@ -21,13 +21,11 @@ namespace api { namespace { -const int kMaxAutoCorrectWordSize = 8; - bool HasWordCharacters(const base::string16& text, int index) { const base::char16* data = text.data(); int length = text.length(); while (index < length) { - uint32 code = 0; + uint32_t code = 0; U16_NEXT(data, index, length, code); UErrorCode error = U_ZERO_ERROR; if (uscript_getScript(code, &error) != USCRIPT_COMMON) @@ -42,8 +40,7 @@ SpellCheckClient::SpellCheckClient(const std::string& language, bool auto_spell_correct_turned_on, v8::Isolate* isolate, v8::Local provider) - : auto_spell_correct_turned_on_(auto_spell_correct_turned_on), - isolate_(isolate), + : isolate_(isolate), provider_(isolate, provider) { character_attributes_.SetDefaultLanguage(language); @@ -96,14 +93,6 @@ void SpellCheckClient::requestCheckingOfText( completionCallback->didFinishCheckingText(results); } -blink::WebString SpellCheckClient::autoCorrectWord( - const blink::WebString& misspelledWord) { - if (auto_spell_correct_turned_on_) - return GetAutoCorrectionWord(base::string16(misspelledWord)); - else - return blink::WebString(); -} - void SpellCheckClient::showSpellingUI(bool show) { } @@ -170,53 +159,6 @@ bool SpellCheckClient::SpellCheckWord(const base::string16& word_to_check) { return true; } -base::string16 SpellCheckClient::GetAutoCorrectionWord( - const base::string16& word) { - base::string16 autocorrect_word; - - int word_length = static_cast(word.size()); - if (word_length < 2 || word_length > kMaxAutoCorrectWordSize) - return autocorrect_word; - - base::char16 misspelled_word[kMaxAutoCorrectWordSize + 1]; - const base::char16* word_char = word.c_str(); - for (int i = 0; i <= kMaxAutoCorrectWordSize; ++i) { - if (i >= word_length) - misspelled_word[i] = 0; - else - misspelled_word[i] = word_char[i]; - } - - // Swap adjacent characters and spellcheck. - int misspelling_start, misspelling_len; - for (int i = 0; i < word_length - 1; i++) { - // Swap. - std::swap(misspelled_word[i], misspelled_word[i + 1]); - - // Check spelling. - misspelling_start = misspelling_len = 0; - spellCheck(blink::WebString(misspelled_word, word_length), - misspelling_start, - misspelling_len, - NULL); - - // Make decision: if only one swap produced a valid word, then we want to - // return it. If we found two or more, we don't do autocorrection. - if (misspelling_len == 0) { - if (autocorrect_word.empty()) { - autocorrect_word.assign(misspelled_word); - } else { - autocorrect_word.clear(); - break; - } - } - - // Restore the swapped characters. - std::swap(misspelled_word[i], misspelled_word[i + 1]); - } - return autocorrect_word; -} - // Returns whether or not the given string is a valid contraction. // This function is a fall-back when the SpellcheckWordIterator class // returns a concatenated word which is not in the selected dictionary diff --git a/atom/renderer/api/atom_api_spell_check_client.h b/atom/renderer/api/atom_api_spell_check_client.h index 345cad186ace..af72756e2ec1 100644 --- a/atom/renderer/api/atom_api_spell_check_client.h +++ b/atom/renderer/api/atom_api_spell_check_client.h @@ -41,8 +41,6 @@ class SpellCheckClient : public blink::WebSpellCheckClient { const blink::WebVector& markersInText, const blink::WebVector& markerOffsets, blink::WebTextCheckingCompletion* completionCallback) override; - blink::WebString autoCorrectWord( - const blink::WebString& misspelledWord) override; void showSpellingUI(bool show) override; bool isShowingSpellingUI() override; void updateSpellingUIWithMisspelledWord( diff --git a/atom/renderer/api/atom_api_web_frame.cc b/atom/renderer/api/atom_api_web_frame.cc index c72882886b91..0b291d3b552a 100644 --- a/atom/renderer/api/atom_api_web_frame.cc +++ b/atom/renderer/api/atom_api_web_frame.cc @@ -5,17 +5,20 @@ #include "atom/renderer/api/atom_api_web_frame.h" #include "atom/common/api/event_emitter_caller.h" +#include "atom/common/native_mate_converters/blink_converter.h" #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/string16_converter.h" #include "atom/renderer/api/atom_api_spell_check_client.h" +#include "base/memory/memory_pressure_listener.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/WebCache.h" #include "third_party/WebKit/public/web/WebDocument.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" -#include "third_party/WebKit/public/web/WebScopedUserGesture.h" +#include "third_party/WebKit/public/web/WebScriptExecutionCallback.h" #include "third_party/WebKit/public/web/WebScriptSource.h" #include "third_party/WebKit/public/web/WebSecurityPolicy.h" #include "third_party/WebKit/public/web/WebView.h" @@ -26,8 +29,37 @@ namespace atom { namespace api { -WebFrame::WebFrame() +namespace { + +class ScriptExecutionCallback : public blink::WebScriptExecutionCallback { + public: + using CompletionCallback = + base::Callback& result)>; + + explicit ScriptExecutionCallback(const CompletionCallback& callback) + : callback_(callback) {} + ~ScriptExecutionCallback() {} + + void completed( + const blink::WebVector>& result) override { + if (!callback_.is_null() && !result.isEmpty() && !result[0].IsEmpty()) + // Right now only single results per frame is supported. + callback_.Run(result[0]); + delete this; + } + + private: + CompletionCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(ScriptExecutionCallback); +}; + +} // namespace + +WebFrame::WebFrame(v8::Isolate* isolate) : web_frame_(blink::WebLocalFrame::frameForCurrentContext()) { + Init(isolate); } WebFrame::~WebFrame() { @@ -39,7 +71,7 @@ void WebFrame::SetName(const std::string& name) { double WebFrame::SetZoomLevel(double level) { double ret = web_frame_->view()->setZoomLevel(level); - mate::EmitEvent(isolate(), GetWrapper(isolate()), "zoom-level-changed", ret); + mate::EmitEvent(isolate(), GetWrapper(), "zoom-level-changed", ret); return ret; } @@ -124,14 +156,39 @@ void WebFrame::ExecuteJavaScript(const base::string16& code, mate::Arguments* args) { bool has_user_gesture = false; args->GetNext(&has_user_gesture); - scoped_ptr gesture( - has_user_gesture ? new blink::WebScopedUserGesture : nullptr); - web_frame_->executeScriptAndReturnValue(blink::WebScriptSource(code)); + ScriptExecutionCallback::CompletionCallback completion_callback; + args->GetNext(&completion_callback); + std::unique_ptr callback( + new ScriptExecutionCallback(completion_callback)); + web_frame_->requestExecuteScriptAndReturnValue( + blink::WebScriptSource(code), + has_user_gesture, + callback.release()); } -mate::ObjectTemplateBuilder WebFrame::GetObjectTemplateBuilder( +// static +mate::Handle WebFrame::Create(v8::Isolate* isolate) { + return mate::CreateHandle(isolate, new WebFrame(isolate)); +} + +blink::WebCache::ResourceTypeStats WebFrame::GetResourceUsage( v8::Isolate* isolate) { - return mate::ObjectTemplateBuilder(isolate) + blink::WebCache::ResourceTypeStats stats; + blink::WebCache::getResourceTypeStats(&stats); + return stats; +} + +void WebFrame::ClearCache(v8::Isolate* isolate) { + isolate->IdleNotificationDeadline(0.5); + blink::WebCache::clear(); + base::MemoryPressureListener::NotifyMemoryPressure( + base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL); +} + +// static +void WebFrame::BuildPrototype( + v8::Isolate* isolate, v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) .SetMethod("setName", &WebFrame::SetName) .SetMethod("setZoomLevel", &WebFrame::SetZoomLevel) .SetMethod("getZoomLevel", &WebFrame::GetZoomLevel) @@ -151,12 +208,9 @@ mate::ObjectTemplateBuilder WebFrame::GetObjectTemplateBuilder( .SetMethod("registerURLSchemeAsPrivileged", &WebFrame::RegisterURLSchemeAsPrivileged) .SetMethod("insertText", &WebFrame::InsertText) - .SetMethod("executeJavaScript", &WebFrame::ExecuteJavaScript); -} - -// static -mate::Handle WebFrame::Create(v8::Isolate* isolate) { - return CreateHandle(isolate, new WebFrame); + .SetMethod("executeJavaScript", &WebFrame::ExecuteJavaScript) + .SetMethod("getResourceUsage", &WebFrame::GetResourceUsage) + .SetMethod("clearCache", &WebFrame::ClearCache); } } // namespace api diff --git a/atom/renderer/api/atom_api_web_frame.h b/atom/renderer/api/atom_api_web_frame.h index d55b24fd25ea..df0392a933e0 100644 --- a/atom/renderer/api/atom_api_web_frame.h +++ b/atom/renderer/api/atom_api_web_frame.h @@ -11,6 +11,7 @@ #include "base/memory/scoped_ptr.h" #include "native_mate/handle.h" #include "native_mate/wrappable.h" +#include "third_party/WebKit/public/web/WebCache.h" namespace blink { class WebLocalFrame; @@ -26,13 +27,16 @@ namespace api { class SpellCheckClient; -class WebFrame : public mate::Wrappable { +class WebFrame : public mate::Wrappable { public: static mate::Handle Create(v8::Isolate* isolate); + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + private: - WebFrame(); - virtual ~WebFrame(); + explicit WebFrame(v8::Isolate* isolate); + ~WebFrame() override; void SetName(const std::string& name); @@ -66,11 +70,11 @@ class WebFrame : public mate::Wrappable { // Excecuting scripts. void ExecuteJavaScript(const base::string16& code, mate::Arguments* args); - // mate::Wrappable: - virtual mate::ObjectTemplateBuilder GetObjectTemplateBuilder( - v8::Isolate* isolate); + // Resource related methods + blink::WebCache::ResourceTypeStats GetResourceUsage(v8::Isolate* isolate); + void ClearCache(v8::Isolate* isolate); - scoped_ptr spell_check_client_; + std::unique_ptr spell_check_client_; blink::WebLocalFrame* web_frame_; diff --git a/atom/renderer/api/lib/desktop-capturer.js b/atom/renderer/api/lib/desktop-capturer.js deleted file mode 100644 index 8912deea3a8f..000000000000 --- a/atom/renderer/api/lib/desktop-capturer.js +++ /dev/null @@ -1,47 +0,0 @@ -const ipcRenderer = require('electron').ipcRenderer; -const nativeImage = require('electron').nativeImage; - -var nextId = 0; -var includes = [].includes; - -var getNextId = function() { - return ++nextId; -}; - -// |options.type| can not be empty and has to include 'window' or 'screen'. -var isValid = function(options) { - return ((options != null ? options.types : void 0) != null) && Array.isArray(options.types); -}; - -exports.getSources = function(options, callback) { - var captureScreen, captureWindow, id; - if (!isValid(options)) { - return callback(new Error('Invalid options')); - } - captureWindow = includes.call(options.types, 'window'); - captureScreen = includes.call(options.types, 'screen'); - if (options.thumbnailSize == null) { - options.thumbnailSize = { - width: 150, - height: 150 - }; - } - id = getNextId(); - ipcRenderer.send('ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, options.thumbnailSize, id); - return ipcRenderer.once("ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_" + id, function(event, sources) { - var source; - return callback(null, (function() { - var i, len, results; - results = []; - for (i = 0, len = sources.length; i < len; i++) { - source = sources[i]; - results.push({ - id: source.id, - name: source.name, - thumbnail: nativeImage.createFromDataURL(source.thumbnail) - }); - } - return results; - })()); - }); -}; diff --git a/atom/renderer/api/lib/exports/electron.js b/atom/renderer/api/lib/exports/electron.js deleted file mode 100644 index 3f0d3254cb8b..000000000000 --- a/atom/renderer/api/lib/exports/electron.js +++ /dev/null @@ -1,38 +0,0 @@ -const common = require('../../../../common/api/lib/exports/electron'); - -// Import common modules. -common.defineProperties(exports); - -Object.defineProperties(exports, { - // Renderer side modules, please sort with alphabet order. - desktopCapturer: { - enumerable: true, - get: function() { - return require('../desktop-capturer'); - } - }, - ipcRenderer: { - enumerable: true, - get: function() { - return require('../ipc-renderer'); - } - }, - remote: { - enumerable: true, - get: function() { - return require('../remote'); - } - }, - screen: { - enumerable: true, - get: function() { - return require('../screen'); - } - }, - webFrame: { - enumerable: true, - get: function() { - return require('../web-frame'); - } - } -}); diff --git a/atom/renderer/api/lib/ipc-renderer.js b/atom/renderer/api/lib/ipc-renderer.js deleted file mode 100644 index dd614103cfaf..000000000000 --- a/atom/renderer/api/lib/ipc-renderer.js +++ /dev/null @@ -1,27 +0,0 @@ -const binding = process.atomBinding('ipc'); -const v8Util = process.atomBinding('v8_util'); - -var slice = [].slice; - -// Created by init.coffee. -const ipcRenderer = v8Util.getHiddenValue(global, 'ipc'); - -ipcRenderer.send = function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return binding.send('ipc-message', slice.call(args)); -}; - -ipcRenderer.sendSync = function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return JSON.parse(binding.sendSync('ipc-message-sync', slice.call(args))); -}; - -ipcRenderer.sendToHost = function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return binding.send('ipc-message-host', slice.call(args)); -}; - -module.exports = ipcRenderer; diff --git a/atom/renderer/api/lib/ipc.js b/atom/renderer/api/lib/ipc.js deleted file mode 100644 index a4bab33af815..000000000000 --- a/atom/renderer/api/lib/ipc.js +++ /dev/null @@ -1,31 +0,0 @@ -const ipcRenderer = require('electron').ipcRenderer; -const deprecate = require('electron').deprecate; -const EventEmitter = require('events').EventEmitter; - -var slice = [].slice; - -// This module is deprecated, we mirror everything from ipcRenderer. -deprecate.warn('ipc module', 'require("electron").ipcRenderer'); - -// Routes events of ipcRenderer. -var ipc = new EventEmitter; - -ipcRenderer.emit = function() { - var channel = arguments[0]; - var args = 3 <= arguments.length ? slice.call(arguments, 2) : []; - ipc.emit.apply(ipc, [channel].concat(slice.call(args))); - return EventEmitter.prototype.emit.apply(ipcRenderer, arguments); -}; - -// Deprecated. -for (var method in ipcRenderer) { - if (method.startsWith('send')) { - ipc[method] = ipcRenderer[method]; - } -} - -deprecate.rename(ipc, 'sendChannel', 'send'); - -deprecate.rename(ipc, 'sendChannelSync', 'sendSync'); - -module.exports = ipc; diff --git a/atom/renderer/api/lib/remote.js b/atom/renderer/api/lib/remote.js deleted file mode 100644 index a9798338faa6..000000000000 --- a/atom/renderer/api/lib/remote.js +++ /dev/null @@ -1,304 +0,0 @@ -'use strict'; - -const ipcRenderer = require('electron').ipcRenderer; -const CallbacksRegistry = require('electron').CallbacksRegistry; -const v8Util = process.atomBinding('v8_util'); -const IDWeakMap = process.atomBinding('id_weak_map').IDWeakMap; - -const callbacksRegistry = new CallbacksRegistry; - -var includes = [].includes; - -var remoteObjectCache = new IDWeakMap; - -// Check for circular reference. -var isCircular = function(field, visited) { - if (typeof field === 'object') { - if (includes.call(visited, field)) { - return true; - } - visited.push(field); - } - return false; -}; - -// Convert the arguments object into an array of meta data. -var wrapArgs = function(args, visited) { - var valueToMeta; - if (visited == null) { - visited = []; - } - valueToMeta = function(value) { - var field, prop, ret; - if (Array.isArray(value)) { - return { - type: 'array', - value: wrapArgs(value, visited) - }; - } else if (Buffer.isBuffer(value)) { - return { - type: 'buffer', - value: Array.prototype.slice.call(value, 0) - }; - } else if (value instanceof Date) { - return { - type: 'date', - value: value.getTime() - }; - } else if ((value != null ? value.constructor.name : void 0) === 'Promise') { - return { - type: 'promise', - then: valueToMeta(value.then.bind(value)) - }; - } else if ((value != null) && typeof value === 'object' && v8Util.getHiddenValue(value, 'atomId')) { - return { - type: 'remote-object', - id: v8Util.getHiddenValue(value, 'atomId') - }; - } else if ((value != null) && typeof value === 'object') { - ret = { - type: 'object', - name: value.constructor.name, - members: [] - }; - for (prop in value) { - field = value[prop]; - ret.members.push({ - name: prop, - value: valueToMeta(isCircular(field, visited) ? null : field) - }); - } - return ret; - } else if (typeof value === 'function' && v8Util.getHiddenValue(value, 'returnValue')) { - return { - type: 'function-with-return-value', - value: valueToMeta(value()) - }; - } else if (typeof value === 'function') { - return { - type: 'function', - id: callbacksRegistry.add(value), - location: v8Util.getHiddenValue(value, 'location') - }; - } else { - return { - type: 'value', - value: value - }; - } - }; - return Array.prototype.slice.call(args).map(valueToMeta); -}; - -// Populate object's members from descriptors. -// This matches |getObjectMemebers| in rpc-server. -let setObjectMembers = function(object, metaId, members) { - for (let member of members) { - if (object.hasOwnProperty(member.name)) - continue; - - let descriptor = { enumerable: member.enumerable }; - if (member.type === 'method') { - let remoteMemberFunction = function() { - if (this && this.constructor === remoteMemberFunction) { - // Constructor call. - let ret = ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_CONSTRUCTOR', metaId, member.name, wrapArgs(arguments)); - return metaToValue(ret); - } else { - // Call member function. - let ret = ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_CALL', metaId, member.name, wrapArgs(arguments)); - return metaToValue(ret); - } - }; - descriptor.value = remoteMemberFunction; - } else if (member.type === 'get') { - descriptor.get = function() { - return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_GET', metaId, member.name)); - }; - - // Only set setter when it is writable. - if (member.writable) { - descriptor.set = function(value) { - ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_SET', metaId, member.name, value); - return value; - }; - } - } - - Object.defineProperty(object, member.name, descriptor); - } -}; - -// Populate object's prototype from descriptor. -// This matches |getObjectPrototype| in rpc-server. -let setObjectPrototype = function(object, metaId, descriptor) { - if (descriptor === null) - return; - let proto = {}; - setObjectMembers(proto, metaId, descriptor.members); - setObjectPrototype(proto, metaId, descriptor.proto); - Object.setPrototypeOf(object, proto); -}; - -// Convert meta data from browser into real value. -let metaToValue = function(meta) { - var el, i, len, ref1, results, ret; - switch (meta.type) { - case 'value': - return meta.value; - case 'array': - ref1 = meta.members; - results = []; - for (i = 0, len = ref1.length; i < len; i++) { - el = ref1[i]; - results.push(metaToValue(el)); - } - return results; - case 'buffer': - return new Buffer(meta.value); - case 'promise': - return Promise.resolve({ - then: metaToValue(meta.then) - }); - case 'error': - return metaToPlainObject(meta); - case 'date': - return new Date(meta.value); - case 'exception': - throw new Error(meta.message + "\n" + meta.stack); - default: - if (remoteObjectCache.has(meta.id)) - return remoteObjectCache.get(meta.id); - - if (meta.type === 'function') { - // A shadow class to represent the remote function object. - let remoteFunction = function() { - if (this && this.constructor === remoteFunction) { - // Constructor call. - let obj = ipcRenderer.sendSync('ATOM_BROWSER_CONSTRUCTOR', meta.id, wrapArgs(arguments)); - // Returning object in constructor will replace constructed object - // with the returned object. - // http://stackoverflow.com/questions/1978049/what-values-can-a-constructor-return-to-avoid-returning-this - return metaToValue(obj); - } else { - // Function call. - let obj = ipcRenderer.sendSync('ATOM_BROWSER_FUNCTION_CALL', meta.id, wrapArgs(arguments)); - return metaToValue(obj); - } - }; - ret = remoteFunction; - } else { - ret = {}; - } - - // Populate delegate members. - setObjectMembers(ret, meta.id, meta.members); - // Populate delegate prototype. - setObjectPrototype(ret, meta.id, meta.proto); - - // Set constructor.name to object's name. - Object.defineProperty(ret.constructor, 'name', { value: meta.name }); - - // Track delegate object's life time, and tell the browser to clean up - // when the object is GCed. - v8Util.setDestructor(ret, function() { - ipcRenderer.send('ATOM_BROWSER_DEREFERENCE', meta.id); - }); - - // Remember object's id. - v8Util.setHiddenValue(ret, 'atomId', meta.id); - remoteObjectCache.set(meta.id, ret); - return ret; - } -}; - -// Construct a plain object from the meta. -var metaToPlainObject = function(meta) { - var i, len, name, obj, ref1, ref2, value; - obj = (function() { - switch (meta.type) { - case 'error': - return new Error; - default: - return {}; - } - })(); - ref1 = meta.members; - for (i = 0, len = ref1.length; i < len; i++) { - ref2 = ref1[i], name = ref2.name, value = ref2.value; - obj[name] = value; - } - return obj; -}; - -// Browser calls a callback in renderer. -ipcRenderer.on('ATOM_RENDERER_CALLBACK', function(event, id, args) { - return callbacksRegistry.apply(id, metaToValue(args)); -}); - -// A callback in browser is released. -ipcRenderer.on('ATOM_RENDERER_RELEASE_CALLBACK', function(event, id) { - return callbacksRegistry.remove(id); -}); - -// List all built-in modules in browser process. -const browserModules = require('../../../browser/api/lib/exports/electron'); - -// And add a helper receiver for each one. -var fn = function(name) { - return Object.defineProperty(exports, name, { - get: function() { - return exports.getBuiltin(name); - } - }); -}; -for (var name in browserModules) { - fn(name); -} - -// Get remote module. -exports.require = function(module) { - return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_REQUIRE', module)); -}; - -// Alias to remote.require('electron').xxx. -exports.getBuiltin = function(module) { - return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_GET_BUILTIN', module)); -}; - -// Get current BrowserWindow. -exports.getCurrentWindow = function() { - return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_CURRENT_WINDOW')); -}; - -// Get current WebContents object. -exports.getCurrentWebContents = function() { - return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_CURRENT_WEB_CONTENTS')); -}; - -// Get a global object in browser. -exports.getGlobal = function(name) { - return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_GLOBAL', name)); -}; - -// Get the process object in browser. -exports.__defineGetter__('process', function() { - return exports.getGlobal('process'); -}); - -// Create a funtion that will return the specifed value when called in browser. -exports.createFunctionWithReturnValue = function(returnValue) { - var func; - func = function() { - return returnValue; - }; - v8Util.setHiddenValue(func, 'returnValue', true); - return func; -}; - -// Get the guest WebContents from guestInstanceId. -exports.getGuestWebContents = function(guestInstanceId) { - var meta; - meta = ipcRenderer.sendSync('ATOM_BROWSER_GUEST_WEB_CONTENTS', guestInstanceId); - return metaToValue(meta); -}; diff --git a/atom/renderer/api/lib/screen.js b/atom/renderer/api/lib/screen.js deleted file mode 100644 index fb74d78f79d2..000000000000 --- a/atom/renderer/api/lib/screen.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('electron').remote.screen; diff --git a/atom/renderer/api/lib/web-frame.js b/atom/renderer/api/lib/web-frame.js deleted file mode 100644 index 8ae35d7b8ac6..000000000000 --- a/atom/renderer/api/lib/web-frame.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -const deprecate = require('electron').deprecate; -const EventEmitter = require('events').EventEmitter; - -const webFrame = process.atomBinding('web_frame').webFrame; - -// webFrame is an EventEmitter. -webFrame.__proto__ = EventEmitter.prototype; - -// Lots of webview would subscribe to webFrame's events. -webFrame.setMaxListeners(0); - -// Deprecated. -deprecate.rename(webFrame, 'registerUrlSchemeAsSecure', 'registerURLSchemeAsSecure'); -deprecate.rename(webFrame, 'registerUrlSchemeAsBypassingCSP', 'registerURLSchemeAsBypassingCSP'); -deprecate.rename(webFrame, 'registerUrlSchemeAsPrivileged', 'registerURLSchemeAsPrivileged'); - -module.exports = webFrame; diff --git a/atom/renderer/atom_render_view_observer.cc b/atom/renderer/atom_render_view_observer.cc index cdbdb3d7c3ce..7ee93efb39b7 100644 --- a/atom/renderer/atom_render_view_observer.cc +++ b/atom/renderer/atom_render_view_observer.cc @@ -39,7 +39,11 @@ bool GetIPCObject(v8::Isolate* isolate, v8::Local context, v8::Local* ipc) { v8::Local key = mate::StringToV8(isolate, "ipc"); - v8::Local value = context->Global()->GetHiddenValue(key); + v8::Local privateKey = v8::Private::ForApi(isolate, key); + v8::Local global_object = context->Global(); + v8::Local value; + if (!global_object->GetPrivate(context, privateKey).ToLocal(&value)) + return false; if (value.IsEmpty() || !value->IsObject()) return false; *ipc = value->ToObject(); @@ -55,6 +59,34 @@ std::vector> ListValueToVector( return result; } +void EmitIPCEvent(blink::WebFrame* frame, + const base::string16& channel, + const base::ListValue& args) { + if (!frame || frame->isWebRemoteFrame()) + return; + + v8::Isolate* isolate = blink::mainThreadIsolate(); + v8::HandleScope handle_scope(isolate); + + v8::Local context = frame->mainWorldScriptContext(); + v8::Context::Scope context_scope(context); + + // Only emit IPC event for context with node integration. + node::Environment* env = node::Environment::GetCurrent(context); + if (!env) + return; + + v8::Local ipc; + if (GetIPCObject(isolate, context, &ipc)) { + 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); + } +} + base::StringPiece NetResourceProvider(int key) { if (key == IDR_DIR_HEADER_HTML) { base::StringPiece html_data = @@ -119,7 +151,8 @@ bool AtomRenderViewObserver::OnMessageReceived(const IPC::Message& message) { return handled; } -void AtomRenderViewObserver::OnBrowserMessage(const base::string16& channel, +void AtomRenderViewObserver::OnBrowserMessage(bool send_to_all, + const base::string16& channel, const base::ListValue& args) { if (!document_created_) return; @@ -131,20 +164,13 @@ void AtomRenderViewObserver::OnBrowserMessage(const base::string16& channel, if (!frame || frame->isWebRemoteFrame()) return; - v8::Isolate* isolate = blink::mainThreadIsolate(); - v8::HandleScope handle_scope(isolate); + EmitIPCEvent(frame, channel, args); - v8::Local context = frame->mainWorldScriptContext(); - v8::Context::Scope context_scope(context); - - v8::Local ipc; - if (GetIPCObject(isolate, context, &ipc)) { - 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); + // Also send the message to all sub-frames. + if (send_to_all) { + for (blink::WebFrame* child = frame->firstChild(); child; + child = child->nextSibling()) + EmitIPCEvent(child, channel, args); } } diff --git a/atom/renderer/atom_render_view_observer.h b/atom/renderer/atom_render_view_observer.h index 4b9d59f3fa08..376138f0849a 100644 --- a/atom/renderer/atom_render_view_observer.h +++ b/atom/renderer/atom_render_view_observer.h @@ -30,7 +30,8 @@ class AtomRenderViewObserver : public content::RenderViewObserver { void DraggableRegionsChanged(blink::WebFrame* frame) override; bool OnMessageReceived(const IPC::Message& message) override; - void OnBrowserMessage(const base::string16& channel, + void OnBrowserMessage(bool send_to_all, + const base::string16& channel, const base::ListValue& args); // Weak reference to renderer client. diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index a996bd0efb25..ee2dd7d54e04 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -10,13 +10,17 @@ #include "atom/common/api/api_messages.h" #include "atom/common/api/atom_bindings.h" #include "atom/common/api/event_emitter_caller.h" +#include "atom/common/color_util.h" +#include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/node_bindings.h" #include "atom/common/node_includes.h" #include "atom/common/options_switches.h" #include "atom/renderer/atom_render_view_observer.h" #include "atom/renderer/guest_view_container.h" #include "atom/renderer/node_array_buffer_bridge.h" +#include "atom/renderer/preferences_manager.h" #include "base/command_line.h" +#include "base/strings/utf_string_conversions.h" #include "chrome/renderer/media/chrome_key_systems.h" #include "chrome/renderer/pepper/pepper_helper.h" #include "chrome/renderer/printing/print_web_view_helper.h" @@ -25,15 +29,26 @@ #include "content/public/renderer/render_frame.h" #include "content/public/renderer/render_frame_observer.h" #include "content/public/renderer/render_thread.h" +#include "content/public/renderer/render_view.h" #include "ipc/ipc_message_macros.h" +#include "native_mate/dictionary.h" +#include "net/base/net_errors.h" #include "third_party/WebKit/public/web/WebCustomElement.h" +#include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/public/web/WebFrameWidget.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" #include "third_party/WebKit/public/web/WebPluginParams.h" #include "third_party/WebKit/public/web/WebKit.h" +#include "third_party/WebKit/public/web/WebScriptSource.h" #include "third_party/WebKit/public/web/WebSecurityPolicy.h" #include "third_party/WebKit/public/web/WebRuntimeFeatures.h" #include "third_party/WebKit/public/web/WebView.h" +#if defined(OS_MACOSX) +#include "base/mac/mac_util.h" +#include "base/strings/sys_string_conversions.h" +#endif + #if defined(OS_WIN) #include #endif @@ -48,32 +63,60 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { AtomRenderFrameObserver(content::RenderFrame* frame, AtomRendererClient* renderer_client) : content::RenderFrameObserver(frame), + render_frame_(frame), world_id_(-1), renderer_client_(renderer_client) {} // content::RenderFrameObserver: + void DidClearWindowObject() override { + renderer_client_->DidClearWindowObject(render_frame_); + } + void DidCreateScriptContext(v8::Handle context, int extension_group, int world_id) override { if (world_id_ != -1 && world_id_ != world_id) return; world_id_ = world_id; - renderer_client_->DidCreateScriptContext(context); + renderer_client_->DidCreateScriptContext(context, render_frame_); } void WillReleaseScriptContext(v8::Local context, int world_id) override { if (world_id_ != world_id) return; - renderer_client_->WillReleaseScriptContext(context); + renderer_client_->WillReleaseScriptContext(context, render_frame_); } private: + content::RenderFrame* render_frame_; int world_id_; AtomRendererClient* renderer_client_; DISALLOW_COPY_AND_ASSIGN(AtomRenderFrameObserver); }; +v8::Local GetRenderProcessPreferences( + const PreferencesManager* preferences_manager, v8::Isolate* isolate) { + if (preferences_manager->preferences()) + return mate::ConvertToV8(isolate, *preferences_manager->preferences()); + else + return v8::Null(isolate); +} + +void AddRenderBindings(v8::Isolate* isolate, + v8::Local process, + const PreferencesManager* preferences_manager) { + mate::Dictionary dict(isolate, process); + dict.SetMethod( + "getRenderProcessPreferences", + base::Bind(GetRenderProcessPreferences, preferences_manager)); +} + +bool IsDevToolsExtension(content::RenderFrame* render_frame) { + return static_cast(render_frame->GetWebFrame()->document().url()) + .SchemeIs("chrome-extension"); +} + } // namespace AtomRendererClient::AtomRendererClient() @@ -84,29 +127,16 @@ AtomRendererClient::AtomRendererClient() AtomRendererClient::~AtomRendererClient() { } -void AtomRendererClient::WebKitInitialized() { +void AtomRendererClient::RenderThreadStarted() { blink::WebCustomElement::addEmbedderCustomElementName("webview"); blink::WebCustomElement::addEmbedderCustomElementName("browserplugin"); OverrideNodeArrayBuffer(); - node_bindings_->Initialize(); - node_bindings_->PrepareMessageLoop(); - - DCHECK(!global_env); - - // Create a default empty environment which would be used when we need to - // run V8 code out of a window context (like running a uv callback). - v8::Isolate* isolate = blink::mainThreadIsolate(); - v8::HandleScope handle_scope(isolate); - v8::Local context = v8::Context::New(isolate); - global_env = node::Environment::New(context, uv_default_loop()); -} - -void AtomRendererClient::RenderThreadStarted() { - content::RenderThread::Get()->AddObserver(this); + preferences_manager_.reset(new PreferencesManager); #if defined(OS_WIN) + // Set ApplicationUserModelID in renderer process. base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); base::string16 app_id = command_line->GetSwitchValueNative(switches::kAppUserModelId); @@ -114,25 +144,75 @@ void AtomRendererClient::RenderThreadStarted() { SetCurrentProcessExplicitAppUserModelID(app_id.c_str()); } #endif + +#if defined(OS_MACOSX) + // Disable rubber banding by default. + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + if (!command_line->HasSwitch(switches::kScrollBounce)) { + base::ScopedCFTypeRef key( + base::SysUTF8ToCFStringRef("NSScrollViewRubberbanding")); + base::ScopedCFTypeRef value( + base::SysUTF8ToCFStringRef("false")); + CFPreferencesSetAppValue(key, value, kCFPreferencesCurrentApplication); + CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication); + } +#endif } void AtomRendererClient::RenderFrameCreated( content::RenderFrame* render_frame) { new PepperHelper(render_frame); + new AtomRenderFrameObserver(render_frame, this); // Allow file scheme to handle service worker by default. + // FIXME(zcbenz): Can this be moved elsewhere? blink::WebSecurityPolicy::registerURLSchemeAsAllowingServiceWorkers("file"); - - // Only insert node integration for the main frame. - if (!render_frame->IsMainFrame()) - return; - - new AtomRenderFrameObserver(render_frame, this); } void AtomRendererClient::RenderViewCreated(content::RenderView* render_view) { new printing::PrintWebViewHelper(render_view); new AtomRenderViewObserver(render_view, this); + + blink::WebFrameWidget* web_frame_widget = render_view->GetWebFrameWidget(); + if (!web_frame_widget) + return; + + base::CommandLine* cmd = base::CommandLine::ForCurrentProcess(); + if (cmd->HasSwitch(switches::kGuestInstanceID)) { // webview. + web_frame_widget->setBaseBackgroundColor(SK_ColorTRANSPARENT); + } else { // normal window. + // If backgroundColor is specified then use it. + std::string name = cmd->GetSwitchValueASCII(switches::kBackgroundColor); + // Otherwise use white background. + SkColor color = name.empty() ? SK_ColorWHITE : ParseHexColor(name); + web_frame_widget->setBaseBackgroundColor(color); + } +} + +void AtomRendererClient::DidClearWindowObject( + content::RenderFrame* render_frame) { + // Make sure every page will get a script context created. + render_frame->GetWebFrame()->executeScript(blink::WebScriptSource("void 0")); +} + +void AtomRendererClient::RunScriptsAtDocumentStart( + content::RenderFrame* render_frame) { + // Inform the document start pharse. + node::Environment* env = node_bindings_->uv_env(); + if (env) { + v8::HandleScope handle_scope(env->isolate()); + mate::EmitEvent(env->isolate(), env->process_object(), "document-start"); + } +} + +void AtomRendererClient::RunScriptsAtDocumentEnd( + content::RenderFrame* render_frame) { + // Inform the document end pharse. + node::Environment* env = node_bindings_->uv_env(); + if (env) { + v8::HandleScope handle_scope(env->isolate()); + mate::EmitEvent(env->isolate(), env->process_object(), "document-end"); + } } blink::WebSpeechSynthesizer* AtomRendererClient::OverrideSpeechSynthesizer( @@ -155,28 +235,46 @@ bool AtomRendererClient::OverrideCreatePlugin( } void AtomRendererClient::DidCreateScriptContext( - v8::Handle context) { - // Give the node loop a run to make sure everything is ready. - node_bindings_->RunMessageLoop(); + v8::Handle context, content::RenderFrame* render_frame) { + // Only allow node integration for the main frame, unless it is a devtools + // extension page. + if (!render_frame->IsMainFrame() && !IsDevToolsExtension(render_frame)) + return; + + // Whether the node binding has been initialized. + bool first_time = node_bindings_->uv_env() == nullptr; + + // Prepare the node bindings. + if (first_time) { + node_bindings_->Initialize(); + node_bindings_->PrepareMessageLoop(); + } // Setup node environment for each window. node::Environment* env = node_bindings_->CreateEnvironment(context); // Add atom-shell extended APIs. atom_bindings_->BindTo(env->isolate(), env->process_object()); - - // Make uv loop being wrapped by window context. - if (node_bindings_->uv_env() == nullptr) - node_bindings_->set_uv_env(env); + AddRenderBindings(env->isolate(), env->process_object(), + preferences_manager_.get()); // Load everything. node_bindings_->LoadEnvironment(env); + + if (first_time) { + // Make uv loop being wrapped by window context. + node_bindings_->set_uv_env(env); + + // Give the node loop a run to make sure everything is ready. + node_bindings_->RunMessageLoop(); + } } void AtomRendererClient::WillReleaseScriptContext( - v8::Handle context) { + v8::Handle context, content::RenderFrame* render_frame) { node::Environment* env = node::Environment::GetCurrent(context); - mate::EmitEvent(env->isolate(), env->process_object(), "exit"); + if (env) + mate::EmitEvent(env->isolate(), env->process_object(), "exit"); } bool AtomRendererClient::ShouldFork(blink::WebLocalFrame* frame, @@ -209,4 +307,16 @@ void AtomRendererClient::AddKeySystems( AddChromeKeySystems(key_systems); } +void AtomRendererClient::GetNavigationErrorStrings( + content::RenderFrame* render_frame, + const blink::WebURLRequest& failed_request, + const blink::WebURLError& error, + std::string* error_html, + base::string16* error_description) { + if (!error_description) + return; + + *error_description = base::UTF8ToUTF16(net::ErrorToShortString(error.reason)); +} + } // namespace atom diff --git a/atom/renderer/atom_renderer_client.h b/atom/renderer/atom_renderer_client.h index 59b407ba1efb..f4a548908e06 100644 --- a/atom/renderer/atom_renderer_client.h +++ b/atom/renderer/atom_renderer_client.h @@ -9,21 +9,23 @@ #include #include "content/public/renderer/content_renderer_client.h" -#include "content/public/renderer/render_process_observer.h" namespace atom { class AtomBindings; +class PreferencesManager; class NodeBindings; -class AtomRendererClient : public content::ContentRendererClient, - public content::RenderProcessObserver { +class AtomRendererClient : public content::ContentRendererClient { public: AtomRendererClient(); virtual ~AtomRendererClient(); - void DidCreateScriptContext(v8::Handle context); - void WillReleaseScriptContext(v8::Handle context); + void DidClearWindowObject(content::RenderFrame* render_frame); + void DidCreateScriptContext( + v8::Handle context, content::RenderFrame* render_frame); + void WillReleaseScriptContext( + v8::Handle context, content::RenderFrame* render_frame); private: enum NodeIntegration { @@ -33,13 +35,12 @@ class AtomRendererClient : public content::ContentRendererClient, DISABLE, }; - // content::RenderProcessObserver: - void WebKitInitialized() override; - // content::ContentRendererClient: void RenderThreadStarted() override; void RenderFrameCreated(content::RenderFrame*) override; void RenderViewCreated(content::RenderView*) override; + void RunScriptsAtDocumentStart(content::RenderFrame* render_frame) override; + void RunScriptsAtDocumentEnd(content::RenderFrame* render_frame) override; blink::WebSpeechSynthesizer* OverrideSpeechSynthesizer( blink::WebSpeechSynthesizerClient* client) override; bool OverrideCreatePlugin(content::RenderFrame* render_frame, @@ -57,9 +58,15 @@ class AtomRendererClient : public content::ContentRendererClient, const std::string& mime_type, const GURL& original_url) override; void AddKeySystems(std::vector* key_systems) override; + void GetNavigationErrorStrings(content::RenderFrame* render_frame, + const blink::WebURLRequest& failed_request, + const blink::WebURLError& error, + std::string* error_html, + base::string16* error_description) override; - scoped_ptr node_bindings_; - scoped_ptr atom_bindings_; + std::unique_ptr node_bindings_; + std::unique_ptr atom_bindings_; + std::unique_ptr preferences_manager_; DISALLOW_COPY_AND_ASSIGN(AtomRendererClient); }; diff --git a/atom/renderer/lib/chrome-api.js b/atom/renderer/lib/chrome-api.js deleted file mode 100644 index 7ff7d2e87b99..000000000000 --- a/atom/renderer/lib/chrome-api.js +++ /dev/null @@ -1,13 +0,0 @@ -const url = require('url'); -const chrome = window.chrome = window.chrome || {}; - -chrome.extension = { - getURL: function(path) { - return url.format({ - protocol: location.protocol, - slashes: true, - hostname: location.hostname, - pathname: path - }); - } -}; diff --git a/atom/renderer/lib/init.js b/atom/renderer/lib/init.js deleted file mode 100644 index 340a1ef50536..000000000000 --- a/atom/renderer/lib/init.js +++ /dev/null @@ -1,131 +0,0 @@ -'use strict'; - -const events = require('events'); -const path = require('path'); -const Module = require('module'); - -// We modified the original process.argv to let node.js load the -// atom-renderer.js, we need to restore it here. -process.argv.splice(1, 1); - -// Clear search paths. -require(path.resolve(__dirname, '..', '..', 'common', 'lib', 'reset-search-paths')); - -// Import common settings. -require(path.resolve(__dirname, '..', '..', 'common', 'lib', 'init')); - -var globalPaths = Module.globalPaths; - -if (!process.env.ELECTRON_HIDE_INTERNAL_MODULES) { - globalPaths.push(path.resolve(__dirname, '..', 'api', 'lib')); -} - -// Expose public APIs. -globalPaths.push(path.resolve(__dirname, '..', 'api', 'lib', 'exports')); - -// The global variable will be used by ipc for event dispatching -var v8Util = process.atomBinding('v8_util'); - -v8Util.setHiddenValue(global, 'ipc', new events.EventEmitter); - -// Use electron module after everything is ready. -const electron = require('electron'); - -// Call webFrame method. -electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', (event, method, args) => { - electron.webFrame[method].apply(electron.webFrame, args); -}); - -// Process command line arguments. -var nodeIntegration = 'false'; -var preloadScript = null; - -var ref = process.argv; -var i, len, arg; -for (i = 0, len = ref.length; i < len; i++) { - arg = ref[i]; - if (arg.indexOf('--guest-instance-id=') === 0) { - // This is a guest web view. - process.guestInstanceId = parseInt(arg.substr(arg.indexOf('=') + 1)); - } else if (arg.indexOf('--opener-id=') === 0) { - // This is a guest BrowserWindow. - process.openerId = parseInt(arg.substr(arg.indexOf('=') + 1)); - } else if (arg.indexOf('--node-integration=') === 0) { - nodeIntegration = arg.substr(arg.indexOf('=') + 1); - } else if (arg.indexOf('--preload=') === 0) { - preloadScript = arg.substr(arg.indexOf('=') + 1); - } -} - -if (location.protocol === 'chrome-devtools:') { - // Override some inspector APIs. - require('./inspector'); - nodeIntegration = 'true'; -} else if (location.protocol === 'chrome-extension:') { - // Add implementations of chrome API. - require('./chrome-api'); - nodeIntegration = 'true'; -} else { - // Override default web functions. - require('./override'); - - // Load webview tag implementation. - if (process.guestInstanceId == null) { - require('./web-view/web-view'); - require('./web-view/web-view-attributes'); - } -} - -if (nodeIntegration === 'true' || nodeIntegration === 'all' || nodeIntegration === 'except-iframe' || nodeIntegration === 'manual-enable-iframe') { - // Export node bindings to global. - global.require = require; - global.module = module; - - // Set the __filename to the path of html file if it is file: protocol. - if (window.location.protocol === 'file:') { - var pathname = process.platform === 'win32' && window.location.pathname[0] === '/' ? window.location.pathname.substr(1) : window.location.pathname; - global.__filename = path.normalize(decodeURIComponent(pathname)); - global.__dirname = path.dirname(global.__filename); - - // Set module's filename so relative require can work as expected. - module.filename = global.__filename; - - // Also search for module under the html file. - module.paths = module.paths.concat(Module._nodeModulePaths(global.__dirname)); - } else { - global.__filename = __filename; - global.__dirname = __dirname; - } - - // Redirect window.onerror to uncaughtException. - window.onerror = function(message, filename, lineno, colno, error) { - if (global.process.listeners('uncaughtException').length > 0) { - global.process.emit('uncaughtException', error); - return true; - } else { - return false; - } - }; -} else { - // Delete Node's symbols after the Environment has been loaded. - process.once('loaded', function() { - delete global.process; - delete global.setImmediate; - delete global.clearImmediate; - return delete global.global; - }); -} - -// Load the script specfied by the "preload" attribute. -if (preloadScript) { - try { - require(preloadScript); - } catch (error) { - if (error.code === 'MODULE_NOT_FOUND') { - console.error("Unable to load preload script " + preloadScript); - } else { - console.error(error); - console.error(error.stack); - } - } -} diff --git a/atom/renderer/lib/inspector.js b/atom/renderer/lib/inspector.js deleted file mode 100644 index edec3074b95e..000000000000 --- a/atom/renderer/lib/inspector.js +++ /dev/null @@ -1,81 +0,0 @@ -window.onload = function() { - // Use menu API to show context menu. - InspectorFrontendHost.showContextMenuAtPoint = createMenu; - - // Use dialog API to override file chooser dialog. - return WebInspector.createFileSelectorElement = createFileSelectorElement; -}; - -var convertToMenuTemplate = function(items) { - var fn, i, item, len, template; - template = []; - fn = function(item) { - var transformed; - transformed = item.type === 'subMenu' ? { - type: 'submenu', - label: item.label, - enabled: item.enabled, - submenu: convertToMenuTemplate(item.subItems) - } : item.type === 'separator' ? { - type: 'separator' - } : item.type === 'checkbox' ? { - type: 'checkbox', - label: item.label, - enabled: item.enabled, - checked: item.checked - } : { - type: 'normal', - label: item.label, - enabled: item.enabled - }; - if (item.id != null) { - transformed.click = function() { - DevToolsAPI.contextMenuItemSelected(item.id); - return DevToolsAPI.contextMenuCleared(); - }; - } - return template.push(transformed); - }; - for (i = 0, len = items.length; i < len; i++) { - item = items[i]; - fn(item); - } - return template; -}; - -var createMenu = function(x, y, items) { - const remote = require('electron').remote; - const Menu = remote.Menu; - const menu = Menu.buildFromTemplate(convertToMenuTemplate(items)); - - // The menu is expected to show asynchronously. - return setTimeout(function() { - return menu.popup(remote.getCurrentWindow()); - }); -}; - -var showFileChooserDialog = function(callback) { - var dialog, files, remote; - remote = require('electron').remote; - dialog = remote.dialog; - files = dialog.showOpenDialog({}); - if (files != null) { - return callback(pathToHtml5FileObject(files[0])); - } -}; - -var pathToHtml5FileObject = function(path) { - var blob, fs; - fs = require('fs'); - blob = new Blob([fs.readFileSync(path)]); - blob.name = path; - return blob; -}; - -var createFileSelectorElement = function(callback) { - var fileSelectorElement; - fileSelectorElement = document.createElement('span'); - fileSelectorElement.style.display = 'none'; - fileSelectorElement.click = showFileChooserDialog.bind(this, callback); - return fileSelectorElement; -}; diff --git a/atom/renderer/lib/override.js b/atom/renderer/lib/override.js deleted file mode 100644 index 6ff1e80785d8..000000000000 --- a/atom/renderer/lib/override.js +++ /dev/null @@ -1,230 +0,0 @@ -const ipcRenderer = require('electron').ipcRenderer; -const remote = require('electron').remote; - -var slice = [].slice; - -// Helper function to resolve relative url. -var a = window.top.document.createElement('a'); - -var resolveURL = function(url) { - a.href = url; - return a.href; -}; - -// Window object returned by "window.open". -var BrowserWindowProxy = (function() { - BrowserWindowProxy.proxies = {}; - - BrowserWindowProxy.getOrCreate = function(guestId) { - var base; - return (base = this.proxies)[guestId] != null ? base[guestId] : base[guestId] = new BrowserWindowProxy(guestId); - }; - - BrowserWindowProxy.remove = function(guestId) { - return delete this.proxies[guestId]; - }; - - function BrowserWindowProxy(guestId1) { - this.guestId = guestId1; - this.closed = false; - ipcRenderer.once("ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_" + this.guestId, (function(_this) { - return function() { - BrowserWindowProxy.remove(_this.guestId); - return _this.closed = true; - }; - })(this)); - } - - BrowserWindowProxy.prototype.close = function() { - return ipcRenderer.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', this.guestId); - }; - - BrowserWindowProxy.prototype.focus = function() { - return ipcRenderer.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'focus'); - }; - - BrowserWindowProxy.prototype.blur = function() { - return ipcRenderer.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'blur'); - }; - - BrowserWindowProxy.prototype.postMessage = function(message, targetOrigin) { - if (targetOrigin == null) { - targetOrigin = '*'; - } - return ipcRenderer.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', this.guestId, message, targetOrigin, location.origin); - }; - - BrowserWindowProxy.prototype["eval"] = function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return ipcRenderer.send.apply(ipcRenderer, ['ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, 'executeJavaScript'].concat(slice.call(args))); - }; - - return BrowserWindowProxy; - -})(); - -if (process.guestInstanceId == null) { - // Override default window.close. - window.close = function() { - return remote.getCurrentWindow().close(); - }; -} - -// Make the browser window or guest view emit "new-window" event. -window.open = function(url, frameName, features) { - var feature, guestId, i, ints, j, len, len1, name, options, ref1, ref2, value; - if (frameName == null) { - frameName = ''; - } - if (features == null) { - features = ''; - } - options = {}; - ints = ['x', 'y', 'width', 'height', 'min-width', 'max-width', 'min-height', 'max-height', 'zoom-factor']; - - // Make sure to get rid of excessive whitespace in the property name - ref1 = features.split(/,\s*/); - for (i = 0, len = ref1.length; i < len; i++) { - feature = ref1[i]; - ref2 = feature.split(/\s*=/), name = ref2[0], value = ref2[1]; - options[name] = value === 'yes' || value === '1' ? true : value === 'no' || value === '0' ? false : value; - } - if (options.left) { - if (options.x == null) { - options.x = options.left; - } - } - if (options.top) { - if (options.y == null) { - options.y = options.top; - } - } - if (options.title == null) { - options.title = frameName; - } - if (options.width == null) { - options.width = 800; - } - if (options.height == null) { - options.height = 600; - } - - // Resolve relative urls. - url = resolveURL(url); - for (j = 0, len1 = ints.length; j < len1; j++) { - name = ints[j]; - if (options[name] != null) { - options[name] = parseInt(options[name], 10); - } - } - guestId = ipcRenderer.sendSync('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, options); - if (guestId) { - return BrowserWindowProxy.getOrCreate(guestId); - } else { - return null; - } -}; - -// Use the dialog API to implement alert(). -window.alert = function(message, title) { - var buttons; - if (title == null) { - title = ''; - } - buttons = ['OK']; - message = message.toString(); - remote.dialog.showMessageBox(remote.getCurrentWindow(), { - message: message, - title: title, - buttons: buttons - }); - - // Alert should always return undefined. -}; - -// And the confirm(). -window.confirm = function(message, title) { - var buttons, cancelId; - if (title == null) { - title = ''; - } - buttons = ['OK', 'Cancel']; - cancelId = 1; - return !remote.dialog.showMessageBox(remote.getCurrentWindow(), { - message: message, - title: title, - buttons: buttons, - cancelId: cancelId - }); -}; - -// But we do not support prompt(). -window.prompt = function() { - throw new Error('prompt() is and will not be supported.'); -}; - -if (process.openerId != null) { - window.opener = BrowserWindowProxy.getOrCreate(process.openerId); -} - -ipcRenderer.on('ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', function(event, sourceId, message, sourceOrigin) { - // Manually dispatch event instead of using postMessage because we also need to - // set event.source. - event = document.createEvent('Event'); - event.initEvent('message', false, false); - event.data = message; - event.origin = sourceOrigin; - event.source = BrowserWindowProxy.getOrCreate(sourceId); - return window.dispatchEvent(event); -}); - -// Forward history operations to browser. -var sendHistoryOperation = function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return ipcRenderer.send.apply(ipcRenderer, ['ATOM_SHELL_NAVIGATION_CONTROLLER'].concat(slice.call(args))); -}; - -var getHistoryOperation = function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return ipcRenderer.sendSync.apply(ipcRenderer, ['ATOM_SHELL_SYNC_NAVIGATION_CONTROLLER'].concat(slice.call(args))); -}; - -window.history.back = function() { - return sendHistoryOperation('goBack'); -}; - -window.history.forward = function() { - return sendHistoryOperation('goForward'); -}; - -window.history.go = function(offset) { - return sendHistoryOperation('goToOffset', offset); -}; - -Object.defineProperty(window.history, 'length', { - get: function() { - return getHistoryOperation('length'); - } -}); - -// Make document.hidden and document.visibilityState return the correct value. -Object.defineProperty(document, 'hidden', { - get: function() { - var currentWindow; - currentWindow = remote.getCurrentWindow(); - return currentWindow.isMinimized() || !currentWindow.isVisible(); - } -}); - -Object.defineProperty(document, 'visibilityState', { - get: function() { - if (document.hidden) { - return "hidden"; - } else { - return "visible"; - } - } -}); diff --git a/atom/renderer/lib/web-view/guest-view-internal.js b/atom/renderer/lib/web-view/guest-view-internal.js deleted file mode 100644 index a7427abd631c..000000000000 --- a/atom/renderer/lib/web-view/guest-view-internal.js +++ /dev/null @@ -1,112 +0,0 @@ -const ipcRenderer = require('electron').ipcRenderer; -const webFrame = require('electron').webFrame; - -var slice = [].slice; -var requestId = 0; - -var WEB_VIEW_EVENTS = { - 'load-commit': ['url', 'isMainFrame'], - 'did-finish-load': [], - 'did-fail-load': ['errorCode', 'errorDescription', 'validatedURL'], - 'did-frame-finish-load': ['isMainFrame'], - 'did-start-loading': [], - 'did-stop-loading': [], - 'did-get-response-details': ['status', 'newURL', 'originalURL', 'httpResponseCode', 'requestMethod', 'referrer', 'headers'], - 'did-get-redirect-request': ['oldURL', 'newURL', 'isMainFrame'], - 'dom-ready': [], - 'console-message': ['level', 'message', 'line', 'sourceId'], - 'devtools-opened': [], - 'devtools-closed': [], - 'devtools-focused': [], - 'new-window': ['url', 'frameName', 'disposition', 'options'], - 'will-navigate': ['url'], - 'did-navigate': ['url'], - 'did-navigate-in-page': ['url'], - 'close': [], - 'crashed': [], - 'gpu-crashed': [], - 'plugin-crashed': ['name', 'version'], - 'media-started-playing': [], - 'media-paused': [], - 'did-change-theme-color': ['themeColor'], - 'destroyed': [], - 'page-title-updated': ['title', 'explicitSet'], - 'page-favicon-updated': ['favicons'], - 'enter-html-full-screen': [], - 'leave-html-full-screen': [], - 'found-in-page': ['result'] -}; - -var DEPRECATED_EVENTS = { - 'page-title-updated': 'page-title-set' -}; - -var dispatchEvent = function() { - var args, domEvent, eventKey, eventName, f, i, j, len, ref1, webView; - webView = arguments[0], eventName = arguments[1], eventKey = arguments[2], args = 4 <= arguments.length ? slice.call(arguments, 3) : []; - if (DEPRECATED_EVENTS[eventName] != null) { - dispatchEvent.apply(null, [webView, DEPRECATED_EVENTS[eventName], eventKey].concat(slice.call(args))); - } - domEvent = new Event(eventName); - ref1 = WEB_VIEW_EVENTS[eventKey]; - for (i = j = 0, len = ref1.length; j < len; i = ++j) { - f = ref1[i]; - domEvent[f] = args[i]; - } - webView.dispatchEvent(domEvent); - if (eventName === 'load-commit') { - return webView.onLoadCommit(domEvent); - } -}; - -module.exports = { - registerEvents: function(webView, viewInstanceId) { - ipcRenderer.on("ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-" + viewInstanceId, function() { - var eventName = arguments[1]; - var args = 3 <= arguments.length ? slice.call(arguments, 2) : []; - return dispatchEvent.apply(null, [webView, eventName, eventName].concat(slice.call(args))); - }); - ipcRenderer.on("ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-" + viewInstanceId, function() { - var channel = arguments[1]; - var args = 3 <= arguments.length ? slice.call(arguments, 2) : []; - var domEvent = new Event('ipc-message'); - domEvent.channel = channel; - domEvent.args = slice.call(args); - return webView.dispatchEvent(domEvent); - }); - return ipcRenderer.on("ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-" + viewInstanceId, function() { - var args, domEvent, f, i, j, len, ref1; - args = 2 <= arguments.length ? slice.call(arguments, 1) : []; - domEvent = new Event('size-changed'); - ref1 = ['oldWidth', 'oldHeight', 'newWidth', 'newHeight']; - for (i = j = 0, len = ref1.length; j < len; i = ++j) { - f = ref1[i]; - domEvent[f] = args[i]; - } - return webView.onSizeChanged(domEvent); - }); - }, - deregisterEvents: function(viewInstanceId) { - ipcRenderer.removeAllListeners("ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-" + viewInstanceId); - ipcRenderer.removeAllListeners("ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-" + viewInstanceId); - return ipcRenderer.removeAllListeners("ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-" + viewInstanceId); - }, - createGuest: function(params, callback) { - requestId++; - ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', params, requestId); - return ipcRenderer.once("ATOM_SHELL_RESPONSE_" + requestId, callback); - }, - attachGuest: function(elementInstanceId, guestInstanceId, params) { - ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_ATTACH_GUEST', elementInstanceId, guestInstanceId, params); - return webFrame.attachGuest(elementInstanceId); - }, - destroyGuest: function(guestInstanceId) { - return ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', guestInstanceId); - }, - setSize: function(guestInstanceId, params) { - return ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_SET_SIZE', guestInstanceId, params); - }, - setAllowTransparency: function(guestInstanceId, allowtransparency) { - return ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_SET_ALLOW_TRANSPARENCY', guestInstanceId, allowtransparency); - } -}; diff --git a/atom/renderer/lib/web-view/web-view.js b/atom/renderer/lib/web-view/web-view.js deleted file mode 100644 index 44fc10421621..000000000000 --- a/atom/renderer/lib/web-view/web-view.js +++ /dev/null @@ -1,466 +0,0 @@ -'use strict'; - -const deprecate = require('electron').deprecate; -const webFrame = require('electron').webFrame; -const remote = require('electron').remote; -const ipcRenderer = require('electron').ipcRenderer; - -const v8Util = process.atomBinding('v8_util'); -const guestViewInternal = require('./guest-view-internal'); -const webViewConstants = require('./web-view-constants'); - -var hasProp = {}.hasOwnProperty; -var slice = [].slice; - -// ID generator. -var nextId = 0; - -var getNextId = function() { - return ++nextId; -}; - -// Represents the internal state of the WebView node. -var WebViewImpl = (function() { - function WebViewImpl(webviewNode) { - var shadowRoot; - this.webviewNode = webviewNode; - v8Util.setHiddenValue(this.webviewNode, 'internal', this); - this.attached = false; - this.elementAttached = false; - this.beforeFirstNavigation = true; - - // on* Event handlers. - this.on = {}; - this.browserPluginNode = this.createBrowserPluginNode(); - shadowRoot = this.webviewNode.createShadowRoot(); - this.setupWebViewAttributes(); - this.setupFocusPropagation(); - this.viewInstanceId = getNextId(); - shadowRoot.appendChild(this.browserPluginNode); - - // Subscribe to host's zoom level changes. - this.onZoomLevelChanged = (zoomLevel) => { - this.webviewNode.setZoomLevel(zoomLevel); - }; - webFrame.on('zoom-level-changed', this.onZoomLevelChanged); - } - - WebViewImpl.prototype.createBrowserPluginNode = function() { - // We create BrowserPlugin as a custom element in order to observe changes - // to attributes synchronously. - var browserPluginNode; - browserPluginNode = new WebViewImpl.BrowserPlugin(); - v8Util.setHiddenValue(browserPluginNode, 'internal', this); - return browserPluginNode; - }; - - // Resets some state upon reattaching element to the DOM. - WebViewImpl.prototype.reset = function() { - // Unlisten the zoom-level-changed event. - webFrame.removeListener('zoom-level-changed', this.onZoomLevelChanged); - - // If guestInstanceId is defined then the has navigated and has - // already picked up a partition ID. Thus, we need to reset the initialization - // state. However, it may be the case that beforeFirstNavigation is false BUT - // guestInstanceId has yet to be initialized. This means that we have not - // heard back from createGuest yet. We will not reset the flag in this case so - // that we don't end up allocating a second guest. - if (this.guestInstanceId) { - guestViewInternal.destroyGuest(this.guestInstanceId); - this.webContents = null; - this.guestInstanceId = void 0; - this.beforeFirstNavigation = true; - this.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId = true; - } - return this.internalInstanceId = 0; - }; - - // Sets the .request property. - WebViewImpl.prototype.setRequestPropertyOnWebViewNode = function(request) { - return Object.defineProperty(this.webviewNode, 'request', { - value: request, - enumerable: true - }); - }; - - WebViewImpl.prototype.setupFocusPropagation = function() { - if (!this.webviewNode.hasAttribute('tabIndex')) { - - // needs a tabIndex in order to be focusable. - // TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute - // to allow to be focusable. - // See http://crbug.com/231664. - this.webviewNode.setAttribute('tabIndex', -1); - } - this.webviewNode.addEventListener('focus', (function(_this) { - return function() { - // Focus the BrowserPlugin when the takes focus. - return _this.browserPluginNode.focus(); - }; - })(this)); - return this.webviewNode.addEventListener('blur', (function(_this) { - return function() { - // Blur the BrowserPlugin when the loses focus. - return _this.browserPluginNode.blur(); - }; - })(this)); - }; - - - // This observer monitors mutations to attributes of the and - // updates the BrowserPlugin properties accordingly. In turn, updating - // a BrowserPlugin property will update the corresponding BrowserPlugin - // attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more - // details. - WebViewImpl.prototype.handleWebviewAttributeMutation = function(attributeName, oldValue, newValue) { - if (!this.attributes[attributeName] || this.attributes[attributeName].ignoreMutation) { - return; - } - - // Let the changed attribute handle its own mutation; - return this.attributes[attributeName].handleMutation(oldValue, newValue); - }; - - WebViewImpl.prototype.handleBrowserPluginAttributeMutation = function(attributeName, oldValue, newValue) { - if (attributeName === webViewConstants.ATTRIBUTE_INTERNALINSTANCEID && !oldValue && !!newValue) { - this.browserPluginNode.removeAttribute(webViewConstants.ATTRIBUTE_INTERNALINSTANCEID); - this.internalInstanceId = parseInt(newValue); - - // Track when the element resizes using the element resize callback. - webFrame.registerElementResizeCallback(this.internalInstanceId, this.onElementResize.bind(this)); - if (!this.guestInstanceId) { - return; - } - return guestViewInternal.attachGuest(this.internalInstanceId, this.guestInstanceId, this.buildParams()); - } - }; - - WebViewImpl.prototype.onSizeChanged = function(webViewEvent) { - var maxHeight, maxWidth, minHeight, minWidth, newHeight, newWidth, node, width; - newWidth = webViewEvent.newWidth; - newHeight = webViewEvent.newHeight; - node = this.webviewNode; - width = node.offsetWidth; - - // Check the current bounds to make sure we do not resize - // outside of current constraints. - maxWidth = this.attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() | width; - maxHeight = this.attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() | width; - minWidth = this.attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() | width; - minHeight = this.attributes[webViewConstants.ATTRIBUTE_MINHEIGHT].getValue() | width; - minWidth = Math.min(minWidth, maxWidth); - minHeight = Math.min(minHeight, maxHeight); - if (!this.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue() || (newWidth >= minWidth && newWidth <= maxWidth && newHeight >= minHeight && newHeight <= maxHeight)) { - node.style.width = newWidth + 'px'; - node.style.height = newHeight + 'px'; - - // Only fire the DOM event if the size of the has actually - // changed. - return this.dispatchEvent(webViewEvent); - } - }; - - WebViewImpl.prototype.onElementResize = function(newSize) { - // Dispatch the 'resize' event. - var resizeEvent; - resizeEvent = new Event('resize', { - bubbles: true - }); - resizeEvent.newWidth = newSize.width; - resizeEvent.newHeight = newSize.height; - this.dispatchEvent(resizeEvent); - if (this.guestInstanceId) { - return guestViewInternal.setSize(this.guestInstanceId, { - normal: newSize - }); - } - }; - - WebViewImpl.prototype.createGuest = function() { - return guestViewInternal.createGuest(this.buildParams(), (function(_this) { - return function(event, guestInstanceId) { - return _this.attachWindow(guestInstanceId); - }; - })(this)); - }; - - WebViewImpl.prototype.dispatchEvent = function(webViewEvent) { - return this.webviewNode.dispatchEvent(webViewEvent); - }; - - // Adds an 'on' property on the webview, which can be used to set/unset - // an event handler. - WebViewImpl.prototype.setupEventProperty = function(eventName) { - var propertyName; - propertyName = 'on' + eventName.toLowerCase(); - return Object.defineProperty(this.webviewNode, propertyName, { - get: (function(_this) { - return function() { - return _this.on[propertyName]; - }; - })(this), - set: (function(_this) { - return function(value) { - if (_this.on[propertyName]) { - _this.webviewNode.removeEventListener(eventName, _this.on[propertyName]); - } - _this.on[propertyName] = value; - if (value) { - return _this.webviewNode.addEventListener(eventName, value); - } - }; - })(this), - enumerable: true - }); - }; - - // Updates state upon loadcommit. - WebViewImpl.prototype.onLoadCommit = function(webViewEvent) { - var newValue, oldValue; - oldValue = this.webviewNode.getAttribute(webViewConstants.ATTRIBUTE_SRC); - newValue = webViewEvent.url; - if (webViewEvent.isMainFrame && (oldValue !== newValue)) { - - // Touching the src attribute triggers a navigation. To avoid - // triggering a page reload on every guest-initiated navigation, - // we do not handle this mutation. - return this.attributes[webViewConstants.ATTRIBUTE_SRC].setValueIgnoreMutation(newValue); - } - }; - - WebViewImpl.prototype.onAttach = function(storagePartitionId) { - return this.attributes[webViewConstants.ATTRIBUTE_PARTITION].setValue(storagePartitionId); - }; - - WebViewImpl.prototype.buildParams = function() { - var attribute, attributeName, css, elementRect, params, ref1; - params = { - instanceId: this.viewInstanceId, - userAgentOverride: this.userAgentOverride - }; - ref1 = this.attributes; - for (attributeName in ref1) { - if (!hasProp.call(ref1, attributeName)) continue; - attribute = ref1[attributeName]; - params[attributeName] = attribute.getValue(); - } - - // When the WebView is not participating in layout (display:none) - // then getBoundingClientRect() would report a width and height of 0. - // However, in the case where the WebView has a fixed size we can - // use that value to initially size the guest so as to avoid a relayout of - // the on display:block. - css = window.getComputedStyle(this.webviewNode, null); - elementRect = this.webviewNode.getBoundingClientRect(); - params.elementWidth = parseInt(elementRect.width) || parseInt(css.getPropertyValue('width')); - params.elementHeight = parseInt(elementRect.height) || parseInt(css.getPropertyValue('height')); - return params; - }; - - WebViewImpl.prototype.attachWindow = function(guestInstanceId) { - this.guestInstanceId = guestInstanceId; - this.webContents = remote.getGuestWebContents(this.guestInstanceId); - if (!this.internalInstanceId) { - return true; - } - return guestViewInternal.attachGuest(this.internalInstanceId, this.guestInstanceId, this.buildParams()); - }; - - return WebViewImpl; - -})(); - -// Registers browser plugin custom element. -var registerBrowserPluginElement = function() { - var proto; - proto = Object.create(HTMLObjectElement.prototype); - proto.createdCallback = function() { - this.setAttribute('type', 'application/browser-plugin'); - this.setAttribute('id', 'browser-plugin-' + getNextId()); - - // The node fills in the container. - this.style.display = 'block'; - this.style.width = '100%'; - return this.style.height = '100%'; - }; - proto.attributeChangedCallback = function(name, oldValue, newValue) { - var internal; - internal = v8Util.getHiddenValue(this, 'internal'); - if (!internal) { - return; - } - return internal.handleBrowserPluginAttributeMutation(name, oldValue, newValue); - }; - proto.attachedCallback = function() { - // Load the plugin immediately. - return this.nonExistentAttribute; - }; - WebViewImpl.BrowserPlugin = webFrame.registerEmbedderCustomElement('browserplugin', { - "extends": 'object', - prototype: proto - }); - delete proto.createdCallback; - delete proto.attachedCallback; - delete proto.detachedCallback; - return delete proto.attributeChangedCallback; -}; - -// Registers custom element. -var registerWebViewElement = function() { - var createBlockHandler, createNonBlockHandler, i, j, len, len1, m, methods, nonblockMethods, proto; - proto = Object.create(HTMLObjectElement.prototype); - proto.createdCallback = function() { - return new WebViewImpl(this); - }; - proto.attributeChangedCallback = function(name, oldValue, newValue) { - var internal; - internal = v8Util.getHiddenValue(this, 'internal'); - if (!internal) { - return; - } - return internal.handleWebviewAttributeMutation(name, oldValue, newValue); - }; - proto.detachedCallback = function() { - var internal; - internal = v8Util.getHiddenValue(this, 'internal'); - if (!internal) { - return; - } - guestViewInternal.deregisterEvents(internal.viewInstanceId); - internal.elementAttached = false; - return internal.reset(); - }; - proto.attachedCallback = function() { - var internal; - internal = v8Util.getHiddenValue(this, 'internal'); - if (!internal) { - return; - } - if (!internal.elementAttached) { - guestViewInternal.registerEvents(internal, internal.viewInstanceId); - internal.elementAttached = true; - return internal.attributes[webViewConstants.ATTRIBUTE_SRC].parse(); - } - }; - - // Public-facing API methods. - methods = [ - 'getURL', - 'loadURL', - 'getTitle', - 'isLoading', - 'isWaitingForResponse', - 'stop', - 'reload', - 'reloadIgnoringCache', - 'canGoBack', - 'canGoForward', - 'canGoToOffset', - 'clearHistory', - 'goBack', - 'goForward', - 'goToIndex', - 'goToOffset', - 'isCrashed', - 'setUserAgent', - 'getUserAgent', - 'openDevTools', - 'closeDevTools', - 'isDevToolsOpened', - 'isDevToolsFocused', - 'inspectElement', - 'setAudioMuted', - 'isAudioMuted', - 'undo', - 'redo', - 'cut', - 'copy', - 'paste', - 'pasteAndMatchStyle', - 'delete', - 'selectAll', - 'unselect', - 'replace', - 'replaceMisspelling', - 'findInPage', - 'stopFindInPage', - 'getId', - 'downloadURL', - 'inspectServiceWorker', - 'print', - 'printToPDF', - ]; - nonblockMethods = [ - 'executeJavaScript', - 'insertCSS', - 'insertText', - 'send', - 'sendInputEvent', - 'setZoomFactor', - 'setZoomLevel', - 'setZoomLevelLimits', - ]; - - // Forward proto.foo* method calls to WebViewImpl.foo*. - createBlockHandler = function(m) { - return function() { - var args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - var internal = v8Util.getHiddenValue(this, 'internal'); - if (internal.webContents) { - return internal.webContents[m].apply(internal.webContents, args); - } else { - throw new Error(`Cannot call ${m} because the webContents is unavailable. The WebView must be attached to the DOM and the dom-ready event emitted before this method can be called.`); - } - }; - }; - for (i = 0, len = methods.length; i < len; i++) { - m = methods[i]; - proto[m] = createBlockHandler(m); - } - createNonBlockHandler = function(m) { - return function() { - var args, internal; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - internal = v8Util.getHiddenValue(this, 'internal'); - return ipcRenderer.send.apply(ipcRenderer, ['ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', internal.guestInstanceId, m].concat(slice.call(args))); - }; - }; - for (j = 0, len1 = nonblockMethods.length; j < len1; j++) { - m = nonblockMethods[j]; - proto[m] = createNonBlockHandler(m); - } - - // WebContents associated with this webview. - proto.getWebContents = function() { - var internal = v8Util.getHiddenValue(this, 'internal'); - return internal.webContents; - }; - - // Deprecated. - deprecate.rename(proto, 'getUrl', 'getURL'); - window.WebView = webFrame.registerEmbedderCustomElement('webview', { - prototype: proto - }); - - // Delete the callbacks so developers cannot call them and produce unexpected - // behavior. - delete proto.createdCallback; - delete proto.attachedCallback; - delete proto.detachedCallback; - return delete proto.attributeChangedCallback; -}; - -var useCapture = true; - -var listener = function(event) { - if (document.readyState === 'loading') { - return; - } - registerBrowserPluginElement(); - registerWebViewElement(); - return window.removeEventListener(event.type, listener, useCapture); -}; - -window.addEventListener('readystatechange', listener, true); - -module.exports = WebViewImpl; diff --git a/atom/renderer/node_array_buffer_bridge.cc b/atom/renderer/node_array_buffer_bridge.cc index 80f2530524d2..61ad25222f99 100644 --- a/atom/renderer/node_array_buffer_bridge.cc +++ b/atom/renderer/node_array_buffer_bridge.cc @@ -4,7 +4,7 @@ #include "atom/renderer/node_array_buffer_bridge.h" -#include "base/basictypes.h" +#include "base/macros.h" #include "atom/common/node_includes.h" #include "native_mate/converter.h" #include "third_party/WebKit/public/web/WebArrayBuffer.h" @@ -53,7 +53,7 @@ v8::Local BlinkUint8ArrayNew( ab, mate::ConvertToV8(isolate, offset), mate::ConvertToV8(isolate, size) }; return v8::Local::Cast(constructor->NewInstance( - context, arraysize(args), args).ToLocalChecked()); + context, node::arraysize(args), args).ToLocalChecked()); } } // namespace diff --git a/atom/renderer/preferences_manager.cc b/atom/renderer/preferences_manager.cc new file mode 100644 index 000000000000..a9ed710a9dbd --- /dev/null +++ b/atom/renderer/preferences_manager.cc @@ -0,0 +1,34 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/renderer/preferences_manager.h" + +#include "atom/common/api/api_messages.h" +#include "content/public/renderer/render_thread.h" + +namespace atom { + +PreferencesManager::PreferencesManager() { + content::RenderThread::Get()->AddObserver(this); +} + +PreferencesManager::~PreferencesManager() { +} + +bool PreferencesManager::OnControlMessageReceived( + const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(PreferencesManager, message) + IPC_MESSAGE_HANDLER(AtomMsg_UpdatePreferences, OnUpdatePreferences) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void PreferencesManager::OnUpdatePreferences( + const base::ListValue& preferences) { + preferences_ = preferences.CreateDeepCopy(); +} + +} // namespace atom diff --git a/atom/renderer/preferences_manager.h b/atom/renderer/preferences_manager.h new file mode 100644 index 000000000000..451928085d12 --- /dev/null +++ b/atom/renderer/preferences_manager.h @@ -0,0 +1,35 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_RENDERER_PREFERENCES_MANAGER_H_ +#define ATOM_RENDERER_PREFERENCES_MANAGER_H_ + +#include + +#include "base/values.h" +#include "content/public/renderer/render_process_observer.h" + +namespace atom { + +class PreferencesManager : public content::RenderProcessObserver { + public: + PreferencesManager(); + ~PreferencesManager() override; + + const base::ListValue* preferences() const { return preferences_.get(); } + + private: + // content::RenderThreadObserver: + bool OnControlMessageReceived(const IPC::Message& message) override; + + void OnUpdatePreferences(const base::ListValue& preferences); + + std::unique_ptr preferences_; + + DISALLOW_COPY_AND_ASSIGN(PreferencesManager); +}; + +} // namespace atom + +#endif // ATOM_RENDERER_PREFERENCES_MANAGER_H_ diff --git a/atom/utility/atom_content_utility_client.cc b/atom/utility/atom_content_utility_client.cc index 2e591f2c6a8d..aff567fbcaab 100644 --- a/atom/utility/atom_content_utility_client.cc +++ b/atom/utility/atom_content_utility_client.cc @@ -37,7 +37,7 @@ int64_t AtomContentUtilityClient::max_ipc_message_size_ = AtomContentUtilityClient::AtomContentUtilityClient() : filter_messages_(false) { #if defined(OS_WIN) - handlers_.push_back(new PrintingHandlerWin()); + handlers_.push_back(new printing::PrintingHandlerWin()); #endif } @@ -71,11 +71,4 @@ void AtomContentUtilityClient::OnStartupPing() { // Don't release the process, we assume further messages are on the way. } -// static -void AtomContentUtilityClient::PreSandboxStartup() { -#if defined(OS_WIN) - PrintingHandlerWin::PreSandboxStartup(); -#endif -} - } // namespace atom diff --git a/atom/utility/atom_content_utility_client.h b/atom/utility/atom_content_utility_client.h index 229ae3f91765..ba0fef471a11 100644 --- a/atom/utility/atom_content_utility_client.h +++ b/atom/utility/atom_content_utility_client.h @@ -31,8 +31,6 @@ class AtomContentUtilityClient : public content::ContentUtilityClient { void UtilityThreadStarted() override; bool OnMessageReceived(const IPC::Message& message) override; - static void PreSandboxStartup(); - static void set_max_ipc_message_size_for_test(int64_t max_message_size) { max_ipc_message_size_ = max_message_size; } diff --git a/chromium_src/SkUserConfig.h b/chromium_src/SkUserConfig.h new file mode 100644 index 000000000000..755b050de6c0 --- /dev/null +++ b/chromium_src/SkUserConfig.h @@ -0,0 +1,158 @@ + +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkUserConfig_DEFINED +#define SkUserConfig_DEFINED + +/* SkTypes.h, the root of the public header files, does the following trick: + + #include "SkPreConfig.h" + #include "SkUserConfig.h" + #include "SkPostConfig.h" + + SkPreConfig.h runs first, and it is responsible for initializing certain + skia defines. + + SkPostConfig.h runs last, and its job is to just check that the final + defines are consistent (i.e. that we don't have mutually conflicting + defines). + + SkUserConfig.h (this file) runs in the middle. It gets to change or augment + the list of flags initially set in preconfig, and then postconfig checks + that everything still makes sense. + + Below are optional defines that add, subtract, or change default behavior + in Skia. Your port can locally edit this file to enable/disable flags as + you choose, or these can be delared on your command line (i.e. -Dfoo). + + By default, this include file will always default to having all of the flags + commented out, so including it will have no effect. +*/ + +/////////////////////////////////////////////////////////////////////////////// + +/* Skia has lots of debug-only code. Often this is just null checks or other + parameter checking, but sometimes it can be quite intrusive (e.g. check that + each 32bit pixel is in premultiplied form). This code can be very useful + during development, but will slow things down in a shipping product. + + By default, these mutually exclusive flags are defined in SkPreConfig.h, + based on the presence or absence of NDEBUG, but that decision can be changed + here. + */ +//#define SK_DEBUG +//#define SK_RELEASE + +/* Skia has certain debug-only code that is extremely intensive even for debug + builds. This code is useful for diagnosing specific issues, but is not + generally applicable, therefore it must be explicitly enabled to avoid + the performance impact. By default these flags are undefined, but can be + enabled by uncommenting them below. + */ +//#define SK_DEBUG_GLYPH_CACHE +//#define SK_DEBUG_PATH + +/* If, in debugging mode, Skia needs to stop (presumably to invoke a debugger) + it will call SK_CRASH(). If this is not defined it, it is defined in + SkPostConfig.h to write to an illegal address + */ +//#define SK_CRASH() *(int *)(uintptr_t)0 = 0 + + +/* preconfig will have attempted to determine the endianness of the system, + but you can change these mutually exclusive flags here. + */ +//#define SK_CPU_BENDIAN +//#define SK_CPU_LENDIAN + +/* Most compilers use the same bit endianness for bit flags in a byte as the + system byte endianness, and this is the default. If for some reason this + needs to be overridden, specify which of the mutually exclusive flags to + use. For example, some atom processors in certain configurations have big + endian byte order but little endian bit orders. +*/ +//#define SK_UINT8_BITFIELD_BENDIAN +//#define SK_UINT8_BITFIELD_LENDIAN + + +/* To write debug messages to a console, skia will call SkDebugf(...) following + printf conventions (e.g. const char* format, ...). If you want to redirect + this to something other than printf, define yours here + */ +// Log the file and line number for assertions. +#define SkDebugf(...) + +/* + * To specify a different default font cache limit, define this. If this is + * undefined, skia will use a built-in value. + */ +//#define SK_DEFAULT_FONT_CACHE_LIMIT (1024 * 1024) + +/* + * To specify the default size of the image cache, undefine this and set it to + * the desired value (in bytes). SkGraphics.h as a runtime API to set this + * value as well. If this is undefined, a built-in value will be used. + */ +//#define SK_DEFAULT_IMAGE_CACHE_LIMIT (1024 * 1024) + +/* Define this to allow PDF scalars above 32k. The PDF/A spec doesn't allow + them, but modern PDF interpreters should handle them just fine. + */ +//#define SK_ALLOW_LARGE_PDF_SCALARS + +/* Define this to provide font subsetter in PDF generation. + */ +//#define SK_SFNTLY_SUBSETTER "sfntly/subsetter/font_subsetter.h" + +/* Define this to set the upper limit for text to support LCD. Values that + are very large increase the cost in the font cache and draw slower, without + improving readability. If this is undefined, Skia will use its default + value (e.g. 48) + */ +//#define SK_MAX_SIZE_FOR_LCDTEXT 48 + +/* If SK_DEBUG is defined, then you can optionally define SK_SUPPORT_UNITTEST + which will run additional self-tests at startup. These can take a long time, + so this flag is optional. + */ +#ifdef SK_DEBUG +//#define SK_SUPPORT_UNITTEST +#endif + +/* Change the ordering to work in X windows. + */ +#ifdef SK_SAMPLES_FOR_X + #define SK_R32_SHIFT 16 + #define SK_G32_SHIFT 8 + #define SK_B32_SHIFT 0 + #define SK_A32_SHIFT 24 +#endif + + +/* Determines whether to build code that supports the GPU backend. Some classes + that are not GPU-specific, such as SkShader subclasses, have optional code + that is used allows them to interact with the GPU backend. If you'd like to + omit this code set SK_SUPPORT_GPU to 0. This also allows you to omit the gpu + directories from your include search path when you're not building the GPU + backend. Defaults to 1 (build the GPU code). + */ +//#define SK_SUPPORT_GPU 1 + + +/* The PDF generation code uses Path Ops to handle complex clipping paths, + * but at this time, Path Ops is not release ready yet. So, the code is + * hidden behind this #define guard. If you are feeling adventurous and + * want the latest and greatest PDF generation code, uncomment the #define. + * When Path Ops is release ready, the define guards and this user config + * define should be removed entirely. + */ +//#define SK_PDF_USE_PATHOPS_CLIPPING + +#endif + diff --git a/chromium_src/chrome/browser/browser_process.h b/chromium_src/chrome/browser/browser_process.h index f971320f8213..53ec0ba75f1c 100644 --- a/chromium_src/chrome/browser/browser_process.h +++ b/chromium_src/chrome/browser/browser_process.h @@ -12,7 +12,7 @@ #include -#include "base/basictypes.h" +#include "base/macros.h" #include "base/memory/scoped_ptr.h" namespace printing { @@ -31,7 +31,7 @@ class BrowserProcess { printing::PrintJobManager* print_job_manager(); private: - scoped_ptr print_job_manager_; + std::unique_ptr print_job_manager_; DISALLOW_COPY_AND_ASSIGN(BrowserProcess); }; diff --git a/chromium_src/chrome/browser/certificate_manager_model.cc b/chromium_src/chrome/browser/certificate_manager_model.cc new file mode 100644 index 000000000000..2274c09e3415 --- /dev/null +++ b/chromium_src/chrome/browser/certificate_manager_model.cc @@ -0,0 +1,173 @@ +// 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/certificate_manager_model.h" + +#include + +#include "base/bind.h" +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/resource_context.h" +#include "crypto/nss_util.h" +#include "crypto/nss_util_internal.h" +#include "net/base/crypto_module.h" +#include "net/base/net_errors.h" +#include "net/cert/nss_cert_database.h" +#include "net/cert/x509_certificate.h" + +using content::BrowserThread; + +namespace { + +net::NSSCertDatabase* g_nss_cert_database = nullptr; + +net::NSSCertDatabase* GetNSSCertDatabaseForResourceContext( + content::ResourceContext* context, + const base::Callback& callback) { + // This initialization is not thread safe. This CHECK ensures that this code + // is only run on a single thread. + CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); + if (!g_nss_cert_database) { + // Linux has only a single persistent slot compared to ChromeOS's separate + // public and private slot. + // Redirect any slot usage to this persistent slot on Linux. + g_nss_cert_database = new net::NSSCertDatabase( + crypto::ScopedPK11Slot( + crypto::GetPersistentNSSKeySlot()) /* public slot */, + crypto::ScopedPK11Slot( + crypto::GetPersistentNSSKeySlot()) /* private slot */); + } + return g_nss_cert_database; +} + +} // namespace + +// CertificateManagerModel is created on the UI thread. It needs a +// NSSCertDatabase handle (and on ChromeOS it needs to get the TPM status) which +// needs to be done on the IO thread. +// +// The initialization flow is roughly: +// +// UI thread IO Thread +// +// CertificateManagerModel::Create +// \--------------------------------------v +// CertificateManagerModel::GetCertDBOnIOThread +// | +// GetNSSCertDatabaseForResourceContext +// | +// CertificateManagerModel::DidGetCertDBOnIOThread +// v--------------------------------------/ +// CertificateManagerModel::DidGetCertDBOnUIThread +// | +// new CertificateManagerModel +// | +// callback + +// static +void CertificateManagerModel::Create( + content::BrowserContext* browser_context, + const CreationCallback& callback) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(&CertificateManagerModel::GetCertDBOnIOThread, + browser_context->GetResourceContext(), + callback)); +} + +CertificateManagerModel::CertificateManagerModel( + net::NSSCertDatabase* nss_cert_database, + bool is_user_db_available) + : cert_db_(nss_cert_database), + is_user_db_available_(is_user_db_available) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); +} + +CertificateManagerModel::~CertificateManagerModel() { +} + +int CertificateManagerModel::ImportFromPKCS12(net::CryptoModule* module, + const std::string& data, + const base::string16& password, + bool is_extractable, + net::CertificateList* imported_certs) { + return cert_db_->ImportFromPKCS12(module, data, password, + is_extractable, imported_certs); +} + +int CertificateManagerModel::ImportUserCert(const std::string& data) { + return cert_db_->ImportUserCert(data); +} + +bool CertificateManagerModel::ImportCACerts( + const net::CertificateList& certificates, + net::NSSCertDatabase::TrustBits trust_bits, + net::NSSCertDatabase::ImportCertFailureList* not_imported) { + return cert_db_->ImportCACerts(certificates, trust_bits, not_imported); +} + +bool CertificateManagerModel::ImportServerCert( + const net::CertificateList& certificates, + net::NSSCertDatabase::TrustBits trust_bits, + net::NSSCertDatabase::ImportCertFailureList* not_imported) { + return cert_db_->ImportServerCert(certificates, trust_bits, + not_imported); +} + +bool CertificateManagerModel::SetCertTrust( + const net::X509Certificate* cert, + net::CertType type, + net::NSSCertDatabase::TrustBits trust_bits) { + return cert_db_->SetCertTrust(cert, type, trust_bits); +} + +bool CertificateManagerModel::Delete(net::X509Certificate* cert) { + return cert_db_->DeleteCertAndKey(cert); +} + +// static +void CertificateManagerModel::DidGetCertDBOnUIThread( + net::NSSCertDatabase* cert_db, + bool is_user_db_available, + const CreationCallback& callback) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + std::unique_ptr model(new CertificateManagerModel( + cert_db, is_user_db_available)); + callback.Run(std::move(model)); +} + +// static +void CertificateManagerModel::DidGetCertDBOnIOThread( + const CreationCallback& callback, + net::NSSCertDatabase* cert_db) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + bool is_user_db_available = !!cert_db->GetPublicSlot(); + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&CertificateManagerModel::DidGetCertDBOnUIThread, + cert_db, + is_user_db_available, + callback)); +} + +// static +void CertificateManagerModel::GetCertDBOnIOThread( + content::ResourceContext* context, + const CreationCallback& callback) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + net::NSSCertDatabase* cert_db = GetNSSCertDatabaseForResourceContext( + context, + base::Bind(&CertificateManagerModel::DidGetCertDBOnIOThread, + callback)); + if (cert_db) + DidGetCertDBOnIOThread(callback, cert_db); +} diff --git a/chromium_src/chrome/browser/certificate_manager_model.h b/chromium_src/chrome/browser/certificate_manager_model.h new file mode 100644 index 000000000000..81c3b6c8a8d1 --- /dev/null +++ b/chromium_src/chrome/browser/certificate_manager_model.h @@ -0,0 +1,119 @@ +// 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_CERTIFICATE_MANAGER_MODEL_H_ +#define CHROME_BROWSER_CERTIFICATE_MANAGER_MODEL_H_ + +#include +#include + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "net/cert/nss_cert_database.h" + +namespace content { +class BrowserContext; +class ResourceContext; +} // namespace content + +// CertificateManagerModel provides the data to be displayed in the certificate +// manager dialog, and processes changes from the view. +class CertificateManagerModel { + public: + typedef base::Callback)> + CreationCallback; + + // Creates a CertificateManagerModel. The model will be passed to the callback + // when it is ready. The caller must ensure the model does not outlive the + // |browser_context|. + static void Create(content::BrowserContext* browser_context, + const CreationCallback& callback); + + ~CertificateManagerModel(); + + bool is_user_db_available() const { return is_user_db_available_; } + + // Accessor for read-only access to the underlying NSSCertDatabase. + const net::NSSCertDatabase* cert_db() const { return cert_db_; } + + // Import private keys and certificates from PKCS #12 encoded + // |data|, using the given |password|. If |is_extractable| is false, + // mark the private key as unextractable from the module. + // Returns a net error code on failure. + int ImportFromPKCS12(net::CryptoModule* module, + const std::string& data, + const base::string16& password, + bool is_extractable, + net::CertificateList* imported_certs); + + // Import user certificate from DER encoded |data|. + // Returns a net error code on failure. + int ImportUserCert(const std::string& data); + + // Import CA certificates. + // Tries to import all the certificates given. The root will be trusted + // according to |trust_bits|. Any certificates that could not be imported + // will be listed in |not_imported|. + // |trust_bits| should be a bit field of TRUST* values from NSSCertDatabase. + // Returns false if there is an internal error, otherwise true is returned and + // |not_imported| should be checked for any certificates that were not + // imported. + bool ImportCACerts(const net::CertificateList& certificates, + net::NSSCertDatabase::TrustBits trust_bits, + net::NSSCertDatabase::ImportCertFailureList* not_imported); + + // Import server certificate. The first cert should be the server cert. Any + // additional certs should be intermediate/CA certs and will be imported but + // not given any trust. + // Any certificates that could not be imported will be listed in + // |not_imported|. + // |trust_bits| can be set to explicitly trust or distrust the certificate, or + // use TRUST_DEFAULT to inherit trust as normal. + // Returns false if there is an internal error, otherwise true is returned and + // |not_imported| should be checked for any certificates that were not + // imported. + bool ImportServerCert( + const net::CertificateList& certificates, + net::NSSCertDatabase::TrustBits trust_bits, + net::NSSCertDatabase::ImportCertFailureList* not_imported); + + // Set trust values for certificate. + // |trust_bits| should be a bit field of TRUST* values from NSSCertDatabase. + // Returns true on success or false on failure. + bool SetCertTrust(const net::X509Certificate* cert, + net::CertType type, + net::NSSCertDatabase::TrustBits trust_bits); + + // Delete the cert. Returns true on success. |cert| is still valid when this + // function returns. + bool Delete(net::X509Certificate* cert); + + private: + CertificateManagerModel(net::NSSCertDatabase* nss_cert_database, + bool is_user_db_available); + + // Methods used during initialization, see the comment at the top of the .cc + // file for details. + static void DidGetCertDBOnUIThread( + net::NSSCertDatabase* cert_db, + bool is_user_db_available, + const CreationCallback& callback); + static void DidGetCertDBOnIOThread( + const CreationCallback& callback, + net::NSSCertDatabase* cert_db); + static void GetCertDBOnIOThread(content::ResourceContext* context, + const CreationCallback& callback); + + net::NSSCertDatabase* cert_db_; + // Whether the certificate database has a public slot associated with the + // profile. If not set, importing certificates is not allowed with this model. + bool is_user_db_available_; + + DISALLOW_COPY_AND_ASSIGN(CertificateManagerModel); +}; + +#endif // CHROME_BROWSER_CERTIFICATE_MANAGER_MODEL_H_ diff --git a/chromium_src/chrome/browser/chrome_notification_types.h b/chromium_src/chrome/browser/chrome_notification_types.h index eb5ed40342d0..05df960c5ea4 100644 --- a/chromium_src/chrome/browser/chrome_notification_types.h +++ b/chromium_src/chrome/browser/chrome_notification_types.h @@ -32,7 +32,7 @@ enum NotificationType { NOTIFICATION_BROWSER_CLOSING, // This message is sent after a window has been closed. The source is a - // Source containing the affected Browser. No details are exptected. + // Source containing the affected Browser. No details are expected. NOTIFICATION_BROWSER_CLOSED, // This message is sent when closing a browser has been cancelled, either by @@ -411,7 +411,7 @@ enum NotificationType { // the source is a Profile. NOTIFICATION_EXTENSION_LOADED_DEPRECATED, - // An error occured while attempting to load an extension. The details are a + // An error occurred while attempting to load an extension. The details are a // string with details about why the load failed. NOTIFICATION_EXTENSION_LOAD_ERROR, @@ -434,7 +434,7 @@ enum NotificationType { // The details are an InstalledExtensionInfo, and the source is a Profile. NOTIFICATION_EXTENSION_INSTALLED, - // An error occured during extension install. The details are a string with + // An error occurred during extension install. The details are a string with // details about why the install failed. NOTIFICATION_EXTENSION_INSTALL_ERROR, @@ -625,7 +625,7 @@ enum NotificationType { // TabSpecificContentSettings object, there are no details. NOTIFICATION_COLLECTED_COOKIES_SHOWN, - // Sent when a non-default setting in the the notification content settings + // Sent when a non-default setting in the notification content settings // map has changed. The source is the DesktopNotificationService, the // details are None. NOTIFICATION_DESKTOP_NOTIFICATION_SETTINGS_CHANGED, @@ -776,7 +776,7 @@ enum NotificationType { NOTIFICATION_USER_LIST_CHANGED, // Sent when the screen lock state has changed. The source is - // ScreenLocker and the details is a bool specifing that the + // ScreenLocker and the details is a bool specifying that the // screen is locked. When details is a false, the source object // is being deleted, so the receiver shouldn't use the screen locker // object. @@ -838,7 +838,7 @@ enum NotificationType { // which was installed. NOTIFICATION_APP_INSTALLED_TO_NTP, - // Similar to NOTIFICATION_APP_INSTALLED_TO_NTP but used to nofity ash AppList + // Similar to NOTIFICATION_APP_INSTALLED_TO_NTP but used to notify ash AppList // about installed app. Source is the profile in which the app is installed // and Details is the string ID of the extension. NOTIFICATION_APP_INSTALLED_TO_APPLIST, diff --git a/chromium_src/chrome/browser/extensions/global_shortcut_listener.h b/chromium_src/chrome/browser/extensions/global_shortcut_listener.h index 1f07df2b6e10..9aec54a3263d 100644 --- a/chromium_src/chrome/browser/extensions/global_shortcut_listener.h +++ b/chromium_src/chrome/browser/extensions/global_shortcut_listener.h @@ -7,7 +7,7 @@ #include -#include "base/basictypes.h" +#include "base/macros.h" #include "ui/events/keycodes/keyboard_codes.h" namespace ui { diff --git a/chromium_src/chrome/browser/extensions/global_shortcut_listener_win.h b/chromium_src/chrome/browser/extensions/global_shortcut_listener_win.h index b3917e9a160e..1999e0e7747b 100644 --- a/chromium_src/chrome/browser/extensions/global_shortcut_listener_win.h +++ b/chromium_src/chrome/browser/extensions/global_shortcut_listener_win.h @@ -41,7 +41,7 @@ class GlobalShortcutListenerWin : public GlobalShortcutListener { typedef std::map HotkeyIdMap; HotkeyIdMap hotkey_ids_; - scoped_ptr singleton_hwnd_observer_; + std::unique_ptr singleton_hwnd_observer_; DISALLOW_COPY_AND_ASSIGN(GlobalShortcutListenerWin); }; diff --git a/chromium_src/chrome/browser/extensions/global_shortcut_listener_x11.cc b/chromium_src/chrome/browser/extensions/global_shortcut_listener_x11.cc index 9a3cca69d538..00a689a7cb2f 100644 --- a/chromium_src/chrome/browser/extensions/global_shortcut_listener_x11.cc +++ b/chromium_src/chrome/browser/extensions/global_shortcut_listener_x11.cc @@ -4,10 +4,13 @@ #include "chrome/browser/extensions/global_shortcut_listener_x11.h" +#include + +#include "base/macros.h" #include "content/public/browser/browser_thread.h" #include "ui/base/accelerators/accelerator.h" #include "ui/events/keycodes/keyboard_code_conversion_x.h" -#include "ui/events/platform/x11/x11_event_source.h" +#include "ui/events/platform/platform_event_source.h" #include "ui/gfx/x/x11_error_tracker.h" #include "ui/gfx/x/x11_types.h" @@ -69,7 +72,7 @@ void GlobalShortcutListenerX11::StartListening() { DCHECK(!registered_hot_keys_.empty()); // Also don't start if no hotkey is // registered. - ui::X11EventSource::GetInstance()->AddPlatformEventDispatcher(this); + ui::PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this); is_listening_ = true; } @@ -79,7 +82,7 @@ void GlobalShortcutListenerX11::StopListening() { DCHECK(registered_hot_keys_.empty()); // Make sure the set is clean before // ending. - ui::X11EventSource::GetInstance()->RemovePlatformEventDispatcher(this); + ui::PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this); is_listening_ = false; } @@ -149,7 +152,6 @@ void GlobalShortcutListenerX11::OnXKeyPressEvent(::XEvent* x_event) { modifiers |= (x_event->xkey.state & ShiftMask) ? ui::EF_SHIFT_DOWN : 0; modifiers |= (x_event->xkey.state & ControlMask) ? ui::EF_CONTROL_DOWN : 0; modifiers |= (x_event->xkey.state & Mod1Mask) ? ui::EF_ALT_DOWN : 0; - // For Windows key modifiers |= (x_event->xkey.state & Mod4Mask) ? ui::EF_COMMAND_DOWN: 0; ui::Accelerator accelerator( diff --git a/chromium_src/chrome/browser/extensions/global_shortcut_listener_x11.h b/chromium_src/chrome/browser/extensions/global_shortcut_listener_x11.h index 8d551993841d..43230b7aa310 100644 --- a/chromium_src/chrome/browser/extensions/global_shortcut_listener_x11.h +++ b/chromium_src/chrome/browser/extensions/global_shortcut_listener_x11.h @@ -5,9 +5,12 @@ #ifndef CHROME_BROWSER_EXTENSIONS_GLOBAL_SHORTCUT_LISTENER_X11_H_ #define CHROME_BROWSER_EXTENSIONS_GLOBAL_SHORTCUT_LISTENER_X11_H_ +#include #include + #include +#include "base/macros.h" #include "chrome/browser/extensions/global_shortcut_listener.h" #include "ui/events/platform/platform_event_dispatcher.h" @@ -20,20 +23,18 @@ class GlobalShortcutListenerX11 : public GlobalShortcutListener, public ui::PlatformEventDispatcher { public: GlobalShortcutListenerX11(); - virtual ~GlobalShortcutListenerX11(); + ~GlobalShortcutListenerX11() override; // ui::PlatformEventDispatcher implementation. - virtual bool CanDispatchEvent(const ui::PlatformEvent& event) override; - virtual uint32_t DispatchEvent(const ui::PlatformEvent& event) override; + bool CanDispatchEvent(const ui::PlatformEvent& event) override; + uint32_t DispatchEvent(const ui::PlatformEvent& event) override; private: // GlobalShortcutListener implementation. - virtual void StartListening() override; - virtual void StopListening() override; - virtual bool RegisterAcceleratorImpl( - const ui::Accelerator& accelerator) override; - virtual void UnregisterAcceleratorImpl( - const ui::Accelerator& accelerator) override; + void StartListening() override; + void StopListening() override; + bool RegisterAcceleratorImpl(const ui::Accelerator& accelerator) override; + void UnregisterAcceleratorImpl(const ui::Accelerator& accelerator) override; // Invoked when a global shortcut is pressed. void OnXKeyPressEvent(::XEvent* x_event); diff --git a/chromium_src/chrome/browser/media/desktop_media_list.h b/chromium_src/chrome/browser/media/desktop_media_list.h index 7ef703e8b7b3..6572e792a122 100644 --- a/chromium_src/chrome/browser/media/desktop_media_list.h +++ b/chromium_src/chrome/browser/media/desktop_media_list.h @@ -5,7 +5,6 @@ #ifndef CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_H_ #define CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_H_ -#include "base/basictypes.h" #include "base/strings/string16.h" #include "base/time/time.h" #include "content/public/browser/desktop_media_id.h" diff --git a/chromium_src/chrome/browser/media/native_desktop_media_list.cc b/chromium_src/chrome/browser/media/native_desktop_media_list.cc index 4a7fb58962c6..a524dfcf38cb 100644 --- a/chromium_src/chrome/browser/media/native_desktop_media_list.cc +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.cc @@ -34,12 +34,12 @@ const int kDefaultUpdatePeriod = 1000; // Returns a hash of a DesktopFrame content to detect when image for a desktop // media source has changed. -uint32 GetFrameHash(webrtc::DesktopFrame* frame) { +uint32_t GetFrameHash(webrtc::DesktopFrame* frame) { int data_size = frame->stride() * frame->size().height(); return base::SuperFastHash(reinterpret_cast(frame->data()), data_size); } -gfx::ImageSkia ScaleDesktopFrame(scoped_ptr frame, +gfx::ImageSkia ScaleDesktopFrame(std::unique_ptr frame, gfx::Size size) { gfx::Rect scaled_rect = media::ComputeLetterboxRegion( gfx::Rect(0, 0, size.width(), size.height()), @@ -86,8 +86,8 @@ class NativeDesktopMediaList::Worker : public webrtc::DesktopCapturer::Callback { public: Worker(base::WeakPtr media_list, - scoped_ptr screen_capturer, - scoped_ptr window_capturer); + std::unique_ptr screen_capturer, + std::unique_ptr window_capturer); ~Worker() override; void Refresh(const gfx::Size& thumbnail_size, @@ -102,10 +102,10 @@ class NativeDesktopMediaList::Worker base::WeakPtr media_list_; - scoped_ptr screen_capturer_; - scoped_ptr window_capturer_; + std::unique_ptr screen_capturer_; + std::unique_ptr window_capturer_; - scoped_ptr current_frame_; + std::unique_ptr current_frame_; ImageHashesMap image_hashes_; @@ -114,11 +114,11 @@ class NativeDesktopMediaList::Worker NativeDesktopMediaList::Worker::Worker( base::WeakPtr media_list, - scoped_ptr screen_capturer, - scoped_ptr window_capturer) + std::unique_ptr screen_capturer, + std::unique_ptr window_capturer) : media_list_(media_list), - screen_capturer_(screen_capturer.Pass()), - window_capturer_(window_capturer.Pass()) { + screen_capturer_(std::move(screen_capturer)), + window_capturer_(std::move(window_capturer)) { if (screen_capturer_) screen_capturer_->Start(this); if (window_capturer_) @@ -195,14 +195,14 @@ void NativeDesktopMediaList::Worker::Refresh( // |current_frame_| may be NULL if capture failed (e.g. because window has // been closed). if (current_frame_) { - uint32 frame_hash = GetFrameHash(current_frame_.get()); + uint32_t frame_hash = GetFrameHash(current_frame_.get()); new_image_hashes[source.id] = frame_hash; // Scale the image only if it has changed. ImageHashesMap::iterator it = image_hashes_.find(source.id); if (it == image_hashes_.end() || it->second != frame_hash) { gfx::ImageSkia thumbnail = - ScaleDesktopFrame(current_frame_.Pass(), thumbnail_size); + ScaleDesktopFrame(std::move(current_frame_), thumbnail_size); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&NativeDesktopMediaList::OnSourceThumbnail, @@ -229,10 +229,10 @@ void NativeDesktopMediaList::Worker::OnCaptureCompleted( } NativeDesktopMediaList::NativeDesktopMediaList( - scoped_ptr screen_capturer, - scoped_ptr window_capturer) - : screen_capturer_(screen_capturer.Pass()), - window_capturer_(window_capturer.Pass()), + std::unique_ptr screen_capturer, + std::unique_ptr window_capturer) + : screen_capturer_(std::move(screen_capturer)), + window_capturer_(std::move(window_capturer)), update_period_(base::TimeDelta::FromMilliseconds(kDefaultUpdatePeriod)), thumbnail_size_(100, 100), view_dialog_id_(-1), @@ -269,7 +269,8 @@ void NativeDesktopMediaList::StartUpdating(DesktopMediaListObserver* observer) { observer_ = observer; worker_.reset(new Worker(weak_factory_.GetWeakPtr(), - screen_capturer_.Pass(), window_capturer_.Pass())); + std::move(screen_capturer_), + std::move(window_capturer_))); Refresh(); } diff --git a/chromium_src/chrome/browser/media/native_desktop_media_list.h b/chromium_src/chrome/browser/media/native_desktop_media_list.h index 943d3dd32565..803243979801 100644 --- a/chromium_src/chrome/browser/media/native_desktop_media_list.h +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.h @@ -5,7 +5,6 @@ #ifndef CHROME_BROWSER_MEDIA_NATIVE_DESKTOP_MEDIA_LIST_H_ #define CHROME_BROWSER_MEDIA_NATIVE_DESKTOP_MEDIA_LIST_H_ -#include "base/basictypes.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/sequenced_task_runner.h" @@ -26,8 +25,8 @@ class NativeDesktopMediaList : public DesktopMediaList { // types of sources the model should be populated with (e.g. it will only // contain windows, if |screen_capturer| is NULL). NativeDesktopMediaList( - scoped_ptr screen_capturer, - scoped_ptr window_capturer); + std::unique_ptr screen_capturer, + std::unique_ptr window_capturer); ~NativeDesktopMediaList() override; // DesktopMediaList interface. @@ -67,8 +66,8 @@ class NativeDesktopMediaList : public DesktopMediaList { void OnRefreshFinished(); // Capturers specified in SetCapturers() and passed to the |worker_| later. - scoped_ptr screen_capturer_; - scoped_ptr window_capturer_; + std::unique_ptr screen_capturer_; + std::unique_ptr window_capturer_; // Time interval between mode updates. base::TimeDelta update_period_; @@ -88,7 +87,7 @@ class NativeDesktopMediaList : public DesktopMediaList { // An object that does all the work of getting list of sources on a background // thread (see |capture_task_runner_|). Destroyed on |capture_task_runner_| // after the model is destroyed. - scoped_ptr worker_; + std::unique_ptr worker_; // Current list of sources. std::vector sources_; diff --git a/chromium_src/chrome/browser/printing/pdf_to_emf_converter.cc b/chromium_src/chrome/browser/printing/pdf_to_emf_converter.cc index 9064cd961bd5..e13effd64de0 100644 --- a/chromium_src/chrome/browser/printing/pdf_to_emf_converter.cc +++ b/chromium_src/chrome/browser/printing/pdf_to_emf_converter.cc @@ -48,7 +48,7 @@ class RefCountedTempDir DISALLOW_COPY_AND_ASSIGN(RefCountedTempDir); }; -typedef scoped_ptr +typedef std::unique_ptr ScopedTempFile; // Wrapper for Emf to keep only file handle in memory, and load actual data only @@ -58,7 +58,7 @@ typedef scoped_ptr class LazyEmf : public MetafilePlayer { public: LazyEmf(const scoped_refptr& temp_dir, ScopedTempFile file) - : temp_dir_(temp_dir), file_(file.Pass()) {} + : temp_dir_(temp_dir), file_(std::move(file)) {} virtual ~LazyEmf() { Close(); } virtual bool SafePlayback(HDC hdc) const override; @@ -109,21 +109,21 @@ class PdfToEmfUtilityProcessHostClient private: class GetPageCallbackData { - MOVE_ONLY_TYPE_FOR_CPP_03(GetPageCallbackData, RValue); + MOVE_ONLY_TYPE_FOR_CPP_03(GetPageCallbackData); public: GetPageCallbackData(int page_number, PdfToEmfConverter::GetPageCallback callback) : page_number_(page_number), callback_(callback) {} - // Move constructor for STL. - GetPageCallbackData(RValue other) { this->operator=(other); } + GetPageCallbackData(GetPageCallbackData&& other) { + *this = std::move(other); + } - // Move assignment for STL. - GetPageCallbackData& operator=(RValue rhs) { - page_number_ = rhs.object->page_number_; - callback_ = rhs.object->callback_; - emf_ = rhs.object->emf_.Pass(); + GetPageCallbackData& operator=(GetPageCallbackData&& rhs) { + page_number_ = rhs.page_number_; + callback_ = rhs.callback_; + emf_ = std::move(rhs.emf_); return *this; } @@ -131,8 +131,8 @@ class PdfToEmfUtilityProcessHostClient const PdfToEmfConverter::GetPageCallback& callback() const { return callback_; } - ScopedTempFile emf() { return emf_.Pass(); } - void set_emf(ScopedTempFile emf) { emf_ = emf.Pass(); } + ScopedTempFile TakeEmf() { return std::move(emf_); } + void set_emf(ScopedTempFile emf) { emf_ = std::move(emf); } private: int page_number_; @@ -203,10 +203,10 @@ ScopedTempFile CreateTempFile(scoped_refptr* temp_dir) { *temp_dir = new RefCountedTempDir(); ScopedTempFile file; if (!(*temp_dir)->IsValid()) - return file.Pass(); + return file; base::FilePath path; if (!base::CreateTemporaryFileInDir((*temp_dir)->GetPath(), &path)) - return file.Pass(); + return file; file.reset(new base::File(path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE | @@ -215,7 +215,7 @@ ScopedTempFile CreateTempFile(scoped_refptr* temp_dir) { base::File::FLAG_TEMPORARY)); if (!file->IsValid()) file.reset(); - return file.Pass(); + return file; } ScopedTempFile CreateTempPdfFile( @@ -230,7 +230,7 @@ ScopedTempFile CreateTempPdfFile( pdf_file.reset(); } pdf_file->Seek(base::File::FROM_BEGIN, 0); - return pdf_file.Pass(); + return pdf_file; } bool LazyEmf::SafePlayback(HDC hdc) const { @@ -256,7 +256,7 @@ void LazyEmf::Close() const { bool LazyEmf::LoadEmf(Emf* emf) const { file_->Seek(base::File::FROM_BEGIN, 0); - int64 size = file_->GetLength(); + int64_t size = file_->GetLength(); if (size <= 0) return false; std::vector data(size); @@ -319,12 +319,11 @@ void PdfToEmfUtilityProcessHostClient::OnProcessStarted() { void PdfToEmfUtilityProcessHostClient::OnTempPdfReady(ScopedTempFile pdf) { DCHECK_CURRENTLY_ON(BrowserThread::IO); - if (!utility_process_host_) + if (!utility_process_host_ || !pdf) return OnFailed(); - base::ProcessHandle process = utility_process_host_->GetData().handle; // Should reply with OnPageCount(). Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles( - IPC::GetFileHandleForProcess(pdf->GetPlatformFile(), process, false), + IPC::GetPlatformFileForTransit(pdf->GetPlatformFile(), false), settings_)); } @@ -373,12 +372,11 @@ void PdfToEmfUtilityProcessHostClient::OnTempEmfReady( GetPageCallbackData* callback_data, ScopedTempFile emf) { DCHECK_CURRENTLY_ON(BrowserThread::IO); - if (!utility_process_host_) + if (!utility_process_host_ || !emf) return OnFailed(); - base::ProcessHandle process = utility_process_host_->GetData().handle; IPC::PlatformFileForTransit transit = - IPC::GetFileHandleForProcess(emf->GetPlatformFile(), process, false); - callback_data->set_emf(emf.Pass()); + IPC::GetPlatformFileForTransit(emf->GetPlatformFile(), false); + callback_data->set_emf(std::move(emf)); // Should reply with OnPageDone(). Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles_GetPage( callback_data->page_number(), transit)); @@ -389,10 +387,16 @@ void PdfToEmfUtilityProcessHostClient::OnPageDone(bool success, DCHECK_CURRENTLY_ON(BrowserThread::IO); if (get_page_callbacks_.empty()) return OnFailed(); - scoped_ptr emf; GetPageCallbackData& data = get_page_callbacks_.front(); - if (success) - emf.reset(new LazyEmf(temp_dir_, data.emf().Pass())); + std::unique_ptr emf; + + if (success) { + ScopedTempFile temp_emf = data.TakeEmf(); + if (!temp_emf) // Unexpected message from utility process. + return OnFailed(); + emf.reset(new LazyEmf(temp_dir_, std::move(temp_emf))); + } + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(&PdfToEmfConverterImpl::RunCallback, @@ -487,8 +491,8 @@ PdfToEmfConverter::~PdfToEmfConverter() { } // static -scoped_ptr PdfToEmfConverter::CreateDefault() { - return scoped_ptr(new PdfToEmfConverterImpl()); +std::unique_ptr PdfToEmfConverter::CreateDefault() { + return std::unique_ptr(new PdfToEmfConverterImpl()); } } // namespace printing diff --git a/chromium_src/chrome/browser/printing/pdf_to_emf_converter.h b/chromium_src/chrome/browser/printing/pdf_to_emf_converter.h index 60fdbc4b7e4e..859d42293135 100644 --- a/chromium_src/chrome/browser/printing/pdf_to_emf_converter.h +++ b/chromium_src/chrome/browser/printing/pdf_to_emf_converter.h @@ -23,11 +23,11 @@ class PdfToEmfConverter { typedef base::Callback StartCallback; typedef base::Callback emf)> GetPageCallback; + std::unique_ptr emf)> GetPageCallback; virtual ~PdfToEmfConverter(); - static scoped_ptr CreateDefault(); + static std::unique_ptr CreateDefault(); // Starts conversion of PDF provided as |data|. Calls |start_callback| // with positive |page_count|. |page_count| is 0 if initialization failed. diff --git a/chromium_src/chrome/browser/printing/print_job.cc b/chromium_src/chrome/browser/printing/print_job.cc index 65449ef121e3..87971055e68f 100644 --- a/chromium_src/chrome/browser/printing/print_job.cc +++ b/chromium_src/chrome/browser/printing/print_job.cc @@ -133,7 +133,7 @@ void PrintJob::StartPrinting() { make_scoped_refptr(this), base::Bind(&PrintJobWorker::StartPrinting, base::Unretained(worker_.get()), - document_))); + base::RetainedRef(document_)))); // Set the flag right now. is_job_pending_ = true; @@ -267,7 +267,7 @@ class PrintJob::PdfToEmfState { int pages_in_progress_; gfx::Size page_size_; gfx::Rect content_area_; - scoped_ptr converter_; + std::unique_ptr converter_; }; void PrintJob::StartPdfToEmfConversion( @@ -296,7 +296,7 @@ void PrintJob::OnPdfToEmfStarted(int page_count) { void PrintJob::OnPdfToEmfPageConverted(int page_number, float scale_factor, - scoped_ptr emf) { + std::unique_ptr emf) { DCHECK(ptd_to_emf_state_); if (!document_.get() || !emf) { ptd_to_emf_state_.reset(); @@ -306,7 +306,7 @@ void PrintJob::OnPdfToEmfPageConverted(int page_number, // Update the rendered document. It will send notifications to the listener. document_->SetPage(page_number, - emf.Pass(), + std::move(emf), scale_factor, ptd_to_emf_state_->page_size(), ptd_to_emf_state_->content_area()); @@ -335,7 +335,7 @@ void PrintJob::UpdatePrintedDocument(PrintedDocument* new_document) { make_scoped_refptr(this), base::Bind(&PrintJobWorker::OnDocumentChanged, base::Unretained(worker_.get()), - document_))); + base::RetainedRef(document_)))); } } @@ -441,7 +441,7 @@ void PrintJob::HoldUntilStopIsCalled() { } void PrintJob::Quit() { - base::MessageLoop::current()->Quit(); + base::MessageLoop::current()->QuitWhenIdle(); } // Takes settings_ ownership and will be deleted in the receiving thread. diff --git a/chromium_src/chrome/browser/printing/print_job.h b/chromium_src/chrome/browser/printing/print_job.h index 48daaa6cb971..5569f87679f7 100644 --- a/chromium_src/chrome/browser/printing/print_job.h +++ b/chromium_src/chrome/browser/printing/print_job.h @@ -5,7 +5,6 @@ #ifndef CHROME_BROWSER_PRINTING_PRINT_JOB_H_ #define CHROME_BROWSER_PRINTING_PRINT_JOB_H_ -#include "base/basictypes.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/message_loop/message_loop.h" @@ -99,7 +98,7 @@ class PrintJob : public PrintJobWorkerOwner, void OnPdfToEmfStarted(int page_count); void OnPdfToEmfPageConverted(int page_number, float scale_factor, - scoped_ptr emf); + std::unique_ptr emf); #endif // OS_WIN @@ -135,7 +134,7 @@ class PrintJob : public PrintJobWorkerOwner, // All the UI is done in a worker thread because many Win32 print functions // are blocking and enters a message loop without your consent. There is one // worker thread per print job. - scoped_ptr worker_; + std::unique_ptr worker_; // Cache of the print context settings for access in the UI thread. PrintSettings settings_; @@ -152,7 +151,7 @@ class PrintJob : public PrintJobWorkerOwner, #if defined(OS_WIN) class PdfToEmfState; - scoped_ptr ptd_to_emf_state_; + std::unique_ptr ptd_to_emf_state_; #endif // OS_WIN // Used at shutdown so that we can quit a nested message loop. diff --git a/chromium_src/chrome/browser/printing/print_job_worker.cc b/chromium_src/chrome/browser/printing/print_job_worker.cc index 889ad6d2523f..7a88a8570c42 100644 --- a/chromium_src/chrome/browser/printing/print_job_worker.cc +++ b/chromium_src/chrome/browser/printing/print_job_worker.cc @@ -146,7 +146,7 @@ void PrintJobWorker::GetSettings( } void PrintJobWorker::SetSettings( - scoped_ptr new_settings) { + std::unique_ptr new_settings) { DCHECK(task_runner_->RunsTasksOnCurrentThread()); BrowserThread::PostTask( @@ -160,7 +160,7 @@ void PrintJobWorker::SetSettings( } void PrintJobWorker::UpdatePrintSettings( - scoped_ptr new_settings) { + std::unique_ptr new_settings) { DCHECK_CURRENTLY_ON(BrowserThread::UI); PrintingContext::Result result = printing_context_->UpdatePrintSettings(*new_settings); @@ -338,11 +338,9 @@ void PrintJobWorker::OnDocumentDone() { } owner_->PostTask(FROM_HERE, - base::Bind(&NotificationCallback, - make_scoped_refptr(owner_), + base::Bind(&NotificationCallback, base::RetainedRef(owner_), JobEventDetails::DOC_DONE, - document_, - scoped_refptr())); + base::RetainedRef(document_), nullptr)); // Makes sure the variables are reinitialized. document_ = NULL; @@ -354,11 +352,9 @@ void PrintJobWorker::SpoolPage(PrintedPage* page) { // Signal everyone that the page is about to be printed. owner_->PostTask(FROM_HERE, - base::Bind(&NotificationCallback, - make_scoped_refptr(owner_), - JobEventDetails::NEW_PAGE, - document_, - make_scoped_refptr(page))); + base::Bind(&NotificationCallback, base::RetainedRef(owner_), + JobEventDetails::NEW_PAGE, base::RetainedRef(document_), + base::RetainedRef(page))); // Preprocess. if (printing_context_->NewPage() != PrintingContext::OK) { @@ -380,12 +376,11 @@ void PrintJobWorker::SpoolPage(PrintedPage* page) { } // Signal everyone that the page is printed. - owner_->PostTask(FROM_HERE, - base::Bind(&NotificationCallback, - make_scoped_refptr(owner_), - JobEventDetails::PAGE_DONE, - document_, - make_scoped_refptr(page))); + owner_->PostTask( + FROM_HERE, + base::Bind(&NotificationCallback, base::RetainedRef(owner_), + JobEventDetails::PAGE_DONE, base::RetainedRef(document_), + base::RetainedRef(page))); } void PrintJobWorker::OnFailure() { @@ -394,12 +389,11 @@ void PrintJobWorker::OnFailure() { // We may loose our last reference by broadcasting the FAILED event. scoped_refptr handle(owner_); - owner_->PostTask(FROM_HERE, - base::Bind(&NotificationCallback, - make_scoped_refptr(owner_), - JobEventDetails::FAILED, - document_, - scoped_refptr())); + owner_->PostTask( + FROM_HERE, + base::Bind(&NotificationCallback, base::RetainedRef(owner_), + JobEventDetails::FAILED, + base::RetainedRef(document_), nullptr)); Cancel(); // Makes sure the variables are reinitialized. diff --git a/chromium_src/chrome/browser/printing/print_job_worker.h b/chromium_src/chrome/browser/printing/print_job_worker.h index a8378bb2353d..6b15097b7af4 100644 --- a/chromium_src/chrome/browser/printing/print_job_worker.h +++ b/chromium_src/chrome/browser/printing/print_job_worker.h @@ -48,7 +48,7 @@ class PrintJobWorker { MarginType margin_type); // Set the new print settings. - void SetSettings(scoped_ptr new_settings); + void SetSettings(std::unique_ptr new_settings); // Starts the printing loop. Every pages are printed as soon as the data is // available. Makes sure the new_document is the right one. @@ -116,7 +116,7 @@ class PrintJobWorker { void GetSettingsWithUIDone(PrintingContext::Result result); // Called on the UI thread to update the print settings. - void UpdatePrintSettings(scoped_ptr new_settings); + void UpdatePrintSettings(std::unique_ptr new_settings); // Reports settings back to owner_. void GetSettingsDone(PrintingContext::Result result); @@ -127,10 +127,10 @@ class PrintJobWorker { void UseDefaultSettings(); // Printing context delegate. - scoped_ptr printing_context_delegate_; + std::unique_ptr printing_context_delegate_; // Information about the printer setting. - scoped_ptr printing_context_; + std::unique_ptr printing_context_; // The printed document. Only has read-only access. scoped_refptr document_; diff --git a/chromium_src/chrome/browser/printing/print_preview_message_handler.cc b/chromium_src/chrome/browser/printing/print_preview_message_handler.cc index 613f3f2343ce..d1f3660f8d2d 100644 --- a/chromium_src/chrome/browser/printing/print_preview_message_handler.cc +++ b/chromium_src/chrome/browser/printing/print_preview_message_handler.cc @@ -43,7 +43,7 @@ void StopWorker(int document_cookie) { char* CopyPDFDataOnIOThread( const PrintHostMsg_DidPreviewDocument_Params& params) { DCHECK_CURRENTLY_ON(BrowserThread::IO); - scoped_ptr shared_buf( + std::unique_ptr shared_buf( new base::SharedMemory(params.metafile_data_handle, true)); if (!shared_buf->Map(params.data_size)) return nullptr; @@ -118,7 +118,7 @@ void PrintPreviewMessageHandler::PrintToPDF( } void PrintPreviewMessageHandler::RunPrintToPDFCallback( - int request_id, uint32 data_size, char* data) { + int request_id, uint32_t data_size, char* data) { DCHECK_CURRENTLY_ON(BrowserThread::UI); v8::Isolate* isolate = v8::Isolate::GetCurrent(); diff --git a/chromium_src/chrome/browser/printing/print_preview_message_handler.h b/chromium_src/chrome/browser/printing/print_preview_message_handler.h index 453d78761bbe..1aac74baa229 100644 --- a/chromium_src/chrome/browser/printing/print_preview_message_handler.h +++ b/chromium_src/chrome/browser/printing/print_preview_message_handler.h @@ -47,7 +47,7 @@ class PrintPreviewMessageHandler const PrintHostMsg_DidPreviewDocument_Params& params); void OnPrintPreviewFailed(int document_cookie, int request_id); - void RunPrintToPDFCallback(int request_id, uint32 data_size, char* data); + void RunPrintToPDFCallback(int request_id, uint32_t data_size, char* data); PrintToPDFCallbackMap print_to_pdf_callback_map_; diff --git a/chromium_src/chrome/browser/printing/print_view_manager_base.cc b/chromium_src/chrome/browser/printing/print_view_manager_base.cc index ede1d3b8ba8c..2bbafdd8e55a 100644 --- a/chromium_src/chrome/browser/printing/print_view_manager_base.cc +++ b/chromium_src/chrome/browser/printing/print_view_manager_base.cc @@ -6,7 +6,7 @@ #include "base/bind.h" #include "base/memory/scoped_ptr.h" -#include "base/prefs/pref_service.h" +#include "components/prefs/pref_service.h" #include "base/strings/utf_string_conversions.h" #include "base/timer/timer.h" #include "chrome/browser/browser_process.h" @@ -132,7 +132,7 @@ void PrintViewManagerBase::OnDidPrintPage( } } - scoped_ptr metafile(new PdfMetafileSkia); + std::unique_ptr metafile(new PdfMetafileSkia); if (metafile_must_be_valid) { if (!metafile->InitFromData(shared_buf.memory(), params.data_size)) { NOTREACHED() << "Invalid metafile header"; @@ -144,7 +144,7 @@ void PrintViewManagerBase::OnDidPrintPage( #if !defined(OS_WIN) // Update the rendered document. It will send notifications to the listener. document->SetPage(params.page_number, - metafile.Pass(), + std::move(metafile), params.page_size, params.content_area); @@ -305,7 +305,7 @@ void PrintViewManagerBase::ShouldQuitFromInnerMessageLoop() { inside_inner_message_loop_) { // We are in a message loop created by RenderAllMissingPagesNow. Quit from // it. - base::MessageLoop::current()->Quit(); + base::MessageLoop::current()->QuitWhenIdle(); inside_inner_message_loop_ = false; } } @@ -411,9 +411,9 @@ bool PrintViewManagerBase::RunInnerMessageLoop() { // memory-bound. static const int kPrinterSettingsTimeout = 60000; base::OneShotTimer quit_timer; - quit_timer.Start(FROM_HERE, - TimeDelta::FromMilliseconds(kPrinterSettingsTimeout), - base::MessageLoop::current(), &base::MessageLoop::Quit); + quit_timer.Start( + FROM_HERE, TimeDelta::FromMilliseconds(kPrinterSettingsTimeout), + base::MessageLoop::current(), &base::MessageLoop::QuitWhenIdle); inside_inner_message_loop_ = true; diff --git a/chromium_src/chrome/browser/printing/print_view_manager_base.h b/chromium_src/chrome/browser/printing/print_view_manager_base.h index 0d44248bfe29..78e5729a5fb1 100644 --- a/chromium_src/chrome/browser/printing/print_view_manager_base.h +++ b/chromium_src/chrome/browser/printing/print_view_manager_base.h @@ -6,7 +6,7 @@ #define CHROME_BROWSER_PRINTING_PRINT_VIEW_MANAGER_BASE_H_ #include "base/memory/ref_counted.h" -#include "base/prefs/pref_member.h" +#include "components/prefs/pref_member.h" #include "base/strings/string16.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" diff --git a/chromium_src/chrome/browser/printing/printer_query.cc b/chromium_src/chrome/browser/printing/printer_query.cc index 6cd11ff067ef..72e2b85f635c 100644 --- a/chromium_src/chrome/browser/printing/printer_query.cc +++ b/chromium_src/chrome/browser/printing/printer_query.cc @@ -85,7 +85,7 @@ void PrinterQuery::GetSettings( margin_type)); } -void PrinterQuery::SetSettings(scoped_ptr new_settings, +void PrinterQuery::SetSettings(std::unique_ptr new_settings, const base::Closure& callback) { StartWorker(callback); diff --git a/chromium_src/chrome/browser/printing/printer_query.h b/chromium_src/chrome/browser/printing/printer_query.h index 9fd9a82b33ea..934c18592646 100644 --- a/chromium_src/chrome/browser/printing/printer_query.h +++ b/chromium_src/chrome/browser/printing/printer_query.h @@ -50,7 +50,7 @@ class PrinterQuery : public PrintJobWorkerOwner { const base::Closure& callback); // Updates the current settings with |new_settings| dictionary values. - void SetSettings(scoped_ptr new_settings, + void SetSettings(std::unique_ptr new_settings, const base::Closure& callback); // Stops the worker thread since the client is done with this object. @@ -73,7 +73,7 @@ class PrinterQuery : public PrintJobWorkerOwner { // All the UI is done in a worker thread because many Win32 print functions // are blocking and enters a message loop without your consent. There is one // worker thread per print job. - scoped_ptr worker_; + std::unique_ptr worker_; // Cache of the print context settings for access in the UI thread. PrintSettings settings_; diff --git a/chromium_src/chrome/browser/printing/printing_message_filter.cc b/chromium_src/chrome/browser/printing/printing_message_filter.cc index 6fd536ef68c3..819c6af5b020 100644 --- a/chromium_src/chrome/browser/printing/printing_message_filter.cc +++ b/chromium_src/chrome/browser/printing/printing_message_filter.cc @@ -71,10 +71,6 @@ void RenderParamsFromPrintSettings(const PrintSettings& settings, params->margin_top = settings.page_setup_device_units().content_area().y(); params->margin_left = settings.page_setup_device_units().content_area().x(); params->dpi = settings.dpi(); - // Currently hardcoded at 1.25. See PrintSettings' constructor. - params->min_shrink = settings.min_shrink(); - // Currently hardcoded at 2.0. See PrintSettings' constructor. - params->max_shrink = settings.max_shrink(); // Currently hardcoded at 72dpi. See PrintSettings' constructor. params->desired_dpi = settings.desired_dpi(); // Always use an invalid cookie. @@ -144,7 +140,7 @@ void PrintingMessageFilter::OnDuplicateSection( base::SharedMemoryHandle* browser_handle) { // Duplicate the handle in this process right now so the memory is kept alive // (even if it is not mapped) - base::SharedMemory shared_buf(renderer_handle, true, PeerHandle()); + base::SharedMemory shared_buf(renderer_handle, true); shared_buf.GiveToProcess(base::GetCurrentProcessHandle(), browser_handle); } #endif @@ -377,7 +373,7 @@ void PrintingMessageFilter::UpdateFileDescriptor(int render_view_id, int fd) { void PrintingMessageFilter::OnUpdatePrintSettings( int document_cookie, const base::DictionaryValue& job_settings, IPC::Message* reply_msg) { - scoped_ptr new_settings(job_settings.DeepCopy()); + std::unique_ptr new_settings(job_settings.DeepCopy()); scoped_refptr printer_query; printer_query = queue_->PopPrinterQuery(document_cookie); @@ -394,7 +390,7 @@ void PrintingMessageFilter::OnUpdatePrintSettings( printer_query = queue_->CreatePrinterQuery(host_id, routing_id); } printer_query->SetSettings( - new_settings.Pass(), + std::move(new_settings), base::Bind(&PrintingMessageFilter::OnUpdatePrintSettingsReply, this, printer_query, reply_msg)); } diff --git a/chromium_src/chrome/browser/printing/printing_message_filter.h b/chromium_src/chrome/browser/printing/printing_message_filter.h index 624b28fd35dd..e8536a69c5a3 100644 --- a/chromium_src/chrome/browser/printing/printing_message_filter.h +++ b/chromium_src/chrome/browser/printing/printing_message_filter.h @@ -107,7 +107,7 @@ class PrintingMessageFilter : public content::BrowserMessageFilter { #if defined(ENABLE_FULL_PRINTING) // Check to see if print preview has been cancelled. - void OnCheckForCancel(int32 preview_ui_id, + void OnCheckForCancel(int32_t preview_ui_id, int preview_request_id, bool* cancel); #endif diff --git a/chromium_src/chrome/browser/printing/printing_ui_web_contents_observer.h b/chromium_src/chrome/browser/printing/printing_ui_web_contents_observer.h index 66d51e7fba2b..de969f5cdb55 100644 --- a/chromium_src/chrome/browser/printing/printing_ui_web_contents_observer.h +++ b/chromium_src/chrome/browser/printing/printing_ui_web_contents_observer.h @@ -5,7 +5,6 @@ #ifndef CHROME_BROWSER_PRINTING_PRINTING_UI_WEB_CONTENTS_OBSERVER_H_ #define CHROME_BROWSER_PRINTING_PRINTING_UI_WEB_CONTENTS_OBSERVER_H_ -#include "base/basictypes.h" #include "content/public/browser/web_contents_observer.h" #include "ui/gfx/native_widget_types.h" diff --git a/chromium_src/chrome/browser/process_singleton.h b/chromium_src/chrome/browser/process_singleton.h index 3eeb53393e12..eab6c35479a3 100644 --- a/chromium_src/chrome/browser/process_singleton.h +++ b/chromium_src/chrome/browser/process_singleton.h @@ -12,7 +12,6 @@ #include #include -#include "base/basictypes.h" #include "base/callback.h" #include "base/command_line.h" #include "base/files/file_path.h" diff --git a/chromium_src/chrome/browser/process_singleton_posix.cc b/chromium_src/chrome/browser/process_singleton_posix.cc index 98fb948730e0..5742b3585205 100644 --- a/chromium_src/chrome/browser/process_singleton_posix.cc +++ b/chromium_src/chrome/browser/process_singleton_posix.cc @@ -54,7 +54,6 @@ #include "atom/common/atom_command_line.h" #include "base/base_paths.h" -#include "base/basictypes.h" #include "base/bind.h" #include "base/command_line.h" #include "base/files/file_path.h" @@ -75,11 +74,12 @@ #include "base/strings/stringprintf.h" #include "base/strings/sys_string_conversions.h" #include "base/strings/utf_string_conversions.h" +#include "base/thread_task_runner_handle.h" #include "base/threading/platform_thread.h" #include "base/time/time.h" #include "base/timer/timer.h" #include "content/public/browser/browser_thread.h" -#include "net/base/net_util.h" +#include "net/base/network_interfaces.h" #include "ui/base/l10n/l10n_util.h" #if defined(TOOLKIT_VIEWS) && defined(OS_LINUX) && !defined(OS_CHROMEOS) @@ -111,7 +111,7 @@ const base::FilePath::CharType kSingletonCookieFilename[] = const base::FilePath::CharType kSingletonLockFilename[] = FILE_PATH_LITERAL("SingletonLock"); const base::FilePath::CharType kSingletonSocketFilename[] = - FILE_PATH_LITERAL("SingletonSocket"); + FILE_PATH_LITERAL("SS"); // Set the close-on-exec bit on a file descriptor. // Returns 0 on success, -1 on failure. @@ -222,7 +222,7 @@ int SetupSocketOnly() { int sock = socket(PF_UNIX, SOCK_STREAM, 0); PCHECK(sock >= 0) << "socket() failed"; - int rv = net::SetNonBlocking(sock); + int rv = base::SetNonBlocking(sock); DCHECK_EQ(0, rv) << "Failed to make non-blocking socket."; rv = SetCloseOnExec(sock); DCHECK_EQ(0, rv) << "Failed to set CLOEXEC on socket."; @@ -577,7 +577,7 @@ void ProcessSingleton::LinuxWatcher::OnFileCanReadWithoutBlocking(int fd) { PLOG(ERROR) << "accept() failed"; return; } - int rv = net::SetNonBlocking(connection_socket); + int rv = base::SetNonBlocking(connection_socket); DCHECK_EQ(0, rv) << "Failed to make non-blocking socket."; SocketReader* reader = new SocketReader(this, ui_message_loop_, @@ -717,6 +717,9 @@ ProcessSingleton::ProcessSingleton( const NotificationCallback& notification_callback) : notification_callback_(notification_callback), current_pid_(base::GetCurrentProcId()) { + // The user_data_dir may have not been created yet. + base::CreateDirectoryAndGetError(user_data_dir, nullptr); + socket_path_ = user_data_dir.Append(kSingletonSocketFilename); lock_path_ = user_data_dir.Append(kSingletonLockFilename); cookie_path_ = user_data_dir.Append(kSingletonCookieFilename); @@ -943,6 +946,19 @@ bool ProcessSingleton::Create() { #endif } +#if defined(MAS_BUILD) + // For Mac App Store build, the tmp dir could be too long to fit + // addr->sun_path, so we need to make it as short as possible. + base::FilePath tmp_dir; + if (!base::GetTempDir(&tmp_dir)) { + LOG(ERROR) << "Failed to get temporary directory."; + return false; + } + if (!socket_dir_.Set(tmp_dir.Append("S"))) { + LOG(ERROR) << "Failed to set socket directory."; + return false; + } +#else // 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. @@ -950,6 +966,7 @@ bool ProcessSingleton::Create() { LOG(ERROR) << "Failed to create socket directory."; return false; } +#endif // Check that the directory was created with the correct permissions. int dir_mode = 0; @@ -990,8 +1007,8 @@ bool ProcessSingleton::Create() { // In Electron the ProcessSingleton is created earlier than the IO // thread gets created, so we have to postpone the call until message // loop is up an running. - scoped_refptr task_runner( - base::ThreadTaskRunnerHandle::Get()); + scoped_refptr task_runner = + base::ThreadTaskRunnerHandle::Get(); task_runner->PostTask( FROM_HERE, base::Bind(&ProcessSingleton::StartListening, diff --git a/chromium_src/chrome/browser/process_singleton_win.cc b/chromium_src/chrome/browser/process_singleton_win.cc index 14e53bec5fa7..b488ad457674 100644 --- a/chromium_src/chrome/browser/process_singleton_win.cc +++ b/chromium_src/chrome/browser/process_singleton_win.cc @@ -10,13 +10,13 @@ #include "base/bind.h" #include "base/command_line.h" #include "base/files/file_path.h" +#include "base/files/file_util.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" @@ -191,6 +191,8 @@ ProcessSingleton::ProcessSingleton( user_data_dir_(user_data_dir), should_kill_remote_process_callback_( base::Bind(&TerminateAppWithError)) { + // The user_data_dir may have not been created yet. + base::CreateDirectoryAndGetError(user_data_dir, nullptr); } ProcessSingleton::~ProcessSingleton() { diff --git a/chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.cc b/chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.cc index 98932a494bf8..5364aa0b881f 100644 --- a/chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.cc +++ b/chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.cc @@ -29,7 +29,7 @@ ChromeBrowserPepperHostFactory::ChromeBrowserPepperHostFactory( ChromeBrowserPepperHostFactory::~ChromeBrowserPepperHostFactory() {} -scoped_ptr ChromeBrowserPepperHostFactory::CreateResourceHost( +std::unique_ptr ChromeBrowserPepperHostFactory::CreateResourceHost( ppapi::host::PpapiHost* host, PP_Resource resource, PP_Instance instance, @@ -38,7 +38,7 @@ scoped_ptr ChromeBrowserPepperHostFactory::CreateResourceHost( // Make sure the plugin is giving us a valid instance for this resource. if (!host_->IsValidInstance(instance)) - return scoped_ptr(); + return std::unique_ptr(); // Private interfaces. if (host_->GetPpapiHost()->permissions().HasPermission( @@ -47,7 +47,7 @@ scoped_ptr ChromeBrowserPepperHostFactory::CreateResourceHost( case PpapiHostMsg_Broker_Create::ID: { scoped_refptr broker_filter( new PepperBrokerMessageFilter(instance, host_)); - return scoped_ptr(new MessageFilterHost( + return std::unique_ptr(new MessageFilterHost( host_->GetPpapiHost(), instance, resource, broker_filter)); } } @@ -58,16 +58,16 @@ scoped_ptr ChromeBrowserPepperHostFactory::CreateResourceHost( ppapi::PERMISSION_FLASH)) { switch (message.type()) { case PpapiHostMsg_Flash_Create::ID: - return scoped_ptr( + return std::unique_ptr( new PepperFlashBrowserHost(host_, instance, resource)); case PpapiHostMsg_FlashClipboard_Create::ID: { scoped_refptr clipboard_filter( new PepperFlashClipboardMessageFilter); - return scoped_ptr(new MessageFilterHost( + return std::unique_ptr(new MessageFilterHost( host_->GetPpapiHost(), instance, resource, clipboard_filter)); } case PpapiHostMsg_FlashDRM_Create::ID: - return scoped_ptr( + return std::unique_ptr( new chrome::PepperFlashDRMHost(host_, instance, resource)); } } @@ -82,12 +82,12 @@ scoped_ptr ChromeBrowserPepperHostFactory::CreateResourceHost( PepperIsolatedFileSystemMessageFilter* isolated_fs_filter = PepperIsolatedFileSystemMessageFilter::Create(instance, host_); if (!isolated_fs_filter) - return scoped_ptr(); - return scoped_ptr( + return std::unique_ptr(); + return std::unique_ptr( new MessageFilterHost(host, instance, resource, isolated_fs_filter)); } - return scoped_ptr(); + return std::unique_ptr(); } } // namespace chrome diff --git a/chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.h b/chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.h index b817953b54dc..84385140ceca 100644 --- a/chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.h +++ b/chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.h @@ -5,7 +5,7 @@ #ifndef CHROME_BROWSER_RENDERER_HOST_PEPPER_CHROME_BROWSER_PEPPER_HOST_FACTORY_H_ #define CHROME_BROWSER_RENDERER_HOST_PEPPER_CHROME_BROWSER_PEPPER_HOST_FACTORY_H_ -#include "base/compiler_specific.h" +#include "base/macros.h" #include "ppapi/host/host_factory.h" namespace content { @@ -20,7 +20,7 @@ class ChromeBrowserPepperHostFactory : public ppapi::host::HostFactory { explicit ChromeBrowserPepperHostFactory(content::BrowserPpapiHost* host); ~ChromeBrowserPepperHostFactory() override; - scoped_ptr CreateResourceHost( + std::unique_ptr CreateResourceHost( ppapi::host::PpapiHost* host, PP_Resource resource, PP_Instance instance, diff --git a/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_browser_host.h b/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_browser_host.h index 6fb4aced1819..40a03a1ff56f 100644 --- a/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_browser_host.h +++ b/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_browser_host.h @@ -5,7 +5,6 @@ #ifndef CHROME_BROWSER_RENDERER_HOST_PEPPER_PEPPER_FLASH_BROWSER_HOST_H_ #define CHROME_BROWSER_RENDERER_HOST_PEPPER_PEPPER_FLASH_BROWSER_HOST_H_ -#include "base/basictypes.h" #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" #include "ppapi/host/host_message_context.h" diff --git a/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.cc b/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.cc index fdc054f59fbd..4e66d772ec38 100644 --- a/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.cc +++ b/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.cc @@ -4,6 +4,8 @@ #include "chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.h" +#include + #include "base/pickle.h" #include "base/strings/utf_string_conversions.h" #include "content/public/browser/browser_thread.h" @@ -48,10 +50,10 @@ ui::ClipboardType ConvertClipboardType(uint32_t type) { // clipboard interface for custom data. bool JumpToFormatInPickle(const base::string16& format, base::PickleIterator* iter) { - size_t size = 0; - if (!iter->ReadSizeT(&size)) + uint32_t size = 0; + if (!iter->ReadUInt32(&size)) return false; - for (size_t i = 0; i < size; ++i) { + for (uint32_t i = 0; i < size; ++i) { base::string16 stored_format; if (!iter->ReadString16(&stored_format)) return false; @@ -83,7 +85,7 @@ std::string ReadDataFromPickle(const base::string16& format, bool WriteDataToPickle(const std::map& data, base::Pickle* pickle) { - pickle->WriteSizeT(data.size()); + pickle->WriteUInt32(data.size()); for (std::map::const_iterator it = data.begin(); it != data.end(); ++it) { @@ -240,8 +242,8 @@ int32_t PepperFlashClipboardMessageFilter::OnMsgReadData( base::string16 html; std::string url; - uint32 fragment_start; - uint32 fragment_end; + uint32_t fragment_start; + uint32_t fragment_end; clipboard->ReadHTML(type, &html, &url, &fragment_start, &fragment_end); result = PP_OK; clipboard_string = base::UTF16ToUTF8( diff --git a/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.h b/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.h index ff07eb73750c..4c146dd5daa0 100644 --- a/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.h +++ b/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.h @@ -8,8 +8,6 @@ #include #include -#include "base/basictypes.h" -#include "base/compiler_specific.h" #include "ppapi/host/resource_message_filter.h" #include "ppapi/shared_impl/flash_clipboard_format_registry.h" diff --git a/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_drm_host.cc b/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_drm_host.cc index f94216461769..34a7ff881f48 100644 --- a/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_drm_host.cc +++ b/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_drm_host.cc @@ -21,7 +21,7 @@ #include "content/public/browser/child_process_security_policy.h" #include "content/public/browser/render_frame_host.h" #include "content/public/common/pepper_plugin_info.h" -#include "net/base/net_util.h" +#include "net/base/network_interfaces.h" #include "ppapi/c/pp_errors.h" #include "ppapi/host/dispatch_host_message.h" #include "ppapi/host/host_message_context.h" diff --git a/chromium_src/chrome/browser/speech/tts_controller.h b/chromium_src/chrome/browser/speech/tts_controller.h index 3c40b9e5471d..f4dcd7747047 100644 --- a/chromium_src/chrome/browser/speech/tts_controller.h +++ b/chromium_src/chrome/browser/speech/tts_controller.h @@ -240,7 +240,7 @@ class Utterance { // The full options arg passed to tts.speak, which may include fields // other than the ones we explicitly parse, below. - scoped_ptr options_; + std::unique_ptr options_; // The extension ID of the extension that called speak() and should // receive events. diff --git a/chromium_src/chrome/browser/speech/tts_linux.cc b/chromium_src/chrome/browser/speech/tts_linux.cc index ba15516ce036..b1e8bc404336 100644 --- a/chromium_src/chrome/browser/speech/tts_linux.cc +++ b/chromium_src/chrome/browser/speech/tts_linux.cc @@ -81,7 +81,7 @@ class TtsPlatformImplLinux : public TtsPlatformImpl { // Map a string composed of a voicename and module to the voicename. Used to // uniquely identify a voice across all available modules. - scoped_ptr > all_native_voices_; + std::unique_ptr > all_native_voices_; friend struct base::DefaultSingletonTraits; diff --git a/chromium_src/chrome/browser/speech/tts_message_filter.cc b/chromium_src/chrome/browser/speech/tts_message_filter.cc index 66e4c40bd5eb..1a198d9e26e1 100644 --- a/chromium_src/chrome/browser/speech/tts_message_filter.cc +++ b/chromium_src/chrome/browser/speech/tts_message_filter.cc @@ -83,7 +83,7 @@ void TtsMessageFilter::OnInitializeVoiceList() { void TtsMessageFilter::OnSpeak(const TtsUtteranceRequest& request) { CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - scoped_ptr utterance(new Utterance(browser_context_)); + std::unique_ptr utterance(new Utterance(browser_context_)); utterance->set_src_id(request.id); utterance->set_text(request.text); utterance->set_lang(request.lang); diff --git a/chromium_src/chrome/browser/speech/tts_win.cc b/chromium_src/chrome/browser/speech/tts_win.cc index ac2582058894..bc9411a8c159 100644 --- a/chromium_src/chrome/browser/speech/tts_win.cc +++ b/chromium_src/chrome/browser/speech/tts_win.cc @@ -87,7 +87,7 @@ bool TtsPlatformImplWin::Speak( // 0.1 -> -10 // 1.0 -> 0 // 10.0 -> 10 - speech_synthesizer_->SetRate(static_cast(10 * log10(params.rate))); + speech_synthesizer_->SetRate(static_cast(10 * log10(params.rate))); } if (params.pitch >= 0.0) { @@ -102,7 +102,7 @@ bool TtsPlatformImplWin::Speak( if (params.volume >= 0.0) { // The TTS api allows a range of 0 to 100 for speech volume. - speech_synthesizer_->SetVolume(static_cast(params.volume * 100)); + speech_synthesizer_->SetVolume(static_cast(params.volume * 100)); } // TODO(dmazzoni): convert SSML to SAPI xml. http://crbug.com/88072 diff --git a/chromium_src/chrome/browser/ui/cocoa/color_chooser_mac.mm b/chromium_src/chrome/browser/ui/cocoa/color_chooser_mac.mm index cb366022d6e5..6183dd5d5bd0 100644 --- a/chromium_src/chrome/browser/ui/cocoa/color_chooser_mac.mm +++ b/chromium_src/chrome/browser/ui/cocoa/color_chooser_mac.mm @@ -74,7 +74,7 @@ ColorChooserMac::ColorChooserMac(content::WebContents* web_contents, SkColor initial_color) : web_contents_(web_contents) { panel_.reset([[ColorPanelCocoa alloc] initWithChooser:this]); - [panel_ setColor:gfx::SkColorToDeviceNSColor(initial_color)]; + [panel_ setColor:skia::SkColorToDeviceNSColor(initial_color)]; [[NSColorPanel sharedColorPanel] makeKeyAndOrderFront:nil]; } @@ -101,7 +101,7 @@ void ColorChooserMac::End() { } void ColorChooserMac::SetSelectedColor(SkColor color) { - [panel_ setColor:gfx::SkColorToDeviceNSColor(color)]; + [panel_ setColor:skia::SkColorToDeviceNSColor(color)]; } @implementation ColorPanelCocoa @@ -139,7 +139,7 @@ void ColorChooserMac::SetSelectedColor(SkColor color) { nonUserChange_ = NO; return; } - chooser_->DidChooseColorInColorPanel(gfx::NSDeviceColorToSkColor( + chooser_->DidChooseColorInColorPanel(skia::NSDeviceColorToSkColor( [[panel color] colorUsingColorSpaceName:NSDeviceRGBColorSpace])); nonUserChange_ = NO; } diff --git a/chromium_src/chrome/browser/ui/views/color_chooser_aura.h b/chromium_src/chrome/browser/ui/views/color_chooser_aura.h index 6394b973a3de..355f540b19d5 100644 --- a/chromium_src/chrome/browser/ui/views/color_chooser_aura.h +++ b/chromium_src/chrome/browser/ui/views/color_chooser_aura.h @@ -5,8 +5,7 @@ #ifndef CHROME_BROWSER_UI_VIEWS_COLOR_CHOOSER_AURA_H_ #define CHROME_BROWSER_UI_VIEWS_COLOR_CHOOSER_AURA_H_ -#include "base/basictypes.h" -#include "base/compiler_specific.h" +#include "base/macros.h" #include "content/public/browser/color_chooser.h" #include "ui/views/color_chooser/color_chooser_listener.h" diff --git a/chromium_src/chrome/browser/ui/views/color_chooser_win.cc b/chromium_src/chrome/browser/ui/views/color_chooser_win.cc index b62801399e88..7a4f75733339 100644 --- a/chromium_src/chrome/browser/ui/views/color_chooser_win.cc +++ b/chromium_src/chrome/browser/ui/views/color_chooser_win.cc @@ -9,8 +9,10 @@ #include "chrome/browser/ui/views/color_chooser_dialog.h" #include "content/public/browser/color_chooser.h" #include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/web_contents.h" +#include "ui/aura/window.h" #include "ui/views/color_chooser/color_chooser_listener.h" class ColorChooserWin : public content::ColorChooser, @@ -55,9 +57,11 @@ ColorChooserWin* ColorChooserWin::Open(content::WebContents* web_contents, ColorChooserWin::ColorChooserWin(content::WebContents* web_contents, SkColor initial_color) : web_contents_(web_contents) { - gfx::NativeWindow owning_window = (gfx::NativeWindow)::GetAncestor( - (HWND)web_contents->GetRenderViewHost()->GetView()->GetNativeView(), - GA_ROOT); + gfx::NativeWindow owning_window = web_contents->GetRenderViewHost() + ->GetWidget() + ->GetView() + ->GetNativeView() + ->GetToplevelWindow(); color_chooser_dialog_ = new ColorChooserDialog(this, initial_color, owning_window); diff --git a/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h b/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h index 694f776b24ef..95a08b229f47 100644 --- a/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h +++ b/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h @@ -9,7 +9,6 @@ #include -#include "base/basictypes.h" #include "base/memory/ref_counted.h" #include "base/memory/singleton.h" #include "ui/base/glib/glib_signal.h" diff --git a/chromium_src/chrome/common/chrome_paths_linux.cc b/chromium_src/chrome/common/chrome_paths_linux.cc index 91348fec4808..e89ae822d904 100644 --- a/chromium_src/chrome/common/chrome_paths_linux.cc +++ b/chromium_src/chrome/common/chrome_paths_linux.cc @@ -60,7 +60,7 @@ bool GetUserMediaDirectory(const std::string& xdg_name, // ~/.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()); + std::unique_ptr env(base::Environment::Create()); base::FilePath config_dir(GetXDGDirectory(env.get(), kXdgConfigHomeEnvVar, kDotConfigDir)); @@ -85,7 +85,7 @@ void GetUserCacheDirectory(const base::FilePath& profile_dir, // Default value in cases where any of the following fails. *result = profile_dir; - scoped_ptr env(base::Environment::Create()); + std::unique_ptr env(base::Environment::Create()); base::FilePath cache_dir; if (!PathService::Get(base::DIR_CACHE, &cache_dir)) diff --git a/chromium_src/chrome/common/chrome_paths_mac.mm b/chromium_src/chrome/common/chrome_paths_mac.mm index 4a4eb87dc254..bbfd6c9b23b0 100644 --- a/chromium_src/chrome/common/chrome_paths_mac.mm +++ b/chromium_src/chrome/common/chrome_paths_mac.mm @@ -13,7 +13,7 @@ #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/memory/free_deleter.h" #include "base/path_service.h" #include "chrome/common/chrome_constants.h" @@ -235,7 +235,7 @@ NSBundle* OuterAppBundle() { bool GetUserDataDirectoryForBrowserBundle(NSBundle* bundle, base::FilePath* result) { - scoped_ptr + std::unique_ptr product_dir_name(ProductDirNameForBundle(bundle)); return GetDefaultUserDataDirectoryForProduct(product_dir_name.get(), result); } diff --git a/chromium_src/chrome/common/chrome_paths_win.cc b/chromium_src/chrome/common/chrome_paths_win.cc index 37f4ec2b05bd..89c2ae48eaa2 100644 --- a/chromium_src/chrome/common/chrome_paths_win.cc +++ b/chromium_src/chrome/common/chrome_paths_win.cc @@ -12,7 +12,6 @@ #include "base/files/file_path.h" #include "base/path_service.h" -#include "base/win/metro.h" #include "base/win/scoped_co_mem.h" #include "chrome/common/chrome_constants.h" diff --git a/chromium_src/chrome/common/pref_names.cc b/chromium_src/chrome/common/pref_names.cc index 3e3a73b99837..23235cd1f4b0 100644 --- a/chromium_src/chrome/common/pref_names.cc +++ b/chromium_src/chrome/common/pref_names.cc @@ -8,5 +8,6 @@ namespace prefs { const char kSelectFileLastDirectory[] = "selectfile.last_directory"; const char kDownloadDefaultDirectory[] = "download.default_directory"; +const char kDevToolsFileSystemPaths[] = "devtools.file_system_paths"; } // namespace prefs diff --git a/chromium_src/chrome/common/pref_names.h b/chromium_src/chrome/common/pref_names.h index 542a2d2c733f..5101c720133d 100644 --- a/chromium_src/chrome/common/pref_names.h +++ b/chromium_src/chrome/common/pref_names.h @@ -8,5 +8,6 @@ namespace prefs { extern const char kSelectFileLastDirectory[]; extern const char kDownloadDefaultDirectory[]; +extern const char kDevToolsFileSystemPaths[]; } // namespace prefs diff --git a/chromium_src/chrome/common/print_messages.cc b/chromium_src/chrome/common/print_messages.cc index 5f8e30f3698a..b0ec282382d6 100644 --- a/chromium_src/chrome/common/print_messages.cc +++ b/chromium_src/chrome/common/print_messages.cc @@ -4,7 +4,6 @@ #include "chrome/common/print_messages.h" -#include "base/basictypes.h" #include "base/strings/string16.h" #include "ui/gfx/geometry/size.h" diff --git a/chromium_src/chrome/common/print_messages.h b/chromium_src/chrome/common/print_messages.h index cd775d0478b7..034691b5b958 100644 --- a/chromium_src/chrome/common/print_messages.h +++ b/chromium_src/chrome/common/print_messages.h @@ -161,7 +161,7 @@ IPC_STRUCT_BEGIN(PrintHostMsg_DidPrintPage_Params) IPC_STRUCT_MEMBER(base::SharedMemoryHandle, metafile_data_handle) // Size of the metafile data. - IPC_STRUCT_MEMBER(uint32, data_size) + IPC_STRUCT_MEMBER(uint32_t, data_size) // Cookie for the document to ensure correctness. IPC_STRUCT_MEMBER(int, document_cookie) @@ -193,7 +193,7 @@ IPC_STRUCT_BEGIN(PrintHostMsg_DidPreviewDocument_Params) IPC_STRUCT_MEMBER(base::SharedMemoryHandle, metafile_data_handle) // Size of metafile data. - IPC_STRUCT_MEMBER(uint32, data_size) + IPC_STRUCT_MEMBER(uint32_t, data_size) // Cookie for the document to ensure correctness. IPC_STRUCT_MEMBER(int, document_cookie) diff --git a/chromium_src/chrome/common/tts_utterance_request.h b/chromium_src/chrome/common/tts_utterance_request.h index e0b7adfa4a02..a4b4cab68ca3 100644 --- a/chromium_src/chrome/common/tts_utterance_request.h +++ b/chromium_src/chrome/common/tts_utterance_request.h @@ -7,7 +7,7 @@ #include -#include "base/basictypes.h" +#include "base/macros.h" #include "base/strings/string16.h" struct TtsUtteranceRequest { @@ -41,4 +41,4 @@ struct TtsUtteranceResponse { int id; }; -#endif // CHROME_COMMON_TTS_UTTERANCE_REQUEST_H_ \ No newline at end of file +#endif // CHROME_COMMON_TTS_UTTERANCE_REQUEST_H_ diff --git a/chromium_src/chrome/common/widevine_cdm_constants.cc b/chromium_src/chrome/common/widevine_cdm_constants.cc index 60f487e2ae8d..587966a9c368 100644 --- a/chromium_src/chrome/common/widevine_cdm_constants.cc +++ b/chromium_src/chrome/common/widevine_cdm_constants.cc @@ -12,5 +12,5 @@ const base::FilePath::CharType kWidevineCdmBaseDirectory[] = const char kWidevineCdmPluginExtension[] = ""; -const int32 kWidevineCdmPluginPermissions = ppapi::PERMISSION_DEV | - ppapi::PERMISSION_PRIVATE; +const int32_t kWidevineCdmPluginPermissions = ppapi::PERMISSION_DEV | + ppapi::PERMISSION_PRIVATE; diff --git a/chromium_src/chrome/common/widevine_cdm_constants.h b/chromium_src/chrome/common/widevine_cdm_constants.h index b626079a11b7..9597b1cb41fe 100644 --- a/chromium_src/chrome/common/widevine_cdm_constants.h +++ b/chromium_src/chrome/common/widevine_cdm_constants.h @@ -5,7 +5,7 @@ #ifndef CHROME_COMMON_WIDEVINE_CDM_CONSTANTS_H_ #define CHROME_COMMON_WIDEVINE_CDM_CONSTANTS_H_ -#include "base/basictypes.h" +#include "base/macros.h" #include "base/files/file_path.h" // The Widevine CDM adapter and Widevine CDM are in this directory. @@ -14,6 +14,6 @@ extern const base::FilePath::CharType kWidevineCdmBaseDirectory[]; extern const char kWidevineCdmPluginExtension[]; // Permission bits for Widevine CDM plugin. -extern const int32 kWidevineCdmPluginPermissions; +extern const int32_t kWidevineCdmPluginPermissions; #endif // CHROME_COMMON_WIDEVINE_CDM_CONSTANTS_H_ diff --git a/chromium_src/chrome/renderer/media/chrome_key_systems.cc b/chromium_src/chrome/renderer/media/chrome_key_systems.cc index 5d97169005b4..417a61fcdab9 100644 --- a/chromium_src/chrome/renderer/media/chrome_key_systems.cc +++ b/chromium_src/chrome/renderer/media/chrome_key_systems.cc @@ -124,7 +124,7 @@ static void AddPepperBasedWidevine( } cdm::AddWidevineWithCodecs( - cdm::WIDEVINE, supported_codecs, + supported_codecs, #if defined(OS_CHROMEOS) media::EmeRobustness::HW_SECURE_ALL, // Maximum audio robustness. media::EmeRobustness::HW_SECURE_ALL, // Maximim video robustness. diff --git a/chromium_src/chrome/renderer/pepper/chrome_renderer_pepper_host_factory.cc b/chromium_src/chrome/renderer/pepper/chrome_renderer_pepper_host_factory.cc index dd83b8191c06..bcf6debc4cf5 100644 --- a/chromium_src/chrome/renderer/pepper/chrome_renderer_pepper_host_factory.cc +++ b/chromium_src/chrome/renderer/pepper/chrome_renderer_pepper_host_factory.cc @@ -24,7 +24,7 @@ ChromeRendererPepperHostFactory::ChromeRendererPepperHostFactory( ChromeRendererPepperHostFactory::~ChromeRendererPepperHostFactory() {} -scoped_ptr ChromeRendererPepperHostFactory::CreateResourceHost( +std::unique_ptr ChromeRendererPepperHostFactory::CreateResourceHost( ppapi::host::PpapiHost* host, PP_Resource resource, PP_Instance instance, @@ -33,24 +33,24 @@ scoped_ptr ChromeRendererPepperHostFactory::CreateResourceHost( // Make sure the plugin is giving us a valid instance for this resource. if (!host_->IsValidInstance(instance)) - return scoped_ptr(); + return std::unique_ptr(); if (host_->GetPpapiHost()->permissions().HasPermission( ppapi::PERMISSION_FLASH)) { switch (message.type()) { case PpapiHostMsg_Flash_Create::ID: { - return scoped_ptr( + return std::unique_ptr( new PepperFlashRendererHost(host_, instance, resource)); } case PpapiHostMsg_FlashFullscreen_Create::ID: { - return scoped_ptr( + return std::unique_ptr( new PepperFlashFullscreenHost(host_, instance, resource)); } case PpapiHostMsg_FlashMenu_Create::ID: { ppapi::proxy::SerializedFlashMenu serialized_menu; if (ppapi::UnpackMessage( message, &serialized_menu)) { - return scoped_ptr(new PepperFlashMenuHost( + return std::unique_ptr(new PepperFlashMenuHost( host_, instance, resource, serialized_menu)); } break; @@ -71,7 +71,7 @@ scoped_ptr ChromeRendererPepperHostFactory::CreateResourceHost( PP_PrivateFontCharset charset; if (ppapi::UnpackMessage( message, &description, &charset)) { - return scoped_ptr(new PepperFlashFontFileHost( + return std::unique_ptr(new PepperFlashFontFileHost( host_, instance, resource, description, charset)); } break; @@ -79,5 +79,5 @@ scoped_ptr ChromeRendererPepperHostFactory::CreateResourceHost( } } - return scoped_ptr(); + return std::unique_ptr(); } diff --git a/chromium_src/chrome/renderer/pepper/chrome_renderer_pepper_host_factory.h b/chromium_src/chrome/renderer/pepper/chrome_renderer_pepper_host_factory.h index 13ab2853a356..c52c73b56f04 100644 --- a/chromium_src/chrome/renderer/pepper/chrome_renderer_pepper_host_factory.h +++ b/chromium_src/chrome/renderer/pepper/chrome_renderer_pepper_host_factory.h @@ -5,8 +5,7 @@ #ifndef CHROME_RENDERER_PEPPER_CHROME_RENDERER_PEPPER_HOST_FACTORY_H_ #define CHROME_RENDERER_PEPPER_CHROME_RENDERER_PEPPER_HOST_FACTORY_H_ -#include "base/basictypes.h" -#include "base/compiler_specific.h" +#include "base/macros.h" #include "ppapi/host/host_factory.h" namespace content { @@ -19,7 +18,7 @@ class ChromeRendererPepperHostFactory : public ppapi::host::HostFactory { ~ChromeRendererPepperHostFactory() override; // HostFactory. - scoped_ptr CreateResourceHost( + std::unique_ptr CreateResourceHost( ppapi::host::PpapiHost* host, PP_Resource resource, PP_Instance instance, diff --git a/chromium_src/chrome/renderer/pepper/pepper_flash_font_file_host.h b/chromium_src/chrome/renderer/pepper/pepper_flash_font_file_host.h index 02bb30f315fd..eeaa7209b5f0 100644 --- a/chromium_src/chrome/renderer/pepper/pepper_flash_font_file_host.h +++ b/chromium_src/chrome/renderer/pepper/pepper_flash_font_file_host.h @@ -5,8 +5,6 @@ #ifndef CHROME_RENDERER_PEPPER_PEPPER_FLASH_FONT_FILE_HOST_H_ #define CHROME_RENDERER_PEPPER_PEPPER_FLASH_FONT_FILE_HOST_H_ -#include "base/basictypes.h" -#include "base/compiler_specific.h" #include "ppapi/c/private/pp_private_font_charset.h" #include "ppapi/host/resource_host.h" diff --git a/chromium_src/chrome/renderer/pepper/pepper_flash_fullscreen_host.h b/chromium_src/chrome/renderer/pepper/pepper_flash_fullscreen_host.h index 3550ea136631..86d0af73aee5 100644 --- a/chromium_src/chrome/renderer/pepper/pepper_flash_fullscreen_host.h +++ b/chromium_src/chrome/renderer/pepper/pepper_flash_fullscreen_host.h @@ -5,8 +5,6 @@ #ifndef CHROME_RENDERER_PEPPER_PEPPER_FLASH_FULLSCREEN_HOST_H_ #define CHROME_RENDERER_PEPPER_PEPPER_FLASH_FULLSCREEN_HOST_H_ -#include "base/basictypes.h" -#include "base/compiler_specific.h" #include "ppapi/host/resource_host.h" namespace content { diff --git a/chromium_src/chrome/renderer/pepper/pepper_flash_renderer_host.cc b/chromium_src/chrome/renderer/pepper/pepper_flash_renderer_host.cc index 66edd3f938ad..59046b328bfe 100644 --- a/chromium_src/chrome/renderer/pepper/pepper_flash_renderer_host.cc +++ b/chromium_src/chrome/renderer/pepper/pepper_flash_renderer_host.cc @@ -205,7 +205,7 @@ int32_t PepperFlashRendererHost::OnDrawGlyphs( style |= SkTypeface::kBold; if (params.font_desc.italic) style |= SkTypeface::kItalic; - skia::RefPtr typeface = skia::AdoptRef(SkTypeface::CreateFromName( + sk_sp typeface(SkTypeface::CreateFromName( params.font_desc.face.c_str(), static_cast(style))); if (!typeface) return PP_ERROR_FAILED; @@ -255,7 +255,7 @@ int32_t PepperFlashRendererHost::OnDrawGlyphs( paint.setAntiAlias(true); paint.setHinting(SkPaint::kFull_Hinting); paint.setTextSize(SkIntToScalar(params.font_desc.size)); - paint.setTypeface(typeface.get()); // Takes a ref and manages lifetime. + paint.setTypeface(std::move(typeface)); if (params.allow_subpixel_aa) { paint.setSubpixelText(true); paint.setLCDRenderText(true); diff --git a/chromium_src/chrome/renderer/pepper/pepper_flash_renderer_host.h b/chromium_src/chrome/renderer/pepper/pepper_flash_renderer_host.h index de22f46045a5..f37907a8655e 100644 --- a/chromium_src/chrome/renderer/pepper/pepper_flash_renderer_host.h +++ b/chromium_src/chrome/renderer/pepper/pepper_flash_renderer_host.h @@ -8,7 +8,6 @@ #include #include -#include "base/basictypes.h" #include "base/memory/weak_ptr.h" #include "ppapi/host/host_message_context.h" #include "ppapi/host/resource_host.h" diff --git a/chromium_src/chrome/renderer/pepper/pepper_helper.cc b/chromium_src/chrome/renderer/pepper/pepper_helper.cc index a610a30dfff5..c9700c997137 100644 --- a/chromium_src/chrome/renderer/pepper/pepper_helper.cc +++ b/chromium_src/chrome/renderer/pepper/pepper_helper.cc @@ -18,9 +18,9 @@ void PepperHelper::DidCreatePepperPlugin(content::RendererPpapiHost* host) { // TODO(brettw) figure out how to hook up the host factory. It needs some // kind of filter-like system to allow dynamic additions. host->GetPpapiHost()->AddHostFactoryFilter( - scoped_ptr( + std::unique_ptr( new ChromeRendererPepperHostFactory(host))); host->GetPpapiHost()->AddInstanceMessageFilter( - scoped_ptr( + std::unique_ptr( new PepperSharedMemoryMessageFilter(host))); } diff --git a/chromium_src/chrome/renderer/pepper/pepper_shared_memory_message_filter.cc b/chromium_src/chrome/renderer/pepper/pepper_shared_memory_message_filter.cc index 3ef6dff0c8bc..7c219f60b814 100644 --- a/chromium_src/chrome/renderer/pepper/pepper_shared_memory_message_filter.cc +++ b/chromium_src/chrome/renderer/pepper/pepper_shared_memory_message_filter.cc @@ -43,9 +43,8 @@ void PepperSharedMemoryMessageFilter::OnHostMsgCreateSharedMemory( ppapi::proxy::SerializedHandle* plugin_handle) { plugin_handle->set_null_shmem(); *host_handle_id = -1; - scoped_ptr shm(content::RenderThread::Get() - ->HostAllocateSharedMemoryBuffer(size) - .Pass()); + std::unique_ptr shm( + content::RenderThread::Get()->HostAllocateSharedMemoryBuffer(size)); if (!shm.get()) return; diff --git a/chromium_src/chrome/renderer/pepper/pepper_shared_memory_message_filter.h b/chromium_src/chrome/renderer/pepper/pepper_shared_memory_message_filter.h index d7e0934cd6ec..860e1c9dbd15 100644 --- a/chromium_src/chrome/renderer/pepper/pepper_shared_memory_message_filter.h +++ b/chromium_src/chrome/renderer/pepper/pepper_shared_memory_message_filter.h @@ -5,8 +5,6 @@ #ifndef CHROME_RENDERER_PEPPER_PEPPER_SHARED_MEMORY_MESSAGE_FILTER_H_ #define CHROME_RENDERER_PEPPER_PEPPER_SHARED_MEMORY_MESSAGE_FILTER_H_ -#include "base/basictypes.h" -#include "base/compiler_specific.h" #include "ppapi/c/pp_instance.h" #include "ppapi/host/instance_message_filter.h" diff --git a/chromium_src/chrome/renderer/printing/print_web_view_helper.cc b/chromium_src/chrome/renderer/printing/print_web_view_helper.cc index 3bfe719a0c92..7ff3471f953a 100644 --- a/chromium_src/chrome/renderer/printing/print_web_view_helper.cc +++ b/chromium_src/chrome/renderer/printing/print_web_view_helper.cc @@ -39,6 +39,7 @@ #include "third_party/WebKit/public/web/WebSettings.h" #include "third_party/WebKit/public/web/WebView.h" #include "third_party/WebKit/public/web/WebViewClient.h" +#include "third_party/skia/include/core/SkCanvas.h" #include "ui/base/resource/resource_bundle.h" using content::WebPreferences; @@ -62,9 +63,9 @@ int GetDPI(const PrintMsg_Print_Params* print_params) { bool PrintMsg_Print_Params_IsValid(const PrintMsg_Print_Params& params) { return !params.content_size.IsEmpty() && !params.page_size.IsEmpty() && !params.printable_area.IsEmpty() && params.document_cookie && - params.desired_dpi && params.max_shrink && params.min_shrink && - params.dpi && (params.margin_top >= 0) && (params.margin_left >= 0) && - params.dpi > kMinDpi && params.document_cookie != 0; + params.desired_dpi && params.dpi && params.margin_top >= 0 && + params.margin_left >= 0 && params.dpi > kMinDpi && + params.document_cookie != 0; } PrintMsg_Print_Params GetCssPrintParams( @@ -386,7 +387,7 @@ class PrepareFrameAndViewForPrint : public blink::WebViewClient, blink::WebLocalFrame* frame, const blink::WebNode& node, bool ignore_css_margins); - virtual ~PrepareFrameAndViewForPrint(); + ~PrepareFrameAndViewForPrint() override; // Optional. Replaces |frame_| with selection if needed. Will call |on_ready| // when completed. @@ -415,22 +416,20 @@ class PrepareFrameAndViewForPrint : public blink::WebViewClient, return owns_web_view_ && frame() && frame()->isLoading(); } - // TODO(ojan): Remove this override and have this class use a non-null - // layerTreeView. - // blink::WebViewClient override: - virtual bool allowsBrokenNullLayerTreeView() const; - protected: // blink::WebViewClient override: - virtual void didStopLoading(); + void didStopLoading() override; + bool allowsBrokenNullLayerTreeView() const override; - // blink::WebFrameClient override: - virtual blink::WebFrame* createChildFrame( + // blink::WebFrameClient: + blink::WebFrame* createChildFrame( blink::WebLocalFrame* parent, blink::WebTreeScopeType scope, const blink::WebString& name, - blink::WebSandboxFlags sandboxFlags); - virtual void frameDetached(blink::WebFrame* frame, DetachType type); + const blink::WebString& unique_name, + blink::WebSandboxFlags sandbox_flags, + const blink::WebFrameOwnerProperties& frame_owner_properties) override; + void frameDetached(blink::WebFrame* frame, DetachType type) override; private: void CallOnReady(); @@ -494,6 +493,7 @@ void PrepareFrameAndViewForPrint::ResizeForPrinting() { // think the page is 125% larger so the size of the page is correct for // minimum (default) scaling. // This is important for sites that try to fill the page. + // The 1.25 value is |printingMinimumShrinkFactor|. gfx::Size print_layout_size(web_print_params_.printContentArea.width, web_print_params_.printContentArea.height); print_layout_size.set_height( @@ -576,7 +576,9 @@ blink::WebFrame* PrepareFrameAndViewForPrint::createChildFrame( blink::WebLocalFrame* parent, blink::WebTreeScopeType scope, const blink::WebString& name, - blink::WebSandboxFlags sandboxFlags) { + const blink::WebString& unique_name, + blink::WebSandboxFlags sandbox_flags, + const blink::WebFrameOwnerProperties& frame_owner_properties) { blink::WebFrame* frame = blink::WebLocalFrame::create(scope, this); parent->appendChild(frame); return frame; @@ -812,13 +814,19 @@ bool PrintWebViewHelper::FinalizePrintReadyDocument() { DCHECK(!is_print_ready_metafile_sent_); print_preview_context_.FinalizePrintReadyDocument(); - // Get the size of the resulting metafile. PdfMetafileSkia* metafile = print_preview_context_.metafile(); - uint32 buf_size = metafile->GetDataSize(); - DCHECK_GT(buf_size, 0u); PrintHostMsg_DidPreviewDocument_Params preview_params; - preview_params.data_size = buf_size; + + // Ask the browser to create the shared memory for us. + if (!CopyMetafileDataToSharedMem(*metafile, + &(preview_params.metafile_data_handle))) { + LOG(ERROR) << "CopyMetafileDataToSharedMem failed"; + print_preview_context_.set_error(PREVIEW_ERROR_METAFILE_COPY_FAILED); + return false; + } + + preview_params.data_size = metafile->GetDataSize(); preview_params.document_cookie = print_pages_params_->params.document_cookie; preview_params.expected_pages_count = print_preview_context_.total_page_count(); @@ -826,13 +834,6 @@ bool PrintWebViewHelper::FinalizePrintReadyDocument() { preview_params.preview_request_id = print_pages_params_->params.preview_request_id; - // Ask the browser to create the shared memory for us. - if (!CopyMetafileDataToSharedMem(metafile, - &(preview_params.metafile_data_handle))) { - LOG(ERROR) << "CopyMetafileDataToSharedMem failed"; - print_preview_context_.set_error(PREVIEW_ERROR_METAFILE_COPY_FAILED); - return false; - } is_print_ready_metafile_sent_ = true; Send(new PrintHostMsg_MetafileReadyForPrinting(routing_id(), preview_params)); @@ -1162,21 +1163,25 @@ bool PrintWebViewHelper::RenderPagesForPrint(blink::WebLocalFrame* frame, #if defined(OS_POSIX) bool PrintWebViewHelper::CopyMetafileDataToSharedMem( - PdfMetafileSkia* metafile, + const PdfMetafileSkia& metafile, base::SharedMemoryHandle* shared_mem_handle) { - uint32 buf_size = metafile->GetDataSize(); - scoped_ptr shared_buf( - content::RenderThread::Get()->HostAllocateSharedMemoryBuffer( - buf_size).release()); + uint32_t buf_size = metafile.GetDataSize(); + if (buf_size == 0) + return false; - if (shared_buf) { - if (shared_buf->Map(buf_size)) { - metafile->GetData(shared_buf->memory(), buf_size); - return shared_buf->GiveToProcess(base::GetCurrentProcessHandle(), - shared_mem_handle); - } - } - return false; + std::unique_ptr shared_buf( + content::RenderThread::Get()->HostAllocateSharedMemoryBuffer(buf_size)); + if (!shared_buf) + return false; + + if (!shared_buf->Map(buf_size)) + return false; + + if (!metafile.GetData(shared_buf->memory(), buf_size)) + return false; + + return shared_buf->GiveToProcess(base::GetCurrentProcessHandle(), + shared_mem_handle); } #endif // defined(OS_POSIX) @@ -1262,11 +1267,7 @@ bool PrintWebViewHelper::PrintPreviewContext::CreatePreviewDocument( } metafile_.reset(new PdfMetafileSkia); - if (!metafile_->Init()) { - set_error(PREVIEW_ERROR_METAFILE_INIT_FAILED); - LOG(ERROR) << "PdfMetafileSkia Init failed"; - return false; - } + CHECK(metafile_->Init()); current_page_index_ = 0; pages_to_render_ = pages; diff --git a/chromium_src/chrome/renderer/printing/print_web_view_helper.h b/chromium_src/chrome/renderer/printing/print_web_view_helper.h index bfe9cb612d16..e3e644880974 100644 --- a/chromium_src/chrome/renderer/printing/print_web_view_helper.h +++ b/chromium_src/chrome/renderer/printing/print_web_view_helper.h @@ -83,9 +83,9 @@ class PrintWebViewHelper PREVIEW_ERROR_NONE, // Always first. PREVIEW_ERROR_BAD_SETTING, PREVIEW_ERROR_METAFILE_COPY_FAILED, - PREVIEW_ERROR_METAFILE_INIT_FAILED, + PREVIEW_ERROR_METAFILE_INIT_FAILED_DEPRECATED, PREVIEW_ERROR_ZERO_PAGES, - PREVIEW_ERROR_MAC_DRAFT_METAFILE_INIT_FAILED, + PREVIEW_ERROR_MAC_DRAFT_METAFILE_INIT_FAILED_DEPRECATED, PREVIEW_ERROR_PAGE_RENDERED_WITHOUT_METAFILE, PREVIEW_ERROR_INVALID_PRINTER_SETTINGS, PREVIEW_ERROR_LAST_ENUM // Always last. @@ -218,7 +218,7 @@ class PrintWebViewHelper // Helper methods ----------------------------------------------------------- - bool CopyMetafileDataToSharedMem(PdfMetafileSkia* metafile, + bool CopyMetafileDataToSharedMem(const PdfMetafileSkia& metafile, base::SharedMemoryHandle* shared_mem_handle); // Helper method to get page layout in points and fit to page if needed. @@ -244,10 +244,10 @@ class PrintWebViewHelper void SetPrintPagesParams(const PrintMsg_PrintPages_Params& settings); // WebView used only to print the selection. - scoped_ptr prep_frame_view_; + std::unique_ptr prep_frame_view_; bool reset_prep_frame_view_; - scoped_ptr print_pages_params_; + std::unique_ptr print_pages_params_; bool is_print_ready_metafile_sent_; bool ignore_css_margins_; @@ -343,8 +343,8 @@ class PrintWebViewHelper FrameReference source_frame_; blink::WebNode source_node_; - scoped_ptr prep_frame_view_; - scoped_ptr metafile_; + std::unique_ptr prep_frame_view_; + std::unique_ptr metafile_; // Total page count in the renderer. int total_page_count_; diff --git a/chromium_src/chrome/renderer/printing/print_web_view_helper_linux.cc b/chromium_src/chrome/renderer/printing/print_web_view_helper_linux.cc index 82d7779d0266..608f406e623f 100644 --- a/chromium_src/chrome/renderer/printing/print_web_view_helper_linux.cc +++ b/chromium_src/chrome/renderer/printing/print_web_view_helper_linux.cc @@ -11,7 +11,6 @@ #include "printing/metafile_skia_wrapper.h" #include "printing/page_size_margins.h" #include "printing/pdf_metafile_skia.h" -#include "skia/ext/platform_device.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" #if !defined(OS_CHROMEOS) && !defined(OS_ANDROID) @@ -30,7 +29,7 @@ bool PrintWebViewHelper::RenderPreviewPage( PrintMsg_PrintPage_Params page_params; page_params.params = print_params; page_params.page_number = page_number; - scoped_ptr draft_metafile; + std::unique_ptr draft_metafile; PdfMetafileSkia* initial_render_metafile = print_preview_context_.metafile(); if (print_preview_context_.IsModifiable() && is_print_ready_metafile_sent_) { draft_metafile.reset(new PdfMetafileSkia); @@ -91,49 +90,15 @@ bool PrintWebViewHelper::PrintPagesNative(blink::WebFrame* frame, metafile.FinishDocument(); - // Get the size of the resulting metafile. - uint32 buf_size = metafile.GetDataSize(); - DCHECK_GT(buf_size, 0u); - -#if defined(OS_CHROMEOS) || defined(OS_ANDROID) - int sequence_number = -1; - base::FileDescriptor fd; - - // Ask the browser to open a file for us. - Send(new PrintHostMsg_AllocateTempFileForPrinting(routing_id(), - &fd, - &sequence_number)); - if (!metafile.SaveToFD(fd)) - return false; - - // Tell the browser we've finished writing the file. - Send(new PrintHostMsg_TempFileForPrintingWritten(routing_id(), - sequence_number)); - return true; -#else PrintHostMsg_DidPrintPage_Params printed_page_params; - printed_page_params.data_size = 0; - printed_page_params.document_cookie = params.params.document_cookie; - - { - scoped_ptr shared_mem( - content::RenderThread::Get()->HostAllocateSharedMemoryBuffer( - buf_size).release()); - if (!shared_mem.get()) { - NOTREACHED() << "AllocateSharedMemoryBuffer failed"; - return false; - } - - if (!shared_mem->Map(buf_size)) { - NOTREACHED() << "Map failed"; - return false; - } - metafile.GetData(shared_mem->memory(), buf_size); - printed_page_params.data_size = buf_size; - shared_mem->GiveToProcess(base::GetCurrentProcessHandle(), - &(printed_page_params.metafile_data_handle)); + if (!CopyMetafileDataToSharedMem( + metafile, &printed_page_params.metafile_data_handle)) { + return false; } + printed_page_params.data_size = metafile.GetDataSize(); + printed_page_params.document_cookie = params.params.document_cookie; + for (size_t i = 0; i < printed_pages.size(); ++i) { printed_page_params.page_number = printed_pages[i]; Send(new PrintHostMsg_DidPrintPage(routing_id(), printed_page_params)); @@ -141,7 +106,6 @@ bool PrintWebViewHelper::PrintPagesNative(blink::WebFrame* frame, printed_page_params.metafile_data_handle.fd = -1; } return true; -#endif // defined(OS_CHROMEOS) } void PrintWebViewHelper::PrintPageInternal( @@ -159,13 +123,12 @@ void PrintWebViewHelper::PrintPageInternal( &content_area); gfx::Rect canvas_area = content_area; - skia::PlatformCanvas* canvas = metafile->GetVectorCanvasForNewPage( + SkCanvas* canvas = metafile->GetVectorCanvasForNewPage( page_size, canvas_area, scale_factor); if (!canvas) return; MetafileSkiaWrapper::SetMetafileOnCanvas(*canvas, metafile); - skia::SetIsDraftMode(*canvas, is_print_ready_metafile_sent_); RenderPageContent(frame, params.page_number, canvas_area, content_area, scale_factor, canvas); diff --git a/chromium_src/chrome/renderer/printing/print_web_view_helper_mac.mm b/chromium_src/chrome/renderer/printing/print_web_view_helper_mac.mm index 0785e30a9cfb..36ba4a597cdd 100644 --- a/chromium_src/chrome/renderer/printing/print_web_view_helper_mac.mm +++ b/chromium_src/chrome/renderer/printing/print_web_view_helper_mac.mm @@ -12,9 +12,9 @@ #include "chrome/common/print_messages.h" #include "printing/metafile_skia_wrapper.h" #include "printing/page_size_margins.h" -#include "skia/ext/platform_device.h" #include "third_party/WebKit/public/platform/WebCanvas.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" +#include "third_party/skia/include/core/SkCanvas.h" namespace printing { @@ -24,8 +24,7 @@ void PrintWebViewHelper::PrintPageInternal( const PrintMsg_PrintPage_Params& params, WebFrame* frame) { PdfMetafileSkia metafile; - if (!metafile.Init()) - return; + CHECK(metafile.Init()); int page_number = params.page_number; gfx::Size page_size_in_dpi; @@ -42,8 +41,9 @@ void PrintWebViewHelper::PrintPageInternal( page_params.content_area = content_area_in_dpi; // Ask the browser to create the shared memory for us. - if (!CopyMetafileDataToSharedMem(&metafile, + if (!CopyMetafileDataToSharedMem(metafile, &(page_params.metafile_data_handle))) { + // TODO(thestig): Fail and return false instead. page_params.data_size = 0; } @@ -54,7 +54,7 @@ bool PrintWebViewHelper::RenderPreviewPage( int page_number, const PrintMsg_Print_Params& print_params) { PrintMsg_Print_Params printParams = print_params; - scoped_ptr draft_metafile; + std::unique_ptr draft_metafile; PdfMetafileSkia* initial_render_metafile = print_preview_context_.metafile(); bool render_to_draft = print_preview_context_.IsModifiable() && @@ -62,12 +62,7 @@ bool PrintWebViewHelper::RenderPreviewPage( if (render_to_draft) { draft_metafile.reset(new PdfMetafileSkia()); - if (!draft_metafile->Init()) { - print_preview_context_.set_error( - PREVIEW_ERROR_MAC_DRAFT_METAFILE_INIT_FAILED); - LOG(ERROR) << "Draft PdfMetafileSkia Init failed"; - return false; - } + CHECK(draft_metafile->Init()); initial_render_metafile = draft_metafile.get(); } @@ -116,15 +111,13 @@ void PrintWebViewHelper::RenderPage(const PrintMsg_Print_Params& params, gfx::Rect canvas_area = content_area; { - skia::PlatformCanvas* canvas = metafile->GetVectorCanvasForNewPage( + SkCanvas* canvas = metafile->GetVectorCanvasForNewPage( *page_size, canvas_area, scale_factor); if (!canvas) return; MetafileSkiaWrapper::SetMetafileOnCanvas(*canvas, metafile); - skia::SetIsDraftMode(*canvas, is_print_ready_metafile_sent_); skia::SetIsPreviewMetafile(*canvas, is_preview); - RenderPageContent(frame, page_number, canvas_area, content_area, scale_factor, static_cast(canvas)); } diff --git a/chromium_src/chrome/renderer/printing/print_web_view_helper_pdf_win.cc b/chromium_src/chrome/renderer/printing/print_web_view_helper_pdf_win.cc index 0b21de46995b..7ff17a388bcc 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 @@ -27,7 +27,7 @@ bool PrintWebViewHelper::RenderPreviewPage( PrintMsg_PrintPage_Params page_params; page_params.params = print_params; page_params.page_number = page_number; - scoped_ptr draft_metafile; + std::unique_ptr draft_metafile; PdfMetafileSkia* initial_render_metafile = print_preview_context_.metafile(); if (print_preview_context_.IsModifiable() && is_print_ready_metafile_sent_) { draft_metafile.reset(new PdfMetafileSkia); @@ -95,40 +95,16 @@ bool PrintWebViewHelper::PrintPagesNative(blink::WebFrame* frame, metafile.FinishDocument(); - // Get the size of the resulting metafile. - uint32 buf_size = metafile.GetDataSize(); - DCHECK_GT(buf_size, 0u); - PrintHostMsg_DidPrintPage_Params printed_page_params; - printed_page_params.data_size = 0; + if (!CopyMetafileDataToSharedMem( + metafile, &printed_page_params.metafile_data_handle)) { + return false; + } + + printed_page_params.content_area = params.params.printable_area; + printed_page_params.data_size = metafile.GetDataSize(); printed_page_params.document_cookie = params.params.document_cookie; printed_page_params.page_size = params.params.page_size; - printed_page_params.content_area = params.params.printable_area; - - { - base::SharedMemory shared_buf; - // Allocate a shared memory buffer to hold the generated metafile data. - if (!shared_buf.CreateAndMapAnonymous(buf_size)) { - NOTREACHED() << "Buffer allocation failed"; - return false; - } - - // Copy the bits into shared memory. - if (!metafile.GetData(shared_buf.memory(), buf_size)) { - NOTREACHED() << "GetData() failed"; - shared_buf.Unmap(); - return false; - } - shared_buf.GiveToProcess(base::GetCurrentProcessHandle(), - &printed_page_params.metafile_data_handle); - shared_buf.Unmap(); - - printed_page_params.data_size = buf_size; - Send(new PrintHostMsg_DuplicateSection( - routing_id(), - printed_page_params.metafile_data_handle, - &printed_page_params.metafile_data_handle)); - } for (size_t i = 0; i < printed_pages.size(); ++i) { printed_page_params.page_number = printed_pages[i]; @@ -182,13 +158,12 @@ void PrintWebViewHelper::PrintPageInternal( frame->getPrintPageShrink(params.page_number); float scale_factor = css_scale_factor * webkit_page_shrink_factor; - skia::PlatformCanvas* canvas = metafile->GetVectorCanvasForNewPage( + SkCanvas* canvas = metafile->GetVectorCanvasForNewPage( page_size, canvas_area, scale_factor); if (!canvas) return; MetafileSkiaWrapper::SetMetafileOnCanvas(*canvas, metafile); - skia::SetIsDraftMode(*canvas, is_print_ready_metafile_sent_); #if 0 if (params.params.display_header_footer) { @@ -216,24 +191,25 @@ void PrintWebViewHelper::PrintPageInternal( } bool PrintWebViewHelper::CopyMetafileDataToSharedMem( - PdfMetafileSkia* metafile, + const PdfMetafileSkia& metafile, base::SharedMemoryHandle* shared_mem_handle) { - uint32 buf_size = metafile->GetDataSize(); + uint32_t buf_size = metafile.GetDataSize(); + if (buf_size == 0) + return false; + base::SharedMemory shared_buf; // Allocate a shared memory buffer to hold the generated metafile data. - if (!shared_buf.CreateAndMapAnonymous(buf_size)) { - NOTREACHED() << "Buffer allocation failed"; + if (!shared_buf.CreateAndMapAnonymous(buf_size)) return false; - } // Copy the bits into shared memory. - if (!metafile->GetData(shared_buf.memory(), buf_size)) { - NOTREACHED() << "GetData() failed"; - shared_buf.Unmap(); + if (!metafile.GetData(shared_buf.memory(), buf_size)) + return false; + + if (!shared_buf.GiveToProcess(base::GetCurrentProcessHandle(), + shared_mem_handle)) { return false; } - shared_buf.GiveToProcess(base::GetCurrentProcessHandle(), shared_mem_handle); - shared_buf.Unmap(); Send(new PrintHostMsg_DuplicateSection(routing_id(), *shared_mem_handle, shared_mem_handle)); diff --git a/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.cc b/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.cc index 815a9c08b345..46465f4dfd4a 100644 --- a/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.cc +++ b/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.cc @@ -9,7 +9,6 @@ #include #include -#include "base/basictypes.h" #include "base/i18n/break_iterator.h" #include "base/logging.h" #include "base/strings/stringprintf.h" @@ -323,7 +322,7 @@ bool SpellcheckWordIterator::Initialize( if (rule.empty()) return false; - scoped_ptr iterator( + std::unique_ptr iterator( new base::i18n::BreakIterator(base::string16(), rule)); if (!iterator->Init()) { // Since we're not passing in any text, the only reason this could fail @@ -332,7 +331,7 @@ bool SpellcheckWordIterator::Initialize( NOTREACHED() << "failed to open iterator (broken rules)"; return false; } - iterator_ = iterator.Pass(); + iterator_ = std::move(iterator); // Set the character attributes so we can normalize the words extracted by // this iterator. diff --git a/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.h b/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.h index 2ac28a2e2402..4490c7a90e8e 100644 --- a/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.h +++ b/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.h @@ -11,7 +11,7 @@ #include -#include "base/basictypes.h" +#include "base/macros.h" #include "base/memory/scoped_ptr.h" #include "base/strings/string16.h" #include "third_party/icu/source/common/unicode/uscript.h" @@ -167,7 +167,7 @@ class SpellcheckWordIterator { const SpellcheckCharAttribute* attribute_; // The break iterator. - scoped_ptr iterator_; + std::unique_ptr iterator_; DISALLOW_COPY_AND_ASSIGN(SpellcheckWordIterator); }; diff --git a/chromium_src/chrome/renderer/tts_dispatcher.cc b/chromium_src/chrome/renderer/tts_dispatcher.cc index 91b67ba16749..0d3b97c84549 100644 --- a/chromium_src/chrome/renderer/tts_dispatcher.cc +++ b/chromium_src/chrome/renderer/tts_dispatcher.cc @@ -4,7 +4,6 @@ #include "chrome/renderer/tts_dispatcher.h" -#include "base/basictypes.h" #include "base/strings/utf_string_conversions.h" #include "chrome/common/tts_messages.h" #include "chrome/common/tts_utterance_request.h" @@ -197,4 +196,4 @@ void TtsDispatcher::OnSpeakingErrorOccurred(int utterance_id, // The web speech API doesn't support an error message. synthesizer_client_->speakingErrorOccurred(utterance); utterance_id_map_.erase(utterance_id); -} \ No newline at end of file +} diff --git a/chromium_src/chrome/renderer/tts_dispatcher.h b/chromium_src/chrome/renderer/tts_dispatcher.h index fd18acba2062..0a770d721831 100644 --- a/chromium_src/chrome/renderer/tts_dispatcher.h +++ b/chromium_src/chrome/renderer/tts_dispatcher.h @@ -7,8 +7,6 @@ #include -#include "base/basictypes.h" -#include "base/compiler_specific.h" #include "base/containers/hash_tables.h" #include "content/public/renderer/render_process_observer.h" #include "third_party/WebKit/public/platform/WebSpeechSynthesizer.h" @@ -75,4 +73,4 @@ class TtsDispatcher DISALLOW_COPY_AND_ASSIGN(TtsDispatcher); }; -#endif // CHROME_RENDERER_TTS_DISPATCHER_H_ \ No newline at end of file +#endif // CHROME_RENDERER_TTS_DISPATCHER_H_ diff --git a/chromium_src/chrome/utility/printing_handler_win.cc b/chromium_src/chrome/utility/printing_handler_win.cc index ec908d19fc51..105e574f7358 100644 --- a/chromium_src/chrome/utility/printing_handler_win.cc +++ b/chromium_src/chrome/utility/printing_handler_win.cc @@ -10,11 +10,14 @@ #include "base/scoped_native_library.h" #include "chrome/common/print_messages.h" #include "content/public/utility/utility_thread.h" +#include "pdf/pdf.h" #include "printing/emf_win.h" #include "printing/page_range.h" #include "printing/pdf_render_settings.h" #include "ui/gfx/gdi_util.h" +namespace printing { + namespace { bool Send(IPC::Message* message) { @@ -25,114 +28,12 @@ void ReleaseProcessIfNeeded() { content::UtilityThread::Get()->ReleaseProcessIfNeeded(); } -class PdfFunctions { - public: - PdfFunctions() : get_pdf_doc_info_func_(NULL), - render_pdf_to_dc_func_(NULL) {} - - bool Init() { - base::FilePath module_path; - if (!PathService::Get(base::DIR_MODULE, &module_path)) - return false; - base::FilePath::StringType name(FILE_PATH_LITERAL("pdf.dll")); - pdf_lib_.Reset(base::LoadNativeLibrary(module_path.Append(name), NULL)); - if (!pdf_lib_.is_valid()) { - LOG(WARNING) << "Couldn't load PDF plugin"; - return false; - } - - get_pdf_doc_info_func_ = - reinterpret_cast( - pdf_lib_.GetFunctionPointer("GetPDFDocInfo")); - LOG_IF(WARNING, !get_pdf_doc_info_func_) << "Missing GetPDFDocInfo"; - - render_pdf_to_dc_func_ = - reinterpret_cast( - pdf_lib_.GetFunctionPointer("RenderPDFPageToDC")); - LOG_IF(WARNING, !render_pdf_to_dc_func_) << "Missing RenderPDFPageToDC"; - - if (!get_pdf_doc_info_func_ || !render_pdf_to_dc_func_) { - Reset(); - } - - return IsValid(); - } - - bool IsValid() const { - return pdf_lib_.is_valid(); - } - - void Reset() { - pdf_lib_.Reset(NULL); - } - - bool GetPDFDocInfo(const void* pdf_buffer, - int buffer_size, - int* page_count, - double* max_page_width) { - if (!get_pdf_doc_info_func_) - return false; - return get_pdf_doc_info_func_(pdf_buffer, buffer_size, page_count, - max_page_width); - } - - bool RenderPDFPageToDC(const void* pdf_buffer, - int buffer_size, - int page_number, - HDC dc, - int dpi, - int bounds_origin_x, - int bounds_origin_y, - int bounds_width, - int bounds_height, - bool fit_to_bounds, - bool stretch_to_bounds, - bool keep_aspect_ratio, - bool center_in_bounds, - bool autorotate) { - if (!render_pdf_to_dc_func_) - return false; - return render_pdf_to_dc_func_(pdf_buffer, buffer_size, page_number, - dc, dpi, bounds_origin_x, - bounds_origin_y, bounds_width, bounds_height, - fit_to_bounds, stretch_to_bounds, - keep_aspect_ratio, center_in_bounds, - autorotate); - } - - private: - // Exported by PDF plugin. - typedef bool (*GetPDFDocInfoProc)(const void* pdf_buffer, - int buffer_size, int* page_count, - double* max_page_width); - typedef bool (*RenderPDFPageToDCProc)( - const void* pdf_buffer, int buffer_size, int page_number, HDC dc, - int dpi, int bounds_origin_x, int bounds_origin_y, - int bounds_width, int bounds_height, bool fit_to_bounds, - bool stretch_to_bounds, bool keep_aspect_ratio, bool center_in_bounds, - bool autorotate); - - RenderPDFPageToDCProc render_pdf_to_dc_func_; - GetPDFDocInfoProc get_pdf_doc_info_func_; - - base::ScopedNativeLibrary pdf_lib_; - - DISALLOW_COPY_AND_ASSIGN(PdfFunctions); -}; - -base::LazyInstance g_pdf_lib = LAZY_INSTANCE_INITIALIZER; - } // namespace PrintingHandlerWin::PrintingHandlerWin() {} PrintingHandlerWin::~PrintingHandlerWin() {} -// static -void PrintingHandlerWin::PreSandboxStartup() { - g_pdf_lib.Get().Init(); -} - bool PrintingHandlerWin::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(PrintingHandlerWin, message) @@ -149,10 +50,10 @@ bool PrintingHandlerWin::OnMessageReceived(const IPC::Message& message) { void PrintingHandlerWin::OnRenderPDFPagesToMetafile( IPC::PlatformFileForTransit pdf_transit, - const printing::PdfRenderSettings& settings) { + const PdfRenderSettings& settings) { pdf_rendering_settings_ = settings; base::File pdf_file = IPC::PlatformFileForTransitToFile(pdf_transit); - int page_count = LoadPDF(pdf_file.Pass()); + int page_count = LoadPDF(std::move(pdf_file)); //int page_count = 1; Send( new ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageCount(page_count)); @@ -164,7 +65,7 @@ void PrintingHandlerWin::OnRenderPDFPagesToMetafileGetPage( base::File emf_file = IPC::PlatformFileForTransitToFile(output_file); float scale_factor = 1.0f; bool success = - RenderPdfPageToMetafile(page_number, emf_file.Pass(), &scale_factor); + RenderPdfPageToMetafile(page_number, std::move(emf_file), &scale_factor); Send(new ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageDone( success, scale_factor)); } @@ -174,10 +75,7 @@ void PrintingHandlerWin::OnRenderPDFPagesToMetafileStop() { } int PrintingHandlerWin::LoadPDF(base::File pdf_file) { - if (!g_pdf_lib.Get().IsValid()) - return 0; - - int64 length64 = pdf_file.GetLength(); + int64_t length64 = pdf_file.GetLength(); if (length64 <= 0 || length64 > std::numeric_limits::max()) return 0; int length = static_cast(length64); @@ -187,8 +85,8 @@ int PrintingHandlerWin::LoadPDF(base::File pdf_file) { return 0; int total_page_count = 0; - if (!g_pdf_lib.Get().GetPDFDocInfo( - &pdf_data_.front(), pdf_data_.size(), &total_page_count, NULL)) { + if (!chrome_pdf::GetPDFDocInfo(&pdf_data_.front(), pdf_data_.size(), + &total_page_count, nullptr)) { return 0; } return total_page_count; @@ -197,7 +95,7 @@ int PrintingHandlerWin::LoadPDF(base::File pdf_file) { bool PrintingHandlerWin::RenderPdfPageToMetafile(int page_number, base::File output_file, float* scale_factor) { - printing::Emf metafile; + Emf metafile; metafile.Init(); // We need to scale down DC to fit an entire page into DC available area. @@ -216,7 +114,7 @@ bool PrintingHandlerWin::RenderPdfPageToMetafile(int page_number, // The underlying metafile is of type Emf and ignores the arguments passed // to StartPage. metafile.StartPage(gfx::Size(), gfx::Rect(), 1); - if (!g_pdf_lib.Get().RenderPDFPageToDC( + if (!chrome_pdf::RenderPDFPageToDC( &pdf_data_.front(), pdf_data_.size(), page_number, @@ -237,3 +135,5 @@ bool PrintingHandlerWin::RenderPdfPageToMetafile(int page_number, metafile.FinishDocument(); return metafile.SaveTo(&output_file); } + +} // printing diff --git a/chromium_src/chrome/utility/printing_handler_win.h b/chromium_src/chrome/utility/printing_handler_win.h index 5b8c5e970f15..e7fcc5bed158 100644 --- a/chromium_src/chrome/utility/printing_handler_win.h +++ b/chromium_src/chrome/utility/printing_handler_win.h @@ -12,10 +12,10 @@ #include "printing/pdf_render_settings.h" namespace printing { + class PdfRenderSettings; struct PwgRasterSettings; struct PageRange; -} // Dispatches IPCs for printing. class PrintingHandlerWin : public UtilityMessageHandler { @@ -26,12 +26,10 @@ class PrintingHandlerWin : public UtilityMessageHandler { // IPC::Listener: bool OnMessageReceived(const IPC::Message& message) override; - static void PrintingHandlerWin::PreSandboxStartup(); - private: // IPC message handlers. void OnRenderPDFPagesToMetafile(IPC::PlatformFileForTransit pdf_transit, - const printing::PdfRenderSettings& settings); + const PdfRenderSettings& settings); void OnRenderPDFPagesToMetafileGetPage( int page_number, IPC::PlatformFileForTransit output_file); @@ -43,9 +41,11 @@ class PrintingHandlerWin : public UtilityMessageHandler { float* scale_factor); std::vector pdf_data_; - printing::PdfRenderSettings pdf_rendering_settings_; + PdfRenderSettings pdf_rendering_settings_; DISALLOW_COPY_AND_ASSIGN(PrintingHandlerWin); }; +} // namespace printing + #endif // CHROME_UTILITY_PRINTING_HANDLER_WIN_H_ 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 897b23bbd56e..2514c636cbee 100644 --- a/chromium_src/net/test/embedded_test_server/stream_listen_socket.cc +++ b/chromium_src/net/test/embedded_test_server/stream_listen_socket.cc @@ -17,6 +17,7 @@ #include "net/base/net_errors.h" #endif +#include "base/files/file_util.h" #include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" @@ -26,7 +27,8 @@ #include "build/build_config.h" #include "net/base/ip_endpoint.h" #include "net/base/net_errors.h" -#include "net/base/net_util.h" +#include "net/base/network_interfaces.h" +#include "net/base/sockaddr_storage.h" #include "net/socket/socket_descriptor.h" using std::string; @@ -125,7 +127,7 @@ SocketDescriptor StreamListenSocket::AcceptSocket() { if (conn == kInvalidSocket) LOG(ERROR) << "Error accepting connection."; else - SetNonBlocking(conn); + base::SetNonBlocking(conn); return conn; } diff --git a/chromium_src/net/test/embedded_test_server/stream_listen_socket.h b/chromium_src/net/test/embedded_test_server/stream_listen_socket.h index 02a8b9827a2e..7ad25437ec39 100644 --- a/chromium_src/net/test/embedded_test_server/stream_listen_socket.h +++ b/chromium_src/net/test/embedded_test_server/stream_listen_socket.h @@ -28,7 +28,7 @@ #include "base/message_loop/message_loop.h" #endif -#include "base/basictypes.h" +#include "base/macros.h" #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" #include "net/base/net_export.h" @@ -58,7 +58,7 @@ class StreamListenSocket : // |server| is the original listening Socket, connection is the new // Socket that was created. virtual void DidAccept(StreamListenSocket* server, - scoped_ptr connection) = 0; + std::unique_ptr connection) = 0; virtual void DidRead(StreamListenSocket* connection, const char* data, int len) = 0; @@ -140,7 +140,7 @@ class StreamListenSocketFactory { virtual ~StreamListenSocketFactory() {} // Returns a new instance of StreamListenSocket or NULL if an error occurred. - virtual scoped_ptr CreateAndListen( + virtual std::unique_ptr CreateAndListen( StreamListenSocket::Delegate* delegate) const = 0; }; diff --git a/chromium_src/net/test/embedded_test_server/tcp_listen_socket.cc b/chromium_src/net/test/embedded_test_server/tcp_listen_socket.cc index 418f34592127..50aac809052e 100644 --- a/chromium_src/net/test/embedded_test_server/tcp_listen_socket.cc +++ b/chromium_src/net/test/embedded_test_server/tcp_listen_socket.cc @@ -8,6 +8,7 @@ // winsock2.h must be included first in order to ensure it is included before // windows.h. #include +#include #elif defined(OS_POSIX) #include #include @@ -21,7 +22,7 @@ #include "base/sys_byteorder.h" #include "base/threading/platform_thread.h" #include "build/build_config.h" -#include "net/base/net_util.h" +#include "net/base/network_interfaces.h" #include "net/base/winsock_init.h" #include "net/socket/socket_descriptor.h" @@ -32,16 +33,16 @@ namespace net { namespace test_server { // static -scoped_ptr TCPListenSocket::CreateAndListen( +std::unique_ptr TCPListenSocket::CreateAndListen( const string& ip, - uint16 port, + uint16_t port, StreamListenSocket::Delegate* del) { SocketDescriptor s = CreateAndBind(ip, port); if (s == kInvalidSocket) - return scoped_ptr(); - scoped_ptr sock(new TCPListenSocket(s, del)); + return std::unique_ptr(); + std::unique_ptr sock(new TCPListenSocket(s, del)); sock->Listen(); - return sock.Pass(); + return sock; } TCPListenSocket::TCPListenSocket(SocketDescriptor s, @@ -52,7 +53,8 @@ TCPListenSocket::TCPListenSocket(SocketDescriptor s, TCPListenSocket::~TCPListenSocket() { } -SocketDescriptor TCPListenSocket::CreateAndBind(const string& ip, uint16 port) { +SocketDescriptor TCPListenSocket::CreateAndBind(const string& ip, + uint16_t port) { SocketDescriptor s = CreatePlatformSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (s != kInvalidSocket) { #if defined(OS_POSIX) @@ -79,7 +81,7 @@ SocketDescriptor TCPListenSocket::CreateAndBind(const string& ip, uint16 port) { } SocketDescriptor TCPListenSocket::CreateAndBindAnyPort(const string& ip, - uint16* port) { + uint16_t* port) { SocketDescriptor s = CreateAndBind(ip, 0); if (s == kInvalidSocket) return kInvalidSocket; @@ -106,11 +108,11 @@ void TCPListenSocket::Accept() { SocketDescriptor conn = AcceptSocket(); if (conn == kInvalidSocket) return; - scoped_ptr sock(new TCPListenSocket(conn, socket_delegate_)); + std::unique_ptr sock(new TCPListenSocket(conn, socket_delegate_)); #if defined(OS_POSIX) sock->WatchSocket(WAITING_READ); #endif - socket_delegate_->DidAccept(this, sock.Pass()); + socket_delegate_->DidAccept(this, std::move(sock)); } } // namespace test_server diff --git a/chromium_src/net/test/embedded_test_server/tcp_listen_socket.h b/chromium_src/net/test/embedded_test_server/tcp_listen_socket.h index 12b3fa40745e..db18fd0f0eb4 100644 --- a/chromium_src/net/test/embedded_test_server/tcp_listen_socket.h +++ b/chromium_src/net/test/embedded_test_server/tcp_listen_socket.h @@ -7,7 +7,7 @@ #include -#include "base/basictypes.h" +#include "base/macros.h" #include "net/base/net_export.h" #include "net/socket/socket_descriptor.h" #include "net/test/embedded_test_server/stream_listen_socket.h" @@ -23,9 +23,9 @@ class TCPListenSocket : public StreamListenSocket { // Listen on port for the specified IP address. Use 127.0.0.1 to only // accept local connections. - static scoped_ptr CreateAndListen( + static std::unique_ptr CreateAndListen( const std::string& ip, - uint16 port, + uint16_t port, StreamListenSocket::Delegate* del); protected: @@ -39,11 +39,11 @@ class TCPListenSocket : public StreamListenSocket { friend class TCPListenSocketTester; // Get raw TCP socket descriptor bound to ip:port. - static SocketDescriptor CreateAndBind(const std::string& ip, uint16 port); + static SocketDescriptor CreateAndBind(const std::string& ip, uint16_t port); // Get raw TCP socket descriptor bound to ip and return port it is bound to. static SocketDescriptor CreateAndBindAnyPort(const std::string& ip, - uint16* port); + uint16_t* port); DISALLOW_COPY_AND_ASSIGN(TCPListenSocket); }; diff --git a/common.gypi b/common.gypi index 7c41c3616dfb..29fe70afc02c 100644 --- a/common.gypi +++ b/common.gypi @@ -4,6 +4,8 @@ 'vendor/brightray/brightray.gypi', ], 'variables': { + # Tell crashpad to build as external project. + 'crashpad_dependencies': 'external', # Required by breakpad. 'os_bsd': 0, 'chromeos': 0, @@ -13,7 +15,7 @@ 'openssl_fips': '', 'openssl_no_asm': 1, 'node_release_urlbase': 'https://atom.io/download/atom-shell', - 'node_byteorder': ' { + app.quit() +}) + +exports.load = (appUrl) => { + app.on('ready', () => { + mainWindow = new BrowserWindow({ + width: 800, + height: 600, + autoHideMenuBar: true, + backgroundColor: '#FFFFFF', + useContentSize: true + }) + mainWindow.loadURL(appUrl) + mainWindow.focus() + }) +} diff --git a/default_app/index.html b/default_app/index.html new file mode 100644 index 000000000000..edfb3e6ddc92 --- /dev/null +++ b/default_app/index.html @@ -0,0 +1,219 @@ + + + Electron + + + + + +
+ + + + Electron + + + + + +
+ Docs + Repository + Blog +
+
+ +
+ +

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

+ + + +

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

+ +

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

+ +

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

+ +
+ Drag your app here to run it +
+ +
+ + + + diff --git a/default_app/main.js b/default_app/main.js new file mode 100644 index 000000000000..e26c70590b1a --- /dev/null +++ b/default_app/main.js @@ -0,0 +1,332 @@ +const {app, dialog, shell, Menu} = require('electron') + +const fs = require('fs') +const Module = require('module') +const path = require('path') +const repl = require('repl') +const url = require('url') + +// Parse command line options. +const argv = process.argv.slice(1) +const option = { file: null, help: null, version: null, webdriver: null, modules: [] } +for (let 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] === '--interactive' || argv[i] === '-i') { + option.interactive = true + } else if (argv[i] === '--test-type=webdriver') { + option.webdriver = true + } else if (argv[i] === '--require' || argv[i] === '-r') { + option.modules.push(argv[++i]) + continue + } else if (argv[i][0] === '-') { + continue + } else { + option.file = argv[i] + break + } +} + +// Quit when all windows are closed and no other one is listening to this. +app.on('window-all-closed', () => { + if (app.listeners('window-all-closed').length === 1 && !option.interactive) { + app.quit() + } +}) + +// Create default menu. +app.once('ready', () => { + if (Menu.getApplicationMenu()) return + + const template = [ + { + label: 'Edit', + submenu: [ + { + label: 'Undo', + accelerator: 'CmdOrCtrl+Z', + role: 'undo' + }, + { + label: 'Redo', + accelerator: 'Shift+CmdOrCtrl+Z', + role: 'redo' + }, + { + type: 'separator' + }, + { + label: 'Cut', + accelerator: 'CmdOrCtrl+X', + role: 'cut' + }, + { + label: 'Copy', + accelerator: 'CmdOrCtrl+C', + role: 'copy' + }, + { + label: 'Paste', + accelerator: 'CmdOrCtrl+V', + role: 'paste' + }, + { + label: 'Select All', + accelerator: 'CmdOrCtrl+A', + role: 'selectall' + } + ] + }, + { + label: 'View', + submenu: [ + { + label: 'Reload', + accelerator: 'CmdOrCtrl+R', + click (item, focusedWindow) { + if (focusedWindow) focusedWindow.reload() + } + }, + { + label: 'Toggle Full Screen', + accelerator: (() => { + return (process.platform === 'darwin') ? 'Ctrl+Command+F' : 'F11' + })(), + click (item, focusedWindow) { + if (focusedWindow) focusedWindow.setFullScreen(!focusedWindow.isFullScreen()) + } + }, + { + label: 'Toggle Developer Tools', + accelerator: (() => { + return (process.platform === 'darwin') ? 'Alt+Command+I' : 'Ctrl+Shift+I' + })(), + click (item, focusedWindow) { + if (focusedWindow) focusedWindow.toggleDevTools() + } + } + ] + }, + { + label: 'Window', + role: 'window', + submenu: [ + { + label: 'Minimize', + accelerator: 'CmdOrCtrl+M', + role: 'minimize' + }, + { + label: 'Close', + accelerator: 'CmdOrCtrl+W', + role: 'close' + } + ] + }, + { + label: 'Help', + role: 'help', + submenu: [ + { + label: 'Learn More', + click () { + shell.openExternal('http://electron.atom.io') + } + }, + { + label: 'Documentation', + click () { + shell.openExternal( + `https://github.com/electron/electron/tree/v${process.versions.electron}/docs#readme` + ) + } + }, + { + label: 'Community Discussions', + click () { + shell.openExternal('https://discuss.atom.io/c/electron') + } + }, + { + label: 'Search Issues', + click () { + shell.openExternal('https://github.com/electron/electron/issues') + } + } + ] + } + ] + + if (process.platform === 'darwin') { + template.unshift({ + label: 'Electron', + submenu: [ + { + label: 'About Electron', + role: 'about' + }, + { + type: 'separator' + }, + { + label: 'Services', + role: 'services', + submenu: [] + }, + { + type: 'separator' + }, + { + label: 'Hide Electron', + accelerator: 'Command+H', + role: 'hide' + }, + { + label: 'Hide Others', + accelerator: 'Command+Alt+H', + role: 'hideothers' + }, + { + label: 'Show All', + role: 'unhide' + }, + { + type: 'separator' + }, + { + label: 'Quit', + accelerator: 'Command+Q', + click () { app.quit() } + } + ] + }) + template[3].submenu.push( + { + type: 'separator' + }, + { + label: 'Bring All to Front', + role: 'front' + } + ) + } + + const menu = Menu.buildFromTemplate(template) + Menu.setApplicationMenu(menu) +}) + +if (option.modules.length > 0) { + Module._preloadModules(option.modules) +} + +function loadApplicationPackage (packagePath) { + // Add a flag indicating app is started from default app. + process.defaultApp = true + + try { + // Override app name and version. + packagePath = path.resolve(packagePath) + const packageJsonPath = path.join(packagePath, 'package.json') + if (fs.existsSync(packageJsonPath)) { + let packageJson + try { + packageJson = JSON.parse(fs.readFileSync(packageJsonPath)) + } catch (e) { + showErrorMessage(`Unable to parse ${packageJsonPath}\n\n${e.message}`) + return + } + + if (packageJson.version) { + app.setVersion(packageJson.version) + } + if (packageJson.productName) { + app.setName(packageJson.productName) + } else if (packageJson.name) { + app.setName(packageJson.name) + } + app.setPath('userData', path.join(app.getPath('appData'), app.getName())) + app.setPath('userCache', path.join(app.getPath('cache'), app.getName())) + app.setAppPath(packagePath) + } + + try { + Module._resolveFilename(packagePath, module, true) + } catch (e) { + showErrorMessage(`Unable to find Electron app at ${packagePath}\n\n${e.message}`) + return + } + + // Run the app. + Module._load(packagePath, module, true) + } catch (e) { + console.error('App threw an error during load') + console.error(e.stack || e) + throw e + } +} + +function showErrorMessage (message) { + app.focus() + dialog.showErrorBox('Error launching app', message) + process.exit(1) +} + +function loadApplicationByUrl (appUrl) { + require('./default_app').load(appUrl) +} + +function startRepl () { + repl.start('> ').on('exit', () => { + process.exit(0) + }) +} + +// Start the specified app if there is one specified in command line, otherwise +// start the default app. +if (option.file && !option.webdriver) { + const file = option.file + const protocol = url.parse(file).protocol + const extension = path.extname(file) + if (protocol === 'http:' || protocol === 'https:' || protocol === 'file:') { + loadApplicationByUrl(file) + } else if (extension === '.html' || extension === '.htm') { + loadApplicationByUrl('file://' + path.resolve(file)) + } else { + loadApplicationPackage(file) + } +} else if (option.version) { + console.log('v' + process.versions.electron) + process.exit(0) +} else if (option.help) { + const helpMessage = `Electron ${process.versions.electron} - Build cross platform desktop apps with JavaScript, HTML, and CSS + + Usage: electron [options] [path] + + A path to an Electron app may be specified. The path must be one of the following: + + - index.js file. + - Folder containing a package.json file. + - Folder containing an index.js file. + - .html/.htm file. + - http://, https://, or file:// URL. + + Options: + -h, --help Print this usage message. + -i, --interactive Open a REPL to the main process. + -r, --require Module to preload (option can be repeated) + -v, --version Print the version.` + console.log(helpMessage) + process.exit(0) +} else if (option.interactive) { + startRepl() +} else { + const indexPath = path.join(__dirname, '/index.html') + loadApplicationByUrl(`file://${indexPath}`) +} diff --git a/atom/browser/default_app/package.json b/default_app/package.json similarity index 100% rename from atom/browser/default_app/package.json rename to default_app/package.json diff --git a/docs-translations/es/README.md b/docs-translations/es/README.md index e69e76b1c4c7..c2358bbb0161 100644 --- a/docs-translations/es/README.md +++ b/docs-translations/es/README.md @@ -35,7 +35,7 @@ * [content-tracing](../../docs/api/content-tracing.md) * [dialog](../../docs/api/dialog.md) * [global-shortcut](../../docs/api/global-shortcut.md) -* [ipc (proceso principal)](../../docs/api/ipc-main-process.md) +* [ipc (proceso principal)](../../docs/api/ipc-main.md) * [menu](../../docs/api/menu.md) * [menu-item](../../docs/api/menu-item.md) * [power-monitor](../../docs/api/power-monitor.md) @@ -66,6 +66,6 @@ * [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 (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) +* [Configurando un Servidor de Símbolos en el depurador](development/setting-up-symbol-server.md) diff --git a/docs-translations/es/api/process.md b/docs-translations/es/api/process.md index 9e95ba988541..e50a2c1aac62 100644 --- a/docs-translations/es/api/process.md +++ b/docs-translations/es/api/process.md @@ -5,8 +5,8 @@ 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.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 diff --git a/docs-translations/es/api/synopsis.md b/docs-translations/es/api/synopsis.md index 534fafcf2f7c..54c264a1aa74 100644 --- a/docs-translations/es/api/synopsis.md +++ b/docs-translations/es/api/synopsis.md @@ -1,4 +1,4 @@ -# Synopsis +# Sinopsis 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 diff --git a/docs-translations/es/development/atom-shell-vs-node-webkit.md b/docs-translations/es/development/atom-shell-vs-node-webkit.md index 434e67658074..8d0170673294 100644 --- a/docs-translations/es/development/atom-shell-vs-node-webkit.md +++ b/docs-translations/es/development/atom-shell-vs-node-webkit.md @@ -31,4 +31,4 @@ Si usted es un usuario experimentado NW.js, usted debe estar familiarizado con e 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 +[node-bindings]: https://github.com/electron/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 index 28ad828fa709..58beef4ecf04 100644 --- a/docs-translations/es/development/build-instructions-linux.md +++ b/docs-translations/es/development/build-instructions-linux.md @@ -29,7 +29,7 @@ Si usted planea construir Electron en una máquina virtual, necesitará un disp #Obteniendo el codigo -`$ git clone https://github.com/atom/electron.git` +`$ git clone https://github.com/electron/electron.git` #Bootstrapping (Arranque) diff --git a/docs-translations/es/development/build-instructions-osx.md b/docs-translations/es/development/build-instructions-osx.md index 6e2d7b7f392e..2ac3641e8361 100644 --- a/docs-translations/es/development/build-instructions-osx.md +++ b/docs-translations/es/development/build-instructions-osx.md @@ -13,7 +13,7 @@ Si está utilizando Python descargado de Homebrew, también es necesario instal #Obtener el Código -`$ git clone https://github.com/atom/electron.git` +`$ git clone https://github.com/electron/electron.git` #Bootstrapping (arranque) diff --git a/docs-translations/es/development/coding-style.md b/docs-translations/es/development/coding-style.md index de02a33a86d3..e2f364d8b9c2 100644 --- a/docs-translations/es/development/coding-style.md +++ b/docs-translations/es/development/coding-style.md @@ -34,4 +34,4 @@ siguientes reglas: 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. +[discusión](https://github.com/electron/electron/issues/46) sobre esto. diff --git a/docs-translations/es/styleguide.md b/docs-translations/es/styleguide.md index 4948035ffffe..18fa1aa3bb3a 100644 --- a/docs-translations/es/styleguide.md +++ b/docs-translations/es/styleguide.md @@ -43,7 +43,7 @@ Para agregar otro set (o un set parcial): - 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). +[README](https://github.com/electron/electron#documentation-translations). ## Leyendo la Documentación de Electron diff --git a/docs-translations/es/tutorial/application-packaging.md b/docs-translations/es/tutorial/application-packaging.md index c0cca7ecd916..0e46a44d2b48 100644 --- a/docs-translations/es/tutorial/application-packaging.md +++ b/docs-translations/es/tutorial/application-packaging.md @@ -24,8 +24,8 @@ $ asar pack your-app app.asar ## Utilizando los paquetes `asar` -En Electron existen dos tipos de APIs: las APIs de Node, proveídas por Node.js, -y las APIs Web, proveídas por Chromium. Ambas APIs soportan la lecutra de paquetes `asar`. +En Electron existen dos tipos de APIs: las APIs de Node, provistas por Node.js, +y las APIs Web, provistas por Chromium. Ambas APIs soportan la lectura de paquetes `asar`. ### API Node @@ -92,7 +92,7 @@ $.get('file:///path/to/example.asar/file.txt', function(data) { ### Utilizando un paquete `asar` como un archivo normal En algunas situaciones necesitaremos acceder al paquete `asar` como archivo, por ejemplo, -si necesitaramos verificar la integridad del archivo con un checksum. +si necesitáramos verificar la integridad del archivo con un checksum. Para casos así es posible utilizar el módulo `original-fs`, que provee la API `fs` original: ```javascript @@ -105,7 +105,7 @@ originalFs.readFileSync('/path/to/example.asar'); A pesar de que hemos intentado que los paquetes `asar` funcionen como directorios de la mejor forma posible, aún existen limitaciones debido a la naturaleza de bajo nivel de la API Node. -### Los paquetes son de sólo lecutra +### Los paquetes son de sólo lectura Los paquetes `asar` no pueden ser modificados, por lo cual todas las funciones que modifiquen archivos no funcionarán. @@ -127,12 +127,12 @@ Las APIs que requieren el desempaquetamiento adicional son: * `child_process.execFile` * `fs.open` * `fs.openSync` -* `process.dlopen` - Utilizado po `require` en los módulos nativos +* `process.dlopen` - Utilizado por `require` en los módulos nativos ### Información falsa en `fs.stat` El objeto `Stats` retornado por `fs.stat` y otras funciones relacionadas, -no es preciso, ya que los archivos del paquete `asar` no existen el sistema de archivos. +no es preciso, ya que los archivos del paquete `asar` no existen en el sistema de archivos. La utilización del objeto `Stats` sólo es recomendable para obtener el tamaño del archivo y/o comprobar el tipo de archivo. @@ -143,8 +143,8 @@ Como se menciona arriba, algunas APIs de Node desempaquetarán archivos cuando e que los referencie, además de los problemas de rendimiento que esto podría ocasionar, también podría accionar alertas falsas en software antivirus. -Para lidiar con esto, puedes desempaquetar algunos archivos utilizando la opción `--unpack`, -a continuación un ejemplo que excluye las librerías compartidas de los módulos nativos: +Para lidiar con esto, puedes desempaquetar algunos archivos utilizando la opción `--unpack`. +A continuación, un ejemplo que excluye las librerías compartidas de los módulos nativos: ```bash $ asar pack app app.asar --unpack *.node diff --git a/docs-translations/es/tutorial/devtools-extension.md b/docs-translations/es/tutorial/devtools-extension.md index f54c3e0eaa83..495f56058d5f 100644 --- a/docs-translations/es/tutorial/devtools-extension.md +++ b/docs-translations/es/tutorial/devtools-extension.md @@ -1,13 +1,13 @@ # Extensión DevTools -Para facilitar la depuración, Electron provee un soporte básico para la extensión -[Chrome DevTools Extension][devtools-extension]. +Para facilitar la depuración, Electron provee un soporte básico para la extensión +[Chrome DevTools][devtools-extension]. Para la mayoría de las extensiones devtools, simplemente puedes descargar el código fuente y utilizar `BrowserWindow.addDevToolsExtension` para cargarlas, las extensiones cargadas serán recordadas para que no sea necesario llamar a la función cada vez que creas una ventana. -Por ejemplo, para usar la extensión [React DevTools Extension](https://github.com/facebook/react-devtools), primero debes descargar el código fuente: +Por ejemplo, para usar la extensión [React DevTools](https://github.com/facebook/react-devtools), primero debes descargar el código fuente: ```bash $ cd /some-directory diff --git a/docs-translations/es/tutorial/quick-start.md b/docs-translations/es/tutorial/quick-start.md index b038f7cb08d6..9e65042c5035 100644 --- a/docs-translations/es/tutorial/quick-start.md +++ b/docs-translations/es/tutorial/quick-start.md @@ -1,8 +1,6 @@ -# Intro - ## Introducción -Electron permite la creación de aplicaciones de escritorio utilizando JavaScript puro, a través de un runtime con APIs nativas. Puedes verlo como una variante de io.js, enfocado en aplicaciones de escritorio, en vez de servidores web. +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 a 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, @@ -117,7 +115,7 @@ Finalmente el `index.html` es la página web que mostraremos:

Hello World!

We are using io.js - and Electron . + and Electron . ``` @@ -147,4 +145,4 @@ En OS X: $ ./Electron.app/Contents/MacOS/Electron your-app/ ``` -`Electron.app` es parte del paquete de release de Electron, puedes descargarlo [aquí](https://github.com/atom/electron/releases). +`Electron.app` es parte del paquete de release de Electron, puedes descargarlo [aquí](https://github.com/electron/electron/releases). diff --git a/docs-translations/es/tutorial/using-selenium-and-webdriver.md b/docs-translations/es/tutorial/using-selenium-and-webdriver.md index 7d9989057056..919a889d38dd 100644 --- a/docs-translations/es/tutorial/using-selenium-and-webdriver.md +++ b/docs-translations/es/tutorial/using-selenium-and-webdriver.md @@ -8,7 +8,7 @@ De [ChromeDriver - WebDriver for Chrome][chrome-driver]: > el protocolo de WebDriver para Chromium. Se encuentra en desarrollo por los miembros de > Chromium y WebDriver. -En la página de [lanzamientos](https://github.com/atom/electron/releases) de Electron encontrarás paquetes de `chromedriver`. +En la página de [lanzamientos](https://github.com/electron/electron/releases) de Electron encontrarás paquetes de `chromedriver`. ## Ajustando parámetros con WebDriverJs @@ -65,7 +65,7 @@ driver.quit(); ## Workflow -Para probar tu aplicación sin recompilar Electron, simplemente [copia](https://github.com/atom/electron/blob/master/docs/tutorial/application-distribution.md) las fuentes de tu aplicación en el directorio de recursos de Electron. +Para probar tu aplicación sin recompilar Electron, simplemente [copia](https://github.com/electron/electron/blob/master/docs/tutorial/application-distribution.md) las fuentes de tu aplicación en el directorio de recursos de Electron. [chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ diff --git a/docs-translations/fr-FR/styleguide.md b/docs-translations/fr-FR/styleguide.md index 5fe781773c12..b683ee68aad7 100644 --- a/docs-translations/fr-FR/styleguide.md +++ b/docs-translations/fr-FR/styleguide.md @@ -43,7 +43,7 @@ Pour ajouter une nouvelle langue (ou commencer) : - Traduire les fichiers. - Mettre à jour le `README.md` à l'intérieur du dossier de langue en mettant les liens vers les fichiers traduits. -- Ajouter un lien vers le nouveau dossier de langue dans le [README](https://github.com/atom/electron#documentation-translations) +- Ajouter un lien vers le nouveau dossier de langue dans le [README](https://github.com/electron/electron#documentation-translations) principal d'Electron. ## Lire la documentation d'Electron diff --git a/docs-translations/jp/README.md b/docs-translations/jp/README.md index accde9739769..7359745d6c0b 100644 --- a/docs-translations/jp/README.md +++ b/docs-translations/jp/README.md @@ -3,6 +3,7 @@ そうでない場合、おそらくご使用の Electron のバージョンと互換性のない API 変更を含んだ development ブランチのドキュメントを使っているものと思われます。 その場合、atom.io の [available versions](http://electron.atom.io/docs/) リストにある別のバージョンのドキュメントに切り替えることができます。また GitHub で閲覧している場合、"Switch branches/tags" ドロップダウンを開いて、バージョンに対応したタグを選ぶこともできます。 +_リンクになっていないリストは未翻訳のものです。_ ## FAQ 頻繁に聞かれる質問がありますので、issueを作成する前にこれをチェックしてください。 @@ -12,8 +13,11 @@ ## ガイド * [サポートするプラットフォーム](tutorial/supported-platforms.md) +* [セキュリティ](tutorial/security.md) +* [Electronのバージョン管理](tutorial/electron-versioning.md) * [アプリケーションの配布](tutorial/application-distribution.md) * [Mac App Store 提出ガイド](tutorial/mac-app-store-submission-guide.md) +* Windows Store Guide (tutorial/windows-store-guide.md) * [アプリケーションのパッケージ化](tutorial/application-packaging.md) * [ネイティブのNodeモジュールを使用する](tutorial/using-native-node-modules.md) * [メインプロセスのデバッグ](tutorial/debugging-main-process.md) @@ -21,6 +25,8 @@ * [DevTools エクステンション](tutorial/devtools-extension.md) * [Pepper Flashプラグインを使用する](tutorial/using-pepper-flash-plugin.md) * [Widevine CDMプラグインを使用する](tutorial/using-widevine-cdm-plugin.md) +* Testing on Headless CI Systems (Travis, Jenkins) (tutorial/testing-on-headless-ci.md) + # チュートリアル @@ -31,3 +37,60 @@ ## API リファレンス * [概要](api/synopsis.md) +* [Process Object](api/process.md) +* [サポートしているChromeコマンドラインスイッチ](api/chrome-command-line-switches.md) +* [環境変数](api/environment-variables.md) + +### カスタムDOM要素: + +* [`File` Object](api/file-object.md) +* [`` タグ](api/web-view-tag.md) +* [`window.open` 関数](api/window-open.md) + +### Main Processのモジュール: + +* [app](api/app.md) +* [autoUpdater](api/auto-updater.md) +* BrowserWindow (api/browser-window.md) + * [フレームの無いウィンドウ](api/frameless-window.md) +* [contentTracing](api/content-tracing.md) +* [dialog](api/dialog.md) +* [globalShortcut](api/global-shortcut.md) +* [ipcMain](api/ipc-main.md) +* [Menu](api/menu.md) +* [MenuItem](api/menu-item.md) +* [powerMonitor](api/power-monitor.md) +* [powerSaveBlocker](api/power-save-blocker.md) +* [protocol](api/protocol.md) +* [session](api/session.md) +* systemPreferences (api/system-preferences.md) +* webContents (api/web-contents.md) +* [Tray](api/tray.md) + +### Renderer Processのモジュール (Web Page): + +* [desktopCapturer](api/desktop-capturer.md) +* [ipcRenderer](api/ipc-renderer.md) +* [remote](api/remote.md) +* [webFrame](api/web-frame.md) + +### 両方のProcessのモジュール : + +* [clipboard](api/clipboard.md) +* [crashReporter](api/crash-reporter.md) +* [nativeImage](api/native-image.md) +* [screen](api/screen.md) +* [shell](api/shell.md) + +## 開発 + + +* Coding Style (development/coding-style.md) +* Source Code Directory Structure (development/source-code-directory-structure.md) +* Technical Differences to NW.js (formerly node-webkit)(development/atom-shell-vs-node-webkit.md) +* Build System Overview (development/build-system-overview.md) +* Build Instructions (OS X) (development/build-instructions-osx.md) +* Build Instructions (Windows) (development/build-instructions-windows.md) +* Build Instructions (Linux) (development/build-instructions-linux.md) +* Debug Instructions (Windows) (development/debug-instructions-windows.md) +* Setting Up Symbol Server in debugger (development/setting-up-symbol-server.md) diff --git a/docs-translations/jp/api/app.md b/docs-translations/jp/api/app.md index 8d1099373589..168f7a093993 100644 --- a/docs-translations/jp/api/app.md +++ b/docs-translations/jp/api/app.md @@ -378,6 +378,7 @@ if (browserOptions.transparent) { // No transparency, so we load a fallback that uses basic styles. win.loadURL('file://' + __dirname + '/fallback.html'); } +``` ### `app.commandLine.appendSwitch(switch[, value])` diff --git a/docs-translations/jp/api/chrome-command-line-switches.md b/docs-translations/jp/api/chrome-command-line-switches.md index b3359eb322ce..b041cf3baffb 100644 --- a/docs-translations/jp/api/chrome-command-line-switches.md +++ b/docs-translations/jp/api/chrome-command-line-switches.md @@ -1,29 +1,32 @@ #サポートしているChromeコマンドラインスイッチ -ElectronでサポートしているChromeブラウザーで使用できるコマンドラインスイッチをこのページで一覧にしています。[app][app]モジュールの[ready][ready]イベントが出力される前にアプリのメインスクリプトに追加するために[app.commandLine.appendSwitch][append-switch]を使えます。 +> Electronでサポートされているコマンドラインスイッチ + +アプリケーションのメインスクリプトで[app.commandLine.appendSwitch][append-switch]を使うことで、[app][app]モジュールの[ready][ready]イベントが発行される前にコマンドラインスイッチを追加できます。 ```javascript -const app = require('electron').app; +const {app} = require('electron'); app.commandLine.appendSwitch('remote-debugging-port', '8315'); app.commandLine.appendSwitch('host-rules', 'MAP * 127.0.0.1'); -app.on('ready', function() { +app.on('ready', () => { // Your code here }); ``` -## --client-certificate=`path` - -クライアントの証明書ファイルの`path`を設定します。 ## --ignore-connections-limit=`domains` -接続数の制限を無視する`,`で分割した`domains`リスト +`domains`で指定されたリストは接続数制限を無視します。リストは`,`で区切られます。 ## --disable-http-cache HTTPリクエストのディスクキャッシュの無効化。 +## --disable-http2 + +HTTP/2 と SPDY/3.1 プロトコルを無効にします。 + ## --remote-debugging-port=`port` `port`で指定したHTTP越しのリモートデバッグの有効化。 @@ -42,7 +45,8 @@ $ electron --js-flags="--harmony_proxies --harmony_collections" your-app ## --proxy-bypass-list=`hosts` -ホスト一覧をセミコロンで分割したプロキシサーバーをバイパスしてするためにElectronに指示します。このフラグは、`--proxy-server`と同時に使われるときのみに影響します。 +プロキシを使用しないサーバーをセミコロンで区切って指定します。 +このフラグは、`--proxy-server`と同時に使われるときのみに影響します。 例: @@ -50,7 +54,7 @@ $ electron --js-flags="--harmony_proxies --harmony_collections" your-app 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`を除いてすべてのホストでプロキシサーバーが使われます。 +ローカルアドレス(`localhost`や`127.0.0.1`など)、`google.com`サブドメイン、`foo.com` サフィックスを含むホスト、`1.2.3.4:5678`を除いてすべてのホストでプロキシサーバーが使われます。 ## --proxy-pac-url=`url` @@ -58,7 +62,7 @@ app.commandLine.appendSwitch('proxy-bypass-list', ';*.google.com;*foo.com ## --no-proxy-server -プロキシサーバーを使わず、常に直接接続します。遠輝ほかのプロキシサーバーフラグを上書きします。 +プロキシサーバーを使わず、常に直接接続します。ほかのプロキシサーバーフラグを上書きします。 ## --host-rules=`rules` @@ -105,17 +109,17 @@ TLSフォールバックを許可する最小のSSL/TLSバージョン ("tls1" 不可視のページのレンダラープロセスの優先度を下げることからChromiumを防ぎます。 -このフラグは、グローバルですべてのレンダラープロセスに有効で、一つのウィンドウだけで無効化したい場合、[playing silent audio][play-silent-audio]をハックして対応します。 +このフラグは、グローバルですべてのレンダラープロセスに有効で、一つのウィンドウだけで無効化したい場合、[無音を再生する][play-silent-audio]というハックで対応します。 ## --enable-logging コンソールにChromiumのログを出力します。 -このスイッチは`app.commandLine.appendSwitch` で使えず、アプリがロードされるよりもパースしますが、同じ効果を受けるために`ELECTRON_ENABLE_LOGGING`を環境変数に設定します。 +ユーザーのアプリが読み込まれる前に解析されるため、`app.commandLine.appendSwitch`では使用できませんが、`ELECTRON_ENABLE_LOGGING`を環境変数に設定すると同じ効果を得ることができます。 ## --v=`log_level` -既定の最大アクティブなV-loggingレベルが付与されています。0が既定です。通常、正の値はV-loggingレベルに使用されます。 +標準のloggingレベルを設定します。0が既定です。通常、V-loggingレベルには正の値が使用されます。 `--enable-logging` が渡された時だけ、このスイッチは動作します。 diff --git a/docs-translations/jp/api/clipboard.md b/docs-translations/jp/api/clipboard.md index 816725395420..88a7fe4c1b4f 100644 --- a/docs-translations/jp/api/clipboard.md +++ b/docs-translations/jp/api/clipboard.md @@ -33,13 +33,13 @@ console.log(clipboard.readText('selection')); プレーンテキストとしてクリップボードに`text`を書き込みます。 -### `clipboard.readHtml([type])` +### `clipboard.readHTML([type])` * `type` String (optional) HTMLマークアップとして、クリップボードの内容を返します。 -### `clipboard.writeHtml(markup[, type])` +### `clipboard.writeHTML(markup[, type])` * `markup` String * `type` String (optional) diff --git a/docs-translations/jp/api/environment-variables.md b/docs-translations/jp/api/environment-variables.md index f5277f146591..75c0ab0c2a9a 100644 --- a/docs-translations/jp/api/environment-variables.md +++ b/docs-translations/jp/api/environment-variables.md @@ -1,5 +1,7 @@ # 環境変数 +> アプリケーションの設定や動作を、コードの変更なしで制御します。 + コマンドラインやアプリのコードよりも早く初期化されるために、Electronのいくつかの挙動は環境変数がコントロールしています。 POSIX shellでの例: @@ -47,6 +49,3 @@ Electronがクラッシュしたとき、Windowsのクラッシュダイアロ Linuxでグローバルメニューバーを使用できません。 -## `ELECTRON_HIDE_INTERNAL_MODULES` - -`require('ipc')`のような古い組み込みモジュールとの互換モードを無効にします。 diff --git a/docs-translations/jp/api/file-object.md b/docs-translations/jp/api/file-object.md index 2e497def56b4..ec2724cff1d2 100644 --- a/docs-translations/jp/api/file-object.md +++ b/docs-translations/jp/api/file-object.md @@ -1,5 +1,7 @@ # `File` object +> ファイルシステム上のファイルを扱うには、HTML5のFile APIを使用します。 + DOMのファイルインターフェイスにより、ユーザーはHTML 5 ファイルAPIで直接、ネイティブファイルで作業できるように、ネイティブファイル周りの抽象化を提供します。Electronは、ファイルシステム上のファイルの実際のパスを公開する`File`インターフェイスの`path`属性を追加します。 アプリ上にドラッグしたファイルの実際のパスを取得する例: @@ -10,16 +12,16 @@ DOMのファイルインターフェイスにより、ユーザーはHTML 5 フ @@ -39,30 +39,27 @@ app.on('ready', function() { ## 分割代入 -CoffeeScript か Babel を使っているなら、[分割代入][desctructuring-assignment]でビルトインモジュールの使用をより簡単にできます: +0.37の時点で、 、[分割代入][desctructuring-assignment]でビルトインモジュールの使用をより簡単にできます: ```javascript -const {app, BrowserWindow} = require('electron') +const {app, BrowserWindow} = require('electron'); ``` -しかし、素の JavaScript を使っている場合、Chrome が ES6 を完全サポートするまで待たなければいけません。 - -## Disable old styles of using built-in modules - -v0.35.0 以前は全てのビルトインモジュールは `require('module-name')` の形式で使われなければいけません。この形式は[多くの欠点][issue-387]がありますが、古いアプリケーションとの互換性のためにまだサポートしています。 - -古い形式を完全に無効にするために、環境変数 `ELECTRON_HIDE_INTERNAL_MODULES` を設定できます: +もし`electron`モジュール全体が必要であれば、requireして、それぞれのモジュールを`electron`からアクセスすることができます。 ```javascript -process.env.ELECTRON_HIDE_INTERNAL_MODULES = 'true' +const electron = require('electron'); +const {app, BrowserWindow} = electron; ``` -もしくは `hideInternalModules` API を呼んでください: +これは、次のコードと同じ意味を持ちます。 ```javascript -require('electron').hideInternalModules() +const electron = require('electron'); +const app = electron.app; +const BrowserWindow = electron.BrowserWindow; ``` [gui]: https://en.wikipedia.org/wiki/Graphical_user_interface [desctructuring-assignment]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment -[issue-387]: https://github.com/atom/electron/issues/387 +[issue-387]: https://github.com/electron/electron/issues/387 diff --git a/docs-translations/jp/api/web-view-tag.md b/docs-translations/jp/api/web-view-tag.md new file mode 100644 index 000000000000..e70f84f2d258 --- /dev/null +++ b/docs-translations/jp/api/web-view-tag.md @@ -0,0 +1,745 @@ +# `` タグ + +> 外部ウェブコンテンツを別のプロセスとフレームで表示します。 + +Electronアプリ内でウェブページのような外部コンテンツを埋め込む際は、`webview`タグを使用してください。外部コンテンツは`webview`コンテナ内に閉じ込められます。 +アプリケーションの埋め込みページは、外部コンテンツがどのように表示されるかを制御することができます。 + +`iframe`と違い、`webview`はあなたのアプリと別のプロセスで動作します。あなたのウェブページとは違う実行許可レベルを持っており、あなたのアプリと埋め込みコンテンツの間のデータのやり取りは非同期で行われます。これにより、あなたのアプリケーションは埋め込みコンテンツからの安全が確保されます。 + +セキュリティ的な理由で、`webview`は`nodeIntegration`が有効な`BrowserWindow`でしか使用できません。 + +## 例 + +ウェブページをあなたのアプリに埋め込むには、`webview`タグをあなたの埋め込みページ(外部コンテンツを表示するアプリのページ)に追加してください。 +もっとも単純な方法では、`webview`タグはウェブページの`src`と、見た目を制御する`style`を持っています。 + +```html + +``` + +もし、外部コンテンツを制御したい場合、`webview`イベントを受け取り、`webview`メソッドでそれらのイベントに返答するJavaScriptコードを書くことになります。 +下記にあるのはウェブページの読み込みが開始した時と、止まった時にイベントを受け取るサンプルで、読み込み中に"loading..."というメッセージを表示します。 + +```html + +``` + +## CSS Styling Notes + +もし、flexbox layouts(v.0.36.11以降)を使用する場合は、`webview`タグは子`object`要素が`webview`自体の高さと幅いっぱいとなるよう、内部で`display: flex;`を使用しているのに注意してください。 +インラインレイアウトとしたい時に`display: inline-flex;`を指定する以外には、この`display: flex;`は上書きしないでください。 + +`webview`は`hidden`属性か、`display: none;`と使用して非表示にする際に、いくつか問題があります。 +`browserplugin`オブジェクトの中での描画や、ウェブページを再度表示するために再読み込みした際などに通常とは異なる描画となる場合があります。 +`webview`を隠すオススメの方法としては、`width`と`height`をゼロに設定し、`flex`を通じて、0pxまで小さくできるようにします。 + +```html + +``` + +## Tag 属性 + +`webview`タグは下記のような属性を持っています: + +### `src` + +```html + +``` + +表示されているURLを返します。本属性への書き込みは、トップレベルナビゲーションを開始させます。 + +`src`の値を現在の値に再度設定することで、現在のページを再読み込みさせることができます。 + +また、`src`属性はdata URLを指定することができます(例: `data:text/plain,Hello, world!`)。 + +### `autosize` + +```html + +``` + +"on"の際は、`minwidth`, `minheight`, `maxwidth`, `maxheight`で設定された範囲内で、自動的に大きさが変化します。 +これらの制限値は、`autosize`が有効でない場合は影響しません。 +`autosize`が有効の際は、`webview`コンテナサイズは指定した最小値より小さくなりませんし、最大値より大きくなりません。 + +### `nodeintegration` + +```html + +``` + +"on"の際は、`webview`内のゲストページ(外部コンテンツ)でnode integrationが有効となり、`require`や`process`と行ったnode APIでシステムリソースにアクセスできるようになります。1 + +**注意:** 親ウィンドウでnode integrationが無効の場合は、`webview`でも常に無効になります。 + + +### `plugins` + +```html + +``` + +"on"の際は、ゲストページはブラウザプラグインを使用できます。 + +### `preload` + +```html + +``` + +ゲストページ上のスクリプトより先に実行されるスクリプトを指定してください。内部では、ゲストページ内で`require`で読み込まれるので、スクリプトのURLのプロトコルはは`file:`または`asar:`でなければなりません。 + +ゲストページがnode integrationが無効の場合でも、このスクリプトは全てのNode APIにアクセスできます。ただし、グローバルオブジェクトはこのスクリプトの実行終了後にすべて削除されます。 + +### `httpreferrer` + +```html + +``` + +ゲストページのためにリファラを設定します。 + +### `useragent` + +```html + +``` + +ページ遷移の前に、User agentを指定します。すでにページが読み込まれている場合は、`setUserAgent`メソッドを利用して変更してください。 + +### `disablewebsecurity` + +```html + +``` + +"on"の際は、ゲストページのウェブセキュリティは無効となります。 + +### `partition` + +```html + + +``` + +ページで使用されるセッションを設定します。もし、`partition`が`persist:`から始まる場合、アプリ上の同じ`partition`を指定した全てのページで有効な永続セッションを使用します。 +`persist:`接頭辞なしで指定した場合、メモリ内セッションを使用します。同じセッションを指定すると複数のページで同じセッションを使用できます。 +`partition`を設定しない場合は、アプリケーションのデフォルトセッションが使用されます。 + +レンダラプロセスのセッションは変更できないため、この値は最初のページ遷移の前に変更されないといけません。 +その後に変更をしようとしても、DOM例外を起こすことになります。 + +### `allowpopups` + +```html + +``` + +"on"の場合、ゲストページは新しいウィンドウを開くことができます。 + +### `blinkfeatures` + +```html + +``` + +有効にしたいblink featureを`,`で区切って指定します。 +サポートされている全ての機能は、[setFeatureEnabledFromString][blink-feature-string]にリストがあります。 + +## メソッド + +`webview`タグは、下記のようなメソッドを持っています: + +**注意:** webview要素はメソッドを使用する前に読み込まれていないといけません。 + +**例** + +```javascript +webview.addEventListener('dom-ready', () => { + webview.openDevTools(); +}); +``` + +### `.loadURL(url[, options])` + +* `url` URL +* `options` Object (optional) + * `httpReferrer` String - リファラURL + * `userAgent` String - リクエストに使用されるUser agent + * `extraHeaders` String - 追加のヘッダを"\n"で区切って指定します。 + +`url`をwebviewで読み込みます。`url`はプロトコル接頭辞(`http://`や`file://`など)を含んでいなければいけません。 + +### `.getURL()` + +ゲストページのURLを取得します。 + +### `.getTitle()` + +ゲストページのタイトルを返します。 + +### `.isLoading()` + +ゲストページが読み込み中かのbool値を返します。 + +### `.isWaitingForResponse()` + +ゲストページがページの最初の返答を待っているかのbool値を返します。 + +### `.stop()` + +実行待ち中のナビゲーションを中止します。 + +### `.reload()` + +ゲストページを再読み込みします。 + +### `.reloadIgnoringCache()` + +キャッシュを無効にして再読み込みします。 + +### `.canGoBack()` + +ページを戻ることができるかのbool値を返します。 + +### `.canGoForward()` + +ページを進むことができるかのbool値を返します。 + +### `.canGoToOffset(offset)` + +* `offset` Integer + +`offset`だけ、移動できるかのbool値を返します。 + +### `.clearHistory()` + +移動履歴をクリアします。 + +### `.goBack()` + +ページを戻ります。 + +### `.goForward()` + +ページを進みます。 + +### `.goToIndex(index)` + +* `index` Integer + +インデックスを指定して移動します。 + +### `.goToOffset(offset)` + +* `offset` Integer + +現在の場所からの移動量を指定して移動します。 + +### `.isCrashed()` + +レンダラプロセスがクラッシュしているかを返します。 + +### `.setUserAgent(userAgent)` + +* `userAgent` String + +ゲストページ用のUser agentを変更します。 + +### `.getUserAgent()` + +User agentを取得します。 + +### `.insertCSS(css)` + +* `css` String + +ゲストエージにCSSを追加します。 + +### `.executeJavaScript(code, userGesture, callback)` + +* `code` String +* `userGesture` Boolean - Default `false`. +* `callback` Function (optional) - Called after script has been executed. + * `result` + +ページ内で`code`を評価します。`userGesture`を設定した場合、ページ内でgesture contextを作成します。例えば`requestFullScreen`のようなユーザーの対応が必要なHTML APIでは、自動化時にこれが有利になる時があります。 + +### `.openDevTools()` + +DevToolsを開きます。 + +### `.closeDevTools()` + +DevToolsを閉じます。 + +### `.isDevToolsOpened()` + +DevToolsが開いているかのbool値を返します。 + +### `.isDevToolsFocused()` + +DevToolsがフォーカスを得ているかのbool値を返します。 + +### `.inspectElement(x, y)` + +* `x` Integer +* `y` Integer + +ゲストページの(`x`, `y`)の位置にある要素を調べます。 + +### `.inspectServiceWorker()` + +ゲストページのサービスワーカーのDevToolsを開きます。 + +### `.setAudioMuted(muted)` + +* `muted` Boolean + +ページをミュートするかを設定します。 + +### `.isAudioMuted()` + +ページがミュートされているかを返します。 + +### `.undo()` + +`undo` (元に戻す)を行います。 +Executes editing command `undo` in page. + +### `.redo()` + +`redo` (やり直し)を行います。 + +### `.cut()` + +`cut` (切り取り)を行います。 + +### `.copy()` + +`copy` (コピー)を行います。 + +### `.paste()` + +`paste` (貼り付け)を行います。 + +### `.pasteAndMatchStyle()` + +`pasteAndMatchStyle`(貼り付けてスタイルを合わせる)を行います。 + +### `.delete()` + +`delete` (削除)を行います。 + +### `.selectAll()` + +`selectAll` (全て選択)を行います。 + +### `.unselect()` + +`unselect` (選択を解除)を行います。 + +### `.replace(text)` + +* `text` String + +`replace` (置き換え)を行います。 + +### `.replaceMisspelling(text)` + +* `text` String + +`replaceMisspelling` (スペル違いを置き換え)を行います。 + +### `.insertText(text)` + +* `text` String + +`text`を選択された要素に挿入します。 + +### `.findInPage(text[, options])` + +* `text` String - 検索する文字列(空文字列にはできません) +* `options` Object (省略可) + * `forward` Boolean - 前方・後方のどちらを検索するかどうかです。省略時は`true`で前方に検索します。 + * `findNext` Boolean - 初回検索か、次を検索するかを選びます。省略時は`false`で、初回検索です。 + * `matchCase` Boolean - 大文字・小文字を区別するかを指定します。省略時は`false`で、区別しません。 + * `wordStart` Boolean - ワード始まりからの検索かを指定します。省略時は`false`で、語途中でもマッチします。 + * `medialCapitalAsWordStart` Boolean - `wordStart`指定時、CamelCaseの途中もワード始まりと見なすかを指定します。省略時は`false`です。 + +`text`をページ内全てから検索し、リクエストに使用するリクエストID(`Integer`)を返します。リクエストの結果は、[`found-in-page`](web-view-tag.md#event-found-in-page)イベントを介して受け取ることができます。 + +### `.stopFindInPage(action)` + +* `action` String - [`.findInPage`](web-view-tag.md#webviewtagfindinpage)リクエストを終わらせる時にとるアクションを指定します。 + * `clearSelection` - 選択をクリアします。 + * `keepSelection` - 選択を通常の選択へと変換します。 + * `activateSelection` - 選択ノードにフォーカスを当て、クリックします。 + +`action`にしたがって`webview`への`findInPage`リクエストを中止します。 + +### `.print([options])` + +`webview`で表示されているウェブページを印刷します。`webContents.print([options])`と同じです。 + +### `.printToPDF(options, callback)` + +`webview`のウェブサイトをPDFとして印刷します。`webContents.printToPDF(options, callback)`と同じです。 + +### `.send(channel[, arg1][, arg2][, ...])` + +* `channel` String +* `arg` (optional) + +`channel`を通じてレンダラプロセスに非同期メッセージを送ります。合わせて、 +任意のメッセージを送ることができます。レンダラプロセスは`ipcRenderer`モジュールを +使用して、`channel`イベントでメッセージを把握することができます。 + +サンプルが[webContents.send](web-contents.md#webcontentssendchannel-args)にあります。 + +### `.sendInputEvent(event)` + +* `event` Object + +入力イベント(`event`)をページに送ります。 + +`event`に関する詳しい説明は、[webContents.sendInputEvent](web-contents.md##webcontentssendinputeventevent)を +参照してください。 + +### `.getWebContents()` + +`webview`に関連付けられた[WebContents](web-contents.md)を取得します。 + +## DOM events + +`webview`タグで有効なイベントは次の通りです: + +### Event: 'load-commit' + +返り値: + +* `url` String +* `isMainFrame` Boolean + +読み込みが行われる時に発生するイベントです。 +これには、現在のドキュメントやサブフレームの読み込みが含まれます。ただし、非同期なリソース読み込みは含まれません。 + +### Event: 'did-finish-load' + +ナビゲーションが完了した際に発生するイベントです。 +言い換えると、タブ上の"くるくる"が止まった時に発生し、`onload`イベントが配信されます。 + +### Event: 'did-fail-load' + +返り値: + +* `errorCode` Integer +* `errorDescription` String +* `validatedURL` String +* `isMainFrame` Boolean + +`did-finish-load`と同じようですが、読み込みに失敗した時やキャンセルされた時に発生します。 +例えば、`window.stop()`が呼ばれた時などです。 + +### Event: 'did-frame-finish-load' + +返り値: + +* `isMainFrame` Boolean + +フレームがナビゲーションを終えた時に発生します。 + +### Event: 'did-start-loading' + +タブ上の"くるくる"が回転を始めた時点でこのイベントが発生します。 + +### Event: 'did-stop-loading' + +タブ上の"くるくる"が回転をやめた時点でこのイベントが発生します。 + +### Event: 'did-get-response-details' + +返り値: + +* `status` Boolean +* `newURL` String +* `originalURL` String +* `httpResponseCode` Integer +* `requestMethod` String +* `referrer` String +* `headers` Object +* `resourceType` String + +読み込むリソースの詳細がわかった時点で発生します。 +`status`は、リソースをダウンロードするソケット接続であるかを示します。 + +### Event: 'did-get-redirect-request' + +返り値: + +* `oldURL` String +* `newURL` String +* `isMainFrame` Boolean + +リソースの取得中に、リダイレクトを受け取った際に発生します。 + +### Event: 'dom-ready' + +該当フレーム内のドキュメントが読み込まれた時に発生します。 + +### Event: 'page-title-updated' + +返り値: + +* `title` String +* `explicitSet` Boolean + +ページのタイトルが設定された時に発生します。 +タイトルがurlから作られたものであれば、`explicitSet`は`false`になります。 + +### Event: 'page-favicon-updated' + +返り値: + +* `favicons` Array - URLの配列 + +ページのfavicon URLを受け取った時に発生します。 + +### Event: 'enter-html-full-screen' + +HTML APIでフルスクリーンになった際に発生します。 + +### Event: 'leave-html-full-screen' + +HTML APIでフルスクリーンでなくなった際に発生します。 + +### Event: 'console-message' + +返り値: + +* `level` Integer +* `message` String +* `line` Integer +* `sourceId` String + +ゲストウィンドウがコンソールメッセージを記録する際に発生します。 + +下記のサンプルは、埋め込まれたサイトのログを、log levelに関係なく親側に転送します。 + + +```javascript +webview.addEventListener('console-message', (e) => { + console.log('Guest page logged a message:', e.message); +}); +``` + +### Event: 'found-in-page' + +返り値: + +* `result` Object + * `requestId` Integer + * `finalUpdate` Boolean - 次のレスポンスが待っているかを示します。 + * `activeMatchOrdinal` Integer (optional) - このマッチの場所を示します。 + * `matches` Integer (optional) - マッチした数です。 + * `selectionArea` Object (optional) - 最初のマッチした場所です。 + +[`webview.findInPage`](web-view-tag.md#webviewtagfindinpage)リクエストで結果が得られた場合に発生します。 + +```javascript +webview.addEventListener('found-in-page', (e) => { + if (e.result.finalUpdate) + webview.stopFindInPage('keepSelection'); +}); + +const requestId = webview.findInPage('test'); +``` + +### Event: 'new-window' + +返り値: + +* `url` String +* `frameName` String +* `disposition` String -`default`, `foreground-tab`, `background-tab`, + `new-window`, `other`のどれかです。 +* `options` Object - 新しい`BrowserWindow`を作る際に使用されるoptionです。 + +ゲストページが新しいブラウザウィンドウを開こうとする際に発生します。 + +下記のサンプルは、新しいURLをシステムのデフォルトブラウザで開きます。 + +```javascript +const {shell} = require('electron'); + +webview.addEventListener('new-window', (e) => { + const protocol = require('url').parse(e.url).protocol; + if (protocol === 'http:' || protocol === 'https:') { + shell.openExternal(e.url); + } +}); +``` + +### Event: 'will-navigate' + +返り値: + +* `url` String + +ユーザーまたはページがナビゲーションを始めようとする際に発生します。これは、 +`window.location`が変更になる時や、ユーザーがリンクをクリックした際に発生します。 + +`.loadURL`や`.back`でプログラムによりナビゲーションが始まった場合は +このイベントは発生しません。 + +アンカーリンクのクリックや`window.location.hash`の変更といった、ページ内遷移でも、このイベントは発生しません。 +この場合は、`did-navigate-in-page`イベントを使ってください。 + +`event.preventDefault()`は使用しても__何も起こりません__。 + +### Event: 'did-navigate' + +返り値: + +* `url` String + +ナビゲーション終了時に呼ばれます。 + +アンカーリンクのクリックや`window.location.hash`の変更といった、ページ内遷移では、このイベントは発生しません。 +この場合は、`did-navigate-in-page`イベントを使ってください。 + +### Event: 'did-navigate-in-page' + +返り値: + +* `url` String + +ページ内遷移の際に発生します。 + +ページ内遷移の際は、ページURLは変更になりますが、ページ外部へのナビゲーションは発生しません。 +例として、アンカーリンクのクリック時や、DOMの`hashchange`イベントが発生した時にこれが起こります。 + +### Event: 'close' + +ゲストページそのものが、閉じられようとしている際に発生します。 + +下記のサンプルは、`webview`が閉じられる際に、`about:blank`にナビゲートします。 + +```javascript +webview.addEventListener('close', () => { + webview.src = 'about:blank'; +}); +``` + +### Event: 'ipc-message' + +返り値: + +* `channel` String +* `args` Array + +ゲストページが埋め込み元に非同期メッセージを送ってきた際に発生します。 + +`sendToHost`メソッドと、`ipc-message`イベントを利用すると、ゲストページと埋め込み元のページでデータのやり取りを簡単に行うことができます。 + +```javascript +// 埋め込み元ページ(があるページ)で +webview.addEventListener('ipc-message', (event) => { + console.log(event.channel); + // Prints "pong" +}); +webview.send('ping'); +``` + +```javascript +// ゲストページ(内)で +const {ipcRenderer} = require('electron'); +ipcRenderer.on('ping', () => { + ipcRenderer.sendToHost('pong'); +}); +``` + +### Event: 'crashed' + +プロセスがクラッシュした際に発生します。 + +### Event: 'gpu-crashed' + +GPUプロセスがクラッシュした際に発生します。 + +### Event: 'plugin-crashed' + +返り値: + +* `name` String +* `version` String + +プラグインプロセスがクラッシュした際に発生します。 + +### Event: 'destroyed' + +WebContentsが破壊された際に呼ばれます。 + +### Event: 'media-started-playing' + +メディアの再生が開始された際に呼ばれます。 + +### Event: 'media-paused' + +メディアが一時停止になるか、再生を終えた際に呼ばれます。 + +### Event: 'did-change-theme-color' + +返り値: + +* `themeColor` String + +ページのテーマカラーが変更になった際に呼ばれます。 +これは、下記のようなメタタグがある際に通常発生します: + +```html + +``` + +### Event: 'devtools-opened' + +DevToolsが開かれた際に発生します。 + +### Event: 'devtools-closed' + +DevToolsが閉じられた際に発生します。 + +### Event: 'devtools-focused' + +DevToolsにフォーカスが当たった際 / 開かれた際に発生します。 + +[blink-feature-string]: https://code.google.com/p/chromium/codesearch#chromium/src/out/Debug/gen/blink/platform/RuntimeEnabledFeatures.cpp&sq=package:chromium&type=cs&l=527 diff --git a/docs-translations/jp/faq/electron-faq.md b/docs-translations/jp/faq/electron-faq.md index eef30c013a3f..3991b71953cd 100644 --- a/docs-translations/jp/faq/electron-faq.md +++ b/docs-translations/jp/faq/electron-faq.md @@ -2,10 +2,12 @@ ## Electronは、いつ最新のChromeにアップグレードされますか? -ElectronのChromeバージョンは、通常、新しいChromeのstabeleバージョンがリリースされた後、1~2週間以内に上げられます。 +ElectronのChromeバージョンは、通常、新しいChromeのstabeleバージョンがリリースされた後、1~2週間以内に上げられます。ただし、この期間というのは保障されてはおらず、またバージョンアップでの作業量に左右されます。 また、Chromeのstableチャンネルのみを使用し、もし、重要な修正がbetaまたはdevチャンネルにある場合、それをバックポートします。 +もっと知りたければ、[セキュリティについて](../tutorial/security.md)をご参照ください。 + ## Electronは、いつ最新のNode.jsにアップグレードされますか? Node.js の新しいバージョンがリリースされたとき、私たちは Electron の Node.js を更新するのを通常約1か月待ちます。そのようにして、とても頻繁に発生している、新しい Node.js バージョンによって取り込まれたバグによる影響を避けることができます。 @@ -69,7 +71,7 @@ Electronに組み込まれているNode.jsの影響で, `module`, `exports`, `re ```javascript // In the main process. -var mainWindow = new BrowserWindow({ +var win = new BrowserWindow({ webPreferences: { nodeIntegration: false } @@ -113,7 +115,7 @@ console.log(require.resolve('electron')); "/path/to/Electron.app/Contents/Resources/atom.asar/renderer/api/lib/exports/electron.js" ``` -If it is something like もし、`node_modules/electron/index.js` のような形式の場合は、npm `electron` モジュールを削除するか、それをリネームします。 +もし、`node_modules/electron/index.js` のような形式の場合は、npm `electron` モジュールを削除するか、それをリネームします。 ```bash npm uninstall electron diff --git a/docs-translations/jp/tutorial/application-distribution.md b/docs-translations/jp/tutorial/application-distribution.md index f05b098514b9..19cc42979e13 100644 --- a/docs-translations/jp/tutorial/application-distribution.md +++ b/docs-translations/jp/tutorial/application-distribution.md @@ -25,7 +25,7 @@ electron/resources/app ## ファイルにアプリケーションをパッケージングする -すべてのソースコードをコピーすることでアプリケーションを提供する方法とは別に、アプリケーションのソースコードをユーザーに見えるのを避けるために、[asar](https://github.com/atom/asar) にアーカイブしてアプリケーションをパッケージ化することができます。 +すべてのソースコードをコピーすることでアプリケーションを提供する方法とは別に、アプリケーションのソースコードをユーザーに見えるのを避けるために、[asar](https://github.com/electron/asar) にアーカイブしてアプリケーションをパッケージ化することができます。 `app` フォルダの代わりに `asar` アーカイブを使用するためには、アーカイブファイルを `app.asar` という名前に変更し、Electron のリソースディレクトリに以下のように配置する必要があります。すると、Electron はアーカイブを読み込もうとし、それを開始します。 diff --git a/docs-translations/jp/tutorial/application-packaging.md b/docs-translations/jp/tutorial/application-packaging.md index e5196a6f6262..79b807d3111e 100644 --- a/docs-translations/jp/tutorial/application-packaging.md +++ b/docs-translations/jp/tutorial/application-packaging.md @@ -63,8 +63,8 @@ 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}); +const {BrowserWindow} = require('electron'); +let win = new BrowserWindow({width: 800, height: 600}); win.loadURL('file:///path/to/example.asar/static/index.html'); ``` @@ -78,7 +78,7 @@ Node API と同様、`asar` アーカイブはディレクトリのように扱 ```html @@ -89,7 +89,7 @@ $.get('file:///path/to/example.asar/file.txt', function(data) { アーカイブのチェックサムを検証する等の幾つかのケースでは、`asar` アーカイブをファイルとして読み込む必要があります。この目的のために、 `asar` サポートしないオリジナルの `fs` API を提供するビルトインの `original-fs` モジュールを使用できます。 ```javascript -var originalFs = require('original-fs'); +const originalFs = require('original-fs'); originalFs.readFileSync('/path/to/example.asar'); ``` @@ -146,4 +146,4 @@ $ asar pack app app.asar --unpack *.node このコマンドを実行した後、`app.asar` とは別に、アンパックされたファイルを含んだ`app.asar.unpacked` フォルダーが生成されます。ユーザーに提供するときには、`app.asar` と一緒にコピーしなければなりません。 -[asar]: https://github.com/atom/asar +[asar]: https://github.com/electron/asar diff --git a/docs-translations/jp/tutorial/desktop-environment-integration.md b/docs-translations/jp/tutorial/desktop-environment-integration.md index 96bf76d73709..6cd838afa7d7 100644 --- a/docs-translations/jp/tutorial/desktop-environment-integration.md +++ b/docs-translations/jp/tutorial/desktop-environment-integration.md @@ -8,14 +8,17 @@ 3つのオペレーティングシステム全てで、アプリケーションからユーザーに通知を送る手段が提供されています。通知を表示するためにオペレーティングシステムのネイティブ通知APIを使用しする[HTML5 Notification API](https://notifications.spec.whatwg.org/)で、Electronは、開発者に通知を送ることができます。 +**注意:** これはHTML5 APIですので、レンダラプロセス内のみで有効です。 + + ```javascript -var myNotification = new Notification('Title', { +let myNotification = new Notification('Title', { body: 'Lorem Ipsum Dolor Sit Amet' }); -myNotification.onclick = function () { - console.log('Notification clicked') -} +myNotification.onclick = () => { + console.log('Notification clicked'); +}; ``` オペレーティングシステム間でコードとユーザ体験は似ていますが、細かい違いがあります。 @@ -88,8 +91,8 @@ 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'); } }, +const dockMenu = Menu.buildFromTemplate([ + { label: 'New Window', click() { console.log('New Window'); } }, { label: 'New Window with Settings', submenu: [ { label: 'Basic' }, { label: 'Pro'} @@ -153,24 +156,24 @@ __Windows Media Playerの縮小表示ツールバー:__ アプリケーションに縮小表示ツールバーを設定するために、[BrowserWindow.setThumbarButtons][setthumbarbuttons]を使えます: ```javascript -const BrowserWindow = require('electron').BrowserWindow; +const {BrowserWindow} = require('electron'); const path = require('path'); -var win = new BrowserWindow({ +let win = new BrowserWindow({ width: 800, height: 600 }); win.setThumbarButtons([ { - tooltip: "button1", + tooltip: 'button1', icon: path.join(__dirname, 'button1.png'), - click: function() { console.log("button2 clicked"); } + click() { console.log("button2 clicked"); } }, { - tooltip: "button2", + tooltip: 'button2', icon: path.join(__dirname, 'button2.png'), - flags:['enabled', 'dismissonclick'], - click: function() { console.log("button2 clicked."); } + flags: ['enabled', 'dismissonclick'], + click() { console.log("button2 clicked."); } } ]); ``` @@ -189,25 +192,22 @@ __Audaciousのランチャーショートカット:__ ![audacious](https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles?action=AttachFile&do=get&target=shortcuts.png) -## タスクバーの進行状況バー (Windows & Unity) +## タスクバーの進行状況バー (Windows, OS X, Unity) Windowsでは、タスクバーボタンは、進行状況バーを表示するのに使えます。ウィンドウを切り替えることなくウィンドウの進行状況情報をユーザーに提供することができます。 +OS Xではプログレスバーはドックアイコンの一部として表示されます。 Unity DEは、ランチャーに進行状況バーの表示をするのと同様の機能を持っています。 __タスクバーボタン上の進行状況バー:__ ![Taskbar Progress Bar](https://cloud.githubusercontent.com/assets/639601/5081682/16691fda-6f0e-11e4-9676-49b6418f1264.png) -__Unityランチャーでの進行状況バー:__ - -![Unity Launcher](https://cloud.githubusercontent.com/assets/639601/5081747/4a0a589e-6f0f-11e4-803f-91594716a546.png) - ウィンドウに進行状況バーを設定するために、[BrowserWindow.setProgressBar][setprogressbar] APIを使えます: ```javascript -var window = new BrowserWindow({...}); -window.setProgressBar(0.5); +let win = new BrowserWindow({...}); +win.setProgressBar(0.5); ``` ## タスクバーでアイコンをオーバーレイする (Windows) @@ -223,8 +223,8 @@ __タスクバーボタンでのオーバーレイ:__ ウィンドウでオーバーレイアイコンを設定するために、[BrowserWindow.setOverlayIcon][setoverlayicon] APIを使用できます。 ```javascript -var window = new BrowserWindow({...}); -window.setOverlayIcon('path/to/overlay.png', 'Description for overlay'); +let win = new BrowserWindow({...}); +win.setOverlayIcon('path/to/overlay.png', 'Description for overlay'); ``` ## Windowのファイル表示 (OS X) @@ -240,9 +240,9 @@ __Represented file ポップアップメニュー:__ ウィンドウにrepresented fileを設定するために、[BrowserWindow.setRepresentedFilename][setrepresentedfilename] と [BrowserWindow.setDocumentEdited][setdocumentedited] APIsを使えます: ```javascript -var window = new BrowserWindow({...}); -window.setRepresentedFilename('/etc/passwd'); -window.setDocumentEdited(true); +let win = new BrowserWindow({...}); +win.setRepresentedFilename('/etc/passwd'); +win.setDocumentEdited(true); ``` [addrecentdocument]: ../api/app.md#appaddrecentdocumentpath-os-x-windows diff --git a/docs-translations/jp/tutorial/devtools-extension.md b/docs-translations/jp/tutorial/devtools-extension.md index 85eff391a43b..b631b4e406e9 100644 --- a/docs-translations/jp/tutorial/devtools-extension.md +++ b/docs-translations/jp/tutorial/devtools-extension.md @@ -4,7 +4,7 @@ DevToolsエクステンションのために、ソースコードをダウンローし、`BrowserWindow.addDevToolsExtension` APIを使って読み込みます。読み込んだエクステンションは維持されているので、ウィンドウを作成するとき、毎回APIをコールする必要はありません。 -** NOTE: React DevTools は動作しません。issue https://github.com/atom/electron/issues/915 でフォローしています** +** NOTE: React DevTools は動作しません。issue https://github.com/electron/electron/issues/915 をご覧ください ** 例えば、[React DevTools Extension](https://github.com/facebook/react-devtools)を使うために、最初にソースコードをダウンロードする必要があります。 @@ -22,7 +22,7 @@ const BrowserWindow = require('electron').remote.BrowserWindow; BrowserWindow.addDevToolsExtension('/some-directory/react-devtools/shells/chrome'); ``` -エクステンションをアンロードするために、名前と`BrowserWindow.removeDevToolsExtension` APIをコールすると、次回DevToolsを開いた時にはロードされません。 +エクステンションをアンロードするために、`BrowserWindow.removeDevToolsExtension` APIを名前を指定してコールすると、次回DevToolsを開いた時にはロードされません。 ```javascript BrowserWindow.removeDevToolsExtension('React Developer Tools'); @@ -34,12 +34,12 @@ BrowserWindow.removeDevToolsExtension('React Developer Tools'); ## バックグラウンドページ -今のところ、ElectronはChromエクステンションのバックグラウンドページ機能に対応していません。そのため、Electronでは、この機能に依存するDevToolsエクステンションは動作しません。 +今のところ、ElectronはChromeエクステンションのバックグラウンドページ機能に対応していません。そのため、Electronでは、この機能に依存するDevToolsエクステンションは動作しません。 ## `chrome.*` APIs いくつかのChromeエクステンションは、`chrome.*` APIsを使用しており、ElectronでそれらのAPIを実装するための努力をしていますが、すべては実装されていません。 -すべては実装されていない`chrome.*` APIについて考えると、もしDevToolsエクステンションが `chrome.devtools.*` 以外のAPIを使用していると、エクステンションは動作しない可能性が非常に高いです。 +すべては実装されていない`chrome.*` APIについて考えると、もしDevToolsエクステンションが `chrome.devtools.*` 以外のAPIを使用していると、エクステンションは動作しない可能性が非常に高いです。うまく動作しないエクステンションがあれば、APIのサポートの追加のために、issue trackerへご報告ください。 [devtools-extension]: https://developer.chrome.com/extensions/devtools diff --git a/docs-translations/jp/tutorial/electron-versioning.md b/docs-translations/jp/tutorial/electron-versioning.md new file mode 100644 index 000000000000..e8fc8b64c5f8 --- /dev/null +++ b/docs-translations/jp/tutorial/electron-versioning.md @@ -0,0 +1,11 @@ +# Electronのバージョン管理 + +もしあなたが経験豊富なNodeプログラマなら、`semver`(セマンティックバージョニング)のことはご存知でしょうし、バージョン番号をきっちり固定するのではなく、大まかな指針をパッケージの依存管理システムに指定していることと思います。ElectronはNodeとChromiumに強く依存しているので、少し難しい位置付けにあってsemverにきっちり従っていません。そのため、いつでも特定バージョンのElectronを参照しないといけません。 + +バージョン番号は次のルールに沿ってあげられます。 + + * メジャーバージョン: ElectronのAPIに大きな変更があった時- 例えば、`0.37.0`から`1.0.0`にアップグレードした時は、あなたはアプリケーションのアップデートをしなければなりません。 + * マイナーバージョン: ChromeのメジャーバージョンまたはNodeのマイナーバージョンのアップデート、またはElectronの重大な変更 - 例えば`1.0.0`から`1.1.0`にアップグレードした時は、あなたのアプリは多分動きますが、もしかしたら少々の変更をしないといけないかもしれません。 + * パッチ: 新しい機能やバグフィックス - `1.0.0`から`1.0.1`へのアップグレードでは、あなたのアプリはそのまま動くはずです。 + +もし、`electron-prebuilt`を使用しているのであれば、Electronのすべてのアップデートが開発者であるあなたによって手動で行われることを保証するために、固定バージョンを指定(`^1.1.0`ではなく`1.1.0`)することをお勧めします。 diff --git a/docs-translations/jp/tutorial/mac-app-store-submission-guide.md b/docs-translations/jp/tutorial/mac-app-store-submission-guide.md index 47d7a76b925b..fd42d55937d9 100644 --- a/docs-translations/jp/tutorial/mac-app-store-submission-guide.md +++ b/docs-translations/jp/tutorial/mac-app-store-submission-guide.md @@ -2,13 +2,11 @@ v0.34.0から、ElectronはMac App Store (MAS)にパッケージ化したアプリを登録することができます。このガイドでは、MASビルド用の制限とアプリを登録する方法についての情報を提供します。 -__Note:__ v0.36.0から、アプリがサンドボックス化された後GPUプロセスを妨害するバグがあるので、このバグが修正されるまでは、v0.35.xを使用することを推奨します。[issue #3871][issue-3871]で、このことに関する追加情報を確認できます。 - __Note:__ Mac App Storeにアプリを登録するには、費用が発生する[Apple Developer Program][developer-program]に登録する必要があります。 ## アプリを登録する方法 -次の手順は、Mac App Sotreにアプリを登録する簡単な手順を説明します。しかし、これらの手順はAppleによってAppが承認されることを保証するものではありません。Mac App Storeの要件については、Appleの[Submitting Your App][submitting-your-app]ガイドを読んでおく必要があります。 +次の手順は、Mac App Storeにアプリを登録する簡単な手順を説明します。しかし、これらの手順はAppleによってAppが承認されることを保証するものではありません。Mac App Storeの要件については、Appleの[Submitting Your App][submitting-your-app]ガイドを読んでおく必要があります。 ### 証明書の取得 @@ -44,11 +42,16 @@ Appleから証明書を取得した後、[Application Distribution](application- com.apple.security.app-sandbox + com.apple.security.application-groups + + your.bundle.id + ``` -次のスクリプトでアプリをサインします。 +_`your.bundle.id`は`Info.plist`で指定されているあなたのアプリのBundle IDに置き換えてください。_ +そして、次のスクリプトでアプリを署名します。 ```bash #!/bin/bash @@ -65,26 +68,31 @@ INSTALLER_KEY="3rd Party Mac Developer Installer: Company Name (APPIDENTITY)" FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper.app/" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper EH.app/" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper NP.app/" -if [ -d "$FRAMEWORKS_PATH/Squirrel.framework/Versions/A" ]; then - # Signing a non-MAS build. - codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Mantle.framework/Versions/A" - codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/ReactiveCocoa.framework/Versions/A" - codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Squirrel.framework/Versions/A" -fi -codesign -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH" +codesign -s "$APP_KEY" -f --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Electron Framework" +codesign -s "$APP_KEY" -f --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libffmpeg.dylib" +codesign -s "$APP_KEY" -f --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libnode.dylib" +codesign -s "$APP_KEY" -f --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework" +codesign -s "$APP_KEY" -f --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper.app/Contents/MacOS/$APP Helper" +codesign -s "$APP_KEY" -f --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper.app/" +codesign -s "$APP_KEY" -f --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper EH.app/Contents/MacOS/$APP Helper EH" +codesign -s "$APP_KEY" -f --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper EH.app/" +codesign -s "$APP_KEY" -f --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper NP.app/Contents/MacOS/$APP Helper NP" +codesign -s "$APP_KEY" -f --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper NP.app/" +codesign -s "$APP_KEY" -f --entitlements child.plist "$APP_PATH/Contents/MacOS/$APP" +codesign -s "$APP_KEY" -f --entitlements parent.plist "$APP_PATH" productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH" ``` -OS Xで、サンドボックスにアプリを新しく追加した場合、基本的な考え方は、Appleの[Enabling App Sandbox][enable-app-sandbox]を読み、エンタイトルメントファイルにアプリに必要なパーミッションキーを追加します。 +OS Xでのアプリのサンドボックス化を行うことが初めてなら、Appleの[Enabling App Sandbox][enable-app-sandbox]を通読し、基本的な考え方を確認してから、、エンタイトルメントファイルにアプリに必要なパーミッションキーを追加します。 -### Appをアップロードし、レビューに登録する +### Appをアップロードする。 -アプリに署名後、iTunes ConnectにアップロードするためにApplication Loaderを使用できます。アップロードする前に[created a record][create-record]を確認してください。そして[レビュー用にアプリを登録できます][submit-for-review]. +アプリに署名後、iTunes ConnectにアップロードするためにApplication Loaderを使用できます。アップロードする前に[created a record][create-record]を確認してください。 + +### アプリケーションを審査に提出 + +これらのステップを終えた後、[レビュー用にアプリを登録できます][submit-for-review]. ## MAS Buildの制限 @@ -131,7 +139,7 @@ Electron は次の暗号アルゴリズムを使用しています: * RC5 - http://people.csail.mit.edu/rivest/Rivest-rc5rev.pdf * RIPEMD - [ISO/IEC 10118-3](http://webstore.ansi.org/RecordDetail.aspx?sku=ISO%2FIEC%2010118-3:2004) -ERNの同意を取得するには、 [How to legally submit an app to Apple’s App Store when it uses encryption (or how to obtain an ERN)][ern-tutorial]を参照してくだsだい。 +ERNの同意を取得するには、 [How to legally submit an app to Apple's App Store when it uses encryption (or how to obtain an ERN)][ern-tutorial]を参照してください。 [developer-program]: https://developer.apple.com/support/compare-memberships/ [submitting-your-app]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourApp/SubmittingYourApp.html @@ -140,5 +148,6 @@ ERNの同意を取得するには、 [How to legally submit an app to Apple’s [create-record]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/CreatingiTunesConnectRecord.html [submit-for-review]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SubmittingTheApp.html [app-sandboxing]: https://developer.apple.com/app-sandboxing/ -[issue-3871]: https://github.com/atom/electron/issues/3871 +[issue-3871]: https://github.com/electron/electron/issues/3871 [ern-tutorial]: https://carouselapps.com/2015/12/15/legally-submit-app-apples-app-store-uses-encryption-obtain-ern/ +[temporary-exception]: https://developer.apple.com/library/mac/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/AppSandboxTemporaryExceptionEntitlements.html diff --git a/docs-translations/jp/tutorial/online-offline-events.md b/docs-translations/jp/tutorial/online-offline-events.md index 92503b04dfcb..3b4f669c1f2d 100644 --- a/docs-translations/jp/tutorial/online-offline-events.md +++ b/docs-translations/jp/tutorial/online-offline-events.md @@ -9,10 +9,11 @@ const electron = require('electron'); const app = electron.app; const BrowserWindow = electron.BrowserWindow; -var onlineStatusWindow; -app.on('ready', function() { +let onlineStatusWindow; + +app.on('ready', () => { onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); + onlineStatusWindow.loadURL(`file://${__dirname}/online-status.html`); }); ``` @@ -23,7 +24,7 @@ _online-status.html_ ``` -또 하나의 예를 들자면 다음 예제는 랜더러 프로세스에서 template API를 사용하여 +또 하나의 예를 들자면 다음 예시는 렌더러 프로세스에서 template API를 사용하여 어플리케이션 메뉴를 만듭니다: ```javascript -var template = [ +const template = [ { label: 'Edit', submenu: [ @@ -78,35 +78,24 @@ var template = [ { label: 'Reload', accelerator: 'CmdOrCtrl+R', - click: function(item, focusedWindow) { - if (focusedWindow) - focusedWindow.reload(); + click(item, focusedWindow) { + if (focusedWindow) focusedWindow.reload(); } }, { label: 'Toggle Full Screen', - accelerator: (function() { - if (process.platform == 'darwin') - return 'Ctrl+Command+F'; - else - return 'F11'; - })(), - click: function(item, focusedWindow) { + accelerator: process.platform === 'darwin' ? 'Ctrl+Command+F' : 'F11', + click(item, focusedWindow) { if (focusedWindow) focusedWindow.setFullScreen(!focusedWindow.isFullScreen()); } }, { label: 'Toggle Developer Tools', - accelerator: (function() { - if (process.platform == 'darwin') - return 'Alt+Command+I'; - else - return 'Ctrl+Shift+I'; - })(), - click: function(item, focusedWindow) { + accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I', + click(item, focusedWindow) { if (focusedWindow) - focusedWindow.toggleDevTools(); + focusedWindow.webContents.toggleDevTools(); } }, ] @@ -133,14 +122,14 @@ var template = [ submenu: [ { label: 'Learn More', - click: function() { require('electron').shell.openExternal('http://electron.atom.io') } + click() { require('electron').shell.openExternal('http://electron.atom.io'); } }, ] }, ]; -if (process.platform == 'darwin') { - var name = require('electron').app.getName(); +if (process.platform === 'darwin') { + const name = require('electron').remote.app.getName(); template.unshift({ label: name, submenu: [ @@ -179,7 +168,7 @@ if (process.platform == 'darwin') { { label: 'Quit', accelerator: 'Command+Q', - click: function() { app.quit(); } + click() { app.quit(); } }, ] }); @@ -195,7 +184,7 @@ if (process.platform == 'darwin') { ); } -var menu = Menu.buildFromTemplate(template); +const menu = Menu.buildFromTemplate(template); Menu.setApplicationMenu(menu); ``` @@ -223,7 +212,9 @@ Linux에선 각 창의 상단에 표시됩니다. `action`을 어플리케이션의 first responder에 전달합니다. 이 메서드는 Cocoa 메뉴 동작을 에뮬레이트 하는데 사용되며 보통 `MenuItem`의 `role` 속성에 사용됩니다. -**참고:** 이 메서드는 OS X에서만 사용할 수 있습니다. +OS X의 네이티브 액션에 대해 자세히 알아보려면 +[OS X Cocoa Event Handling Guide](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/EventOverview/EventArchitecture/EventArchitecture.html#//apple_ref/doc/uid/10000060i-CH3-SW7) +문서를 참고하세요. ### `Menu.buildFromTemplate(template)` @@ -235,7 +226,11 @@ Linux에선 각 창의 상단에 표시됩니다. 또한 `template`에는 다른 속성도 추가할 수 있으며 메뉴가 만들어질 때 해당 메뉴 아이템의 프로퍼티로 변환됩니다. -### `Menu.popup([browserWindow, x, y, positioningItem])` +## Instance Methods + +`menu` 객체는 다음과 같은 인스턴스 메서드를 가지고 있습니다: + +### `menu.popup([browserWindow, x, y, positioningItem])` * `browserWindow` BrowserWindow (optional) - 기본값은 `null`입니다. * `x` Number (optional) - 기본값은 -1입니다. @@ -248,20 +243,24 @@ Linux에선 각 창의 상단에 표시됩니다. `positioningItem` 속성은 메뉴 팝업 시 마우스 커서에 바로 위치시킬 메뉴 아이템의 인덱스입니다. (OS X에서만 지원합니다) -### `Menu.append(menuItem)` +### `menu.append(menuItem)` * `menuItem` MenuItem 메뉴의 리스트 끝에 `menuItem`을 삽입합니다. -### `Menu.insert(pos, menuItem)` +### `menu.insert(pos, menuItem)` * `pos` Integer * `menuItem` MenuItem `pos` 위치에 `menuItem`을 삽입합니다. -### `Menu.items()` +## Instance Properties + +`menu` 객체는 또한 다음과 같은 속성을 가지고 있습니다: + +### `menu.items` 메뉴가 가지고 있는 메뉴 아이템들의 배열입니다. @@ -321,7 +320,7 @@ OS X에선 지정한 어플리케이션 메뉴에 상관없이 메뉴의 첫번 이동하고 싶은 특정 그룹의 아이템들이 있을 경우 해당 그룹의 맨 첫번째 메뉴 아이템의 위치만을 지정하면 됩니다. -### 예제 +### 예시 메뉴 템플릿: @@ -372,4 +371,4 @@ OS X에선 지정한 어플리케이션 메뉴에 상관없이 메뉴의 첫번 ``` [AboutInformationPropertyListFiles]: https://developer.apple.com/library/ios/documentation/general/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html -[setMenu]: https://github.com/atom/electron/blob/master/docs/api/browser-window.md#winsetmenumenu-linux-windows +[setMenu]: ./browser-window.md#winsetmenumenu-linux-windows diff --git a/docs-translations/ko-KR/api/native-image.md b/docs-translations/ko-KR/api/native-image.md index 80be2beeafce..8c03b78e114b 100644 --- a/docs-translations/ko-KR/api/native-image.md +++ b/docs-translations/ko-KR/api/native-image.md @@ -1,5 +1,7 @@ # nativeImage +> PNG 또는 JPG 파일을 사용하여 트레이, 독, 어플리케이션 아이콘을 생성합니다. + Electron은 파일 경로 또는 `nativeImage` 인스턴스를 통해 이미지를 사용할 수 있는 API를 가지고 있습니다. `null`을 전달할 경우 빈 이미지가 생성됩니다. @@ -7,15 +9,15 @@ Electron은 파일 경로 또는 `nativeImage` 인스턴스를 통해 이미지 전달하여 이미지를 사용할 수 있습니다: ```javascript -var appIcon = new Tray('/Users/somebody/images/icon.png'); -var window = new BrowserWindow({icon: '/Users/somebody/images/window.png'}); +const appIcon = new Tray('/Users/somebody/images/icon.png'); +let win = new BrowserWindow({icon: '/Users/somebody/images/window.png'}); ``` -이 예제는 클립보드로부터 가져온 `nativeImage`로 트레이 메뉴를 생성합니다: +이 예시는 클립보드로부터 가져온 `nativeImage`로 트레이 메뉴를 생성합니다: ```javascript -var image = clipboard.readImage(); -var appIcon = new Tray(image); +const image = clipboard.readImage(); +const appIcon = new Tray(image); ``` ## 지원하는 포맷 @@ -23,7 +25,13 @@ var appIcon = new Tray(image); 현재 `PNG` 와 `JPEG` 이미지 포맷을 지원하고 있습니다. 손실 없는 이미지 압축과 투명도 지원을 위해 `PNG` 사용을 권장합니다. -그리고 Windows에서는 `ICO` 포맷도 사용할 수 있습니다. +Windows에서는 파일 경로로부터 `ICO` 포맷도 사용할 수 있으며, 가장 좋은 시각적 효과를 +얻기 위해 최소한 아이콘에 다음 사이즈를 포함하는 것을 권장합니다: + +* 16x16 +* 32x32 +* 64x64 +* 256x256 ## 고해상도 이미지 @@ -46,7 +54,7 @@ images/ ```javascript -var appIcon = new Tray('/Users/somebody/images/icon.png'); +let appIcon = new Tray('/Users/somebody/images/icon.png'); ``` 지원하는 DPI 접미사는 다음과 같습니다: @@ -66,14 +74,14 @@ var appIcon = new Tray('/Users/somebody/images/icon.png'); ## 템플릿 이미지 템플릿 이미지는 검은색과 명확한 색상(알파 채널)으로 이루어져 있습니다. 템플릿 이미지는 -단독 이미지로 사용되지 않고 다른 컨텐츠와 혼합되어 최종 외관 만드는데 사용됩니다. +단독 이미지로 사용되지 않고 다른 콘텐츠와 혼합되어 최종 외관 만드는데 사용됩니다. 가장 일반적으로 템플릿 이미지는 밝고 어두운 테마 색상으로 변경할 수 있는 메뉴 바 아이콘 등에 사용되고 있습니다. **참고:** 템플릿 이미지는 OS X 운영체제만 지원합니다. -템플릿 이미지를 지정하려면 다음 예제와 같이 파일명에 `Template` 문자열을 추가해야 +템플릿 이미지를 지정하려면 다음 예시와 같이 파일명에 `Template` 문자열을 추가해야 합니다: * `xxxTemplate.png` @@ -113,8 +121,7 @@ var appIcon = new Tray('/Users/somebody/images/icon.png'); ```javascript const nativeImage = require('electron').nativeImage; - -var image = nativeImage.createFromPath('/Users/somebody/images/icon.png'); +let image = nativeImage.createFromPath('/Users/somebody/images/icon.png'); ``` ### `image.toPng()` @@ -131,6 +138,15 @@ var image = nativeImage.createFromPath('/Users/somebody/images/icon.png'); 이미지를 data URL로 반환합니다. +### `image.getNativeHandle()` _OS X_ + +이미지의 네이티브 핸들 밑에 있는 C 포인터를 담은 [Buffer][buffer]을 반환합니다. +OS X에선, `NSImage` 인스턴스가 반환됩니다. + +참고로 반환된 포인터는 복사본이 아닌 네이티브 이미지의 밑에 있는 약한 포인터이며, +따라서 반드시 관련된 `nativeImage` 인스턴스가 확실하게 유지되고 있는지를 인지해야 +합니다. + ### `image.isEmpty()` 이미지가 비었는지 확인합니다. diff --git a/docs-translations/ko-KR/api/power-monitor.md b/docs-translations/ko-KR/api/power-monitor.md index 29a3aabc84a1..860ac5eecbd4 100644 --- a/docs-translations/ko-KR/api/power-monitor.md +++ b/docs-translations/ko-KR/api/power-monitor.md @@ -1,17 +1,17 @@ # powerMonitor -`power-monitor` 모듈은 PC의 파워 상태를 나타냅니다. (주로 노트북 등에서 사용됩니다) -이 모듈은 메인 프로세스에서만 사용할 수 있으며, (remote 모듈(RPC)을 사용해도 작동이 -됩니다) 메인 프로세스의 `app` 모듈에서 `ready` 이벤트를 호출하기 전까지 사용할 수 -없습니다. +> 파워의 상태 변경을 모니터링합니다. -예제: +이 모듈은 메인 프로세스에서만 사용할 수 있습니다. `app` 모듈의 `ready` 이벤트가 +발생한 이후에만 사용할 수 없습니다. + +예시: ```javascript -var app = require('app'); +const {app} = require('electron'); -app.on('ready', function() { - require('electron').powerMonitor.on('suspend', function() { +app.on('ready', () => { + require('electron').powerMonitor.on('suspend', () => { console.log('절전모드로 진입합니다!'); }); }); @@ -29,10 +29,10 @@ app.on('ready', function() { 시스템의 절전모드가 해제될 때 발생하는 이벤트입니다. -## Event: `on-ac` +## Event: `on-ac` _Windows_ 시스템이 AC 어뎁터 충전기를 사용하기 시작할 때 발생하는 이벤트입니다. -## Event: `on-battery` +## Event: `on-battery` _Windows_ 시스템이 배터리를 사용하기 시작할 때 발생하는 이벤트입니다. diff --git a/docs-translations/ko-KR/api/power-save-blocker.md b/docs-translations/ko-KR/api/power-save-blocker.md index 560b3fc9cef5..145b746cbbbc 100644 --- a/docs-translations/ko-KR/api/power-save-blocker.md +++ b/docs-translations/ko-KR/api/power-save-blocker.md @@ -1,14 +1,13 @@ # powerSaveBlocker -`powerSaveBlocker` 모듈은 시스템이 저전력(슬립) 모드로 진입하는 것을 막고 앱 시스템과 -화면이 항상 활성화 상태를 유지할 수 있도록 하는 몇가지 유틸리티를 제공하는 모듈입니다. +> 시스템이 저전력 (슬립) 모드로 진입하는 것을 막습니다. -예제: +예시: ```javascript -const powerSaveBlocker = require('electron').powerSaveBlocker; +const {powerSaveBlocker} = require('electron'); -var id = powerSaveBlocker.start('prevent-display-sleep'); +const id = powerSaveBlocker.start('prevent-display-sleep'); console.log(powerSaveBlocker.isStarted(id)); powerSaveBlocker.stop(id); diff --git a/docs-translations/ko-KR/api/process.md b/docs-translations/ko-KR/api/process.md index de24b5ac174e..946dd92eac50 100644 --- a/docs-translations/ko-KR/api/process.md +++ b/docs-translations/ko-KR/api/process.md @@ -1,14 +1,8 @@ -# process +# process -Electron의 `process` 객체는 기존의 node와는 달리 약간의 차이점이 있습니다: +> process 객체에 대한 확장 기능. -* `process.type` String - 프로세스의 타입, `browser` (메인 프로세스) 또는 - `renderer`가 됩니다. -* `process.versions['electron']` String - Electron의 버전. -* `process.versions['chrome']` String - Chromium의 버전. -* `process.resourcesPath` String - JavaScript 소스 코드의 경로. -* `process.mas` Boolean - Mac 앱 스토어용 빌드일 때 `true`로 지정됩니다. 다른 - 빌드일 땐 `undefined`로 지정됩니다. +`process` 객체는 Electron에서 약간 추가적인 기능이 있으며 API는 다음과 같습니다: ## Events @@ -22,9 +16,9 @@ Electron 내부 초기화 스크립트의 로드가 완료되고, 웹 페이지 ```javascript // preload.js -var _setImmediate = setImmediate; -var _clearImmediate = clearImmediate; -process.once('loaded', function() { +const _setImmediate = setImmediate; +const _clearImmediate = clearImmediate; +process.once('loaded', () => { global.setImmediate = _setImmediate; global.clearImmediate = _clearImmediate; }); @@ -34,6 +28,96 @@ process.once('loaded', function() { ### `process.noAsar` +Setting this to `true` can disable the support for `asar` archives in Node's +built-in modules. + +### `process.type` + +Current process's type, can be `"browser"` (i.e. main process) or `"renderer"`. + +### `process.versions.electron` + +Electron's version string. + +### `process.versions.chrome` + +Chrome's version string. + +### `process.resourcesPath` + +Path to the resources directory. + +### `process.mas` + +For Mac App Store build, this property is `true`, for other builds it is +`undefined`. + +### `process.windowsStore` + +If the app is running as a Windows Store app (appx), this property is `true`, +for otherwise it is `undefined`. + +### `process.defaultApp` + +When app is started by being passed as parameter to the default app, this +property is `true` in the main process, otherwise it is `undefined`. + +## Methods + +The `process` object has the following method: + +### `process.crash()` + +Causes the main thread of the current process crash. + +### `process.hang()` + +Causes the main thread of the current process hang. + +### `process.setFdLimit(maxDescriptors)` _OS X_ _Linux_ + +* `maxDescriptors` Integer + +Sets the file descriptor soft limit to `maxDescriptors` or the OS hard +limit, whichever is lower for the current process. + +### `process.getProcessMemoryInfo()` + +Returns an object giving memory usage statistics about the current process. Note +that all statistics are reported in Kilobytes. + +* `workingSetSize` - The amount of memory currently pinned to actual physical + RAM. +* `peakWorkingSetSize` - The maximum amount of memory that has ever been pinned + to actual physical RAM. +* `privateBytes` - The amount of memory not shared by other processes, such as + JS heap or HTML content. +* `sharedBytes` - The amount of memory shared between processes, typically + memory consumed by the Electron code itself + +### `process.getSystemMemoryInfo()` + +Returns an object giving memory usage statistics about the entire system. Note +that all statistics are reported in Kilobytes. + +* `total` - The total amount of physical memory in Kilobytes available to the + system. +* `free` - The total amount of memory not being used by applications or disk + cache. + +On Windows / Linux: + +* `swapTotal` - The total amount of swap memory in Kilobytes available to the + system. +* `swapFree` - The free amount of swap memory in Kilobytes available to the + system. + +---------------------------------------- + +## Properties + +### `process.noAsar` + 이 속성을 `true`로 지정하면 Node 빌트인 모듈의 `asar` 아카이브 지원을 비활성화 시킬 수 있습니다. @@ -41,6 +125,10 @@ process.once('loaded', function() { `process` 객체는 다음과 같은 메서드를 가지고 있습니다: +### `process.crash()` + +현재 프로세스의 메인 스레드에 크래시를 일으킵니다. + ### `process.hang()` 현재 프로세스의 주 스레드를 중단시킵니다. diff --git a/docs-translations/ko-KR/api/protocol.md b/docs-translations/ko-KR/api/protocol.md index 2b42226e2ffb..97a47c632dc0 100644 --- a/docs-translations/ko-KR/api/protocol.md +++ b/docs-translations/ko-KR/api/protocol.md @@ -1,28 +1,26 @@ # protocol -`protocol` 모듈은 이미 있는 프로토콜의 동작을 가로채거나 새로운 프로토콜을 만들 수 -있는 기능을 제공합니다. +> 커스텀 프로토콜을 등록하거나 이미 존재하능 프로토콜의 요청의 동작을 변경합니다. -다음 예제는 `file://` 프로토콜과 비슷한 일을 하는 커스텀 프로토콜을 설정합니다: +다음 예시는 `file://` 프로토콜과 비슷한 일을 하는 커스텀 프로토콜을 설정합니다: ```javascript -const electron = require('electron'); -const app = electron.app; +const {app, protocol} = require('electron'); const path = require('path'); -app.on('ready', function() { - var protocol = electron.protocol; - protocol.registerFileProtocol('atom', function(request, callback) { - var url = request.url.substr(7); - callback({path: path.normalize(__dirname + '/' + url)}); - }, function (error) { - if (error) - console.error('Failed to register protocol') - }); +app.on('ready', () => { + protocol.registerFileProtocol('atom', function (request, callback) { + const url = request.url.substr(7); + callback({path: path.normalize(__dirname + '/' + url)}); + }, function (error) { + if (error) + console.error('Failed to register protocol'); + }); }); ``` -**참고:** 이 모듈은 `app` 모듈의 `ready` 이벤트가 발생한 이후에만 사용할 수 있습니다. +**참고:** 모든 메서드는 따로 표기하지 않는 한 `app` 모듈의 `ready` 이벤트가 발생한 +이후에만 사용할 수 있습니다. ## Methods @@ -32,12 +30,40 @@ app.on('ready', function() { * `schemes` Array - 표준 스킴으로 등록할 커스텀 스킴 리스트 -표준 `scheme`의 형식은 RFC 3986 [일반 URI 구문](https://tools.ietf.org/html/rfc3986#section-3) -표준을 따릅니다. 이 형식은 `file:`과 `filesystem:`을 포함합니다. +표준 스킴의 형식은 RFC 3986 [일반 URI 문법](https://tools.ietf.org/html/rfc3986#section-3) +표준을 따릅니다. 예를 들어 `http`와 `https` 같은 표준 스킴과 `file`과 같은 표준이 +아닌 스킴이 있습니다. + +표준 스킴으로 등록하면, 상대, 절대 경로의 리소스를 올바르게 취할 수 있습니다. 다른 +경우엔 스킴이 상대 경로 URL에 대한 분석 기능이 제외된 `file` 프로토콜과 같이 +작동합니다. + +예를 들어 다음과 같은 페이지에서 표준 스킴 등록 절차 없이 커스텀 프로토콜을 사용하여 +이미지를 로드하려 했을 때, 표준 스킴으로 등록되지 않은 상대 경로 URL을 인식하지 못하고 +로드에 실패하게 됩니다: + +```html + + + +``` + +따라서 커스텀 프로토콜을 등록하여 `http` 프로토콜을 덮어 쓰려면, 표준 스킴으로 +등록해야만 합니다: + +```javascript +protocol.registerStandardSchemes(['atom']); +app.on('ready', () => { + protocol.registerHttpProtocol('atom', ...); +}); +``` + +**참고:** 이 메서드는 `app` 모듈의 `ready` 이벤트가 발생하기 이전에만 사용할 수 +있습니다. ### `protocol.registerServiceWorkerSchemes(schemes)` -* `schemes` Array - 등록될 서비스 워커를 조작할 커스텀 스키마 +* `schemes` Array - 서비스 워커를 제어하기 위해 등록될 커스텀 스킴. ### `protocol.registerFileProtocol(scheme, handler[, completion])` @@ -63,12 +89,12 @@ The `uploadData` is an array of `data` objects: * `bytes` Buffer - Content being sent. * `file` String - Path of file being uploaded. -`request`를 처리할 때 반드시 파일 경로 또는 `path` 속성을 포함하는 객체를 인자에 +`request`를 처리할 때 반드시 파일 경로 또는 `path` 속성을 포함하는 객체를 인수에 포함하여 `callback`을 호출해야 합니다. 예: `callback(filePath)` 또는 `callback({path: filePath})`. -만약 `callback`이 아무 인자도 없이 호출되거나 숫자나 `error` 프로퍼티를 가진 객체가 -인자로 전달될 경우 `request`는 지정한 `error` 코드(숫자)를 출력합니다. 사용할 수 있는 +만약 `callback`이 아무 인수도 없이 호출되거나 숫자나 `error` 프로퍼티를 가진 객체가 +인수로 전달될 경우 `request`는 지정한 `error` 코드(숫자)를 출력합니다. 사용할 수 있는 에러 코드는 [네트워크 에러 목록][net-error]에서 확인할 수 있습니다. 기본적으로 `scheme`은 `http:`와 같이 처리됩니다. `file:`과 같이 "일반적인 URI 문법" @@ -87,14 +113,14 @@ The `uploadData` is an array of `data` objects: 속성을 포함하는 객체와 함께 호출되어야 한다는 점을 제외하면 `registerFileProtocol`과 사용법이 같습니다. -예제: +예시: ```javascript -protocol.registerBufferProtocol('atom', function(request, callback) { +protocol.registerBufferProtocol('atom', (request, callback) => { callback({mimeType: 'text/html', data: new Buffer('
Response
')}); -}, function (error) { +}, (error) => { if (error) - console.error('Failed to register protocol') + console.error('Failed to register protocol'); }); ``` @@ -134,8 +160,8 @@ HTTP 요청을 응답으로 전송할 `scheme`의 프로토콜을 등록합니 POST 요청에는 반드시 `uploadData` 객체가 제공되어야 합니다. * `uploadData` object - * `contentType` String - 컨텐츠의 MIME 타입. - * `data` String - 전송할 컨텐츠. + * `contentType` String - 콘텐츠의 MIME 타입. + * `data` String - 전송할 콘텐츠. ### `protocol.unregisterProtocol(scheme[, completion])` diff --git a/docs-translations/ko-KR/api/remote.md b/docs-translations/ko-KR/api/remote.md index 3a09084acdef..b8489d119e72 100644 --- a/docs-translations/ko-KR/api/remote.md +++ b/docs-translations/ko-KR/api/remote.md @@ -1,25 +1,26 @@ # remote -`remote` 모듈은 메인 프로세스와 랜더러 프로세스(웹 페이지) 사이의 inter-process +> 메인 프로세스 모듈을 렌더러 프로세스에서 사용합니다. + +`remote` 모듈은 메인 프로세스와 렌더러 프로세스(웹 페이지) 사이의 inter-process (IPC) 통신을 간단하게 추상화 한 모듈입니다. Electron의 메인 프로세스에선 GUI와 관련 있는(`dialog`, `menu`등) 모듈만 사용할 수 -있습니다. 랜더러 프로세스에서 이러한 모듈들을 사용하려면 `ipc` 모듈을 통해 메인 -프로세스와 inter-process 통신을 해야합니다. 또한, `remote` 모듈을 사용하면 +있습니다. 렌더러 프로세스에서 이러한 모듈들을 사용하려면 `ipc` 모듈을 통해 메인 +프로세스와 inter-process 통신을 해야 합니다. 또한, `remote` 모듈을 사용하면 inter-process 통신을 하지 않고도 간단한 API를 통해 직접 메인 프로세스의 모듈과 메서드를 사용할 수 있습니다. 이 개념은 Java의 [RMI][rmi]와 비슷합니다. -다음 예제는 랜더러 프로세스에서 브라우저 창을 만드는 예제입니다: +다음 예시는 렌더러 프로세스에서 브라우저 창을 만드는 예시입니다: ```javascript -const remote = require('electron').remote; -const BrowserWindow = remote.BrowserWindow; +const {BrowserWindow} = require('electron').remote; -var win = new BrowserWindow({ width: 800, height: 600 }); +let win = new BrowserWindow({width: 800, height: 600}); win.loadURL('https://github.com'); ``` -**참고:** 반대로 메인 프로세스에서 랜더러 프로세스에 접근 하려면 [webContents.executeJavascript](web-contents.md#webcontentsexecutejavascriptcode-usergesture) +**참고:** 반대로 메인 프로세스에서 렌더러 프로세스에 접근 하려면 [webContents.executeJavascript](web-contents.md#webcontentsexecutejavascriptcode-usergesture) 메서드를 사용하면 됩니다. ## Remote 객체 @@ -29,21 +30,22 @@ win.loadURL('https://github.com'); 메서드를 호출하거나, 객체에 접근하거나, 생성자로 객체를 생성하는 등의 작업은 실질적으로 동기형 inter-process 메시지를 보냅니다. -위의 예제에서 사용한 두 `BrowserWindow`와 `win`은 remote 객체입니다. 그리고 -`new BrowserWindow`이 생성하는 `BrowserWindow` 객체는 랜더러 프로세스에서 생성되지 -않습니다. 대신에 이 `BrowserWindow` 객체는 메인 프로세스에서 생성되며 랜더러 +위의 예시에서 사용한 두 `BrowserWindow`와 `win`은 remote 객체입니다. 그리고 +`new BrowserWindow`이 생성하는 `BrowserWindow` 객체는 렌더러 프로세스에서 생성되지 +않습니다. 대신에 이 `BrowserWindow` 객체는 메인 프로세스에서 생성되며 렌더러 프로세스에 `win` 객체와 같이 이에 대응하는 remote 객체를 반환합니다. -참고로 remote를 통해선 [enumerable 속성](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties)을 -가진 프로퍼티에만 접근할 수 있습니다. +참고로 remote 객체가 처음 참조될 때 표시되는 +[enumerable 속성](https://developer.mozilla.org/ko/docs/Web/JavaScript/Enumerability_and_ownership_of_properties)은 +remote를 통해서만 접근할 수 있습니다. ## Remote 객체의 생명 주기 -Electron은 랜더러 프로세스의 remote 객체가 살아있는 한(다시 말해서 GC(garbage +Electron은 렌더러 프로세스의 remote 객체가 살아있는 한(다시 말해서 GC(garbage collection)가 일어나지 않습니다) 대응하는 메인 프로세스의 객체는 릴리즈되지 않습니다. Remote 객체가 GC 되려면 대응하는 메인 프로세스 내부 객체의 참조가 해제되어야만 합니다. -만약 remote 객체가 랜더러 프로세스에서 누수가 생겼다면 (예시: 맵에 저장하고 할당 +만약 remote 객체가 렌더러 프로세스에서 누수가 생겼다면 (예시: 맵에 저장하고 할당 해제하지 않음) 대응하는 메인 프로세스의 객체도 누수가 생깁니다. 그래서 remote 객체를 사용할 땐 메모리 누수가 생기지 않도록 매우 주의해서 사용해야 합니다. @@ -51,71 +53,67 @@ Remote 객체가 GC 되려면 대응하는 메인 프로세스 내부 객체의 ## 메인 프로세스로 콜백 넘기기 -메인 프로세스의 코드는 `remote` 모듈을 통해 랜더러 프로세스가 전달하는 콜백 함수를 +메인 프로세스의 코드는 `remote` 모듈을 통해 렌더러 프로세스가 전달하는 콜백 함수를 받을 수 있습니다. 하지만 이 작업은 반드시 주의를 기울여 사용해야 합니다. 첫째, 데드락을 피하기 위해 메인 프로세스로 전달된 콜백들은 비동기로 호출됩니다. 이러한 -이유로 메인 프로세스로 전달된 콜백들의 반환 값을 내부 함수에서 언제나 정상적으로 받을 +이유로 메인 프로세스에 전달된 콜백의 반환 값을 내부 함수에서 언제나 정상적으로 받을 것이라고 예측해선 안됩니다. -예를 들어 메인 프로세스에서 `Array.map` 같은 메서드를 사용할 때 랜더러 프로세스에서 +예를 들어 메인 프로세스에서 `Array.map` 같은 메서드를 사용할 때 렌더러 프로세스에서 전달된 함수를 사용해선 안됩니다: ```javascript // mapNumbers.js 메인 프로세스 -exports.withRendererCallback = function(mapper) { +exports.withRendererCallback = (mapper) => { return [1,2,3].map(mapper); -} +; -exports.withLocalCallback = function() { - return exports.mapNumbers(function(x) { - return x + 1; - }); -} +exports.withLocalCallback = () => { + return exports.mapNumbers(x => x + 1); +}; ``` ```javascript -// 랜더러 프로세스 -var mapNumbers = require("remote").require("./mapNumbers"); +// 렌더러 프로세스 +const mapNumbers = require('remote').require('./mapNumbers'); -var withRendererCb = mapNumbers.withRendererCallback(function(x) { - return x + 1; -}) +const withRendererCb = mapNumbers.withRendererCallback(x => x + 1); -var withLocalCb = mapNumbers.withLocalCallback() +const withLocalCb = mapNumbers.withLocalCallback(); -console.log(withRendererCb, withLocalCb) // [true, true, true], [2, 3, 4] +console.log(withRendererCb, withLocalCb); // [true, true, true], [2, 3, 4] ``` -보다시피 랜더러 콜백의 동기 반환 값은 예상되지 않은 처리입니다. -그리고 메인 프로세스에서 처리한 함수의 반환 값과 일치하지 않습니다. +보다시피 동기적인 렌더러 콜백 함수의 반환 값은 예상되지 않은 값입니다. 그리고 메인 +프로세스에서 처리한 함수의 반환 값과 일치하지 않습니다. 둘째, 콜백들은 메인 프로세스로 전달, 호출된 이후에도 자동으로 함수의 참조가 릴리즈 되지 않습니다. 함수 참조는 메인 프로세스에서 GC가 일어나기 전까지 계속 프로세스에 남아있게 됩니다. -다음 코드를 보면 느낌이 올 것입니다. 이 예제는 remote 객체에 `close` 이벤트 콜백을 -설치합니다: +다음 코드를 보면 느낌이 올 것입니다. 이 예시는 remote 객체에 `close` 이벤트 콜백을 +등록합니다: ```javascript -var remote = require('remote'); +const remote = require('remote'); -remote.getCurrentWindow().on('close', function() { +remote.getCurrentWindow().on('close', () => { // blabla... }); ``` -하지만 이 코드 처럼 이벤트를 명시적으로 제거하지 않는 이상 콜백 함수의 참조가 계속해서 -메인 프로세스에 남아있게 됩니다. 만약 명시적으로 콜백을 제거하지 않으면 매 번 창을 -새로고침 할 때마다 콜백을 새로 설치합니다. 게다가 이전 콜백이 제거되지 않고 계속해서 -쌓이면서 메모리 누수가 발생합니다. +하지만 이 코드와 같이 등록된 이벤트는 명시적으로 제거하지 않는 이상 콜백 함수의 참조가 +계속해서 메인 프로세스에 남아있게 됩니다. 만약 명시적으로 콜백을 제거하지 않으면 매 번 +창을 새로고침 할 때마다 콜백을 새로 설치합니다. 게다가 이전 콜백이 제거되지 않고 +계속해서 쌓이면서 메모리 누수가 발생합니다. -설상가상으로 이전에 설치된 콜백의 콘텍스트가 릴리즈 되고 난 후(예: 페이지 새로고침) +설상가상으로 이전에 등록된 콜백의 컨텍스트가 릴리즈 되고 난 후 (e.g. 페이지 새로고침) `close` 이벤트가 발생하면 예외가 발생하고 메인 프로세스가 작동 중지됩니다. -이러한 문제를 피하려면 랜더러 프로세스에서 메인 프로세스로 넘긴 함수의 참조를 사용 후 +이러한 문제를 피하려면 렌더러 프로세스에서 메인 프로세스로 넘긴 함수의 참조를 사용 후 확실하게 제거해야 합니다. 작업 후 이벤트 콜백을 포함하여 책임 있게 함수의 참조를 -제거하거나 메인 프로세스에서 랜더러 프로세스가 종료될 때 내부적으로 함수 참조를 +제거하거나 메인 프로세스에서 렌더러 프로세스가 종료될 때 내부적으로 함수 참조를 제거하도록 설계해야 합니다. ## 메인 프로세스의 빌트인 모듈에 접근 diff --git a/docs-translations/ko-KR/api/screen.md b/docs-translations/ko-KR/api/screen.md index 6cc6535235b6..7ac3539016d9 100644 --- a/docs-translations/ko-KR/api/screen.md +++ b/docs-translations/ko-KR/api/screen.md @@ -1,52 +1,49 @@ # screen -`screen` 모듈은 화면 크기, 디스플레이, 커서 위치 등등의 다양한 정보를 가져옵니다. -이 모듈은 `app` 모듈의 `ready` 이벤트가 발생하기 전까지 사용할 수 없습니다. +> 화면 크기, 디스플레이, 커서 위치 등의 정보를 가져옵니다. + +이 모듈은 `app` 모듈의 `ready` 이벤트가 발생하기 전까지 사용할 수 없습니다. (호출 또는 +모듈 포함) `screen`은 [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)를 상속 받았습니다. -**참고:** 랜더러 / DevTools에선 이미 DOM 속성이 `window.screen`을 가지고 있으므로 -`screen = require('screen')` 형식으로 모듈을 사용할 수 없습니다. 아래의 예제와 같이 +**참고:** 렌더러 / DevTools에선 이미 DOM 속성이 `window.screen`을 가지고 있으므로 +`screen = require('screen')` 형식으로 모듈을 사용할 수 없습니다. 아래의 예시와 같이 `electronScreen` 같은 이름으로 모듈 이름을 대체하여 사용해야 합니다. -다음 예제는 화면 전체를 채우는 윈도우 창을 생성합니다: +다음 예시는 화면 전체를 채우는 윈도우 창을 생성합니다: ```javascript -const electron = require('electron'); -const app = electron.app; -const BrowserWindow = electron.BrowserWindow; +const {app, BrowserWindow, screen: electronScreen} = require('electron'); -var mainWindow; +let win; -app.on('ready', function() { - var electronScreen = electron.screen; - var size = electronScreen.getPrimaryDisplay().workAreaSize; - mainWindow = new BrowserWindow({ width: size.width, height: size.height }); +app.on('ready', () => { + let {width, height} = electronScreen.getPrimaryDisplay().workAreaSize; + win = new BrowserWindow({width, height}); }); ``` -다음 예제는 확장 디스플레이에 윈도우를 생성합니다: +다음 예시는 확장 디스플레이에 윈도우를 생성합니다: ```javascript -var app = require('app'); -var BrowserWindow = require('browser-window'); +const {app, BrowserWindow, screen: electronScreen} = require('electron'); -var mainWindow; +let win; -app.on('ready', function() { - 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) { +app.on('ready', () => { + let displays = electronScreen.getAllDisplays(); + let externalDisplay = null; + for (let i in displays) { + if (displays[i].bounds.x !== 0 || displays[i].bounds.y !== 0) { externalDisplay = displays[i]; break; } } if (externalDisplay) { - mainWindow = new BrowserWindow({ + win = new BrowserWindow({ x: externalDisplay.bounds.x + 50, y: externalDisplay.bounds.y + 50 }); @@ -62,8 +59,8 @@ app.on('ready', function() { * `display` object * `id` Integer - 디스플레이에 관련된 유일 식별자. - * `rotation` Integer - 값은 0, 1, 2, 3이 될 수 있고, 각 값은 시계 방향을 기준으로 - 0, 90, 180, 270도의 화면 회전 상태로 표현됩니다. + * `rotation` Integer - 값은 0, 90, 180, 270이 될 수 있고, 각 값은 시계 방향을 + 기준으로 0, 90, 180, 270도의 화면 회전 상태를 표현합니다. * `scaleFactor` Number - 기기의 픽셀 스케일 크기. * `touchSupport` String - 터치 스크린의 여부, `available`, `unavailable`, `unknown` 값으로 반환됩니다. diff --git a/docs-translations/ko-KR/api/session.md b/docs-translations/ko-KR/api/session.md index 5bb4867eda0e..e21fa3b75bd6 100644 --- a/docs-translations/ko-KR/api/session.md +++ b/docs-translations/ko-KR/api/session.md @@ -1,17 +1,19 @@ # session +> 브라우저 세션, 쿠키, 캐시, 프록시 설정 등을 관리합니다. + `session` 모듈은 새로운 `Session` 객체를 만드는데 사용할 수 있습니다. 또한 존재하는 [`BrowserWindow`](browser-window.md)의 [`webContents`](web-contents.md)에서 `session` 속성으로 접근할 수도 있습니다. ```javascript -var BrowserWindow = require('browser-window'); +const {BrowserWindow} = require('electron'); -var win = new BrowserWindow({ width: 800, height: 600 }); -win.loadURL("http://github.com"); +let win = new BrowserWindow({width: 800, height: 600}); +win.loadURL('http://github.com'); -var ses = win.webContents.session; +let ses = win.webContents.session; ``` ## Methods @@ -44,7 +46,7 @@ var ses = win.webContents.session; ```javascript const session = require('electron').session; -var ses = session.fromPartition('persist:name'); +const ses = session.fromPartition('persist:name'); ``` ### Instance Events @@ -63,9 +65,9 @@ Electron의 `webContents`에서 `item`을 다운로드할 때 발생하는 이 틱부터 `item`을 사용할 수 없게 됩니다. ```javascript -session.defaultSession.on('will-download', function(event, item, webContents) { +session.defaultSession.on('will-download', (event, item, webContents) => { event.preventDefault(); - require('request')(item.getURL(), function(data) { + require('request')(item.getURL(), (data) => { require('fs').writeFileSync('/somewhere', data); }); }); @@ -82,19 +84,19 @@ session.defaultSession.on('will-download', function(event, item, webContents) { ```javascript // 모든 쿠키를 요청합니다. -session.defaultSession.cookies.get({}, function(error, cookies) { +session.defaultSession.cookies.get({}, (error, cookies) => { console.log(cookies); }); // url에 관련된 쿠키를 모두 가져옵니다. -session.defaultSession.cookies.get({ url : "http://www.github.com" }, function(error, cookies) { +session.defaultSession.cookies.get({url: 'http://www.github.com'}, (error, cookies) => { console.log(cookies); }); // 지정한 쿠키 데이터를 설정합니다. // 동일한 쿠키가 있으면 해당 쿠키를 덮어씁니다. -var cookie = { url : "http://www.github.com", name : "dummy_name", value : "dummy" }; -session.defaultSession.cookies.set(cookie, function(error) { +const cookie = {url: 'http://www.github.com', name: 'dummy_name', value: 'dummy'}; +session.defaultSession.cookies.set(cookie, (error) => { if (error) console.error(error); }); @@ -134,17 +136,18 @@ session.defaultSession.cookies.set(cookie, function(error) { #### `ses.cookies.set(details, callback)` * `details` Object - * `url` String - `url`에 관련된 쿠키를 가져옵니다. + * `url` String - 쿠키에 대한 `url` 링크. * `name` String - 쿠키의 이름입니다. 기본적으로 비워두면 생략됩니다. * `value` String - 쿠키의 값입니다. 기본적으로 비워두면 생략됩니다. * `domain` String - 쿠키의 도메인입니다. 기본적으로 비워두면 생략됩니다. * `path` String - 쿠키의 경로입니다. 기본적으로 비워두면 생략됩니다. * `secure` Boolean - 쿠키가 안전한 것으로 표시되는지에 대한 여부입니다. 기본값은 false입니다. - * `session` Boolean - 쿠키가 HttpOnly로 표시되는지에 대한 여부입니다. 기본값은 + * `session` Boolean - 쿠키가 Http 전용으로 표시되는지에 대한 여부입니다. 기본값은 false입니다. * `expirationDate` Double (optional) - UNIX 시간으로 표시되는 쿠키의 만료일에 - 대한 초 단위 시간입니다. 세션 쿠키에 제공되지 않습니다. + 대한 초 단위 시간입니다. 생략되면 쿠키가 세션 쿠기가 되며 세션 사이에 유지되지 + 않게 됩니다. * `callback` Function `details` 객체에 따라 쿠키를 설정합니다. 작업이 완료되면 `callback`이 @@ -282,8 +285,8 @@ window.webContents.session.enableNetworkEmulation({offline: true}); `setCertificateVerifyProc(null)`을 호출하면 기본 검증 프로세스로 되돌립니다. ```javascript -myWindow.webContents.session.setCertificateVerifyProc(function(hostname, cert, callback) { - if (hostname == 'github.com') +myWindow.webContents.session.setCertificateVerifyProc((hostname, cert, callback) => { + if (hostname === 'github.com') callback(true); else callback(false); @@ -293,18 +296,18 @@ myWindow.webContents.session.setCertificateVerifyProc(function(hostname, cert, c * `handler` Function * `webContents` Object - [WebContents](web-contents.md) 권한을 요청. - * `permission` String - 'media', 'geolocation', 'notifications', - 'midiSysex', 'pointerLock', 'fullscreen'의 나열. - * `callback` Function - 권한 허용 및 거부. + * `permission` String - 'media', 'geolocation', 'notifications', + 'midiSysex', 'pointerLock', 'fullscreen', 'openExternal'의 나열. + * `callback` Function - 권한 허용 및 거부. `session`의 권한 요청에 응답을 하는데 사용하는 핸들러를 설정합니다. `callback(true)`를 호출하면 권한 제공을 허용하고 `callback(false)`를 호출하면 권한 제공을 거부합니다. ```javascript -session.fromPartition(partition).setPermissionRequestHandler(function(webContents, permission, callback) { +session.fromPartition(partition).setPermissionRequestHandler((webContents, permission, callback) => { if (webContents.getURL() === host) { - if (permission == "notifications") { + if (permission === 'notifications') { callback(false); // 거부됨. return; } @@ -320,9 +323,25 @@ session.fromPartition(partition).setPermissionRequestHandler(function(webContent 호스트 리소버(resolver) 캐시를 지웁니다. +#### `ses.allowNTLMCredentialsForDomains(domains)` + +* `domains` String - 통합 인증을 사용하도록 설정할 쉼표로 구분된 서버의 리스트. + +동적으로 HTTP NTML 또는 Negotiate 인증을 위해 언제나 자격 증명을 보낼지 여부를 +설정합니다. + +```javascript +// 통합 인증을 위해 `example.com`, `foobar.com`, `baz`로 끝나는 +// 모든 url을 지정합니다. +session.defaultSession.allowNTLMCredentialsForDomains('*example.com, *foobar.com, *baz') + +// 통합 인증을 위해 모든 url을 지정합니다. +session.defaultSession.allowNTLMCredentialsForDomains('*') +``` + #### `ses.webRequest` -`webRequest` API는 생명주기의 다양한 단계에 맞춰 요청 컨텐츠를 가로채거나 변경할 수 +`webRequest` API는 생명주기의 다양한 단계에 맞춰 요청 콘텐츠를 가로채거나 변경할 수 있도록 합니다. 각 API는 `filter`와 `listener`를 선택적으로 받을 수 있습니다. `listener`는 API의 @@ -338,11 +357,11 @@ session.fromPartition(partition).setPermissionRequestHandler(function(webContent ```javascript // 다음 url에 대한 User Agent를 조작합니다. -var filter = { - urls: ["https://*.github.com/*", "*://electron.github.io"] +const filter = { + urls: ['https://*.github.com/*', '*://electron.github.io'] }; -session.defaultSession.webRequest.onBeforeSendHeaders(filter, function(details, callback) { +session.defaultSession.webRequest.onBeforeSendHeaders(filter, (details, callback) => { details.requestHeaders['User-Agent'] = "MyAgent"; callback({cancel: false, requestHeaders: details.requestHeaders}); }); @@ -367,7 +386,7 @@ session.defaultSession.webRequest.onBeforeSendHeaders(filter, function(details, `uploadData`는 `data` 객체의 배열입니다: * `data` Object - * `bytes` Buffer - 전송될 컨텐츠. + * `bytes` Buffer - 전송될 콘텐츠. * `file` String - 업로드될 파일의 경로. `callback`은 `response` 객체와 함께 호출되어야 합니다: @@ -419,7 +438,7 @@ HTTP 요청을 보내기 전 요청 헤더를 사용할 수 있을 때 `listener * `timestamp` Double * `requestHeaders` Object -#### `ses.webRequest.onHeadersReceived([filter,] listener)` +#### `ses.webRequest.onHeadersReceived([filter, ]listener)` * `filter` Object * `listener` Function @@ -444,6 +463,9 @@ HTTP 요청을 보내기 전 요청 헤더를 사용할 수 있을 때 `listener * `cancel` Boolean * `responseHeaders` Object (optional) - 이 속성이 제공되면 서버는 이 헤더와 함께 응답합니다. + * `statusLine` String (optional) - `responseHeaders`를 덮어쓸 땐, 헤더의 상태를 + 변경하기 위해 반드시 지정되어야 합니다. 그렇지 않은 경우, 기존의 응답 헤더의 상태가 + 사용됩니다. #### `ses.webRequest.onResponseStarted([filter, ]listener)` diff --git a/docs-translations/ko-KR/api/shell.md b/docs-translations/ko-KR/api/shell.md index c4831092f3cf..d8237db9b382 100644 --- a/docs-translations/ko-KR/api/shell.md +++ b/docs-translations/ko-KR/api/shell.md @@ -1,11 +1,14 @@ # shell +> 파일과 URL을 각 기본 어플리케이션을 통해 관리합니다. + `shell` 모듈은 데스크톱 환경 통합에 관련한 유틸리티를 제공하는 모듈입니다. -다음 예제는 설정된 URL을 유저의 기본 브라우저로 엽니다: +다음 예시는 설정된 URL을 유저의 기본 브라우저로 엽니다: ```javascript -const shell = require('electron').shell; +const {shell} = require('electron'); + shell.openExternal('https://github.com'); ``` @@ -36,14 +39,12 @@ shell.openExternal('https://github.com'); mailto: URL은 유저의 기본 이메일 에이전트로 URL을 엽니다.) 어플리케이션이 해당 URL을 열 수 있을 때 `true`를 반환합니다. 아니라면 `false`를 반환합니다. -역주: 폴더는 'file:\\\\C:\\'와 같이 지정하여 열 수 있습니다. (Windows의 경우) +**역주:** 탐색기로 폴더만 표시하려면 `'file://경로'`와 같이 지정하여 열 수 있습니다. ### `shell.moveItemToTrash(fullPath)` * `fullPath` String -Move the given file to trash and returns boolean status for the operation. - 지정한 파일을 휴지통으로 이동합니다. 작업의 성공여부를 boolean 형으로 리턴합니다. ### `shell.beep()` diff --git a/docs-translations/ko-KR/api/synopsis.md b/docs-translations/ko-KR/api/synopsis.md index e70dfbb3349f..cb2ea015e295 100644 --- a/docs-translations/ko-KR/api/synopsis.md +++ b/docs-translations/ko-KR/api/synopsis.md @@ -1,34 +1,34 @@ # 개요 +> Node.js와 Electron API를 사용하는 방법. + Electron은 모든 [Node.js의 built-in 모듈](http://nodejs.org/api/)과 third-party node 모듈을 완벽하게 지원합니다. ([네이티브 모듈](../tutorial/using-native-node-modules.md) 포함) 또한 Electron은 네이티브 데스크톱 어플리케이션을 개발 할 수 있도록 추가적인 built-in -모듈을 제공합니다. 몇몇 모듈은 메인 프로세스에서만 사용할 수 있고 어떤 모듈은 랜더러 +모듈을 제공합니다. 몇몇 모듈은 메인 프로세스에서만 사용할 수 있고 어떤 모듈은 렌더러 프로세스(웹 페이지)에서만 사용할 수 있습니다. 또한 두 프로세스 모두 사용할 수 있는 모듈도 있습니다. 기본적인 규칙으로 [GUI][gui]와 저 수준 시스템에 관련된 모듈들은 오직 메인 -프로세스에서만 사용할 수 있습니다. [메인 프로세스 vs. 랜더러 프로세스](../tutorial/quick-start.md#메인-프로세스) +프로세스에서만 사용할 수 있습니다. [메인 프로세스 vs. 렌더러 프로세스](../tutorial/quick-start.md#메인-프로세스) 컨셉에 익숙해야 모듈을 다루기 쉬우므로 관련 문서를 읽어 보는 것을 권장합니다. 메인 프로세스 스크립트는 일반 Node.js 스크립트와 비슷합니다: ```javascript -const electron = require('electron'); -const app = electron.app; -const BrowserWindow = electron.BrowserWindow; +const {app, BrowserWindow} = require('electron'); -var window = null; +let win = null; -app.on('ready', function() { - window = new BrowserWindow({width: 800, height: 600}); - window.loadURL('https://github.com'); +app.on('ready', () => { + win = new BrowserWindow({width: 800, height: 600}); + win.loadURL('https://github.com'); }); ``` -랜더러 프로세스도 예외적인 node module들을 사용할 수 있다는 점을 제외하면 일반 웹 +렌더러 프로세스도 예외적인 node module들을 사용할 수 있다는 점을 제외하면 일반 웹 페이지와 크게 다를게 없습니다: ```html @@ -36,8 +36,8 @@ app.on('ready', function() { @@ -48,35 +48,28 @@ app.on('ready', function() { ## 분리 할당 -만약 CoffeeScript나 Babel을 사용하고 있다면, 빌트인 모듈을 사용할 때 -[분리 할당][desctructuring-assignment]을 통해 직관적으로 사용할 수 있습니다: +0.37 버전부터, [분리 할당][destructuring-assignment]을 통해 빌트인 모듈을 더 +직관적으로 사용할 수 있습니다: ```javascript -const {app, BrowserWindow} = require('electron') +const {app, BrowserWindow} = require('electron'); ``` -아직 플레인 자바스크립트를 쓰고 있다면, Chrome이 ES6를 완전히 지원하기 전까지 기다려야 -합니다. - -## 이전 스타일의 빌트인 모듈 비활성화 - -v0.35.0 이전 버전에선 빌트인 모듈이 모두 `require('module-name')`같은 형식으로 -사용되었습니다. 하지만 [많은 단점][issue-387]이 있기 때문에 현재 API가 변경되었습니다. -하지만 오래된 앱의 호환성 유지를 위해 아직 구 버전 API를 지원하고 있습니다. - -완벽하게 모든 구 버전 API를 비활성화하려면 `ELECTRON_HIDE_INTERNAL_MODULES` 환경 -변수를 설정하면 됩니다: +모든 `electron` 모듈이 필요하다면, 먼저 require한 후 각 독립적인 모듈을 +`electron`에서 분리 할당함으로써 모듈을 사용할 수 있습니다. ```javascript -process.env.ELECTRON_HIDE_INTERNAL_MODULES = 'true' -``` +const electron = require('electron'); +const {app, BrowserWindow} = electron; + ``` -또는 `hideInternalModules` API를 사용해도 됩니다: +위 코드는 다음과 같습니다: ```javascript -require('electron').hideInternalModules() +const electron = require('electron'); +const app = electron.app; +const BrowserWindow = electron.BrowserWindow; ``` [gui]: https://en.wikipedia.org/wiki/Graphical_user_interface -[desctructuring-assignment]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment -[issue-387]: https://github.com/atom/electron/issues/387 +[destructuring-assignment]: https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment diff --git a/docs-translations/ko-KR/api/system-preferences.md b/docs-translations/ko-KR/api/system-preferences.md new file mode 100644 index 000000000000..9b53baee35bc --- /dev/null +++ b/docs-translations/ko-KR/api/system-preferences.md @@ -0,0 +1,85 @@ +# systemPreferences + +> 시스템 설정을 가져옵니다. + +## Methods + +### `systemPreferences.isDarkMode()` _OS X_ + +이 메서드는 시스템이 어두운 모드 상태인 경우 `true`를 반환하고 아닐 경우 `false`를 +반환합니다. + +### `systemPreferences.subscribeNotification(event, callback)` _OS X_ + +* `event` String +* `callback` Function + +OS X의 네이티브 알림을 구독하며, 해당하는 `event`가 발생하면 `callback`이 +`callback(event, userInfo)` 형태로 호출됩니다. `userInfo`는 알림과 함께 전송되는 +사용자 정보 딕셔너리를 포함하는 객체입니다. + +구독자의 `id`가 반환되며 `event`를 구독 해제할 때 사용할 수 있습니다. + +이 API는 후드에서 `NSDistributedNotificationCenter`를 구독하며, `event`의 예시 +값은 다음과 같습니다: + +* `AppleInterfaceThemeChangedNotification` +* `AppleAquaColorVariantChanged` +* `AppleColorPreferencesChangedNotification` +* `AppleShowScrollBarsSettingChanged` + +### `systemPreferences.unsubscribeNotification(id)` _OS X_ + +* `id` Integer + +`id`와 함께 구독자를 제거합니다. + +### `systemPreferences.getUserDefault(key, type)` _OS X_ + +* `key` String +* `type` String - `string`, `boolean`, `integer`, `float`, `double`, `url`, + `array`, `dictionary` 값이 될 수 있습니다. + +시스템 설정에서 `key`에 해당하는 값을 가져옵니다. + +OS X에선 API가 `NSUserDefaults`를 읽어들입니다. 유명한 `key`와 `type`은 다음과 +같습니다: + +* `AppleInterfaceStyle: string` +* `AppleAquaColorVariant: integer` +* `AppleHighlightColor: string` +* `AppleShowScrollBars: string` +* `NSNavRecentPlaces: array` +* `NSPreferredWebServices: dictionary` +* `NSUserDictionaryReplacementItems: array` + +### `systemPreferences.isAeroGlassEnabled()` _Windows_ + +이 메서드는 [DWM 컴포지션][dwm-composition] (Aero Glass)가 활성화 되어있을 때 +`true`를 반환합니다. 아닌 경우 `false`를 반환합니다. + +다음은 투명한 윈도우를 만들지, 일반 윈도우를 만들지를 판단하여 윈도우를 생성하는 +예시입니다 (투명한 윈도우는 DWM 컴포지션이 비활성화되어있을 시 작동하지 않습니다): + +```javascript +let browserOptions = {width: 1000, height: 800}; + +// 플랫폼이 지원하는 경우에만 투명 윈도우를 생성. +if (process.platform !== 'win32' || systemPreferences.isAeroGlassEnabled()) { + browserOptions.transparent = true; + browserOptions.frame = false; +} + +// 원도우 생성 +let win = new BrowserWindow(browserOptions); + +// 페이지 로드. +if (browserOptions.transparent) { + win.loadURL('file://' + __dirname + '/index.html'); +} else { + // 투명 윈도우 상태가 아니라면, 기본적인 스타일 사용 + win.loadURL('file://' + __dirname + '/fallback.html'); +} +``` + +[dwm-composition]:https://msdn.microsoft.com/en-us/library/windows/desktop/aa969540.aspx diff --git a/docs-translations/ko-KR/api/tray.md b/docs-translations/ko-KR/api/tray.md index d82b0a9d7e40..9d5d8a81bc9e 100644 --- a/docs-translations/ko-KR/api/tray.md +++ b/docs-translations/ko-KR/api/tray.md @@ -1,27 +1,22 @@ # Tray -`Tray`는 OS의 알림 영역에 아이콘을 표시합니다. 보통 컨텍스트 메뉴(context menu)를 -같이 사용합니다. +> 아이콘과 컨텍스트 메뉴를 시스템 알림 영역에 추가합니다. ```javascript -const electron = require('electron'); -const app = electron.app; -const Menu = electron.Menu; -const Tray = electron.Tray; +const {app, Menu, Tray} = require('electron'); -var appIcon = null; -app.on('ready', function(){ - appIcon = new Tray('/path/to/my/icon'); // 현재 어플리케이션 디렉터리를 기준으로 하려면 `__dirname + '/images/tray.png'` 형식으로 입력해야합니다. - var contextMenu = Menu.buildFromTemplate([ - { label: 'Item1', type: 'radio' }, - { label: 'Item2', type: 'radio' }, - { label: 'Item3', type: 'radio', checked: true }, - { label: 'Item4', type: 'radio' } +let appIcon = null; +app.on('ready', () => { + appIcon = new Tray('/path/to/my/icon'); // 현재 어플리케이션 디렉터리를 기준으로 하려면 `__dirname + '/images/tray.png'` 형식으로 입력해야 합니다. + const contextMenu = Menu.buildFromTemplate([ + {label: 'Item1', type: 'radio'}, + {label: 'Item2', type: 'radio'}, + {label: 'Item3', type: 'radio', checked: true}, + {label: 'Item4', type: 'radio'} ]); appIcon.setToolTip('이것은 나의 어플리케이션 입니다!'); appIcon.setContextMenu(contextMenu); }); - ``` __플랫폼별 한계:__ @@ -32,6 +27,8 @@ __플랫폼별 한계:__ 트레이 아이콘이 작동하도록 만들 수 있습니다. * 앱 알림 표시기는 컨텍스트 메뉴를 가지고 있을 때만 보입니다. * Linux에서 앱 표시기가 사용될 경우, `click` 이벤트는 무시됩니다. +* Windows에선 가장 좋은 시각적 효과를 얻기 위해 `ICO` 아이콘을 사용하는 것을 + 권장합니다. * Linux에서 각각 개별 `MenuItem`의 변경을 적용하려면 `setContextMenu`를 다시 호출해야 합니다. 예를 들면: @@ -74,7 +71,7 @@ appIcon.setContextMenu(contextMenu); 트레이 아이콘이 클릭될 때 발생하는 이벤트입니다. -__주의:__ `bounds`는 OS X 와 Windows에서만 작동합니다. +**참고:** `bounds`는 OS X 와 Windows에서만 작동합니다. ### Event: 'right-click' _OS X_ _Windows_ diff --git a/docs-translations/ko-KR/api/web-contents.md b/docs-translations/ko-KR/api/web-contents.md index 6e41f31155ce..41592011613c 100644 --- a/docs-translations/ko-KR/api/web-contents.md +++ b/docs-translations/ko-KR/api/web-contents.md @@ -1,17 +1,19 @@ # webContents +> 웹 페이지를 렌더링하고 제어합니다. + `webContents`는 [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)를 상속받았습니다. 웹 페이지의 렌더링과 관리를 책임지며 [`BrowserWindow`](browser-window.md)의 속성입니다. 다음은 `webContents` 객체에 -접근하는 예제입니다: +접근하는 예시입니다: ```javascript -const BrowserWindow = require('electron').BrowserWindow; +const {BrowserWindow} = require('electron'); -var win = new BrowserWindow({width: 800, height: 1500}); -win.loadURL("http://github.com"); +let win = new BrowserWindow({width: 800, height: 1500}); +win.loadURL('http://github.com'); -var webContents = win.webContents; +let webContents = win.webContents; ``` ## Events @@ -31,11 +33,13 @@ Returns: * `errorCode` Integer * `errorDescription` String * `validatedURL` String +* `isMainFrame` Boolean 이 이벤트는 `did-finish-load`와 비슷하나, 로드가 실패했거나 취소되었을 때 발생합니다. 예를 들면 `window.stop()`이 실행되었을 때 발생합니다. 발생할 수 있는 전체 에러 코드의 -목록과 설명은 [여기](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h)서 -확인할 수 있습니다. +목록과 설명은 [여기서](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h) +확인할 수 있습니다. 참고로 리다이렉트 응답은 `errorCode` -3과 함께 발생합니다; 이 +에러는 명시적으로 무시할 수 있습니다. ### Event: 'did-frame-finish-load' @@ -66,6 +70,7 @@ Returns: * `requestMethod` String * `referrer` String * `headers` Object +* `resourceType` String 요청한 리소스에 관련된 자세한 정보를 사용할 수 있을 때 발생하는 이벤트입니다. `status`는 리소스를 다운로드하기 위한 소켓 연결을 나타냅니다. @@ -221,7 +226,7 @@ Returns: * `issuerName` String - 인증서 발급자 이름 * `callback` Function -클라이언트 인증이 요청되었을 때 발생하는 이벤트 입니다. +클라이언트 인증이 요청되었을 때 발생하는 이벤트입니다. 사용법은 [`app`의 `select-client-certificate` 이벤트](app.md#event-select-client-certificate)와 같습니다. @@ -255,6 +260,7 @@ Returns: * `result` Object * `requestId` Integer * `finalUpdate` Boolean - 더 많은 응답이 따르는 경우를 표시합니다. + * `activeMatchOrdinal` Integer (optional) - 활성화 일치의 위치. * `matches` Integer (optional) - 일치하는 개수. * `selectionArea` Object (optional) - 첫 일치 부위의 좌표. @@ -287,7 +293,7 @@ Returns: * `image` NativeImage (optional) * `scale` Float (optional) -커서 타입이 변경될 때 발생하는 이벤트입니다. `type` 매개변수는 다음 값이 될 수 있습니다: +커서 종류가 변경될 때 발생하는 이벤트입니다. `type` 인수는 다음 값이 될 수 있습니다: `default`, `crosshair`, `pointer`, `text`, `wait`, `help`, `e-resize`, `n-resize`, `ne-resize`, `nw-resize`, `s-resize`, `se-resize`, `sw-resize`, `w-resize`, `ns-resize`, `ew-resize`, `nesw-resize`, `nwse-resize`, `col-resize`, @@ -296,10 +302,99 @@ Returns: `cell`, `context-menu`, `alias`, `progress`, `nodrop`, `copy`, `none`, `not-allowed`, `zoom-in`, `zoom-out`, `grab`, `grabbing`, `custom`. -만약 `type` 매개변수가 `custom` 이고 `image` 매개변수가 `NativeImage`를 통한 커스텀 -커서를 지정했을 때, 해당 이미지로 커서가 변경됩니다. 또한 `scale` 매개변수는 이미지의 +만약 `type` 인수가 `custom` 이고 `image` 인수가 `NativeImage`를 통한 커스텀 +커서를 지정했을 때, 해당 이미지로 커서가 변경됩니다. 또한 `scale` 인수는 이미지의 크기를 조정합니다. +### Event: 'context-menu' + +Returns: + +* `event` Event +* `params` Object + * `x` Integer - x 좌표 + * `y` Integer - y 좌표 + * `linkURL` String - 컨텍스트 메뉴가 호출된 노드를 둘러싸는 링크의 URL. + * `linkText` String - 링크에 연관된 텍스트. 콘텐츠의 링크가 이미지인 경우 빈 + 문자열이 됩니다. + * `pageURL` String - 컨텍스트 메뉴가 호출된 상위 수준 페이지의 URL. + * `frameURL` String - 컨텍스트 메뉴가 호출된 서브 프레임의 URL. + * `srcURL` String - 컨텍스트 메뉴가 호출된 요소에 대한 소스 URL. 요소와 소스 URL은 + 이미지, 오디오, 비디오입니다. + * `mediaType` String - 컨텍스트 메뉴가 호출된 노드의 종류. 값은 `none`, `image`, + `audio`, `video`, `canvas`, `file` 또는 `plugin`이 될 수 있습니다. + * `hasImageContent` Boolean - 컨텍스트 메뉴가 내용이 있는 이미지에서 호출되었는지 + 여부. + * `isEditable` Boolean - 컨텍스트를 편집할 수 있는지 여부. + * `selectionText` String - 컨텍스트 메뉴가 호출된 부분에 있는 선택된 텍스트. + * `titleText` String - 컨텍스트 메뉴가 호출된 선택된 제목 또는 알림 텍스트. + * `misspelledWord` String - 만약 있는 경우, 커서가 가르키는 곳에서 발생한 오타. + * `frameCharset` String - 메뉴가 호출된 프레임의 문자열 인코딩. + * `inputFieldType` String - 컨텍스트 메뉴가 입력 필드에서 호출되었을 때, 그 필드의 + 종류. 값은 `none`, `plainText`, `password`, `other` 중 한 가지가 될 수 있습니다. + * `menuSourceType` String - 컨텍스트 메뉴를 호출한 입력 소스. 값은 `none`, + `mouse`, `keyboard`, `touch`, `touchMenu` 중 한 가지가 될 수 있습니다. + * `mediaFlags` Object - 컨텍스트 메뉴가 호출된 미디어 요소에 대한 플래그. 자세한 + 사항은 아래를 참고하세요. + * `editFlags` Object - 이 플래그는 렌더러가 어떤 행동을 이행할 수 있는지 여부를 + 표시합니다. 자세한 사항은 아래를 참고하세요. + +`mediaFlags`는 다음과 같은 속성을 가지고 있습니다: + * `inError` Boolean - 미디어 객체가 크래시되었는지 여부. + * `isPaused` Boolean - 미디어 객체가 일시중지되었는지 여부. + * `isMuted` Boolean - 미디어 객체가 음소거되었는지 여부. + * `hasAudio` Boolean - 미디어 객체가 오디오를 가지고 있는지 여부. + * `isLooping` Boolean - 미디어 객체가 루프중인지 여부. + * `isControlsVisible` Boolean - 미디어 객체의 컨트롤이 보이는지 여부. + * `canToggleControls` Boolean - 미디어 객체의 컨트롤을 토글할 수 있는지 여부. + * `canRotate` Boolean - 미디어 객체를 돌릴 수 있는지 여부. + +`editFlags`는 다음과 같은 속성을 가지고 있습니다: + * `canUndo` Boolean - 렌더러에서 실행 취소할 수 있는지 여부. + * `canRedo` Boolean - 렌더러에서 다시 실행할 수 있는지 여부. + * `canCut` Boolean - 렌더러에서 잘라내기를 실행할 수 있는지 여부. + * `canCopy` Boolean - 렌더러에서 복사를 실행할 수 있는지 여부. + * `canPaste` Boolean - 렌더러에서 붙여넣기를 실행할 수 있는지 여부. + * `canDelete` Boolean - 렌더러에서 삭제를 실행할 수 있는지 여부. + * `canSelectAll` Boolean - 렌더러에서 모두 선택을 실행할 수 있는지 여부. + +새로운 컨텍스트 메뉴의 제어가 필요할 때 발생하는 이벤트입니다. + +### Event: 'select-bluetooth-device' + +Returns: + +* `event` Event +* `devices` [Objects] + * `deviceName` String + * `deviceId` String +* `callback` Function + * `deviceId` String + +`navigator.bluetooth.requestDevice`의 호출에 의해 블루투스 기기가 선택되어야 할 때 +발생하는 이벤트입니다. `navigator.bluetooth` API를 사용하려면 `webBluetooth`가 +활성화되어 있어야 합니다. 만약 `event.preventDefault`이 호출되지 않으면, 첫 번째로 +사용 가능한 기기가 선택됩니다. `callback`은 반드시 선택될 `deviceId`와 함께 +호출되어야 하며, 빈 문자열을 `callback`에 보내면 요청이 취소됩니다. + +```javascript +app.commandLine.appendSwitch('enable-web-bluetooth') + +app.on('ready', () => { + webContents.on('select-bluetooth-device', (event, deviceList, callback) => { + event.preventDefault() + let result = deviceList.find((device) => { + return device.deviceName === 'test' + }) + if (!result) { + callback('') + } else { + callback(result.deviceId) + } + }) +}) +``` + ## Instance Methods `webContents`객체는 다음과 같은 인스턴스 메서드들을 가지고 있습니다. @@ -317,7 +412,7 @@ Returns: 하는 경우 `pragma` 헤더를 사용할 수 있습니다. ```javascript -const options = {"extraHeaders" : "pragma: no-cache\n"} +const options = {extraHeaders: 'pragma: no-cache\n'}; webContents.loadURL(url, options) ``` @@ -333,10 +428,10 @@ webContents.loadURL(url, options) 현재 웹 페이지의 URL을 반환합니다. ```javascript -var win = new BrowserWindow({width: 800, height: 600}); -win.loadURL("http://github.com"); +let win = new BrowserWindow({width: 800, height: 600}); +win.loadURL('http://github.com'); -var currentURL = win.webContents.getURL(); +let currentURL = win.webContents.getURL(); ``` ### `webContents.getTitle()` @@ -347,6 +442,10 @@ var currentURL = win.webContents.getURL(); 현재 웹 페이지가 리소스를 로드중인지 여부를 반환합니다. +### `webContents.isLoadingMainFrame()` + +메인 프레임이 여전히 로딩중인지 여부를 반환합니다. (내부 iframe 또는 frame 포함) + ### `webContents.isWaitingForResponse()` 현재 웹 페이지가 페이지의 메인 리소스로부터 첫 응답을 기다리고있는지 여부를 반환합니다. @@ -421,10 +520,12 @@ var currentURL = win.webContents.getURL(); CSS 코드를 현재 웹 페이지에 삽입합니다. -### `webContents.executeJavaScript(code[, userGesture])` +### `webContents.executeJavaScript(code[, userGesture, callback])` * `code` String * `userGesture` Boolean (optional) +* `callback` Function (optional) - 스크립트의 실행이 완료되면 호출됩니다. + * `result` 페이지에서 자바스크립트 코드를 실행합니다. @@ -492,7 +593,7 @@ CSS 코드를 현재 웹 페이지에 삽입합니다. ### `webContents.findInPage(text[, options])` -* `text` String - 찾을 컨텐츠, 반드시 공백이 아니여야 합니다. +* `text` String - 찾을 콘텐츠, 반드시 공백이 아니여야 합니다. * `options` Object (optional) * `forward` Boolean - 앞에서부터 검색할지 뒤에서부터 검색할지 여부입니다. 기본값은 `true`입니다. @@ -522,12 +623,12 @@ CSS 코드를 현재 웹 페이지에 삽입합니다. 제공된 `action`에 대한 `webContents`의 모든 `findInPage` 요청을 중지합니다. ```javascript -webContents.on('found-in-page', function(event, result) { +webContents.on('found-in-page', (event, result) => { if (result.finalUpdate) - webContents.stopFindInPage("clearSelection"); + webContents.stopFindInPage('clearSelection'); }); -const requestId = webContents.findInPage("api"); +const requestId = webContents.findInPage('api'); ``` ### `webContents.hasServiceWorker(callback)` @@ -568,12 +669,13 @@ print기능을 사용하지 않는 경우 전체 바이너리 크기를 줄이 * `marginsType` Integer - 사용할 마진의 종류를 지정합니다. 0 부터 2 사이 값을 사용할 수 있고 각각 기본 마진, 마진 없음, 최소 마진입니다. * `pageSize` String - 생성되는 PDF의 페이지 크기를 지정합니다. 값은 `A3`, `A4`, - `A5`, `Legal`, `Letter` 와 `Tabloid`가 사용될 수 있습니다. + `A5`, `Legal`, `Letter`, `Tabloid` 또는 마이크론 단위의 `height` & `width`가 + 포함된 객체를 사용할 수 있습니다. * `printBackground` Boolean - CSS 배경을 프린트할지 여부를 정합니다. * `printSelectionOnly` Boolean - 선택된 영역만 프린트할지 여부를 정합니다. * `landscape` Boolean - landscape을 위해선 `true`를, portrait를 위해선 `false`를 사용합니다. -* `callback` Function - `function(error, data) {}` +* `callback` Function - `(error, data) => {}` Chromium의 미리보기 프린팅 커스텀 설정을 이용하여 윈도우의 웹 페이지를 PDF로 프린트합니다. @@ -592,23 +694,25 @@ Chromium의 미리보기 프린팅 커스텀 설정을 이용하여 윈도우의 } ``` +다음은 `webContents.printToPDF`의 예시입니다: + ```javascript -const BrowserWindow = require('electron').BrowserWindow; +const {BrowserWindow} = require('electron'); const fs = require('fs'); -var win = new BrowserWindow({width: 800, height: 600}); -win.loadURL("http://github.com"); +let 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) { +win.webContents.on('did-finish-load', () => { + // 기본 프린트 옵션을 사용합니다 + win.webContents.printToPDF({}, (error, data) => { if (error) throw error; - fs.writeFile("/tmp/print.pdf", data, function(error) { + fs.writeFile('/tmp/print.pdf', data, (error) => { if (error) throw error; - console.log("Write PDF successfully."); - }) - }) + console.log('Write PDF successfully.'); + }); + }); }); ``` @@ -620,8 +724,8 @@ win.webContents.on("did-finish-load", function() { 이후에 사용해야 합니다. ```javascript -mainWindow.webContents.on('devtools-opened', function() { - mainWindow.webContents.addWorkSpace(__dirname); +win.webContents.on('devtools-opened', () => { + win.webContents.addWorkSpace(__dirname); }); ``` @@ -635,6 +739,10 @@ mainWindow.webContents.on('devtools-opened', function() { * `options` Object (optional) * `detach` Boolean - 새 창에서 개발자 도구를 엽니다. + * `mode` String - 개발자 도구 표시 상태를 지정합니다. 옵션은 "right", "bottom", + "undocked", "detach"가 될 수 있습니다. 기본값은 마지막 표시 상태를 + 사용합니다. `undocked` 모드에선 다시 도킹할 수 있습니다. 하지만 `detach` + 모드에선 할 수 없습니다. 개발자 도구를 엽니다. @@ -646,20 +754,20 @@ mainWindow.webContents.on('devtools-opened', function() { 개발자 도구가 열려있는지 여부를 반환합니다. -### `webContents.toggleDevTools()` - -개발자 도구를 토글합니다. - ### `webContents.isDevToolsFocused()` 개발자 도구에 포커스 되어있는지 여부를 반환합니다. +### `webContents.toggleDevTools()` + +개발자 도구를 토글합니다. + ### `webContents.inspectElement(x, y)` * `x` Integer * `y` Integer -(`x`, `y`)위치의 엘레먼트를 조사합니다. +(`x`, `y`)위치의 요소를 조사합니다. ### `webContents.inspectServiceWorker()` @@ -681,12 +789,12 @@ mainWindow.webContents.on('devtools-opened', function() { ```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!'); +let win = null; +app.on('ready', () => { + win = new BrowserWindow({width: 800, height: 600}); + win.loadURL('file://' + __dirname + '/index.html'); + win.webContents.on('did-finish-load', () => { + win.webContents.send('ping', 'whoooooooh!'); }); }); ``` @@ -696,8 +804,8 @@ app.on('ready', function() { @@ -744,7 +852,7 @@ app.on('ready', function() { ### `webContents.sendInputEvent(event)` * `event` Object - * `type` String (**required**) - 이벤트의 타입. 다음 값들을 사용할 수 있습니다: + * `type` String (**required**) - 이벤트의 종류. 다음 값들을 사용할 수 있습니다: `mouseDown`, `mouseUp`, `mouseEnter`, `mouseLeave`, `contextMenu`, `mouseWheel`, `mouseMove`, `keyDown`, `keyUp`, `char`. * `modifiers` Array - 이벤트의 수정자(modifier)들에 대한 배열. 다음 값들을 포함 @@ -756,11 +864,8 @@ Input `event`를 웹 페이지로 전송합니다. 키보드 이벤트들에 대해서는 `event` 객체는 다음 속성들을 사용할 수 있습니다: -* `keyCode` Char or String (**required**) - 키보드 이벤트로 보내지는 문자. 단일 - UTF-8 문자를 사용할 수 있고 이벤트를 발생시키는 다음 키 중 하나를 포함할 수 있습니다: - `enter`, `backspace`, `delete`, `tab`, `escape`, `control`, `alt`, `shift`, - `end`, `home`, `insert`, `left`, `up`, `right`, `down`, `pageUp`, `pageDown`, - `printScreen` +* `keyCode` String (**required**) - 키보드 이벤트가 발생할 때 보내질 문자. + [Accelerator](accelerator.md)의 올바른 키 코드만 사용해야 합니다. 마우스 이벤트들에 대해서는 `event` 객체는 다음 속성들을 사용할 수 있습니다: @@ -804,11 +909,11 @@ Input `event`를 웹 페이지로 전송합니다. ### `webContents.savePage(fullPath, saveType, callback)` * `fullPath` String - 전체 파일 경로. -* `saveType` String - 저장 타입을 지정합니다. +* `saveType` String - 저장 종류를 지정합니다. * `HTMLOnly` - 페이지의 HTML만 저장합니다. * `HTMLComplete` - 페이지의 완성된 HTML을 저장합니다. * `MHTML` - 페이지의 완성된 HTML을 MHTML로 저장합니다. -* `callback` Function - `function(error) {}`. +* `callback` Function - `(error) => {}`. * `error` Error 만약 페이지를 저장하는 프로세스가 성공적으로 끝났을 경우 true를 반환합니다. @@ -816,10 +921,10 @@ Input `event`를 웹 페이지로 전송합니다. ```javascript win.loadURL('https://github.com'); -win.webContents.on('did-finish-load', function() { - win.webContents.savePage('/tmp/test.html', 'HTMLComplete', function(error) { +win.webContents.on('did-finish-load', () => { + win.webContents.savePage('/tmp/test.html', 'HTMLComplete', (error) => { if (!error) - console.log("Save page successfully"); + console.log('Save page successfully'); }); }); ``` @@ -828,10 +933,18 @@ win.webContents.on('did-finish-load', function() { `WebContents`객체들은 다음 속성들을 가지고 있습니다: +### `webContents.id` + +이 WebContents의 유일 ID. + ### `webContents.session` 이 webContents에서 사용하는 [session](session.md) 객체를 반환합니다. +### `webContents.hostWebContents` + +현재 `WebContents`를 소유하는 `WebContents`를 반환합니다. + ### `webContents.devToolsWebContents` 이 `WebContents`에 대한 개발자 도구의 `WebContents`를 가져옵니다. @@ -845,23 +958,23 @@ win.webContents.on('did-finish-load', function() { ```javascript try { - win.webContents.debugger.attach("1.1"); + win.webContents.debugger.attach('1.1'); } catch(err) { - console.log("Debugger attach failed : ", err); + console.log('Debugger attach failed : ', err); }; -win.webContents.debugger.on('detach', function(event, reason) { - console.log("Debugger detached due to : ", reason); +win.webContents.debugger.on('detach', (event, reason) => { + console.log('Debugger detached due to : ', reason); }); -win.webContents.debugger.on('message', function(event, method, params) { - if (method == "Network.requestWillBeSent") { - if (params.request.url == "https://www.github.com") +win.webContents.debugger.on('message', (event, method, params) => { + if (method === 'Network.requestWillBeSent') { + if (params.request.url === 'https://www.github.com') win.webContents.debugger.detach(); } -}) +}); -win.webContents.debugger.sendCommand("Network.enable"); +win.webContents.debugger.sendCommand('Network.enable'); ``` #### `webContents.debugger.attach([protocolVersion])` @@ -882,7 +995,7 @@ win.webContents.debugger.sendCommand("Network.enable"); * `method` String - 메서드 이름, 반드시 원격 디버깅 프로토콜에 의해 정의된 메서드중 하나가 됩니다. -* `commandParams` Object (optional) - 요청 매개변수를 표현한 JSON 객체. +* `commandParams` Object (optional) - 요청 인수를 표현한 JSON 객체. * `callback` Function (optional) - 응답 * `error` Object - 커맨드의 실패를 표시하는 에러 메시지. * `result` Object - 원격 디버깅 프로토콜에서 커맨드 설명의 'returns' 속성에 의해 @@ -902,8 +1015,7 @@ win.webContents.debugger.sendCommand("Network.enable"); * `event` Event * `method` String - 메서드 이름. -* `params` Object - 원격 디버깅 프로토콜의 'parameters' 속성에서 정의된 이벤트 - 매개변수 +* `params` Object - 원격 디버깅 프로토콜의 'parameters' 속성에서 정의된 이벤트 인수 디버깅 타겟이 관련 이벤트를 발생시킬 때 마다 발생하는 이벤트입니다. diff --git a/docs-translations/ko-KR/api/web-frame.md b/docs-translations/ko-KR/api/web-frame.md index f77ed68f9073..fdc68d222313 100644 --- a/docs-translations/ko-KR/api/web-frame.md +++ b/docs-translations/ko-KR/api/web-frame.md @@ -1,19 +1,18 @@ # webFrame -`web-frame` 모듈은 현재 웹 페이지의 랜더링 상태를 설정 할 수 있도록 관련 유틸리티를 -제공하는 모듈입니다. +> 현재 웹 페이지의 렌더링 상태를 커스터마이즈합니다. -다음 예제는 현재 페이지를 200% 줌 합니다: +다음 예시는 현재 페이지를 200% 줌 합니다: ```javascript -var webFrame = require('electron').webFrame; +const {webFrame} = require('electron'); webFrame.setZoomFactor(2); ``` ## Methods -`web-frame` 모듈은 다음과 같은 메서드를 가지고 있습니다: +`webFrame` 모듈은 다음과 같은 메서드를 가지고 있습니다: ### `webFrame.setZoomFactor(factor)` @@ -56,11 +55,11 @@ Input field나 text area에 철자 검사(spell checking) 제공자를 설정합 `provider`는 반드시 전달된 단어의 철자가 맞았는지 검사하는 `spellCheck` 메소드를 가지고 있어야 합니다. -[node-spellchecker][spellchecker]를 철자 검사 제공자로 사용하는 예제입니다: +[node-spellchecker][spellchecker]를 철자 검사 제공자로 사용하는 예시입니다: ```javascript -webFrame.setSpellCheckProvider("en-US", true, { - spellCheck: function(text) { +webFrame.setSpellCheckProvider('en-US', true, { + spellCheck(text) { return !(require('spellchecker').isMisspelled(text)); } }); @@ -72,14 +71,14 @@ webFrame.setSpellCheckProvider("en-US", true, { `scheme`을 보안 스킴으로 등록합니다. -보안 스킴은 혼합된 컨텐츠 경고를 발생시키지 않습니다. 예를 들어 `https` 와 `data`는 +보안 스킴은 혼합된 콘텐츠 경고를 발생시키지 않습니다. 예를 들어 `https` 와 `data`는 네트워크 공격자로부터 손상될 가능성이 없기 때문에 보안 스킴이라고 할 수 있습니다. ### `webFrame.registerURLSchemeAsBypassingCSP(scheme)` * `scheme` String -현재 페이지 컨텐츠의 보안 정책에 상관없이 `scheme`로부터 리소스가 로드됩니다. +현재 페이지 콘텐츠의 보안 정책에 상관없이 `scheme`로부터 리소스가 로드됩니다. ### `webFrame.registerURLSchemeAsPrivileged(scheme)` @@ -104,4 +103,40 @@ ServiceWorker의 등록과 fetch API를 사용할 수 있도록 지원합니다. 브라우저 윈도우에서 어떤 `requestFullScreen` 같은 HTML API는 사용자의 승인이 필요합니다. `userGesture`를 `true`로 설정하면 이러한 제약을 제거할 수 있습니다. +### `webFrame.getResourceUsage()` + +Blink의 내부 메모리 캐시 사용 정보를 담고있는 객체를 반환합니다. + +```javascript +console.log(webFrame.getResourceUsage()) +``` + +다음이 출력됩니다: + +```javascript +{ + images: { + count: 22, + size: 2549, + liveSize: 2542, + decodedSize: 478, + purgedSize: 0, + purgeableSize: 0 + }, + cssStyleSheets: { /* same with "images" */ }, + xslStyleSheets: { /* same with "images" */ }, + fonts: { /* same with "images" */ }, + other: { /* same with "images" */ }, +} +``` + +### `webFrame.clearCache()` + +사용하지 않는 메모리 비우기를 시도합니다. (이전 페이지의 이미지 등) + +참고로 맹목적으로 이 메서드를 호출하는 것은 이 빈 캐시를 다시 채워야하기 때문에 +Electron을 느리게 만듭니다. 따라서 이 메서드는 페이지가 예상했던 것 보다 실질적으로 더 +적은 메모리를 사용하게 만드는 어플리케이션 이벤트가 발생했을 때만 호출해야 합니다. +(i.e. 아주 무거운 페이지에서 거의 빈 페이지로 이동한 후 계속 유지할 경우) + [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 30da9f253b45..71032fcb5732 100644 --- a/docs-translations/ko-KR/api/web-view-tag.md +++ b/docs-translations/ko-KR/api/web-view-tag.md @@ -1,48 +1,84 @@ # `` 태그 -`guest` 컨텐츠(웹 페이지)를 Electron 앱 페이지에 삽입하기 위해 `webview` 태그를 -사용할 수 있습니다. 게스트 컨텐츠는 `webview` 컨테이너에 담겨 대상 페이지에 삽입되고 -해당 페이지에선 게스트 컨텐츠의 배치 및 렌더링 과정을 조작할 수 있습니다. +> 외부 웹 콘텐츠를 고립된 프레임과 프로세스에서 표시합니다. + +`guest` 콘텐츠(웹 페이지)를 Electron 앱 페이지에 삽입하기 위해 `webview` 태그를 +사용할 수 있습니다. 게스트 콘텐츠는 `webview` 컨테이너에 담겨 대상 페이지에 삽입되고 +해당 페이지에선 게스트 콘텐츠의 배치 및 렌더링 과정을 조작할 수 있습니다. `iframe`과는 달리 `webview`는 어플리케이션과 분리된 프로세스에서 작동합니다. -이는 웹 페이지와 같은 권한을 가지지 않고 앱과 임베디드(게스트) 컨텐츠간의 모든 -상호작용이 비동기로 작동한다는 것을 의미합니다. 따라서 임베디드 컨텐츠로부터 +이는 웹 페이지와 같은 권한을 가지지 않고 앱과 임베디드(게스트) 콘텐츠간의 모든 +상호작용이 비동기로 작동한다는 것을 의미합니다. 따라서 임베디드 콘텐츠로부터 어플리케이션을 안전하게 유지할 수 있습니다. -## 예제 +보안상의 이유로, `webview`는 `nodeIntegration`이 활성화된 `BrowserWindow`에서만 사용할 수 있습니다. + +## 예시 웹 페이지를 어플리케이션에 삽입하려면 `webview` 태그를 사용해 원하는 타겟 페이지에 -추가하면 됩니다. (게스트 컨텐츠가 앱 페이지에 추가 됩니다) 간단한 예로 `webview` +추가하면 됩니다. (게스트 콘텐츠가 앱 페이지에 추가 됩니다) 간단한 예로 `webview` 태그의 `src` 속성에 페이지를 지정하고 css 스타일을 이용해서 컨테이너의 외관을 설정할 수 있습니다: ```html - + ``` -게스트 컨텐츠를 조작하기 위해 자바스크립트로 `webview` 태그의 이벤트를 리스닝 하여 -응답을 받을 수 있습니다. 다음 예제를 참고하세요: 첫번째 리스너는 페이지 로딩 시작시의 +게스트 콘텐츠를 조작하기 위해 자바스크립트로 `webview` 태그의 이벤트를 리스닝 하여 +응답을 받을 수 있습니다. 다음 예시를 참고하세요: 첫번째 리스너는 페이지 로딩 시작시의 이벤트를 확인하고 두번째 리스너는 페이지의 로딩이 끝난시점을 확인합니다. 그리고 페이지를 로드하는 동안 "loading..." 메시지를 표시합니다. ```html ``` +## CSS 스타일링 참고 + +주의할 점은 `webview` 태그의 스타일은 전통적인 flexbox 레이아웃을 사용했을 때 자식 +`object` 요소가 해당 `webview` 컨테이너의 전체 높이와 넓이를 확실히 채우도록 +내부적으로 `display:flex;`를 사용합니다. (v0.36.11 부터) 따라서 인라인 레이아웃을 +위해 `display:inline-flex;`를 쓰지 않는 한, 기본 `display:flex;` CSS 속성을 +덮어쓰지 않도록 주의해야 합니다. + +`webview`는 `hidden` 또는 `display: none;` 속성을 사용할 때 발생하는 문제를 한 가지 +가지고 있습니다. 자식 `browserplugin` 객체 내에서 비정상적인 랜더링 동작을 발생시킬 수 +있으며 웹 페이지가 로드되었을 때, `webview`가 숨겨지지 않았을 때, 반대로 그냥 바로 +다시 보이게 됩니다. `webview`를 숨기는 방법으로 가장 권장되는 방법은 `width` & +`height`를 0으로 지정하는 CSS를 사용하는 것이며 `flex`를 통해 0px로 요소를 수축할 수 +있도록 합니다. + +```html + +``` + ## 태그 속성 `webview` 태그는 다음과 같은 속성을 가지고 있습니다: @@ -80,6 +116,9 @@ "on"으로 지정하면 `webview` 페이지 내에서 `require`와 `process 객체`같은 node.js API를 사용할 수 있습니다. 이를 지정하면 내부에서 로우레벨 리소스에 접근할 수 있습니다. +**참고:** Node 통합 기능은 `webview`에서 부모 윈도우가 해당 옵션이 비활성화되어있는 +경우 항상 비활성화됩니다. + ### `plugins` ```html @@ -140,7 +179,7 @@ API를 사용할 수 있습니다. 이를 지정하면 내부에서 로우레벨 동일한 세션을 공유할 수 있도록 할 수 있습니다. 만약 `partition`이 지정되지 않으면 앱의 기본 세션을 사용합니다. -이 값은 첫 탐색 이전에만 지정할 수 있습니다. 즉. 작동중인 랜더러 프로세스의 세션은 +이 값은 첫 탐색 이전에만 지정할 수 있습니다. 즉. 작동중인 렌더러 프로세스의 세션은 변경할 수 없습니다. 이후 이 값을 바꾸려고 시도하면 DOM 예외를 발생시킵니다. ### `allowpopups` @@ -167,10 +206,10 @@ API를 사용할 수 있습니다. 이를 지정하면 내부에서 로우레벨 **참고:** 태그 객체의 메서드는 페이지 로드가 끝난 뒤에만 사용할 수 있습니다. -**예제** +**예시** ```javascript -webview.addEventListener("dom-ready", function() { +webview.addEventListener('dom-ready', () => { webview.openDevTools(); }); ``` @@ -254,7 +293,7 @@ Webview에 웹 페이지 `url`을 로드합니다. `url`은 `http://`, `file://` ### `.isCrashed()` -랜더러 프로세스가 크래시 됬는지 확인합니다. +렌더러 프로세스가 크래시 됬는지 확인합니다. ### `.setUserAgent(userAgent)` @@ -272,18 +311,20 @@ Webview에 웹 페이지 `url`을 로드합니다. `url`은 `http://`, `file://` 페이지에 CSS를 삽입합니다. -### `.executeJavaScript(code[, userGesture])` +### `.executeJavaScript(code[, userGesture, callback])` * `code` String * `userGesture` Boolean +* `callback` Function (optional) - 스크립트의 실행이 완료되면 호출됩니다. + * `result` -페이지에서 자바스크립트 `code`를 실행합니다. +페이지에서 자바스크립트 코드를 실행합니다. 만약 `userGesture`가 `true`로 설정되어 있으면 페이지에 유저 제스쳐 컨텍스트를 만듭니다. 이 옵션을 활성화 시키면 `requestFullScreen`와 같은 HTML API에서 유저의 승인을 무시하고 개발자가 API를 바로 사용할 수 있도록 허용합니다. -역주: 기본적으로 브라우저에선 전체화면, 웹캠, 파일 열기등의 API를 사용하려면 유저의 +**역주:** 기본적으로 브라우저에선 전체화면, 웹캠, 파일 열기등의 API를 사용하려면 유저의 승인(이벤트)이 필요합니다. ### `.openDevTools()` @@ -369,7 +410,7 @@ Service worker에 대한 개발자 도구를 엽니다. ### `webContents.findInPage(text[, options])` -* `text` String - 찾을 컨텐츠, 반드시 공백이 아니여야 합니다. +* `text` String - 찾을 콘텐츠, 반드시 공백이 아니여야 합니다. * `options` Object (optional) * `forward` Boolean - 앞에서부터 검색할지 뒤에서부터 검색할지 여부입니다. 기본값은 `true`입니다. @@ -392,19 +433,19 @@ Service worker에 대한 개발자 도구를 엽니다. * `action` String - [`.findInPage`](web-view-tag.md#webviewtagfindinpage) 요청이 종료되었을 때 일어날 수 있는 작업을 지정합니다. - * `clearSelection` - 선택을 일반 선택으로 변경합니다. - * `keepSelection` - 선택을 취소합니다. + * `clearSelection` - 선택을 취소합니다. + * `keepSelection` - 선택을 일반 선택으로 변경합니다. * `activateSelection` - 포커스한 후 선택된 노드를 클릭합니다. 제공된 `action`에 대한 `webContents`의 모든 `findInPage` 요청을 중지합니다. ### `.print([options])` -Webview 페이지를 인쇄합니다. `webContents.print([options])` 메서드와 같습니다. +`webview` 페이지를 인쇄합니다. `webContents.print([options])` 메서드와 같습니다. ### `.printToPDF(options, callback)` -Webview 페이지를 PDF 형식으로 인쇄합니다. +`webview` 페이지를 PDF 형식으로 인쇄합니다. `webContents.printToPDF(options, callback)` 메서드와 같습니다. ### `.send(channel[, arg1][, arg2][, ...])` @@ -412,21 +453,25 @@ Webview 페이지를 PDF 형식으로 인쇄합니다. * `channel` String * `args` (optional) -`channel`을 통해 랜더러 프로세스로 비동기 메시지를 보냅니다. 또한 `args`를 지정하여 -임의의 인자를 보낼 수도 있습니다. 랜더러 프로세스는 `ipcRenderer` 모듈의 `channel` +`channel`을 통해 렌더러 프로세스로 비동기 메시지를 보냅니다. 또한 `args`를 지정하여 +임의의 인수를 보낼 수도 있습니다. 렌더러 프로세스는 `ipcRenderer` 모듈의 `channel` 이벤트로 이 메시지를 받아 처리할 수 있습니다. -예제는 [webContents.send](web-contents.md#webcontentssendchannel-args)를 참고하세요. +예시는 [webContents.send](web-contents.md#webcontentssendchannel-args)를 참고하세요. ### `.sendInputEvent(event)` * `event` Object -페이지에 input `event`를 보냅니다. +페이지에 입력 `event`를 보냅니다. `event` 객체에 대해 자세히 알아보려면 [webContents.sendInputEvent](web-contents.md##webcontentssendinputeventevent)를 참고하세요. +### `.getWebContents()` + +이 `webview`에 해당하는 [WebContents](web-contents.md)를 반환합니다. + ## DOM 이벤트 `webview` 태그는 다음과 같은 DOM 이벤트를 가지고 있습니다: @@ -454,9 +499,10 @@ Returns: * `errorCode` Integer * `errorDescription` String * `validatedURL` String +* `isMainFrame` Boolean -`did-finish-load`와 비슷합니다. 하지만 이 이벤트는 `window.stop()`과 같은 무언가로 -인해 로드에 실패했을 때 발생하는 이벤트입니다. +`did-finish-load`와 비슷합니다. 하지만 이 이벤트는 `window.stop()`과 같이 취소 +함수가 호출되었거나 로드에 실패했을 때 발생하는 이벤트입니다. ### Event: 'did-frame-finish-load' @@ -486,6 +532,7 @@ Returns: * `requestMethod` String * `referrer` String * `headers` Object +* `resourceType` String 요청한 리소스에 관해 자세한 내용을 알 수 있을 때 발생하는 이벤트입니다. `status`는 리소스를 다운로드할 소켓 커낵션을 나타냅니다. @@ -541,11 +588,11 @@ Returns: `console.log` API에 의해 로깅될 때 발생하는 이벤트입니다. -다음 예제는 모든 로그 메시지를 로그 레벨이나 다른 속성에 관련 없이 호스트 페이지의 -콘솔에 다시 로깅하는 예제입니다. +다음 예시는 모든 로그 메시지를 로그 레벨이나 다른 속성에 관련 없이 호스트 페이지의 +콘솔에 다시 로깅하는 예시입니다. ```javascript -webview.addEventListener('console-message', function(e) { +webview.addEventListener('console-message', (e) => { console.log('Guest page logged a message:', e.message); }); ``` @@ -557,6 +604,7 @@ Returns: * `result` Object * `requestId` Integer * `finalUpdate` Boolean - 더 많은 응답이 따르는 경우를 표시합니다. + * `activeMatchOrdinal` Integer (optional) - 활성화 일치의 위치. * `matches` Integer (optional) - 일치하는 개수. * `selectionArea` Object (optional) - 첫 일치 부위의 좌표. @@ -564,12 +612,12 @@ Returns: 사용할 수 있을 때 발생하는 이벤트입니다. ```javascript -webview.addEventListener('found-in-page', function(e) { +webview.addEventListener('found-in-page', (e) => { if (e.result.finalUpdate) - webview.stopFindInPage("keepSelection"); + webview.stopFindInPage('keepSelection'); }); -const rquestId = webview.findInPage("test"); +const requestId = webview.findInPage('test'); ``` ### Event: 'new-window' @@ -584,11 +632,16 @@ Returns: 페이지가 새로운 브라우저 창을 생성할 때 발생하는 이벤트입니다. -다음 예제 코드는 새 URL을 시스템의 기본 브라우저로 여는 코드입니다. +다음 예시 코드는 새 URL을 시스템의 기본 브라우저로 여는 코드입니다. ```javascript -webview.addEventListener('new-window', function(e) { - require('electron').shell.openExternal(e.url); +const {shell} = require('electron'); + +webview.addEventListener('new-window', (e) => { + const protocol = require('url').parse(e.url).protocol; + if (protocol === 'http:' || protocol === 'https:') { + shell.openExternal(e.url); + } }); ``` @@ -636,11 +689,11 @@ Returns: 페이지가 자체적으로 닫힐 때 발생하는 이벤트입니다. -다음 예제 코드는 페이지가 자체적으로 닫힐 때 `webview`를 `about:blank` 페이지로 -이동시키는 예제입니다. +다음 예시 코드는 페이지가 자체적으로 닫힐 때 `webview`를 `about:blank` 페이지로 +이동시키는 예시입니다. ```javascript -webview.addEventListener('close', function() { +webview.addEventListener('close', () => { webview.src = 'about:blank'; }); ``` @@ -659,7 +712,7 @@ Returns: ```javascript // In embedder page. -webview.addEventListener('ipc-message', function(event) { +webview.addEventListener('ipc-message', (event) => { console.log(event.channel); // Prints "pong" }); @@ -668,15 +721,15 @@ webview.send('ping'); ```javascript // In guest page. -var ipcRenderer = require('electron').ipcRenderer; -ipcRenderer.on('ping', function() { +const {ipcRenderer} = require('electron'); +ipcRenderer.on('ping', () => { ipcRenderer.sendToHost('pong'); }); ``` ### Event: 'crashed' -랜더러 프로세스가 크래시 되었을 때 발생하는 이벤트입니다. +렌더러 프로세스가 크래시 되었을 때 발생하는 이벤트입니다. ### Event: 'gpu-crashed' @@ -705,6 +758,10 @@ WebContents가 파괴될 때 발생하는 이벤트입니다. ### Event: 'did-change-theme-color' +Returns: + +* `themeColor` String + 페이지의 테마 색이 변경될 때 발생하는 이벤트입니다. 이 이벤트는 보통 meta 태그에 의해서 발생합니다: diff --git a/docs-translations/ko-KR/api/window-open.md b/docs-translations/ko-KR/api/window-open.md index f81c6b65be87..0c4fb7153376 100644 --- a/docs-translations/ko-KR/api/window-open.md +++ b/docs-translations/ko-KR/api/window-open.md @@ -1,5 +1,7 @@ # `window.open` 함수 +> 새 윈도우를 열고 URL을 로드합니다. + `window.open` 함수가 호출되면 새 창을 생성하고 `url` 페이지를 불러옵니다. 이 창은 지정한 `url`을 로드하여 만들어진 `BrowserWindow`의 새 인스턴스이며 본래 창 객체 대신 페이지의 컨트롤이 제한된 프록시 객체를 반환합니다. @@ -9,7 +11,7 @@ 합니다. 새롭게 생성된 `BrowserWindow`는 기본적으로 부모 창의 옵션을 상속합니다. 이 옵션을 -변경하려면 새 창을 열 때 `features` 인자를 지정해야 합니다. +변경하려면 새 창을 열 때 `features` 인수를 지정해야 합니다. ### `window.open(url[, frameName][, features])` @@ -22,6 +24,9 @@ `features` 문자열은 표준 브라우저의 포맷을 따르고 있지만, 각 기능은 `BrowserWindow`의 옵션이어야 합니다. +**참고:** Node 통합 기능은 열린 `window`에서 부모 윈도우가 해당 옵션이 +비활성화되어있는 경우 항상 비활성화됩니다. + ### `window.opener.postMessage(message, targetOrigin)` * `message` String @@ -54,6 +59,10 @@ 자식 윈도우에 포커스를 맞춥니다. (창을 맨 앞으로 가져옵니다) +### `BrowserWindowProxy.print()` + +자식 윈도우에 프린트 대화 상자를 호출합니다. + ### `BrowserWindowProxy.postMessage(message, targetOrigin)` * `message` String diff --git a/docs-translations/ko-KR/development/atom-shell-vs-node-webkit.md b/docs-translations/ko-KR/development/atom-shell-vs-node-webkit.md index 22e8e3756469..21972d2f38ae 100644 --- a/docs-translations/ko-KR/development/atom-shell-vs-node-webkit.md +++ b/docs-translations/ko-KR/development/atom-shell-vs-node-webkit.md @@ -16,7 +16,7 @@ main 필드에 메인 웹 페이지(index.html) URL을 지정하면 어플리케 Electron에선 JavaScript를 엔트리 포인트로 사용합니다. URL을 직접 제공하는 대신 API를 사용하여 직접 브라우저 창과 HTML 파일을 로드할 수 있습니다. 또한 윈도우의 종료시기를 -결정하는 이벤트를 리스닝해야합니다. +결정하는 이벤트를 리스닝해야 합니다. Electron은 Node.js 런타임과 비슷하게 작동합니다. Electron의 API는 저수준이기에 브라우저 테스팅을 위해 [PhantomJS](http://phantomjs.org/)를 사용할 수도 있습니다. @@ -44,4 +44,4 @@ __4. 다중 컨텍스트__ 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 +[node-bindings]: https://github.com/electron/electron/tree/master/atom/common diff --git a/docs-translations/ko-KR/development/build-instructions-linux.md b/docs-translations/ko-KR/development/build-instructions-linux.md index c4f161fa56a2..3fe755313ebb 100644 --- a/docs-translations/ko-KR/development/build-instructions-linux.md +++ b/docs-translations/ko-KR/development/build-instructions-linux.md @@ -4,10 +4,11 @@ ## 빌드전 요구사양 +* 최소한 25GB 이상의 디스크 공간과 8GB 램이 필요합니다. * Python 2.7.x. 몇몇 CentOS와 같은 배포판들은 아직도 Python 2.6.x 버전을 사용합니다. 그래서 먼저 `python -V`를 통해 버전을 확인할 필요가 있습니다. * Node.js v0.12.x. Node를 설치하는 방법은 여러 가지가 있습니다. 먼저, - [Node.js](http://nodejs.org) 사이트에서 소스코드를 받아 빌드하는 방법입니다. + [Node.js](http://nodejs.org) 사이트에서 소스 코드를 받아 빌드하는 방법입니다. 이렇게 하면 Node를 일반 유저로 홈 디렉터리에 설치할 수 있습니다. 다른 방법으로는 [NodeSource](https://nodesource.com/blog/nodejs-v012-iojs-and-the-nodesource-linux-repositories)에서 소스 파일을 받아와 설치할 수 있습니다. 자세한 내용은 [Node.js 설치 방법](https://github.com/joyent/node/wiki/Installation)을 @@ -21,7 +22,7 @@ Ubuntu를 사용하고 있다면 다음과 같이 라이브러리를 설치해 $ sudo apt-get install build-essential clang libdbus-1-dev libgtk2.0-dev \ libnotify-dev libgnome-keyring-dev libgconf2-dev \ libasound2-dev libcap-dev libcups2-dev libxtst-dev \ - libxss1 libnss3-dev gcc-multilib g++-multilib + libxss1 libnss3-dev gcc-multilib g++-multilib curl ``` Fedora를 사용하고 있다면 다음과 같이 라이브러리를 설치해야 합니다: @@ -33,18 +34,13 @@ $ sudo yum install clang dbus-devel gtk2-devel libnotify-devel libgnome-keyring- ``` 다른 배포판의 경우 pacman 같은 패키지 매니저를 통해 패키지를 설치 할 수 있습니다. -패키지의 이름은 대부분 위 예시와 비슷할 것입니다. 또는 소스코드를 내려받아 +패키지의 이름은 대부분 위 예시와 비슷할 것입니다. 또는 소스 코드를 내려받아 직접 빌드하는 방법도 있습니다. -## 가상머신을 사용하여 빌드 하는 경우 - -만약 Electron을 가상머신으로 빌드 할 계획이라면 해당 가상머신의 스토리지를 최소 25GB -이상 확보해 놓아야 합니다. - ## 코드 가져오기 ```bash -$ git clone https://github.com/atom/electron.git +$ git clone https://github.com/electron/electron.git ``` ## 부트 스트랩 @@ -75,7 +71,7 @@ $ sudo apt-get install libc6-dev-armhf-cross linux-libc-dev-armhf-cross \ $ ./script/bootstrap.py -v --target_arch=arm ``` -## 빌드 하기 +## 빌드하기 `Release` 와 `Debug` 두 타겟 모두 빌드 합니다: @@ -104,7 +100,7 @@ $ ./script/build.py -c D 빌드가 모두 끝나면 `out/D` 디렉터리에서 `electron` 디버그 바이너리를 찾을 수 있습니다. -## 정리 하기 +## 정리하기 빌드 파일들을 정리합니다: @@ -114,8 +110,6 @@ $ ./script/clean.py ## 문제 해결 -개발 종속성 라이브러리들을 제대로 설치했는지 확인하세요. - ## libtinfo.so.5 동적 링크 라이브러리를 로드하는 도중 에러가 발생할 경우 미리 빌드된 `clang`은 `libtinfo.so.5`로 링크를 시도합니다. 따라서 플랫폼에 따라 @@ -130,7 +124,7 @@ $ sudo ln -s /usr/lib/libncurses.so.5 /usr/lib/libtinfo.so.5 프로젝트 코딩 스타일을 확인하려면: ```bash -$ ./script/cpplint.py +$ npm run lint ``` 테스트를 실행하려면: @@ -138,3 +132,73 @@ $ ./script/cpplint.py ```bash $ ./script/test.py ``` + +## 고급 주제 + +기본적인 빌드 구성은 가장 주력인 Linux 배포판에 초점이 맞춰져있으며, 특정 배포판이나 +기기에 빌드할 계획이라면 다음 정보들이 도움이 될 것입니다. + +### 로컬에서 `libchromiumcontent` 빌드하기 + +미리 빌드된 `libchromiumcontent`를 사용하는 것을 피하기 위해, `bootstrap.py` +스크립트에 `--build_libchromiumcontent` 스위치를 추가할 수 있습니다: + +```bash +$ ./script/bootstrap.py -v --build_libchromiumcontent +``` + +참고로 `shared_library` 구성은 기본적으로 빌드되어있지 않으며, 다음 모드를 사용하면 +`Release` 버전의 Electron만 빌드할 수 있습니다: + +```bash +$ ./script/build.py -c R +``` + +### 다운로드된 `clang` 바이너리 대신 시스템의 `clang` 사용하기 + +기본적으로 Electron은 Chromium 프로젝트에서 제공하는 미리 빌드된 `clang` 바이너리를 +통해 빌드됩니다. 만약 어떤 이유로 시스템에 설치된 `clang`을 사용하여 빌드하고 싶다면, +`bootstrap.py`를 `--clang_dir=` 스위치와 함께 실행함으로써 해결할 수 있습니다. +빌드 스크립트를 이 스위치와 함께 실행할 때 스크립트는 `/bin/`와 같은 경로로 +`clang` 바이너리를 찾습니다. + +예를 들어 `clang`을 `/user/local/bin/clang`에 설치했다면 다음과 같습니다: + +```bash +$ ./script/bootstrap.py -v --build_libchromiumcontent --clang_dir /usr/local +$ ./script/build.py -c R +``` + +### `clang` 대신 다른 컴파일러 사용하기 + +Electron을 `g++`과 같은 다른 컴파일러로 빌드하려면, 먼저 `--disable_clang` 스위치를 +통해 `clang`을 비활성화 시켜야 하고, 필요하다면 `CC`와 `CXX` 환경 변수도 설정합니다. + +예를 들어 GCC 툴체인을 사용하여 빌드한다면 다음과 같습니다: + +```bash +$ env CC=gcc CXX=g++ ./script/bootstrap.py -v --build_libchromiumcontent --disable_clang +$ ./script/build.py -c R +``` + +### 환경 변수 + +또한 `CC`와 `CXX`와는 별개로, 빌드 구성을 변경하기 위해 다음 환경 변수들을 사용할 수 +있습니다: + +* `CPPFLAGS` +* `CPPFLAGS_host` +* `CFLAGS` +* `CFLAGS_host` +* `CXXFLAGS` +* `CXXFLAGS_host` +* `AR` +* `AR_host` +* `CC` +* `CC_host` +* `CXX` +* `CXX_host` +* `LDFLAGS` + +이 환경 변수는 `bootstrap.py` 스크립트를 실행할 때 설정되어야 하며, `build.py` +스크립트에선 작동하지 않습니다. diff --git a/docs-translations/ko-KR/development/build-instructions-osx.md b/docs-translations/ko-KR/development/build-instructions-osx.md index 9e2a76d31045..55a88b0cefd9 100644 --- a/docs-translations/ko-KR/development/build-instructions-osx.md +++ b/docs-translations/ko-KR/development/build-instructions-osx.md @@ -15,21 +15,21 @@ ## 코드 가져오기 ```bash -$ git clone https://github.com/atom/electron.git +$ git clone https://github.com/electron/electron.git ``` ## 부트 스트랩 부트스트랩 스크립트는 필수적인 빌드 종속성 라이브러리들을 모두 다운로드하고 프로젝트 -파일을 생성합니다. 참고로 Electron은 `ninja`를 빌드 툴체인으로 사용하므로 Xcode -프로젝트는 생성되지 않습니다. +파일을 생성합니다. 참고로 Electron은 [ninja](https://ninja-build.org/)를 빌드 +툴체인으로 사용하므로 Xcode 프로젝트는 생성되지 않습니다. ```bash $ cd electron $ ./script/bootstrap.py -v ``` -## 빌드 하기 +## 빌드하기 `Release` 와 `Debug` 두 타겟 모두 빌드 합니다: diff --git a/docs-translations/ko-KR/development/build-instructions-windows.md b/docs-translations/ko-KR/development/build-instructions-windows.md index 3c7932479ecd..525dae2f0143 100644 --- a/docs-translations/ko-KR/development/build-instructions-windows.md +++ b/docs-translations/ko-KR/development/build-instructions-windows.md @@ -5,7 +5,7 @@ ## 빌드전 요구 사항 * Windows 7 / Server 2008 R2 또는 최신 버전 -* Visual Studio 2013 Update 4 - [VS 2013 커뮤니티 에디션 무료 다운로드](https://www.visualstudio.com/news/vs2013-community-vs) +* Visual Studio 2015 - [VS 2015 커뮤니티 에디션 무료 다운로드](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx) * [Python 2.7](http://www.python.org/download/releases/2.7/) * [Node.js](http://nodejs.org/download/) * [Git](http://git-scm.com) @@ -21,13 +21,13 @@ Studio를 사용할 수 없습니다. 하지만 여전히 Electron을 개발할 **참고:** Visual Studio가 직접 빌드에 사용되지 않더라도 IDE와 같이 제공된 빌드 툴체인이 빌드에 **반드시** 사용되므로 여전히 필요합니다. -**참고:** Visual Studio 2015는 사용할 수 없습니다 MSVS **2013** 을 사용하고 있는지 +**참고:** Visual Studio 2013은 사용할 수 없습니다 MSVS **2015** 을 사용하고 있는지 확인해주세요. ## 코드 가져오기 ```powershell -$ git clone https://github.com/atom/electron.git +$ git clone https://github.com/electron/electron.git ``` ## 부트 스트랩 @@ -41,7 +41,7 @@ $ cd electron $ python script\bootstrap.py -v ``` -## 빌드 하기 +## 빌드하기 `Release` 와 `Debug` 두 타겟 모두 빌드 합니다: @@ -61,7 +61,7 @@ $ python script\build.py -c D ## 64비트 빌드 64비트를 타겟으로 빌드 하려면 부트스트랩 스크립트를 실행할 때 `--target_arch=x64` -인자를 같이 넘겨주면 됩니다: +인수를 같이 넘겨주면 됩니다: ```powershell $ python script\bootstrap.py -v --target_arch=x64 @@ -97,7 +97,7 @@ $ python script\test.py -R ### Command xxxx not found 만약 `Command xxxx not found`와 같은 형식의 에러가 발생했다면 -`VS2012 Command Prompt` 콘솔로 빌드 스크립트를 실행해볼 필요가 있습니다. +`VS2015 Command Prompt` 콘솔로 빌드 스크립트를 실행해 보는게 좋습니다. ### Fatal internal compiler error: C1001 @@ -146,4 +146,4 @@ $ mkdir ~\AppData\Roaming\npm ### node-gyp is not recognized as an internal or external command Git Bash로 빌드 했을 때 이러한 에러가 발생할 수 있습니다. 반드시 PowerShell이나 -VS2012 Command Prompt에서 빌드를 진행해야 합니다. +VS2015 Command Prompt에서 빌드를 진행해야 합니다. diff --git a/docs-translations/ko-KR/development/build-system-overview.md b/docs-translations/ko-KR/development/build-system-overview.md index 79ead32718d7..044a2da0ae29 100644 --- a/docs-translations/ko-KR/development/build-system-overview.md +++ b/docs-translations/ko-KR/development/build-system-overview.md @@ -1,7 +1,8 @@ # 빌드 시스템 개요 -Electron은 프로젝트 생성을 위해 `gyp`를 사용하며 `ninja`를 이용하여 빌드합니다. -프로젝트 설정은 `.gyp` 와 `.gypi` 파일에서 볼 수 있습니다. +Electron은 프로젝트 생성을 위해 [gyp](https://gyp.gsrc.io/)를 사용하며 +[ninja](https://ninja-build.org/)를 이용하여 빌드합니다. 프로젝트 설정은 `.gyp` 와 +`.gypi` 파일에서 볼 수 있습니다. ## gyp 파일 diff --git a/docs-translations/ko-KR/development/coding-style.md b/docs-translations/ko-KR/development/coding-style.md index 21ee03fd0a1d..ff162575d72c 100644 --- a/docs-translations/ko-KR/development/coding-style.md +++ b/docs-translations/ko-KR/development/coding-style.md @@ -2,6 +2,9 @@ 이 가이드는 Electron의 코딩 스타일에 관해 설명합니다. +`npm run lint`를 실행하여 `cpplint`와 `eslint`를 통해 어떤 코딩 스타일 이슈를 확인할 +수 있습니다. + ## C++과 Python C++과 Python 스크립트는 Chromium의 @@ -18,21 +21,37 @@ C++ 코드는 많은 Chromium의 추상화와 타입을 사용합니다. 따라 자동으로 메모리에서 할당을 해제합니다. 스마트 포인터와 같습니다) 그리고 로깅 메커니즘 등을 언급하고 있습니다. -## CoffeeScript - -CoffeeScript의 경우 GitHub의 -[스타일 가이드](https://github.com/styleguide/javascript)를 기본으로 따릅니다. -그리고 추가로 다음 규칙을 따릅니다: +## JavaScript +* [표준](http://npm.im/standard) JavaScript 코딩 스타일을 사용합니다. * Google의 코딩 스타일에도 맞추기 위해 파일의 끝에는 **절대** 개행을 삽입해선 안됩니다. * 파일 이름의 공백은 `_`대신에 `-`을 사용하여야 합니다. 예를 들어 -`file_name.coffee`를 `file-name.coffee`로 고쳐야합니다. 왜냐하면 +`file_name.js`를 `file-name.js`로 고쳐야 합니다. 왜냐하면 [github/atom](https://github.com/github/atom)에서 사용되는 모듈의 이름은 보통 -`module-name` 형식이기 때문입니다. 이 규칙은 '.coffee' 파일에만 적용됩니다. +`module-name` 형식이기 때문입니다. 이 규칙은 '.js' 파일에만 적용됩니다. +* 적절한 곳에 새로운 ES6/ES2015 문법을 사용해도 됩니다. + * [`const`](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/const) + 는 requires와 다른 상수에 사용합니다 + * [`let`](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/let) + 은 변수를 정의할 때 사용합니다 + * [Arrow functions](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/Arrow_functions) + 는 `function () { }` 표현 대신에 사용합니다 + * [Template literals](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Template_literals) + 는 `+`로 문자열을 합치는 대신 사용합니다. -## API 이름 +## 이름 짓기 -새로운 API를 만들 땐 getter, setter스타일 대신 jQuery의 one-function 스타일을 -사용해야 합니다. 예를 들어 `.getText()`와 `.setText(text)`대신에 `.text([text])` -형식으로 설계하면 됩니다. 포럼에 이 문제에 대한 [논의](https://github.com/atom/electron/issues/46)가 +Electron API는 Node.js와 비슷한 명명법을 사용합니다: + +- `BrowserWindow`와 같은 모듈 자체를 뜻하는 이름은, `CamelCase`를 사용합니다. +- `globalShortcut`과 같은 API의 세트일 땐, `mixedCase`를 사용합니다. +- API가 객체의 속성일 경우, 그리고 `win.webContents`와 같이 충분히 복잡하고 분리된 + 부분일 경우, `mixedCase`를 사용합니다. +- 다른 모듈이 아닌 API를 구현할 땐, ` Tag` 또는 `Process Object`와 같이 + 단순하고 자연스러운 제목을 사용합니다. + +새로운 API를 만들 땐 jQuery의 one-function 스타일 대신 getter, setter스타일을 +사용해야 합니다. 예를 들어 `.text([text])` 대신 `.getText()`와 `.setText(text)` +형식으로 함수를 설계하면 됩니다. 포럼에서 이 문제에 대한 +[논의](https://github.com/electron/electron/issues/46)가 진행되고 있습니다. diff --git a/docs-translations/ko-KR/development/debug-instructions-windows.md b/docs-translations/ko-KR/development/debug-instructions-windows.md new file mode 100644 index 000000000000..27f96c284b00 --- /dev/null +++ b/docs-translations/ko-KR/development/debug-instructions-windows.md @@ -0,0 +1,87 @@ +# Windows에서 Electron 디버깅하기 + +만약 작성한 Javascript 어플리케이션이 아닌 Electron 자체의 크래시나 문제를 경험하고 +있다면, 네이티브/C++ 디버깅에 익숙하지 않은 개발자는 디버깅이 약간 까다로울 수 +있습니다. 그렇다 해도, Visual Studio, GitHub의 Electron이 호스팅하는 심볼 서버, +Electron 소스 코드가 중단점을 통해 순차적으로 쉽게 디버깅할 수 있는 환경을 제공합니다. + +## 요구 사항 + +* **Electron의 디버그 빌드**: 가장 쉬운 방법은 보통 + [Windows용 빌드 설명서](build-instructions-windows.md)에 명시된 요구 사항과 툴을 + 사용하여 스스로 빌드하는 것입니다. 물론 직접 다운로드 받은 Electron 바이너리에도 + 디버거 연결 및 디버깅을 사용할 수 있지만, 실질적으로 디버깅이 까다롭게 고도의 + 최적화가 되어있음을 발견하게 될 것입니다: 인라인화, 꼬리 호출, 이외 여러 가지 + 생소한 최적화가 적용되어 디버거가 모든 변수와 실행 경로를 정상적으로 표시할 수 + 없습니다. + +* **Visual Studio와 C++ 툴**: Visual Studio 2013과 Visual Studio 2015 두 가지 + 커뮤니티 에디션 모두 잘 작동합니다. 설치가 완료되면, + [Visual Studio가 GitHub의 Electron 심볼 서버를 사용하도록](setting-up-symbol-server.md) + 설정해야 합니다. 이 작업은 Visual Studio가 Electron에서 무슨일이 일어나는지 더 잘 + 이해할 수 있도록 하며 변수를 사람이 읽기 좋은 포맷으로 쉽게 표현할 수 있도록 합니다. + +* **ProcMon**: 이 무료 [SysInternals][sys-internals] 툴은 프로세스 인수, 파일 + 핸들러 그리고 레지스트리 작업을 탐색할 수 있게 도와줍니다. + +## Electron에 디버거 연결하고 디버깅하기 + +디버깅 작업을 시작하려면, PowerShell/CMD 중 한 가지를 열고 디버그 빌드 상태의 +Electron에 인수로 어플리케이션을 전달하여 실행합니다: + +```powershell +$ ./out/D/electron.exe ~/my-electron-app/ +``` + +### 중단점 설정 + +그리고, Visual Studio를 엽니다. Electron은 Visual Studio로 만들어지지 않았으며 +이러한 이유로 인해 프로젝트 파일을 가지고 있지 않습니다. 하지만 "파일로 열기"를 통해 +소스 코드 파일들을 열 수 있습니다. Visual Studio가 각각의 파일을 따로 연다는 것입니다. +여전히 중단점을 설정할 수 있습니다. Visual Studio는 현재 소스 코드와 일치하는 작동 +중인 프로세스와 중단점을 자동으로 찾아냅니다. + +관련된 코드 파일들은 `./atom/`에서 찾을 수 있으며 또한 Brightray 안 +`./vendor/brightray/browser`와 `./vendor/brightray/common`에서도 찾을 수 있습니다. +만약 하드코어를 좋아한다면, Chromium을 직접 디버깅할 수도 있습니다. 확실히 +`chromium_src` 안에서 찾을 수 있습니다. + +### 디버거 연결 + +로컬에서 작동 중인 프로세스 또는 원격 컴퓨터에 Visual Studio 디버거를 적용시킬 수 +있습니다. 프로세스의 실행이 끝난 후, 디버그 / 프로세스에 연결을 (또는 `Ctrl+Alt+P` +입력) 클릭하면 "프로세스에 연결" 대화 상자가 열립니다. 이 도구를 통해 로컬 또는 +원격에서 작동 중인 어플리케이션을 디버깅할 수 있으며 여러 프로세스를 동시에 디버깅할 +수도 있습니다. + +만약 Electron이 서로 다른 유저 계정에서 실행 중이라면, `모든 사용자의 프로세스 +보이기`를 선택하면 됩니다. 참고로 이는 `BrowserWindow`가 열린 개수에 따라 달라질 수 +있으며 아마 다수의 프로세스를 발견할 수 있을 것입니다. 전형적인 one-window +어플리케이션은 Visual Studio에서 두 개의 `Electron.exe` 항목으로 표시됩니다. 하나는 +메인 프로세스이며 다른 하나는 렌더러 프로세스입니다. 리스트는 단지 이름 하나만 제공하기 +때문에 현재까지는 다른 적절한 프로세스 판별법이 없습니다. + +### 어떤 프로세스에 디버거를 적용해야 하나요? + +코드는 메인 프로세스 내에서 실행되며 (이는 코드를 안에서 찾을 수 있거나, 결국 메인 +Javascript 파일에 의해 실행) remote (`require('electron').remote`)를 사용하여 +코드를 실행하는 것 또한 메인 프로세스 내에서 실행됩니다. 다른 코드는 각각의 렌더러 +프로세스 내에서 실행됩니다. + +디버깅할 때 여러 프로그램에 디버거를 적용할 수 있지만, 언제나 한 개의 프로그램만 +디버거에서 활성화되어야 합니다. `디버그 경로` 툴바 또는 `프로세스 창`에서 활성화 +프로그램을 설정할 수 있습니다. + +## 프로세스를 관찰하기 위해 ProcMon 사용 + +Visual Studio는 특정 코드 경로를 탐색하는것에 대해 환상적인 기능을 제공하고 ProcMon은 +어플리케이션이 운영체제와 하는 일의 모든 것을 관찰하는데 강력한 기능을 가지고 있습니다. +이 툴은 프로세스의 파일, 레지스트리, 네트워킹, 프로세스, 프로파일링 상세를 포착할 수 +있으며 강력하게 **모든** 이벤트의 발생을 로깅을 시도합니다. 만약 어플리케이션이 +운영체제에 대해 무슨 일을 하고 있는지 이해하고 싶다면 이는 좋은 자원이 될 것입니다. + +ProcMon의 기본적인 디버깅 기능을 알아보고 싶다면 Microsoft에서 제공하는 +[동영상 강좌][procmon-instructions]를 참고하세요. + +[sys-internals]: https://technet.microsoft.com/en-us/sysinternals/processmonitor.aspx +[procmon-instructions]: https://channel9.msdn.com/shows/defrag-tools/defrag-tools-4-process-monitor diff --git a/docs-translations/ko-KR/development/setting-up-symbol-server.md b/docs-translations/ko-KR/development/setting-up-symbol-server.md index 014f383ab29e..6a214c5b843a 100644 --- a/docs-translations/ko-KR/development/setting-up-symbol-server.md +++ b/docs-translations/ko-KR/development/setting-up-symbol-server.md @@ -9,19 +9,19 @@ 참고로 릴리즈된 Electron 빌드는 자체적으로 많은 최적화가 되어 있는 관계로 경우에 따라 디버깅이 쉽지 않을 수 있습니다. Inlining, tail call 등 컴파일러 최적화에 의해 디버거가 -모든 변수의 컨텐츠를 보여줄 수 없는 경우도 있고 실행 경로가 이상하게 보여지는 경우도 +모든 변수의 콘텐츠를 보여줄 수 없는 경우도 있고 실행 경로가 이상하게 보여지는 경우도 있습니다. 유일한 해결 방법은 최적화되지 않은 로컬 빌드를 하는 것입니다. 공식적인 Electron의 심볼 서버의 URL은 http://54.249.141.255:8086/atom-shell/symbols 입니다. 일단 이 URL에 직접적으로 -접근할 수는 없습니다: 디버깅 툴에 심볼의 경로를 추가해야합니다. 아래의 예제를 참고하면 +접근할 수는 없습니다: 디버깅 툴에 심볼의 경로를 추가해야 합니다. 아래의 예시를 참고하면 로컬 캐시 디렉터리는 서버로부터 중복되지 않게 PDB를 가져오는데 사용됩니다. `c:\code\symbols` 캐시 디렉터리를 사용중인 OS에 맞춰 적당한 경로로 변경하세요. ## Windbg에서 심볼 서버 사용하기 Windbg 심볼 경로는 구분자와 `*` 문자로 설정되어 있습니다. Electron 심볼 서버만 -사용하려면 심볼 경로의 엔트리를 추가해야 합니다. (__참고:__ `c:\code\symbols` +사용하려면 심볼 경로의 엔트리를 추가해야 합니다. (**참고:** `c:\code\symbols` 디렉터리 경로를 PC가 원하는 경로로 수정할 수 있습니다): ``` @@ -30,7 +30,7 @@ SRV*c:\code\symbols\*http://54.249.141.255:8086/atom-shell/symbols Windbg 메뉴 또는 `.sympath` 커맨드를 이용하여 환경에 `_NT_SYMBOL_PATH` 문자열을 설정합니다. 만약 Microsoft의 심볼서버로부터 심볼을 받아오려면 다음과 같이 리스팅을 -먼저 해야합니다: +먼저해야 합니다: ``` SRV*c:\code\symbols\*http://msdl.microsoft.com/download/symbols;SRV*c:\code\symbols\*http://54.249.141.255:8086/atom-shell/symbols diff --git a/docs-translations/ko-KR/development/source-code-directory-structure.md b/docs-translations/ko-KR/development/source-code-directory-structure.md index e0e172d18b4f..aa6d2612df57 100644 --- a/docs-translations/ko-KR/development/source-code-directory-structure.md +++ b/docs-translations/ko-KR/development/source-code-directory-structure.md @@ -10,34 +10,35 @@ Electron의 소스 코드는 몇 개의 파트로 분리되어 있습니다. 그 ``` Electron -├──atom - Electron의 소스코드. -| ├── app - 시스템 엔트리 코드. -| ├── browser - 주 윈도우를 포함한 프론트엔드, UI, 그리고 메인 프로세스에 관련된 것과 -| | 랜더러와 웹 페이지 관리 관련 코드. -| | ├── lib - 메인 프로세스 초기화 코드의 자바스크립트 파트. -| | ├── ui - 크로스 플랫폼에 대응하는 UI 구현. -| | | ├── cocoa - 코코아 특정 소스 코드. -| | | ├── gtk - GTK+ 특정 소스 코드. -| | | └── win - Windows GUI 특정 소스 코드. -| | ├── default_app - 어플리케이션이 제공되지 않으면 기본으로 사용되는 페이지. -| | ├── api - 메인 프로세스 API의 구현. -| | | └── lib - API 구현의 자바스크립트 파트. -| | ├── net - 네트워크 관련 코드. -| | ├── mac - Mac 특정 Objective-C 소스 코드. -| | └── resources - 아이콘들, 플랫폼 종속성 파일들, 기타 등등. -| ├── renderer - 랜더러 프로세스에서 작동하는 코드들. -| | ├── lib - 랜더러 프로세스 초기화 코드의 자바스크립트 파트. -| | └── api - 랜더러 프로세스 API들의 구현. -| | └── lib - API 구현의 자바스크립트 파트. -| └── common - 메인 프로세스와 랜더러 프로세스에서 공유하는 코드. 유틸리티 함수와 -| node 메시지 루프를 Chromium의 메시지 루프에 통합시킨 코드도 포함. -| ├── lib - 공통 자바스크립트 초기화 코드. -| └── api - 공통 API들의 구현, Electron의 빌트인 모듈 기초 코드들. -| └── lib - API 구현의 자바스크립트 파트. -├── chromium_src - 복사된 Chromium 소스 코드. -├── docs - 참조 문서. -├── spec - 자동화된 테스트. -├── atom.gyp - Electron의 빌드 규칙. +├── atom/ - C++ 소스 코드. +| ├── app/ - 시스템 엔트리 코드. +| ├── browser/ - 주 윈도우를 포함한 프론트엔드, UI, 그리고 메인 프로세스에 관련된 +| | 코드와 렌더러 및 웹 페이지 관리 관련 코드. +| | ├── ui/ - 서로 다른 플랫폼에 대한 UI 관련 구현 코드. +| | | ├── cocoa/ - Cocoa 특정 소스 코드. +| | | ├── win/ - Windows GUI 특정 소스 코드. +| | | └── x/ - X11 특정 소스 코드. +| | ├── api/ - 메인 프로세스 API의 구현. +| | ├── net/ - 네트워킹 관련 코드. +| | ├── mac/ - Mac 특정 Objective-C 소스 코드. +| | └── resources/ - 아이콘들, 플랫폼 종속성 파일들, 기타 등등.. +| ├── renderer/ - 렌더러 프로세스에서 작동하는 코드. +| | └── api/ - 렌더러 프로세스 API의 구현. +| └── common/ - 메인과 렌더러 프로세스에서 모두 사용하는 코드, 몇가지 유틸리티 +| 함수들이 포함되어 있고 node의 메시지 루프와 Chromium의 메시지 루프를 통합. +| └── api/ - 공통 API 구현들, 기초 Electron 빌트-인 모듈들. +├── chromium_src/ - Chromium에서 복사하여 가져온 소스 코드. +├── default_app/ - Electron에 앱이 제공되지 않았을 때 보여지는 기본 페이지. +├── docs/ - 참조 문서. +├── lib/ - JavaScript 소스 코드. +| ├── browser/ - Javascript 메인 프로세스 초기화 코드. +| | └── api/ - Javascript API 구현 코드. +| ├── common/ - 메인과 렌더러 프로세스에서 모두 사용하는 JavaScript +| | └── api/ - Javascript API 구현 코드. +| └── renderer/ - Javascript 렌더러 프로세스 초기화 코드. +| └── api/ - Javascript API 구현 코드. +├── spec/ - 자동화 테스트. +├── electron.gyp - Electron의 빌드 규칙. └── common.gypi - 컴파일러 설정 및 `node` 와 `breakpad` 등의 구성요소를 위한 빌드 규칙. ``` @@ -47,7 +48,7 @@ Electron * **script** - 개발목적으로 사용되는 빌드, 패키징, 테스트, 기타등을 실행하는 스크립트. * **tools** - gyp 파일에서 사용되는 헬퍼 스크립트 `script`와는 다르게 유저로부터 직접 실행되지 않는 스크립트들을 이곳에 넣습니다. -* **vendor** - 소스코드의 서드파티 종속성 코드 소스 코드 디렉터리가 겹쳐 혼란을 일으킬 +* **vendor** - 소스 코드의 서드파티 종속성 코드 소스 코드 디렉터리가 겹쳐 혼란을 일으킬 수 있기 때문에 `third_party`와 같은 Chromium 소스 코드 디렉터리에서 사용된 폴더 이름은 사용하지 않았습니다. * **node_modules** - 빌드에 사용되는 node 서드파티 모듈. @@ -56,3 +57,32 @@ Electron 스크립트로부터 만들어지는 임시 디렉터리. * **external_binaries** - `gyp` 빌드를 지원하지 않아 따로 다운로드된 서드파티 프레임워크 바이너리들. + +## Git 서브 모듈 최신 버전으로 유지 + +Electron 저장소는 몇 가지 외부 벤더 종속성을 가지고 있으며 [/vendor][vendor] +디렉터리에서 확인할 수 있습니다. 때때로 `git status`를 실행했을 때 아마 다음과 같은 +메시지를 흔히 목격할 것입니다: + +```sh +$ git status + + modified: vendor/brightray (new commits) + modified: vendor/node (new commits) +``` + +이 외부 종속성 모듈들을 업데이트 하려면, 다음 커맨드를 실행합니다: + +```sh +git submodule update --init --recursive +``` + +만약 자기 자신이 너무 이 커맨드를 자주 사용하는 것 같다면, `~/.gitconfig` 파일을 +생성하여 편하게 업데이트할 수 있습니다: + +``` +[alias] + su = submodule update --init --recursive +``` + +[vendor]: https://github.com/electron/electron/tree/master/vendor diff --git a/docs-translations/ko-KR/faq/electron-faq.md b/docs-translations/ko-KR/faq/electron-faq.md index ea9953fcd4cd..c186d148c2a0 100644 --- a/docs-translations/ko-KR/faq/electron-faq.md +++ b/docs-translations/ko-KR/faq/electron-faq.md @@ -3,16 +3,19 @@ ## 언제 Electron이 최신 버전의 Chrome으로 업그레이드 되나요? Electron의 Chrome 버전은 보통 새로운 Chrome 안정 버전이 릴리즈 된 이후 1주 내지 2주 -내로 업데이트됩니다. +내로 업데이트됩니다. 하지만 이러한 업데이트 주기는 보장되지 않으며 업그레이드에 필요한 +작업의 양에 따라 달라집니다. -또한 우리는 크롬의 안정된 채널만을 이용합니다, 만약 중요한 수정이 베타 또는 개발 채널인 -경우, 우리는 해당 버전 대신 이전 버전을 다시 사용합니다. +Electron은 크롬이 사용하는 안정된 채널만을 이용합니다, 만약 중요한 수정이 베타 또는 +개발 채널에 패치된 경우, 이전 버전의 채널로 롤백합니다. + +자세한 내용은 [보안 설명](../tutorial/security.md)을 참고하세요. ## Electron은 언제 최신 버전의 Node.js로 업그레이드 하나요? -새로운 버전의 Node.js가 릴리즈 되면, 우리는 보통 Electron을 업그레이드 하기 전에 한 -달 정도 대기합니다. 이렇게 하면 새로운 Node.js 버전을 업데이트 함으로써 발생하는 -버그들을 피할 수 있습니다. 이러한 상황은 자주 발생합니다. +새로운 버전의 Node.js가 릴리즈 되면, 보통 Electron을 업그레이드 하기 전에 한 달 정도 +대기합니다. 이렇게 하면 새로운 Node.js 버전을 업데이트 함으로써 발생하는 버그들을 +피할 수 있기 때문입니다. 이러한 상황은 자주 발생합니다. Node.js의 새로운 기능은 보통 V8 업그레이드에서 가져옵니다. Electron은 Chrome 브라우저에 탑재된 V8을 사용하고 있습니다. 눈부신 새로운 Node.js 버전의 자바스크립트 @@ -20,13 +23,13 @@ Node.js의 새로운 기능은 보통 V8 업그레이드에서 가져옵니다. ## 어떻게 웹 페이지 간에 데이터를 공유할 수 있나요? -두 웹페이지 간에 (랜더러 프로세스) 데이터를 공유하려면 간단히 이미 모든 브라우저에서 +두 웹페이지 간에 (렌더러 프로세스) 데이터를 공유하려면 간단히 이미 모든 브라우저에서 사용할 수 있는 HTML5 API들을 사용하면 됩니다. 가장 좋은 후보는 [Storage API][storage], [`localStorage`][local-storage], [`sessionStorage`][session-storage], 그리고 [IndexedDB][indexed-db]가 있습니다. 또는 Electron에서만 사용할 수 있는 IPC 시스템을 사용하여 메인 프로세스의 global -변수에 데이터를 저장한 후 다음과 같이 랜더러 프로세스에서 `remote` 모듈을 사용하여 +변수에 데이터를 저장한 후 다음과 같이 렌더러 프로세스에서 `remote` 모듈을 사용하여 접근할 수 있습니다: ```javascript @@ -59,18 +62,18 @@ console.log(require('remote').getGlobal('sharedObject').someProperty); 만약 빠르게 고치고 싶다면, 다음과 같이 변수를 전역 변수로 만드는 방법이 있습니다: ```javascript -app.on('ready', function() { - var tray = new Tray('/path/to/icon.png'); -}) +app.on('ready', () => { + const tray = new Tray('/path/to/icon.png'); +}); ``` 를 이렇게: ```javascript -var tray = null; -app.on('ready', function() { +let tray = null; +app.on('ready', () => { tray = new Tray('/path/to/icon.png'); -}) +}); ``` ## Electron에서 jQuery/RequireJS/Meteor/AngularJS를 사용할 수 없습니다. @@ -83,7 +86,7 @@ Node.js가 Electron에 합쳐졌기 때문에, DOM에 `module`, `exports`, `requ ```javascript // 메인 프로세스에서. -var mainWindow = new BrowserWindow({ +let win = new BrowserWindow({ webPreferences: { nodeIntegration: false } @@ -141,13 +144,13 @@ npm uninstall -g electron 그런데 여전히 빌트인 모듈이 계속해서 문제를 발생시키는 경우, 아마 모듈을 잘못 사용하고 있을 가능성이 큽니다. 예를 들면 `electron.app`은 메인 프로세스에서만 사용할 수 있는 -모듈이며, 반면 `electron.webFrame` 모듈은 랜더러 프로세스에서만 사용할 수 있는 +모듈이며, 반면 `electron.webFrame` 모듈은 렌더러 프로세스에서만 사용할 수 있는 모듈입니다. [memory-management]: https://developer.mozilla.org/ko/docs/Web/JavaScript/Memory_Management [variable-scope]: https://msdn.microsoft.com/library/bzt2dkta(v=vs.94).aspx [electron-module]: https://www.npmjs.com/package/electron -[storage]: https://developer.mozilla.org/en-US/docs/Web/API/Storage -[local-storage]: https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage -[session-storage]: https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage -[indexed-db]: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API +[storage]: https://developer.mozilla.org/ko/docs/Web/API/Storage +[local-storage]: https://developer.mozilla.org/ko/docs/Web/API/Window/localStorage +[session-storage]: https://developer.mozilla.org/ko/docs/Web/API/Window/sessionStorage +[indexed-db]: https://developer.mozilla.org/ko/docs/Web/API/IndexedDB_API diff --git a/docs-translations/ko-KR/styleguide.md b/docs-translations/ko-KR/styleguide.md index 8327cd3c1dd7..340fa4204a05 100644 --- a/docs-translations/ko-KR/styleguide.md +++ b/docs-translations/ko-KR/styleguide.md @@ -18,7 +18,7 @@ Electron 문서를 작성하는 규칙은 다음과 같습니다. 추가합니다. - 메서드 헤더는 `code backtick` 으로 표시합니다. - 이벤트 헤더는 한 '따옴표'로 표시합니다. -- 리스트를 2 단계 이상 중첩하지 않습니다. (안타깝게도 markdown 랜더러가 이를 지원하지 +- 리스트를 2 단계 이상 중첩하지 않습니다. (안타깝게도 markdown 렌더러가 이를 지원하지 않습니다) - 섹션에 대한 제목을 추가합니다. Events, Class 메서드 그리고 인스턴스 메서드 등 - 어떤 '것'의 사용 결과를 설명할 때 '될 것입니다' 형식을 사용하여 설명합니다. @@ -41,7 +41,7 @@ Electron 문서를 작성하는 규칙은 다음과 같습니다. 유지합니다. - 문서를 번역합니다. - 언어 디렉터리 내의 `README.md`에 번역한 문서의 링크를 추가합니다. -- 메인(upstream) Electron의 [README](https://github.com/atom/electron#documentation-translations)에 +- 메인(upstream) Electron의 [README](https://github.com/electron/electron#documentation-translations)에 번역된 언어 디렉터리의 링크를 추가합니다. ## Electron 문서 읽기 @@ -50,8 +50,8 @@ Electron 문서 구조를 이해하는 데 참고할 수 있는 유용한 도움 ### Methods -[Method](https://developer.mozilla.org/en-US/docs/Glossary/Method) 문서의 -예제입니다: +[Method](https://developer.mozilla.org/ko/docs/Glossary/Method) 문서의 +예시입니다: --- @@ -66,16 +66,16 @@ Electron 문서 구조를 이해하는 데 참고할 수 있는 유용한 도움 묶어 이 인수가 다른 인수뒤에서 선택적으로 사용될 수 있다는 것을 표시합니다. 메서드 이름 하단에선 각 인수에 대해 자세한 설명을 합니다. 인수의 타입은: -[`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) +[`String`](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/String), +[`Number`](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Number), +[`Object`](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object), +[`Array`](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array) 와 같은 일반적으로 쓰이는 타입 중 하나를 받거나 Electron의 [`webContent`](api/web-content.md) 같은 커스텀 타입을 받습니다. ### Events -[Event](https://developer.mozilla.org/en-US/docs/Web/API/Event) 문서의 예제입니다: +[Event](https://developer.mozilla.org/ko/docs/Web/API/Event) 문서의 예시입니다: --- @@ -92,7 +92,7 @@ Returns: 다음과 같이 사용할 수 있습니다: ```javascript -Alarm.on('wake-up', function(time) { +Alarm.on('wake-up', (time) => { console.log(time) -}) +}); ``` diff --git a/docs-translations/ko-KR/tutorial/application-distribution.md b/docs-translations/ko-KR/tutorial/application-distribution.md index 7072d7c4d952..fedd613c0f11 100644 --- a/docs-translations/ko-KR/tutorial/application-distribution.md +++ b/docs-translations/ko-KR/tutorial/application-distribution.md @@ -30,8 +30,8 @@ electron/resources/app ## asar로 앱 패키징 하기 -소스파일 전체를 복사해서 배포하는 것과는 별개로 [asar](https://github.com/atom/asar) -아카이브를 통해 어플리케이션의 소스코드가 사용자에게 노출되는 것을 방지할 수 있습니다. +소스파일 전체를 복사해서 배포하는 것과는 별개로 [asar](https://github.com/electron/asar) +아카이브를 통해 어플리케이션의 소스 코드가 사용자에게 노출되는 것을 방지할 수 있습니다. `asar` 아카이브를 사용할 땐 단순히 `app` 폴더 대신에 어플리케이션을 패키징한 `app.asar` 파일로 대체하면됩니다. Electron은 자동으로 `app`폴더 대신 asar 아카이브를 @@ -105,9 +105,17 @@ MyApp.app/Contents 아이콘은 [.desktop](https://developer.gnome.org/integration-guide/stable/desktop-files.html.en) 파일을 사용하여 지정할 수 있습니다. -## Electron 소스코드를 다시 빌드하여 리소스 수정하기 +## 패키징 툴 -또한 Electron 소스코드를 다시 빌드할 때 어플리케이션 이름을 변경할 수 있습니다. +어플리케이션을 일일이 수동으로 패키지로 만드는 대신, 서드 파티 패키징 툴을 사용하며 +이러한 작업을 자동화 시킬 수 있습니다: + +* [electron-packager](https://github.com/maxogden/electron-packager) +* [electron-builder](https://github.com/loopline-systems/electron-builder) + +## Electron 소스 코드를 다시 빌드하여 리소스 수정하기 + +또한 Electron 소스 코드를 다시 빌드할 때 어플리케이션 이름을 변경할 수 있습니다. `GYP_DEFINES` 환경변수를 사용하여 다음과 같이 다시 빌드할 수 있습니다: @@ -131,17 +139,56 @@ $ script/build.py -c Release -t myapp ### grunt-build-atom-shell -Electron의 소스코드를 수정하고 다시 빌드하는 작업은 상당히 복잡합니다. 일일이 -소스코드를 수정하는 대신 [grunt-build-atom-shell](https://github.com/paulcbetts/grunt-build-atom-shell)을 +Electron의 소스 코드를 수정하고 다시 빌드하는 작업은 상당히 복잡합니다. 일일이 +소스 코드를 수정하는 대신 [grunt-build-atom-shell](https://github.com/paulcbetts/grunt-build-atom-shell)을 사용하여 빌드를 자동화 시킬 수 있습니다. 이 툴을 사용하면 자동으로 `.gyp`파일을 수정하고 다시 빌드합니다. 그리고 어플리케이션의 네이티브 Node 모듈 또한 새로운 실행파일 이름으로 일치시킵니다. -## 패키징 툴 +### Electron 커스텀 포크 만들기 -어플리케이션을 일일이 수동으로 패키지로 만드는 대신, 서드 파티 패키징 툴을 사용하며 -이러한 작업을 자동화 시킬 수 있습니다: +Electron의 커스텀 포크를 만드는 것은 거의 확실히 앱을 만드는데 있어서 필요한 작업이 +아닐 수 있으며, 심지어 "제품 등급"의 어플리케이션이라도 필요하지 않습니다. +`electron-packager` 또는 `electron-builder`와 같은 도구를 사용하면 다른 특별한 +과정 없이 Electron을 "Rebrand" 할 수 있습니다. -* [electron-packager](https://github.com/maxogden/electron-packager) -* [electron-builder](https://github.com/loopline-systems/electron-builder) +업스트림 단에서 추가될 수 없는 기능이나 이미 공식 버전에서 거부된 기능을 Electron에 +직접적으로 패치하기 위해 커스텀 C++를 추가해야 한다면 Electron을 포크해야 합니다. +Electron의 개발자로써, Electron을 매우 많은 시나리오에서도 작동하도록 만들려고 +합니다. 따라서 가능한한 변경 사항을 공식 버전의 Electron에 추가할 수 있도록 시도해 +주길 바라며, 당신에겐 아주 아주 쉬운 작업일 것이고 이러한 당신의 도움에 대해 감사하게 +생각합니다. + +#### surf-build와 함께 커스텀 릴리즈 만들기 + +1. npm을 통해 [Surf](https://github.com/surf-build/surf)를 설치합니다: + `npm install -g surf-build@latest` + +2. 새로운 S3 bucket을 만들고 다음과 같은 빈 디렉토리 구조를 만듭니다: + +``` +- atom-shell/ + - symbols/ + - dist/ +``` + +3. 다음의 환경 변수들을 설정합니다: + +* `ELECTRON_GITHUB_TOKEN` - GitHub에 릴리즈를 만들 수 있는 토큰. +* `ELECTRON_S3_ACCESS_KEY`, `ELECTRON_S3_BUCKET`, `ELECTRON_S3_SECRET_KEY` - + node.js 헤더 뿐만 아니라 심볼을 업로드할 장소. +* `ELECTRON_RELEASE` - `true`로 지정하고 업로드 부분이 실행되면, 설정되지 않은 + 부분을 남기고 `surf-build`가 CI-type 확인을 실행합니다. 모든 pull request를 + 실행할 때 적합합니다. +* `CI` - `true` 또는 다른 것을 지정하면 실패합니다. +* `GITHUB_TOKEN` - `ELECTRON_GITHUB_TOKEN`와 같게 설정 +* `SURF_TEMP` - Windows에선 `C:\Temp`로 설정하면 긴 경로 문제를 해결할 수 있습니다. +* `TARGET_ARCH` - `ia32` 또는 `x64`를 지정. + +4. Electron에 기여를 하는 기여자라면, _반드시_ `script/upload.py`에서 포크를 위해 + `ELECTRON_REPO`를 설정해야 합니다. (`MYORG/electron`) + +5. `surf-build -r https://github.com/MYORG/electron -s YOUR_COMMIT -n 'surf-PLATFORM-ARCH'` + +6. 빌드가 완료될 때까지 아주 아주 긴 시간을 기다립니다. diff --git a/docs-translations/ko-KR/tutorial/application-packaging.md b/docs-translations/ko-KR/tutorial/application-packaging.md index 9183f0eafe96..6dd52b34ef83 100644 --- a/docs-translations/ko-KR/tutorial/application-packaging.md +++ b/docs-translations/ko-KR/tutorial/application-packaging.md @@ -1,7 +1,7 @@ # 어플리케이션 패키징 Windows에서 일어나는 긴 경로 이름에 대한 [issues](https://github.com/joyent/node/issues/6960)를 -완화하고 `require` 속도를 약간 빠르게 하며 어플리케이션의 리소스와 소스코드를 좋지 않은 +완화하고 `require` 속도를 약간 빠르게 하며 어플리케이션의 리소스와 소스 코드를 좋지 않은 사용자로부터 보호하기 위해 어플리케이션을 [asar][asar] 아카이브로 패키징 할 수 있습니다. ## `asar` 아카이브 생성 @@ -70,8 +70,8 @@ require('/path/to/example.asar/dir/module.js'); `BrowserWindow` 클래스를 이용해 원하는 웹 페이지도 표시할 수 있습니다: ```javascript -const BrowserWindow = require('electron').BrowserWindow; -var win = new BrowserWindow({width: 800, height: 600}); +const {BrowserWindow} = require('electron'); +let win = new BrowserWindow({width: 800, height: 600}); win.loadURL('file:///path/to/example.asar/static/index.html'); ``` @@ -84,8 +84,8 @@ win.loadURL('file:///path/to/example.asar/static/index.html'); ```html @@ -99,7 +99,7 @@ $.get('file:///path/to/example.asar/file.txt', function(data) { 읽어들입니다: ```javascript -var originalFs = require('original-fs'); +const originalFs = require('original-fs'); originalFs.readFileSync('/path/to/example.asar'); ``` @@ -168,7 +168,7 @@ Node API에는 `child_process.exec`, `child_process.spawn` 그리고 바이러스로 진단 할 수도 있습니다. 이 문제를 해결하려면 `--unpack` 옵션을 통해 파일을 압축이 풀려진 상태로 유지해야 합니다. -다음의 예제는 node 네이티브 모듈의 공유 라이브러리를 압축이 풀려진 상태로 유지합니다: +다음의 예시는 node 네이티브 모듈의 공유 라이브러리를 압축이 풀려진 상태로 유지합니다: ```bash $ asar pack app app.asar --unpack *.node @@ -179,4 +179,4 @@ $ asar pack app app.asar --unpack *.node 포함되어 있습니다. 사용자에게 어플리케이션을 배포할 때 반드시 해당 폴더도 같이 배포해야 합니다. -[asar]: https://github.com/atom/asar +[asar]: https://github.com/electron/asar diff --git a/docs-translations/ko-KR/tutorial/debugging-main-process.md b/docs-translations/ko-KR/tutorial/debugging-main-process.md index 0b69c0a165aa..339c28daab7a 100644 --- a/docs-translations/ko-KR/tutorial/debugging-main-process.md +++ b/docs-translations/ko-KR/tutorial/debugging-main-process.md @@ -1,6 +1,6 @@ # 메인 프로세스 디버깅하기 -브라우저 창의 개발자 도구는 웹 페이지 같은 랜더러 프로세스의 스크립트만 디버깅이 +브라우저 창의 개발자 도구는 웹 페이지 같은 렌더러 프로세스의 스크립트만 디버깅이 가능합니다. 대신 Electron은 메인 프로세스의 디버깅을 위해 `--debug` 과 `--debug-brk` 스위치들을 제공합니다. @@ -19,7 +19,7 @@ ## node-inspector로 디버깅 하기 -__참고:__ Electron은 현재 node-inspector 유틸리티와 호환성 문제가 있습니다. 따라서 +**참고:** Electron은 현재 node-inspector 유틸리티와 호환성 문제가 있습니다. 따라서 node-inspector 콘솔 내에서 메인 프로세스의 `process` 객체를 탐색할 경우 크래시가 발생할 수 있습니다. @@ -40,11 +40,11 @@ $ npm install git+https://git@github.com/enlight/node-pre-gyp.git#detect-electro ### 4. Electron용 `node-inspector` `v8` 모듈을 재 컴파일 (target을 사용하는 Electron의 버전에 맞춰 변경) ```bash -$ node_modules/.bin/node-pre-gyp --target=0.36.2 --runtime=electron --fallback-to-build --directory node_modules/v8-debug/ --dist-url=https://atom.io/download/atom-shell reinstall -$ node_modules/.bin/node-pre-gyp --target=0.36.2 --runtime=electron --fallback-to-build --directory node_modules/v8-profiler/ --dist-url=https://atom.io/download/atom-shell reinstall +$ node_modules/.bin/node-pre-gyp --target=0.36.11 --runtime=electron --fallback-to-build --directory node_modules/v8-debug/ --dist-url=https://atom.io/download/atom-shell reinstall +$ node_modules/.bin/node-pre-gyp --target=0.36.11 --runtime=electron --fallback-to-build --directory node_modules/v8-profiler/ --dist-url=https://atom.io/download/atom-shell reinstall ``` -또한 [네이티브 모듈을 사용하는 방법](how-to-install-native-modules) 문서도 참고해보세요. +또한 [네이티브 모듈을 사용하는 방법][how-to-install-native-modules] 문서도 참고해보세요. ### 5. Electron 디버그 모드 활성화 diff --git a/docs-translations/ko-KR/tutorial/desktop-environment-integration.md b/docs-translations/ko-KR/tutorial/desktop-environment-integration.md index 38830b91e82d..beece23e3f75 100644 --- a/docs-translations/ko-KR/tutorial/desktop-environment-integration.md +++ b/docs-translations/ko-KR/tutorial/desktop-environment-integration.md @@ -14,14 +14,16 @@ Windows, Linux, OS X 운영체제 모두 기본적으로 어플리케이션에 통해 개발자가 편리하게 데스크톱 알림을 사용할 수 있는 기능을 제공합니다. 데스크톱 알림은 운영체제의 네이티브 알림 API를 사용하여 표시합니다. +**참고:** 이 API는 HTML5 API이기 때문에 렌더러 프로세스에서만 사용할 수 있습니다. + ```javascript -var myNotification = new Notification('Title', { +let myNotification = new Notification('Title', { body: 'Lorem Ipsum Dolor Sit Amet' }); -myNotification.onclick = function () { - console.log('Notification clicked') -} +myNotification.onclick = () => { + console.log('Notification clicked'); +}; ``` 위 코드를 통해 생성한 데스크톱 알림은 각 운영체제 모두 비슷한 사용자 경험을 제공하지만, @@ -88,7 +90,7 @@ app.clearRecentDocuments(); 않습니다. 어플리케이션 등록에 관련된 API의 모든 내용은 [Application Registration][app-registration]에서 찾아볼 수 있습니다. -유저가 JumpList에서 파일을 클릭할 경우 클릭된 파일의 경로가 커맨드 라인 인자로 추가되어 +유저가 JumpList에서 파일을 클릭할 경우 클릭된 파일의 경로가 커맨드 라인 인수로 추가되어 새로운 인스턴스의 어플리케이션이 실행됩니다. ### OS X에서 주의할 점 @@ -108,18 +110,17 @@ __Terminal.app의 dock menu:__ OS X에서만 사용 가능합니다: ```javascript -const electron = require('electron'); -const app = electron.app; -const Menu = electron.Menu; +const {app, Menu} = require('electron'); -var dockMenu = Menu.buildFromTemplate([ - { label: 'New Window', click: function() { console.log('New Window'); } }, - { label: 'New Window with Settings', submenu: [ - { label: 'Basic' }, - { label: 'Pro'} +const dockMenu = Menu.buildFromTemplate([ + {label: 'New Window', click() { console.log('New Window'); }}, + {label: 'New Window with Settings', submenu: [ + {label: 'Basic'}, + {label: 'Pro'} ]}, - { label: 'New Command...'} + {label: 'New Command...'} ]); + app.dock.setMenu(dockMenu); ``` @@ -147,7 +148,7 @@ __Internet Explorer의 작업:__ ![IE](http://i.msdn.microsoft.com/dynimg/IC420539.png) OS X의 dock menu(진짜 메뉴)와는 달리 Windows의 사용자 작업은 어플리케이션 바로 -가기처럼 작동합니다. 유저가 작업을 클릭할 때 설정한 인자와 함께 새로운 어플리케이션이 +가기처럼 작동합니다. 유저가 작업을 클릭할 때 설정한 인수와 함께 새로운 어플리케이션이 실행됩니다. 사용자 작업을 설정하려면 [app.setUserTasks][setusertaskstasks] 메서드를 통해 구현할 @@ -166,7 +167,7 @@ app.setUserTasks([ ]); ``` -작업 리스트를 비우려면 간단히 `app.setUserTasks` 메서드의 첫번째 인자에 빈 배열을 넣어 +작업 리스트를 비우려면 간단히 `app.setUserTasks` 메서드의 첫번째 인수에 빈 배열을 넣어 호출하면 됩니다: ```javascript @@ -201,24 +202,25 @@ __Windows Media Player의 미리보기 툴바:__ 미리보기 툴바를 설정할 수 있습니다: ```javascript -const BrowserWindow = require('electron').BrowserWindow; +const {BrowserWindow} = require('electron'); const path = require('path'); -var win = new BrowserWindow({ +let win = new BrowserWindow({ width: 800, height: 600 }); + win.setThumbarButtons([ { - tooltip: "button1", + tooltip: 'button1', icon: path.join(__dirname, 'button1.png'), - click: function() { console.log("button2 clicked"); } + click() { console.log('button2 clicked'); } }, { - tooltip: "button2", + tooltip: 'button2', icon: path.join(__dirname, 'button2.png'), flags:['enabled', 'dismissonclick'], - click: function() { console.log("button2 clicked."); } + click() { console.log('button2 clicked.'); } } ]); ``` @@ -239,28 +241,26 @@ __Audacious의 런처 숏컷:__ ![audacious](https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles?action=AttachFile&do=get&target=shortcuts.png) -## Taskbar progress 기능 (Windows & Unity) +## 작업 표시줄 안의 프로그래스 바 (Windows, OS X, Unity) -Windows에선 태스크바의 어플리케이션 버튼에 progress bar를 추가할 수 있습니다. +Windows에선 작업 표시줄의 어플리케이션 버튼에 프로그래스 바를 추가할 수 있습니다. 이 기능은 사용자가 어플리케이션의 창을 열지 않고도 어플리케이션의 작업의 상태 정보를 시각적으로 보여줄 수 있도록 해줍니다. -또한 Unity DE도 런처에 progress bar를 부착할 수 있습니다. +OS X에선 프로그래스바가 dock 아이콘의 일부에 표시됩니다. -__태스크바 버튼의 progress bar:__ +또한 Unity DE도 런처에 프로그래스 바를 부착할 수 있습니다. + +__작업 표시줄 버튼의 프로그래스 바:__ ![Taskbar Progress Bar](https://cloud.githubusercontent.com/assets/639601/5081682/16691fda-6f0e-11e4-9676-49b6418f1264.png) -__Unity 런처의 progress bar:__ - -![Unity Launcher](https://cloud.githubusercontent.com/assets/639601/5081747/4a0a589e-6f0f-11e4-803f-91594716a546.png) - 이 기능은 [BrowserWindow.setProgressBar][setprogressbar] API를 사용하여 구현할 수 있습니다: ```javascript -var window = new BrowserWindow({...}); -window.setProgressBar(0.5); +let win = new BrowserWindow({...}); +win.setProgressBar(0.5); ``` ## 작업 표시줄의 아이콘 오버레이 (Windows) @@ -286,8 +286,8 @@ __작업 표시줄 버튼 위의 오버레이:__ API를 사용할 수 있습니다: ```javascript -var window = new BrowserWindow({...}); -window.setOverlayIcon('path/to/overlay.png', 'Description for overlay'); +let win = new BrowserWindow({...}); +win.setOverlayIcon('path/to/overlay.png', 'Description for overlay'); ``` ## 대표 파일 제시 (OS X) @@ -305,9 +305,9 @@ __대표 파일 팝업 메뉴:__ [BrowserWindow.setDocumentEdited][setdocumentedited]를 사용할 수 있습니다: ```javascript -var window = new BrowserWindow({...}); -window.setRepresentedFilename('/etc/passwd'); -window.setDocumentEdited(true); +let win = new BrowserWindow({...}); +win.setRepresentedFilename('/etc/passwd'); +win.setDocumentEdited(true); ``` [addrecentdocument]: ../api/app.md#appaddrecentdocumentpath-os-x-windows diff --git a/docs-translations/ko-KR/tutorial/devtools-extension.md b/docs-translations/ko-KR/tutorial/devtools-extension.md index 655c6ed7d5c2..633e23d82683 100644 --- a/docs-translations/ko-KR/tutorial/devtools-extension.md +++ b/docs-translations/ko-KR/tutorial/devtools-extension.md @@ -1,63 +1,59 @@ -# 개발자 도구 확장 기능 +# 개발자 도구 확장 기능 어플리케이션의 디버깅을 쉽게 하기 위해 Electron은 기본적으로 [Chrome DevTools Extension][devtools-extension]을 지원합니다. -개발자 도구 확장 기능은 간단하게 사용할 확장 기능 플러그인의 소스 코드를 다운로드한 후 -`BrowserWindow.addDevToolsExtension` API를 통해 어플리케이션 내에 로드할 수 있습니다. -한가지 주의할 점은 확장 기능 사용시 창이 생성될 때 마다 일일이 해당 API를 호출할 필요는 -없습니다. +Electron은 유명한 웹 프레임워크를 디버깅하기 위해 사용할 수 있는 개발자 도구 확장 +기능을 사용할 수 있도록 [Chrome 개발자 도구 확장 기능][devtools-extension]을 +지원합니다. -**주의: 현재 React DevTools은 작동하지 않습니다. https://github.com/atom/electron/issues/915 이슈를 참고하세요!** +## 개발자 도구는 어떻게 로드하나요 -다음 예제는 [React DevTools Extension](https://github.com/facebook/react-devtools)을 -사용합니다. +Electron에 확장 기능을 로드하려면, Chrome 브라우저에서 다운로드 해야 하며, 파일 시스템 경로를 지정해야 합니다. 그리고 `BrowserWindow.addDevToolsExtension(extension)`를 호출함으로써 기능을 로드할 수 있습니다. -먼저 소스코드를 다운로드 받습니다: +예시로 [React Developer Tools][react-devtools]를 사용한다면: -```bash -$ cd /some-directory -$ git clone --recursive https://github.com/facebook/react-devtools.git -``` +1. Chrome 브라우저를 설치합니다. +2. `chrome://extensions`로 이동한 후 해시된 `fmkadmapgofadopljbjfkapdkoienihi` + 같이 생긴 확장 기능의 ID를 찾습니다. +3. Chrome에서 사용하는 확장 기능을 저장해둔 파일 시스템 경로를 찾습니다: + * Windows에선 `%LOCALAPPDATA%\Google\Chrome\User Data\Default\Extensions`; + * Linux에선: + * `~/.config/google-chrome/Default/Extensions/` + * `~/.config/google-chrome-beta/Default/Extensions/` + * `~/.config/google-chrome-canary/Default/Extensions/` + * `~/.config/chromium/Default/Extensions/` + * OS X에선 `~/Library/Application Support/Google/Chrome/Default/Extensions`. +4. 확장 기능의 경로를 `BrowserWindow.addDevToolsExtension` API로 전달합니다. + React Developer Tools의 경우 다음과 비슷해야 합니다: + `~/Library/Application Support/Google/Chrome/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/0.14.10_0` -[`react-devtools/shells/chrome/Readme.md`](https://github.com/facebook/react-devtools/blob/master/shells/chrome/Readme.md)를 -통해 확장 기능을 개발하는 방법을 알아볼 수 있습니다. +확장 기능의 이름은 `BrowserWindow.addDevToolsExtension`에서 반환되며, 이 이름을 +`BrowserWindow.removeDevToolsExtension` API로 전달함으로써 해당하는 확장 기능을 +언로드할 수 있습니다. -그리고 개발자 도구에서 다음 코드를 입력하면 확장 기능을 로드할 수 있습니다: +## 지원하는 개발자 도구 확장 기능 -```javascript -const BrowserWindow = require('electron').remote.BrowserWindow; -BrowserWindow.addDevToolsExtension('/some-directory/react-devtools/shells/chrome'); -``` +Electron은 아주 제한적인 `chrome.*` API만을 지원하므로 확장 기능이 지원하지 않는 +`chrome.*` API를 사용한다면 해당 기능은 작동하지 않을 것입니다. 다음 개발자 도구들은 +Electron에서 정상적으로 작동하는 것을 확인했으며 작동 여부를 보장할 수 있는 확장 +기능입니다: -확장 기능을 언로드 하고 콘솔을 다시 열 때 해당 확장 기능이 로드되지 않도록 하려면 -`BrowserWindow.removeDevToolsExtension` API를 사용하면 됩니다: +* [Ember Inspector](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) +* [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi) +* [Backbone Debugger](https://chrome.google.com/webstore/detail/backbone-debugger/bhljhndlimiafopmmhjlgfpnnchjjbhd) +* [jQuery Debugger](https://chrome.google.com/webstore/detail/jquery-debugger/dbhhnnnpaeobfddmlalhnehgclcmjimi) +* [AngularJS Batarang](https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk) +* [Vue.js devtools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) -```javascript -BrowserWindow.removeDevToolsExtension('React Developer Tools'); -``` +### 개발자 도구가 작동하지 않을 때 어떻게 해야 하나요? -## 개발자 도구 확장 기능의 구성 형식 +먼저 해당 확장 기능이 확실히 계속 유지되고 있는지를 확인하세요. 몇몇 확장 기능들은 +최신 버전의 Chrome 브라우저에서도 작동하지 않습니다. 그리고 이러한 확장 기능에 대해선 +Electron 개발팀에 해줄 수 있는 것이 아무것도 없습니다. -모든 개발자 도구 확장은 완벽히 Chrome 브라우저를 위해 작성되었기 때문에 Electron에서도 -로드할 수 있습니다. 하지만 반드시 확장 기능은 소스 코드 디렉터리(폴더) 형태여야 합니다. -그래서 `crx` 등의 포맷으로 패키징된 확장 기능의 경우 사용자가 직접 해당 패키지의 압축을 -풀어서 로드하지 않는 이상 Electron에서 해당 확장 기능의 압축을 풀 방법이 없습니다. - -## 백그라운드 페이지 - -현재 Electron은 Chrome에서 지원하는 백그라운드 페이지(background pages)를 지원하지 -않습니다. 몇몇 확장 기능은 이 기능에 의존하는 경우가 있는데, 이 때 해당 확장 기능은 -Electron에서 작동하지 않을 수 있습니다. - -## `chrome.*` API - -몇몇 Chrome 확장 기능은 특정 기능을 사용하기 위해 `chrome.*` API를 사용하는데, 이 -API들을 구현하기 위해 노력했지만 안타깝게도 아직 모든 API가 구현되지는 않았습니다. - -아직 모든 API가 구현되지 않았기 때문에 확장 기능에서 `chrome.devtools.*` 대신 -`chrome.*` API를 사용할 경우 확장 기능이 제대로 작동하지 않을 수 있음을 감안해야 합니다. -만약 문제가 발생할 경우 Electron의 GitHub 저장소에 관련 이슈를 올리면 해당 API를 -추가하는데 많은 도움이 됩니다. +위와 같은 상황이 아니라면 Electron의 이슈 리스트에 버그 보고를 추가한 후 예상한 것과 +달리 확장 기능의 어떤 부분의 정상적으로 작동하지 않았는지 설명하세요. [devtools-extension]: https://developer.chrome.com/extensions/devtools +[react-devtools]: https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi diff --git a/docs-translations/ko-KR/tutorial/electron-versioning.md b/docs-translations/ko-KR/tutorial/electron-versioning.md new file mode 100644 index 000000000000..13d83563033d --- /dev/null +++ b/docs-translations/ko-KR/tutorial/electron-versioning.md @@ -0,0 +1,20 @@ +# Electron 버전 관리 + +노련한 Node 개발자라면, `semver` (유의적 버전)에 대해 확실히 알고 있을 것입니다 - +그리고 제공된 의존성 관리 시스템은 고정된 버전 숫자 대신 견고한 가이드라인을 따릅니다. +Electron은 Node와 Chromium에 큰 의존성을 지니고 있는 만큼, 유의적 버전을 그대로 +따르지 않습니다. 따라서 항상 Electron의 특정 버전을 참조해야 합니다. + +버전 숫자는 다음과 같은 규칙으로 올라갑니다: + +* Major: Electron API의 주요 변경 사항을 반영합니다 - 만약 `0.37.0`에서 `1.0.0`로 + 업그레이드하는 경우, 어플리케이션을 업데이트해야 합니다. +* Minor: 주요 Chrome과 Node 버전의 업그레이드를 반영하거나; Electron의 중요한 변경 + 사항을 반영합니다 - 만약 `1.0.0`에서 `1.1.0`로 업그레이드하는 경우, 어플리케이션은 + 여전히 작동하겠지만, 약간의 업데이트가 필요할 수 있습니다. +* Patch: 새로운 기능과 버그 수정을 반영합니다 - 만약 `1.0.0`에서 `1.0.1`로 + 업그레이드하는 경우, 어플리케이션은 잘 작동할 것입니다. + +`electron-prebuilt`를 사용하고 있다면, Electron의 변경 사항을 확실하게 인지하고 +개발자 스스로 업그레이드를 적용하기 위해 고정된 버전 숫자를 사용하는 것을 권장합니다. +(`^1.1.0` 대신 `1.1.0` 사용) diff --git a/docs-translations/ko-KR/tutorial/mac-app-store-submission-guide.md b/docs-translations/ko-KR/tutorial/mac-app-store-submission-guide.md index c319f344b638..d163cb5e5c79 100644 --- a/docs-translations/ko-KR/tutorial/mac-app-store-submission-guide.md +++ b/docs-translations/ko-KR/tutorial/mac-app-store-submission-guide.md @@ -4,11 +4,7 @@ Electron은 v0.34.0 버전부터 앱 패키지를 Mac App Store(MAS)에 제출 되었습니다. 이 가이드는 어플리케이션을 앱 스토어에 등록하는 방법과 빌드의 한계에 대한 설명을 제공합니다. -__참고:__ v0.36.0 버전부터 어플리케이션이 샌드박스화 된 상태로 실행되면 GPU 작동을 -방지하는 버그가 있었습니다. 따라서 이 버그가 고쳐지기 전까진 v0.35.x 버전을 사용하는 -것을 권장합니다. 이 버그에 관한 자세한 사항은 [issue #3871][issue-3871]를 참고하세요. - -__참고:__ Mac App Store에 어플리케이션을 등록하려면 +**참고:** Mac App Store에 어플리케이션을 등록하려면 [Apple Developer Program][developer-program]에 등록되어 있어야 하며 비용이 발생할 수 있습니다. @@ -17,20 +13,39 @@ __참고:__ Mac App Store에 어플리케이션을 등록하려면 다음 몇 가지 간단한 절차에 따라 앱 스토어에 어플리케이션을 등록하는 방법을 알아봅니다. 한가지, 이 절차는 제출한 앱이 Apple로부터 승인되는 것을 보장하지 않습니다. 따라서 여전히 Apple의 [Submitting Your App][submitting-your-app] 가이드를 숙지하고 있어야 -하며 앱 스토어 제출 요구 사항을 확실히 인지하고 있어야합니다. +하며 앱 스토어 제출 요구 사항을 확실히 인지하고 있어야 합니다. ### 인증서 취득 앱 스토어에 어플리케이션을 제출하려면, 먼저 Apple로부터 인증서를 취득해야 합니다. 취득 방법은 웹에서 찾아볼 수 있는 [가이드][nwjs-guide]를 참고하면 됩니다. +### Team ID 얻기 + +어플리케이션에 서명하기 전에, 먼저 개발 계정의 Team ID를 알아야 합니다. Team ID를 +알아보려면 [Apple Developer Center](https://developer.apple.com/account/)에 +로그인한 후, 사이드바의 Membership을 클릭합니다. Team ID는 Membership Information +섹션의 팀 이름 밑에 위치합니다. + ### 앱에 서명하기 -Apple로부터 인증서를 취득했다면, [어플리케이션 배포](application-distribution.md) -문서에 따라 어플리케이션을 패키징한 후 어플리케이션에 서명 합니다. 이 절차는 기본적으로 -다른 프로그램과 같습니다. 하지만 키는 Electron 종속성 파일에 각각 따로 서명 해야 합니다. +준비 작업이 끝난 후, [어플리케이션 배포](application-distribution.md) 문서에 따라 +어플리케이션을 패키징한 후 어플리케이션에 서명 합니다. -첫번째, 다음 두 자격(plist) 파일을 준비합니다. +먼저, Team ID를 키로 가지고 있는 어플리케이션의 `Info.plist`에 `ElectronTeamID` 키를 +추가해야 합니다: + +```xml + + + ... + ElectronTeamID + TEAM_ID + + +``` + +그리고, 다음 두 자격 파일을 준비해야 합니다. `child.plist`: @@ -56,38 +71,47 @@ Apple로부터 인증서를 취득했다면, [어플리케이션 배포](applica com.apple.security.app-sandbox + com.apple.security.application-groups + TEAM_ID.your.bundle.id ``` -그리고 다음 스크립트에 따라 어플리케이션에 서명합니다: +`TEAM_ID` 부분은 Team ID로 치환하고, `your.bundle.id` 부분은 어플리케이션의 Bundle +ID로 치환해야 합니다. + +그리고 다음 스크립트를 통해 어플리케이션에 서명합니다: ```bash #!/bin/bash -# 어플리케이션의 이름 +# 어플리케이션의 이름. APP="YourApp" -# 서명할 어플리케이션의 경로 -APP_PATH="/path/to/YouApp.app" -# 서명된 패키지의 출력 경로 +# 서명할 어플리케이션의 경로. +APP_PATH="/path/to/YourApp.app" +# 서명된 패키지의 출력 경로. RESULT_PATH="~/Desktop/$APP.pkg" -# 요청한 인증서의 이름 +# 요청한 인증서의 이름. APP_KEY="3rd Party Mac Developer Application: Company Name (APPIDENTITY)" INSTALLER_KEY="3rd Party Mac Developer Installer: Company Name (APPIDENTITY)" +# plist 파일의 경로. +CHILD_PLIST="/path/to/child.plist" +PARENT_PLIST="/path/to/parent.plist" FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper.app/" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper EH.app/" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper NP.app/" -if [ -d "$FRAMEWORKS_PATH/Squirrel.framework/Versions/A" ]; then - # non-MAS 빌드 서명 - codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Mantle.framework/Versions/A" - codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/ReactiveCocoa.framework/Versions/A" - codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Squirrel.framework/Versions/A" -fi -codesign -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Electron Framework" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libffmpeg.dylib" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libnode.dylib" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper.app/Contents/MacOS/$APP Helper" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper.app/" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper EH.app/Contents/MacOS/$APP Helper EH" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper EH.app/" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper NP.app/Contents/MacOS/$APP Helper NP" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper NP.app/" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$APP_PATH/Contents/MacOS/$APP" +codesign -s "$APP_KEY" -f --entitlements "$PARENT_PLIST" "$APP_PATH" productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH" ``` @@ -96,11 +120,15 @@ productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RES 문서를 참고하여 기본적인 개념을 이해해야 합니다. 그리고 자격(plist) 파일에 어플리케이션에서 요구하는 권한의 키를 추가합니다. -### 어플리케이션을 업로드하고 심사용 앱으로 제출 +### 어플리케이션 업로드 어플리케이션 서명을 완료한 후 iTunes Connect에 업로드하기 위해 Application Loader를 사용할 수 있습니다. 참고로 업로드하기 전에 [레코드][create-record]를 만들었는지 -확인해야 합니다. 그리고 [심사를 위해 앱을 제출][submit-for-review]할 수 있습니다. +확인해야 합니다. + +### 어플리케이션을 심사에 제출 + +위 과정을 마치면 [어플리케이션을 심사를 위해 제출][submit-for-review]할 수 있습니다. ## MAS 빌드의 한계 @@ -117,7 +145,7 @@ productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RES * 어플리케이션이 DNS의 변경을 감지하지 못할 수 있습니다. 또한 어플리케이션 샌드박스 개념으로 인해 어플리케이션에서 접근할 수 있는 리소스는 -엄격하게 제한되어 있습니다. 자세한 내용은 [App Sandboxing][app-sandboxing] 문서를 +엄격하게 제한되어 있습니다. 자세한 내용은 [앱 샌드박싱][app-sandboxing] 문서를 참고하세요. ## Electron에서 사용하는 암호화 알고리즘 @@ -164,5 +192,5 @@ ERN의 승인을 얻는 방법은, 다음 글을 참고하는 것이 좋습니 [create-record]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/CreatingiTunesConnectRecord.html [submit-for-review]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SubmittingTheApp.html [app-sandboxing]: https://developer.apple.com/app-sandboxing/ -[issue-3871]: https://github.com/atom/electron/issues/3871 [ern-tutorial]: https://carouselapps.com/2015/12/15/legally-submit-app-apples-app-store-uses-encryption-obtain-ern/ +[temporary-exception]: https://developer.apple.com/library/mac/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/AppSandboxTemporaryExceptionEntitlements.html diff --git a/docs-translations/ko-KR/tutorial/online-offline-events.md b/docs-translations/ko-KR/tutorial/online-offline-events.md index f66e2e2bbb59..de2e10f9efe9 100644 --- a/docs-translations/ko-KR/tutorial/online-offline-events.md +++ b/docs-translations/ko-KR/tutorial/online-offline-events.md @@ -1,18 +1,17 @@ # 온라인/오프라인 이벤트 감지 -온라인/오프라인 이벤트는 다음 예제와 같이 랜더러 프로세스에서 표준 HTML5 API를 이용하여 +온라인/오프라인 이벤트는 다음 예시와 같이 렌더러 프로세스에서 표준 HTML5 API를 이용하여 구현할 수 있습니다. _main.js_ ```javascript -const electron = require('electron'); -const app = electron.app; -const BrowserWindow = electron.BrowserWindow; +const {app, BrowserWindow} = require('electron'); -var onlineStatusWindow; -app.on('ready', function() { - onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); +let onlineStatusWindow; + +app.on('ready', () => { + onlineStatusWindow = new BrowserWindow({width: 0, height: 0, show: false}); onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); }); ``` @@ -24,7 +23,7 @@ _online-status.html_ , Chrome , - Electron 을 사용합니다. + Electron 을 사용합니다. ``` @@ -140,12 +155,13 @@ app.on('ready', function() { 패키징 하고 패키징한 앱을 실행할 수 있습니다. 또한 Electron 실행파일을 다운로드 받아 바로 실행해 볼 수도 있습니다. -### electron-prebuilt 사용 +### electron-prebuilt -`npm`을 통해 `electron-prebuilt` 패키지를 전역에 설치하면 간단한 명령으로 앱을 -실행할 수 있습니다. +[`electron-prebuilt`](https://github.com/electron-userland/electron-prebuilt)는 +Electron의 미리 컴파일된 바이너리를 포함하는 `npm` 모듈입니다. -앱 디렉터리 내에서 다음 명령으로 실행할 수 있습니다: +만약 `npm`을 통해 전역에 이 모듈을 설치했다면, 어플리케이션 소스 디렉터리에서 다음 +명령을 실행하면 바로 실행할 수 있습니다: ```bash electron . @@ -186,7 +202,7 @@ $ ./Electron.app/Contents/MacOS/Electron your-app/ ``` 어플리케이션 실행파일은 `Electron`의 release 패키지에 포함되어 있습니다. -[여기](https://github.com/atom/electron/releases)에서 다운로드 받을 수 있습니다. +[여기](https://github.com/electron/electron/releases)에서 다운로드 받을 수 있습니다. ### 배포용 실행 파일 만들기 @@ -195,10 +211,10 @@ $ ./Electron.app/Contents/MacOS/Electron your-app/ ### 미리 작성된 앱 실행하기 -[`atom/electron-quick-start`](https://github.com/atom/electron-quick-start) -저장소를 클론하면 이 문서에서 작성한 예제 앱을 바로 실행해 볼 수 있습니다. +[`atom/electron-quick-start`](https://github.com/electron/electron-quick-start) +저장소를 클론하면 이 문서에서 작성한 예시 앱을 바로 실행해 볼 수 있습니다. -**참고**: 이 예제를 실행시키려면 [Git](https://git-scm.com)과 +**참고**: 이 예시를 실행시키려면 [Git](https://git-scm.com)과 [Node.js](https://nodejs.org/en/download/)가 필요합니다. (CLI에서 실행 가능한 [npm](https://npmjs.org)이 있어야 합니다) @@ -206,7 +222,7 @@ $ ./Electron.app/Contents/MacOS/Electron your-app/ ```bash # 저장소를 클론합니다 -$ git clone https://github.com/atom/electron-quick-start +$ git clone https://github.com/electron/electron-quick-start # 저장소 안으로 들어갑니다 $ cd electron-quick-start # 어플리케이션의 종속성 모듈을 설치한 후 실행합니다 diff --git a/docs-translations/ko-KR/tutorial/security.md b/docs-translations/ko-KR/tutorial/security.md new file mode 100644 index 000000000000..5d16d86ed6ec --- /dev/null +++ b/docs-translations/ko-KR/tutorial/security.md @@ -0,0 +1,75 @@ +# 보안, 네이티브 호환성, 그리고 신뢰성 + +웹 개발자로써, 우리는 일반적으로 브라우저의 강력한 웹 보안을 잘 이용해왔습니다 - 작성한 +코드에 관련된 보안 문제는 아주 적었습니다. 우리는 웹 사이트의 샌드박스안에서 허용된 +상당히 제한된 권한과 기능에 의존해왔으며, 우리는 새로운 보안 위협에 대해 발 빠르게 +대응할 수 있는 엔지니어들로 이루어진 커다란 팀으로부터 만들어진 브라우저를 사용자들이 +마음 놓고 즐길 것이라고 믿어왔습니다. + +하지만 Electron을 사용하여 작업한다면, Electron은 웹 브라우저가 아니라는 것을 기억해 +두어야 합니다. Electron은 친근한 웹 기술을 사용하여 풍부한 기능의 데스크톱 +어플리케이션을 만들 수 있도록 해주지만 그만큼 코드가 더 큰 힘을 사용합니다. +JavaScript가 파일 시스템, 유저 쉘, 그외 여러가지 기능에 접근할 수 있습니다. 이러한 +능력은 높은 퀄리티를 가진 네이티브 어플리케이션을 개발할 수 있도록 해주지만 코드에 +부여된 추가적인 기능만큼 고유한 보안 문제가 발생할 가능성이 있습니다. + +이를 염두해두고, 신뢰할 수 없는 출처의 임의의 콘텐츠를 표시할 때 Electron에서 +자체적으로 처리하지 않는 심각한 보안 문제를 야기할 수 있다는 점을 주의해야 합니다. +실제로도, 가장 유명한 Electron 어플리케이션들은 (Atom, Slack, Visual Studio Code, +등) 주로 로컬 콘텐츠를 (또는 Node 통합이 제외된 신뢰된, 보안된 원격 콘텐츠) 사용합니다 +- 만약 어플리케이션이 온라인 출처에서 가져온 코드를 실행한다면, 그 코드가 악성 코드가 +아닌지 판별하는 것은 본인의 책임입니다. + +## Chromium 보안 문제와 업그레이드 + +Electron이 새로운 버전의 Chromium을 가능한 한 빠르게 지원하려고 노력하지만, +개발자는 이러한 업그레이딩 작업은 매우 힘든 작업이라는 것을 알아야 합니다 - 각 관련된 +수십에서 심지어 백자리 개수의 파일들을 손수 수정해야 합니다. 주어진 자원과 현재 +기여도를 생각한다면, Electron은 항상 최신 버전의 Chromium을 유지하지 못할 수 있으며, +며칠부터 몇 주까지 더 걸릴 수 있습니다. + +현재 Chromium 구성 요소를 업데이트하는 시스템은 우리가 사용할 수 있는 자원과 이 +프레임워크를 기반으로 구축된 대부분의 어플리케이션이 요구하는 것 사이에서 적절한 균형을 +유지하고 있다고 느끼고 있습니다. 우리는 확실히 Electron 위에 무언가를 만드는 사람들의 +사용 사례에 대한 자세한 내용을 듣는 것에 관심이 있습니다. 이러한 노력을 지원하는 Pull +request와 기여는 언제나 환영합니다. + +## 위 조언 무시하기 + +원격 위치에서 받아온 코드를 로컬에서 실행하는 경우 언제나 보안 문제가 존재합니다. +예를 들어, 원격 웹 사이트가 브라우저 윈도우에서 표시될 때를 생각해볼 때, 만약 공격자가 +어떠한 방법으로 웹 페이지의 콘텐츠를 변경하는 경우 (소스를 직접적으로 공격하거나 +어플리케이션과 실질적인 위치 사이에서 공격하는 등), 공갹자는 사용자의 기기에서 네이티브 +코드를 실행할 수 있습니다. + +> :warning: 어떠한 상황에서도 원격 코드를 로드하고 실행할 땐 Node 통합 기능을 +비활성화하고, 로컬 파일만 (어플리케이션 패키지 내부에 포함된) Node 코드를 실행시킬 수 +있도록 하는 것이 좋습니다. 원격 콘텐츠를 표시할 땐, 항상 `webview`를 사용하고 +`nodeIntegration`이 비활성화되어있는지 확인하세요. + +#### 체크 리스트 + +이 리스트는 완벽하지 않습니다, 하지만 최소한 다음 사항은 확인하는 것이 좋습니다: + +* 보안된 (https) 콘텐츠만 표시합니다. +* 원격 콘텐츠를 표시하는 모든 렌더러에서 Node 통합 기능을 비활성화합니다. + (`webPreferences` 사용) +* `webSecurity`를 비활성화하지 않습니다. 이 옵션을 비활성화하면 동일-출처 정책도 + 비활성화됩니다. +* [`Content-Security-Policy`](http://www.html5rocks.com/en/tutorials/security/content-security-policy/) + 를 정의하고, 한정적인 규칙을 사용하세요 (ie: `script-src 'self'`) +* 문자열을 코드로 실행할 수 있는 + [`eval`을 덮어쓰고 비활성화](https://github.com/nylas/N1/blob/0abc5d5defcdb057120d726b271933425b75b415/static/index.js#L6)하세요. +* `allowDisplayingInsecureContent`를 `true`로 설정하지 마세요. +* `allowRunningInsecureContent`를 `true`로 설정하지 마세요. +* 무엇을 하고 있는지 확실히 알고 있지않는 이상 `experimentalFeatures` 또는 + `experimentalCanvasFeatures`를 활성화하지 마세요. +* 무엇을 하고 있는지 확실히 알고 있지않는 이상 `blinkFeatures`를 활성화하지 마세요. +* WebViews: `nodeintegration`를 `false`로 설정하세요. +* WebViews: `disablewebsecurity`를 사용하지 마세요. +* WebViews: `allowpopups`를 사용하지 마세요. +* WebViews: 원격 CSS/JS와 `insertCSS` 또는 `executeJavaScript`를 함께 사용하지 + 마세요. + +다시 말하지만, 이 리스트는 그저 위험을 최소화할 뿐이며 완전히 제거하지 않습니다. 만약 +목적이 그저 웹 사이트를 보여주는 것이라면 일반 웹 브라우저가 더 안전한 방법입니다. diff --git a/docs-translations/ko-KR/tutorial/supported-platforms.md b/docs-translations/ko-KR/tutorial/supported-platforms.md index 656d31308e07..d71951cd3d8e 100644 --- a/docs-translations/ko-KR/tutorial/supported-platforms.md +++ b/docs-translations/ko-KR/tutorial/supported-platforms.md @@ -12,7 +12,7 @@ Windows 7 이후 버전만 지원됩니다. Windows Vista에서도 작동할 수 테스트가 완료되지 않았습니다. 윈도우용 바이너리는 `x86`과 `x64` 모두 제공됩니다. 그리고 `ARM` 버전 윈도우는 아직 -지원하지 않습니다. (역주: 추후 지원할 가능성이 있습니다) +지원하지 않습니다. ### Linux diff --git a/docs-translations/ko-KR/tutorial/testing-on-headless-ci.md b/docs-translations/ko-KR/tutorial/testing-on-headless-ci.md new file mode 100644 index 000000000000..8343777e4364 --- /dev/null +++ b/docs-translations/ko-KR/tutorial/testing-on-headless-ci.md @@ -0,0 +1,58 @@ +# Headless CI 시스템에서 테스팅하기 (Travis, Jenkins) + +Chromium을 기반으로 한 Electron은 작업을 위해 디스플레이 드라이버가 필요합니다. +만약 Chromium이 디스플레이 드라이버를 찾기 못한다면, Electron은 그대로 실행에 +실패할 것입니다. 따라서 실행하는 방법에 관계없이 모든 테스트를 실행하지 못하게 됩니다. +Electron 기반 어플리케이션을 Travis, Circle, Jenkins 또는 유사한 시스템에서 테스팅을 +진행하려면 약간의 설정이 필요합니다. 요점만 말하자면, 가상 디스플레이 드라이버가 +필요합니다. + +## 가상 디스플레이 드라이버 설정 + +먼저, [Xvfb](https://en.wikipedia.org/wiki/Xvfb)를 설치합니다. 이것은 X11 +디스플레이 서버 프로토콜의 구현이며 모든 그래픽 작업을 스크린 출력없이 인-메모리에서 +수행하는 가상 프레임버퍼입니다. 정확히 우리가 필요로 하는 것입니다. + +그리고, 가상 xvfb 스크린을 생성하고 DISPLAY라고 불리우는 환경 변수를 지정합니다. +Electron의 Chromium은 자동적으로 `$DISPLAY` 변수를 찾습니다. 따라서 앱의 추가적인 +다른 설정이 필요하지 않습니다. 이러한 작업은 Paul Betts의 +[xvfb-maybe](https://github.com/paulcbetts/xvfb-maybe)를 통해 자동화 할 수 +있습니다: `xvfb-maybe`를 테스트 커맨드 앞에 추가하고 현재 시스템에서 요구하면 +이 작은 툴이 자동적으로 xvfb를 설정합니다. Windows와 Mac OS X에선 간단히 아무 작업도 +하지 않습니다. + +``` +## Windows와 OS X에선, 그저 electron-mocha를 호출합니다 +## Linux에선, 현재 headless 환경에 있는 경우 +## xvfb-run electron-mocha ./test/*.js와 같습니다 +xvfb-maybe electron-mocha ./test/*.js +``` + +### Travis CI + +Travis에선, `.travis.yml`이 대충 다음과 같이 되어야 합니다: + +``` +addons: + apt: + packages: + - xvfb + +install: + - export DISPLAY=':99.0' + - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & +``` + +### Jenkins + +Jenkins는 [Xvfb 플러그인이 존재합니다](https://wiki.jenkins-ci.org/display/JENKINS/Xvfb+Plugin). + +### Circle CI + +Circle CI는 멋지게도 이미 xvfb와 `$DISPLY` 변수가 준비되어 있습니다. 따라서 +[추가적인 설정이 필요하지](https://circleci.com/docs/environment#browsers) 않습니다. + +### AppVeyor + +AppVeyor는 Windows에서 작동하기 때문에 Selenium, Chromium, Electron과 그 비슷한 +툴들을 복잡한 과정 없이 모두 지원합니다. - 설정이 필요하지 않습니다. diff --git a/docs-translations/ko-KR/tutorial/using-native-node-modules.md b/docs-translations/ko-KR/tutorial/using-native-node-modules.md index d1c3aa167ad6..156319aaec4b 100644 --- a/docs-translations/ko-KR/tutorial/using-native-node-modules.md +++ b/docs-translations/ko-KR/tutorial/using-native-node-modules.md @@ -9,10 +9,10 @@ Electron의 V8 버전에 맞춰 네이티브 모듈을 다시 빌드하고 헤 네이티브 모듈은 node.js가 새로운 V8 버전을 사용함으로 인해 작동하지 않을 수 있습니다. 사용하는 네이티브 모듈이 Electron에 맞춰 작동할 수 있도록 하려면 Electron에서 사용하는 node.js의 버전을 확인할 필요가 있습니다. Electron에서 사용하는 node 버전은 -[releases](https://github.com/atom/electron/releases)에서 확인할 수 있으며 +[releases](https://github.com/electron/electron/releases)에서 확인할 수 있으며 `process.version`을 출력하여 버전을 확인할 수도 있습니다. -([시작하기](https://github.com/atom/electron/blob/master/docs/tutorial/quick-start.md)의 -예제를 참고하세요) +([시작하기](./quick-start.md)의 +예시를 참고하세요) 혹시 직접 만든 네이티브 모듈이 있다면 [NAN](https://github.com/nodejs/nan/) 모듈을 사용하는 것을 고려해보는 것이 좋습니다. 이 모듈은 다중 버전의 node.js를 지원하기 쉽게 @@ -28,7 +28,7 @@ Electron도 이 모듈을 통해 포팅된 네이티브 모듈을 사용할 수 [`electron-rebuild`](https://github.com/paulcbetts/electron-rebuild) 패키지를 사용하면 빠르고 간단하게 네이티브 모듈을 다시 빌드할 수 있습니다. -다음 예제는 `electron-rebuild`를 통해 자동으로 모듈의 헤더를 다운로드하고 네이티브 +다음 예시는 `electron-rebuild`를 통해 자동으로 모듈의 헤더를 다운로드하고 네이티브 모듈을 빌드합니다: ```sh 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 2f15a3532eea..c892d154e5a5 100644 --- a/docs-translations/ko-KR/tutorial/using-pepper-flash-plugin.md +++ b/docs-translations/ko-KR/tutorial/using-pepper-flash-plugin.md @@ -1,7 +1,8 @@ # Pepper 플래시 플러그인 사용하기 -Electron은 Pepper 플래시 플러그인을 지원합니다. -Pepper 플래시 플러그인을 사용하려면 Pepper 플래시 플러그인의 위치를 지정해야 합니다. +Electron은 Pepper 플래시 플러그인을 지원합니다. Electron에서 Pepper 플래시 +플러그인을 사용하려면 Pepper 플래시 플러그인의 위치를 지정한 후 어플리케이션 내에서 +활성화 시켜야 합니다. ## 플래시 플러그인 준비하기 @@ -12,29 +13,29 @@ Pepper 플래시 플러그인을 사용하려면 Pepper 플래시 플러그인 ## Electron 스위치 추가 플러그인을 사용하려면 Electron 커맨드 라인에 `--ppapi-flash-path` 와 -`ppapi-flash-version` 플래그를 app의 ready 이벤트가 호출되기 전에 추가해야 합니다. -그리고 `browser-window`에 `plugins` 스위치도 추가해야합니다. +`ppapi-flash-version` 플래그를 `app`의 `ready` 이벤트가 발생하기 전에 추가해야 +합니다. 그리고 `browser-window`에 `plugins` 옵션을 활성화해야 합니다. ```javascript // 플래시 플러그인의 위치를 설정합니다. -// Windows의 경우, /path/to/pepflashplayer.dll +// Windows의 경우, /path/to/pepflashplayer.dll 또는 main.js에 존재하는 경우 pepflashplayer.dll // OS X의 경우, /path/to/PepperFlashPlayer.plugin // Linux의 경우, /path/to/libpepflashplayer.so app.commandLine.appendSwitch('ppapi-flash-path', '/path/to/libpepflashplayer.so'); -// Specify flash version, for example, v17.0.0.169 +// 선택적인으로 플래시 플레이어의 버전을 설정합니다. 예시로는, 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 +app.on('ready', () => { + win = new BrowserWindow({ + width: 800, + height: 600, + webPreferences: { + plugins: true } }); - mainWindow.loadURL('file://' + __dirname + '/index.html'); - // Something else + win.loadURL(`file://${__dirname}/index.html`); + // 이외의 코드 }); ``` @@ -45,3 +46,13 @@ app.on('ready', function() { ```html ``` + +## 문제 해결 + +개발자 도구의 콘솔에서 `navigator.plugins`를 탐색하면 Pepper 플래시 플러그인이 잘 +로드되었는지를 확인할 수 있습니다. (물론 플러그인의 경로를 잘 설정하지 않으면 확인할 +수 없습니다) + +Pepper 플래시 플러그인의 구조는 Electron과 일치해야 합니다. Windows에서 자주 +발생하는 문제는 32비트 버전의 플래시 플레이어를 64비트 버전의 Electron에서 사용하는 +것입니다. diff --git a/docs-translations/ko-KR/tutorial/using-selenium-and-webdriver.md b/docs-translations/ko-KR/tutorial/using-selenium-and-webdriver.md index 6cc5472bb12c..e4d333c44e05 100644 --- a/docs-translations/ko-KR/tutorial/using-selenium-and-webdriver.md +++ b/docs-translations/ko-KR/tutorial/using-selenium-and-webdriver.md @@ -7,20 +7,59 @@ > 등의 작업을 수행할 수 있습니다. ChromeDriver는 Chromium의 WebDriver wire 프로토콜 > 스텐드얼론 서버 구현입니다. Chromium 과 WebDriver 팀 멤버에 의해 개발되었습니다. -Electron에서 `chromedriver`를 사옹하려면 드라이버에서 Electron을 찾을 수 있도록 해야 -하며 Electron은 Chrome 브라우저와 비슷하다는 점을 기억해야 합니다. +## Spectron 설정하기 + +[Spectron][spectron]은 공식적으로 지원하는 Electron을 위한 ChromeDriver 테스팅 +프레임워크입니다. 이는 [WebdriverIO](http://webdriver.io/)를 기반으로 만들어졌고, +테스트에서 Electron API에 접근하기 위한 헬퍼를 가지고 있으며 ChromeDriver를 포함하고 +있습니다. + +```bash +$ npm install --save-dev spectron +``` + +```js +// 윈도우가 제목과 함께 보이는지 검증하는 간단한 테스트. +var Application = require('spectron').Application +var assert = require('assert') + +var app = new Application({ + path: '/Applications/MyApp.app/Contents/MacOS/MyApp' +}) + +app.start().then(function () { + // 윈도우가 보이는지 확인합니다. + return app.browserWindow.isVisible() +}).then(function (isVisible) { + // 윈도우가 보이는지 검증합니다. + assert.equal(isVisible, true) +}).then(function () { + // 윈도우의 제목을 가져옵니다. + return app.client.getTitle() +}).then(function (title) { + // 윈도우의 제목을 검증합니다. + assert.equal(title, 'My App') +}).then(function () { + // 어플리케이션을 중지합니다. + return app.stop() +}).catch(function (error) { + // 테스트의 실패가 있다면 로깅합니다. + console.error('Test failed', error.message) +}) +``` ## WebDriverJs 설정하기 [WebDriverJs](https://code.google.com/p/selenium/wiki/WebDriverJs)는 WebDriver를 -사용하여 테스트 할 수 있도록 도와주는 node 패키지입니다. 다음 예제를 참고하세요. +사용하여 테스트 할 수 있도록 도와주는 node 패키지입니다. 다음 예시를 참고하세요. ### 1. 크롬 드라이버 시작 먼저, `chromedriver` 바이너리를 다운로드 받고 실행합니다: ```bash -$ ./chromedriver +$ npm install electron-chromedriver +$ ./node_modules/.bin/chromedriver Starting ChromeDriver (v2.10.291558) on port 9515 Only local connections are allowed. ``` @@ -42,7 +81,7 @@ $ npm install selenium-webdriver ```javascript const webdriver = require('selenium-webdriver'); -var driver = new webdriver.Builder() +const driver = new webdriver.Builder() // 작동하고 있는 크롬 드라이버의 포트 "9515"를 사용합니다. .usingServer('http://localhost:9515') .withCapabilities({ @@ -57,8 +96,8 @@ var driver = new webdriver.Builder() driver.get('http://www.google.com'); driver.findElement(webdriver.By.name('q')).sendKeys('webdriver'); driver.findElement(webdriver.By.name('btnG')).click(); -driver.wait(function() { - return driver.getTitle().then(function(title) { +driver.wait(() => { + return driver.getTitle().then((title) => { return title === 'webdriver - Google Search'; }); }, 1000); @@ -76,7 +115,8 @@ node 패키지입니다. 먼저, `chromedriver` 바이너리를 다운로드 받고 실행합니다: ```bash -$ chromedriver --url-base=wd/hub --port=9515 +$ npm install electron-chromedriver +$ ./node_modules/.bin/chromedriver --url-base=wd/hub --port=9515 Starting ChromeDriver (v2.10.291558) on port 9515 Only local connections are allowed. ``` @@ -92,29 +132,29 @@ $ npm install webdriverio ### 3. 크롬 드라이버에 연결 ```javascript const webdriverio = require('webdriverio'); -var options = { - host: "localhost", // localhost에서 작동중인 크롬 드라이버 서버를 사용합니다. - port: 9515, // 연결할 크롬 드라이버 서버의 포트를 설정합니다. - desiredCapabilities: { - browserName: 'chrome', - chromeOptions: { - binary: '/Path-to-Your-App/electron', // Electron 바이너리 경로 - args: [/* cli arguments */] // 선택 사항, 'app=' + /path/to/your/app/ - } +let options = { + host: 'localhost', // localhost에서 작동중인 크롬 드라이버 서버를 사용합니다. + port: 9515, // 연결할 크롬 드라이버 서버의 포트를 설정합니다. + desiredCapabilities: { + browserName: 'chrome', + chromeOptions: { + binary: '/Path-to-Your-App/electron', // Electron 바이너리 경로 + args: [/* cli arguments */] // 선택 사항, 'app=' + /path/to/your/app/ } + } }; -var client = webdriverio.remote(options); +let client = webdriverio.remote(options); client - .init() - .url('http://google.com') - .setValue('#q', 'webdriverio') - .click('#btnG') - .getTitle().then(function(title) { - console.log('Title was: ' + title); - }) - .end(); + .init() + .url('http://google.com') + .setValue('#q', 'webdriverio') + .click('#btnG') + .getTitle().then((title) => { + console.log('Title was: ' + title); + }) + .end(); ``` ## 작업 환경 @@ -127,3 +167,4 @@ client 디렉터리로 복사하는 불필요한 과정을 생략할 수 있습니다. [chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ +[spectron]: http://electron.atom.io/spectron diff --git a/docs-translations/ko-KR/tutorial/using-widevine-cdm-plugin.md b/docs-translations/ko-KR/tutorial/using-widevine-cdm-plugin.md index b9fec886769d..6031857efeab 100644 --- a/docs-translations/ko-KR/tutorial/using-widevine-cdm-plugin.md +++ b/docs-translations/ko-KR/tutorial/using-widevine-cdm-plugin.md @@ -8,7 +8,7 @@ Electron은 라이센스상의 문제로 Widevine CDM 플러그인을 직접 제 따라서 플러그인을 얻으려면 먼저 사용할 Electron 빌드의 아키텍쳐와 버전에 맞춰 공식 Chrome 브라우저를 설치해야 합니다. -__참고:__ Chrome 브라우저의 메이저 버전은 Electron에서 사용하는 Chrome 버전과 +**참고:** Chrome 브라우저의 메이저 버전은 Electron에서 사용하는 Chrome 버전과 같습니다, 만약 그렇지 않다면 `navigator.plugins`가 로드됐더라도 정상적으로 작동하지 않습니다. @@ -42,7 +42,7 @@ Linux에선 플러그인 바이너리들이 Chrome 브라우저와 함께 제공 `widevinecdmadapter`의 위치를 전달하고 플러그인의 버전을 `--widevine-cdm-version` 스위치에 전달해야 합니다. -__참고:__ `widevinecdmadapter` 바이너리가 Electron으로 전달되어도, `widevinecdm` +**참고:** `widevinecdmadapter` 바이너리가 Electron으로 전달되어도, `widevinecdm` 바이너리는 옆에 같이 두어야 합니다. 커맨드 라인 스위치들은 `app` 모듈의 `ready` 이벤트가 발생하기 전에 전달되어야 합니다. @@ -59,14 +59,14 @@ app.commandLine.appendSwitch('widevine-cdm-path', '/path/to/widevinecdmadapter.p // 플러그인의 버전은 크롬의 `chrome://plugins` 페이지에서 취득할 수 있습니다. app.commandLine.appendSwitch('widevine-cdm-version', '1.4.8.866'); -var mainWindow = null; -app.on('ready', function() { - mainWindow = new BrowserWindow({ +let win = null; +app.on('ready', () => { + win = new BrowserWindow({ webPreferences: { // `plugins`은 활성화되어야 합니다. plugins: true } - }) + }); }); ``` diff --git a/docs-translations/ko-KR/tutorial/windows-store-guide.md b/docs-translations/ko-KR/tutorial/windows-store-guide.md new file mode 100644 index 000000000000..e6401b08563d --- /dev/null +++ b/docs-translations/ko-KR/tutorial/windows-store-guide.md @@ -0,0 +1,136 @@ +# Windows 스토어 가이드 + +Windows 8부터, 오래되고 좋은 win32 실행 파일이 새로운 형제를 얻었습니다: Universial +Windows Platform. 새로운 `.appx` 포맷은 Cortana와 푸시 알림과 같은 다수의 강력한 +API뿐만 아니라, Windows Store를 통해 설치와 업데이트를 단순화합니다. + +Microsoft는 개발자들이 새로운 어플리케이션 모델의 좋은 기능들을 사용할 수 있도록 +[Electron 어플리케이션을 `.appx` 패키지로 컴파일시키는 도구를 개발했습니다](http://github.com/catalystcode/electron-windows-store). +이 가이드는 이 도구를 사용하는 방법과 Electron AppX 패키지의 호환성과 한정 사항을 +설명합니다. + +## 기본 상식과 요구 사항 + +Windows 10 "기념일 업데이트"는 win32 `.exe` 바이너리를 가상화된 파일 시스템과 +레지스트리를 함께 실행시킬 수 있도록 만들었습니다. 두 가지 모두 실행 중인 +어플리케이션과 Windows 컨테이너 안의 인스톨러에 의해 컴파일되는 동안 만들어지며, +설치가 되는 동안 Windows가 확실하게 어떤 변경 사항이 운영 체제에 적용되는지 식별할 수 +있도록 합니다. 가상 파일 시스템과 가상 레지스트리를 페어링 하는 실행 파일은 Windows가 +원-클릭으로 설치와 삭제를 할 수 있도록 만듭니다. + +더 나아가서, exe는 appx 모델 안에서 실행됩니다 - 이 말은 즉 Universial Windows +Platform에서 제공되는 수많은 API를 사용할 수 있다는 이야기입니다. 더 많은 기능을 +사용하기 위해, Electron 어플리케이션은 숨겨진 UWP 앱과 페어링 하여 `exe`와 같이 +실행할 수 있습니다 - 이렇게 헬퍼와 비슷하게 실행되고 작업을 실행하기 위해 +백그라운드에서 작동하며, 푸시 알림을 받거나, 다른 UWP 어플리케이션과 통신하는 역할을 +합니다. + +현재 존재하는 Electron 어플리케이션을 컴파일 하려면, 다음 요구 사항을 충족해야 합니다: + +* Windows 10 기념일 업데이트 - 엔터프라이즈 에디션 (이 업데이트는 빌드 번호가 14316 + 이거나, 더 높습니다 - 2016년 5월, Windows Insiders Preview 업데이트에 포함되어 + 있습니다) +* 64 비트 (x64) 머신과 프로세서, 하드웨어-보조 가상화, 그리고 Second Level Address + Translation (SLAT) +* Windows 10 SDK, [여기서](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk) + 다운로드할 수 있습니다. +* 최소 Node 4 버전 이상 (버전을 확인하려면 `node -v`를 실행하세요) + +그리고 CLI에서 `electron-windows-store`를 설치합니다: + +``` +npm install -g electron-windows-store +``` + +## 설정과 준비 + +처음 CLI를 실행하기 전에, "Windows Desktop App Converter"를 먼저 설정해야 합니다. +이 작업은 약 몇 분 정도 소요됩니다. 하지만 걱정하지 않아도 됩니다 - 이 작업은 딱 한 +번만 하면 됩니다. Desktop App Converter는 [여기](https://www.microsoft.com/en-us/download/details.aspx?id=51691)에서 +다운로드받을 수 있고, `DesktopAppConverter.zip`와 `BaseImage-14316.wim` 두 파일을 +모두 받아야 합니다. + +1. `DesktopAppConverter.zip`의 압축을 풉니다. 그다음 PowerShell을 관리자 권한으로 + 실행하고 압축을 푼 위치로 이동합니다. (실행하는 모든 명령에 "관리자 권한"을 + 적용하려면 `Set-ExecutionPolicy bypass`를 실행하면 됩니다) +2. 그리고, `.\DesktopAppConverter.ps1 -Setup -BaseImage .\BaseImage-14316.wim`를 + 실행하여 Windows 베이스 이미지 (`BaseImage-14316.wim`)를 Desktop App Converter로 + 전달하고 설치를 진행합니다. +3. 만약 위 명령이 재시작을 요구하면, 기기를 재시작하고 위 명령을 다시 실행시키세요. + +설치가 완료되면, 컴파일할 Electron 어플리케이션 경로로 이동합니다. + +## Electron 어플리케이션 패키지 만들기 + +[electron-packager](https://github.com/electron-userland/electron-packager)를 +사용하여 어플리케이션을 패키징합니다. (또는 비슷한 도구를 사용합니다) 마지막으로 최종 +어플리케이션에선 `node_modules`가 필요 없으며 그저 어플리케이션의 크기를 늘릴 뿐이니 +확실하게 지웠는지 확인합니다. + +출력된 파일들은 대충 다음과 같아야 합니다: + +``` +├── Ghost.exe +├── LICENSE +├── content_resources_200_percent.pak +├── content_shell.pak +├── d3dcompiler_47.dll +├── ffmpeg.dll +├── icudtl.dat +├── libEGL.dll +├── libGLESv2.dll +├── locales +│ ├── am.pak +│ ├── ar.pak +│ ├── [...] +├── msvcp120.dll +├── msvcr120.dll +├── natives_blob.bin +├── node.dll +├── pdf.dll +├── resources +│ ├── app +│ └── atom.asar +├── snapshot_blob.bin +├── squirrel.exe +├── ui_resources_200_percent.pak +├── vccorlib120.dll +└── xinput1_3.dll +``` + +## 명령줄 도구 실행하기 + +관리자 권한의 PowerShell ("관리자 권한으로 실행")을 실행하고, 디렉토리 입력과 출력, +어플리케이션의 이름과 버전, 마지막으로 `node_modules`를 평탄화시키는 인수들과 함께 +`electron-windows-store`를 실행합니다. + +``` +electron-windows-store ` + --input-directory C:\myelectronapp ` + --output-directory C:\output\myelectronapp ` + --flatten true ` + --package-version 1.0.0.0 ` + --package-name myelectronapp +``` + +명령이 실행되면, 도구는 다음과 같이 작동합니다: Electron 어플리케이션을 입력으로 받고, +`node_modules`를 평탄화하고 어플리케이션을 `app.zip`으로 압축합니다. 그리고 +인스톨러와 Windows Container를 사용하여, "확장된" AppX 패키지를 만듭니다 - +Windows Application Manifest (`AppXManifest.xml`)와 동시에 가상 파일 시스템과 가상 +레지스트리를 포함하여 출력 폴더로 내보냅니다. + +확장된 AppX 파일이 만들어지면, 도구는 Windows App Packager (`MakeAppx.exe`)를 +사용하여 디스크의 파일로부터 단일-파일 AppX 패키지를 생성해냅니다. 최종적으로, 이 +도구는 새 AppX 패키지에 서명하기 위해 컴퓨터에서 신뢰된 인증서를 만드는 데 사용할 수 +있습니다. 서명된 AppX 패키지로, CLI는 자동으로 기기에 패키지를 설치할 수 있습니다. + +## AppX 패키지 사용하기 + +Windows 기념일 업데이트 (코드네임 Windows 레드스톤)가 아직 모든 일반 사용자에게 +배포되지 않았기 때문에, 올해까지는 어플리케이션을 Windows Store에 올릴 수 없을 것 +입니다 - 하지만 개발자 또는 회사 환경에서 `Add-AppxPackage` +[PowerShell Cmdlet을 통해](https://technet.microsoft.com/en-us/library/hh856048.aspx) +기기에 어플리케이션을 설치할 수 있습니다. + +또 다른 중요한 제약은 컴파일된 AppX 패키지는 여전히 win32 실행 파일이 담겨있다는 +것입니다 - 따라서 Xbox, HoloLen, Phone에서 실행할 수 없습니다. diff --git a/docs-translations/pt-BR/README.md b/docs-translations/pt-BR/README.md index 05fedfeb4381..8498ca420fc8 100644 --- a/docs-translations/pt-BR/README.md +++ b/docs-translations/pt-BR/README.md @@ -1,7 +1,7 @@ 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 +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. @@ -20,7 +20,7 @@ Existem muitas perguntas comuns que são feitas, verifique antes de criar uma is * [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) +* [Extensão DevTools](tutorial/devtools-extension.md) * [Usando o Plugin Pepper Flash](tutorial/using-pepper-flash-plugin.md) * [Usando o Plugin Widevine CDM](../../tutorial/using-widevine-cdm-plugin.md) diff --git a/docs-translations/pt-BR/api/browser-window.md b/docs-translations/pt-BR/api/browser-window.md index 143b6368bc1e..5ebfa6637725 100644 --- a/docs-translations/pt-BR/api/browser-window.md +++ b/docs-translations/pt-BR/api/browser-window.md @@ -112,10 +112,10 @@ Normalmente você utiliza o manipulador de eventos do `beforeunload` para decidi window.onbeforeunload = function(e) { console.log('Não quero ser fechada'); - // Diferente de navegadores comuns, nos quais uma string deve ser retornada e + // Diferente de navegadores comuns, nos quais uma string deve ser retornada e // o usuário deve confirmar se a janela será fechada, o Electron dá mais opções // aos desenvolvedores. Retornar uma string vazia ou false cancela o fechamento. - // Você também pode usar a API de diálogo para deixar que o usuário confirme o + // Você também pode usar a API de diálogo para deixar que o usuário confirme o // fechamento do aplicativo. e.returnValue = false; }; @@ -528,14 +528,6 @@ Retorna um boolean indicando se o documento da janela foi editado. Captura uma imagem da página dentro do `rect`. Após completar, `callback` será chamada com `callback(imagem)`. `imagem` é uma instância de [NativeImage](../../../docs/api/native-image.md) que guarda dados sobre a imagem. Omitir `rect` captura toda a página visível. -### `win.print([options])` - -Igual a `webContents.print([options])` - -### `win.printToPDF(options, callback)` - -Igual a `webContents.printToPDF(options, callback)` - ### `win.loadURL(url[, options])` Igual a `webContents.loadURL(url[, options])`. @@ -628,5 +620,3 @@ Define se a janela deve estar visível em todos os *workspaces*. Retorna um boolean indicando se a janela está visível em todos os *workspaces*. **Nota:** Esta API sempre retorna `false` no Windows. - - diff --git a/docs-translations/pt-BR/api/process.md b/docs-translations/pt-BR/api/process.md index 1c20e2df1187..71502de80d62 100644 --- a/docs-translations/pt-BR/api/process.md +++ b/docs-translations/pt-BR/api/process.md @@ -3,8 +3,8 @@ O objeto `process` no Electron tem as seguintes diferenças do objeto no upstrea * `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.versions.electron` String - Versão do Electron. +* `process.versions.chrome` String - Versão do Chromium. * `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`. diff --git a/docs-translations/pt-BR/development/build-instructions-linux.md b/docs-translations/pt-BR/development/build-instructions-linux.md index 532892a40833..5e8ffe447804 100644 --- a/docs-translations/pt-BR/development/build-instructions-linux.md +++ b/docs-translations/pt-BR/development/build-instructions-linux.md @@ -43,7 +43,7 @@ de um container de tamanho fixo de pelo menos 25 gigabytes. ## Baixando o Código ```bash -$ git clone https://github.com/atom/electron.git +$ git clone https://github.com/electron/electron.git ``` ## Bootstrapping diff --git a/docs-translations/pt-BR/development/coding-style.md b/docs-translations/pt-BR/development/coding-style.md index b0068d568924..acfdf7e3dbd7 100644 --- a/docs-translations/pt-BR/development/coding-style.md +++ b/docs-translations/pt-BR/development/coding-style.md @@ -23,4 +23,4 @@ Para CoffeeScript, seguimos o [Guia de Estilo] (https://github.com/styleguide/ja 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. +[discussão](https://github.com/electron/electron/issues/46) sobre este assunto. diff --git a/docs-translations/pt-BR/tutorial/devtools-extension.md b/docs-translations/pt-BR/tutorial/devtools-extension.md new file mode 100644 index 000000000000..958b70c2e1e6 --- /dev/null +++ b/docs-translations/pt-BR/tutorial/devtools-extension.md @@ -0,0 +1,46 @@ +# Extensão DevTools + +Para facilitar a depuração, o Electron provê suporte para a extensão [Chrome DevTools][devtools-extension]. + +Para a maioria das extensões DevTools você pode simplesmente baixar o código-fonte e usar a função `BrowserWindow.addDevToolsExtension` para carregá-las. As extensões carregadas serão lembradas, assim você não precisa carregar sempre que criar uma nova janela. + +**NOTA: Se o DevTools React não funcionar, verifique a issue https://github.com/electron/electron/issues/915** + +Por exemplo, para usar a extensão [React DevTools](https://github.com/facebook/react-devtools), primeiro você deve baixar seu código-fonte: + +```bash +$ cd /some-directory +$ git clone --recursive https://github.com/facebook/react-devtools.git +``` + +Siga as instruções em [`react-devtools/shells/chrome/Readme.md`](https://github.com/facebook/react-devtools/blob/master/shells/chrome/Readme.md) para fazer o build da extensão. + +Agora você poderá carregar a extensão no Electron abrindo o DevTools em qualquer janela, e executando o seguinte código no console do DevTools: + +```javascript +const BrowserWindow = require('electron').remote.BrowserWindow; +BrowserWindow.addDevToolsExtension('/some-directory/react-devtools/shells/chrome'); +``` + +Para remover a extensão, você pode executar a chamada `BrowserWindow.removeDevToolsExtension` +com o nome da extensão e ela não será carregada na próxima vez que você abrir o DevTools: + +```javascript +BrowserWindow.removeDevToolsExtension('React Developer Tools'); +``` + +## Formato das extensões DevTools + +Idealmente todas as extensões DevTools escritas para o navegador Chrome podem ser carregadas pelo Electron, mas elas devem estar em um diretório. Pacotes com extensão `crx` não podem ser carregados pelo Electron a não ser que tenha uma forma de extraí-los em um diretório. + +## Páginas em segundo plano (background pages) + +Atualmente o Electron não suporta páginas em segundo plano nas extensões do Chrome, então extensões com essa característica podem não funcionar no Electron. + +## APIs `chrome.*` + +Algumas extensões do Chrome podem usar a API `chrome.*`. Apesar de um esforço na implementação destas APIs no Electron, elas ainda não estão finalizadas. + +Dado que nem todas as funções `chrome.*` esstão implementadas, algumas extensões que utilizam `chrome.devtools.*` podem não funcionar. Você pode reportar este erro no issue tracker para que possamos adicionar suporte a essas APIs. + +[devtools-extension]: https://developer.chrome.com/extensions/devtools diff --git a/docs-translations/pt-BR/tutorial/quick-start.md b/docs-translations/pt-BR/tutorial/quick-start.md index 1b1d6942379d..6a21123ecf6e 100644 --- a/docs-translations/pt-BR/tutorial/quick-start.md +++ b/docs-translations/pt-BR/tutorial/quick-start.md @@ -130,7 +130,7 @@ Finalmente o `index.html` é a página web que você quer mostrar:

Hello World!

Nós estamos usando io.js - e Electron . + e Electron . ``` @@ -180,7 +180,7 @@ $ ./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). +[aqui](https://github.com/electron/electron/releases). ### Executar como uma distribuição diff --git a/docs-translations/pt-BR/tutorial/using-native-node-modules.md b/docs-translations/pt-BR/tutorial/using-native-node-modules.md index c5e9eeddd61e..8cc883046eb9 100644 --- a/docs-translations/pt-BR/tutorial/using-native-node-modules.md +++ b/docs-translations/pt-BR/tutorial/using-native-node-modules.md @@ -12,8 +12,8 @@ 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) +página [releases](https://github.com/electron/electron/releases) ou usando +`process.version` (veja [Quick Start](https://github.com/electron/electron/blob/master/docs/tutorial/quick-start.md) por exemplo). Considere usar [NAN](https://github.com/nodejs/nan/) para seus próprios diff --git a/docs-translations/ru-RU/tutorial/application-distribution.md b/docs-translations/ru-RU/tutorial/application-distribution.md new file mode 100644 index 000000000000..a43be71f65a9 --- /dev/null +++ b/docs-translations/ru-RU/tutorial/application-distribution.md @@ -0,0 +1,126 @@ +# Распространение приложения + +Чтобы разпространять ваше приложение на 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` (или `electron` на Linux, `electron.exe` на Windows), +и Electron запустится как ваше приложение. Теперь папка `electron` и есть дистрибутив, +который вы должны распространять пользователям. + +## Упаковка вашего приложения в файл + +Если вы не хотите распространять исходные коды вашего проект, вы можете +упаковать его в архив [asar](https://github.com/atom/asar), чтобы не +показывать пользователям исходные коды. + +Чтобы использовать `asar` для замены папки `app` на архив вам нужно +переименовать архив в `app.asar` и положить его в папку ресурсов Electron, +после чего Electron попробует считать ресурсы и запустить архив. + + +На OS X: + +```text +electron/Electron.app/Contents/Resources/ +└── app.asar +``` + +На Windows и Linux: + +```text +electron/resources/ +└── app.asar +``` + +Больше деталей можна найти в [инстуркции по упаковке приложения](application-packaging.md). + +## Ребрендирование скачанных исполняемых файлов +После того, как вы подключили ваше приложение к Electron, +вам наверняка захочеться ребрендировать его перед распространением. + +### Windows + +Вы можете переименовать `electron.exe` как пожелаете и поменять иконку и прочую +информацию приложениями вроде [rcedit](https://github.com/atom/rcedit). + +### 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` как пожелаете. + +## Rebranding by Rebuilding Electron from Source + +Вы также можете ребрендировать Electron изменив имя продукта и собрав его +из исходных кодов. Чтобы сделать это вам нужно изменить `atom.gyp` и полностью +пересобрать Electron. + +### grunt-build-atom-shell + +Проверка и пересборка кода Electron довольно сложная задача, так что мы +мы сделали файл-инструкцию для Grunt, который будет делать это автоматически: +[grunt-build-atom-shell](https://github.com/paulcbetts/grunt-build-atom-shell). + +Этот файл автоматически просмотрит изменения в `.gyp` фалле, соберёт +Electron из исходных кодов и пересоберёт модули Node, чтобы всё подходило +под новое имя. + +## Инструменты + +Вы также можете использовать инструменты оттретьих лиц, +которые сделают работу за вас: + +* [electron-packager](https://github.com/maxogden/electron-packager) +* [electron-builder](https://github.com/loopline-systems/electron-builder) diff --git a/docs-translations/ru-RU/tutorial/application-packaging.md b/docs-translations/ru-RU/tutorial/application-packaging.md new file mode 100644 index 000000000000..b63565ed7b8e --- /dev/null +++ b/docs-translations/ru-RU/tutorial/application-packaging.md @@ -0,0 +1,179 @@ +# Упаковка приложения + +Чтобы смягчить [проблемы](https://github.com/joyent/node/issues/6960) с длинными +именами под Windows, немного ускорить `require` и скрыть ваши исходные коды, вы +можете упаковать его в архив [asar][asar], немного поменяв исходный код. + +## Генерация архива `asar` + +Архив [asar][asar] - простой фомат похожий на tar, который собирает много файлов +в один. Electron может читать такой файл без распаковки. + +Шаги для упавки вашего приложения архив `asar`: + +### 1. Установите саму утилиту asar + +```bash +$ npm install -g asar +``` + +### 2. Упакуйте с помощью `asar pack` + +```bash +$ asar pack your-app app.asar +``` + +## Использование архивов `asar` + +В Electron есть два вида API: API Node, которые устанавливаются с помощью Node.Js и +веб API, которые предоставляюся Chromium. Оба предоставляют возможность считывать из +архивов `asar`. + +### Node API + +С специальными патчами в Electron, части Node API вроде `fs.readFile` и `require` +считают архивы `asar` виртуальными папками и файлы в них доступны как в обычных. + +Например, у нас есть арихив `example.asar` в `/path/to`: + +```bash +$ asar list /path/to/example.asar +/app.js +/file.txt +/dir/module.js +/static/index.html +/static/main.css +/static/jquery.min.js +``` + +Прочитаеем файл в архиве `asar`: + +```javascript +const fs = require('fs'); +fs.readFileSync('/path/to/example.asar/file.txt'); +``` + +Список всех файлов начиная от корня архива: + +```javascript +const fs = require('fs'); +fs.readdirSync('/path/to/example.asar'); +``` + +Ичпользуем модуль из архива: + +```javascript +require('/path/to/example.asar/dir/module.js'); +``` + +Вы также можете показывать веб страницы из архива `asar` через `BrowserWindow`: + +```javascript +const BrowserWindow = require('electron').BrowserWindow; +var win = new BrowserWindow({width: 800, height: 600}); +win.loadURL('file:///path/to/example.asar/static/index.html'); +``` + +### Веб API + +На веб страницах файлы запрашиваются с помощью протокола `file:`. Как и в Node API +архивы `asar` считаются за директории. + +Пример получения файла с помощью `$.get`: + +```html + +``` + + +### Использование архива `asar` в качестве обычного файла + +Для случаев, когда вам, например, нужно проверить хэш-сумму архива `asar`, +нужно использовать архив как файл. Для этой цели существует встроенный модуль +`original-fs`, который предоставляет доступ к `fs` без поддежки `asar`: + +```javascript +var originalFs = require('original-fs'); +originalFs.readFileSync('/path/to/example.asar'); +``` +Вы также можете выставить `process.noAsar` в `true`, чтобы выключить поддержку `asar` +в модуле `fs`: + +```javascript +process.noAsar = true; +fs.readFileSync('/path/to/example.asar'); +``` + +## Ограничения Node API + +Хотя мы и старались как могли, чтобы сделать `asar` максимально похожим на папки, +всё ещё существуют некоторые ограничения из-за низкоуровневой натуры Node API. + +### Архивы только для чтения + +Архивы не могут быть изменены, так что все функции API Node, которые меняют файлы, +не буду работать с архивами `asar`. + +### Нельзя установить рабочую директорию в архиве + +Хотя архивы `asar` и считаются папками, они ими на самом деле не являются, +так что вы не можете установить в них рабочую директорию. Передача +архивов `asar` в качестве аргумента `cwd` некоторым API также может вызывать ошибки. + + +### Распаковка для некоторых API + +Большинство API `fs` могут читать файлы или получить сведения о них прямо из +архива, без распаковки, однако для некоторых, которые передают путь к системным +вызовам, Electron распакует нужный файл в временную папку и передаст путь к этому +файлу. + +API которым нужна распаковка: + +* `child_process.execFile` +* `child_process.execFileSync` +* `fs.open` +* `fs.openSync` +* `process.dlopen` - используется `require` на нативных модулях + +### Подельная информация для `fs.stat` + +Объект `Stats`, возвращаемый `fs.stat`, и его друзья для остальных файлов +в архиве `asar` специально генерируются, потому что на самом деле этих файлов +не существует в файловой системе. Вам не стоит доверять информации +из объектов `Stats`, кроме, разве что, размера и типа файлов. + +### Запуск исполняемых файлов из архивов `asar` + +Существуют некоторые API Node, которые исполняют файлы, например `child_process.exec`, +`child_process.spawn` и `child_process.execFile`, но только `execFile` может +исполнять файлы из архивов `asar`. + +Так вышло потому, что `exec` и `spawn` принимают `команду` а не `файл` как параметр, +а `команды` исполняются в оболочке. Нет никакой реальной возможности проверить, +реален ли файл или находится в архиве `asar`, и даже если мы смогли бы проверить, +то неясно, как земенить путь к файлу без побочных эффектов. + +## Добавление распакованых файлов в архив `asar` + +Как говорилось выше, некоторые API Node будут распаковывать файлв, +чтобы их использовать. Кроме увеличенного потребления ресурсов это также +может вызвать предупрждения от антивирусов. + +Чтобы обойти это, вы можете распаковать некоторые файлы, создавая архивы, +с помощью опции `--unpack`. Пример показывает распаковку нативных модулей: + +```bash +$ asar pack app app.asar --unpack *.node +``` + +После запуска команды выше в вашей папке, кроме `app.asar`, появится +`app.asar.unpacked`, которая будет содержать распакованные файлы, эту +папку стоит копировать вместе с `app.asar` при распространении. + +[asar]: https://github.com/atom/asar diff --git a/docs-translations/ru-RU/tutorial/mac-app-store-submission-guide.md b/docs-translations/ru-RU/tutorial/mac-app-store-submission-guide.md new file mode 100644 index 000000000000..480ec35f760b --- /dev/null +++ b/docs-translations/ru-RU/tutorial/mac-app-store-submission-guide.md @@ -0,0 +1,145 @@ + +#Руководство по утверждению вашего приложения в App Store + +Начиная с версии v0.34.0 Electron позволяет вам сформировать данные для App Store к вашему приложению. +Данное руководство представляет собой пошаговую инструкцию по созданию данных файлов. + +Помните, что когда Вы подаете свое приложение на рассмотрение в App Store Вы должны обладать аккаунтом разработчика, +который стоит денег. + +## Как отправить свое приложение на рассмотрение в App Store + +Последующие шаги подробно описывают, что и в какой последовательности Вам требуется сделать, но не гарантируют что Ваше приложение +будет рассмотрено Apple. Мы так же рекомендуем вам прочитать официальную документацию по оформлению своего приложения и информации к нему +чтобы пройти проверку в App Store. + +## Получение сертификата + +Перед тем, как отправить свое приложение Вы должны получить сертефикат, как это описано в этом [руководстве](https://github.com/nwjs/nw.js/wiki/Mac-App-Store-%28MAS%29-Submission-Guideline#first-steps "Ссылка на руководство") + +## Регистрируем свое приложение (подписываем) + +После того, как Вы получили сертефикат, Вы можете упаковать свое прилоежние следуя правилам Application Distribution, +а затем подписать свое приложение. Этот шаг является базовым, но подписывать свое приложение нам нужно всего лишь один раз. + +Во-первых, нам нужно подготовить два файла: + + child.plist: + + + + + + com.apple.security.app-sandbox + + com.apple.security.inherit + + + + + parent.plist: + + + + + + com.apple.security.app-sandbox + + com.apple.security.temporary-exception.sbpl + (allow mach-lookup (global-name-regex #"^org.chromium.Chromium.rohitfork.[0-9]+$")) + + + +Затем подписываем свое приложение, с помощью специального сценария: + #!/bin/bash + + # Имя вашего приложения. + APP="YourApp" + # Путь до вашейго приложения. + APP_PATH="/path/to/YourApp.app" + # Путь до вашего установочного пакета. + RESULT_PATH="~/Desktop/$APP.pkg" + # Имя сертификата которое вы хотите. + 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 -s "$APP_KEY" -f --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Electron Framework" + codesign -s "$APP_KEY" -f --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libffmpeg.dylib" + codesign -s "$APP_KEY" -f --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libnode.dylib" + codesign -s "$APP_KEY" -f --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework" + codesign -s "$APP_KEY" -f --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper.app/Contents/MacOS/$APP Helper" + codesign -s "$APP_KEY" -f --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper.app/" + codesign -s "$APP_KEY" -f --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper EH.app/Contents/MacOS/$APP Helper EH" + codesign -s "$APP_KEY" -f --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper EH.app/" + codesign -s "$APP_KEY" -f --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper NP.app/Contents/MacOS/$APP Helper NP" + codesign -s "$APP_KEY" -f --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper NP.app/" + codesign -s "$APP_KEY" -f --entitlements child.plist "$APP_PATH/Contents/MacOS/$APP" + codesign -s "$APP_KEY" -f --entitlements parent.plist "$APP_PATH" + + productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH" + +Если вы только начали разрабатывать под Mac OS X, то мы желаем Вам прочитать [App SandBox](https://developer.apple.com/library/ios/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html "Ссылка для новичков в разработке приложений для Mac OS X") + +## Обновление приложения + +После того, как Вы подписали свое приложение вы сможете загрузить его в Itunes Connect для обработки, убедитесь, что вы создали [запись](https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/CreatingiTunesConnectRecord.html "ссылка на показ как создавать запись в Itunes Connect") перед отправкой. + +## Объяснение использования 'temporary-exception' + +Когда песочница Apple временно исключила ваше приложение, согласно [документации](https://developer.apple.com/library/mac/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/AppSandboxTemporaryExceptionEntitlements.html "Документация по исключениям") вам нужно объяснить насколько это важное исключение: + +>Примечание: если вы временно исключаете свое приложение, обязательно прочитайте и выполните рекомендации по правам на исключение. +>которые предоставляются в Itunes Connect. Самое важное указать почему ваше приложение должно быть исключенно. + +Вы можете объяснить, что ваше приложение построено на основе браузера Chromium, который использует Mach из-за его мульти-процесс архитектуры. Но есть еще вероятность, что ваше приложение не удалось проверить именно из-за этого. + +## Отправка приложения на проверку + +Следующие шаги описаны в официальной [документации](https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SubmittingTheApp.html "Официальная статья по отправке приложения на проверку") + +#Ограничения в Mac App Store + +Для того чтобы удовлетворить всем просьбам SandBox App Store , некотоыре из модулей были отключены: +- crashReporter +- autoUpdater +и следующие проблемы были несколько изменены: +- Захват видео на некоторых машинах может не работать +- Некоторые специалньые возможности могут не работать +- Приложения не будут в курсе изменения DNS +Так же из за использования SandBox App Store некоторые возможности могут быть не доступны или ограничены, подробнее о ограничениях +Вы можете прочитать в [документации](https://developer.apple.com/app-sandboxing/ "Ссылка на ограничения в SandBox AppStore") + +# Криптографические алгоритмы котоыре использует Electron + +Смотря в какой стране и городе Вы находитесь Apple может потребовать от Вас задокументировать алгоритмы криптографии котоыре вы используете +и если потребуется то попросит предоставить Вас копию регистрации вашего алгоритма шифрования. + +Electron использует следующие алгоритмы шифрования: +- AES - NIST SP 800-38A, NIST SP 800-38D, RFC 3394 +- HMAC - FIPS 198-1 +- ECDSA - ANS X9.62–2005 +- ECDH - ANS X9.63–2001 +- HKDF - NIST SP 800-56C +- PBKDF2 - RFC 2898 +- RSA - RFC 3447 +- SHA - FIPS 180-4 +- Blowfish - https://www.schneier.com/cryptography/blowfish/ +- CAST - RFC 2144, RFC 2612 +- DES - FIPS 46-3 +- DH - RFC 2631 +- DSA - ANSI X9.30 +- EC - SEC 1 +- IDEA - “On the Design and Security of Block Ciphers” book by X. Lai +- MD2 - RFC 1319 +- MD4 - RFC 6150 +- MD5 - RFC 1321 +- MDC2 - ISO/IEC 10118-2 +- RC2 - RFC 2268 +- RC4 - RFC 4345 +- RC5 - http://people.csail.mit.edu/rivest/Rivest-rc5rev.pdf +- RIPEMD - ISO/IEC 10118-3 + +Если Вы используете необычный алгоритм, то вот статья о том как получить разрешение на использование собственного алгоритма шифрования в +рамках закона США - [статья](https://pupeno.com/2015/12/15/legally-submit-app-apples-app-store-uses-encryption-obtain-ern/ "Статья о том как получить разрешение на свой алгоритм шифрования") diff --git a/docs-translations/ru-RU/tutorial/quick-start.md b/docs-translations/ru-RU/tutorial/quick-start.md new file mode 100644 index 000000000000..07244ec9be19 --- /dev/null +++ b/docs-translations/ru-RU/tutorial/quick-start.md @@ -0,0 +1,225 @@ +# Быстрый старт + +Electron позволяет вам делать приложения для рабочего стола на чистом JavaScript, +предоставляя среду с богатым API. Можете представлять его как Node.js, который +ориентирован на рабочий стол, а не веб сервера. + +Это, однако, не значит, что Electron — лишь привязки к GUI билиотекам. На деле +Electron использует веб-страницы как интерфейс, так что вы можете считать его +небольшим Chroumium браузером, который контролируется с помощью JavaScript. + +### Главный процесс + +В Electron процесс, который запускает `main` из `package.json` называется +__главным процессом__. Скрипт, который работает в главном процессе может +показывать GUI, создавая веб-страницы. + +### Процесс-рендерер + +Так как Electron использует Chromium для показа веб-страниц, +мульти-процессовая архитектура показа страниц Chromium тоже используется. +Каждая веб-страницы в Electron работает в своём собственном процессе, +который называется __процесс-рендерер__. + +В обычных браузерах веб-страницы обычно запускаются в "песочнице" и им недоступны +реальные ресурсы компьютера. Пользователи Electron же могут использовать API +Node.js на страницах, что допускает более низкоуровневую работу с операционной системой. + +### Разница мужду главным процессом и процессом-рендерером + +Главный процесс создаёт веб-страницы используя `BrowserWindow`. Каждый экземпляр +`BrowserWindow` показывает веб-страницу через свой собственный процесс-рендерер. +Если экземпляр `BrowserWindow` уничтожается, то и соответствующий процесс-рендерер тоже +завершается. + +Главный процесс управляет всеми веб-страницами и соответствующими им процессами-редерерами. +Каждый процесс-рендерер изолирован и управляет только своей веб-страницей. + +На веб-страницах не позволяется вызывать нативные API, которые управляют GUI, +потому что это очень опасно и может легко вызвать утечку ресурсов. Если вы хотите +выполнить действия с GUI на странице, процесс-рендерер этой страницы должен +"попросить" главный процесс сделать эти действия. + +В Electron есть несолько способов общения между процессам. Например, модули +[`ipcRenderer`](../api/ipc-renderer.md) и [`ipcMain`](../api/ipc-main.md) используются +для отправки сообщений, а [remote](../api/remote.md) - для коммуникации в RPC стиле. +В ЧАВО также есть пункт о том, [как разделять информацию между страницами][share-data] + +## Первое приложение на Electron + +Как правило, приложение Electron структурировано следующим образом:: + +```text +your-app/ +├── package.json +├── main.js +└── index.html +``` + +Формат `package.json` точно такой же, как у модулей Node и сприпт, объявленый +как `main`, будет выполняться при запуске вашего приложения, работая в +главном процессе. Например, ваш `package.json` может выглядеть вот так: + +```json +{ + "name" : "your-app", + "version" : "0.1.0", + "main" : "main.js" +} +``` + +__Заметка__: Если поле `main` отсутствует в `package.json`, Electron попробует +загрузить `index.js`. + + +`main.js` должен создавать окно и управлять системными событиями, +типичный пример: + +```javascript +const electron = require('electron') +// Модуль, контролирующий основное: сам Electron. +const app = electron.app +// Модуль, создающий окно приложения. +const BrowserWindow = electron.BrowserWindow + +// Удерживайте глобальное обращение к объекту окна, если вы так не сделаете, то +// окно само закроется после того, как объект будет собран сборщиком мусора. +let mainWindow + +function createWindow () { + // Создаём окно браузера. + mainWindow = new BrowserWindow({width: 800, height: 600}) + + // и загружаем index.html приложения. + mainWindow.loadURL('file://' + __dirname + '/index.html') + + // Открываем DevTools. + mainWindow.webContents.openDevTools() + + // Будет выполнено, когда пользователь закроет окно + mainWindow.on('closed', function () { + //Убрать обращение на объект окна, обычно стоит хранить окна в массиве, + //если ваше приложение поддерживает несколько, сейчас стоит удалить + //соответствующий элемент. + mainWindow = null + }) +} + +//Этот метод будет вызван, когда Electron закончит инициализацию +//и будет готов создавать окна браузера. +//Некоторые API возможно использовать только после того, как +//это произойдёт. +app.on('ready', createWindow) + +// Выйти, если все окна закрыты +app.on('window-all-closed', function () { + //На OS X приложение и его строка меню обычно остаются активными, + //пока пользователь не завершит их с помощью Cmd + Q. + if (process.platform !== 'darwin') { + app.quit() + } +}) + +app.on('activate', function () { + //На OS X приложение обычно пересоздаёт окно, когда + //пользователь кликает на его иконку в доке, если не открыто + //других окон. + if (mainWindow === null) { + createWindow() + } +}) + +//В этот файл вы можете включить остальной код вашего главного процесса. +//Вы также можете разложить его по отдельным файлам и подключить с помощью require. + +``` + +Наконец, `index.html`, страница, которую вы хотите показать: + +```html + + + + + Hello World! + + +

Привет, мир!

+ Мы используем Node , + Chrome , + и Electron . + + +``` + +## Запуск вашего приложения + +Когда вы создали `main.js`, `index.html` и `package.json` вас скорее всего захочется +запустить ваше приложение, чтобы проверить, что оно работает так, как надо. + +### electron-prebuilt + +[`electron-prebuilt`](https://github.com/electron-userland/electron-prebuilt) — `npm` модуль, +который содержит прекомпилированную версию Electron. + +Если вы установили Electron глобально через `npm`, то вам нужно будет всего лишь +запустить сдедующее в папке вашего проекта: + +```bash +electron . +``` + +Если вы установили Electron локально, то выполните это: + +```bash +./node_modules/.bin/electron . +``` + +### Исполняемые файлы Electron, скачанные вручную + +Если вы скачали Electron вручную, то вы можете использовать +исполняемые файлы прямо в папке вашего проекта. + +#### Windows + +```bash +$ .\electron\electron.exe your-app\ +``` + +#### Linux + +```bash +$ ./electron/electron your-app/ +``` + +#### OS X + +```bash +$ ./Electron.app/Contents/MacOS/Electron your-app/ +``` + +`Electron.app` — часть реализного пакета Electron, вы можете скачать его +[тут](https://github.com/electron/electron/releases). + +### Запустить как дистрибутив + +Когда вы закончили написание вашего приложения, вы можете создать +дистрибутив, следуя инструкциям [отсюда](./application-distribution.md) и +затем запустить полученное приложение. + +### Попробуйте этот пример + +Скопируйте и запустите этот обучающий код, ичпользуя репозиторий [`atom/electron-quick-start`](https://github.com/electron/electron-quick-start) + +**Заметка**: Для запуска требуется [Git](https://git-scm.com) и [Node.js](https://nodejs.org/en/download/) (который включает в себя [npm](https://npmjs.org)). + +```bash +# Склонируйте репозиторий +$ git clone https://github.com/electron/electron-quick-start +# Перейдите в папку репозитория +$ cd electron-quick-start +# Установите зависимости и запустите +$ npm install && npm start +``` + +[share-data]: ../faq/electron-faq.md#how-to-share-data-between-web-pages diff --git a/docs-translations/ru-RU/tutorial/supported-platforms.md b/docs-translations/ru-RU/tutorial/supported-platforms.md new file mode 100644 index 000000000000..d927bc0a7b98 --- /dev/null +++ b/docs-translations/ru-RU/tutorial/supported-platforms.md @@ -0,0 +1,15 @@ +Платформы поддерживаемые Electron: + +#OS X +Поддерживает только 64-ых битные OS X. Минимально поддерживаемой версией является OS X 10.9 + +#Windows +Поддерживаются операционные систем Windows 7 и выше, старые операционные системы не поддерживаются (и не работают). +Поддерживаются платформы на x86 и amd64 (64-разрядная) для Windows. Будьте внимательны, что ARM Windows не поддерживается на данный момент. + +#Linux +Поддерживает архитектуры ia32(i686 в) и x64(amd64). +Гарантированно будет работать в дистрибутивах: +- Ubuntu 12.04 и выше +- Fedora 21 +- Debian 8 diff --git a/docs-translations/tr-TR/README.md b/docs-translations/tr-TR/README.md new file mode 100644 index 000000000000..b055e63f5af1 --- /dev/null +++ b/docs-translations/tr-TR/README.md @@ -0,0 +1,86 @@ +Lütfen kullandığınız dokümanın Electron versiyonunuzla aynı olduğundan emin olun. +Versiyon numarası okuduğunuz dokümanın URL'sindekiyle aynı olmalı. Eğer aynı değilse, muhtemelen geliştirme aşamasındaki API değişikliklerini içerebilecek dokümantasyonudur. +Eğer öyleyse, atom.io üzerinden [mevcut sürümler](http://electron.atom.io/docs/)e göz atabilirsiniz ya da eğer GitHub arayüzünü kullanıyorsanız "Switch branches/tags" açılır menüsünden versiyonunuza uygun olanı seçebilirsiniz. + +## SSS(Sıkça Sorulan Sorular) + +Bir problem(issue) bildirmeden önce sıkça sorulan sorulara göz atın: +* [Electron SSS](https://github.com/electron/electron/tree/master/docs/faq/electron-faq.md) + +## Klavuzlar + +* [Desteklenen Platformlar ](https://github.com/electron/electron/tree/master/docs/tutorial/supported-platforms.md) +* [Uygulama Dağıtımı](https://github.com/electron/electron/tree/master/docs/tutorial/application-distribution.md) +* [Mac Uygulama Mağazası Başvuru Klavuzu](https://github.com/electron/electron/tree/master/docs/tutorial/mac-app-store-submission-guide.md) +* [Uygulama Paketleme](https://github.com/electron/electron/tree/master/docs/tutorial/application-packaging.md) +* [Native Node Modüllerini Kullanma](https://github.com/electron/electron/tree/master/docs/tutorial/using-native-node-modules.md) +* [Ana Süreç(Main Process) Hata ayıklama](https://github.com/electron/electron/tree/master/docs/tutorial/debugging-main-process.md) +* [Selenium ve WebDriver kullanımı](https://github.com/electron/electron/tree/master/docs/tutorial/using-selenium-and-webdriver.md) +* [DevTools Eklentisi](https://github.com/electron/electron/tree/master/docs/tutorial/devtools-extension.md) +* [Pepper Flash Kullanımı](https://github.com/electron/electron/tree/master/docs/tutorial/using-pepper-flash-plugin.md) +* [Widevine CDM Kullanımı](https://github.com/electron/electron/tree/master/docs/tutorial/using-widevine-cdm-plugin.md) +* [CI Sistem Testleri (Travis, Jenkins)](https://github.com/electron/electron/tree/master/docs/tutorial/testing-on-headless-ci.md) + +## Eğitimler + +* [Quick Start](https://github.com/electron/electron/tree/master/docs/tutorial/quick-start.md) +* [Desktop Environment Integration](https://github.com/electron/electron/tree/master/docs/tutorial/desktop-environment-integration.md) +* [Online/Offline Event Detection](https://github.com/electron/electron/tree/master/docs/tutorial/online-offline-events.md) + +## API Kaynakları + +* [Synopsis](https://github.com/electron/electron/tree/master/docs/api/synopsis.md) +* [Process Object](https://github.com/electron/electron/tree/master/docs/api/process.md) +* [Desteklenen Chrome Komut Satırı Anahtarları](https://github.com/electron/electron/tree/master/docs/api/chrome-command-line-switches.md) +* [Environment Değişkenleri](https://github.com/electron/electron/tree/master/docs/api/environment-variables.md) + +### Özel DOM Elementleri: + +* [`File` Nesnesi](api/file-object.md) +* [`` Etiketi](https://github.com/electron/electron/tree/master/docs/api/web-view-tag.md) +* [`window.open` Fonksiyonu](https://github.com/electron/electron/tree/master/docs/api/window-open.md) + +### Ana Süreç(Main Process) Modülleri: + +* [app](https://github.com/electron/electron/tree/master/docs/api/app.md) +* [autoUpdater](https://github.com/electron/electron/tree/master/docs/api/auto-updater.md) +* [BrowserWindow](https://github.com/electron/electron/tree/master/docs/api/browser-window.md) +* [contentTracing](https://github.com/electron/electron/tree/master/docs/api/content-tracing.md) +* [dialog](https://github.com/electron/electron/tree/master/docs/api/dialog.md) +* [globalShortcut](https://github.com/electron/electron/tree/master/docs/api/global-shortcut.md) +* [ipcMain](https://github.com/electron/electron/tree/master/docs/api/ipc-main.md) +* [Menu](https://github.com/electron/electron/tree/master/docs/api/menu.md) +* [MenuItem](https://github.com/electron/electron/tree/master/docs/api/menu-item.md) +* [powerMonitor](https://github.com/electron/electron/tree/master/docs/api/power-monitor.md) +* [powerSaveBlocker](https://github.com/electron/electron/tree/master/docs/api/power-save-blocker.md) +* [protocol](https://github.com/electron/electron/tree/master/docs/api/protocol.md) +* [session](https://github.com/electron/electron/tree/master/docs/api/session.md) +* [webContents](https://github.com/electron/electron/tree/master/docs/api/web-contents.md) +* [Tray](https://github.com/electron/electron/tree/master/docs/api/tray.md) + +### Renderer Process Modülelri (Web Page): + +* [desktopCapturer](https://github.com/electron/electron/tree/master/docs/api/desktop-capturer.md) +* [ipcRenderer](https://github.com/electron/electron/tree/master/docs/api/ipc-renderer.md) +* [remote](https://github.com/electron/electron/tree/master/docs/api/remote.md) +* [webFrame](https://github.com/electron/electron/tree/master/docs/api/web-frame.md) + +### Her İki Süreç İçin Geçerli Modüller: + +* [clipboard](https://github.com/electron/electron/tree/master/docs/api/clipboard.md) +* [crashReporter](https://github.com/electron/electron/tree/master/docs/api/crash-reporter.md) +* [nativeImage](https://github.com/electron/electron/tree/master/docs/api/native-image.md) +* [screen](https://github.com/electron/electron/tree/master/docs/api/screen.md) +* [shell](https://github.com/electron/electron/tree/master/docs/api/shell.md) + +## Geliştirme + +* [Kodlama Stili](https://github.com/electron/electron/tree/master/docs/development/coding-style.md) +* [Kaynak Kod Dizin Yapısı](https://github.com/electron/electron/tree/master/docs/development/source-code-directory-structure.md) +* [NW.js(node-webkit adıyla bilinen) İle Arasındaki Teknik Farklılıklar](https://github.com/electron/electron/tree/master/docs/development/atom-shell-vs-node-webkit.md) +* [Build Sisyem Genel Bakış](https://github.com/electron/electron/tree/master/docs/development/build-system-overview.md) +* [(OS X) Build Komutları](https://github.com/electron/electron/tree/master/docs/development/build-instructions-osx.md) +* [(Windows) Build Komutları](https://github.com/electron/electron/tree/master/docs/development/build-instructions-windows.md) +* [(Linux) Build Komutları](https://github.com/electron/electron/tree/master/docs/development/build-instructions-linux.md) +* [(Windows) Hata Ayıklama Komutları](https://github.com/electron/electron/tree/master/docs/development/debug-instructions-windows.md) +* [Simge Sunucusu(Symbol Server) Hata Ayıklama Kurulumu](https://github.com/electron/electron/tree/master/docs/development/setting-up-symbol-server.md) diff --git a/docs-translations/tr-TR/api/accelerator.md b/docs-translations/tr-TR/api/accelerator.md new file mode 100644 index 000000000000..9105e5c6e460 --- /dev/null +++ b/docs-translations/tr-TR/api/accelerator.md @@ -0,0 +1,49 @@ +# Hızlandırıcı + +> Kısayol Tanımlama. + +Hızlandırıcılar `+` karakteriyle birden fazla niteleyici ile kombinlenebilir. + +Örnek: + +* `CommandOrControl+A` +* `CommandOrControl+Shift+Z` + +## Platform bilgileri + +Linux ve Windows'ta `Command` tuşu herhangi bir etki göstermez. Bunun yerine +`CommandOrControl` niteleyicisini kullanın. Bu işlem OS X'te `Command`, +Linux ve Windows'ta `Control` tuşunun işlevini sağlar. `Alt` ise tüm platformlarda mevcuttur. + +`Super` tuşu Windows ve Linux'te `Windows` tuşuna, OS X'te ise `Cmd` tuşuna eşleştirilmiştir. + +## Mevcut düzenleyiciler + +* `Command` (ya da kısa tanım için `Cmd`) +* `Control` (ya da kısa tanım için `Ctrl`) +* `CommandOrControl` (ya da kısa tanım için `CmdOrCtrl`) +* `Alt` +* `Option` +* `AltGr` +* `Shift` +* `Super` + +## Mevcut tuş kodları + +* `0`dan `9`a +* `A`dan `Z`ye +* `F1`dan `F24`e +* Noktalama işaretleri `~`, `!`, `@`, `#`, `$`, vb. +* `Plus` +* `Space` +* `Backspace` +* `Delete` +* `Insert` +* `Return` (ya da `Enter`) +* `Up`, `Down`, `Left` ve `Right` +* `Home` ve `End` +* `PageUp` ve `PageDown` +* `Escape` (ya da kısa tanım için `Esc`) +* `VolumeUp`, `VolumeDown` ve `VolumeMute` +* `MediaNextTrack`, `MediaPreviousTrack`, `MediaStop` ve `MediaPlayPause` +* `PrintScreen` diff --git a/docs-translations/tr-TR/api/file-object.md b/docs-translations/tr-TR/api/file-object.md new file mode 100644 index 000000000000..a99bdb329c5d --- /dev/null +++ b/docs-translations/tr-TR/api/file-object.md @@ -0,0 +1,29 @@ +# `File` nesnesi + +> Dosya ve dosya sistemlerinde HTML5 `File` nesnesini native olarak çalışır. + +DOM Dosya arayüzü HTML5 dosya API'sini kullanarak kullanıcılara doğrudan native dosyalar üzerinde çalışmasına olanak sağlar. Electron'da `File` arayüzü için `path` özelliğini eklemiştir. + +`dragged-onto-the-app`'dan tam dosya yolu alma örneği: + +```html +
+ Dosyalarınızı buraya sürükleyin +
+ + +``` diff --git a/docs-translations/tr-TR/styleguide.md b/docs-translations/tr-TR/styleguide.md new file mode 100644 index 000000000000..5e8f75240721 --- /dev/null +++ b/docs-translations/tr-TR/styleguide.md @@ -0,0 +1,95 @@ +# Electron Dokümantasyonu Stil Rehberi + +Size uygun bölümü bulun: [Electron Dokümantasyonunu okumak](#reading-electron-documentation) +ya da [Electron Dokümantasyonunu yazmak](#writing-electron-documentation). + +## Electron Dokümantasyonunu Yazmak + +Electron Dokümantasyonunu geliştirmek için aşağıdaki yöntemleri takip edin. + +- Her sayfada en fazla bir tane `h1` etiketi olmalıdır. +- Kod bloklarında `cmd` yerine `bash` kullanın.(syntax highlighter için). +- `h1` Başlığı nesne ismiyle eşleşmeli (ör. `browser-window` → + `BrowserWindow`). + - Hyphen separated filenames, however, are fine. +- No headers following headers, add at least a one-sentence description. +- Methods headers are wrapped in `code` ticks. +- Event headers are wrapped in single 'quotation' marks. +- No nesting lists more than 2 levels (unfortunately because of markdown + renderer). +- Add section titles: Events, Class Methods and Instance Methods. +- Use 'will' over 'would' when describing outcomes. +- Events and methods are `h3` headers. +- Optional arguments written as `function (required[, optional])`. +- Optional arguments are denoted when called out in list. +- Line length is 80-column wrapped. +- Platform specific methods are noted in italics following method header. + - ```### `method(foo, bar)` _OS X_``` +- Prefer 'in the ___ process' over 'on' + +### Dokümantasyon Çevirisi + +Electron Dokümantasyonunun çevirileri `docs-translations` klasörü içerisindedir. + +To add another set (or partial set): + +- Create a subdirectory named by language abbreviation. +- Within that subdirectory, duplicate the `docs` directory, keeping the + names of directories and files same. +- Translate the files. +- Update the `README.md` within your language directory to link to the files + you have translated. +- Add a link to your translation directory on the main Electron [README](https://github.com/electron/electron#documentation-translations). + +## Electron Dokümantasyonunu Okumak + +Electron Dokümantasyon sözdizimini(syntax) anlayabileceğiniz bir kaç ipucu: + +### Metodlar + +[Method](https://developer.mozilla.org/en-US/docs/Glossary/Method) dokümantasyonunun bir örneği: + +--- + +`methodName(required[, optional]))` + +* `require` String (**required**) +* `optional` Integer + +--- + +The method name is followed by the arguments it takes. Optional arguments are +notated by brackets surrounding the optional argument as well as the comma +required if this optional argument follows another argument. + +Below the method is more detailed information on each of the arguments. The type +of argument is notated by either the common types: +[`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String), +[`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number), +[`Object`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object), +[`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) +or a custom type like Electron's [`webContent`](https://github.com/electron/electron/tree/master/docs/api/web-content.md). + +### Events + +[event](https://developer.mozilla.org/en-US/docs/Web/API/Event) Dokümantasyonunun bir örneği: + +--- + +Event: 'wake-up' + +Returns: + +* `time` String + +--- + +The event is a string that is used after a `.on` listener method. If it returns +a value it and its type is noted below. If you were to listen and respond to +this event it might look something like this: + +```javascript +Alarm.on('wake-up', function(time) { + console.log(time) +}) +``` diff --git a/docs-translations/uk-UA/styleguide.md b/docs-translations/uk-UA/styleguide.md index 041596700017..2a5907ec4eaf 100644 --- a/docs-translations/uk-UA/styleguide.md +++ b/docs-translations/uk-UA/styleguide.md @@ -39,7 +39,7 @@ or [writing Electron documentation](#writing-electron-documentation). - Translate the files. - Update the `README.md` within your language directory to link to the files you have translated. -- Add a link to your translation directory on the main Electron [README](https://github.com/atom/electron#documentation-translations). +- Add a link to your translation directory on the main Electron [README](https://github.com/electron/electron#documentation-translations). ## Читання документації Electron diff --git a/docs-translations/zh-CN/README.md b/docs-translations/zh-CN/README.md index d7a8442ad4a3..58293e38470d 100644 --- a/docs-translations/zh-CN/README.md +++ b/docs-translations/zh-CN/README.md @@ -73,7 +73,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) * [在调试中使用 Symbol Server](development/setting-up-symbol-server.md) diff --git a/docs-translations/zh-CN/api/app.md b/docs-translations/zh-CN/api/app.md index dd8d6bcfdf65..21c73cf836a9 100644 --- a/docs-translations/zh-CN/api/app.md +++ b/docs-translations/zh-CN/api/app.md @@ -295,6 +295,12 @@ app.on('login', function(event, webContents, request, authInfo, callback) { 由于 npm 的命名规则,通常 `name` 字段是一个短的小写字符串。但是应用名的完整名称通常是首字母大写的,你应该单独使用一个 `productName` 字段,用于表示你的应用程序的完整名称。Electron 会优先使用这个字段作为应用名。 +### `app.setName(name)` + +* `name` String + +重写当前应用的名字 + ### `app.getLocale()` 返回当前应用程序的语言。 diff --git a/docs-translations/zh-CN/api/browser-window.md b/docs-translations/zh-CN/api/browser-window.md new file mode 100644 index 000000000000..0d720c42591e --- /dev/null +++ b/docs-translations/zh-CN/api/browser-window.md @@ -0,0 +1,753 @@ +# BrowserWindow + + `BrowserWindow` 类让你有创建一个浏览器窗口的权力。例如: + +```javascript +// In the main process. +const BrowserWindow = require('electron').BrowserWindow; + +// Or in the renderer process. +const BrowserWindow = require('electron').remote.BrowserWindow; + +var win = new BrowserWindow({ width: 800, height: 600, show: false }); +win.on('closed', function() { + win = null; +}); + +win.loadURL('https://github.com'); +win.show(); +``` + +你也可以不通过chrome创建窗口,使用 +[Frameless Window](frameless-window.md) API. + +## Class: BrowserWindow + +`BrowserWindow` 是一个 +[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter). + +通过 `options` 可以创建一个具有本质属性的 `BrowserWindow` . + +### `new BrowserWindow([options])` + +* `options` Object + * `width` Integer - 窗口宽度,单位像素. 默认是 `800`. + * `height` Integer - 窗口高度,单位像素. 默认是 `600`. + * `x` Integer - 窗口相对于屏幕的左偏移位置.默认居中. + * `y` Integer - 窗口相对于屏幕的顶部偏移位置.默认居中. + * `useContentSize` Boolean - `width` 和 `height` 使用web网页size, 这意味着实际窗口的size应该包括窗口框架的size,稍微会大一点,默认为 `false`. + * `center` Boolean - 窗口屏幕居中. + * `minWidth` Integer - 窗口最小宽度,默认为 `0`. + * `minHeight` Integer - 窗口最小高度,默认为 `0`. + * `maxWidth` Integer - 窗口最大宽度,默认无限制. + * `maxHeight` Integer - 窗口最大高度,默认无限制. + * `resizable` Boolean - 是否可以改变窗口size,默认为 `true`. + * `movable` Boolean - 窗口是否可以拖动. 在 Linux 上无效. 默认为 `true`. + * `minimizable` Boolean - 窗口是否可以最小化. 在 Linux 上无效. 默认为 `true`. + * `maximizable` Boolean - 窗口是否可以最大化. 在 Linux 上无效. 默认为 `true`. + * `closable` Boolean - 窗口是否可以关闭. 在 Linux 上无效. 默认为 `true`. + * `alwaysOnTop` Boolean - 窗口是否总是显示在其他窗口之前. 在 Linux 上无效. 默认为 `false`. + * `fullscreen` Boolean - 窗口是否可以全屏幕. 当明确设置值为When `false` ,全屏化按钮将会隐藏,在 OS X 将禁用. 默认 `false`. + * `fullscreenable` Boolean - 在 OS X 上,全屏化按钮是否可用,默认为 `true`. + * `skipTaskbar` Boolean - 是否在任务栏中显示窗口. 默认是`false`. + * `kiosk` Boolean - kiosk 方式. 默认为 `false`. + * `title` String - 窗口默认title. 默认 `"Electron"`. + * `icon` [NativeImage](native-image.md) - 窗口图标, 如果不设置,窗口将使用可用的默认图标. + * `show` Boolean - 窗口创建的时候是否显示. 默认为 `true`. + * `frame` Boolean - 指定 `false` 来创建一个 + [Frameless Window](frameless-window.md). 默认为 `true`. + * `acceptFirstMouse` Boolean - 是否允许单击web view来激活窗口 . 默认为 `false`. + * `disableAutoHideCursor` Boolean - 当 typing 时是否隐藏鼠标.默认 `false`. + * `autoHideMenuBar` Boolean - 除非点击 `Alt`,否则隐藏菜单栏.默认为 `false`. + * `enableLargerThanScreen` Boolean - 是否允许允许改变窗口大小大于屏幕. 默认是 `false`. + * `backgroundColor` String -窗口的 background color 值为十六进制,如 `#66CD00` 或 `#FFF` 或 `#80FFFFFF` (支持透明度). 默认为在 Linux 和 Windows 上为 + `#000` (黑色) , Mac上为 `#FFF`(或透明). + * `hasShadow` Boolean - 窗口是否有阴影. 只在 OS X 上有效. 默认为 `true`. + * `darkTheme` Boolean - 为窗口使用 dark 主题, 只在一些拥有 GTK+3 桌面环境上有效. 默认为 `false`. + * `transparent` Boolean - 窗口 [透明](frameless-window.md). + 默认为 `false`. + * `type` String - 窗口type, 默认普通窗口. 下面查看更多. + * `titleBarStyle` String - 窗口标题栏样式. 下面查看更多. + * `webPreferences` Object - 设置界面特性. 下面查看更多. + +`type` 的值和效果不同平台展示效果不同,具体: + +* Linux, 可用值为 `desktop`, `dock`, `toolbar`, `splash`, + `notification`. +* OS X, 可用值为 `desktop`, `textured`. + * `textured` type 添加金属梯度效果 + (`NSTexturedBackgroundWindowMask`). + * `desktop` 设置窗口在桌面背景窗口水平 + (`kCGDesktopWindowLevel - 1`). 注意桌面窗口不可聚焦, 不可不支持键盘和鼠标事件, 但是可以使用 `globalShortcut` 来解决输入问题. + +`titleBarStyle` 只在 OS X 10.10 Yosemite 或更新版本上支持. +可用值: + +* `default` 以及无值, 显示在 Mac 标题栏上为不透明的标准灰色. +* `hidden` 隐藏标题栏,内容充满整个窗口, 然后它依然在左上角,仍然受标准窗口控制. +* `hidden-inset`主体隐藏,显示小的控制按钮在窗口边缘. + +`webPreferences` 参数是个对象,它的属性: + +* `nodeIntegration` Boolean - 是否完整支持node. 默认为 `true`. +* `preload` String - 界面的其它脚本运行之前预先加载一个指定脚本. 这个脚本将一直可以使用 node APIs 无论 node integration 是否开启. 脚本路径为绝对路径. + 当 node integration 关闭, 预加载的脚本将从全局范围重新引入node的全局引用标志. 查看例子 + [here](process.md#event-loaded). +* `session` [Session](session.md#class-session) - 设置界面session. 而不是直接忽略session对象 , 也可用 `partition` 来代替, 它接受一个 partition 字符串. 当同时使用 `session` 和 `partition`, `session` 优先级更高. + 默认使用默认 session. +* `partition` String - 通过session的partition字符串来设置界面session. 如果 `partition` 以 `persist:` 开头, 这个界面将会为所有界面使用相同的 `partition`. 如果没有 `persist:` 前缀, 界面使用历史session. 通过分享同一个 `partition`, 所有界面使用相同的session. 默认使用默认 session. +* `zoomFactor` Number - 界面默认缩放值, `3.0` 表示 + `300%`. 默认 `1.0`. +* `javascript` Boolean - 开启javascript支持. 默认为`true`. +* `webSecurity` Boolean - 当设置为 `false`, 它将禁用同源策略 (通常用来测试网站), 并且如果有2个非用户设置的参数,就设置 + `allowDisplayingInsecureContent` 和 `allowRunningInsecureContent` 的值为 + `true`. 默认为 `true`. +* `allowDisplayingInsecureContent` Boolean -允许一个使用 https的界面来展示由 http URLs 传过来的资源. 默认`false`. +* `allowRunningInsecureContent` Boolean - Boolean -允许一个使用 https的界面来渲染由 http URLs 提交的html,css,javascript. 默认为 `false`. +* `images` Boolean - 开启图片使用支持. 默认 `true`. +* `textAreasAreResizable` Boolean - textArea 可以编辑. 默认为 `true`. +* `webgl` Boolean - 开启 WebGL 支持. 默认为 `true`. +* `webaudio` Boolean - 开启 WebAudio 支持. 默认为 `true`. +* `plugins` Boolean - 是否开启插件支持. 默认为 `false`. +* `experimentalFeatures` Boolean - 开启 Chromium 的 可测试 特性. + 默认为 `false`. +* `experimentalCanvasFeatures` Boolean - 开启 Chromium 的 canvas 可测试特性. 默认为 `false`. +* `directWrite` Boolean - 开启窗口的 DirectWrite font 渲染系统. 默认为 `true`. +* `blinkFeatures` String - 以 `,` 分隔的特性列表, 如 + `CSSVariables,KeyboardEventKey`. 被支持的所有特性可在 [setFeatureEnabledFromString][blink-feature-string] + 中找到. +* `defaultFontFamily` Object - 设置 font-family 默认字体. + * `standard` String - 默认为 `Times New Roman`. + * `serif` String - 默认为 `Times New Roman`. + * `sansSerif` String - 默认为 `Arial`. + * `monospace` String - 默认为 `Courier New`. +* `defaultFontSize` Integer - 默认为 `16`. +* `defaultMonospaceFontSize` Integer - 默认为 `13`. +* `minimumFontSize` Integer - 默认为 `0`. +* `defaultEncoding` String - 默认为 `ISO-8859-1`. + +## 事件 + + `BrowserWindow` 对象可触发下列事件: + +**注意:** 一些事件只能在特定os环境中触发,已经尽可能地标出. + +### Event: 'page-title-updated' + +返回: + +* `event` Event + +当文档改变标题时触发,使用 `event.preventDefault()` 可以阻止原窗口的标题改变. + +### Event: 'close' + +返回: + +* `event` Event + +在窗口要关闭的时候触发. 它在DOM的 `beforeunload` and `unload` 事件之前触发.使用 `event.preventDefault()` 可以取消这个操作 + +通常你想通过 `beforeunload` 处理器来决定是否关闭窗口,但是它也会在窗口重载的时候被触发. 在 Electron 中,返回一个空的字符串或 `false` 可以取消关闭.例如: + +```javascript +window.onbeforeunload = function(e) { + console.log('I do not want to be closed'); + + // Unlike usual browsers, in which a string should be returned and the user is + // prompted to confirm the page unload, Electron gives developers more options. + // Returning empty string or false would prevent the unloading now. + // You can also use the dialog API to let the user confirm closing the application. + e.returnValue = false; +}; +``` + +### Event: 'closed' + +当窗口已经关闭的时候触发.当你接收到这个事件的时候,你应当删除对已经关闭的窗口的引用对象和避免再次使用它. + +### Event: 'unresponsive' + +在界面卡死的时候触发事件. + +### Event: 'responsive' + +在界面恢复卡死的时候触发. + +### Event: 'blur' + +在窗口失去焦点的时候触发. + +### Event: 'focus' + +在窗口获得焦点的时候触发. + +### Event: 'maximize' + +在窗口最大化的时候触发. + +### Event: 'unmaximize' + +在窗口退出最大化的时候触发. + +### Event: 'minimize' + +在窗口最小化的时候触发. + +### Event: 'restore' + +在窗口从最小化恢复的时候触发. + +### Event: 'resize' + +在窗口size改变的时候触发. + +### Event: 'move' + +在窗口移动的时候触发. + +注意:在 OS X 中别名为 `moved`. + +### Event: 'moved' _OS X_ + +在窗口移动的时候触发. + +### Event: 'enter-full-screen' + +在的窗口进入全屏状态时候触发. + +### Event: 'leave-full-screen' + +在的窗口退出全屏状态时候触发. + +### Event: 'enter-html-full-screen' + +在的窗口通过 html api 进入全屏状态时候触发. + +### Event: 'leave-html-full-screen' + +在的窗口通过 html api 退出全屏状态时候触发. + +### Event: 'app-command' _Windows_ + +在请求一个[App Command](https://msdn.microsoft.com/en-us/library/windows/desktop/ms646275(v=vs.85).aspx)的时候触发. +典型的是键盘媒体或浏览器命令, Windows上的 "Back" 按钮用作鼠标也会触发. + +```js +someWindow.on('app-command', function(e, cmd) { + // Navigate the window back when the user hits their mouse back button + if (cmd === 'browser-backward' && someWindow.webContents.canGoBack()) { + someWindow.webContents.goBack(); + } +}); +``` + +### Event: 'scroll-touch-begin' _OS X_ + +在滚动条事件开始的时候触发. + +### Event: 'scroll-touch-end' _OS X_ + +在滚动条事件结束的时候触发. + +## 方法 + +`BrowserWindow` 对象有如下方法: + +### `BrowserWindow.getAllWindows()` + +返回一个所有已经打开了窗口的对象数组. + +### `BrowserWindow.getFocusedWindow()` + +返回应用当前获得焦点窗口,如果没有就返回 `null`. + +### `BrowserWindow.fromWebContents(webContents)` + +* `webContents` [WebContents](web-contents.md) + +根据 `webContents` 查找窗口. + +### `BrowserWindow.fromId(id)` + +* `id` Integer + +根据 id 查找窗口. + +### `BrowserWindow.addDevToolsExtension(path)` + +* `path` String + +添加位于 `path` 的开发者工具栏扩展,并且返回扩展项的名字. + +这个扩展会被添加到历史,所以只需要使用这个API一次,这个api不可用作编程使用. + +### `BrowserWindow.removeDevToolsExtension(name)` + +* `name` String + +删除开发者工具栏名为 `name` 的扩展. + +## 实例属性 + +使用 `new BrowserWindow` 创建的实例对象,有如下属性: + +```javascript +// In this example `win` is our instance +var win = new BrowserWindow({ width: 800, height: 600 }); +``` + +### `win.webContents` + +这个窗口的 `WebContents` 对象,所有与界面相关的事件和方法都通过它完成的. + +查看 [`webContents` documentation](web-contents.md) 的方法和事件. + +### `win.id` + +窗口的唯一id. + +## 实例方法 + +使用 `new BrowserWindow` 创建的实例对象,有如下方法: + +**注意:** 一些方法只能在特定os环境中调用,已经尽可能地标出. + +### `win.destroy()` + +强制关闭窗口, `unload` and `beforeunload` 不会触发,并且 `close` 也不会触发, 但是它保证了 `closed` 触发. + +### `win.close()` + +尝试关闭窗口,这与用户点击关闭按钮的效果一样. 虽然网页可能会取消关闭,查看 [close event](#event-close). + +### `win.focus()` + +窗口获得焦点. + +### `win.isFocused()` + +返回 boolean, 窗口是否获得焦点. + +### `win.show()` + +展示并且使窗口获得焦点. + +### `win.showInactive()` + +展示窗口但是不获得焦点. + +### `win.hide()` + +隐藏窗口. + +### `win.isVisible()` + +返回 boolean, 窗口是否可见. + +### `win.maximize()` + +窗口最大化. + +### `win.unmaximize()` + +取消窗口最大化. + +### `win.isMaximized()` + +返回 boolean, 窗口是否最大化. + +### `win.minimize()` + +窗口最小化. 在一些os中,它将在dock中显示. + +### `win.restore()` + +将最小化的窗口恢复为之前的状态. + +### `win.isMinimized()` + +返回 boolean, 窗口是否最小化. + +### `win.setFullScreen(flag)` + +* `flag` Boolean + +设置是否全屏. + +### `win.isFullScreen()` + +返回 boolean, 窗口是否全屏化. + +### `win.setAspectRatio(aspectRatio[, extraSize])` _OS X_ + +* `aspectRatio` 维持部分视图内容窗口的高宽比值. +* `extraSize` Object (可选) - 维持高宽比值时不包含的额外size. + * `width` Integer + * `height` Integer + +由一个窗口来维持高宽比值. `extraSize` 允许开发者使用它,它的单位为像素,不包含在 `aspectRatio` 中.这个 API 可用来区分窗口的size和内容的size . + +想象一个普通可控的HD video 播放器窗口. 假如左边缘有15控制像素,右边缘有25控制像素,在播放器下面有50控制像素.为了在播放器内保持一个 16:9 的高宽比例,我们可以调用这个api传入参数16/9 and +[ 40, 50 ].第二个参数不管网页中的额外的宽度和高度在什么位置,只要它们存在就行.只需要把网页中的所有额外的高度和宽度加起来就行. + +### `win.setBounds(options[, animate])` + +* `options` Object + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer +* `animate` Boolean (可选) _OS X_ + +重新设置窗口的宽高值,并且移动到指定的 `x`, `y` 位置. + +### `win.getBounds()` + +返回一个对象,它包含了窗口的宽,高,x坐标,y坐标. + +### `win.setSize(width, height[, animate])` + +* `width` Integer +* `height` Integer +* `animate` Boolean (可选) _OS X_ + +重新设置窗口的宽高值. + +### `win.getSize()` + +返回一个数组,它包含了窗口的宽,高. + +### `win.setContentSize(width, height[, animate])` + +* `width` Integer +* `height` Integer +* `animate` Boolean (可选) _OS X_ + +重新设置窗口客户端的宽高值(例如网页界面). + +### `win.getContentSize()` + +返回一个数组,它包含了窗口客户端的宽,高. + +### `win.setMinimumSize(width, height)` + +* `width` Integer +* `height` Integer + +设置窗口最小化的宽高值. + +### `win.getMinimumSize()` + +返回一个数组,它包含了窗口最小化的宽,高. + +### `win.setMaximumSize(width, height)` + +* `width` Integer +* `height` Integer + +设置窗口最大化的宽高值. + +### `win.getMaximumSize()` + +返回一个数组,它包含了窗口最大化的宽,高. + +### `win.setResizable(resizable)` + +* `resizable` Boolean + +设置窗口是否可以被用户改变size. + +### `win.isResizable()` + +返回 boolean,窗口是否可以被用户改变size. + +### `win.setMovable(movable)` _OS X_ _Windows_ + +* `movable` Boolean + +设置窗口是否可以被用户拖动. Linux 无效. + +### `win.isMovable()` _OS X_ _Windows_ + +返回 boolean,窗口是否可以被用户拖动. Linux 总是返回 `true`. + +### `win.setMinimizable(minimizable)` _OS X_ _Windows_ + +* `minimizable` Boolean + +设置窗口是否可以最小化. Linux 无效. + +### `win.isMinimizable()` _OS X_ _Windows_ + +返回 boolean,窗口是否可以最小化. Linux 总是返回 `true`. + +### `win.setMaximizable(maximizable)` _OS X_ _Windows_ + +* `maximizable` Boolean + +设置窗口是否可以最大化. Linux 无效. + +### `win.isMaximizable()` _OS X_ _Windows_ + +返回 boolean,窗口是否可以最大化. Linux 总是返回 `true`. + +### `win.setFullScreenable(fullscreenable)` + +* `fullscreenable` Boolean + +设置点击最大化按钮是否可以全屏或最大化窗口. + +### `win.isFullScreenable()` + +返回 boolean,点击最大化按钮是否可以全屏或最大化窗口. + +### `win.setClosable(closable)` _OS X_ _Windows_ + +* `closable` Boolean + +设置窗口是否可以人为关闭. Linux 无效. + +### `win.isClosable()` _OS X_ _Windows_ + +返回 boolean,窗口是否可以人为关闭. Linux 总是返回 `true`. + +### `win.setAlwaysOnTop(flag)` + +* `flag` Boolean + +是否设置这个窗口始终在其他窗口之上.设置之后,这个窗口仍然是一个普通的窗口,不是一个不可以获得焦点的工具箱窗口. + +### `win.isAlwaysOnTop()` + +返回 boolean,当前窗口是否始终在其它窗口之前. + +### `win.center()` + +窗口居中. + +### `win.setPosition(x, y[, animate])` + +* `x` Integer +* `y` Integer +* `animate` Boolean (可选) _OS X_ + +移动窗口到对应的 `x` and `y` 坐标. + +### `win.getPosition()` + +返回一个包含当前窗口位置的数组. + +### `win.setTitle(title)` + +* `title` String + +改变原窗口的title. + +### `win.getTitle()` + +返回原窗口的title. + +**注意:** 界面title可能和窗口title不相同. + +### `win.flashFrame(flag)` + +* `flag` Boolean + +开始或停止显示窗口来获得用户的关注. + +### `win.setSkipTaskbar(skip)` + +* `skip` Boolean + +让窗口不在任务栏中显示. + +### `win.setKiosk(flag)` + +* `flag` Boolean + +进入或离开 kiosk 模式. + +### `win.isKiosk()` + +返回 boolean,是否进入或离开 kiosk 模式. + +### `win.getNativeWindowHandle()` + +以 `Buffer` 形式返回这个具体平台的窗口的句柄. + +windows上句柄类型为 `HWND` ,OS X `NSView*` , Linux `Window`. + +### `win.hookWindowMessage(message, callback)` _Windows_ + +* `message` Integer +* `callback` Function + +拦截windows 消息,在 WndProc 接收到消息时触发 `callback`函数. + +### `win.isWindowMessageHooked(message)` _Windows_ + +* `message` Integer + +返回 `true` or `false` 来代表是否拦截到消息. + +### `win.unhookWindowMessage(message)` _Windows_ + +* `message` Integer + +不拦截窗口消息. + +### `win.unhookAllWindowMessages()` _Windows_ + +窗口消息全部不拦截. + +### `win.setRepresentedFilename(filename)` _OS X_ + +* `filename` String + +设置窗口当前文件路径,并且将这个文件的图标放在窗口标题栏上. + +### `win.getRepresentedFilename()` _OS X_ + +获取窗口当前文件路径. + +### `win.setDocumentEdited(edited)` _OS X_ + +* `edited` Boolean + +明确指出窗口文档是否可以编辑,如果可以编辑则将标题栏的图标变成灰色. + +### `win.isDocumentEdited()` _OS X_ + +返回 boolean,当前窗口文档是否可编辑. + +### `win.focusOnWebView()` + +### `win.blurWebView()` + +### `win.capturePage([rect, ]callback)` + +* `rect` Object (可选) - 捕获Page位置 + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer +* `callback` Function + +捕获 `rect` 中的page 的快照.完成后将调用回调函数 `callback` 并返回 `image` . `image` 是存储了快照信息的[NativeImage](native-image.md)实例.如果不设置 `rect` 则将捕获所有可见page. + +### `win.loadURL(url[, options])` + +类似 `webContents.loadURL(url[, options])`. + +### `win.reload()` + +类似 `webContents.reload`. + +### `win.setMenu(menu)` _Linux_ _Windows_ + +* `menu` Menu + +设置菜单栏的 `menu` ,设置它为 `null` 则表示不设置菜单栏. + +### `win.setProgressBar(progress)` + +* `progress` Double + +在进度条中设置进度值,有效范围 [0, 1.0]. + +当进度小于0时则不显示进度; +当进度大于0时显示结果不确定. + +在libux上,只支持Unity桌面环境,需要指明 `*.desktop` 文件并且在 `package.json` 中添加文件名字.默认它为 `app.getName().desktop`. + +### `win.setOverlayIcon(overlay, description)` _Windows 7+_ + +* `overlay` [NativeImage](native-image.md) - 在底部任务栏右边显示图标. +* `description` String - 描述. + +向当前任务栏添加一个 16 x 16 像素的图标,通常用来覆盖一些应用的状态,或者直接来提示用户. + +### `win.setHasShadow(hasShadow)` _OS X_ + +* `hasShadow` (Boolean) + +设置窗口是否应该有阴影.在Windows和Linux系统无效. + +### `win.hasShadow()` _OS X_ + +返回 boolean,设置窗口是否有阴影.在Windows和Linux系统始终返回 +`true`. + +### `win.setThumbarButtons(buttons)` _Windows 7+_ + +* `buttons` Array + +在窗口的任务栏button布局出为缩略图添加一个有特殊button的缩略图工具栏. 返回一个 `Boolean` 对象来指示是否成功添加这个缩略图工具栏. + +因为空间有限,缩略图工具栏上的 button 数量不应该超过7个.一旦设置了,由于平台限制,就不能移动它了.但是你可使用一个空数组来调用api来清除 buttons . + +所有 `buttons` 是一个 `Button` 对象数组: + +* `Button` Object + * `icon` [NativeImage](native-image.md) - 在工具栏上显示的图标. + * `click` Function + * `tooltip` String (可选) - tooltip 文字. + * `flags` Array (可选) - 控制button的状态和行为. 默认它是 `['enabled']`. + +`flags` 是一个数组,它包含下面这些 `String`s: + +* `enabled` - button 为激活状态并且开放给用户. +* `disabled` -button 不可用. 目前它有一个可见的状态来表示它不会响应你的行为. +* `dismissonclick` - 点击button,这个缩略窗口直接关闭. +* `nobackground` - 不绘制边框,仅仅使用图像. +* `hidden` - button 对用户不可见. +* `noninteractive` - button 可用但是不可响应; 也不显示按下的状态. 它的值意味着这是一个在通知单使用 button 的实例. + +### `win.showDefinitionForSelection()` _OS X_ + +在界面查找选中文字时显示弹出字典. + +### `win.setAutoHideMenuBar(hide)` + +* `hide` Boolean + +设置窗口的菜单栏是否可以自动隐藏. 一旦设置了,只有当用户按下 `Alt` 键时则显示. + +如果菜单栏已经可见,调用 `setAutoHideMenuBar(true)` 则不会立刻隐藏. + +### `win.isMenuBarAutoHide()` + +返回 boolean,窗口的菜单栏是否可以自动隐藏. + +### `win.setMenuBarVisibility(visible)` + +* `visible` Boolean + +设置菜单栏是否可见.如果菜单栏自动隐藏,用户仍然可以按下 `Alt` 键来显示. + +### `win.isMenuBarVisible()` + +返回 boolean,菜单栏是否可见. + +### `win.setVisibleOnAllWorkspaces(visible)` + +* `visible` Boolean + +设置窗口是否在所有地方都可见. + +**注意:** 这个api 在windows无效. + +### `win.isVisibleOnAllWorkspaces()` + +返回 boolean,窗口是否在所有地方都可见. + +**注意:** 在 windows上始终返回 false. + +### `win.setIgnoreMouseEvents(ignore)` _OS X_ + +* `ignore` Boolean + +忽略窗口的所有鼠标事件. + +[blink-feature-string]: https://code.google.com/p/chromium/codesearch#chromium/src/out/Debug/gen/blink/platform/RuntimeEnabledFeatures.cpp&sq=package:chromium&type=cs&l=527 diff --git a/docs-translations/zh-CN/api/chrome-command-line-switches.md b/docs-translations/zh-CN/api/chrome-command-line-switches.md new file mode 100644 index 000000000000..78b84b77276c --- /dev/null +++ b/docs-translations/zh-CN/api/chrome-command-line-switches.md @@ -0,0 +1,140 @@ +# 支持的 Chrome 命令行开关 + +这页列出了Chrome浏览器和Electron支持的命令行开关. 你也可以在[app][app]模块的[ready][ready]事件发出之前使用[app.commandLine.appendSwitch][append-switch] 来添加它们到你应用的main脚本里面: + +```javascript +const app = require('electron').app; +app.commandLine.appendSwitch('remote-debugging-port', '8315'); +app.commandLine.appendSwitch('host-rules', 'MAP * 127.0.0.1'); + +app.on('ready', function() { + // Your code here +}); +``` + +## --client-certificate=`path` + +设置客户端的证书文件 `path` . + +## --ignore-connections-limit=`domains` + +忽略用 `,` 分隔的 `domains` 列表的连接限制. + +## --disable-http-cache + +禁止请求 HTTP 时使用磁盘缓存. + +## --remote-debugging-port=`port` + +在指定的 `端口` 通过 HTTP 开启远程调试. + +## --js-flags=`flags` + +指定引擎过渡到 JS 引擎. + +在启动Electron时,如果你想在主进程中激活 `flags` ,它将被转换. + +```bash +$ electron --js-flags="--harmony_proxies --harmony_collections" your-app +``` + +## --proxy-server=`address:port` + +使用一个特定的代理服务器,它将比系统设置的优先级更高.这个开关只有在使用 HTTP 协议时有效,它包含 HTTPS 和 WebSocket 请求. 值得注意的是,不是所有的代理服务器都支持 HTTPS 和 WebSocket 请求. + +## --proxy-bypass-list=`hosts` + +让 Electron 使用(原文:bypass) 提供的以 semi-colon 分隔的hosts列表的代理服务器.这个开关只有在使用 `--proxy-server` 时有效. + +例如: + +```javascript +app.commandLine.appendSwitch('proxy-bypass-list', ';*.google.com;*foo.com;1.2.3.4:5678') +``` + + +将会为所有的hosts使用代理服务器,除了本地地址 (`localhost`, +`127.0.0.1` etc.), `google.com` 子域, 以 `foo.com` 结尾的hosts,和所有类似 `1.2.3.4:5678`的. + +## --proxy-pac-url=`url` + +在指定的 `url` 上使用 PAC 脚本. + +## --no-proxy-server + +不使用代理服务并且总是使用直接连接.忽略所有的合理代理标志. + +## --host-rules=`rules` + +一个逗号分隔的 `rule` 列表来控制主机名如何映射. + +例如: + +* `MAP * 127.0.0.1` 强制所有主机名映射到 127.0.0.1 +* `MAP *.google.com proxy` 强制所有 google.com 子域 使用 "proxy". +* `MAP test.com [::1]:77` 强制 "test.com" 使用 IPv6 回环地址. 也强制使用端口 77. +* `MAP * baz, EXCLUDE www.google.com` 重新全部映射到 "baz", 除了 + "www.google.com". + +这些映射适用于终端网络请求 +(TCP 连接 +和 主机解析 以直接连接的方式, 和 `CONNECT` 以代理连接, 还有 终端 host 使用 `SOCKS` 代理连接). + +## --host-resolver-rules=`rules` + +类似 `--host-rules` ,但是 `rules` 只适合主机解析. + +## --ignore-certificate-errors + +忽略与证书相关的错误. + +## --ppapi-flash-path=`path` + +设置Pepper Flash插件的路径 `path` . + +## --ppapi-flash-version=`version` + +设置Pepper Flash插件版本号. + +## --log-net-log=`path` + +使网络日志事件能够被读写到 `path`. + +## --ssl-version-fallback-min=`version` + +设置最简化的 SSL/TLS 版本号 ("tls1", "tls1.1" or "tls1.2"),TLS 可接受回退. + +## --cipher-suite-blacklist=`cipher_suites` + +指定逗号分隔的 SSL 密码套件 列表实效. + +## --disable-renderer-backgrounding + +防止 Chromium 降低隐藏的渲染进程优先级. + +这个标志对所有渲染进程全局有效,如果你只想在一个窗口中禁止使用,你可以采用 hack 方法[playing silent audio][play-silent-audio]. + +## --enable-logging + +打印 Chromium 信息输出到控制台. + +如果在用户应用加载完成之前解析`app.commandLine.appendSwitch` ,这个开关将实效,但是你可以设置 `ELECTRON_ENABLE_LOGGING` 环境变量来达到相同的效果. + +## --v=`log_level` + +设置默认最大活跃 V-logging 标准; 默认为 0.通常 V-logging 标准值为肯定值. + +这个开关只有在 `--enable-logging` 开启时有效. + +## --vmodule=`pattern` + +赋予每个模块最大的 V-logging levels 来覆盖 `--v` 给的值.E.g. `my_module=2,foo*=3` 会改变所有源文件 `my_module.*` and `foo*.*` 的代码中的 logging level . + +任何包含向前的(forward slash)或者向后的(backward slash)模式将被测试用于阻止整个路径名,并且不仅是E.g模块.`*/foo/bar/*=2` 将会改变所有在 `foo/bar` 下的源文件代码中的 logging level . + +这个开关只有在 `--enable-logging` 开启时有效. + +[app]: app.md +[append-switch]: app.md#appcommandlineappendswitchswitch-value +[ready]: app.md#event-ready +[play-silent-audio]: https://github.com/atom/atom/pull/9485/files diff --git a/docs-translations/zh-CN/api/clipboard.md b/docs-translations/zh-CN/api/clipboard.md new file mode 100644 index 000000000000..2678bc5a34b9 --- /dev/null +++ b/docs-translations/zh-CN/api/clipboard.md @@ -0,0 +1,117 @@ +# clipboard + +`clipboard` 模块提供方法来供复制和粘贴操作 . +下面例子展示了如何将一个字符串写道 clipboard 上: + +```javascript +const clipboard = require('electron').clipboard; +clipboard.writeText('Example String'); +``` + +在 X Window 系统上, 有一个可选的 clipboard. 你可以为每个方法使用 `selection` 来控制它: + +```javascript +clipboard.writeText('Example String', 'selection'); +console.log(clipboard.readText('selection')); +``` + +## 方法 + +`clipboard` 模块有以下方法: + +**注意:** 测试 APIs 已经标明,并且在将来会被删除 . + +### `clipboard.readText([type])` + +* `type` String (可选) + +以纯文本形式从 clipboard 返回内容 . + +### `clipboard.writeText(text[, type])` + +* `text` String +* `type` String (可选) + +以纯文本形式向 clipboard 添加内容 . + +### `clipboard.readHTML([type])` + +* `type` String (可选) + +返回 clipboard 中的标记内容. + +### `clipboard.writeHTML(markup[, type])` + +* `markup` String +* `type` String (可选) + +向 clipboard 添加 `markup` 内容 . + +### `clipboard.readImage([type])` + +* `type` String (可选) + +从 clipboard 中返回 [NativeImage](native-image.md) 内容. + +### `clipboard.writeImage(image[, type])` + +* `image` [NativeImage](native-image.md) +* `type` String (可选) + +向 clipboard 中写入 `image` . + +### `clipboard.readRTF([type])` + +* `type` String (可选) + +从 clipboard 中返回 RTF 内容. + +### `clipboard.writeRTF(text[, type])` + +* `text` String +* `type` String (可选) + +向 clipboard 中写入 RTF 格式的 `text` . + +### `clipboard.clear([type])` + +* `type` String (可选) + +清空 clipboard 内容. + +### `clipboard.availableFormats([type])` + +* `type` String (可选) + +返回 clipboard 支持的格式数组 . + +### `clipboard.has(data[, type])` _Experimental_ + +* `data` String +* `type` String (可选) + +返回 clipboard 是否支持指定 `data` 的格式. + +```javascript +console.log(clipboard.has('

selection

')); +``` + +### `clipboard.read(data[, type])` _Experimental_ + +* `data` String +* `type` String (可选) + +读取 clipboard 的 `data`. + +### `clipboard.write(data[, type])` + +* `data` Object + * `text` String + * `html` String + * `image` [NativeImage](native-image.md) +* `type` String (可选) + +```javascript +clipboard.write({text: 'test', html: "test"}); +``` +向 clipboard 写入 `data` . \ No newline at end of file diff --git a/docs-translations/zh-CN/api/content-tracing.md b/docs-translations/zh-CN/api/content-tracing.md new file mode 100644 index 000000000000..fd1edc9bc2f0 --- /dev/null +++ b/docs-translations/zh-CN/api/content-tracing.md @@ -0,0 +1,129 @@ +# contentTracing + +`content-tracing` 模块是用来收集由底层的Chromium content 模块 产生的搜索数据. 这个模块不具备web接口,所有需要我们在chrome浏览器中添加 `chrome://tracing/` 来加载生成文件从而查看结果. + +```javascript +const contentTracing = require('electron').contentTracing; + +const options = { + categoryFilter: '*', + traceOptions: 'record-until-full,enable-sampling' +} + +contentTracing.startRecording(options, function() { + console.log('Tracing started'); + + setTimeout(function() { + contentTracing.stopRecording('', function(path) { + console.log('Tracing data recorded to ' + path); + }); + }, 5000); +}); +``` + +## 方法 + + `content-tracing` 模块的方法如下: + +### `contentTracing.getCategories(callback)` + +* `callback` Function + +获得一组分类组. 分类组可以更改为新的代码路径。 + +一旦所有的子进程都接受到了`getCategories`方法请求, 分类组将调用 `callback`. + +### `contentTracing.startRecording(options, callback)` + +* `options` Object + * `categoryFilter` String + * `traceOptions` String +* `callback` Function + +开始向所有进程进行记录.(recording) + +一旦收到可以开始记录的请求,记录将会立马启动并且在子进程是异步记录听的. 当所有的子进程都收到 `startRecording` 请求的时候,`callback` 将会被调用. + +`categoryFilter`是一个过滤器,它用来控制那些分类组应该被用来查找.过滤器应当有一个可选的 `-` 前缀来排除匹配的分类组.不允许同一个列表既是包含又是排斥. + +例子: + +* `test_MyTest*`, +* `test_MyTest*,test_OtherStuff`, +* `"-excluded_category1,-excluded_category2` + +`traceOptions` 控制着哪种查找应该被启动,这是一个用逗号分隔的列表.可用参数如下: + +* `record-until-full` +* `record-continuously` +* `trace-to-console` +* `enable-sampling` +* `enable-systrace` + +前3个参数是来查找记录模块,并且以后都互斥.如果在`traceOptions` 中超过一个跟踪 +记录模式,那最后一个的优先级最高.如果没有指明跟踪 +记录模式,那么它默认为 `record-until-full`. + +在 `traceOptions` 中的参数被解析应用之前,查找参数初始化默认为 (`record_mode` 设置为 +`record-until-full`, `enable_sampling` 和 `enable_systrace` 设置为 `false`). + +### `contentTracing.stopRecording(resultFilePath, callback)` + +* `resultFilePath` String +* `callback` Function + +停止对所有子进程的记录. + +子进程通常缓存查找数据,并且仅仅将数据截取和发送给主进程.这有利于在通过 IPC 发送查找数据之前减小查找时的运行开销,这样做很有价值.因此,发送查找数据,我们应当异步通知所有子进程来截取任何待查找的数据. + +一旦所有子进程接收到了 `stopRecording` 请求,将调用 `callback` ,并且返回一个包含查找数据的文件. + +如果 `resultFilePath` 不为空,那么将把查找数据写入其中,否则写入一个临时文件.实际文件路径如果不为空,则将调用 `callback` . + +### `contentTracing.startMonitoring(options, callback)` + +* `options` Object + * `categoryFilter` String + * `traceOptions` String +* `callback` Function + +开始向所有进程进行监听.(monitoring) + +一旦收到可以开始监听的请求,记录将会立马启动并且在子进程是异步记监听的. 当所有的子进程都收到 `startMonitoring` 请求的时候,`callback` 将会被调用. + +### `contentTracing.stopMonitoring(callback)` + +* `callback` Function + +停止对所有子进程的监听. + +一旦所有子进程接收到了 `stopMonitoring` 请求,将调用 `callback` . + +### `contentTracing.captureMonitoringSnapshot(resultFilePath, callback)` + +* `resultFilePath` String +* `callback` Function + +获取当前监听的查找数据. + +子进程通常缓存查找数据,并且仅仅将数据截取和发送给主进程.因为如果直接通过 IPC 来发送查找数据的代价昂贵,我们宁愿避免不必要的查找运行开销.因此,为了停止查找,我们应当异步通知所有子进程来截取任何待查找的数据. + +一旦所有子进程接收到了 `captureMonitoringSnapshot` 请求,将调用 `callback` ,并且返回一个包含查找数据的文件. + +### `contentTracing.getTraceBufferUsage(callback)` + +* `callback` Function + +通过查找 buffer 进程来获取百分比最大使用量.当确定了TraceBufferUsage 的值确定的时候,就调用 `callback` . + +### `contentTracing.setWatchEvent(categoryName, eventName, callback)` + +* `categoryName` String +* `eventName` String +* `callback` Function + +任意时刻在任何进程上指定事件发生时将调用 `callback` . + +### `contentTracing.cancelWatchEvent()` + +取消 watch 事件. 如果启动查找,这或许会造成 watch 事件的回调函数 出错. \ No newline at end of file diff --git a/docs-translations/zh-CN/api/crash-reporter.md b/docs-translations/zh-CN/api/crash-reporter.md new file mode 100644 index 000000000000..6f2de5e8459d --- /dev/null +++ b/docs-translations/zh-CN/api/crash-reporter.md @@ -0,0 +1,66 @@ +# crashReporter + +`crash-reporter` 模块开启发送应用崩溃报告. + +下面是一个自动提交崩溃报告给服务器的例子 : + +```javascript +const crashReporter = require('electron').crashReporter; + +crashReporter.start({ + productName: 'YourName', + companyName: 'YourCompany', + submitURL: 'https://your-domain.com/url-to-submit', + autoSubmit: true +}); +``` + +可以使用下面的项目来创建一个服务器,用来接收和处理崩溃报告 : + +* [socorro](https://github.com/mozilla/socorro) +* [mini-breakpad-server](https://github.com/atom/mini-breakpad-server) + +## 方法 + +`crash-reporter` 模块有如下方法: + +### `crashReporter.start(options)` + +* `options` Object + * `companyName` String + * `submitURL` String - 崩溃报告发送的路径,以post方式. + * `productName` String (可选) - 默认为 `Electron`. + * `autoSubmit` Boolean - 是否自动提交. + 默认为 `true`. + * `ignoreSystemCrashHandler` Boolean - 默认为 `false`. + * `extra` Object - 一个你可以定义的对象,附带在崩溃报告上一起发送 . 只有字符串属性可以被正确发送,不支持嵌套对象. + +只可以在使用其它 `crashReporter` APIs 之前使用这个方法. + +**注意:** 在 OS X, Electron 使用一个新的 `crashpad` 客户端, 与 Windows 和 Linux 的 `breakpad` 不同. 为了开启崩溃点搜集,你需要在主进程和其它每个你需要搜集崩溃报告的渲染进程中调用 `crashReporter.start` API 来初始化 `crashpad`. + +### `crashReporter.getLastCrashReport()` + +返回最后一个崩溃报告的日期和 ID.如果没有过崩溃报告发送过来,或者还没有开始崩溃报告搜集,将返回 `null` . + +### `crashReporter.getUploadedReports()` + +返回所有上载的崩溃报告,每个报告包含了上载日期和 ID. + +## crash-reporter Payload + +崩溃报告将发送下面 `multipart/form-data` `POST` 型的数据给 `submitURL` : + +* `ver` String - Electron 版本. +* `platform` String - 例如 'win32'. +* `process_type` String - 例如 'renderer'. +* `guid` String - 例如 '5e1286fc-da97-479e-918b-6bfb0c3d1c72' +* `_version` String - `package.json` 版本. +* `_productName` String - `crashReporter` `options` + 对象中的产品名字. +* `prod` String - 基础产品名字. 这种情况为 Electron. +* `_companyName` String - `crashReporter` `options` + 对象中的公司名字. +* `upload_file_minidump` File - 崩溃报告按照 `minidump` 的格式. +* `crashReporter` 中的 `extra` 对象的所有等级和一个属性. + `options` object \ No newline at end of file diff --git a/docs-translations/zh-CN/api/desktop-capturer.md b/docs-translations/zh-CN/api/desktop-capturer.md new file mode 100644 index 000000000000..954520d05eaa --- /dev/null +++ b/docs-translations/zh-CN/api/desktop-capturer.md @@ -0,0 +1,64 @@ +# desktopCapturer + +`desktopCapturer` 模块可用来获取可用资源,这个资源可通过 `getUserMedia` 捕获得到. + +```javascript +// 在渲染进程中. +var desktopCapturer = require('electron').desktopCapturer; + +desktopCapturer.getSources({types: ['window', 'screen']}, function(error, sources) { + if (error) throw error; + for (var i = 0; i < sources.length; ++i) { + if (sources[i].name == "Electron") { + navigator.webkitGetUserMedia({ + audio: false, + video: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: sources[i].id, + minWidth: 1280, + maxWidth: 1280, + minHeight: 720, + maxHeight: 720 + } + } + }, gotStream, getUserMediaError); + return; + } + } +}); + +function gotStream(stream) { + document.querySelector('video').src = URL.createObjectURL(stream); +} + +function getUserMediaError(e) { + console.log('getUserMediaError'); +} +``` + +当调用 `navigator.webkitGetUserMedia` 时创建一个约束对象,如果使用 `desktopCapturer` 的资源,必须设置 `chromeMediaSource` 为 `"desktop"` ,并且 `audio` 为 `false`. + +如果你想捕获整个桌面的 audio 和 video,你可以设置 `chromeMediaSource` 为 `"screen"` ,和 `audio` 为 `true`. +当使用这个方法的时候,不可以指定一个 `chromeMediaSourceId`. + +## 方法 + +`desktopCapturer` 模块有如下方法: + +### `desktopCapturer.getSources(options, callback)` + +* `options` Object + * `types` Array - 一个 String 数组,列出了可以捕获的桌面资源类型, 可用类型为 `screen` 和 `window`. + * `thumbnailSize` Object (可选) - 建议缩略可被缩放的 size, 默认为 `{width: 150, height: 150}`. +* `callback` Function + +发起一个请求,获取所有桌面资源,当请求完成的时候使用 `callback(error, sources)` 调用 `callback` . + +`sources` 是一个 `Source` 对象数组, 每个 `Source` 表示了一个捕获的屏幕或单独窗口,并且有如下属性 : +* `id` String - 在 `navigator.webkitGetUserMedia` 中使用的捕获窗口或屏幕的 id . 格式为 `window:XX` 祸 + `screen:XX`,`XX` 是一个随机数. +* `name` String - 捕获窗口或屏幕的描述名 . 如果资源为屏幕,名字为 `Entire Screen` 或 `Screen `; 如果资源为窗口, 名字为窗口的标题. +* `thumbnail` [NativeImage](NativeImage.md) - 缩略图. + +**注意:** 不能保证 `source.thumbnail` 的 size 和 `options` 中的 `thumnbailSize` 一直一致. 它也取决于屏幕或窗口的缩放比例. \ No newline at end of file diff --git a/docs-translations/zh-CN/api/dialog.md b/docs-translations/zh-CN/api/dialog.md new file mode 100644 index 000000000000..3e77eeaa2a55 --- /dev/null +++ b/docs-translations/zh-CN/api/dialog.md @@ -0,0 +1,94 @@ +# dialog + +`dialog` 模块提供了api来展示原生的系统对话框,例如打开文件框,alert框,所以web应用可以给用户带来跟系统应用相同的体验. + +对话框例子,展示了选择文件和目录: + +```javascript +var win = ...; // BrowserWindow in which to show the dialog +const dialog = require('electron').dialog; +console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', 'multiSelections' ]})); +``` + +**OS X 上的注意事项**: 如果你想像sheets一样展示对话框,只需要在`browserWindow` 参数中提供一个 `BrowserWindow` 的引用对象. + +## 方法 + +`dialog` 模块有以下方法: + +### `dialog.showOpenDialog([browserWindow, ]options[, callback])` + +* `browserWindow` BrowserWindow (可选) +* `options` Object + * `title` String + * `defaultPath` String + * `filters` Array + * `properties` Array - 包含了对话框的特性值, 可以包含 `openFile`, `openDirectory`, `multiSelections` and + `createDirectory` +* `callback` Function (可选) + +成功使用这个方法的话,就返回一个可供用户选择的文件路径数组,失败返回 `undefined`. + +`filters` 当需要限定用户的行为的时候,指定一个文件数组给用户展示或选择. 例如: + +```javascript +{ + filters: [ + { name: 'Images', extensions: ['jpg', 'png', 'gif'] }, + { name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] }, + { name: 'Custom File Type', extensions: ['as'] }, + { name: 'All Files', extensions: ['*'] } + ] +} +``` + +`extensions` 数组应当只包含扩展名,不应该包含通配符或'.'号 (例如 +`'png'` 正确,但是 `'.png'` 和 `'*.png'` 不正确). 展示全部文件的话, 使用 +`'*'` 通配符 (不支持其他通配符). + +如果 `callback` 被调用, 将异步调用 API ,并且结果将用过 `callback(filenames)` 展示. + +**注意:** 在 Windows 和 Linux ,一个打开的 dialog 不能既是文件选择框又是目录选择框, 所以如果在这些平台上设置 `properties` 的值为 +`['openFile', 'openDirectory']` , 将展示一个目录选择框. + +### `dialog.showSaveDialog([browserWindow, ]options[, callback])` + +* `browserWindow` BrowserWindow (可选) +* `options` Object + * `title` String + * `defaultPath` String + * `filters` Array +* `callback` Function (可选) + +成功使用这个方法的话,就返回一个可供用户选择的文件路径数组,失败返回 `undefined`. + +`filters` 指定展示一个文件类型数组, 例子 +`dialog.showOpenDialog` . + +如果 `callback` 被调用, 将异步调用 API ,并且结果将用过 `callback(filenames)` 展示. + +### `dialog.showMessageBox([browserWindow, ]options[, callback])` + +* `browserWindow` BrowserWindow (可选) +* `options` Object + * `type` String - 可以是 `"none"`, `"info"`, `"error"`, `"question"` 或 + `"warning"`. 在 Windows, "question" 与 "info" 展示图标相同, 除非你使用 "icon" 参数. + * `buttons` Array - buttons 内容,数组. + * `defaultId` Integer - 在message box 对话框打开的时候,设置默认button选中,值为在 buttons 数组中的button索引. + * `title` String - message box 的标题,一些平台不显示. + * `message` String - message box 内容. + * `detail` String - 额外信息. + * `icon` [NativeImage](native-image.md) + * `cancelId` Integer - 当用户关闭对话框的时候,不是通过点击对话框的button,就返回值.默认值为对应 "cancel" 或 "no" 标签button 的索引值, 或者如果没有这种button,就返回0. 在 OS X 和 Windows 上, "Cancel" button 的索引值将一直是 `cancelId`, 不管之前是不是特别指出的. + * `noLink` Boolean - 在 Windows ,Electron 将尝试识别哪个button 是普通 button (如 "Cancel" 或 "Yes"), 然后再对话框中以链接命令(command links)方式展现其它的 button . 这能让对话框展示得很炫酷.如果你不喜欢这种效果,你可以设置 `noLink` 为 `true`. +* `callback` Function + +展示 message box, 它会阻塞进程,直到 message box 关闭为止.返回点击按钮的索引值. + +如果 `callback` 被调用, 将异步调用 API ,并且结果将用过 `callback(response)` 展示. + +### `dialog.showErrorBox(title, content)` + +展示一个传统的包含错误信息的对话框. + +在 `app` 模块触发 `ready` 事件之前,这个 api 可以被安全调用,通常它被用来在启动的早期阶段报告错误. 在 Linux 上,如果在 `app` 模块触发 `ready` 事件之前调用,message 将会被触发显示stderr,并且没有实际GUI 框显示. \ No newline at end of file diff --git a/docs-translations/zh-CN/api/download-item.md b/docs-translations/zh-CN/api/download-item.md new file mode 100644 index 000000000000..e869e8f30570 --- /dev/null +++ b/docs-translations/zh-CN/api/download-item.md @@ -0,0 +1,96 @@ +# DownloadItem + +`DownloadItem`(下载项)是一个在Electron中展示下载项的 +[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)。 +它被用于`Session`模块中的`will-download`事件,允许用户去控制下载项。 + +```javascript +// In the main process. +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"); + } + }); +``` + +## 事件 + +### 事件: 'updated' + +当`downloadItem`获得更新时发射。 + +### 事件: 'done' + +* `event` Event +* `state` String + * `completed` - 下载成功完成。 + * `cancelled` - 下载被取消。 + * `interrupted` - 与文件服务器错误的中断连接。 + +当下载处于一个终止状态时发射。这包括了一个完成的下载,一个被取消的下载(via `downloadItem.cancel()`), +和一个被意外中断的下载(无法恢复)。 + +## 方法 + +`downloadItem`对象有以下一些方法: + +### `downloadItem.setSavePath(path)` + +* `path` String - 设置下载项的保存文件路径. + +这个API仅仅在`session`的`will-download`回调函数中可用。 +如果用户没有这个API去设置保存路径,Electron会用原始程序去确定保存路径(通常提示一个保存对话框)。 + +### `downloadItem.pause()` + +暂停下载。 + +### `downloadItem.resume()` + +恢复被暂停的下载。 + +### `downloadItem.cancel()` + +取消下载操作。 + +### `downloadItem.getURL()` + +以`String`形式返回一个该下载项的下载源url。 + +### `downloadItem.getMimeType()` + +返回一个表示mime类型的`String`。 + +### `downloadItem.hasUserGesture()` + +返回一个`Boolean`表示下载是否有用户动作。 + +### `downloadItem.getFilename()` + +返回一个表示下载项文件名的`String`。 + +**Note:** 此文件名不一定总是保存在本地硬盘上的实际文件名。 +如果用户在下载保存对话框中修改了文件名,保存的文件的实际名称会与`downloadItem.getFilename()`方法返回的文件名不同。 + +### `downloadItem.getTotalBytes()` + +返回一个表示下载项总字节数大小的`Integer`。如果大小未知,返回0。 + +### `downloadItem.getReceivedBytes()` + +返回一个表示下载项已经接收的字节数大小的`Integer`。 + +### `downloadItem.getContentDisposition()` + +以`String`形式返回响应头(response header)中的`Content-Disposition`域。 diff --git a/docs-translations/zh-CN/api/environment-variables.md b/docs-translations/zh-CN/api/environment-variables.md new file mode 100644 index 000000000000..0a35eb59dfdb --- /dev/null +++ b/docs-translations/zh-CN/api/environment-variables.md @@ -0,0 +1,53 @@ +# 环境变量 + +一些 Electron 的行为受到环境变量的控制,因为他们的初始化比命令行和应用代码更早. + +POSIX shells 的例子: + +```bash +$ export ELECTRON_ENABLE_LOGGING=true +$ electron +``` + +Windows 控制台: + +```powershell +> set ELECTRON_ENABLE_LOGGING=true +> electron +``` + +## `ELECTRON_RUN_AS_NODE` + +类似node.js普通进程启动方式. + +## `ELECTRON_ENABLE_LOGGING` + +打印 Chrome 的内部日志到控制台. + +## `ELECTRON_LOG_ASAR_READS` + +当 Electron 读取 ASA 文档,把 read offset 和文档路径做日志记录到系统 `tmpdir`.结果文件将提供给 ASAR 模块来优化文档组织. + +## `ELECTRON_ENABLE_STACK_DUMPING` + +当 Electron 崩溃的时候,打印堆栈记录到控制台. + +如果 `crashReporter` 已经启动那么这个环境变量实效. + +## `ELECTRON_DEFAULT_ERROR_MODE` _Windows_ + +当 Electron 崩溃的时候,显示windows的崩溃对话框. + +如果 `crashReporter` 已经启动那么这个环境变量实效. + +## `ELECTRON_NO_ATTACH_CONSOLE` _Windows_ + +不可使用当前控制台. + +## `ELECTRON_FORCE_WINDOW_MENU_BAR` _Linux_ + +不可再 Linux 上使用全局菜单栏. + +## `ELECTRON_HIDE_INTERNAL_MODULES` + +关闭旧的内置模块如 `require('ipc')` 的通用模块. \ No newline at end of file diff --git a/docs-translations/zh-CN/api/frameless-window.md b/docs-translations/zh-CN/api/frameless-window.md new file mode 100644 index 000000000000..833f070ad27c --- /dev/null +++ b/docs-translations/zh-CN/api/frameless-window.md @@ -0,0 +1,87 @@ +# Frameless Window + +无边框窗口指的是不包含除页面本身以外任何其它可视部分的窗口([chrome](https://developer.mozilla.org/en-US/docs/Glossary/Chrome))。 +像工具栏,是窗口的一部分,但并不属于页面。这些是[`BrowserWindow`](browser-window.md) 类的选项。 + +## 创建无边框窗口 + +为了创建一个无边框窗口,你需要设置[BrowserWindow](browser-window.md)的`frame`为`false`: + + + +```javascript +const BrowserWindow = require('electron').BrowserWindow; +var win = new BrowserWindow({ width: 800, height: 600, frame: false }); +``` + +### OS X上的替代方案 + +在Mac OS X 10.10 Yosemite或者更新的版本中,有一个替代方案去生成一个无边框窗口。 +不同于设置`frame`为`false`会隐藏标题栏以及失去对窗口的控制,你可能想隐藏标题栏使你的页面内容显示在整个窗口上 +,同时又想保持对窗口的控制("traffic lights")。你可以通过指定`titleBarStyle`这一新选项达到目标: + +```javascript +var win = new BrowserWindow({ 'titleBarStyle': 'hidden' }); +``` + +## 透明窗口 + +通过设置`transparent` 选项为 `true`,你能使无边框窗口透明: + +```javascript +var win = new BrowserWindow({ transparent: true, frame: false }); +``` + +### 限制 + +* 你无法点击透明的区域。我们正在采用一个新的API去设置窗口的外形以解决这个问题, + 详见[our issue](https://github.com/electron/electron/issues/1335)。 +* 透明窗口是不可调整大小的。在某些平台上,设置`resizable`为`true`也许会造成这个透明窗口停止工作。 +* `blur`滤光器器只适用于网页,所以没法将模糊效果用于窗口之下(i.e. 其它在用户的系统中打开的应用)。 +* 在Windows系统中,当DWM被禁用时透明窗口不会工作。 +* Linux用户需要在命令行中输入`--enable-transparent-visuals --disable-gpu` + 去禁用GPU以及允许ARGB去渲染透明窗口,这是由于一个Linux上的上游bug[alpha channel doesn't work on some + NVidia drivers](https://code.google.com/p/chromium/issues/detail?id=369209)造成的 +* 在Mac上,透明窗口的阴影不会显示出来. + +## 可拖动区域 + +默认情况下,无边框窗口是不可拖动的。应用在CSS中设置`-webkit-app-region: drag` +告诉Electron哪个区域是可拖动的(比如系统标准的标题栏),应用也可以设置`-webkit-app-region: no-drag` +在可拖动区域中排除不可拖动的区域。需要注意的是,目前只支持矩形区域。 + +为了让整个窗口可拖动,你可以在`body`的样式中添加`-webkit-app-region: drag`: + +```html + + +``` + +另外需要注意的是,如果你设置了整个窗口可拖动,你必须标记按钮为不可拖动的(non-draggable), +否则用户不能点击它们: + +```css +button { + -webkit-app-region: no-drag; +} +``` + +如果你设置一个自定义的标题栏可拖动,你同样需要设置标题栏中所有的按钮为不可拖动(non-draggable)。 + +## 文本选择 + +在一个无边框窗口中,拖动动作会与文本选择发生冲突。比如,当你拖动标题栏,偶尔会选中标题栏上的文本。 +为了防止这种情况发生,你需要向下面这样在一个可拖动区域中禁用文本选择: + +```css +.titlebar { + -webkit-user-select: none; + -webkit-app-region: drag; +} +``` + +## 上下文菜单 + +在一些平台上,可拖动区域会被认为是非客户端框架(non-client frame),所有当你点击右键时,一个系统菜单会弹出。 +为了保证上下文菜单在所有平台下正确的显示,你不应该在可拖动区域使用自定义上下文菜单。 + diff --git a/docs-translations/zh-CN/api/ipc-main.md b/docs-translations/zh-CN/api/ipc-main.md new file mode 100644 index 000000000000..aef9c454a067 --- /dev/null +++ b/docs-translations/zh-CN/api/ipc-main.md @@ -0,0 +1,85 @@ +# ipcMain + +`ipcMain` 模块是类 +[EventEmitter](https://nodejs.org/api/events.html) 的实例.当在主进程中使用它的时候,它控制着由渲染进程(web page)发送过来的异步或同步消息.从渲染进程发送过来的消息将触发事件. + +## 发送消息 + +同样也可以从主进程向渲染进程发送消息,查看更多 [webContents.send][web-contents-send] . + +* 发送消息,事件名为 `channel`. +* 回应同步消息, 你可以设置 `event.returnValue`. +* 回应异步消息, 你可以使用 + `event.sender.send(...)`. + +一个例子,在主进程和渲染进程之间发送和处理消息: + +```javascript +// In main process. +const ipcMain = require('electron').ipcMain; +ipcMain.on('asynchronous-message', function(event, arg) { + console.log(arg); // prints "ping" + event.sender.send('asynchronous-reply', 'pong'); +}); + +ipcMain.on('synchronous-message', function(event, arg) { + console.log(arg); // prints "ping" + event.returnValue = 'pong'; +}); +``` + +```javascript +// In renderer process (web page). +const ipcRenderer = require('electron').ipcRenderer; +console.log(ipcRenderer.sendSync('synchronous-message', 'ping')); // prints "pong" + +ipcRenderer.on('asynchronous-reply', function(event, arg) { + console.log(arg); // prints "pong" +}); +ipcRenderer.send('asynchronous-message', 'ping'); +``` + +## 监听消息 + +`ipcMain` 模块有如下监听事件方法: + +### `ipcMain.on(channel, listener)` + +* `channel` String +* `listener` Function + +监听 `channel`, 当新消息到达,将通过 `listener(event, args...)` 调用 `listener`. + +### `ipcMain.once(channel, listener)` + +* `channel` String +* `listener` Function + +为事件添加一个一次性用的`listener` 函数.这个 `listener` 只有在下次的消息到达 `channel` 时被请求调用,之后就被删除了. + +### `ipcMain.removeListener(channel, listener)` + +* `channel` String +* `listener` Function + +为特定的 `channel` 从监听队列中删除特定的 `listener` 监听者. + +### `ipcMain.removeAllListeners([channel])` + +* `channel` String (可选) + +删除所有监听者,或特指的 `channel` 的所有监听者. + +## 事件对象 + +传递给 `callback` 的 `event` 对象有如下方法: + +### `event.returnValue` + +将此设置为在一个同步消息中返回的值. + +### `event.sender` + +返回发送消息的 `webContents` ,你可以调用 `event.sender.send` 来回复异步消息,更多信息 [webContents.send][web-contents-send]. + +[web-contents-send]: web-contents.md#webcontentssendchannel-arg1-arg2- \ No newline at end of file diff --git a/docs-translations/zh-CN/api/ipc-renderer.md b/docs-translations/zh-CN/api/ipc-renderer.md new file mode 100644 index 000000000000..beeaa6d76232 --- /dev/null +++ b/docs-translations/zh-CN/api/ipc-renderer.md @@ -0,0 +1,69 @@ +# ipcRenderer + +`ipcRenderer` 模块是一个 +[EventEmitter](https://nodejs.org/api/events.html) 类的实例. 它提供了有限的方法,你可以从渲染进程向主进程发送同步或异步消息. 也可以收到主进程的相应. + +查看 [ipcMain](ipc-main.md) 代码例子. + +## 消息监听 + +`ipcRenderer` 模块有下列方法来监听事件: + +### `ipcRenderer.on(channel, listener)` + +* `channel` String +* `listener` Function + +监听 `channel`, 当有新消息到达,使用 `listener(event, args...)` 调用 `listener` . + +### `ipcRenderer.once(channel, listener)` + +* `channel` String +* `listener` Function + +为这个事件添加一个一次性 `listener` 函数.这个 `listener` 将在下一次有新消息被发送到 `channel` 的时候被请求调用,之后就被删除了. + +### `ipcRenderer.removeListener(channel, listener)` + +* `channel` String +* `listener` Function + +从指定的 `channel` 中的监听者数组删除指定的 `listener` . + +### `ipcRenderer.removeAllListeners([channel])` + +* `channel` String (optional) + +删除所有的监听者,或者删除指定 `channel` 中的全部. + +## 发送消息 + +`ipcRenderer` 模块有如下方法来发送消息: + +### `ipcRenderer.send(channel[, arg1][, arg2][, ...])` + +* `channel` String +* `arg` (可选) + +通过 `channel` 向主进程发送异步消息,也可以发送任意参数.参数会被JSON序列化,之后就不会包含函数或原型链. + +主进程通过使用 `ipcMain` 模块来监听 `channel`,从而处理消息. + +### `ipcRenderer.sendSync(channel[, arg1][, arg2][, ...])` + +* `channel` String +* `arg` (可选) + +通过 `channel` 向主进程发送同步消息,也可以发送任意参数.参数会被JSON序列化,之后就不会包含函数或原型链. + +主进程通过使用 `ipcMain` 模块来监听 `channel`,从而处理消息, +通过 `event.returnValue` 来响应. + +__注意:__ 发送同步消息将会阻塞整个渲染进程,除非你知道你在做什么,否则就永远不要用它 . + +### `ipcRenderer.sendToHost(channel[, arg1][, arg2][, ...])` + +* `channel` String +* `arg` (可选) + +类似 `ipcRenderer.send` ,但是它的事件将发往 host page 的 `` 元素,而不是主进程. \ No newline at end of file diff --git a/docs-translations/zh-CN/api/menu-item.md b/docs-translations/zh-CN/api/menu-item.md new file mode 100644 index 000000000000..3d5eceedcb91 --- /dev/null +++ b/docs-translations/zh-CN/api/menu-item.md @@ -0,0 +1,57 @@ +# 菜单项 +菜单项模块允许你向应用或[menu][1]添加选项。 + +查看[menu][1]例子。 + +## 类:MenuItem +使用下面的方法创建一个新的 `MenuItem` + +###new MenuItem(options) +* `options` Object + * `click` Function - 当菜单项被点击的时候,使用 `click(menuItem,browserWindow)` 调用 + * `role` String - 定义菜单项的行为,在指定 `click` 属性时将会被忽略 + * `type` String - 取值 `normal`,`separator`,`checkbox`or`radio` + * `label` String + * `sublabel` String + * `accelerator` [Accelerator][2] + * `icon` [NativeImage][3] + * `enabled` Boolean + * `visible` Boolean + * `checked` Boolean + * `submenu` Menu - 应当作为 `submenu` 菜单项的特定类型,当它作为 `type: 'submenu'` 菜单项的特定类型时可以忽略。如果它的值不是 `Menu`,将自动转为 `Menu.buildFromTemplate`。 + * `id` String - 标志一个菜单的唯一性。如果被定义使用,它将被用作这个菜单项的参考位置属性。 + * `position` String - 定义给定的菜单的具体指定位置信息。 + +在创建菜单项时,如果有匹配的方法,建议指定 `role` 属性,不需要人为操作它的行为,这样菜单使用可以给用户最好的体验。 + + +`role`属性值可以为: + +* `undo` +* `redo` +* `cut` +* `copy` +* `paste` +* `selectall` +* `minimize` - 最小化当前窗口 +* `close` - 关闭当前窗口 + +在 OS X 上,`role` 还可以有以下值: + +* `about` - 匹配 `orderFrontStandardAboutPanel` 行为 +* `hide` - 匹配 `hide` 行为 +* `hideothers` - 匹配 `hideOtherApplications` 行为 +* `unhide` - 匹配 `unhideAllApplications` 行为 +* `front` - 匹配 `arrangeInFront` 行为 +* `window` - "Window" 菜单项 +* `help` - "Help" 菜单项 +* `services` - "Services" 菜单项 + + + + + + + [1]:https://github.com/heyunjiang/electron/blob/master/docs-translations/zh-CN/api/menu.md + [2]:https://github.com/heyunjiang/electron/blob/master/docs/api/accelerator.md + [3]:https://github.com/heyunjiang/electron/blob/master/docs/api/native-image.md \ No newline at end of file diff --git a/docs-translations/zh-CN/api/menu.md b/docs-translations/zh-CN/api/menu.md new file mode 100644 index 000000000000..1997a293d248 --- /dev/null +++ b/docs-translations/zh-CN/api/menu.md @@ -0,0 +1,355 @@ +# 菜单 + +`menu` 类可以用来创建原生菜单,它可用作应用菜单和 +[context 菜单](https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/PopupGuide/ContextMenus). + +这个模块是一个主进程的模块,并且可以通过 `remote` 模块给渲染进程调用. + +每个菜单有一个或几个菜单项 [menu items](menu-item.md),并且每个菜单项可以有子菜单. + +下面这个例子是在网页(渲染进程)中通过 [remote](remote.md) 模块动态创建的菜单,并且右键显示: + +```html + + +``` + +例子,在渲染进程中使用模板api创建应用菜单: + +```javascript +var template = [ + { + label: 'Edit', + submenu: [ + { + label: 'Undo', + accelerator: 'CmdOrCtrl+Z', + role: 'undo' + }, + { + label: 'Redo', + accelerator: 'Shift+CmdOrCtrl+Z', + role: 'redo' + }, + { + type: 'separator' + }, + { + label: 'Cut', + accelerator: 'CmdOrCtrl+X', + role: 'cut' + }, + { + label: 'Copy', + accelerator: 'CmdOrCtrl+C', + role: 'copy' + }, + { + label: 'Paste', + accelerator: 'CmdOrCtrl+V', + role: 'paste' + }, + { + label: 'Select All', + accelerator: 'CmdOrCtrl+A', + role: 'selectall' + }, + ] + }, + { + label: 'View', + submenu: [ + { + label: 'Reload', + accelerator: 'CmdOrCtrl+R', + click: function(item, focusedWindow) { + if (focusedWindow) + focusedWindow.reload(); + } + }, + { + label: 'Toggle Full Screen', + accelerator: (function() { + if (process.platform == 'darwin') + return 'Ctrl+Command+F'; + else + return 'F11'; + })(), + click: function(item, focusedWindow) { + if (focusedWindow) + focusedWindow.setFullScreen(!focusedWindow.isFullScreen()); + } + }, + { + label: 'Toggle Developer Tools', + accelerator: (function() { + if (process.platform == 'darwin') + return 'Alt+Command+I'; + else + return 'Ctrl+Shift+I'; + })(), + click: function(item, focusedWindow) { + if (focusedWindow) + focusedWindow.toggleDevTools(); + } + }, + ] + }, + { + label: 'Window', + role: 'window', + submenu: [ + { + label: 'Minimize', + accelerator: 'CmdOrCtrl+M', + role: 'minimize' + }, + { + label: 'Close', + accelerator: 'CmdOrCtrl+W', + role: 'close' + }, + ] + }, + { + label: 'Help', + role: 'help', + submenu: [ + { + label: 'Learn More', + click: function() { require('electron').shell.openExternal('http://electron.atom.io') } + }, + ] + }, +]; + +if (process.platform == 'darwin') { + var name = require('electron').remote.app.getName(); + template.unshift({ + label: name, + submenu: [ + { + label: 'About ' + name, + role: 'about' + }, + { + type: 'separator' + }, + { + label: 'Services', + role: 'services', + submenu: [] + }, + { + type: 'separator' + }, + { + label: 'Hide ' + name, + accelerator: 'Command+H', + role: 'hide' + }, + { + label: 'Hide Others', + accelerator: 'Command+Alt+H', + role: 'hideothers' + }, + { + label: 'Show All', + role: 'unhide' + }, + { + type: 'separator' + }, + { + label: 'Quit', + accelerator: 'Command+Q', + click: function() { app.quit(); } + }, + ] + }); + // Window menu. + template[3].submenu.push( + { + type: 'separator' + }, + { + label: 'Bring All to Front', + role: 'front' + } + ); +} + +var menu = Menu.buildFromTemplate(template); +Menu.setApplicationMenu(menu); +``` + +## 类: Menu + +### `new Menu()` + +创建一个新的菜单. + +## 方法 + +`菜单` 类有如下方法: + +### `Menu.setApplicationMenu(menu)` + +* `menu` Menu + +在 OS X 上设置应用菜单 `menu` . +在windows 和 linux,是为每个窗口都在其顶部设置菜单 `menu`. + +### `Menu.sendActionToFirstResponder(action)` _OS X_ + +* `action` String + +发送 `action` 给应用的第一个响应器.这个用来模仿 Cocoa 菜单的默认行为,通常你只需要使用 `MenuItem` 的属性 `role`. + +查看更多 OS X 的原生 action [OS X Cocoa Event Handling Guide](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/EventOverview/EventArchitecture/EventArchitecture.html#//apple_ref/doc/uid/10000060i-CH3-SW7) . + +### `Menu.buildFromTemplate(template)` + +* `template` Array + +一般来说,`template` 只是用来创建 [MenuItem](menu-item.md) 的数组 `参数` . + +你也可以向 `template` 元素添加其它东西,并且他们会变成已经有的菜单项的属性. + +##实例方法 + +`menu` 对象有如下实例方法 + +### `menu.popup([browserWindow, x, y, positioningItem])` + +* `browserWindow` BrowserWindow (可选) - 默认为 `null`. +* `x` Number (可选) - 默认为 -1. +* `y` Number (**必须** 如果x设置了) - 默认为 -1. +* `positioningItem` Number (可选) _OS X_ - 在指定坐标鼠标位置下面的菜单项的索引. 默认为 + -1. + +在 `browserWindow` 中弹出 context menu .你可以选择性地提供指定的 `x, y` 来设置菜单应该放在哪里,否则它将默认地放在当前鼠标的位置. + +### `menu.append(menuItem)` + +* `menuItem` MenuItem + +添加菜单项. + +### `menu.insert(pos, menuItem)` + +* `pos` Integer +* `menuItem` MenuItem + +在制定位置添加菜单项. + +### `menu.items()` + +获取一个菜单项数组. + +## OS X Application 上的菜单的注意事项 + +相对于windows 和 linux, OS X 上的应用菜单是完全不同的style,这里是一些注意事项,来让你的菜单项更原生化. + +### 标准菜单 + +在 OS X 上,有很多系统定义的标准菜单,例如 `Services` and +`Windows` 菜单.为了让你的应用更标准化,你可以为你的菜单的 `role` 设置值,然后 electron 将会识别他们并且让你的菜单更标准: + +* `window` +* `help` +* `services` + +### 标准菜单项行为 + +OS X 为一些菜单项提供了标准的行为方法,例如 `About xxx`, +`Hide xxx`, and `Hide Others`. 为了让你的菜单项的行为更标准化,你应该为菜单项设置 `role` 属性. + +### 主菜单名 + +在 OS X ,无论你设置的什么标签,应用菜单的第一个菜单项的标签始终未你的应用名字.想要改变它的话,你必须通过修改应用绑定的 `Info.plist` 文件来修改应用名字.更多信息参考[About Information +Property List Files][AboutInformationPropertyListFiles] . + +## 为制定浏览器窗口设置菜单 (*Linux* *Windows*) + +浏览器窗口的[`setMenu` 方法][setMenu] 能够设置菜单为特定浏览器窗口的类型. + +## 菜单项位置 + +当通过 `Menu.buildFromTemplate` 创建菜单的时候,你可以使用 `position` and `id` 来放置菜单项. + +`MenuItem` 的属性 `position` 格式为 `[placement]=[id]`,`placement` 取值为 `before`, `after`, 或 `endof` 和 `id`, `id` 是菜单已经存在的菜单项的唯一 ID: + +* `before` - 在对应引用id菜单项之前插入. 如果引用的菜单项不存在,则将其插在菜单末尾. +* `after` - 在对应引用id菜单项之后插入. 如果引用的菜单项不存在,则将其插在菜单末尾. +* `endof` - 在逻辑上包含对应引用id菜单项的集合末尾插入. 如果引用的菜单项不存在, 则将使用给定的id创建一个新的集合,并且这个菜单项将插入. + +当一个菜档项插入成功了,所有的没有插入的菜单项将一个接一个地在后面插入.所以如果你想在同一个位置插入一组菜单项,只需要为这组菜单项的第一个指定位置. + +### 例子 + +模板: + +```javascript +[ + {label: '4', id: '4'}, + {label: '5', id: '5'}, + {label: '1', id: '1', position: 'before=4'}, + {label: '2', id: '2'}, + {label: '3', id: '3'} +] +``` + +菜单: + +``` +- 1 +- 2 +- 3 +- 4 +- 5 +``` + +模板: + +```javascript +[ + {label: 'a', position: 'endof=letters'}, + {label: '1', position: 'endof=numbers'}, + {label: 'b', position: 'endof=letters'}, + {label: '2', position: 'endof=numbers'}, + {label: 'c', position: 'endof=letters'}, + {label: '3', position: 'endof=numbers'} +] +``` + +菜单: + +``` +- --- +- a +- b +- c +- --- +- 1 +- 2 +- 3 +``` + +[AboutInformationPropertyListFiles]: https://developer.apple.com/library/ios/documentation/general/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html +[setMenu]: +https://github.com/electron/electron/blob/master/docs/api/browser-window.md#winsetmenumenu-linux-windows \ No newline at end of file diff --git a/docs-translations/zh-CN/api/native-image.md b/docs-translations/zh-CN/api/native-image.md new file mode 100644 index 000000000000..3644ff0b8bcc --- /dev/null +++ b/docs-translations/zh-CN/api/native-image.md @@ -0,0 +1,148 @@ +# nativeImage + +在 Electron 中, 对所有创建 images 的 api 来说, 你可以使用文件路径或 `nativeImage` 实例. 如果使用 `null` ,将创建一个空的image 对象. + +例如, 当创建一个 tray 或设置窗口的图标时候,你可以使用一个字符串的图片路径 : + +```javascript +var appIcon = new Tray('/Users/somebody/images/icon.png'); +var window = new BrowserWindow({icon: '/Users/somebody/images/window.png'}); +``` + +或者从剪切板中读取图片,它返回的是 `nativeImage`: + +```javascript +var image = clipboard.readImage(); +var appIcon = new Tray(image); +``` + +## 支持的格式 + +当前支持 `PNG` 和 `JPEG` 图片格式. 推荐 `PNG` ,因为它支持透明和无损压缩. + +在 Windows, 你也可以使用 `ICO` 图标的格式. + +## 高分辨率图片 + +如果平台支持 high-DPI,你可以在图片基础路径后面添加 `@2x` ,可以标识它为高分辨率的图片. + +例如,如果 `icon.png` 是一个普通图片并且拥有标准分辨率,然后 `icon@2x.png`将被当作高分辨率的图片处理,它将拥有双倍 DPI 密度. + +如果想同时支持展示不同分辨率的图片,你可以将拥有不同size 的图片放在同一个文件夹下,不用 DPI 后缀.例如 : + +```text +images/ +├── icon.png +├── icon@2x.png +└── icon@3x.png +``` + + +```javascript +var appIcon = new Tray('/Users/somebody/images/icon.png'); +``` + +也支持下面这些 DPI 后缀: + +* `@1x` +* `@1.25x` +* `@1.33x` +* `@1.4x` +* `@1.5x` +* `@1.8x` +* `@2x` +* `@2.5x` +* `@3x` +* `@4x` +* `@5x` + +## 模板图片 + +模板图片由黑色和清色(和一个 alpha 通道)组成. +模板图片不是单独使用的,而是通常和其它内容混合起来创建期望的最终效果. + +最常见的用力是将模板图片用到菜单栏图片上,所以它可以同时适应亮、黑不同的菜单栏. + +**注意:** 模板图片只在 OS X 上可用. + +为了将图片标识为一个模板图片,它的文件名应当以 `Template` 结尾. 例如: + +* `xxxTemplate.png` +* `xxxTemplate@2x.png` + +## 方法 + +`nativeImage` 类有如下方法: + +### `nativeImage.createEmpty()` + +创建一个空的 `nativeImage` 实例. + +### `nativeImage.createFromPath(path)` + +* `path` String + +从指定 `path` 创建一个新的 `nativeImage` 实例 . + +### `nativeImage.createFromBuffer(buffer[, scaleFactor])` + +* `buffer` [Buffer][buffer] +* `scaleFactor` Double (可选) + +从 `buffer` 创建一个新的 `nativeImage` 实例 .默认 `scaleFactor` 是 1.0. + +### `nativeImage.createFromDataURL(dataURL)` + +* `dataURL` String + +从 `dataURL` 创建一个新的 `nativeImage` 实例 . + +## 实例方法 + +`nativeImage` 有如下方法: + +```javascript +const nativeImage = require('electron').nativeImage; + +var image = nativeImage.createFromPath('/Users/somebody/images/icon.png'); +``` + +### `image.toPng()` + +返回一个 [Buffer][buffer] ,它包含了图片的 `PNG` 编码数据. + +### `image.toJpeg(quality)` + +* `quality` Integer (**必须**) - 在 0 - 100 之间. + +返回一个 [Buffer][buffer] ,它包含了图片的 `JPEG` 编码数据. + +### `image.toDataURL()` + +返回图片数据的 URL. + +### `image.getNativeHandle()` _OS X_ + +返回一个保存了 c 指针的 [Buffer][buffer] 来潜在处理原始图像.在OS X, 将会返回一个 `NSImage` 指针实例. + +注意那返回的指针是潜在原始图像的弱指针,而不是一个复制,你_必须_ 确保与 `nativeImage` 的关联不间断 . + +### `image.isEmpty()` + +返回一个 boolean ,标识图片是否为空. + +### `image.getSize()` + +返回图片的 size. + +[buffer]: https://nodejs.org/api/buffer.html#buffer_class_buffer + +### `image.setTemplateImage(option)` + +* `option` Boolean + +将图片标识为模板图片. + +### `image.isTemplateImage()` + +返回一个 boolean ,标识图片是否是模板图片. \ No newline at end of file diff --git a/docs-translations/zh-CN/api/power-monitor.md b/docs-translations/zh-CN/api/power-monitor.md new file mode 100644 index 000000000000..3394b4a284e2 --- /dev/null +++ b/docs-translations/zh-CN/api/power-monitor.md @@ -0,0 +1,36 @@ +# powerMonitor + +`power-monitor`模块是用来监听能源区改变的.只能在主进程中使用.在 `app` 模块的 `ready` 事件触发之后就不能使用这个模块了. + +例如: + +```javascript +app.on('ready', function() { + require('electron').powerMonitor.on('suspend', function() { + console.log('The system is going to sleep'); + }); +}); +``` + +## 事件 + +`power-monitor` 模块可以触发下列事件: + +### Event: 'suspend' + +在系统挂起的时候触发. + +### Event: 'resume' + +在系统恢复继续工作的时候触发. +Emitted when system is resuming. + +### Event: 'on-ac' + +在系统使用交流电的时候触发. +Emitted when the system changes to AC power. + +### Event: 'on-battery' + +在系统使用电池电源的时候触发. +Emitted when system changes to battery power. \ No newline at end of file diff --git a/docs-translations/zh-CN/api/power-save-blocker.md b/docs-translations/zh-CN/api/power-save-blocker.md new file mode 100644 index 000000000000..3a045eaea779 --- /dev/null +++ b/docs-translations/zh-CN/api/power-save-blocker.md @@ -0,0 +1,48 @@ +# powerSaveBlocker + +`powerSaveBlocker` 模块是用来阻止应用系统进入睡眠模式的,因此这允许应用保持系统和屏幕继续工作. + +例如: + +```javascript +const powerSaveBlocker = require('electron').powerSaveBlocker; + +var id = powerSaveBlocker.start('prevent-display-sleep'); +console.log(powerSaveBlocker.isStarted(id)); + +powerSaveBlocker.stop(id); +``` + +## 方法 + +`powerSaveBlocker` 模块有如下方法: + +### `powerSaveBlocker.start(type)` + +* `type` String - 强行保存阻塞类型. + * `prevent-app-suspension` - 阻止应用挂起. + 保持系统活跃,但是允许屏幕不亮. 用例: + 下载文件或者播放音频. + * `prevent-display-sleep`- 阻止应用进入休眠. 保持系统和屏幕活跃,屏幕一直亮. 用例: 播放音频. + +开始阻止系统进入睡眠模式.返回一个整数,这个整数标识了保持活跃的blocker. + +**注意:** `prevent-display-sleep` 有更高的优先级 +`prevent-app-suspension`. 只有最高优先级生效. 换句话说, `prevent-display-sleep` 优先级永远高于 +`prevent-app-suspension`. + +例如, A 请求调用了 `prevent-app-suspension`, B请求调用了 `prevent-display-sleep`. `prevent-display-sleep` +将一直工作,直到B停止调用. 在那之后, `prevent-app-suspension` +才起效. + +### `powerSaveBlocker.stop(id)` + +* `id` Integer - 通过 `powerSaveBlocker.start` 返回的保持活跃的 blocker id. + +让指定blocker 停止活跃. + +### `powerSaveBlocker.isStarted(id)` + +* `id` Integer - 通过 `powerSaveBlocker.start` 返回的保持活跃的 blocker id. + +返回 boolean, 是否对应的 `powerSaveBlocker` 已经启动. \ No newline at end of file diff --git a/docs-translations/zh-CN/api/process.md b/docs-translations/zh-CN/api/process.md new file mode 100644 index 000000000000..28127776cf0f --- /dev/null +++ b/docs-translations/zh-CN/api/process.md @@ -0,0 +1,48 @@ +# 进程 + +Electron 中的 `process` 对象 与 upstream node 中的有以下的不同点: + +* `process.type` String - 进程类型, 可以是 `browser` (i.e. main process) + 或 `renderer`. +* `process.versions.electron` String - Electron的版本. +* `process.versions.chrome` String - Chromium的版本. +* `process.resourcesPath` String - JavaScript源代码路径. +* `process.mas` Boolean - 在Mac App Store 创建, 它的值为 `true`, 在其它的地方值为 `undefined`. + +## 事件 + +### 事件: 'loaded' + +在Electron已经加载了其内部预置脚本和它准备加载主进程或渲染进程的时候触发. + +当node被完全关闭的时候,它可以被预加载脚本使用来添加(原文: removed)与node无关的全局符号来回退到全局范围: + +```js +// preload.js +var _setImmediate = setImmediate; +var _clearImmediate = clearImmediate; +process.once('loaded', function() { + global.setImmediate = _setImmediate; + global.clearImmediate = _clearImmediate; +}); +``` + +## 属性 + +### `process.noAsar` + +设置它为 `true` 可以使 `asar` 文件在node的内置模块中失效. + +## 方法 + +`process` 对象有如下方法: + +### `process.hang()` + +使当前进程的主线程挂起. + +### `process.setFdLimit(maxDescriptors)` _OS X_ _Linux_ + +* `maxDescriptors` Integer + +设置文件描述符软限制于 `maxDescriptors` 或硬限制与os, 无论它是否低于当前进程. diff --git a/docs-translations/zh-CN/api/protocol.md b/docs-translations/zh-CN/api/protocol.md new file mode 100644 index 000000000000..a7dab97c5d56 --- /dev/null +++ b/docs-translations/zh-CN/api/protocol.md @@ -0,0 +1,183 @@ +# 协议 + +`protocol` 模块可以注册一个自定义协议,或者使用一个已经存在的协议. + +例子,使用一个与 `file://` 功能相似的协议 : + +```javascript +const electron = require('electron'); +const app = electron.app; +const path = require('path'); + +app.on('ready', function() { + var protocol = electron.protocol; + protocol.registerFileProtocol('atom', function(request, callback) { + var url = request.url.substr(7); + callback({path: path.normalize(__dirname + '/' + url)}); + }, function (error) { + if (error) + console.error('Failed to register protocol') + }); +}); +``` + +**注意:** 这个模块只有在 `app` 模块的 `ready` 事件触发之后才可使用. + +## 方法 + +`protocol` 模块有如下方法: + +### `protocol.registerStandardSchemes(schemes)` + +* `schemes` Array - 将一个自定义的方案注册为标准的方案. + +一个标准的 `scheme` 遵循 RFC 3986 的 +[generic URI syntax](https://tools.ietf.org/html/rfc3986#section-3) 标准. 这包含了 `file:` 和 `filesystem:`. + +### `protocol.registerServiceWorkerSchemes(schemes)` + +* `schemes` Array - 将一个自定义的方案注册为处理 service workers. + +### `protocol.registerFileProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (可选) + +注册一个协议,用来发送响应文件.当通过这个协议来发起一个请求的时候,将使用 `handler(request, callback)` 来调用 +`handler` .当 `scheme` 被成功注册或者完成(错误)时失败,将使用 `completion(null)` 调用 `completion`. + +* `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` Array (可选) +* `callback` Function + +`uploadData` 是一个 `data` 对象数组: + +* `data` Object + * `bytes` Buffer - 被发送的内容. + * `file` String - 上传的文件路径. + +为了处理请求,调用 `callback` 时需要使用文件路径或者一个带 `path` 参数的对象, 例如 `callback(filePath)` 或 +`callback({path: filePath})`. + +当不使用任何参数调用 `callback` 时,你可以指定一个数字或一个带有 `error` 参数的对象,来标识 `request` 失败.你可以使用的 error number 可以参考 +[net error list][net-error]. + +默认 `scheme` 会被注册为一个 `http:` 协议,它与遵循 "generic URI syntax" 规则的协议解析不同,例如 `file:` ,所以你或许应该调用 `protocol.registerStandardSchemes` 来创建一个标准的 scheme. + +### `protocol.registerBufferProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (可选) + +注册一个 `scheme` 协议,用来发送响应 `Buffer` . + +这个方法的用法类似 `registerFileProtocol`,除非使用一个 `Buffer` 对象,或一个有 `data`, +`mimeType`, 和 `charset` 属性的对象来调用 `callback` . + +例子: + +```javascript +protocol.registerBufferProtocol('atom', function(request, callback) { + callback({mimeType: 'text/html', data: new Buffer('
Response
')}); +}, function (error) { + if (error) + console.error('Failed to register protocol') +}); +``` + +### `protocol.registerStringProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (可选) + +注册一个 `scheme` 协议,用来发送响应 `String` . + +这个方法的用法类似 `registerFileProtocol`,除非使用一个 `String` 对象,或一个有 `data`, +`mimeType`, 和 `charset` 属性的对象来调用 `callback` . + +### `protocol.registerHttpProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (可选) + +注册一个 `scheme` 协议,用来发送 HTTP 请求作为响应. + +这个方法的用法类似 `registerFileProtocol`,除非使用一个 `redirectRequest` 对象,或一个有 `url`, `method`, +`referrer`, `uploadData` 和 `session` 属性的对象来调用 `callback` . + +* `redirectRequest` Object + * `url` String + * `method` String + * `session` Object (可选) + * `uploadData` Object (可选) + +默认这个 HTTP 请求会使用当前 session .如果你想使用不同的session值,你应该设置 `session` 为 `null`. + +POST 请求应当包含 `uploadData` 对象. + +* `uploadData` object + * `contentType` String - 内容的 MIME type. + * `data` String - 被发送的内容. + +### `protocol.unregisterProtocol(scheme[, completion])` + +* `scheme` String +* `completion` Function (可选) + +注销自定义协议 `scheme`. + +### `protocol.isProtocolHandled(scheme, callback)` + +* `scheme` String +* `callback` Function + +将使用一个布尔值来调用 `callback` ,这个布尔值标识了是否已经存在 `scheme` 的句柄了. + +### `protocol.interceptFileProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (可选) + +拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送响应文件. + +### `protocol.interceptStringProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (可选) + +拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送响应 `String`. + +### `protocol.interceptBufferProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (可选) + +拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送响应 `Buffer`. + +### `protocol.interceptHttpProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (optional) + +拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送新的响应 HTTP 请求. +Intercepts `scheme` protocol and uses `handler` as the protocol's new handler +which sends a new HTTP request as a response. + +### `protocol.uninterceptProtocol(scheme[, completion])` + +* `scheme` String +* `completion` Function +取消对 `scheme` 的拦截,使用它的原始句柄进行处理. + +[net-error]: https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h \ No newline at end of file diff --git a/docs-translations/zh-CN/api/screen.md b/docs-translations/zh-CN/api/screen.md new file mode 100644 index 000000000000..6c44acfeb196 --- /dev/null +++ b/docs-translations/zh-CN/api/screen.md @@ -0,0 +1,135 @@ +# screen + +`screen` 模块检索屏幕的 size,显示,鼠标位置等的信息.在 `app` 模块的`ready` 事件触发之前不可使用这个模块. + +`screen` 是一个 [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter). + +**注意:** 在渲染进程 / 开发者工具栏, `window.screen` 是一个预设值的 DOM +属性, 所以这样写 `var screen = require('electron').screen` 将不会工作. +在我们下面的例子, 我们取代使用可变名字的 `electronScreen`. +一个例子,创建一个充满整个屏幕的窗口 : + +```javascript +const electron = require('electron'); +const app = electron.app; +const BrowserWindow = electron.BrowserWindow; + +var mainWindow; + +app.on('ready', function() { + var electronScreen = electron.screen; + var size = electronScreen.getPrimaryDisplay().workAreaSize; + mainWindow = new BrowserWindow({ width: size.width, height: size.height }); +}); +``` + +另一个例子,在此页外创建一个窗口: + +```javascript +const electron = require('electron'); +const app = electron.app; +const BrowserWindow = electron.BrowserWindow; + +var mainWindow; + +app.on('ready', function() { + var electronScreen = electron.screen; + var displays = electronScreen.getAllDisplays(); + var externalDisplay = null; + for (var i in displays) { + if (displays[i].bounds.x != 0 || displays[i].bounds.y != 0) { + externalDisplay = displays[i]; + break; + } + } + + if (externalDisplay) { + mainWindow = new BrowserWindow({ + x: externalDisplay.bounds.x + 50, + y: externalDisplay.bounds.y + 50 + }); + } +}); +``` + +## `Display` 对象 + +`Display` 对象表示一个连接到系统的物理显示. 一个虚设的 `Display` 或许存在于一个无头系统(headless system)中,或者一个 `Display` 对应一个远程的、虚拟的display. + +* `display` object + * `id` Integer - 与display 相关的唯一性标志. + * `rotation` Integer - 可以是 0, 1, 2, 3, 每个代表了屏幕旋转的度数 0, 90, 180, 270. + * `scaleFactor` Number - Output device's pixel scale factor. + * `touchSupport` String - 可以是 `available`, `unavailable`, `unknown`. + * `bounds` Object + * `size` Object + * `workArea` Object + * `workAreaSize` Object + +## 事件 + +`screen` 模块有如下事件: + +### Event: 'display-added' + +返回: + +* `event` Event +* `newDisplay` Object + +当添加了 `newDisplay` 时发出事件 + +### Event: 'display-removed' + +返回: + +* `event` Event +* `oldDisplay` Object + +当移出了 `oldDisplay` 时发出事件 + +### Event: 'display-metrics-changed' + +返回: + +* `event` Event +* `display` Object +* `changedMetrics` Array + +当一个 `display` 中的一个或更多的 metrics 改变时发出事件. +`changedMetrics` 是一个用来描述这个改变的数组.可能的变化为 `bounds`, +`workArea`, `scaleFactor` 和 `rotation`. + +## 方法 + +`screen` 模块有如下方法: + +### `screen.getCursorScreenPoint()` + +返回当前鼠标的绝对路径 . + +### `screen.getPrimaryDisplay()` + +返回最主要的 display. + +### `screen.getAllDisplays()` + +返回一个当前可用的 display 数组. + +### `screen.getDisplayNearestPoint(point)` + +* `point` Object + * `x` Integer + * `y` Integer + +返回离指定点最近的 display. + +### `screen.getDisplayMatching(rect)` + +* `rect` Object + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer + +返回与提供的边界范围最密切相关的 display. diff --git a/docs-translations/zh-CN/api/session.md b/docs-translations/zh-CN/api/session.md new file mode 100644 index 000000000000..b9664c08e652 --- /dev/null +++ b/docs-translations/zh-CN/api/session.md @@ -0,0 +1,481 @@ +# session + +`session` 模块可以用来创建一个新的 `Session` 对象. + +你也可以通过使用 [`webContents`](web-contents.md) 的属性 `session` 来使用一个已有页面的 `session` ,`webContents` 是[`BrowserWindow`](browser-window.md) 的属性. + +```javascript +const BrowserWindow = require('electron').BrowserWindow; + +var win = new BrowserWindow({ width: 800, height: 600 }); +win.loadURL("http://github.com"); + +var ses = win.webContents.session; +``` + +## 方法 + +`session` 模块有如下方法: + +### session.fromPartition(partition) + +* `partition` String + +从字符串 `partition` 返回一个新的 `Session` 实例. + +如果 `partition` 以 `persist:` 开头,那么这个page将使用一个持久的 session,这个 session 将对应用的所有 page 可用.如果没前缀,这个 page 将使用一个历史 session.如果 `partition` 为空,那么将返回应用的默认 session . + +## 属性 + +`session` 模块有如下属性: + +### session.defaultSession + +返回应用的默认 session 对象. + +## Class: Session + +可以在 `session` 模块中创建一个 `Session` 对象 : + +```javascript +const session = require('electron').session; + +var ses = session.fromPartition('persist:name'); +``` + +### 实例事件 + +实例 `Session` 有以下事件: + +#### Event: 'will-download' + +* `event` Event +* `item` [DownloadItem](download-item.md) +* `webContents` [WebContents](web-contents.md) + +当 Electron 将要从 `webContents` 下载 `item` 时触发. + +调用 `event.preventDefault()` 可以取消下载,并且在进程的下个 tick中,这个 `item` 也不可用. + +```javascript +session.defaultSession.on('will-download', function(event, item, webContents) { + event.preventDefault(); + require('request')(item.getURL(), function(data) { + require('fs').writeFileSync('/somewhere', data); + }); +}); +``` + +### 实例方法 + +实例 `Session` 有以下方法: + +#### `ses.cookies` + +`cookies` 赋予你全力来查询和修改 cookies. 例如: + +```javascript +// 查询所有 cookies. +session.defaultSession.cookies.get({}, function(error, cookies) { + console.log(cookies); +}); + +// 查询与指定 url 相关的所有 cookies. +session.defaultSession.cookies.get({ url : "http://www.github.com" }, function(error, cookies) { + console.log(cookies); +}); + +// 设置 cookie; +// may overwrite equivalent cookies if they exist. +var cookie = { url : "http://www.github.com", name : "dummy_name", value : "dummy" }; +session.defaultSession.cookies.set(cookie, function(error) { + if (error) + console.error(error); +}); +``` + +#### `ses.cookies.get(filter, callback)` + +* `filter` Object + * `url` String (可选) - 与获取 cookies 相关的 + `url`.不设置的话就是从所有 url 获取 cookies . + * `name` String (可选) - 通过 name 过滤 cookies. + * `domain` String (可选) - 获取对应域名或子域名的 cookies . + * `path` String (可选) - 获取对应路径的 cookies . + * `secure` Boolean (可选) - 通过安全性过滤 cookies. + * `session` Boolean (可选) - 过滤掉 session 或 持久的 cookies. +* `callback` Function + +发送一个请求,希望获得所有匹配 `details` 的 cookies, +在完成的时候,将通过 `callback(error, cookies)` 调用 `callback`. + +`cookies`是一个 `cookie` 对象. + +* `cookie` Object + * `name` String - cookie 名. + * `value` String - cookie值. + * `domain` String - cookie域名. + * `hostOnly` String - 是否 cookie 是一个 host-only cookie. + * `path` String - cookie路径. + * `secure` Boolean - 是否是安全 cookie. + * `httpOnly` Boolean - 是否只是 HTTP cookie. + * `session` Boolean - cookie 是否是一个 session cookie 或一个带截至日期的持久 + cookie . + * `expirationDate` Double (可选) - cookie的截至日期,数值为UNIX纪元以来的秒数. 对session cookies 不提供. + +#### `ses.cookies.set(details, callback)` + +* `details` Object + * `url` String - 与获取 cookies 相关的 + `url`. + * `name` String - cookie 名. 忽略默认为空. + * `value` String - cookie 值. 忽略默认为空. + * `domain` String - cookie的域名. 忽略默认为空. + * `path` String - cookie 的路径. 忽略默认为空. + * `secure` Boolean - 是否已经进行了安全性标识. 默认为 + false. + * `session` Boolean - 是否已经 HttpOnly 标识. 默认为 false. + * `expirationDate` Double - cookie的截至日期,数值为UNIX纪元以来的秒数. 如果忽略, cookie 变为 session cookie. +* `callback` Function + +使用 `details` 设置 cookie, 完成时使用 `callback(error)` 掉哟个 `callback` . + +#### `ses.cookies.remove(url, name, callback)` + +* `url` String - 与 cookies 相关的 + `url`. +* `name` String - 需要删除的 cookie 名. +* `callback` Function + +删除匹配 `url` 和 `name` 的 cookie, 完成时使用 `callback()`调用`callback`. + +#### `ses.getCacheSize(callback)` + +* `callback` Function + * `size` Integer - 单位 bytes 的缓存 size. + +返回 session 的当前缓存 size . + +#### `ses.clearCache(callback)` + +* `callback` Function - 操作完成时调用 + +清空 session 的 HTTP 缓存. + +#### `ses.clearStorageData([options, ]callback)` + +* `options` Object (可选) + * `origin` String - 应当遵循 `window.location.origin` 的格式 + `scheme://host:port`. + * `storages` Array - 需要清理的 storages 类型, 可以包含 : + `appcache`, `cookies`, `filesystem`, `indexdb`, `local storage`, + `shadercache`, `websql`, `serviceworkers` + * `quotas` Array - 需要清理的类型指标, 可以包含: + `temporary`, `persistent`, `syncable`. +* `callback` Function - 操作完成时调用. + +清除 web storages 的数据. + +#### `ses.flushStorageData()` + +将没有写入的 DOMStorage 写入磁盘. + +#### `ses.setProxy(config, callback)` + +* `config` Object + * `pacScript` String - 与 PAC 文件相关的 URL. + * `proxyRules` String - 代理使用规则. +* `callback` Function - 操作完成时调用. + +设置 proxy settings. + +当 `pacScript` 和 `proxyRules` 一同提供时,将忽略 `proxyRules`,并且使用 `pacScript` 配置 . + +`proxyRules` 需要遵循下面的规则: + +``` +proxyRules = schemeProxies[";"] +schemeProxies = ["="] +urlScheme = "http" | "https" | "ftp" | "socks" +proxyURIList = [","] +proxyURL = ["://"][":"] +``` + +例子: + +* `http=foopy:80;ftp=foopy2` - 为 `http://` URL 使用 HTTP 代理 `foopy:80` , 和为 `ftp://` URL + HTTP 代理 `foopy2:80` . +* `foopy:80` - 为所有 URL 使用 HTTP 代理 `foopy:80` . +* `foopy:80,bar,direct://` - 为所有 URL 使用 HTTP 代理 `foopy:80` , 如果 `foopy:80` 不可用,则切换使用 `bar`, 再往后就不使用代理了. +* `socks4://foopy` - 为所有 URL 使用 SOCKS v4 代理 `foopy:1080`. +* `http=foopy,socks5://bar.com` - 为所有 URL 使用 HTTP 代理 `foopy`, 如果 `foopy`不可用,则切换到 SOCKS5 代理 `bar.com`. +* `http=foopy,direct://` - 为所有http url 使用 HTTP 代理,如果 `foopy`不可用,则不使用代理. +* `http=foopy;socks=foopy2` - 为所有http url 使用 `foopy` 代理,为所有其他 url 使用 `socks4://foopy2` 代理. + +### `ses.resolveProxy(url, callback)` + +* `url` URL +* `callback` Function + +解析 `url` 的代理信息.当请求完成的时候使用 `callback(proxy)` 调用 `callback`. + +#### `ses.setDownloadPath(path)` + +* `path` String - 下载地址 + +设置下载保存地址,默认保存地址为各自 app 应用的 `Downloads`目录. + +#### `ses.enableNetworkEmulation(options)` + +* `options` Object + * `offline` Boolean - 是否模拟网络故障. + * `latency` Double - 每毫秒的 RTT + * `downloadThroughput` Double - 每 Bps 的下载速率. + * `uploadThroughput` Double - 每 Bps 的上载速率. + +通过给定配置的 `session` 来模拟网络. + +```javascript +// 模拟 GPRS 连接,使用的 50kbps 流量,500 毫秒的 rtt. +window.webContents.session.enableNetworkEmulation({ + latency: 500, + downloadThroughput: 6400, + uploadThroughput: 6400 +}); + +// 模拟网络故障. +window.webContents.session.enableNetworkEmulation({offline: true}); +``` + +#### `ses.disableNetworkEmulation()` + +停止所有已经使用 `session` 的活跃模拟网络. +重置为原始网络类型. + +#### `ses.setCertificateVerifyProc(proc)` + +* `proc` Function + +为 `session` 设置证书验证过程,当请求一个服务器的证书验证时,使用 `proc(hostname, certificate, callback)` 调用 `proc`.调用 `callback(true)` 来接收证书,调用 `callback(false)` 来拒绝验证证书. + +调用了 `setCertificateVerifyProc(null)` ,则将会回复到默认证书验证过程. + +```javascript +myWindow.webContents.session.setCertificateVerifyProc(function(hostname, cert, callback) { + if (hostname == 'github.com') + callback(true); + else + callback(false); +}); +``` + +#### `ses.setPermissionRequestHandler(handler)` + +* `handler` Function + * `webContents` Object - [WebContents](web-contents.md) 请求许可. + * `permission` String - 枚举了 'media', 'geolocation', 'notifications', 'midiSysex', 'pointerLock', 'fullscreen'. + * `callback` Function - 允许或禁止许可. + +为对应 `session` 许可请求设置响应句柄.调用 `callback(true)` 接收许可,调用 `callback(false)` 禁止许可. + +```javascript +session.fromPartition(partition).setPermissionRequestHandler(function(webContents, permission, callback) { + if (webContents.getURL() === host) { + if (permission == "notifications") { + callback(false); // denied. + return; + } + } + + callback(true); +}); +``` + +#### `ses.clearHostResolverCache([callback])` + +* `callback` Function (可选) - 操作结束调用. + +清除主机解析缓存. + +#### `ses.webRequest` + +在其生命周期的不同阶段,`webRequest` API 设置允许拦截并修改请求内容. + +每个 API 接收一可选的 `filter` 和 `listener`,当 API 事件发生的时候使用 `listener(details)` 调用 `listener`,`details` 是一个用来描述请求的对象.为 `listener` 使用 `null` 则会退定事件. + +`filter` 是一个拥有 `urls` 属性的对象,这是一个 url 模式数组,这用来过滤掉不匹配指定 url 模式的请求.如果忽略 `filter` ,那么所有请求都将可以成功匹配. + +所有事件的 `listener` 都有一个回调事件,当 `listener` 完成它的工作的时候,它将使用一个 `response` 对象来调用. + +```javascript +// 将所有请求的代理都修改为下列 url. +var filter = { + urls: ["https://*.github.com/*", "*://electron.github.io"] +}; + +session.defaultSession.webRequest.onBeforeSendHeaders(filter, function(details, callback) { + details.requestHeaders['User-Agent'] = "MyAgent"; + callback({cancel: false, requestHeaders: details.requestHeaders}); +}); +``` + +#### `ses.webRequest.onBeforeRequest([filter, ]listener)` + +* `filter` Object +* `listener` Function + +当一个请求即将开始的时候,使用 `listener(details, callback)` 调用 `listener`. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `uploadData` Array (可选) +* `callback` Function + +`uploadData` 是一个 `data` 数组对象: + +* `data` Object + * `bytes` Buffer - 被发送的内容. + * `file` String - 上载文件路径. + +`callback` 必须使用一个 `response` 对象来调用: + +* `response` Object + * `cancel` Boolean (可选) + * `redirectURL` String (可选) - 原始请求阻止发送或完成,而不是重定向. + +#### `ses.webRequest.onBeforeSendHeaders([filter, ]listener)` + +* `filter` Object +* `listener` Function + +一旦请求报文头可用了,在发送 HTTP 请求的之前,使用 `listener(details, callback)` 调用 `listener`.这也许会在服务器发起一个tcp 连接,但是在发送任何 http 数据之前发生. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `requestHeaders` Object +* `callback` Function + +必须使用一个 `response` 对象来调用 `callback` : + +* `response` Object + * `cancel` Boolean (可选) + * `requestHeaders` Object (可选) - 如果提供了,将使用这些 headers 来创建请求. + +#### `ses.webRequest.onSendHeaders([filter, ]listener)` + +* `filter` Object +* `listener` Function + +在一个请求正在发送到服务器的时候,使用 `listener(details)` 来调用 `listener` ,之前 `onBeforeSendHeaders` 修改部分响应可用,同时取消监听. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `requestHeaders` Object + +#### `ses.webRequest.onHeadersReceived([filter,] listener)` + +* `filter` Object +* `listener` Function + +当 HTTP 请求报文头已经到达的时候,使用 `listener(details, callback)` 调用 `listener` . + +* `details` Object + * `id` String + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `statusLine` String + * `statusCode` Integer + * `responseHeaders` Object +* `callback` Function + +必须使用一个 `response` 对象来调用 `callback` : + +* `response` Object + * `cancel` Boolean + * `responseHeaders` Object (可选) - 如果提供, 服务器将假定使用这些头来响应. + +#### `ses.webRequest.onResponseStarted([filter, ]listener)` + +* `filter` Object +* `listener` Function + +当响应body的首字节到达的时候,使用 `listener(details)` 调用 `listener`.对 http 请求来说,这意味着状态线和响应头可用了. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `responseHeaders` Object + * `fromCache` Boolean - 标识响应是否来自磁盘 + cache. + * `statusCode` Integer + * `statusLine` String + +#### `ses.webRequest.onBeforeRedirect([filter, ]listener)` + +* `filter` Object +* `listener` Function + +当服务器的重定向初始化正要启动时,使用 `listener(details)` 调用 `listener`. + +* `details` Object + * `id` String + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `redirectURL` String + * `statusCode` Integer + * `ip` String (可选) - 请求的真实服务器ip 地址 + * `fromCache` Boolean + * `responseHeaders` Object + +#### `ses.webRequest.onCompleted([filter, ]listener)` + +* `filter` Object +* `listener` Function + +当请求完成的时候,使用 `listener(details)` 调用 `listener`. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `responseHeaders` Object + * `fromCache` Boolean + * `statusCode` Integer + * `statusLine` String + +#### `ses.webRequest.onErrorOccurred([filter, ]listener)` + +* `filter` Object +* `listener` Function + +当一个错误发生的时候,使用 `listener(details)` 调用 `listener`. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `fromCache` Boolean + * `error` String - 错误描述. \ No newline at end of file diff --git a/docs-translations/zh-CN/api/synopsis.md b/docs-translations/zh-CN/api/synopsis.md new file mode 100644 index 000000000000..014d5893c73a --- /dev/null +++ b/docs-translations/zh-CN/api/synopsis.md @@ -0,0 +1,71 @@ +# 简介 + +所有的[Node.js's built-in modules][1]在Electron中都可用,并且所有的node的第三方组件也可以放心使用(包括[自身的模块][2])。 + +Electron也提供了一些额外的内置组件来开发传统桌面应用。一些组件只可以在主进程中使用,一些只可以在渲染进程中使用,但是也有部分可以在这2种进程中都可使用。 + +基本规则:GUI模块或者系统底层的模块只可以在主进程中使用。要使用这些模块,你应当很熟悉[主进程vs渲染进程][3]脚本的概念。 + +主进程脚本看起来像个普通的nodejs脚本 + +```javascript +const electron = require('electron'); +const app = electron.app; +const BrowserWindow = electron.BrowserWindow; + +var window = null; + +app.on('ready', function() { + window = new BrowserWindow({width: 800, height: 600}); + window.loadURL('https://github.com'); +}); +``` + +渲染进程和传统的web界面一样,除了它具有使用node模块的能力: + +```html + + + + + + +``` + +如果想运行应用,参考 `Run your app` 。 + +## 解构任务 + +如果你使用的是CoffeeScript或Babel,你可以使用[destructuring assignment][4]来让使用内置模块更简单: + +```javascript +const {app, BrowserWindow} = require('electron'); +``` + +然而如果你使用的是普通的JavaScript,你就需要等到Chrome支持ES6了。 + +##使用内置模块时禁用旧样式 + +在版本v0.35.0之前,所有的内置模块都需要按造 `require('module-name')` 形式来使用,虽然它有很多[弊端][5],我们仍然在老的应用中友好的支持它。 + +为了完整的禁用旧样式,你可以设置环境变量 `ELECTRON_HIDE_INTERNAL_MODULES ` : + +```javascript +process.env.ELECTRON_HIDE_INTERNAL_MODULES = 'true' +``` + +或者调用 `hideInternalModules` API: + +```javascript +require('electron').hideInternalModules() +``` + + + [1]:http://nodejs.org/api/ + [2]:https://github.com/heyunjiang/electron/blob/master/docs/tutorial/using-native-node-modules.md + [3]:https://github.com/heyunjiang/electron/blob/master/docs/tutorial/quick-start.md#the-main-process + [4]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment + [5]:https://github.com/electron/electron/issues/387 \ No newline at end of file diff --git a/docs-translations/zh-CN/api/tray.md b/docs-translations/zh-CN/api/tray.md new file mode 100644 index 000000000000..d5647871ac77 --- /dev/null +++ b/docs-translations/zh-CN/api/tray.md @@ -0,0 +1,205 @@ +# Tray + +用一个 `Tray` 来表示一个图标,这个图标处于正在运行的系统的通知区 ,通常被添加到一个 context menu 上. + +```javascript +const electron = require('electron'); +const app = electron.app; +const Menu = electron.Menu; +const Tray = electron.Tray; + +var appIcon = null; +app.on('ready', function(){ + appIcon = new Tray('/path/to/my/icon'); + var contextMenu = Menu.buildFromTemplate([ + { label: 'Item1', type: 'radio' }, + { label: 'Item2', type: 'radio' }, + { label: 'Item3', type: 'radio', checked: true }, + { label: 'Item4', type: 'radio' } + ]); + appIcon.setToolTip('This is my application.'); + appIcon.setContextMenu(contextMenu); +}); + +``` + +__平台限制:__ + +* 在 Linux, 如果支持应用指示器则使用它,否则使用 `GtkStatusIcon` 代替. +* 在 Linux ,配置了只有有了应用指示器的支持, 你必须安装 `libappindicator1` 来让 tray icon 执行. +* 应用指示器只有在它拥有 context menu 时才会显示. +* 当在linux 上使用了应用指示器,将忽略点击事件. +* 在 Linux,为了让单独的 `MenuItem` 起效,需要再次调用 `setContextMenu` .例如: + +```javascript +contextMenu.items[2].checked = false; +appIcon.setContextMenu(contextMenu); +``` +如果想在所有平台保持完全相同的行为,不应该依赖点击事件,而是一直将一个 context menu 添加到 tray icon. + +## Class: Tray + +`Tray` 是一个 [事件发出者][event-emitter]. + +### `new Tray(image)` + +* `image` [NativeImage](native-image.md) + +创建一个与 `image` 相关的 icon. + +## 事件 + +`Tray` 模块可发出下列事件: + +**注意:** 一些事件只能在特定的os中运行,已经标明. + +### Event: 'click' + +* `event` Event + * `altKey` Boolean + * `shiftKey` Boolean + * `ctrlKey` Boolean + * `metaKey` Boolean +* `bounds` Object - tray icon 的 bounds. + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer + +当tray icon被点击的时候发出事件. + +__注意:__ `bounds` 只在 OS X 和 Windows 上起效. + +### Event: 'right-click' _OS X_ _Windows_ + +* `event` Event + * `altKey` Boolean + * `shiftKey` Boolean + * `ctrlKey` Boolean + * `metaKey` Boolean +* `bounds` Object - tray icon 的 bounds. + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer + +当tray icon被鼠标右键点击的时候发出事件. + +### Event: 'double-click' _OS X_ _Windows_ + +* `event` Event + * `altKey` Boolean + * `shiftKey` Boolean + * `ctrlKey` Boolean + * `metaKey` Boolean +* `bounds` Object - tray icon 的 bounds. + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer + +当tray icon被双击的时候发出事件. + +### Event: 'balloon-show' _Windows_ + +当tray 气泡显示的时候发出事件. + +### Event: 'balloon-click' _Windows_ + +当tray 气泡被点击的时候发出事件. + +### Event: 'balloon-closed' _Windows_ + +当tray 气泡关闭的时候发出事件,因为超时或人为关闭. + +### Event: 'drop' _OS X_ + +当tray icon上的任何可拖动项被删除的时候发出事件. + +### Event: 'drop-files' _OS X_ + +* `event` +* `files` Array - 已删除文件的路径. + +当tray icon上的可拖动文件被删除的时候发出事件. + +### Event: 'drag-enter' _OS X_ + +当一个拖动操作进入tray icon的时候发出事件. + +### Event: 'drag-leave' _OS X_ + +当一个拖动操作离开tray icon的时候发出事件. +Emitted when a drag operation exits the tray icon. + +### Event: 'drag-end' _OS X_ + +当一个拖动操作在tray icon上或其它地方停止拖动的时候发出事件. + +## 方法 + +`Tray` 模块有以下方法: + +**Note:** 一些方法只能在特定的os中运行,已经标明. + +### `Tray.destroy()` + +立刻删除 tray icon. + +### `Tray.setImage(image)` + +* `image` [NativeImage](native-image.md) + +让 `image` 与 tray icon 关联起来. + +### `Tray.setPressedImage(image)` _OS X_ + +* `image` [NativeImage](native-image.md) + +当在 OS X 上按压 tray icon 的时候, 让 `image` 与 tray icon 关联起来. + +### `Tray.setToolTip(toolTip)` + +* `toolTip` String + +为 tray icon 设置 hover text. + +### `Tray.setTitle(title)` _OS X_ + +* `title` String + +在状态栏沿着 tray icon 设置标题. + +### `Tray.setHighlightMode(highlight)` _OS X_ + +* `highlight` Boolean + +当 tray icon 被点击的时候,是否设置它的背景色变为高亮(blue).默认为 true. + +### `Tray.displayBalloon(options)` _Windows_ + +* `options` Object + * `icon` [NativeImage](native-image.md) + * `title` String + * `content` String + +展示一个 tray balloon. + +### `Tray.popUpContextMenu([menu, position])` _OS X_ _Windows_ + +* `menu` Menu (optional) +* `position` Object (可选) - 上托位置. + * `x` Integer + * `y` Integer + +从 tray icon 上托出 context menu . 当划过 `menu` 的时候, `menu` 显示,代替 tray 的 context menu . + +`position` 只在 windows 上可用,默认为 (0, 0) . + +### `Tray.setContextMenu(menu)` + +* `menu` Menu + +为这个 icon 设置 context menu . + +[event-emitter]: http://nodejs.org/api/events.html#events_class_events_eventemitter \ No newline at end of file diff --git a/docs-translations/zh-CN/api/web-contents.md b/docs-translations/zh-CN/api/web-contents.md new file mode 100644 index 000000000000..54b9c56dcc6c --- /dev/null +++ b/docs-translations/zh-CN/api/web-contents.md @@ -0,0 +1,864 @@ +# webContents + +`webContents` 是一个 +[事件发出者](http://nodejs.org/api/events.html#events_class_events_eventemitter). + +它负责渲染并控制网页,也是 [`BrowserWindow`](browser-window.md) 对象的属性.一个使用 `webContents` 的例子: + +```javascript +const BrowserWindow = require('electron').BrowserWindow; + +var win = new BrowserWindow({width: 800, height: 1500}); +win.loadURL("http://github.com"); + +var webContents = win.webContents; +``` + +## 事件 + +`webContents` 对象可发出下列事件: + +### Event: 'did-finish-load' + +当导航完成时发出事件,`onload` 事件也完成. + +### Event: 'did-fail-load' + +返回: + +* `event` Event +* `errorCode` Integer +* `errorDescription` String +* `validatedURL` String +* `isMainFrame` Boolean + +这个事件类似 `did-finish-load` ,但是是在加载失败或取消加载时发出, 例如, `window.stop()` 请求结束.错误代码的完整列表和它们的含义都可以在 [here](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h) 找到. + +### Event: 'did-frame-finish-load' + +返回: + +* `event` Event +* `isMainFrame` Boolean + +当一个 frame 导航完成的时候发出事件. + +### Event: 'did-start-loading' + +当 tab 的spinner 开始 spinning的时候. + +### Event: 'did-stop-loading' + +当 tab 的spinner 结束 spinning的时候. + +### Event: 'did-get-response-details' + +返回: + +* `event` Event +* `status` Boolean +* `newURL` String +* `originalURL` String +* `httpResponseCode` Integer +* `requestMethod` String +* `referrer` String +* `headers` Object +* `resourceType` String + +当有关请求资源的详细信息可用的时候发出事件. +`status` 标识了 socket链接来下载资源. + +### Event: 'did-get-redirect-request' + +返回: + +* `event` Event +* `oldURL` String +* `newURL` String +* `isMainFrame` Boolean +* `httpResponseCode` Integer +* `requestMethod` String +* `referrer` String +* `headers` Object + +当在请求资源时收到重定向的时候发出事件. + +### Event: 'dom-ready' + +返回: + +* `event` Event + +当指定 frame 中的 文档加载完成的时候发出事件. + +### Event: 'page-favicon-updated' + +返回: + +* `event` Event +* `favicons` Array - Array of URLs + +当 page 收到图标 url 的时候发出事件. + +### Event: 'new-window' + +返回: + +* `event` Event +* `url` String +* `frameName` String +* `disposition` String - 可为 `default`, `foreground-tab`, `background-tab`, + `new-window` 和 `other`. +* `options` Object - 创建新的 `BrowserWindow`时使用的参数. + +当 page 请求打开指定 url 窗口的时候发出事件.这可以是通过 `window.open` 或一个外部连接如 `` 发出的请求. + +默认指定 `url` 的 `BrowserWindow` 会被创建. + +调用 `event.preventDefault()` 可以用来阻止打开窗口. + +### Event: 'will-navigate' + +返回: + +* `event` Event +* `url` String + +当用户或 page 想要开始导航的时候发出事件.它可在当 `window.location` 对象改变或用户点击 page 中的链接的时候发生. + +当使用 api(如 `webContents.loadURL` 和 `webContents.back`) 以编程方式来启动导航的时候,这个事件将不会发出. + +它也不会在页内跳转发生, 例如点击锚链接或更新 `window.location.hash`.使用 `did-navigate-in-page` 事件可以达到目的. + +调用 `event.preventDefault()` 可以阻止导航. + +### Event: 'did-navigate' + +返回: + +* `event` Event +* `url` String + +当一个导航结束时候发出事件. + +页内跳转时不会发出这个事件,例如点击锚链接或更新 `window.location.hash`.使用 `did-navigate-in-page` 事件可以达到目的. + +### Event: 'did-navigate-in-page' + +返回: + +* `event` Event +* `url` String + +当页内导航发生的时候发出事件. + +当页内导航发生的时候,page 的url 改变,但是不会跳出界面.例如当点击锚链接时或者 DOM 的 `hashchange` 事件发生. + +### Event: 'crashed' + +当渲染进程崩溃的时候发出事件. + +### Event: 'plugin-crashed' + +返回: + +* `event` Event +* `name` String +* `version` String + +当插件进程崩溃时候发出事件. + +### Event: 'destroyed' + +当 `webContents` 被删除的时候发出事件. + +### Event: 'devtools-opened' + +当开发者工具栏打开的时候发出事件. + +### Event: 'devtools-closed' + +当开发者工具栏关闭时候发出事件. + +### Event: 'devtools-focused' + +当开发者工具栏获得焦点或打开的时候发出事件. + +### Event: 'certificate-error' + +返回: + +* `event` Event +* `url` URL +* `error` String - The error code +* `certificate` Object + * `data` Buffer - PEM encoded data + * `issuerName` String +* `callback` Function + +当验证证书或 `url` 失败的时候发出事件. + +使用方法类似 [`app` 的 `certificate-error` 事件](app.md#event-certificate-error). + +### Event: 'select-client-certificate' + +返回: + +* `event` Event +* `url` URL +* `certificateList` [Objects] + * `data` Buffer - PEM encoded data + * `issuerName` String - Issuer's Common Name +* `callback` Function + +当请求客户端证书的时候发出事件. + +使用方法类似 [`app` 的 `select-client-certificate` 事件](app.md#event-select-client-certificate). + +### Event: 'login' + +返回: + +* `event` Event +* `request` Object + * `method` String + * `url` URL + * `referrer` URL +* `authInfo` Object + * `isProxy` Boolean + * `scheme` String + * `host` String + * `port` Integer + * `realm` String +* `callback` Function + +当 `webContents` 想做基本验证的时候发出事件. + +使用方法类似 [the `login` event of `app`](app.md#event-login). + +### Event: 'found-in-page' + +返回: + +* `event` Event +* `result` Object + * `requestId` Integer + * `finalUpdate` Boolean - 标识是否还有更多的值可以查看. + * `activeMatchOrdinal` Integer (可选) - 活动匹配位置 + * `matches` Integer (可选) - 匹配数量. + * `selectionArea` Object (可选) - 协调首个匹配位置. + +当使用 [`webContents.findInPage`](web-contents.md#webcontentsfindinpage) 进行页内查找并且找到可用值得时候发出事件. + +### Event: 'media-started-playing' + +当媒体开始播放的时候发出事件. + +### Event: 'media-paused' + +当媒体停止播放的时候发出事件. + +### Event: 'did-change-theme-color' + +当page 的主题色时候发出事件.这通常由于引入了一个 meta 标签 : + +```html + +``` + +### Event: 'cursor-changed' + +返回: + +* `event` Event +* `type` String +* `image` NativeImage (可选) +* `scale` Float (可选) + +当鼠标的类型发生改变的时候发出事件. `type` 的参数可以是 `default`, +`crosshair`, `pointer`, `text`, `wait`, `help`, `e-resize`, `n-resize`, +`ne-resize`, `nw-resize`, `s-resize`, `se-resize`, `sw-resize`, `w-resize`, +`ns-resize`, `ew-resize`, `nesw-resize`, `nwse-resize`, `col-resize`, +`row-resize`, `m-panning`, `e-panning`, `n-panning`, `ne-panning`, `nw-panning`, +`s-panning`, `se-panning`, `sw-panning`, `w-panning`, `move`, `vertical-text`, +`cell`, `context-menu`, `alias`, `progress`, `nodrop`, `copy`, `none`, +`not-allowed`, `zoom-in`, `zoom-out`, `grab`, `grabbing`, `custom`. + +如果 `type` 参数值为 `custom`, `image` 参数会在一个`NativeImage` 中控制自定义鼠标图片, 并且 `scale` 会控制图片的缩放比例. + +## 实例方法 + +`webContents` 对象有如下的实例方法: + +### `webContents.loadURL(url[, options])` + +* `url` URL +* `options` Object (可选) + * `httpReferrer` String - A HTTP Referrer url. + * `userAgent` String - 产生请求的用户代理 + * `extraHeaders` String - 以 "\n" 分隔的额外头 + +在窗口中加载 `url` , `url` 必须包含协议前缀, +比如 `http://` 或 `file://`. 如果加载想要忽略 http 缓存,可以使用 `pragma` 头来达到目的. + +```javascript +const options = {"extraHeaders" : "pragma: no-cache\n"} +webContents.loadURL(url, options) +``` + +### `webContents.downloadURL(url)` + +* `url` URL + +初始化一个指定 `url` 的资源下载,不导航跳转. `session` 的 `will-download` 事件会触发. + +### `webContents.getURL()` + +返回当前page 的 url. + +```javascript +var win = new BrowserWindow({width: 800, height: 600}); +win.loadURL("http://github.com"); + +var currentURL = win.webContents.getURL(); +``` + +### `webContents.getTitle()` + +返回当前page 的 标题. + +### `webContents.isLoading()` + +返回一个布尔值,标识当前页是否正在加载. + +### `webContents.isWaitingForResponse()` + +返回一个布尔值,标识当前页是否正在等待主要资源的第一次响应. + +### `webContents.stop()` + +停止还为开始的导航. + +### `webContents.reload()` + +重载当前页. + +### `webContents.reloadIgnoringCache()` + +重载当前页,忽略缓存. + +### `webContents.canGoBack()` + +返回一个布尔值,标识浏览器是否能回到前一个page. + +### `webContents.canGoForward()` + +返回一个布尔值,标识浏览器是否能前往下一个page. + +### `webContents.canGoToOffset(offset)` + +* `offset` Integer + +返回一个布尔值,标识浏览器是否能前往指定 `offset` 的page. + +### `webContents.clearHistory()` + +清除导航历史. + +### `webContents.goBack()` + +让浏览器回退到前一个page. + +### `webContents.goForward()` + +让浏览器回前往下一个page. + +### `webContents.goToIndex(index)` + +* `index` Integer + +让浏览器回前往指定 `index` 的page. + +### `webContents.goToOffset(offset)` + +* `offset` Integer + +导航到相对于当前页的偏移位置页. + +### `webContents.isCrashed()` + +渲染进程是否崩溃. + +### `webContents.setUserAgent(userAgent)` + +* `userAgent` String + +重写本页用户代理. + +### `webContents.getUserAgent()` + +返回一个 `String` ,标识本页用户代理信息. + +### `webContents.insertCSS(css)` + +* `css` String + +为当前页插入css. + +### `webContents.executeJavaScript(code[, userGesture, callback])` + +* `code` String +* `userGesture` Boolean (可选) +* `callback` Function (可选) - 脚本执行完成后调用的回调函数. + * `result` + +评估 page `代码`. + +浏览器窗口中的一些 HTML API ,例如 `requestFullScreen`,只能被用户手势请求.设置 `userGesture` 为 `true` 可以取消这个限制. + +### `webContents.setAudioMuted(muted)` + +* `muted` Boolean + +减缓当前也的 audio 的播放速度. + +### `webContents.isAudioMuted()` + +返回一个布尔值,标识当前页是否减缓了 audio 的播放速度. + +### `webContents.undo()` + +执行网页的编辑命令 `undo` . + +### `webContents.redo()` + +执行网页的编辑命令 `redo` . + +### `webContents.cut()` + +执行网页的编辑命令 `cut` . + +### `webContents.copy()` + +执行网页的编辑命令 `copy` . + +### `webContents.paste()` + +执行网页的编辑命令 `paste` . + +### `webContents.pasteAndMatchStyle()` + +执行网页的编辑命令 `pasteAndMatchStyle` . + +### `webContents.delete()` + +执行网页的编辑命令 `delete` . + +### `webContents.selectAll()` + +执行网页的编辑命令 `selectAll` . + +### `webContents.unselect()` + +执行网页的编辑命令 `unselect` . + +### `webContents.replace(text)` + +* `text` String + +执行网页的编辑命令 `replace` . + +### `webContents.replaceMisspelling(text)` + +* `text` String + +执行网页的编辑命令 `replaceMisspelling` . + +### `webContents.insertText(text)` + +* `text` String + +插入 `text` 到获得了焦点的元素. + +### `webContents.findInPage(text[, options])` + +* `text` String - 查找内容, 不能为空. +* `options` Object (可选) + * `forward` Boolean - 是否向前或向后查找, 默认为 `true`. + * `findNext` Boolean - 当前操作是否是第一次查找或下一次查找, + 默认为 `false`. + * `matchCase` Boolean - 查找是否区分大小写, + 默认为 `false`. + * `wordStart` Boolean -是否仅以首字母查找. + 默认为 `false`. + * `medialCapitalAsWordStart` Boolean - 是否结合 `wordStart`,如果匹配是大写字母开头,后面接小写字母或无字母,那么就接受这个词中匹配.接受几个其它的合成词匹配, 默认为 `false`. + +发起请求,在网页中查找所有与 `text` 相匹配的项,并且返回一个 `Integer` 来表示这个请求用的请求Id.这个请求结果可以通过订阅 + [`found-in-page`](web-contents.md#event-found-in-page) 事件来取得. + +### `webContents.stopFindInPage(action)` + +* `action` String - 指定一个行为来接替停止 + [`webContents.findInPage`](web-contents.md#webcontentfindinpage) 请求. + * `clearSelection` - 转变为一个普通的 selection. + * `keepSelection` - 清除 selection. + * `activateSelection` - 获取焦点并点击 selection node. + +使用给定的 `action` 来为 `webContents` 停止任何 `findInPage` 请求. + +```javascript +webContents.on('found-in-page', function(event, result) { + if (result.finalUpdate) + webContents.stopFindInPage("clearSelection"); +}); + +const requestId = webContents.findInPage("api"); +``` + +### `webContents.hasServiceWorker(callback)` + +* `callback` Function + +检查是否有任何 ServiceWorker 注册了,并且返回一个布尔值,来作为 `callback`响应的标识. + +### `webContents.unregisterServiceWorker(callback)` + +* `callback` Function + +如果存在任何 ServiceWorker ,则全部注销,并且当JS承诺执行行或JS拒绝执行而失败的时候,返回一个布尔值,它标识了相应的 `callback`. + +### `webContents.print([options])` + +* `options` Object (可选) + * `silent` Boolean - 不需要请求用户的打印设置. 默认为 `false`. + * `printBackground` Boolean - 打印背景和网页图片. 默认为 `false`. + +打印窗口的网页. 当设置 `silent` 为 `false` 的时候,Electron 将使用系统默认的打印机和打印方式来打印. + +在网页中调用 `window.print()` 和 调用 `webContents.print({silent: false, printBackground: false})`具有相同的作用. + +**注意:** 在 Windows, 打印 API 依赖于 `pdf.dll`. 如果你的应用不使用任何的打印, 你可以安全删除 `pdf.dll` 来减少二进制文件的size. + +### `webContents.printToPDF(options, callback)` + +* `options` Object + * `marginsType` Integer - 指定使用的 margin type. 默认 margin 使用 0, 无 margin 使用 1, 最小化 margin 使用 2. + * `pageSize` String - 指定生成的PDF文件的page size. 可以是 `A3`, + `A4`, `A5`, `Legal`, `Letter` 和 `Tabloid`. + * `printBackground` Boolean - 是否打印 css 背景. + * `printSelectionOnly` Boolean - 是否只打印选中的部分. + * `landscape` Boolean - landscape 为 `true`, portrait 为 `false`. +* `callback` Function + +打印窗口的网页为 pdf ,使用 Chromium 预览打印的自定义设置. + +完成时使用 `callback(error, data)` 调用 `callback` . `data` 是一个 `Buffer` ,包含了生成的 pdf 数据. + +默认,空的 `options` 被视为 : + +```javascript +{ + marginsType: 0, + printBackground: false, + printSelectionOnly: false, + landscape: false +} +``` + +```javascript +const BrowserWindow = require('electron').BrowserWindow; +const fs = require('fs'); + +var win = new BrowserWindow({width: 800, height: 600}); +win.loadURL("http://github.com"); + +win.webContents.on("did-finish-load", function() { + // Use default printing options + win.webContents.printToPDF({}, function(error, data) { + if (error) throw error; + fs.writeFile("/tmp/print.pdf", data, function(error) { + if (error) + throw error; + console.log("Write PDF successfully."); + }) + }) +}); +``` + +### `webContents.addWorkSpace(path)` + +* `path` String + +添加指定的路径给开发者工具栏的 workspace.必须在 DevTools 创建之后使用它 : + +```javascript +mainWindow.webContents.on('devtools-opened', function() { + mainWindow.webContents.addWorkSpace(__dirname); +}); +``` + +### `webContents.removeWorkSpace(path)` + +* `path` String + +从开发者工具栏的 workspace 删除指定的路径. + +### `webContents.openDevTools([options])` + +* `options` Object (可选) + * `detach` Boolean - 在一个新窗口打开开发者工具栏 + +打开开发者工具栏. + +### `webContents.closeDevTools()` + +关闭开发者工具栏. + +### `webContents.isDevToolsOpened()` + +返回布尔值,开发者工具栏是否打开. + +### `webContents.isDevToolsFocused()` + +返回布尔值,开发者工具栏视图是否获得焦点. + +### `webContents.toggleDevTools()` + +Toggles 开发者工具. + +### `webContents.inspectElement(x, y)` + +* `x` Integer +* `y` Integer + +在 (`x`, `y`) 开始检测元素. + +### `webContents.inspectServiceWorker()` + +为 service worker 上下文打开开发者工具栏. + +### `webContents.send(channel[, arg1][, arg2][, ...])` + +* `channel` String +* `arg` (可选) + +通过 `channel` 发送异步消息给渲染进程,你也可发送任意的参数.参数应该在 JSON 内部序列化,并且此后没有函数或原形链被包括了. + +渲染进程可以通过使用 `ipcRenderer` 监听 `channel` 来处理消息. + +例子,从主进程向渲染进程发送消息 : + +```javascript +// 主进程. +var window = null; +app.on('ready', function() { + window = new BrowserWindow({width: 800, height: 600}); + window.loadURL('file://' + __dirname + '/index.html'); + window.webContents.on('did-finish-load', function() { + window.webContents.send('ping', 'whoooooooh!'); + }); +}); +``` + +```html + + + + + + +``` + +### `webContents.enableDeviceEmulation(parameters)` + +`parameters` Object, properties: + +* `screenPosition` String - 指定需要模拟的屏幕 + (默认 : `desktop`) + * `desktop` + * `mobile` +* `screenSize` Object - 设置模拟屏幕 size (screenPosition == mobile) + * `width` Integer - 设置模拟屏幕 width + * `height` Integer - 设置模拟屏幕 height +* `viewPosition` Object - 在屏幕放置 view + (screenPosition == mobile) (默认: `{x: 0, y: 0}`) + * `x` Integer - 设置偏移左上角的x轴 + * `y` Integer - 设置偏移左上角的y轴 +* `deviceScaleFactor` Integer - 设置设备比例因子 (如果为0,默认为原始屏幕比例) (默认: `0`) +* `viewSize` Object - 设置模拟视图 size (空表示不覆盖) + * `width` Integer - 设置模拟视图 width + * `height` Integer - 设置模拟视图 height +* `fitToView` Boolean - 如果有必要的话,是否把模拟视图按比例缩放来适应可用空间 (默认: `false`) +* `offset` Object - 可用空间内的模拟视图偏移 (不在适应模式) (默认: `{x: 0, y: 0}`) + * `x` Float - 设置相对左上角的x轴偏移值 + * `y` Float - 设置相对左上角的y轴偏移值 +* `scale` Float - 可用空间内的模拟视图偏移 (不在适应视图模式) (默认: `1`) + +使用给定的参数来开启设备模拟. + +### `webContents.disableDeviceEmulation()` + +使用 `webContents.enableDeviceEmulation` 关闭设备模拟. + +### `webContents.sendInputEvent(event)` + +* `event` Object + * `type` String (**必需**) - 事件类型,可以是 `mouseDown`, + `mouseUp`, `mouseEnter`, `mouseLeave`, `contextMenu`, `mouseWheel`, + `mouseMove`, `keyDown`, `keyUp`, `char`. + * `modifiers` Array - 事件的 modifiers 数组, 可以是 + include `shift`, `control`, `alt`, `meta`, `isKeypad`, `isAutoRepeat`, + `leftButtonDown`, `middleButtonDown`, `rightButtonDown`, `capsLock`, + `numLock`, `left`, `right`. + +向 page 发送一个输入 `event` . + +对键盘事件来说,`event` 对象还有如下属性 : + +* `keyCode` String (**必需**) - 特点是将作为键盘事件发送. 可用的 key codes [Accelerator](accelerator.md). + + +对鼠标事件来说,`event` 对象还有如下属性 : + +* `x` Integer (**required**) +* `y` Integer (**required**) +* `button` String - button 按下, 可以是 `left`, `middle`, `right` +* `globalX` Integer +* `globalY` Integer +* `movementX` Integer +* `movementY` Integer +* `clickCount` Integer + +对鼠标滚轮事件来说,`event` 对象还有如下属性 : + +* `deltaX` Integer +* `deltaY` Integer +* `wheelTicksX` Integer +* `wheelTicksY` Integer +* `accelerationRatioX` Integer +* `accelerationRatioY` Integer +* `hasPreciseScrollingDeltas` Boolean +* `canScroll` Boolean + +### `webContents.beginFrameSubscription(callback)` + +* `callback` Function + +开始订阅 提交 事件和捕获数据帧,当有 提交 事件时,使用 `callback(frameBuffer)` 调用 `callback`. + +`frameBuffer` 是一个包含原始像素数据的 `Buffer`,像素数据是按照 32bit BGRA 格式有效存储的,但是实际情况是取决于处理器的字节顺序的(大多数的处理器是存放小端序的,如果是在大端序的处理器上,数据是 32bit ARGB 格式). + +### `webContents.endFrameSubscription()` + +停止订阅帧提交事件. + +### `webContents.savePage(fullPath, saveType, callback)` + +* `fullPath` String - 文件的完整路径. +* `saveType` String - 指定保存类型. + * `HTMLOnly` - 只保存html. + * `HTMLComplete` - 保存整个 page 内容. + * `MHTML` - 保存完整的 html 为 MHTML. +* `callback` Function - `function(error) {}`. + * `error` Error + +如果保存界面过程初始化成功,返回 true. + +```javascript +win.loadURL('https://github.com'); + +win.webContents.on('did-finish-load', function() { + win.webContents.savePage('/tmp/test.html', 'HTMLComplete', function(error) { + if (!error) + console.log("Save page successfully"); + }); +}); +``` + +## 实例属性 + +`WebContents` 对象也有下列属性: + +### `webContents.session` + +返回这个 `webContents` 使用的 [session](session.md) 对象. + +### `webContents.hostWebContents` + +返回这个 `webContents` 的父 `webContents` . + +### `webContents.devToolsWebContents` + +获取这个 `WebContents` 的开发者工具栏的 `WebContents` . + +**注意:** 用户不可保存这个对象,因为当开发者工具栏关闭的时候它的值为 `null` . + +### `webContents.debugger` + +调试 API 为 [remote debugging protocol][rdp] 提供交替传送. + +```javascript +try { + win.webContents.debugger.attach("1.1"); +} catch(err) { + console.log("Debugger attach failed : ", err); +}; + +win.webContents.debugger.on('detach', function(event, reason) { + console.log("Debugger detached due to : ", reason); +}); + +win.webContents.debugger.on('message', function(event, method, params) { + if (method == "Network.requestWillBeSent") { + if (params.request.url == "https://www.github.com") + win.webContents.debugger.detach(); + } +}) + +win.webContents.debugger.sendCommand("Network.enable"); +``` + +#### `webContents.debugger.attach([protocolVersion])` + +* `protocolVersion` String (可选) - 请求调试协议版本. + +添加 `webContents` 调试. + +#### `webContents.debugger.isAttached()` + +返回一个布尔值,标识是否已经给 `webContents` 添加了调试. + +#### `webContents.debugger.detach()` + +删除 `webContents` 调试. + +#### `webContents.debugger.sendCommand(method[, commandParams, callback])` + +* `method` String - 方法名, 应该是由远程调试协议定义的方法. +* `commandParams` Object (可选) - 请求参数为 JSON 对象. +* `callback` Function (可选) - Response + * `error` Object - 错误消息,标识命令失败. + * `result` Object - 回复在远程调试协议中由 'returns'属性定义的命令描述. + +发送给定命令给调试目标. + +#### Event: 'detach' + +* `event` Event +* `reason` String - 拆分调试器原因. + +在调试 session 结束时发出事件.这在 `webContents` 关闭时或 `webContents` 请求开发者工具栏时发生. + +#### Event: 'message' + +* `event` Event +* `method` String - 方法名. +* `params` Object - 在远程调试协议中由 'parameters' 属性定义的事件参数. + +每当调试目标发出事件时发出. + +[rdp]: https://developer.chrome.com/devtools/docs/debugger-protocol \ No newline at end of file diff --git a/docs-translations/zh-CN/api/web-frame.md b/docs-translations/zh-CN/api/web-frame.md new file mode 100644 index 000000000000..d278e9427131 --- /dev/null +++ b/docs-translations/zh-CN/api/web-frame.md @@ -0,0 +1,101 @@ +# webFrame + +`web-frame` 模块允许你自定义如何渲染当前网页 . + +例子,放大当前页到 200%. + +```javascript +var webFrame = require('electron').webFrame; + +webFrame.setZoomFactor(2); +``` + +## 方法 + +`web-frame` 模块有如下方法: + +### `webFrame.setZoomFactor(factor)` + +* `factor` Number - 缩放参数. + +将缩放参数修改为指定的参数值.缩放参数是百分制的,所以 300% = 3.0. + +### `webFrame.getZoomFactor()` + +返回当前缩放参数值. + +### `webFrame.setZoomLevel(level)` + +* `level` Number - 缩放水平 + +将缩放水平修改为指定的水平值. 原始 size 为 0 ,并且每次增长都表示放大 20% 或缩小 20%,默认限制为原始 size 的 300% 到 50% 之间 . + +### `webFrame.getZoomLevel()` + +返回当前缩放水平值. + +### `webFrame.setZoomLevelLimits(minimumLevel, maximumLevel)` + +* `minimumLevel` Number +* `maximumLevel` Number + +设置缩放水平的最大值和最小值. + +### `webFrame.setSpellCheckProvider(language, autoCorrectWord, provider)` + +* `language` String +* `autoCorrectWord` Boolean +* `provider` Object + +为输入框或文本域设置一个拼写检查 provider . + +`provider` 必须是一个对象,它有一个 `spellCheck` 方法,这个方法返回扫过的单词是否拼写正确 . + +例子,使用 [node-spellchecker][spellchecker] 作为一个 provider: + +```javascript +webFrame.setSpellCheckProvider("en-US", true, { + spellCheck: function(text) { + return !(require('spellchecker').isMisspelled(text)); + } +}); +``` + +### `webFrame.registerURLSchemeAsSecure(scheme)` + +* `scheme` String + +注册 `scheme` 为一个安全的 scheme. + + +安全的 schemes 不会引发混合内容 warnings.例如, `https` 和 +`data` 是安全的 schemes ,因为它们不能被活跃网络攻击而失效. + +### `webFrame.registerURLSchemeAsBypassingCSP(scheme)` + +* `scheme` String + +忽略当前网页内容的安全策略,直接从 `scheme` 加载. + +### `webFrame.registerURLSchemeAsPrivileged(scheme)` + +* `scheme` String + +通过资源的内容安全策略,注册 `scheme` 为安全的 scheme,允许注册 ServiceWorker并且支持 fetch API. + +### `webFrame.insertText(text)` + +* `text` String + +向获得焦点的原色插入内容 . + +### `webFrame.executeJavaScript(code[, userGesture])` + +* `code` String +* `userGesture` Boolean (可选) - 默认为 `false`. + +评估页面代码 . + +在浏览器窗口中,一些 HTML APIs ,例如 `requestFullScreen`,只可以通过用户手势来使用.设置`userGesture` 为 `true` 可以突破这个限制 . + +[spellchecker]: https://github.com/atom/node-spellchecker \ No newline at end of file diff --git a/docs-translations/zh-CN/api/web-view-tag.md b/docs-translations/zh-CN/api/web-view-tag.md new file mode 100644 index 000000000000..a1348c4d5882 --- /dev/null +++ b/docs-translations/zh-CN/api/web-view-tag.md @@ -0,0 +1,692 @@ +# `` 标签 + +使用 `webview` 标签来把 'guest' 内容(例如 web pages )嵌入到你的 Electron app 中. guest内容包含在 `webview` 容器中.一个嵌入你应用的page控制着guest内容如何布局摆放和表达含义. + +与 `iframe` 不同, `webview` 和你的应用运行的是不同的进程. 它不拥有渲染进程的权限,并且应用和嵌入内容之间的交互全部都是异步的.因为这能保证应用的安全性不受嵌入内容的影响. + +## 例子 + +把一个 web page 嵌入到你的app,首先添加 `webview` 标签到你的app待嵌入page(展示 guest content). 在一个最简单的 `webview` 中,它包含了 web page 的文件路径和一个控制 `webview` 容器展示效果的css样式: + +```html + +``` + +如果想随时控制 guest 内容,可以添加 JavaScript 脚本来监听 `webview` 事件使用 `webview` 方法来做出响应. 这里是2个事件监听的例子:一个监听 web page 准备加载,另一个监听 web page 停止加载,并且在加载的时候显示一条 "loading..." 信息: + +```html + +``` + +## 标签属性 + +`webview` 标签有下面一些属性 : + +### `src` + +```html + +``` + +将一个可用的url做为这个属性的初始值添加到顶部导航. + +如果把当前页的src添加进去将加载当前page. + + `src`同样可以填 data urls,例如 +`data:text/plain,Hello, world!`. + +### `autosize` + +```html + +``` + +如果这个属性的值为 "on" , `webview` 容器将会根据属性`minwidth`, `minheight`, `maxwidth`, 和 +`maxheight` 的值在它们之间自适应. 只有在 `autosize` 开启的时候这个约束才会有效. 当 `autosize` 开启的时候, `webview` 容器的 size 只能在上面那四个属性值之间. + +### `nodeintegration` + +```html + +``` + +如果设置了这个属性, `webview` 中的 guest page 将整合node,并且拥有可以使用系统底层的资源,例如 `require` 和 `process` . + +### `plugins` + +```html + +``` + +如果这个属性的值为 "on" , `webview` 中的 guest page 就可以使用浏览器插件。 + +### `preload` + +```html + +``` + +在 guest page 中的其他脚本执行之前预加载一个指定的脚本。规定预加载脚本的url须如 `file:` 或者 `asar:`,因为它在是 guest page 中通过通过 `require` 命令加载的。 + +如果 guest page 没有整合 node ,这个脚本将试图使用真个 Node APIs ,但是在这个脚本执行完毕时,之前由node插入的全局对象会被删除。 + + +### `httpreferrer` + +```html + +``` + +为 guest page 设置 referrer URL。 + +### `useragent` + +```html + +``` + +在 guest page 加载之前为其设置用户代理。如果页面已经加载了,可以使用 `setUserAgent` 方法来改变用户代理。 + +### `disablewebsecurity` + +```html + +``` + +如果这个属性的值为 "on" , guest page会禁用web安全控制. + +### partition + +```html + + +``` + +为page设置session。如果初始值为 `partition` ,这个 page 将会为app中的所有 page 应用同一个持续有效的 session。如果没有 `persist:` 前缀, 这个 page 将会使用一个历史 session 。通过分配使用相同的 `partition`, 所有的page都可以分享相同的session。如果 `partition` 没有设置,那app将使用默认的session. + +这个值只能在在第一个渲染进程之前设置修改,之后修改的话会无效并且抛出一个DOM异常. + +### `allowpopups` + +```html + +``` + +如果这个属性的值为 "on" ,将允许 guest page 打开一个新窗口。 + +### `blinkfeatures` + +```html + +``` + +这个属性的值为一个用逗号分隔的列表,它的值指定特性被启用。你可以从[setFeatureEnabledFromString][blink-feature-string]函数找到完整的支持特性。 + +## 方法 + + `webview` 的方法集合: + +**注意:** webview 元素必须在使用这些方法之前加载完毕。 + +**例如** + +```javascript +webview.addEventListener("dom-ready", function() { + webview.openDevTools(); +}); +``` + +### `.loadURL(url[, options])` + +* `url` URL +* `options` Object (可选) + * `httpReferrer` String - 一个http类型的url. + * `userAgent` String -用于发起请求的用户代理. + * `extraHeaders` String - 额外的headers,用 "\n"分隔. + +加载 webview 中的 `url`,`url` 必须包含协议前缀,例如 `http://` 或 `file://`. + +### `.getURL()` + +从 guest page 中返回 url. + +### `.getTitle()` + +从 guest page 中返回 title. + +### `.isLoading()` + +返回一个 guest page 是否仍在加载资源的布尔值. + +### `.isWaitingForResponse()` + +返回一个 guest page 是否正在等待page的主要资源做出回应的布尔值. + + +### `.stop()` + +停止渲染. + +### `.reload()` + +重新加载 guest page. + +### `.reloadIgnoringCache()` + +忽视缓存,重新加载 guest page. + +### `.canGoBack()` + +返回一个 guest page 是否能够回退的布尔值. + +### `.canGoForward()` + +返回一个 guest page 是否能够前进的布尔值. + +### `.canGoToOffset(offset)` + +* `offset` Integer + +返回一个 guest page 是否能够前进到 `offset` 的布尔值. + +### `.clearHistory()` + +清除导航历史. + +### `.goBack()` + +guest page 回退. + +### `.goForward()` + +guest page 前进. + +### `.goToIndex(index)` + +* `index` Integer + +guest page 导航到指定的绝对位置. + +### `.goToOffset(offset)` + +* `offset` Integer + +guest page 导航到指定的相对位置. + +### `.isCrashed()` + +返回一个 渲染进程是否崩溃 的布尔值. + +### `.setUserAgent(userAgent)` + +* `userAgent` String + +重新设置用户代理. + +### `.getUserAgent()` + +返回用户代理名字,返回类型:`String`. + +### `.insertCSS(css)` + +* `css` String + +插入css. + +### `.executeJavaScript(code, userGesture, callback)` + +* `code` String +* `userGesture` Boolean - 默认 `false`. +* `callback` Function (可选) - 回调函数. + * `result` + +评估 `code` ,如果 `userGesture` 值为 true ,它将在这个page里面创建用户手势. HTML APIs ,如 `requestFullScreen`,它需要用户响应,那么将自动通过这个参数优化. + +### `.openDevTools()` + +为 guest page 打开开发工具调试窗口. + +### `.closeDevTools()` + +为 guest page 关闭开发工具调试窗口. + +### `.isDevToolsOpened()` + +返回一个 guest page 是否打开了开发工具调试窗口的布尔值. + +### `.isDevToolsFocused()` + +返回一个 guest page 是否聚焦了开发工具调试窗口的布尔值. + +### `.inspectElement(x, y)` + +* `x` Integer +* `y` Integer + +开始检查 guest page 在 (`x`, `y`) 位置的元素. + +### `.inspectServiceWorker()` + +在 guest page 中为服务人员打开开发工具. + +### `.setAudioMuted(muted)` + +* `muted` Boolean +设置 guest page 流畅(muted). + +### `.isAudioMuted()` + +返回一个 guest page 是否流畅的布尔值. + +### `.undo()` + +在page中编辑执行 `undo` 命令. + +### `.redo()` + +在page中编辑执行 `redo` 命令. + +### `.cut()` + +在page中编辑执行 `cut` 命令. + +### `.copy()` + +在page中编辑执行 `copy` 命令. + +### `.paste()` + +在page中编辑执行 `paste` 命令. + +### `.pasteAndMatchStyle()` + +在page中编辑执行 `pasteAndMatchStyle` 命令. + +### `.delete()` + +在page中编辑执行 `delete` 命令. + +### `.selectAll()` + +在page中编辑执行 `selectAll` 命令. + +### `.unselect()` + +在page中编辑执行 `unselect` 命令. + +### `.replace(text)` + +* `text` String + +在page中编辑执行 `replace` 命令. + +### `.replaceMisspelling(text)` + +* `text` String + +在page中编辑执行 `replaceMisspelling` 命令. + +### `.insertText(text)` + +* `text` String + +插入文本. + +### `.findInPage(text[, options])` + +* `text` String - 搜索内容,不能为空. +* `options` Object (可选) + * `forward` Boolean - 向前或向后, 默认为 `true`. + * `findNext` Boolean - 是否查找的第一个结果, + 默认为 `false`. + * `matchCase` Boolean - 是否区分大小写, + 默认为 `false`. + * `wordStart` Boolean - 是否只查找首字母. + 默认为 `false`. + * `medialCapitalAsWordStart` Boolean - 当配合 `wordStart`的时候,接受一个文字中的匹配项,要求匹配项是以大写字母开头后面跟小写字母或者没有字母。可以接受一些其他单词内部匹配, 默认为 `false`. + +发起一个请求来寻找页面中的所有匹配 `text` 的地方并且返回一个 `Integer`来表示这个请求用的请求Id. 这个请求结果可以通过订阅[`found-in-page`](web-view-tag.md#event-found-in-page) 事件来取得. + +### `.stopFindInPage(action)` + +* `action` String - 指定一个行为来接替停止 + [`.findInPage`](web-view-tag.md#webviewtagfindinpage) 请求. + * `clearSelection` - 转变为一个普通的 selection. + * `keepSelection` - 清除 selection. + * `activateSelection` - 聚焦并点击 selection node. + +使用 `action` 停止 `findInPage` 请求. + +### `.print([options])` + +打印输出 `webview` 的 web page. 类似 `webContents.print([options])`. + +### `.printToPDF(options, callback)` + +以pdf格式打印输出 `webview` 的 web page. 类似 `webContents.printToPDF(options, callback)`. + +### `.send(channel[, arg1][, arg2][, ...])` + +* `channel` String +* `arg` (可选) + +通过 `channel` 向渲染进程发出异步消息,你也可以发送任意的参数。 +渲染进程通过`ipcRenderer` 模块监听 `channel` 事件来控制消息. + +例子 +[webContents.send](web-contents.md#webcontentssendchannel-args). + +### `.sendInputEvent(event)` + +* `event` Object + +向 page 发送输入事件. + +查看 [webContents.sendInputEvent](web-contents.md##webcontentssendinputeventevent) +关于 `event` 对象的相信介绍. + +### `.getWebContents()` + +返回和这个 `webview` 相关的 [WebContents](web-contents.md). + +## DOM 事件 + +`webview` 可用下面的 DOM 事件: + +### Event: 'load-commit' + +返回: + +* `url` String +* `isMainFrame` Boolean + +加载完成触发. 这个包含当前文档的导航和副框架的文档加载,但是不包含异步资源加载. + +### Event: 'did-finish-load' + +在导航加载完成时触发,也就是tab 的 spinner停止spinning,并且加载事件处理. + +### Event: 'did-fail-load' + +Returns: + +* `errorCode` Integer +* `errorDescription` String +* `validatedURL` String + +类似 `did-finish-load` ,在加载失败或取消是触发,例如提出 `window.stop()`. + +### Event: 'did-frame-finish-load' + +返回: + +* `isMainFrame` Boolean + +当一个 frame 完成 加载时触发. + +### Event: 'did-start-loading' + +开始加载时触发. + +### Event: 'did-stop-loading' + +停止家在时触发. + +### Event: 'did-get-response-details' + +返回: + +* `status` Boolean +* `newURL` String +* `originalURL` String +* `httpResponseCode` Integer +* `requestMethod` String +* `referrer` String +* `headers` Object +* `resourceType` String + +当获得返回详情的时候触发. + +`status` 指示 socket 连接来下载资源. + +### Event: 'did-get-redirect-request' + +返回: + +* `oldURL` String +* `newURL` String +* `isMainFrame` Boolean + +当重定向请求资源被接收的时候触发. + +### Event: 'dom-ready' + +当指定的frame文档加载完毕时触发. + +### Event: 'page-title-updated' + +返回: + +* `title` String +* `explicitSet` Boolean + +当导航中的页面title被设置时触发. +在title通过文档路径异步加载时`explicitSet`为false. + +### Event: 'page-favicon-updated' + +返回: + +* `favicons` Array - Array of URLs. + +当page收到了图标url时触发. + +### Event: 'enter-html-full-screen' + +当通过HTML API使界面进入全屏时触发. + +### Event: 'leave-html-full-screen' + +当通过HTML API使界面退出全屏时触发. + +### Event: 'console-message' + +返回: + +* `level` Integer +* `message` String +* `line` Integer +* `sourceId` String + +当客户端输出控制台信息的时候触发. + +下面示例代码将所有信息输出到内置控制台,没有考虑到输出等级和其他属性。 + +```javascript +webview.addEventListener('console-message', function(e) { + console.log('Guest page logged a message:', e.message); +}); +``` + +### Event: 'found-in-page' + +返回: + +* `result` Object + * `requestId` Integer + * `finalUpdate` Boolean - 指明下面是否还有更多的回应. + * `activeMatchOrdinal` Integer (可选) - 活动匹配位置 + * `matches` Integer (optional) - 匹配数量. + * `selectionArea` Object (optional) - 整合第一个匹配域. + +在请求[`webview.findInPage`](web-view-tag.md#webviewtagfindinpage)结果有效时触发. + +```javascript +webview.addEventListener('found-in-page', function(e) { + if (e.result.finalUpdate) + webview.stopFindInPage("keepSelection"); +}); + +const rquestId = webview.findInPage("test"); +``` + +### Event: 'new-window' + +返回: + +* `url` String +* `frameName` String +* `disposition` String - 可以为 `default`, `foreground-tab`, `background-tab`, + `new-window` 和 `other`. +* `options` Object - 参数应该被用作创建新的 + `BrowserWindow`. + +在 guest page 试图打开一个新的浏览器窗口时触发. + +下面示例代码在系统默认浏览器中打开了一个新的url. + +```javascript +webview.addEventListener('new-window', function(e) { + require('electron').shell.openExternal(e.url); +}); +``` + +### Event: 'will-navigate' + +返回: + +* `url` String + +当用户或page尝试开始导航时触发. +它能在 `window.location` 变化或者用户点击连接的时候触发. + +这个事件在以 APIS 编程方式开始导航时不会触发,例如 `.loadURL` 和 `.back`. + +在页面内部导航跳转也将不回触发这个事件,例如点击锚链接或更新 `window.location.hash`.使用 `did-navigate-in-page` 来实现页内跳转事件. + +使用 `event.preventDefault()` 并不会起什么用. + +### Event: 'did-navigate' + +返回: + +* `url` String + +当导航结束时触发. + +在页面内部导航跳转也将不回触发这个事件,例如点击锚链接或更新 `window.location.hash`.使用 `did-navigate-in-page` 来实现页内跳转事件. + +### Event: 'did-navigate-in-page' + +返回: + +* `url` String + +当页内导航发生时触发. +当业内导航发生时,page url改变了,但是不会跳出 page . 例如在锚链接被电击或DOM `hashchange` 事件发生时触发. + +### Event: 'close' + +在 guest page试图关闭自己的时候触发. + +下面的示例代码指示了在客户端试图关闭自己的时候将改变导航连接为`about:blank`. + +```javascript +webview.addEventListener('close', function() { + webview.src = 'about:blank'; +}); +``` + +### Event: 'ipc-message' + +返回: + +* `channel` String +* `args` Array + +在 guest page 向嵌入页发送一个异步消息的时候触发. + +你可以很简单的使用 `sendToHost` 方法和 `ipc-message` 事件在 guest page 和 嵌入页(embedder page)之间通信: + +```javascript +// In embedder page. +webview.addEventListener('ipc-message', function(event) { + console.log(event.channel); + // Prints "pong" +}); +webview.send('ping'); +``` + +```javascript +// In guest page. +var ipcRenderer = require('electron').ipcRenderer; +ipcRenderer.on('ping', function() { + ipcRenderer.sendToHost('pong'); +}); +``` + +### Event: 'crashed' + +在渲染进程崩溃的时候触发. + +### Event: 'gpu-crashed' + +在GPU进程崩溃的时候触发. + +### Event: 'plugin-crashed' + +返回: + +* `name` String +* `version` String + +在插件进程崩溃的时候触发. + +### Event: 'destroyed' + +在界面内容销毁的时候触发. + +### Event: 'media-started-playing' + +在媒体准备播放的时候触发. + +### Event: 'media-paused' + +在媒体暂停播放或播放放毕的时候触发. + +### Event: 'did-change-theme-color' + +在页面的主体色改变的时候触发. +在使用 meta 标签的时候这就很常见了: + +```html + +``` + +### Event: 'devtools-opened' + +在开发者工具打开的时候触发. + +### Event: 'devtools-closed' + +在开发者工具关闭的时候触发. + +### Event: 'devtools-focused' + +在开发者工具获取焦点的时候触发. + +[blink-feature-string]: https://code.google.com/p/chromium/codesearch#chromium/src/out/Debug/gen/blink/platform/RuntimeEnabledFeatures.cpp&sq=package:chromium&type=cs&l=527 \ No newline at end of file diff --git a/docs-translations/zh-CN/api/window-open.md b/docs-translations/zh-CN/api/window-open.md new file mode 100644 index 000000000000..069e2c352263 --- /dev/null +++ b/docs-translations/zh-CN/api/window-open.md @@ -0,0 +1,60 @@ +# `window.open` 函数 + +当在界面中使用 `window.open` 来创建一个新的窗口时候,将会创建一个 `BrowserWindow` 的实例,并且将返回一个标识,这个界面通过标识来对这个新的窗口进行有限的控制. + +这个标识对传统的web界面来说,通过它能对子窗口进行有限的功能性兼容控制. +想要完全的控制这个窗口,可以直接创建一个 `BrowserWindow` . + +新创建的 `BrowserWindow` 默认为继承父窗口的属性参数,想重写属性的话可以在 `features` 中设置他们. + +### `window.open(url[, frameName][, features])` + +* `url` String +* `frameName` String (可选) +* `features` String (可选) + +创建一个新的window并且返回一个 `BrowserWindowProxy` 类的实例. + + `features` 遵循标准浏览器的格式,但是每个feature 应该作为 `BrowserWindow` 参数的一个字段. + +### `window.opener.postMessage(message, targetOrigin)` + +* `message` String +* `targetOrigin` String + +通过指定位置或用 `*` 来代替没有明确位置来向父窗口发送信息. + +## Class: BrowserWindowProxy + +`BrowserWindowProxy` 由`window.open` 创建返回,并且提供了对子窗口的有限功能性控制. + +### `BrowserWindowProxy.blur()` + +子窗口的失去焦点. +### `BrowserWindowProxy.close()` + +强行关闭子窗口,忽略卸载事件. + +### `BrowserWindowProxy.closed` + +在子窗口关闭之后恢复正常. + +### `BrowserWindowProxy.eval(code)` + +* `code` String + +评估子窗口的代码. + +### `BrowserWindowProxy.focus()` + +子窗口获得焦点(让其显示在最前). + +### `BrowserWindowProxy.postMessage(message, targetOrigin)` + +* `message` String +* `targetOrigin` String + + +通过指定位置或用 `*` 来代替没有明确位置来向子窗口发送信息. + +除了这些方法,子窗口还可以无特性和使用单一方法来实现 `window.opener` 对象. \ No newline at end of file diff --git a/docs-translations/zh-CN/development/atom-shell-vs-node-webkit.md b/docs-translations/zh-CN/development/atom-shell-vs-node-webkit.md index beea91e3ff16..68fddb11d237 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 @@ -28,4 +28,4 @@ __4. 多上下文__ 通过使用 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 +[node-bindings]: https://github.com/electron/electron/tree/master/atom/common diff --git a/docs-translations/zh-CN/development/build-instructions-linux.md b/docs-translations/zh-CN/development/build-instructions-linux.md new file mode 100644 index 000000000000..9815aeb03e5f --- /dev/null +++ b/docs-translations/zh-CN/development/build-instructions-linux.md @@ -0,0 +1,123 @@ +# Build Instructions (Linux) + +遵循下面的引导,在 Linux 上构建 Electron . + +## Prerequisites + +* Python 2.7.x. 一些发行版如 CentOS 仍然使用 Python 2.6.x ,所以或许需要 check 你的 Python 版本,使用 `python -V`. +* Node.js v0.12.x. 有很多方法来安装 Node. 可以从 [Node.js](http://nodejs.org)下载原文件并且编译它 .也可以作为一个标准的用户在 home 目录下安装 node .或者尝试使用仓库 [NodeSource](https://nodesource.com/blog/nodejs-v012-iojs-and-the-nodesource-linux-repositories). +* Clang 3.4 或更新的版本. +* GTK+开发头文件和libnotify. + +在 Ubuntu, 安装下面的库 : + +```bash +$ sudo apt-get install build-essential clang libdbus-1-dev libgtk2.0-dev \ + libnotify-dev libgnome-keyring-dev libgconf2-dev \ + libasound2-dev libcap-dev libcups2-dev libxtst-dev \ + libxss1 libnss3-dev gcc-multilib g++-multilib +``` + +在 Fedora, 安装下面的库 : + +```bash +$ sudo yum install clang dbus-devel gtk2-devel libnotify-devel libgnome-keyring-devel \ + xorg-x11-server-utils libcap-devel cups-devel libXtst-devel \ + alsa-lib-devel libXrandr-devel GConf2-devel nss-devel +``` + +其它版本的也许提供了相似的包来安装,通过包管理器,例如 pacman. +或一个可以编译源文件的. + +## 使用虚拟机 + +如果在虚拟机上构建 Electron,你需要一个固定大小的设备,至少需要 25 gigabytes . + +## 获取代码 + +```bash +$ git clone https://github.com/electron/electron.git +``` + +## Bootstrapping + +bootstrap 脚本也是必要下载的构建依赖,来创建项目文件.需要使用 Python 2.7.x 来让脚本成功执行.正确下载文件会花费较长的时间. +注意我们使用的是 `ninja` 来构建 Electron,所以没有生成 `Makefile` 项目. + +```bash +$ cd electron +$ ./script/bootstrap.py -v +``` + +### 交叉编译 + +如果想创建一个 `arm` target ,应当还要下载下面的依赖 : + +```bash +$ sudo apt-get install libc6-dev-armhf-cross linux-libc-dev-armhf-cross \ + g++-arm-linux-gnueabihf +``` + +为了编译 `arm` 或 `ia32` targets, 你应当为 `bootstrap.py` 脚本使用 +`--target_arch` 参数: + +```bash +$ ./script/bootstrap.py -v --target_arch=arm +``` + +## 构建 + +创建 `Release` 、 `Debug` target: + +```bash +$ ./script/build.py +``` + +这个脚本也许会在目录 `out/R` 下创建一个巨大的可执行的 Electron . 文件大小或许会超过 1.3 gigabytes. 原因是 Release target 二进制文件包含了 调试符号 .运行 `create-dist.py` 脚本来减小文件的 size : + +```bash +$ ./script/create-dist.py +``` +这会在 `dist` 目录下创建一个有大量小文件的工作空间. 运行 create-dist.py 脚本之后, 或许你想删除仍然在 `out/R` 下的 1.3+ gigabyte 二进制文件. + +可以只创建 `Debug` target: + +```bash +$ ./script/build.py -c D +``` + +创建完毕, 可以在 `out/D`下面找到 `electron`. + +## Cleaning + +删除构建文件 : + +```bash +$ ./script/clean.py +``` + +## 解决问题 + +确保你已经安装了所有的依赖 . + +### Error While Loading Shared Libraries: libtinfo.so.5 + +预构建的 `clang` 会尝试链接到 `libtinfo.so.5`. 取决于 host 架构, 适当的使用 `libncurses`: + +```bash +$ sudo ln -s /usr/lib/libncurses.so.5 /usr/lib/libtinfo.so.5 +``` + +## Tests + +测试你的修改是否符合项目代码风格,使用: + +```bash +$ ./script/cpplint.py +``` + +测试有效性使用: + +```bash +$ ./script/test.py +``` \ No newline at end of file diff --git a/docs-translations/zh-CN/development/build-instructions-osx.md b/docs-translations/zh-CN/development/build-instructions-osx.md new file mode 100644 index 000000000000..98f6648a3598 --- /dev/null +++ b/docs-translations/zh-CN/development/build-instructions-osx.md @@ -0,0 +1,62 @@ +# Build Instructions (OS X) + +遵循下面的引导,在 OS X 上构建 Electron . + +## 前提 + +* OS X >= 10.8 +* [Xcode](https://developer.apple.com/technologies/tools/) >= 5.1 +* [node.js](http://nodejs.org) (外部) + +如果你通过 Homebrew 使用 Python 下载,需要安装下面的 Python 模块: + +* pyobjc + +## 获取代码 + +```bash +$ git clone https://github.com/electron/electron.git +``` + +## Bootstrapping + +bootstrap 脚本也是必要下载的构建依赖,来创建项目文件.注意我们使用的是 [ninja](https://ninja-build.org/) 来构建 Electron,所以没有生成 Xcode 项目. + +```bash +$ cd electron +$ ./script/bootstrap.py -v +``` + +## 构建 + +创建 `Release` 、 `Debug` target: + +```bash +$ ./script/build.py +``` + +可以只创建 `Debug` target: + +```bash +$ ./script/build.py -c D +``` + +创建完毕, 可以在 `out/D` 下面找到 `Electron.app`. + +## 32位支持 + +在 OS X 上,构建 Electron 只支持 64位的,不支持 32位的 . + +## 测试 + +测试你的修改是否符合项目代码风格,使用: + +```bash +$ ./script/cpplint.py +``` + +测试有效性使用: + +```bash +$ ./script/test.py +``` \ No newline at end of file diff --git a/docs-translations/zh-CN/development/build-instructions-windows.md b/docs-translations/zh-CN/development/build-instructions-windows.md new file mode 100644 index 000000000000..4333620a560b --- /dev/null +++ b/docs-translations/zh-CN/development/build-instructions-windows.md @@ -0,0 +1,136 @@ +# Build Instructions (Windows) + +遵循下面的引导,在 Windows 上构建 Electron . + +## 前提 + +* Windows 7 / Server 2008 R2 or higher +* Visual Studio 2015 - [download VS 2015 Community Edition for + free](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx) +* [Python 2.7](http://www.python.org/download/releases/2.7/) +* [Node.js](http://nodejs.org/download/) +* [Git](http://git-scm.com) + +如果你现在还没有安装 Windows , [modern.ie](https://www.modern.ie/en-us/virtualization-tools#downloads) 有一个 timebombed 版本的 Windows ,你可以用它来构建 Electron. + +构建 Electron 完全的依赖于命令行,并且不可通过 Visual Studio. +可以使用任何的编辑器来开发 Electron ,未来会支持 Visual Studio. + +**注意:** 虽然 Visual Studio 不是用来构建的,但是它仍然 +**必须的** ,因为我们需要它提供的构建工具栏. + +**注意:** Visual Studio 2013 不可用. 请确定使用 MSVS +**2015**. + +## 获取代码 + +```powershell +$ git clone https://github.com/electron/electron.git +``` + +## Bootstrapping + +bootstrap 脚本也是必要下载的构建依赖,来创建项目文件.注意我们使用的是 `ninja` 来构建 Electron,所以没有生成 Visual Studio 项目. + +```powershell +$ cd electron +$ python script\bootstrap.py -v +``` + +## 构建 + +创建 `Release` 、 `Debug` target: + +```powershell +$ python script\build.py +``` + +可以只创建 `Debug` target: + +```powershell +$ python script\build.py -c D +``` + +创建完毕, 可以在 `out/D`(debug target) 或 `out\R` (release target) 下面找到 `electron.exe`. + +## 64bit Build + +为了构建64位的 target,在运行 bootstrap 脚本的时候需要使用 `--target_arch=x64` : + +```powershell +$ python script\bootstrap.py -v --target_arch=x64 +``` + +其他构建步骤完全相同. + +## Tests + +测试你的修改是否符合项目代码风格,使用: + +```powershell +$ python script\cpplint.py +``` + +测试有效性使用: + +```powershell +$ python script\test.py +``` +在构建 debug 时为 Tests包含原生模块 (例如 `runas`) 将不会执行(详情 [#2558](https://github.com/electron/electron/issues/2558)), 但是它们在构建 release 会起效. + +运行 release 构建使用 : + +```powershell +$ python script\test.py -R +``` + +## 解决问题 + +### Command xxxx not found + +如果你遇到了一个错误,类似 `Command xxxx not found`, 可以尝试使用 `VS2012 Command Prompt` 控制台来执行构建脚本 . + +### Fatal internal compiler error: C1001 + +确保你已经安装了 Visual Studio 的最新安装包 . + +### Assertion failed: ((handle))->activecnt >= 0 + +如果在 Cygwin 下构建的,你可能会看到 `bootstrap.py` 失败并且附带下面错误 : + +``` +Assertion failed: ((handle))->activecnt >= 0, file src\win\pipe.c, line 1430 + +Traceback (most recent call last): + File "script/bootstrap.py", line 87, in + sys.exit(main()) + File "script/bootstrap.py", line 22, in main + update_node_modules('.') + File "script/bootstrap.py", line 56, in update_node_modules + execute([NPM, 'install']) + File "/home/zcbenz/codes/raven/script/lib/util.py", line 118, in execute + raise e +subprocess.CalledProcessError: Command '['npm.cmd', 'install']' returned non-zero exit status 3 +``` + +这是由同时使用 Cygwin Python 和 Win32 Node 造成的 bug.解决办法就是使用 Win32 Python 执行 bootstrap 脚本 (假定你已经在目录 `C:\Python27` 下安装了 Python): + +```powershell +$ /cygdrive/c/Python27/python.exe script/bootstrap.py +``` + +### LNK1181: cannot open input file 'kernel32.lib' + +重新安装 32位的 Node.js. + +### Error: ENOENT, stat 'C:\Users\USERNAME\AppData\Roaming\npm' + +简单创建目录 [应该可以解决问题](http://stackoverflow.com/a/25095327/102704): + +```powershell +$ mkdir ~\AppData\Roaming\npm +``` + +### node-gyp is not recognized as an internal or external command + +如果你使用 Git Bash 来构建,或许会遇到这个错误,可以使用 PowerShell 或 VS2015 Command Prompt 来代替 . \ No newline at end of file diff --git a/docs-translations/zh-CN/development/build-system-overview.md b/docs-translations/zh-CN/development/build-system-overview.md new file mode 100644 index 000000000000..6bd1452b8164 --- /dev/null +++ b/docs-translations/zh-CN/development/build-system-overview.md @@ -0,0 +1,42 @@ +# Build System Overview + +Electron 使用 [gyp](https://gyp.gsrc.io/) 来生成项目 ,使用 [ninja](https://ninja-build.org/) 来构建项目. 项目配置可以在 `.gyp` 和 `.gypi` 文件中找到. + +## Gyp 文件 + +下面的 `gyp` 文件包含了构建 Electron 的主要规则 : + +* `atom.gyp` 定义了 Electron 它自己是怎样被构建的. +* `common.gypi` 调整 node 的构建配置,来让它结合 Chromium 一起构建. +* `vendor/brightray/brightray.gyp` 定义了 `brightray` 是如何被构建的,并且包含了默认配置来连接到 Chromium. +* `vendor/brightray/brightray.gypi` 包含了常用的创建配置. + +## 创建组件 + +在 Chromium 还是一个相当大的项目的时候,最后链接阶段会花了好几分钟,这让开发变得很困难. 为了解决这个困难,Chromium 引入了 "component build" ,这让每个创建的组建都是分隔开的共享库,让链接更快,但是这浪费了文件大小和性能. + +在 Electron 中,我们采用了一个非常相似的方法 : 在创建 `Debug` , 二进制文件会被链接进入一个 Chromium 组件的共享版本库来达到快速链接; 在创建 `Release`, 二进制文件会被链接进入一个静态版本库, 所以我们可以有最小的二进制文件size和最佳的体验. + +## Minimal Bootstrapping + +在运行 bootstrap 脚本的时候,所有的 Chromium 预编译二进制文件会被下载.默认静态库和共享库会被下载,并且项目的最后大小会在 800MB 到 2GB 之间,这取决于平台类型. + +默认,`libchromiumcontent` 是从 Amazon Web Services 上下载下来的.如果设置了 `LIBCHROMIUMCONTENT_MIRROR` 环境变量,bootstrap脚本会从这里下载下来. [`libchromiumcontent-qiniu-mirror`](https://github.com/hokein/libchromiumcontent-qiniu-mirror) 是 `libchromiumcontent` 的映射.如果你不能连接 AWS,你可以切换下载路径:`export LIBCHROMIUMCONTENT_MIRROR=http://7xk3d2.dl1.z0.glb.clouddn.com/` +如果只是想快速搭建一个 Electron 的测试或开发环境,可以通过 `--dev` 参数只下载共享版本库: + +```bash +$ ./script/bootstrap.py --dev +$ ./script/build.py -c D +``` + +## Two-Phase Project Generation + +在 `Release` 和 `Debug` 构建的时候后,Electron 链接了不同配置的库 .然而 `gyp`不支持为不同的配置文件进行不同的链接设置. + +为了规避这个问题,Electron 在运行 `gyp` 的时候,使用了一个 `gyp` 的变量 `libchromiumcontent_component`来控制应该使用哪个链接设置,并且只生成一个目标. + +## Target Names + +与大多数的项目不同,它们使用 `Release` 和 `Debug` 作为目标名字,而 Electron 使用使用的是 `R` 和 `D`.这是因为如果只定义了一个 `Release` 或 `Debug` 构建配置,`gyp` 会随机崩溃,并且在同一时候,Electron 只生成一个目标,如上所述. + +这只对开发者可用,如果想重新构建 Electron ,将不会成功. \ No newline at end of file diff --git a/docs-translations/zh-CN/development/coding-style.md b/docs-translations/zh-CN/development/coding-style.md index 6652466b9703..4136ae4805b1 100644 --- a/docs-translations/zh-CN/development/coding-style.md +++ b/docs-translations/zh-CN/development/coding-style.md @@ -20,4 +20,4 @@ C++ 代码中用到了许多 Chromium 中的接口和数据类型,所以希望 ## API 命名 当新建一个 API 时,我们倾向于使用 getters 和 setters 而不是 jQuery 单函数的命名方式,比如 `.getText()` 和 `.setText(text)` - 而不是 `.text([text])`。这里有关于该规则的[讨论记录](https://github.com/atom/electron/issues/46)。 + 而不是 `.text([text])`。这里有关于该规则的[讨论记录](https://github.com/electron/electron/issues/46)。 diff --git a/docs-translations/zh-CN/development/setting-up-symbol-server.md b/docs-translations/zh-CN/development/setting-up-symbol-server.md new file mode 100644 index 000000000000..01098bcc77cd --- /dev/null +++ b/docs-translations/zh-CN/development/setting-up-symbol-server.md @@ -0,0 +1,38 @@ +# Setting Up Symbol Server in Debugger + +调试 symbols 让你有更好的调试 sessions. 它们有可执行的动态库的函数信息,并且提供信息来获得洁净的呼叫栈. 一个 Symbol 服务器允许调试器自动加载正确的 symbols, 二进制文件 和 资源文件,不用再去强制用户下载巨大的调试文件. 服务器函数类似 +[Microsoft's symbol server](http://support.microsoft.com/kb/311503) ,所以这里的记录可用. + +注意,因为公众版本的 Electron 构建是最优化的,调试不一定一直简单.调试器将不会给显示出所有变量内容,并且因为内联,尾调用,和其它编译器优化,执行路径会看起来很怪异 . 唯一的解决办法是搭建一个不优化的本地构建. + +Electron 使用的官方 symbol 服务器地址为 +`http://54.249.141.255:8086/atom-shell/symbols` . +你不能直接访问这个路径,必须将其添加到你的调试工具的 symbol 路径上.在下面的例子中,使用了一个本地缓存目录来避免重复从服务器获取 PDB. 在你的电脑上使用一个恰当的缓存目录来代替 `c:\code\symbols` . + +## Using the Symbol Server in Windbg + +Windbg symbol 路径被配制为一个限制带星号字符的字符串. 要只使用 Electron 的 symbol 服务器, 将下列记录添加到你的 symbol 路径 (__注意:__ 如果你愿意使用一个不同的地点来下载 symbols,你可以在你的电脑中使用任何可写的目录来代替 `c:\code\symbols`): + +``` +SRV*c:\code\symbols\*http://54.249.141.255:8086/atom-shell/symbols +``` + +使用 Windbg 菜单或通过输入 `.sympath` 命令,在环境中设置一个 `_NT_SYMBOL_PATH` 字符串.如果你也想从微软的 symbol 服务器获得 symbols ,你应当首先将它们先列出来 : + +``` +SRV*c:\code\symbols\*http://msdl.microsoft.com/download/symbols;SRV*c:\code\symbols\*http://54.249.141.255:8086/atom-shell/symbols +``` + +## 在 Visual Studio 中使用 symbol 服务器 + + + + +## Troubleshooting: Symbols will not load + +在 Windbg 中输入下列命令,打印出未什么 symbols 没有加载 : + +``` +> !sym noisy +> .reload /f chromiumcontent.dll +``` \ No newline at end of file diff --git a/docs-translations/zh-CN/tutorial/application-distribution.md b/docs-translations/zh-CN/tutorial/application-distribution.md index dcfe4d240eeb..2dca19342ad5 100644 --- a/docs-translations/zh-CN/tutorial/application-distribution.md +++ b/docs-translations/zh-CN/tutorial/application-distribution.md @@ -1,6 +1,7 @@ # 应用部署 -为了使用Electron部署你的应用程序,你存放应用程序的文件夹需要叫做 `app` 并且需要放在 Electron 的资源文件夹下(在 OS X 中是指 `Electron.app/Contents/Resources/`,在 Linux 和 Windows 中是指 `resources/`) +为了使用 Electron 部署你的应用程序,你存放应用程序的文件夹需要叫做 `app` 并且需要放在 Electron 的 +资源文件夹下(在 OS X 中是指 `Electron.app/Contents/Resources/`,在 Linux 和 Windows 中是指 `resources/`) 就像这样: 在 OS X 中: @@ -26,7 +27,7 @@ electron/resources/app ## 将你的应用程序打包成一个文件 -除了通过拷贝所有的资源文件来分发你的应用程序之外,你可以可以通过打包你的应用程序为一个 [asar](https://github.com/atom/asar) 库文件以避免暴露你的源代码。 +除了通过拷贝所有的资源文件来分发你的应用程序之外,你可以通过打包你的应用程序为一个 [asar](https://github.com/atom/asar) 库文件以避免暴露你的源代码。 为了使用一个 `asar` 库文件代替 `app` 文件夹,你需要修改这个库文件的名字为 `app.asar` , 然后将其放到 Electron 的资源文件夹下,然后 Electron 就会试图读取这个库文件并从中启动。 @@ -56,7 +57,7 @@ electron/resources/ 你可以将 `electron.exe` 改成任意你喜欢的名字,然后可以使用像 [rcedit](https://github.com/atom/rcedit) -编辑它的icon和其他信息。 +编辑它的 icon 和其他信息。 ### OS X diff --git a/docs-translations/zh-CN/tutorial/application-packaging.md b/docs-translations/zh-CN/tutorial/application-packaging.md index ee4b7bf63b85..13a24d2d1c33 100644 --- a/docs-translations/zh-CN/tutorial/application-packaging.md +++ b/docs-translations/zh-CN/tutorial/application-packaging.md @@ -1,34 +1,38 @@ # 应用打包 -为舒缓Windows下路径名过长的问题[issues](https://github.com/joyent/node/issues/6960), 也略对`require`加速以及简单隐匿你的源代码, 你可以通过极小的源代码改动将你的应用打包成[asar][asar]. +为舒缓 Windows 下路径名过长的问题[issues](https://github.com/joyent/node/issues/6960), +也略对 `require` 加速以及简单隐匿你的源代码,你可以通过极小的源代码改动将你的应用打包成 [asar][asar]。 -## 生成`asar`包 +## 生成 `asar` 包 -[asar][asar]是一种将多个文件合并成一个文件的类tar风格的归档格式。 Electron可以无需解压,即从其中读取任意文件内容。 +[asar][asar] 是一种将多个文件合并成一个文件的类 tar 风格的归档格式。 +Electron 可以无需解压,即从其中读取任意文件内容。 -参照如下步骤将你的应用打包成`asar`: +参照如下步骤将你的应用打包成 `asar`: -### 1. 安装asar +### 1. 安装 asar ```bash $ npm install -g asar ``` -### 2. 用`asar pack`打包 +### 2. 用 `asar pack` 打包 ```bash $ asar pack your-app app.asar ``` -## 使用`asar`包 +## 使用 `asar` 包 -在Electron中有两类APIs:Node.js提供的Node APIs和Chromium提供的Web APIs。这两种APIs都支持从`asar`包中读取文件。 +在 Electron 中有两类 APIs:Node.js 提供的 Node API 和 Chromium 提供的 Web API。 +这两种 API 都支持从 `asar` 包中读取文件。 ### Node API -由于Electron中打了特别补丁, Node APIs中如`fs.readFile`或者`require`之类的方法可以将`asar`视之为虚拟文件夹,读取`asar`里面的文件就和从真实的文件系统中读取一样。 +由于 Electron 中打了特别补丁, Node API 中如 `fs.readFile` 或者 `require` 之类 +的方法可以将 `asar` 视之为虚拟文件夹,读取 `asar` 里面的文件就和从真实的文件系统中读取一样。 -例如,假设我们在`/path/to`文件夹下有个`example.asar`包: +例如,假设我们在 `/path/to` 文件夹下有个 `example.asar` 包: ```bash $ asar list /path/to/example.asar @@ -40,27 +44,27 @@ $ asar list /path/to/example.asar /static/jquery.min.js ``` -从`asar`包读取一个文件: +从 `asar` 包读取一个文件: ```javascript const fs = require('fs'); fs.readFileSync('/path/to/example.asar/file.txt'); ``` -列出`asar`包中根目录下的所有文件: +列出 `asar` 包中根目录下的所有文件: ```javascript const fs = require('fs'); fs.readdirSync('/path/to/example.asar'); ``` -使用`asar`包中的一个模块: +使用 `asar` 包中的一个模块: ```javascript require('/path/to/example.asar/dir/module.js'); ``` -你也可以使用`BrowserWindow`来显示一个`asar`包里的web页面: +你也可以使用 `BrowserWindow` 来显示一个 `asar` 包里的 web 页面: ```javascript const BrowserWindow = require('electron').BrowserWindow; @@ -70,9 +74,9 @@ win.loadURL('file:///path/to/example.asar/static/index.html'); ### Web API -在Web页面里,用`file:`协议可以获取`asar`包中文件。和Node API一样,视`asar`包如虚拟文件夹。 +在 Web 页面里,用 `file:` 协议可以获取 `asar` 包中文件。和 Node API 一样,视 `asar` 包如虚拟文件夹。 -例如,用`$.get`获取文件: +例如,用 `$.get` 获取文件: ```html ``` -### 像“文件”那样处理`asar`包 +### 像“文件”那样处理 `asar` 包 -有些场景,如:核查`asar`包的校验和,我们需要像读取“文件”那样读取`asar`包的内容(而不是当成虚拟文件夹)。你可以使用内置的`original-fs`(提供和`fs`一样的APIs)模块来读取`asar`包的真实信息。 +有些场景,如:核查 `asar` 包的校验和,我们需要像读取“文件”那样读取 `asar` 包的内容(而不是当成虚拟文件夹)。 +你可以使用内置的 `original-fs` (提供和 `fs` 一样的 API)模块来读取 `asar` 包的真实信息。 ```javascript var originalFs = require('original-fs'); originalFs.readFileSync('/path/to/example.asar'); ``` -## Node API缺陷 +## Node API 缺陷 -尽管我们已经尽了最大努力使得`asar`包在Node API下的应用尽可能的趋向于真实的目录结构,但仍有一些底层Node API我们无法保证其正常工作。 +尽管我们已经尽了最大努力使得 `asar` 包在 Node API 下的应用尽可能的趋向于真实的目录结构,但仍有一些底层 Node API 我们无法保证其正常工作。 -### `asar`包是只读的 +### `asar` 包是只读的 -`asar`包中的内容不可更改,所以Node APIs里那些可以用来修改文件的方法在对待`asar`包时都无法正常工作。 +`asar` 包中的内容不可更改,所以 Node APIs 里那些可以用来修改文件的方法在对待 `asar` 包时都无法正常工作。 -### Working Directory在`asar`包中无效 +### Working Directory 在 `asar` 包中无效 -尽管`asar`包是虚拟文件夹,但其实并没有真实的目录架构对应在文件系统里,所以你不可能将working Directory设置成`asar`包里的一个文件夹。将`asar`中的文件夹以`cwd`形式作为参数传入一些API中也会报错。 +尽管 `asar` 包是虚拟文件夹,但其实并没有真实的目录架构对应在文件系统里,所以你不可能将 working Directory +设置成 `asar` 包里的一个文件夹。将 `asar` 中的文件夹以 `cwd` 形式作为参数传入一些 API 中也会报错。 -### API中的额外“开箱” +### API 中的额外“开箱” -大部分`fs`API可以无需解压即从`asar`包中读取文件或者文件的信息,但是在处理一些依赖真实文件路径的底层系统方法时,Electron会将所需文件解压到临时目录下,然后将临时目录下的真实文件路径传给底层系统方法使其正常工作。 对于这类API,耗费会略多一些。 +大部分 `fs` API 可以无需解压即从 `asar` 包中读取文件或者文件的信息,但是在处理一些依赖真实文件路径的底层 +系统方法时,Electron 会将所需文件解压到临时目录下,然后将临时目录下的真实文件路径传给底层系统方法使其正 +常工作。 对于这类API,耗费会略多一些。 -以下是一些需要额外解压的APIs: +以下是一些需要额外解压的 API: * `child_process.execFile` * `child_process.execFileSync` @@ -116,26 +124,32 @@ originalFs.readFileSync('/path/to/example.asar'); * `fs.openSync` * `process.dlopen` - `require`native模块时用到 -### `fs.stat`获取的stat信息不可靠 +### `fs.stat` 获取的 stat 信息不可靠 -对`asar`包中的文件取`fs.stat`,返回的`Stats`对象不是精确值,因为这些文件不是真实存在于文件系统里。所以除了文件大小和文件类型以外,你不应该依赖`Stats`对象的值。 +对 `asar` 包中的文件取 `fs.stat`,返回的 `Stats` 对象不是精确值,因为这些文件不是真实存在于文件系 +统里。所以除了文件大小和文件类型以外,你不应该依赖 `Stats` 对象的值。 -### 执行`asar`包中的程序 +### 执行 `asar` 包中的程序 -Node中有一些可以执行程序的API,如`child_process.exec`,`child_process.spawn`和`child_process.execFile`等,但只有`execFile`可以执行`asar`包中的程序。 +Node 中有一些可以执行程序的 API,如 `child_process.exec`,`child_process.spawn` 和 `child_process.execFile` 等, +但只有 `execFile` 可以执行 `asar` 包中的程序。 -因为`exec`和`spawn`允许`command`替代`file`作为输入,而`command`是需要在shell下执行的,目前没有可靠的方法来判断`command`中是否在操作一个`asar`包中的文件,而且即便可以判断,我们依旧无法保证可以在无任何副作用的情况下替换`command`中的文件路径。 +因为 `exec` 和 `spawn` 允许 `command` 替代 `file` 作为输入,而 `command` 是需要在 shell 下执行的,目前没有 +可靠的方法来判断 `command` 中是否在操作一个 `asar` 包中的文件,而且即便可以判断,我们依旧无法保证可以在无任何 +副作用的情况下替换 `command` 中的文件路径。 ## 打包时排除文件 -如上所述,一些Node API会在调用时将文件解压到文件系统中,除了效率问题外,也有可能引起杀毒软件的注意! +如上所述,一些 Node API 会在调用时将文件解压到文件系统中,除了效率问题外,也有可能引起杀毒软件的注意! -为解决这个问题,你可以在生成`asar`包时使用`--unpack`选项来排除一些文件,使其不打包到`asar`包中,下面是如何排除一些用作共享用途的native模块的方法: +为解决这个问题,你可以在生成 `asar` 包时使用 `--unpack` 选项来排除一些文件,使其不打包到 `asar` 包中, +下面是如何排除一些用作共享用途的 native 模块的方法: ```bash $ asar pack app app.asar --unpack *.node ``` -经过上述命令后,除了生成的`app.asar`包以外,还有一个包含了排除文件的`app.asar.unpacked`文件夹,你需要将这个文件夹一起拷贝,提供给用户。 +经过上述命令后,除了生成的 `app.asar` 包以外,还有一个包含了排除文件的 `app.asar.unpacked` 文件夹, +你需要将这个文件夹一起拷贝,提供给用户。 [asar]: https://github.com/atom/asar diff --git a/docs-translations/zh-CN/tutorial/debugging-main-process.md b/docs-translations/zh-CN/tutorial/debugging-main-process.md index f971a40892fc..361d7c2d57e0 100644 --- a/docs-translations/zh-CN/tutorial/debugging-main-process.md +++ b/docs-translations/zh-CN/tutorial/debugging-main-process.md @@ -1,6 +1,6 @@ # 主进程调试 -浏览器窗口的开发工具仅能调试渲染器的进程脚本(比如web 页面)。为了提供一个可以调试主进程 +浏览器窗口的开发工具仅能调试渲染器的进程脚本(比如 web 页面)。为了提供一个可以调试主进程 的方法,Electron 提供了 `--debug` 和 `--debug-brk` 开关。 ## 命令行开关 @@ -18,7 +18,7 @@ ## 使用 node-inspector 来调试 -__备注:__ Electron 目前对 node-inspector支持的不是特别好, +__备注:__ Electron 目前对 node-inspector 支持的不是特别好, 如果你通过 node-inspector 的 console 来检查 `process` 对象,主进程就会崩溃。 ### 1. 确认你已经安装了 [node-gyp 所需工具](https://github.com/nodejs/node-gyp#installation) @@ -42,6 +42,8 @@ $ node_modules/.bin/node-pre-gyp --target=0.36.2 --runtime=electron --fallback-t $ node_modules/.bin/node-pre-gyp --target=0.36.2 --runtime=electron --fallback-to-build --directory node_modules/v8-profiler/ --dist-url=https://atom.io/download/atom-shell reinstall ``` +[How to install native modules][how-to-install-native-modules]. + ### 5. 打开 Electron 的调试模式 你也可以用调试参数来运行 Electron : diff --git a/docs-translations/zh-CN/tutorial/desktop-environment-integration.md b/docs-translations/zh-CN/tutorial/desktop-environment-integration.md index 614aeed708f6..3f946da34e1c 100644 --- a/docs-translations/zh-CN/tutorial/desktop-environment-integration.md +++ b/docs-translations/zh-CN/tutorial/desktop-environment-integration.md @@ -3,6 +3,47 @@ 本章将会说明怎样使用 Electron APIs 把你的应用和桌面环境集成到一块。 +## Notifications (Windows, Linux, OS X) + +这三个操作系统都为用户提供了发送通知的方法。Electron让开发人员通过 +[HTML5 Notification API](https://notifications.spec.whatwg.org/) +便利的去发送通知,用操作系统自带的通知APIs去显示。 + +**Note:** 因为这是一个HTML5API,所以只在渲染进程中起作用 + +```javascript +var myNotification = new Notification('Title', { + body: 'Lorem Ipsum Dolor Sit Amet' +}); + +myNotification.onclick = function () { + console.log('Notification clicked') +} +``` + +尽管代码和用户体验在不同的操作系统中基本相同,但还是有一些差异。 + +### Windows + +* 在Windows 10上, 通知"可以工作". +* 在Windows 8.1和Windows 8系统下,你需要将你的应用通过一个[Application User +Model ID][app-user-model-id]安装到开始屏幕上。需要注意的是,这不是将你的应用固定到开始屏幕。 +* 在Windows 7以及更低的版本中,通知不被支持。不过你可以使用[Tray API][tray-balloon]发送一个"气泡通知"。 + +此外,通知支持的最大字符长队为250。Windows团队建议通知应该保持在200个字符以下。 + +### Linux + +通知使用`libnotify`发送,它能在任何支持[Desktop Notifications +Specification][notification-spec]的桌面环境中显示,包括 Cinnamon, Enlightenment, Unity, +GNOME, KDE。 + +### OS X + +在OS X系统中,通知是直接转发的,你应该了解[Apple's Human Interface guidelines regarding notifications](https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/OSXHIGuidelines/NotificationCenter.html)。 + +注意通知被限制在256个字节以内,如果超出,则会被截断。 + ## 最近文档 (Windows & OS X) Windows 和 OS X 提供获取最近文档列表的便捷方式,那就是打开跳转列表或者鱼眼菜单。 @@ -149,19 +190,33 @@ window.setDocumentEdited(true); [1]:https://camo.githubusercontent.com/3310597e01f138b1d687e07aa618c50908a88dec/687474703a2f2f692e6d73646e2e6d6963726f736f66742e636f6d2f64796e696d672f49433432303533382e706e67 [2]: https://cloud.githubusercontent.com/assets/639601/5069610/2aa80758-6e97-11e4-8cfb-c1a414a10774.png - [3]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/api/app.md - [4]: https://github.com/atom/electron/blob/master/docs/tutorial/clearrecentdocuments + [3]: https://github.com/electron/electron/blob/master/docs-translations/zh-CN/api/app.md + [4]: https://github.com/electron/electron/blob/master/docs/tutorial/clearrecentdocuments [5]: https://msdn.microsoft.com/en-us/library/windows/desktop/ee872121%28v=vs.85%29.aspx [6]: https://cloud.githubusercontent.com/assets/639601/5069962/6032658a-6e9c-11e4-9953-aa84006bdfff.png [7]: https://camo.githubusercontent.com/30154e0cc36acfc968ac9ae076a8f0d6600dd736/687474703a2f2f692e6d73646e2e6d6963726f736f66742e636f6d2f64796e696d672f49433432303533392e706e67 - [8]: https://github.com/atom/electron/blob/master/docs/api/app.md#appsetusertaskstasks + [8]: https://github.com/electron/electron/blob/master/docs/api/app.md#appsetusertaskstasks [9]: https://camo.githubusercontent.com/098cb0f52f27084a80ec6429e51a195df3d8c333/68747470733a2f2f692d6d73646e2e7365632e732d6d7366742e636f6d2f64796e696d672f49433432303534302e706e67 - [10]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/api/browser-window.md + [10]: https://github.com/electron/electron/blob/master/docs-translations/zh-CN/api/browser-window.md [11]: https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles#Adding_shortcuts_to_a_launcher [12]: https://camo.githubusercontent.com/b6f54e2bc3206ebf8e08dd029529af9ec84d58ae/68747470733a2f2f68656c702e7562756e74752e636f6d2f636f6d6d756e6974792f556e6974794c61756e6368657273416e644465736b746f7046696c65733f616374696f6e3d41747461636846696c6526646f3d676574267461726765743d73686f7274637574732e706e67 [13]: https://cloud.githubusercontent.com/assets/639601/5081682/16691fda-6f0e-11e4-9676-49b6418f1264.png [14]: https://cloud.githubusercontent.com/assets/639601/5081747/4a0a589e-6f0f-11e4-803f-91594716a546.png - [15]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/api/browser-window.md + [15]: https://github.com/electron/electron/blob/master/docs-translations/zh-CN/api/browser-window.md [16]: https://cloud.githubusercontent.com/assets/639601/5082061/670a949a-6f14-11e4-987a-9aaa04b23c1d.png - [17]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/api/browser-window.md - [18]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/api/browser-window.md + [17]: https://github.com/electron/electron/blob/master/docs-translations/zh-CN/api/browser-window.md + [18]: https://github.com/electron/electron/blob/master/docs-translations/zh-CN/api/browser-window.md + +[addrecentdocument]: ../api/app.md#appaddrecentdocumentpath-os-x-windows +[clearrecentdocuments]: ../api/app.md#appclearrecentdocuments-os-x-windows +[setusertaskstasks]: ../api/app.md#appsetusertaskstasks-windows +[setprogressbar]: ../api/browser-window.md#winsetprogressbarprogress +[setoverlayicon]: ../api/browser-window.md#winsetoverlayiconoverlay-description-windows-7 +[setrepresentedfilename]: ../api/browser-window.md#winsetrepresentedfilenamefilename-os-x +[setdocumentedited]: ../api/browser-window.md#winsetdocumenteditededited-os-x +[app-registration]: http://msdn.microsoft.com/en-us/library/windows/desktop/ee872121(v=vs.85).aspx +[unity-launcher]: https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles#Adding_shortcuts_to_a_launcher +[setthumbarbuttons]: ../api/browser-window.md#winsetthumbarbuttonsbuttons-windows-7 +[tray-balloon]: ../api/tray.md#traydisplayballoonoptions-windows +[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx +[notification-spec]: https://developer.gnome.org/notification-spec/ diff --git a/docs-translations/zh-CN/tutorial/devtools-extension.md b/docs-translations/zh-CN/tutorial/devtools-extension.md index 9a2d9fc8832b..e35a971e1c76 100644 --- a/docs-translations/zh-CN/tutorial/devtools-extension.md +++ b/docs-translations/zh-CN/tutorial/devtools-extension.md @@ -1,10 +1,10 @@ # DevTools扩展 -为了使调试更容易,Electron原生支持[Chrome DevTools Extension][devtools-extension]。 +为了使调试更容易,Electron 原生支持 [Chrome DevTools Extension][devtools-extension]。 -对于大多数DevTools的扩展,你可以直接下载源码,然后通过`BrowserWindow.addDevToolsExtension`API加载它们。Electron会记住已经加载了哪些扩展,所以你不需要每次创建一个新window时都调用`BrowserWindow.addDevToolsExtension`API。 +对于大多数DevTools的扩展,你可以直接下载源码,然后通过 `BrowserWindow.addDevToolsExtension` API 加载它们。Electron会记住已经加载了哪些扩展,所以你不需要每次创建一个新window时都调用 `BrowserWindow.addDevToolsExtension` API。 -** 注:React DevTools目前不能直接工作,详情留意[https://github.com/atom/electron/issues/915](https://github.com/atom/electron/issues/915) ** +** 注:React DevTools目前不能直接工作,详情留意 [https://github.com/electron/electron/issues/915](https://github.com/electron/electron/issues/915) ** 例如,要用[React DevTools Extension](https://github.com/facebook/react-devtools),你得先下载他的源码: @@ -13,33 +13,33 @@ $ cd /some-directory $ git clone --recursive https://github.com/facebook/react-devtools.git ``` -参考[`react-devtools/shells/chrome/Readme.md`](https://github.com/facebook/react-devtools/blob/master/shells/chrome/Readme.md)来编译这个扩展源码。 +参考 [`react-devtools/shells/chrome/Readme.md`](https://github.com/facebook/react-devtools/blob/master/shells/chrome/Readme.md) 来编译这个扩展源码。 -然后你就可以在任意页面的DevTools里加载React DevTools了,通过控制台输入如下命令加载扩展: +然后你就可以在任意页面的 DevTools 里加载 React DevTools 了,通过控制台输入如下命令加载扩展: ```javascript const BrowserWindow = require('electron').remote.BrowserWindow; BrowserWindow.addDevToolsExtension('/some-directory/react-devtools/shells/chrome'); ``` -要卸载扩展,可以调用`BrowserWindow.removeDevToolsExtension`API(扩展名作为参数传入),该扩展在下次打开DevTools时就不会加载了: +要卸载扩展,可以调用 `BrowserWindow.removeDevToolsExtension` API (扩展名作为参数传入),该扩展在下次打开DevTools时就不会加载了: ```javascript BrowserWindow.removeDevToolsExtension('React Developer Tools'); ``` -## DevTools扩展的格式 +## DevTools 扩展的格式 -理论上,Electron可以加载所有为chrome浏览器编写的DevTools扩展,但它们必须存放在文件夹里。那些以`crx`形式发布的扩展是不能被加载的,除非你把它们解压到一个文件夹里。 +理论上,Electron 可以加载所有为 chrome 浏览器编写的 DevTools 扩展,但它们必须存放在文件夹里。那些以 `crx` 形式发布的扩展是不能被加载的,除非你把它们解压到一个文件夹里。 ## 后台运行(background pages) -Electron目前并不支持chrome扩展里的后台运行(background pages)功能,所以那些依赖此特性的DevTools扩展在Electron里可能无法正常工作。 +Electron 目前并不支持 chrome 扩展里的后台运行(background pages)功能,所以那些依赖此特性的 DevTools 扩展在 Electron 里可能无法正常工作。 ## `chrome.*` APIs -有些chrome扩展使用了`chrome.*`APIs,而且这些扩展在Electron中需要额外实现一些代码才能使用,所以并不是所有的这类扩展都已经在Electron中实现完毕了。 +有些 chrome 扩展使用了 `chrome.*`APIs,而且这些扩展在 Electron 中需要额外实现一些代码才能使用,所以并不是所有的这类扩展都已经在 Electron 中实现完毕了。 -考虑到并非所有的`chrome.*`APIs都实现完毕,如果DevTools正在使用除了`chrome.devtools.*`之外的其它APIs,这个扩展很可能无法正常工作。你可以通过报告这个扩展的异常信息,这样做方便我们对该扩展的支持。 +考虑到并非所有的 `chrome.*`APIs 都实现完毕,如果 DevTools 正在使用除了 `chrome.devtools.*` 之外的其它 APIs,这个扩展很可能无法正常工作。你可以通过报告这个扩展的异常信息,这样做方便我们对该扩展的支持。 [devtools-extension]: https://developer.chrome.com/extensions/devtools diff --git a/docs-translations/zh-CN/tutorial/mac-app-store-submission-guide.md b/docs-translations/zh-CN/tutorial/mac-app-store-submission-guide.md new file mode 100644 index 000000000000..e7c7af817aa9 --- /dev/null +++ b/docs-translations/zh-CN/tutorial/mac-app-store-submission-guide.md @@ -0,0 +1,147 @@ +# Mac App Store 应用提交向导 + +自从 v0.34.0, Electron 就允许提交应用包到 Mac App Store +(MAS) . 这个向导提供的信息有 : 如何提交应用和 MAS 构建的限制. + +__注意:__ 从 v0.36.0,当应用成为沙箱之后,会有一个 bug 阻止 GPU 进程开启 , 所以在这个 bug 修复之前,建议使用 v0.35.x .更多查看 [issue #3871][issue-3871] . + +__注意:__ 提交应用到 Mac App Store 需要参加 [Apple Developer +Program][developer-program] , 这需要花钱. + +## 如何提交 + +下面步骤介绍了一个简单的提交应用到商店方法.然而,这些步骤不能保证你的应用被 Apple 接受;你仍然需要阅读 Apple 的 [Submitting Your App][submitting-your-app] 关于如何满足 Mac App Store 要求的向导. + +### 获得证书 + +为了提交应用到商店,首先需要从 Apple 获得一个证书.可以遵循 [existing guides][nwjs-guide]. + +### App 签名 + +获得证书之后,你可以使用 [Application Distribution](application-distribution.md) 打包你的应用, 然后前往提交你的应用.这个步骤基本上和其他程序一样,但是这 key 一个个的标识 Electron 的每个依赖. + +首先,你需要准备2个授权文件 . + +`child.plist`: + +```xml + + + + + com.apple.security.app-sandbox + + com.apple.security.inherit + + + +``` + +`parent.plist`: + +```xml + + + + + com.apple.security.app-sandbox + + + +``` + +然后使用下面的脚本标识你的应用 : + +```bash +#!/bin/bash + +# Name of your app. +APP="YourApp" +# The path of you app to sign. +APP_PATH="/path/to/YouApp.app" +# The path to the location you want to put the signed package. +RESULT_PATH="~/Desktop/$APP.pkg" +# The name of certificates you requested. +APP_KEY="3rd Party Mac Developer Application: Company Name (APPIDENTITY)" +INSTALLER_KEY="3rd Party Mac Developer Installer: Company Name (APPIDENTITY)" + +FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks" + +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper.app/" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper EH.app/" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper NP.app/" +if [ -d "$FRAMEWORKS_PATH/Squirrel.framework/Versions/A" ]; then + # Signing a non-MAS build. + codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Mantle.framework/Versions/A" + codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/ReactiveCocoa.framework/Versions/A" + codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Squirrel.framework/Versions/A" +fi +codesign -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH" + +productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH" +``` +如果你是 OS X 下的应用沙箱使用新手,应当仔细阅读 Apple 的 [Enabling App Sandbox][enable-app-sandbox] 来有一点基础,然后向授权文件添加你的应用需要的许可 keys . + +### 上传你的应用并检查提交 + +在签名应用之后,可以使用应用 Loader 来上传到 iTunes 链接处理 , 确保在上传之前你已经 [created a record][create-record]. 然后你能 [submit your app for review][submit-for-review]. + +## MAS构建限制 + +为了让你的应用沙箱满足所有条件,在 MAS 构建的时候,下面的模块被禁用了 : + +* `crashReporter` +* `autoUpdater` + +并且下面的行为也改变了: + +* 一些机子的视频采集功能无效. +* 某些特征不可访问. +* Apps 不可识别 DNS 改变. + +也由于应用沙箱的使用方法,应用可以访问的资源被严格限制了 ; 阅读更多信息 [App Sandboxing][app-sandboxing] . + +## Electron 使用的加密算法 + +取决于你所在地方的国家和地区 , Mac App Store 或许需要记录你应用的加密算法 , 甚至要求你提交一个 U.S 加密注册(ERN) 许可的复印件. + +Electron 使用下列加密算法: + +* AES - [NIST SP 800-38A](http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf), [NIST SP 800-38D](http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf), [RFC 3394](http://www.ietf.org/rfc/rfc3394.txt) +* HMAC - [FIPS 198-1](http://csrc.nist.gov/publications/fips/fips198-1/FIPS-198-1_final.pdf) +* ECDSA - ANS X9.62–2005 +* ECDH - ANS X9.63–2001 +* HKDF - [NIST SP 800-56C](http://csrc.nist.gov/publications/nistpubs/800-56C/SP-800-56C.pdf) +* PBKDF2 - [RFC 2898](https://tools.ietf.org/html/rfc2898) +* RSA - [RFC 3447](http://www.ietf.org/rfc/rfc3447) +* SHA - [FIPS 180-4](http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf) +* Blowfish - https://www.schneier.com/cryptography/blowfish/ +* CAST - [RFC 2144](https://tools.ietf.org/html/rfc2144), [RFC 2612](https://tools.ietf.org/html/rfc2612) +* DES - [FIPS 46-3](http://csrc.nist.gov/publications/fips/fips46-3/fips46-3.pdf) +* DH - [RFC 2631](https://tools.ietf.org/html/rfc2631) +* DSA - [ANSI X9.30](http://webstore.ansi.org/RecordDetail.aspx?sku=ANSI+X9.30-1%3A1997) +* EC - [SEC 1](http://www.secg.org/sec1-v2.pdf) +* IDEA - "On the Design and Security of Block Ciphers" book by X. Lai +* MD2 - [RFC 1319](http://tools.ietf.org/html/rfc1319) +* MD4 - [RFC 6150](https://tools.ietf.org/html/rfc6150) +* MD5 - [RFC 1321](https://tools.ietf.org/html/rfc1321) +* MDC2 - [ISO/IEC 10118-2](https://www.openssl.org/docs/manmaster/crypto/mdc2.html) +* RC2 - [RFC 2268](https://tools.ietf.org/html/rfc2268) +* RC4 - [RFC 4345](https://tools.ietf.org/html/rfc4345) +* RC5 - http://people.csail.mit.edu/rivest/Rivest-rc5rev.pdf +* RIPEMD - [ISO/IEC 10118-3](http://webstore.ansi.org/RecordDetail.aspx?sku=ISO%2FIEC%2010118-3:2004) + +如何获取 ERN 许可, 可看这篇文章: [How to legally +submit an app to Apple’s App Store when it uses encryption (or how to obtain an +ERN)][ern-tutorial]. + +[developer-program]: https://developer.apple.com/support/compare-memberships/ +[submitting-your-app]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourApp/SubmittingYourApp.html +[nwjs-guide]: https://github.com/nwjs/nw.js/wiki/Mac-App-Store-%28MAS%29-Submission-Guideline#first-steps +[enable-app-sandbox]: https://developer.apple.com/library/ios/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html +[create-record]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/CreatingiTunesConnectRecord.html +[submit-for-review]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SubmittingTheApp.html +[app-sandboxing]: https://developer.apple.com/app-sandboxing/ +[issue-3871]: https://github.com/electron/electron/issues/3871 +[ern-tutorial]: https://carouselapps.com/2015/12/15/legally-submit-app-apples-app-store-uses-encryption-obtain-ern/ \ No newline at end of file diff --git a/docs-translations/zh-CN/tutorial/online-offline-events.md b/docs-translations/zh-CN/tutorial/online-offline-events.md index 7389afa4c9e5..d61f7378b967 100644 --- a/docs-translations/zh-CN/tutorial/online-offline-events.md +++ b/docs-translations/zh-CN/tutorial/online-offline-events.md @@ -3,10 +3,11 @@ *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'); @@ -36,17 +37,18 @@ app.on('ready', function() { *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'); }); -ipc.on('online-status-changed', function(event, status) { +ipcMain.on('online-status-changed', function(event, status) { console.log(status); }); ``` @@ -57,9 +59,9 @@ ipc.on('online-status-changed', function(event, status) { - and Electron . + and Electron . ``` @@ -127,7 +127,7 @@ $ ./Electron.app/Contents/MacOS/Electron your-app/ 在你完成了你的应用后,你可以按照[应用部署][4]指导发布一个版本,并且以已经打包好的形式运行应用。 - [1]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/api/ipc-main-process.md - [2]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/api/remote.md - [3]: https://github.com/atom/electron/releases - [4]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/tutorial/application-distribution.md + [1]: https://github.com/electron/electron/blob/master/docs-translations/zh-CN/api/ipc-main-process.md + [2]: https://github.com/electron/electron/blob/master/docs-translations/zh-CN/api/remote.md + [3]: https://github.com/electron/electron/releases + [4]: https://github.com/electron/electron/blob/master/docs-translations/zh-CN/tutorial/application-distribution.md diff --git a/docs-translations/zh-CN/tutorial/supported-platforms.md b/docs-translations/zh-CN/tutorial/supported-platforms.md index d4d42ec03ff3..e03ccd82364a 100644 --- a/docs-translations/zh-CN/tutorial/supported-platforms.md +++ b/docs-translations/zh-CN/tutorial/supported-platforms.md @@ -20,7 +20,7 @@ Ubuntu 12.04 下编译的,`arm` 版的二进制文件是在 ARM v7(硬浮点 Debian Wheezy 版本的 NEON)下完成的。 预编译二进制文件是否能够运行,取决于其中是否包括了编译平台链接的库,所以只有 Ubuntu 12.04 -可以保证正常工作,但是以下的平台也被证实可以运行 Electron的预编译版本: +可以保证正常工作,但是以下的平台也被证实可以运行 Electron 的预编译版本: * Ubuntu 12.04 及更新 * Fedora 21 diff --git a/docs-translations/zh-CN/tutorial/testing-on-headless-ci.md b/docs-translations/zh-CN/tutorial/testing-on-headless-ci.md new file mode 100644 index 000000000000..7da98b0a1a64 --- /dev/null +++ b/docs-translations/zh-CN/tutorial/testing-on-headless-ci.md @@ -0,0 +1,51 @@ +# Testing Electron with headless CI Systems (Travis CI, Jenkins) + +Electron基于Chromium,所以需要一个显示驱动使其运转。如果Chromium无法找到一个显示驱动, +ELectron 会启动失败,因此无论你如何去运行它,Electron不会执行你的任何测试。在Travis, Circle, +Jenkins 或者类似的系统上测试基于Electron的应用时,需要进行一些配置。本质上,我们需要使用一个 +虚拟的显示驱动。 + +## Configuring the Virtual Display Server + +首先安装[Xvfb](https://en.wikipedia.org/wiki/Xvfb)。 +这是一个虚拟的帧缓冲,实现了X11显示服务协议,所有的图形操作都在内存中表现,而不需要显示在 +任何屏幕输出设备上。这正是我们所需要的。 + +然后创建一个虚拟的xvfb屏幕并且导出一个指向他的名为`DISPLAY`的环境变量。Electron中的Chromium +会自动的去寻找`$DISPLAY`,所以你的应用不需要再去进行配置。这一步可以通过Paul Betts's的 +[xvfb-maybe](https://github.com/paulcbetts/xvfb-maybe)实现自动化:如果系统需要,在`xvfb-maybe`前加上你的测试命令 +然后这个小工具会自动的设置xvfb。在Windows或者Mac OS X系统下,它不会执行任何东西。 + +``` +## On Windows or OS X, this just invokes electron-mocha +## On Linux, if we are in a headless environment, this will be equivalent +## to xvfb-run electron-mocha ./test/*.js +xvfb-maybe electron-mocha ./test/*.js +``` + +### Travis CI + +在 Travis 上, 你的 `.travis.yml` 应该和下面的代码相似: + +``` +addons: + apt: + packages: + - xvfb + +install: + - export DISPLAY=':99.0' + - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & +``` + +### Jenkins + +Jenkins下, 有一个可用的[Xvfb插件](https://wiki.jenkins-ci.org/display/JENKINS/Xvfb+Plugin). + +### Circle CI + +Circle CI 是非常棒的而且有xvfb,`$DISPLAY`也[已经搭建,所以不需要再进行设置](https://circleci.com/docs/environment#browsers)。 + +### AppVeyor + +AppVeyor运行于Windows上,支持 Selenium, Chromium, Electron 以及一些类似的工具,开箱即用,无需配置。 diff --git a/docs-translations/zh-CN/tutorial/using-native-node-modules.md b/docs-translations/zh-CN/tutorial/using-native-node-modules.md index e03e7b8948a1..ef85313e0c27 100644 --- a/docs-translations/zh-CN/tutorial/using-native-node-modules.md +++ b/docs-translations/zh-CN/tutorial/using-native-node-modules.md @@ -1,12 +1,12 @@ # 使用原生模块 -Electron同样也支持原生模块,但由于和官方的Node相比使用了不同的V8引擎,如果你想编译原生模块,则需要手动设置Electron的headers的位置。 +Electron 同样也支持原生模块,但由于和官方的 Node 相比使用了不同的 V8 引擎,如果你想编译原生模块,则需要手动设置 Electron 的 headers 的位置。 ## 原生Node模块的兼容性 -当Node开始换新的V8引擎版本时,原生模块可能“坏”掉。为确保一切工作正常,你需要检查你想要使用的原生模块是否被Electron内置的Node支持。你可以在[这里](https://github.com/atom/electron/releases)查看Electron内置的Node版本,或者使用`process.version`(参考:[快速入门](https://github.com/atom/electron/blob/master/docs/tutorial/quick-start.md))查看。 +当 Node 开始换新的V8引擎版本时,原生模块可能“坏”掉。为确保一切工作正常,你需要检查你想要使用的原生模块是否被 Electron 内置的 Node 支持。你可以在[这里](https://github.com/electron/electron/releases)查看 Electron 内置的 Node 版本,或者使用 `process.version` (参考:[快速入门](https://github.com/electron/electron/blob/master/docs/tutorial/quick-start.md))查看。 -考虑到[NAN](https://github.com/nodejs/nan/)可以使你的开发更容易对多版本Node的支持,建议使用它来开发你自己的模块。你也可以使用[NAN](https://github.com/nodejs/nan/)来移植旧的模块到新的Node版本,以使它们可以在新的Electron下良好工作。 +考虑到 [NAN](https://github.com/nodejs/nan/) 可以使你的开发更容易对多版本 Node 的支持,建议使用它来开发你自己的模块。你也可以使用 [NAN](https://github.com/nodejs/nan/) 来移植旧的模块到新的 Nod e版本,以使它们可以在新的 Electron 下良好工作。 ## 如何安装原生模块 @@ -14,7 +14,7 @@ Electron同样也支持原生模块,但由于和官方的Node相比使用了 ### 最简单方式 -最简单的方式就是通过[`electron-rebuild`](https://github.com/paulcbetts/electron-rebuild)包重新编译原生模块,它帮你自动完成了下载headers、编译原生模块等步骤: +最简单的方式就是通过 [`electron-rebuild`](https://github.com/paulcbetts/electron-rebuild) 包重新编译原生模块,它帮你自动完成了下载 headers、编译原生模块等步骤: ```sh npm install --save-dev electron-rebuild @@ -26,9 +26,9 @@ npm install --save-dev electron-rebuild .\node_modules\.bin\electron-rebuild.cmd ``` -### 通过npm安装 +### 通过 npm 安装 -你当然也可以通过`npm`安装原生模块。大部分步骤和安装普通模块时一样,除了以下一些系统环境变量你需要自己操作: +你当然也可以通过 `npm` 安装原生模块。大部分步骤和安装普通模块时一样,除了以下一些系统环境变量你需要自己操作: ```bash export npm_config_disturl=https://atom.io/download/atom-shell @@ -38,16 +38,19 @@ export npm_config_runtime=electron HOME=~/.electron-gyp npm install module-name ``` -### 通过node-gyp安装 +### 通过 node-gyp 安装 -你需要告诉`node-gyp`去哪下载Electron的headers,以及下载什么版本: +你需要告诉 `node-gyp` 去哪下载 Electron 的 headers,以及下载什么版本: ```bash $ cd /path-to-module/ $ HOME=~/.electron-gyp node-gyp rebuild --target=0.29.1 --arch=x64 --dist-url=https://atom.io/download/atom-shell ``` -`HOME=~/.electron-gyp`设置了去哪找开发时的headers。 -`--target=0.29.1`设置了Electron的版本 -`--dist-url=...`设置了Electron的headers的下载地址 -`--arch=x64`设置了该模块为适配64bit操作系统而编译 +`HOME=~/.electron-gyp` 设置去哪找开发时的 headers。 + +`--target=0.29.1` 设置了 Electron 的版本 + +`--dist-url=...` 设置了 Electron 的 headers 的下载地址 + +`--arch=x64` 设置了该模块为适配64位操作系统而编译 diff --git a/docs-translations/zh-CN/tutorial/using-pepper-flash-plugin.md b/docs-translations/zh-CN/tutorial/using-pepper-flash-plugin.md new file mode 100644 index 000000000000..ce1f210c5c9b --- /dev/null +++ b/docs-translations/zh-CN/tutorial/using-pepper-flash-plugin.md @@ -0,0 +1,48 @@ +# 使用 Pepper Flash 插件 + +Electron 现在支持 Pepper Flash 插件。要在 Electron 里面使用 Pepper Flash 插件,你需 +要手动设置 Pepper Flash 的路径和在你的应用里启用 Pepper Flash。 + +## 保留一份 Flash 插件的副本 + +在 OS X 和 Linux 上,你可以在 Chrome 浏览器的 `chrome://plugins` 页面上找到 Pepper +Flash 的插件信息。插件的路径和版本会对 Election 对其的支持有帮助。你也可以把插件 +复制到另一个路径以保留一份副本。 + +## 添加插件在 Electron 里的开关 + +你可以直接在命令行中用 `--ppapi-flash-path` 和 `ppapi-flash-version` 或者 +在 app 的准备事件前调用 `app.commandLine.appendSwitch` 这个 method。同时, +添加 `browser-window` 的插件开关。 +例如: + +```javascript +// Specify flash path. 设置 flash 路径 +// On Windows, it might be /path/to/pepflashplayer.dll +// On OS X, /path/to/PepperFlashPlayer.plugin +// On Linux, /path/to/libpepflashplayer.so +app.commandLine.appendSwitch('ppapi-flash-path', '/path/to/libpepflashplayer.so'); + +// Specify flash version, for example, v17.0.0.169 设置版本号 +app.commandLine.appendSwitch('ppapi-flash-version', '17.0.0.169'); + +app.on('ready', function() { + mainWindow = new BrowserWindow({ + 'width': 800, + 'height': 600, + 'web-preferences': { + 'plugins': true + } + }); + mainWindow.loadURL('file://' + __dirname + '/index.html'); + // Something else +}); +``` + +## 使用 `` 标签启用插件 + +在 `` 标签里添加 `plugins` 属性。 + +```html + +``` diff --git a/docs-translations/zh-CN/tutorial/using-selenium-and-webdriver.md b/docs-translations/zh-CN/tutorial/using-selenium-and-webdriver.md index c05a3190eea1..4c1db714bde8 100644 --- a/docs-translations/zh-CN/tutorial/using-selenium-and-webdriver.md +++ b/docs-translations/zh-CN/tutorial/using-selenium-and-webdriver.md @@ -1,18 +1,18 @@ -# 使用Selenium和WebDriver +# 使用 Selenium 和 WebDriver 引自[ChromeDriver - WebDriver for Chrome][chrome-driver]: -> WebDriver是一款开源的支持多浏览器的自动化测试工具。它提供了操作网页、用户输入、JavaScript执行等能力。ChromeDriver是一个实现了WebDriver与Chromium联接协议的独立服务。它也是由开发了Chromium和WebDriver的团队开发的。 +> WebDriver 是一款开源的支持多浏览器的自动化测试工具。它提供了操作网页、用户输入、JavaScript 执行等能力。ChromeDriver 是一个实现了 WebDriver 与 Chromium 联接协议的独立服务。它也是由开发了 Chromium 和 WebDriver 的团队开发的。 -为了能够使`chromedriver`和Electron一起正常工作,我们需要告诉它Electron在哪,并且让它相信Electron就是Chrome浏览器。 +为了能够使 `chromedriver` 和 Electron 一起正常工作,我们需要告诉它 Electron 在哪,并且让它相信 Electron 就是 Chrome 浏览器。 -## 通过WebDriverJs配置 +## 通过 WebDriverJs 配置 -[WebDriverJs](https://code.google.com/p/selenium/wiki/WebDriverJs) 是一个可以配合WebDriver做测试的node模块,我们会用它来做个演示。 +[WebDriverJs](https://code.google.com/p/selenium/wiki/WebDriverJs) 是一个可以配合 WebDriver 做测试的 node 模块,我们会用它来做个演示。 -### 1. 启动ChromeDriver +### 1. 启动 ChromeDriver -首先,你要下载`chromedriver`,然后运行以下命令: +首先,你要下载 `chromedriver`,然后运行以下命令: ```bash $ ./chromedriver @@ -20,17 +20,17 @@ Starting ChromeDriver (v2.10.291558) on port 9515 Only local connections are allowed. ``` -记住`9515`这个端口号,我们后面会用到 +记住 `9515` 这个端口号,我们后面会用到 -### 2. 安装WebDriverJS +### 2. 安装 WebDriverJS ```bash $ npm install selenium-webdriver ``` -### 3. 联接到ChromeDriver +### 3. 联接到 ChromeDriver -在Electron下使用`selenium-webdriver`和其平时的用法并没有大的差异,只是你需要手动设置连接ChromeDriver,以及Electron的路径: +在 Electron 下使用 `selenium-webdriver` 和其平时的用法并没有大的差异,只是你需要手动设置连接 ChromeDriver,以及 Electron 的路径: ```javascript const webdriver = require('selenium-webdriver'); @@ -59,13 +59,13 @@ driver.wait(function() { driver.quit(); ``` -## 通过WebdriverIO配置 +## 通过 WebdriverIO 配置 -[WebdriverIO](http://webdriver.io/)也是一个配合WebDriver用来测试的node模块 +[WebdriverIO](http://webdriver.io/) 也是一个配合 WebDriver 用来测试的 node 模块 -### 1. 启动ChromeDriver +### 1. 启动 ChromeDriver -首先,下载`chromedriver`,然后运行以下命令: +首先,下载 `chromedriver`,然后运行以下命令: ```bash $ chromedriver --url-base=wd/hub --port=9515 @@ -73,15 +73,15 @@ Starting ChromeDriver (v2.10.291558) on port 9515 Only local connections are allowed. ``` -记住`9515`端口,后面会用到 +记住 `9515` 端口,后面会用到 -### 2. 安装WebdriverIO +### 2. 安装 WebdriverIO ```bash $ npm install webdriverio ``` -### 3. 连接到ChromeDriver +### 3. 连接到 ChromeDriver ```javascript const webdriverio = require('webdriverio'); @@ -112,8 +112,8 @@ client ## 工作流程 -无需重新编译Electron,只要把app的源码放到[Electron的资源目录](https://github.com/atom/electron/blob/master/docs/tutorial/application-distribution.md)里就可直接开始测试了。 +无需重新编译 Electron,只要把 app 的源码放到 [Electron的资源目录](https://github.com/electron/electron/blob/master/docs/tutorial/application-distribution.md) 里就可直接开始测试了。 -当然,你也可以在运行Electron时传入参数指定你app的所在文件夹。这步可以免去你拷贝-粘贴你的app到Electron的资源目录。 +当然,你也可以在运行 Electron 时传入参数指定你 app 的所在文件夹。这步可以免去你拷贝-粘贴你的 app 到 Electron 的资源目录。 [chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ diff --git a/docs-translations/zh-CN/tutorial/using-widevine-cdm-plugin.md b/docs-translations/zh-CN/tutorial/using-widevine-cdm-plugin.md new file mode 100644 index 000000000000..d5df1646c48e --- /dev/null +++ b/docs-translations/zh-CN/tutorial/using-widevine-cdm-plugin.md @@ -0,0 +1,67 @@ +# 使用 Widevine CDM 插件 + +在 Electron ,你可以使用 Widevine CDM 插件装载 Chrome 浏览器 . + +## 获取插件 + +Electron 没有为 Widevine CDM 插件 配制许可 reasons, 为了获得它,首先需要安装官方的 chrome 浏览器,这匹配了体系架构和 Electron 构建使用的 chrome 版本 . + +__注意:__ Chrome 浏览器的主要版本必须和 Electron 使用的版本一样,否则插件不会有效,虽然 `navigator.plugins` 会显示你已经安装了它 . + +### Windows & OS X + +在 Chrome 浏览器中打开 `chrome://components/` ,找到 `WidevineCdm` 并且确定它更新到最新版本,然后你可以从 `APP_DATA/Google/Chrome/WidevineCDM/VERSION/_platform_specific/PLATFORM_ARCH/` 路径找到所有的插件二进制文件 . + +`APP_DATA` 是系统存放数据的地方,在 Windows 上它是 +`%LOCALAPPDATA%`, 在 OS X 上它是 `~/Library/Application Support`. `VERSION` 是 +Widevine CDM 插件的版本字符串, 类似 `1.4.8.866`. `PLATFORM` 是 `mac` 或 +`win`. `ARCH` 是 `x86` 或 `x64`. + +在 Windows,必要的二进制文件是 `widevinecdm.dll` and +`widevinecdmadapter.dll`, 在 OS X ,它们是 `libwidevinecdm.dylib` 和 +`widevinecdmadapter.plugin`. 你可以将它们复制到任何你喜欢的地方,但是它们必须要放在一起. + +### Linux + +在 Linux ,Chrome 浏览器将插件的二进制文件装载在一起 , 你可以在 `/opt/google/chrome` 下找到,文件名是 `libwidevinecdm.so` 和 +`libwidevinecdmadapter.so`. + +## 使用插件 + +在获得了插件文件后,你可以使用 `--widevine-cdm-path` 命令行开关来将 `widevinecdmadapter` 的路径传递给 Electron , 插件版本使用 `--widevine-cdm-version` 开关. + +__注意:__ 虽然只有 `widevinecdmadapter` 的二进制文件传递给了 Electron, `widevinecdm` 二进制文件应当放在它的旁边. + +必须在 `app` 模块的 `ready` 事件触发之前使用命令行开关,并且 page 使用的插件必须激活. + +示例代码 : + +```javascript +// You have to pass the filename of `widevinecdmadapter` here, it is +// * `widevinecdmadapter.plugin` on OS X, +// * `libwidevinecdmadapter.so` on Linux, +// * `widevinecdmadapter.dll` on Windows. +app.commandLine.appendSwitch('widevine-cdm-path', '/path/to/widevinecdmadapter.plugin'); +// The version of plugin can be got from `chrome://plugins` page in Chrome. +app.commandLine.appendSwitch('widevine-cdm-version', '1.4.8.866'); + +var mainWindow = null; +app.on('ready', function() { + mainWindow = new BrowserWindow({ + webPreferences: { + // The `plugins` have to be enabled. + plugins: true + } + }) +}); +``` + +## 验证插件 + +为了验证插件是否工作,你可以使用下面的方法 : + +* 打开开发者工具查看是否 `navigator.plugins` 包含了 Widevine +CDM 插件. +* 打开 `https://shaka-player-demo.appspot.com/` 加载一个使用 +`Widevine` 的 manifest. +* 打开 http://www.dash-player.com/demo/drm-test-area/, 检查是否界面输出 `bitdash uses Widevine in your browser`, 然后播放 video. \ No newline at end of file diff --git a/docs-translations/zh-TW/api/process.md b/docs-translations/zh-TW/api/process.md index a4f45352b9c4..ba3dd245f7a9 100644 --- a/docs-translations/zh-TW/api/process.md +++ b/docs-translations/zh-TW/api/process.md @@ -3,8 +3,8 @@ 在 Electron 裡的 `process` 物件具有以下幾個與 upstream node 的不同點: * `process.type` String - Process 的型態,可以是 `browser` (i.e. 主行程) 或 `renderer`. -* `process.versions['electron']` String - Electron 的版本 -* `process.versions['chrome']` String - Chromium 的版本 +* `process.versions.electron` String - Electron 的版本 +* `process.versions.chrome` String - Chromium 的版本 * `process.resourcesPath` String - JavaScript 源碼的路徑 # 方法 (Methods) diff --git a/docs-translations/zh-TW/tutorial/devtools-extension.md b/docs-translations/zh-TW/tutorial/devtools-extension.md index 1a419fb83756..e65ebbcfa1a5 100644 --- a/docs-translations/zh-TW/tutorial/devtools-extension.md +++ b/docs-translations/zh-TW/tutorial/devtools-extension.md @@ -4,7 +4,7 @@ 多數的 DevTools 擴充可以簡單地透過下載原始碼然後使用 `BrowserWindow.addDevToolsExtension` API 來載入它們,已載入的擴充套件會被記住,如此一來你就不用每次建立一個視窗的時候就要呼叫 API。 -** 注意: React DevTools 無法使用,參考 [issue](https://github.com/atom/electron/issues/915) ** +** 注意: React DevTools 無法使用,參考 [issue](https://github.com/electron/electron/issues/915) ** 例如使用 [React DevTools Extension](https://github.com/facebook/react-devtools),首先你需要下載它的原始碼: diff --git a/docs-translations/zh-TW/tutorial/quick-start.md b/docs-translations/zh-TW/tutorial/quick-start.md index 8c5c701f17d0..af98a0e580cd 100644 --- a/docs-translations/zh-TW/tutorial/quick-start.md +++ b/docs-translations/zh-TW/tutorial/quick-start.md @@ -153,20 +153,20 @@ $ ./electron/electron your-app/ $ ./Electron.app/Contents/MacOS/Electron your-app/ ``` -`Electron.app` 裡面是 Electron 釋出包,你可以在[這裡](https://github.com/atom/electron/releases)下載到。 +`Electron.app` 裡面是 Electron 釋出包,你可以在[這裡](https://github.com/electron/electron/releases)下載到。 # 作為版本發行 -在你完成了你的應用程式後,你可以依照 [應用部署](https://github.com/atom/electron/blob/master/docs/tutorial/application-distribution.md) 指南發布一個版本,並且運行已經打包好的應用程式。 +在你完成了你的應用程式後,你可以依照 [應用部署](https://github.com/electron/electron/blob/master/docs/tutorial/application-distribution.md) 指南發布一個版本,並且運行已經打包好的應用程式。 # 試試這個範例 -Clone 與執行本篇教學的程式碼,它們都放在 [`atom/electron-quick-start`](https://github.com/atom/electron-quick-start) 這個 repository。 +Clone 與執行本篇教學的程式碼,它們都放在 [`atom/electron-quick-start`](https://github.com/electron/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 +$ git clone https://github.com/electron/electron-quick-start # Go into the repository $ cd electron-quick-start # Install dependencies and run the app diff --git a/docs-translations/zh-TW/tutorial/using-native-node-modules.md b/docs-translations/zh-TW/tutorial/using-native-node-modules.md index 82febb6d5ab9..8d90b82d02fe 100644 --- a/docs-translations/zh-TW/tutorial/using-native-node-modules.md +++ b/docs-translations/zh-TW/tutorial/using-native-node-modules.md @@ -4,7 +4,7 @@ ## 原生 Node 模組的相容性 -原生模組可能在 Node 開始使用一個新版本的 V8 時毀損,為了確保你想要用的模組能正確與 Electron 一起運行,你應該檢查是否支援 Electron 內部 Node 版本,你可以查看 [releases](https://github.com/atom/electron/releases) 或是使用 `process.version` (範例請見 [Quick Start](https://github.com/atom/electron/blob/master/docs/tutorial/quick-start.md)) 來檢查哪個 Node 版本是現在的 Electron 使用的。 +原生模組可能在 Node 開始使用一個新版本的 V8 時毀損,為了確保你想要用的模組能正確與 Electron 一起運行,你應該檢查是否支援 Electron 內部 Node 版本,你可以查看 [releases](https://github.com/electron/electron/releases) 或是使用 `process.version` (範例請見 [Quick Start](https://github.com/electron/electron/blob/master/docs/tutorial/quick-start.md)) 來檢查哪個 Node 版本是現在的 Electron 使用的。 你可以考慮給你自己的模組使用 [NAN](https://github.com/nodejs/nan/),因為它可以較輕易的支援多種版本的 Node,它對於移植舊的模組到新版本的 Node 以便與 Electron 一起運作也是很有用的。 diff --git a/docs-translations/zh-TW/tutorial/using-selenium-and-webdriver.md b/docs-translations/zh-TW/tutorial/using-selenium-and-webdriver.md index 8f90597ddae7..e312ffa4a1d4 100644 --- a/docs-translations/zh-TW/tutorial/using-selenium-and-webdriver.md +++ b/docs-translations/zh-TW/tutorial/using-selenium-and-webdriver.md @@ -116,7 +116,7 @@ client ## 運作流程 -要在不重新建置 Electron 的情況下測試你的應用程式,只需要 [放置](https://github.com/atom/electron/blob/master/docs/tutorial/application-distribution.md) 你的應用程式到 Electron 的資源目錄中即可。 +要在不重新建置 Electron 的情況下測試你的應用程式,只需要 [放置](https://github.com/electron/electron/blob/master/docs/tutorial/application-distribution.md) 你的應用程式到 Electron 的資源目錄中即可。 或者,傳遞一個指向你應用程式資料夾的參數來透過你的 Electron 執行檔運行,這會減少複製你應用程式到 Electron 資源資料夾的需求。 diff --git a/docs/README.md b/docs/README.md index 9d2b36eb6c18..1cbe32d8dd80 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,8 +17,11 @@ an issue: ## Guides * [Supported Platforms](tutorial/supported-platforms.md) +* [Security](tutorial/security.md) +* [Electron Versioning](tutorial/electron-versioning.md) * [Application Distribution](tutorial/application-distribution.md) * [Mac App Store Submission Guide](tutorial/mac-app-store-submission-guide.md) +* [Windows Store Guide](tutorial/windows-store-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) @@ -26,6 +29,7 @@ an issue: * [DevTools Extension](tutorial/devtools-extension.md) * [Using Pepper Flash Plugin](tutorial/using-pepper-flash-plugin.md) * [Using Widevine CDM Plugin](tutorial/using-widevine-cdm-plugin.md) +* [Testing on Headless CI Systems (Travis, Jenkins)](tutorial/testing-on-headless-ci.md) ## Tutorials @@ -61,6 +65,7 @@ an issue: * [powerSaveBlocker](api/power-save-blocker.md) * [protocol](api/protocol.md) * [session](api/session.md) +* [systemPreferences](api/system-preferences.md) * [webContents](api/web-contents.md) * [Tray](api/tray.md) @@ -88,4 +93,5 @@ an issue: * [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) +* [Debug Instructions (Windows)](development/debug-instructions-windows.md) * [Setting Up Symbol Server in debugger](development/setting-up-symbol-server.md) diff --git a/docs/api/accelerator.md b/docs/api/accelerator.md index 8858d18e856e..1710d598e3d9 100644 --- a/docs/api/accelerator.md +++ b/docs/api/accelerator.md @@ -1,12 +1,14 @@ # Accelerator -An accelerator is a string that represents a keyboard shortcut. It can contain -multiple modifiers and key codes, combined by the `+` character. +> Define keyboard shortcuts. + +Accelerators can contain multiple modifiers and key codes, combined by +the `+` character. Examples: -* `Command+A` -* `Ctrl+Shift+Z` +* `CommandOrControl+A` +* `CommandOrControl+Shift+Z` ## Platform notice @@ -14,6 +16,9 @@ On Linux and Windows, the `Command` key does not have any effect so use `CommandOrControl` which represents `Command` on OS X and `Control` on Linux and Windows to define some accelerators. +Use `Alt` instead of `Option`. The `Option` key only exists on OS X, whereas +the `Alt` key is available on all platforms. + The `Super` key is mapped to the `Windows` key on Windows and Linux and `Cmd` on OS X. @@ -23,6 +28,8 @@ The `Super` key is mapped to the `Windows` key on Windows and Linux and * `Control` (or `Ctrl` for short) * `CommandOrControl` (or `CmdOrCtrl` for short) * `Alt` +* `Option` +* `AltGr` * `Shift` * `Super` @@ -34,6 +41,7 @@ The `Super` key is mapped to the `Windows` key on Windows and Linux and * Punctuations like `~`, `!`, `@`, `#`, `$`, etc. * `Plus` * `Space` +* `Tab` * `Backspace` * `Delete` * `Insert` @@ -44,3 +52,4 @@ The `Super` key is mapped to the `Windows` key on Windows and Linux and * `Escape` (or `Esc` for short) * `VolumeUp`, `VolumeDown` and `VolumeMute` * `MediaNextTrack`, `MediaPreviousTrack`, `MediaStop` and `MediaPlayPause` +* `PrintScreen` diff --git a/docs/api/app.md b/docs/api/app.md index bdf0a8a55870..becd6039c757 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -1,13 +1,13 @@ # app -The `app` module is responsible for controlling the application's lifecycle. +> Control your application's event lifecycle. The following example shows how to quit the application when the last window is closed: ```javascript -const app = require('electron').app; -app.on('window-all-closed', function() { +const {app} = require('electron'); +app.on('window-all-closed', () => { app.quit(); }); ``` @@ -34,10 +34,12 @@ Emitted when Electron has finished initialization. Emitted when all windows have been closed. -This event is only emitted when the application is not going to quit. If the -user pressed `Cmd + Q`, or the developer called `app.quit()`, Electron will -first try to close all the windows and then emit the `will-quit` event, and in -this case the `window-all-closed` event would not be emitted. +If you do not subscribe to this event and all windows are closed, the default +behavior is to quit the app; however, if you subscribe, you control whether the +app quits or not. If the user pressed `Cmd + Q`, or the developer called +`app.quit()`, Electron will first try to close all the windows and then emit the +`will-quit` event, and in this case the `window-all-closed` event would not be +emitted. ### Event: 'before-quit' @@ -87,7 +89,8 @@ handle this case (even before the `ready` event is emitted). You should call `event.preventDefault()` if you want to handle this event. -On Windows, you have to parse `process.argv` (in the main process) to get the filepath. +On Windows, you have to parse `process.argv` (in the main process) to get the +filepath. ### Event: 'open-url' _OS X_ @@ -108,8 +111,27 @@ Returns: * `event` Event * `hasVisibleWindows` Boolean -Emitted when the application is activated, which usually happens when clicks on -the applications's dock icon. +Emitted when the application is activated, which usually happens when the user +clicks on the application's dock icon. + +### Event: 'continue-activity' _OS X_ + +Returns: + +* `event` Event +* `type` String - A string identifying the activity. Maps to + [`NSUserActivity.activityType`][activity-type]. +* `userInfo` Object - Contains app-specific state stored by the activity on + another device. + +Emitted during [Handoff][handoff] when an activity from a different device wants +to be resumed. You should call `event.preventDefault()` if you want to handle +this event. + +A user activity can be continued only in an app that has the same developer Team +ID as the activity's source app and that supports the activity's type. +Supported activity types are specified in the app's `Info.plist` under the +`NSUserActivityTypes` key. ### Event: 'browser-window-blur' @@ -156,8 +178,8 @@ certificate you should prevent the default behavior with `event.preventDefault()` and call `callback(true)`. ```javascript -app.on('certificate-error', function(event, webContents, url, error, certificate, callback) { - if (url == "https://github.com") { +app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { + if (url === 'https://github.com') { // Verification logic. event.preventDefault(); callback(true); @@ -187,10 +209,10 @@ and `callback` needs to be called with an entry filtered from the list. Using certificate from the store. ```javascript -app.on('select-client-certificate', function(event, webContents, url, list, callback) { +app.on('select-client-certificate', (event, webContents, url, list, callback) => { event.preventDefault(); callback(list[0]); -}) +}); ``` ### Event: 'login' @@ -218,10 +240,10 @@ 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) { +app.on('login', (event, webContents, request, authInfo, callback) => { event.preventDefault(); callback('username', 'secret'); -}) +}); ``` ### Event: 'gpu-process-crashed' @@ -232,7 +254,8 @@ Emitted when the gpu process crashes. The `app` object has the following methods: -**Note:** Some methods are only available on specific operating systems and are labeled as such. +**Note:** Some methods are only available on specific operating systems and are +labeled as such. ### `app.quit()` @@ -244,14 +267,6 @@ 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.hide()` _OS X_ - -Hides all application windows without minimizing them. - -### `app.show()` _OS X_ - -Shows application windows after they were hidden. Does not automatically focus them. - ### `app.exit(exitCode)` * `exitCode` Integer @@ -261,6 +276,47 @@ 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.relaunch([options])` + +* `options` Object (optional) + * `args` Array (optional) + * `execPath` String (optional) + +Relaunches the app when current instance exits. + +By default the new instance will use the same working directory and command line +arguments with current instance. When `args` is specified, the `args` will be +passed as command line arguments instead. When `execPath` is specified, the +`execPath` will be executed for relaunch instead of current app. + +Note that this method does not quit the app when executed, you have to call +`app.quit` or `app.exit` after calling `app.relaunch` to make the app restart. + +When `app.relaunch` is called for multiple times, multiple instances will be +started after current instance exited. + +An example of restarting current instance immediately and adding a new command +line argument to the new instance: + +```javascript +app.relaunch({args: process.argv.slice(1) + ['--relaunch']}) +app.exit(0) +``` + +### `app.focus()` + +On Linux, focuses on the first visible window. On OS X, makes the application +the active app. On Windows, focuses on the application's first window. + +### `app.hide()` _OS X_ + +Hides all application windows without minimizing them. + +### `app.show()` _OS X_ + +Shows application windows after they were hidden. Does not automatically focus +them. + ### `app.getAppPath()` Returns the current application directory. @@ -322,10 +378,21 @@ to the npm modules spec. You should usually also specify a `productName` field, which is your application's full capitalized name, and which will be preferred over `name` by Electron. +### `app.setName(name)` + +* `name` String + +Overrides the current application's name. + ### `app.getLocale()` Returns the current application locale. +**Note:** When distributing your packaged app, you have to also ship the +`locales` folder. + +**Note:** On Windows you have to call it after the `ready` events gets emitted. + ### `app.addRecentDocument(path)` _OS X_ _Windows_ * `path` String @@ -339,6 +406,47 @@ bar, and on OS X you can visit it from dock menu. Clears the recent documents list. +### `app.setAsDefaultProtocolClient(protocol)` _OS X_ _Windows_ + +* `protocol` String - The name of your protocol, without `://`. If you want your + app to handle `electron://` links, call this method with `electron` as the + parameter. + +This method sets the current executable as the default handler for a protocol +(aka URI scheme). It allows you to integrate your app deeper into the operating +system. Once registered, all links with `your-protocol://` will be openend with +the current executable. The whole link, including protocol, will be passed to +your application as a parameter. + +**Note:** On OS X, you can only register protocols that have been added to +your app's `info.plist`, which can not be modified at runtime. You can however +change the file with a simple text editor or script during build time. +Please refer to [Apple's documentation][CFBundleURLTypes] for details. + +The API uses the Windows Registry and LSSetDefaultHandlerForURLScheme internally. + +### `app.removeAsDefaultProtocolClient(protocol)` _OS X_ _Windows_ + +* `protocol` String - The name of your protocol, without `://`. + +This method checks if the current executable as the default handler for a +protocol (aka URI scheme). If so, it will remove the app as the default handler. + +### `app.isDefaultProtocolClient(protocol)` _OS X_ _Windows_ + +* `protocol` String - The name of your protocol, without `://`. + +This method checks if the current executable is the default handler for a protocol +(aka URI scheme). If so, it will return true. Otherwise, it will return false. + +**Note:** On OS X, you can use this method to check if the app has been +registered as the default protocol handler for a protocol. You can also verify +this by checking `~/Library/Preferences/com.apple.LaunchServices.plist` on the +OS X machine. Please refer to +[Apple's documentation][LSCopyDefaultHandlerForURLScheme] for details. + +The API uses the Windows Registry and LSCopyDefaultHandlerForURLScheme internally. + ### `app.setUserTasks(tasks)` _Windows_ * `tasks` Array - Array of `Task` objects @@ -362,16 +470,6 @@ 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 @@ -404,10 +502,10 @@ 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; +```javascript +let myWindow = null; -var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) { +const shouldQuit = app.makeSingleInstance((commandLine, workingDirectory) => { // Someone tried to run a second instance, we should focus our window. if (myWindow) { if (myWindow.isMinimized()) myWindow.restore(); @@ -421,45 +519,47 @@ if (shouldQuit) { } // Create myWindow, load the rest of the app, etc... -app.on('ready', function() { +app.on('ready', () => { }); ``` +### `app.releaseSingleInstance()` + +Releases all locks that were created by `makeSingleInstance`. This will allow +multiple instances of the application to once again run side by side. + +### `app.setUserActivity(type, userInfo[, webpageURL])` _OS X_ + +* `type` String - Uniquely identifies the activity. Maps to + [`NSUserActivity.activityType`][activity-type]. +* `userInfo` Object - App-specific state to store for use by another device. +* `webpageURL` String - The webpage to load in a browser if no suitable app is + installed on the resuming device. The scheme must be `http` or `https`. + +Creates an `NSUserActivity` and sets it as the current activity. The activity +is eligible for [Handoff][handoff] to another device afterward. + +### `app.getCurrentActivityType()` _OS X_ + +Returns the type of the currently running activity. + ### `app.setAppUserModelId(id)` _Windows_ * `id` String Changes the [Application User Model ID][app-user-model-id] to `id`. -### `app.isAeroGlassEnabled()` _Windows_ +### `app.importCertificate(options, callback)` _LINUX_ -This method returns `true` if [DWM composition](https://msdn.microsoft.com/en-us/library/windows/desktop/aa969540.aspx) -(Aero Glass) is enabled, and `false` otherwise. You can use it to determine if -you should create a transparent window or not (transparent windows won't work -correctly when DWM composition is disabled). +* `options` Object + * `certificate` String - Path for the pkcs12 file. + * `password` String - Passphrase for the certificate. +* `callback` Function + * `result` Integer - Result of import. -Usage example: - -```js -let browserOptions = {width: 1000, height: 800}; - -// Make the window transparent only if the platform supports it. -if (process.platform !== 'win32' || app.isAeroGlassEnabled()) { - browserOptions.transparent = true; - browserOptions.frame = false; -} - -// Create the window. -win = new BrowserWindow(browserOptions); - -// Navigate. -if (browserOptions.transparent) { - win.loadURL('file://' + __dirname + '/index.html'); -} else { - // No transparency, so we load a fallback that uses basic styles. - win.loadURL('file://' + __dirname + '/fallback.html'); -} -``` +Imports the certificate in pkcs12 format into the platform certificate store. +`callback` is called with the `result` of import operation, a value of `0` +indicates success while any other value indicates failure according to chromium [net_error_list](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h). ### `app.commandLine.appendSwitch(switch[, value])` @@ -495,6 +595,12 @@ Returns an ID representing the request. Cancel the bounce of `id`. +### `app.dock.downloadFinished(filePath)` _OS X_ + +* `filePath` String + +Bounces the Downloads stack if the filePath is inside the Downloads folder. + ### `app.dock.setBadge(text)` _OS X_ * `text` String @@ -528,3 +634,7 @@ Sets the `image` associated with this dock icon. [dock-menu]:https://developer.apple.com/library/mac/documentation/Carbon/Conceptual/customizing_docktile/concepts/dockconcepts.html#//apple_ref/doc/uid/TP30000986-CH2-TPXREF103 [tasks]:http://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks [app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx +[CFBundleURLTypes]: https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/TP40009249-102207-TPXREF115 +[LSCopyDefaultHandlerForURLScheme]: https://developer.apple.com/library/mac/documentation/Carbon/Reference/LaunchServicesReference/#//apple_ref/c/func/LSCopyDefaultHandlerForURLScheme +[handoff]: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html +[activity-type]: https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSUserActivity_Class/index.html#//apple_ref/occ/instp/NSUserActivity/activityType diff --git a/docs/api/auto-updater.md b/docs/api/auto-updater.md index 0a5ca2d2d3dd..a2eec31ecbf1 100644 --- a/docs/api/auto-updater.md +++ b/docs/api/auto-updater.md @@ -1,6 +1,19 @@ # autoUpdater -This module provides an interface for the `Squirrel` auto-updater framework. +> Enable apps to automatically update themselves. + +The `autoUpdater` module provides an interface for the +[Squirrel](https://github.com/Squirrel) framework. + +You can quickly launch a multi-platform release server for distributing your +application by using one of these projects: + +- [nuts][nuts]: *A smart release server for your applications, using GitHub as a backend. Auto-updates with Squirrel (Mac & Windows)* +- [electron-release-server][electron-release-server]: *A fully featured, + self-hosted release server for electron applications, compatible with + auto-updater* +- [squirrel-updates-server][squirrel-updates-server]: *A simple node.js server + for Squirrel.Mac and Squirrel.Windows which uses GitHub releases* ## Platform notices @@ -13,11 +26,15 @@ 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]. +**Note:** Your application must be signed for automatic updates on Mac OS X. +This is a requirement of `Squirrel.Mac`. + ### Windows 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. +use the `autoUpdater`, so it is recommended that you use the +[electron-winstaller][installer-lib] module or the [grunt-electron-installer][installer] +package to generate a Windows installer. The installer generated with Squirrel will create a shortcut icon with an [Application User Model ID][app-user-model-id] in the format of @@ -97,5 +114,9 @@ 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 +[installer]: https://github.com/electron/grunt-electron-installer +[installer-lib]: https://github.com/electron/windows-installer [app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx +[electron-release-server]: https://github.com/ArekSredzki/electron-release-server +[squirrel-updates-server]: https://github.com/Aluxian/squirrel-updates-server +[nuts]: https://github.com/GitbookIO/nuts diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 2800ac38a049..ecdbece771b7 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -1,17 +1,16 @@ # BrowserWindow -The `BrowserWindow` class gives you the ability to create a browser window. For -example: +> Create and control browser windows. ```javascript // In the main process. -const BrowserWindow = require('electron').BrowserWindow; +const {BrowserWindow} = require('electron'); // Or in the renderer process. -const BrowserWindow = require('electron').remote.BrowserWindow; +const {BrowserWindow} = require('electron').remote; -var win = new BrowserWindow({ width: 800, height: 600, show: false }); -win.on('closed', function() { +let win = new BrowserWindow({width: 800, height: 600, show: false}); +win.on('closed', () => { win = null; }); @@ -34,10 +33,10 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. * `options` Object * `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. + * `x` Integer (**required** if y is used) - Window's left offset from screen. + Default is to center the window. + * `y` Integer (**required** if x is used) - 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. Default is `false`. @@ -59,16 +58,17 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. other windows. Default is `false`. * `fullscreen` Boolean - Whether the window should show in fullscreen. When explicitly set to `false` the fullscreen button will be hidden or disabled - on OS X, or the maximize button will be disabled on Windows. Default is - `false`. - * `fullscreenable` Boolean - Whether the maximize/zoom button on OS X should - toggle full screen mode or maximize window. Default is `true`. + on OS X. Default is `false`. + * `fullscreenable` Boolean - Whether the window can be put into fullscreen + mode. On OS X, also whether the maximize/zoom button should toggle full + screen mode or maximize window. Default is `true`. * `skipTaskbar` Boolean - Whether to show the window in taskbar. Default is `false`. * `kiosk` Boolean - The kiosk mode. Default is `false`. * `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. + * `icon` [NativeImage](native-image.md) - The window icon. On Windows it is + recommended to use `ICO` icons to get best visual effects, you can also + leave it undefined so the executable's icon will be used. * `show` Boolean - Whether window should be shown when created. Default is `true`. * `frame` Boolean - Specify `false` to create a @@ -84,8 +84,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. than screen. Default is `false`. * `backgroundColor` String - Window's background color as Hexadecimal value, like `#66CD00` or `#FFF` or `#80FFFFFF` (alpha is supported). Default is - `#000` (black) for Linux and Windows, `#FFF` for Mac (or clear if - transparent). + `#FFF` (white). * `hasShadow` Boolean - Whether window should have a shadow. This is only implemented on OS X. Default is `true`. * `darkTheme` Boolean - Forces using dark theme for the window, only works on @@ -99,6 +98,11 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. * `webPreferences` Object - Settings of web page's features. See more about this below. +When setting minimum or maximum window size with `minWidth`/`maxWidth`/ +`minHeight`/`maxHeight`, it only constrains the users, it won't prevent you from +passing a size that does not follow size constraints to `setBounds`/`setSize` or +to the constructor of `BrowserWindow`. + The possible values and behaviors of `type` option are platform dependent, supported values are: @@ -168,6 +172,8 @@ The `webPreferences` option is an object that can have following properties: canvas features. Default is `false`. * `directWrite` Boolean - Enables DirectWrite font rendering system on Windows. Default is `true`. +* `scrollBounce` Boolean - Enables scroll bounce (rubber banding) effect on + OS X. Default is `false`. * `blinkFeatures` String - A list of feature strings separated by `,`, like `CSSVariables,KeyboardEventKey`. The full list of supported feature strings can be found in the [setFeatureEnabledFromString][blink-feature-string] @@ -181,6 +187,8 @@ The `webPreferences` option is an object that can have following properties: * `defaultMonospaceFontSize` Integer - Defaults to `13`. * `minimumFontSize` Integer - Defaults to `0`. * `defaultEncoding` String - Defaults to `ISO-8859-1`. +* `backgroundThrottling` Boolean - Whether to throttle animations and timers + when the page becomes background. Defaults to `true`. ## Events @@ -210,17 +218,17 @@ will cancel the close. Usually you would want to use the `beforeunload` handler to decide whether the window should be closed, which will also be called when the window is -reloaded. In Electron, returning an empty string or `false` would cancel the +reloaded. In Electron, returning any value other than `undefined` would cancel the close. For example: ```javascript -window.onbeforeunload = function(e) { +window.onbeforeunload = (e) => { console.log('I do not want to be closed'); - // Unlike usual browsers, in which a string should be returned and the user is - // prompted to confirm the page unload, Electron gives developers more options. - // Returning empty string or false would prevent the unloading now. - // You can also use the dialog API to let the user confirm closing the application. + // Unlike usual browsers that a message box will be prompted to users, returning + // a non-void value will silently cancel the close. + // It is recommended to use the dialog API to let the user confirm closing the + // application. e.returnValue = false; }; ``` @@ -246,6 +254,14 @@ Emitted when the window loses focus. Emitted when the window gains focus. +### Event: 'show' + +Emitted when the window is shown. + +### Event: 'hide' + +Emitted when the window is hidden. + ### Event: 'maximize' Emitted when window is maximized. @@ -294,12 +310,21 @@ Emitted when the window leaves full screen state triggered by html api. ### Event: 'app-command' _Windows_ +Returns: + +* `event` Event +* `command` String + Emitted when an [App Command](https://msdn.microsoft.com/en-us/library/windows/desktop/ms646275(v=vs.85).aspx) is invoked. These are typically related to keyboard media keys or browser commands, as well as the "Back" button built into some mice on Windows. -```js -someWindow.on('app-command', function(e, cmd) { +Commands are lowercased with underscores replaced with hyphens and the +`APPCOMMAND_` prefix stripped off. +e.g. `APPCOMMAND_BROWSER_BACKWARD` is emitted as `browser-backward`. + +```javascript +someWindow.on('app-command', (e, cmd) => { // Navigate the window back when the user hits their mouse back button if (cmd === 'browser-backward' && someWindow.webContents.canGoBack()) { someWindow.webContents.goBack(); @@ -315,6 +340,15 @@ Emitted when scroll wheel event phase has begun. Emitted when scroll wheel event phase has ended. +### Event: 'swipe' _OS X_ + +Returns: + +* `event` Event +* `direction` String + +Emitted on 3-finger swipe. Possible directions are `up`, `right`, `down`, `left`. + ## Methods The `BrowserWindow` object has the following methods: @@ -360,7 +394,7 @@ Objects created with `new BrowserWindow` have the following properties: ```javascript // In this example `win` is our instance -var win = new BrowserWindow({ width: 800, height: 600 }); +let win = new BrowserWindow({width: 800, height: 600}); ``` ### `win.webContents` @@ -398,6 +432,10 @@ the [close event](#event-close). Focus on the window. +### `win.blur()` + +Remove focus on the window. + ### `win.isFocused()` Returns a boolean, whether the window is focused. @@ -582,17 +620,17 @@ nothing. Returns whether the window can be manually maximized by user. On Linux always returns `true`. -### `win.setFullScreenable(fullscreenable)` _OS X_ +### `win.setFullScreenable(fullscreenable)` * `fullscreenable` Boolean Sets whether the maximize/zoom window button toggles fullscreen mode or -maximizes the window. On Windows and Linux does nothing. +maximizes the window. -### `win.isFullScreenable()` _OS X_ +### `win.isFullScreenable()` Returns whether the maximize/zoom window button toggles fullscreen mode or -maximizes the window. On Windows and Linux always returns `true`. +maximizes the window. ### `win.setClosable(closable)` _OS X_ _Windows_ @@ -646,6 +684,17 @@ Returns the title of the native window. **Note:** The title of web page can be different from the title of the native window. +### `win.setSheetOffset(offsetY[, offsetX])` _OS X_ + +Changes the attachment point for sheets on Mac OS X. By default, sheets are +attached just below the window frame, but you may want to display them beneath +a HTML-rendered toolbar. For example: + +```javascript +let toolbarRect = document.getElementById('toolbar').getBoundingClientRect(); +win.setSheetOffset(toolbarRect.height); +``` + ### `win.flashFrame(flag)` * `flag` Boolean @@ -739,14 +788,6 @@ be called with `callback(image)`. The `image` is an instance of [NativeImage](native-image.md) that stores data of the snapshot. Omitting `rect` will capture the whole visible page. -### `win.print([options])` - -Same as `webContents.print([options])` - -### `win.printToPDF(options, callback)` - -Same as `webContents.printToPDF(options, callback)` - ### `win.loadURL(url[, options])` Same as `webContents.loadURL(url[, options])`. @@ -783,8 +824,8 @@ cleared * `description` String - a description that will be provided to Accessibility screen readers -Sets a 16 x 16 pixel overlay onto the current taskbar icon, usually used to convey some -sort of application status or to passively notify the user. +Sets a 16 x 16 pixel overlay onto the current taskbar icon, usually used to +convey some sort of application status or to passively notify the user. ### `win.setHasShadow(hasShadow)` _OS X_ @@ -838,6 +879,12 @@ The `flags` is an array that can include following `String`s: Shows pop-up dictionary that searches the selected word on the page. +### `win.setIcon(icon)` _Windows_ _Linux_ + +* `icon` [NativeImage](native-image.md) + +Changes window icon. + ### `win.setAutoHideMenuBar(hide)` * `hide` Boolean @@ -883,4 +930,4 @@ Returns whether the window is visible on all workspaces. Ignore all moused events that happened in the window. -[blink-feature-string]: https://code.google.com/p/chromium/codesearch#chromium/src/out/Debug/gen/blink/platform/RuntimeEnabledFeatures.cpp&sq=package:chromium&type=cs&l=527 +[blink-feature-string]: https://code.google.com/p/chromium/codesearch#chromium/src/out/Debug/gen/blink/platform/RuntimeEnabledFeatures.cpp&sq=package:chromium&type=cs&l=576 diff --git a/docs/api/chrome-command-line-switches.md b/docs/api/chrome-command-line-switches.md index 65f096eac6e0..4fe6d136da2b 100644 --- a/docs/api/chrome-command-line-switches.md +++ b/docs/api/chrome-command-line-switches.md @@ -1,24 +1,21 @@ -# Supported Chrome command line switches +# 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: +> Command line switches 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 the [app][app] module +is emitted: ```javascript -const app = require('electron').app; +const {app} = require('electron'); app.commandLine.appendSwitch('remote-debugging-port', '8315'); app.commandLine.appendSwitch('host-rules', 'MAP * 127.0.0.1'); -app.on('ready', function() { +app.on('ready', () => { // Your code here }); ``` -## --client-certificate=`path` - -Sets the `path` of client certificate file. - ## --ignore-connections-limit=`domains` Ignore the connections limit for `domains` list separated by `,`. @@ -27,6 +24,10 @@ Ignore the connections limit for `domains` list separated by `,`. Disables the disk cache for HTTP requests. +## --disable-http2 + +Disable HTTP/2 and SPDY/3.1 protocols. + ## --remote-debugging-port=`port` Enables remote debugging over HTTP on the specified `port`. @@ -94,6 +95,24 @@ connection, and the endpoint host in a `SOCKS` proxy connection). Like `--host-rules` but these `rules` only apply to the host resolver. +## --auth-server-whitelist=`url` + +A comma-separated list of servers for which integrated authentication is enabled. + +For example: + +``` +--auth-server-whitelist='*example.com, *foobar.com, *baz' +``` + +then any `url` ending with `example.com`, `foobar.com`, `baz` will be considered +for integrated authentication. Without `*` prefix the url has to match exactly. + +## --auth-negotiate-delegate-whitelist=`url` + +A comma-separated list of servers for which delegation of user credentials is required. +Without `*` prefix the url has to match exactly. + ## --ignore-certificate-errors Ignores certificate related errors. @@ -112,7 +131,7 @@ Enables net log events to be saved and writes them to `path`. ## --ssl-version-fallback-min=`version` -Sets 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` diff --git a/docs/api/clipboard.md b/docs/api/clipboard.md index 7f95a1af26d2..c2a16a79b9fb 100644 --- a/docs/api/clipboard.md +++ b/docs/api/clipboard.md @@ -1,10 +1,11 @@ # clipboard -The `clipboard` module provides methods to perform copy and paste operations. +> Perform copy and paste operations on the system clipboard. + The following example shows how to write a string to the clipboard: ```javascript -const clipboard = require('electron').clipboard; +const {clipboard} = require('electron'); clipboard.writeText('Example String'); ``` @@ -35,13 +36,13 @@ Returns the content in the clipboard as plain text. Writes the `text` into the clipboard as plain text. -### `clipboard.readHtml([type])` +### `clipboard.readHTML([type])` * `type` String (optional) Returns the content in the clipboard as markup. -### `clipboard.writeHtml(markup[, type])` +### `clipboard.writeHTML(markup[, type])` * `markup` String * `type` String (optional) @@ -61,13 +62,13 @@ Returns the content in the clipboard as a [NativeImage](native-image.md). Writes `image` to the clipboard. -### `clipboard.readRtf([type])` +### `clipboard.readRTF([type])` * `type` String (optional) Returns the content in the clipboard as RTF. -### `clipboard.writeRtf(text[, type])` +### `clipboard.writeRTF(text[, type])` * `text` String * `type` String (optional) diff --git a/docs/api/content-tracing.md b/docs/api/content-tracing.md index 0b83c2759c67..d269d602ef59 100644 --- a/docs/api/content-tracing.md +++ b/docs/api/content-tracing.md @@ -1,23 +1,25 @@ # contentTracing -The `content-tracing` module is used to collect tracing data generated by the -underlying Chromium content module. This module does not include a web interface -so you need to open `chrome://tracing/` in a Chrome browser and load the -generated file to view the result. +> Collect tracing data from Chromium's content module for finding performance +bottlenecks and slow operations. + +This module does not include a web interface so you need to open +`chrome://tracing/` in a Chrome browser and load the generated file to view the +result. ```javascript -const contentTracing = require('electron').contentTracing; +const {contentTracing} = require('electron'); const options = { categoryFilter: '*', traceOptions: 'record-until-full,enable-sampling' -} +}; -contentTracing.startRecording(options, function() { +contentTracing.startRecording(options, () => { console.log('Tracing started'); - setTimeout(function() { - contentTracing.stopRecording('', function(path) { + setTimeout(() => { + contentTracing.stopRecording('', (path) => { console.log('Tracing data recorded to ' + path); }); }, 5000); @@ -26,7 +28,7 @@ contentTracing.startRecording(options, function() { ## Methods -The `content-tracing` module has the following methods: +The `contentTracing` module has the following methods: ### `contentTracing.getCategories(callback)` diff --git a/docs/api/crash-reporter.md b/docs/api/crash-reporter.md index 98465dffc971..1a07ac31d418 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -1,12 +1,12 @@ # crashReporter -The `crash-reporter` module enables sending your app's crash reports. +> Submit crash reports to a remote server. The following is an example of automatically submitting a crash report to a remote server: ```javascript -const crashReporter = require('electron').crashReporter; +const {crashReporter} = require('electron'); crashReporter.start({ productName: 'YourName', @@ -16,6 +16,12 @@ crashReporter.start({ }); ``` +For setting up a server to accept and process crash reports, you can use +following projects: + +* [socorro](https://github.com/mozilla/socorro) +* [mini-breakpad-server](https://github.com/electron/mini-breakpad-server) + ## Methods The `crash-reporter` module has the following methods: @@ -54,7 +60,8 @@ ID. ## crash-reporter Payload -The crash reporter will send the following data to the `submitURL` as `POST`: +The crash reporter will send the following data to the `submitURL` as +a `multipart/form-data` `POST`: * `ver` String - The version of Electron. * `platform` String - e.g. 'win32'. @@ -66,6 +73,6 @@ The crash reporter will send the following data to the `submitURL` as `POST`: * `prod` String - Name of the underlying product. In this case Electron. * `_companyName` String - The company name in the `crashReporter` `options` object. -* `upload_file_minidump` File - The crash report as file. +* `upload_file_minidump` File - The crash report in the format of `minidump`. * All level one properties of the `extra` object in the `crashReporter`. `options` object diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index 7863622e372a..a72539a7a283 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -1,16 +1,16 @@ # desktopCapturer -The `desktopCapturer` module can be used to get available sources that can be -used to be captured with `getUserMedia`. +> List `getUserMedia` sources for capturing audio, video, and images from a +microphone, camera, or screen. ```javascript // In the renderer process. -var desktopCapturer = require('electron').desktopCapturer; +const {desktopCapturer} = require('electron'); -desktopCapturer.getSources({types: ['window', 'screen']}, function(error, sources) { +desktopCapturer.getSources({types: ['window', 'screen']}, (error, sources) => { if (error) throw error; - for (var i = 0; i < sources.length; ++i) { - if (sources[i].name == "Electron") { + for (let i = 0; i < sources.length; ++i) { + if (sources[i].name === 'Electron') { navigator.webkitGetUserMedia({ audio: false, video: { @@ -40,7 +40,7 @@ function getUserMediaError(e) { When creating a constraints object for the `navigator.webkitGetUserMedia` call, if you are using a source from `desktopCapturer` your `chromeMediaSource` must -be set to `"desktop"` and your `audio` must be set to `false`. +be set to `"desktop"` and your `audio` must be set to `false`. If you wish to capture the audio and video from the entire desktop you can set @@ -65,13 +65,14 @@ Starts a request to get all desktop sources, `callback` will be called with The `sources` is an array of `Source` objects, each `Source` represents a captured screen or individual window, and has following properties: + * `id` String - The id of the captured window or screen used in `navigator.webkitGetUserMedia`. The format looks like `window:XX` or `screen:XX` where `XX` is a random generated number. * `name` String - The described name of the capturing screen or window. If the source is a screen, the name will be `Entire Screen` or `Screen `; if it is a window, the name will be the window's title. -* `thumbnail` [NativeImage](NativeImage.md) - A thumbnail image. +* `thumbnail` [NativeImage](native-image.md) - A thumbnail native image. **Note:** There is no guarantee that the size of `source.thumbnail` is always the same as the `thumnbailSize` in `options`. It also depends on the scale of diff --git a/docs/api/dialog.md b/docs/api/dialog.md index 84a22ef692b3..c276f2e2d5b7 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -1,20 +1,22 @@ # dialog -The `dialog` module provides APIs to show native system dialogs, such as opening -files or alerting, so web applications can deliver the same user experience as -native applications. +> Display native system dialogs for opening and saving files, alerting, etc. An example of showing a dialog to select multiple files and directories: ```javascript -var win = ...; // BrowserWindow in which to show the dialog -const dialog = require('electron').dialog; -console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', 'multiSelections' ]})); +let win = ...; // BrowserWindow in which to show the dialog +const {dialog} = require('electron'); + +console.log(dialog.showOpenDialog({properties: ['openFile', 'openDirectory', 'multiSelections']})); ``` -**Note for OS X**: If you want to present dialogs as sheets, the only thing you -have to do is provide a `BrowserWindow` reference in the `browserWindow` -parameter. +The Dialog is opened from Electron's main thread. If you want to use the dialog +object from a renderer process, remember to access it using the remote: + +```javascript +const {dialog} = require('electron').remote; +``` ## Methods @@ -26,6 +28,8 @@ The `dialog` module has the following methods: * `options` Object * `title` String * `defaultPath` String + * `buttonLabel` String - Custom label for the confirmation button, when + left empty the default label will be used. * `filters` Array * `properties` Array - Contains which features the dialog should use, can contain `openFile`, `openDirectory`, `multiSelections` and @@ -41,10 +45,10 @@ selected when you want to limit the user to a specific type. For example: ```javascript { filters: [ - { name: 'Images', extensions: ['jpg', 'png', 'gif'] }, - { name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] }, - { name: 'Custom File Type', extensions: ['as'] }, - { name: 'All Files', extensions: ['*'] } + {name: 'Images', extensions: ['jpg', 'png', 'gif']}, + {name: 'Movies', extensions: ['mkv', 'avi', 'mp4']}, + {name: 'Custom File Type', extensions: ['as']}, + {name: 'All Files', extensions: ['*']} ] } ``` @@ -67,6 +71,8 @@ shown. * `options` Object * `title` String * `defaultPath` String + * `buttonLabel` String - Custom label for the confirmation button, when + left empty the default label will be used. * `filters` Array * `callback` Function (optional) @@ -86,7 +92,8 @@ will be passed via `callback(filename)` * `type` String - Can be `"none"`, `"info"`, `"error"`, `"question"` or `"warning"`. On Windows, "question" displays the same icon as "info", unless you set an icon using the "icon" option. - * `buttons` Array - Array of texts for buttons. + * `buttons` Array - Array of texts for buttons. On Windows, an empty array + will result in one button labeled "OK". * `defaultId` Integer - Index of the button in the buttons array which will be selected by default when the message box opens. * `title` String - Title of the message box, some platforms will not show it. @@ -119,3 +126,12 @@ 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. If called before the app `ready`event on Linux, the message will be emitted to stderr, and no GUI dialog will appear. + +## Sheets + +On Mac OS X, dialogs are presented as sheets attached to a window if you provide +a `BrowserWindow` reference in the `browserWindow` parameter, or modals if no +window is provided. + +You can call `BrowserWindow.getCurrentWindow().setSheetOffset(offset)` to change +the offset from the window frame where sheets are attached. diff --git a/docs/api/download-item.md b/docs/api/download-item.md index 756353b8ba3d..14d1931e4468 100644 --- a/docs/api/download-item.md +++ b/docs/api/download-item.md @@ -1,27 +1,30 @@ # DownloadItem -`DownloadItem` is an EventEmitter represents a download item in Electron. It -is used in `will-download` event of `Session` module, and allows users to +> Control file downloads from remote sources. + +`DownloadItem` is an EventEmitter that represents a download item in Electron. +It is used in `will-download` event of `Session` module, and allows users to control the download item. ```javascript // In the main process. -win.webContents.session.on('will-download', function(event, item, webContents) { +win.webContents.session.on('will-download', (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() { + item.on('updated', () => { console.log('Received bytes: ' + item.getReceivedBytes()); }); - item.on('done', function(e, state) { - if (state == "completed") { - console.log("Download successfully"); + item.on('done', (e, state) => { + if (state === 'completed') { + console.log('Download successfully'); } else { - console.log("Download is cancelled or interrupted that can't be resumed"); + console.log('Download is cancelled or interrupted that can\'t be resumed'); } }); +}); ``` ## Events diff --git a/docs/api/environment-variables.md b/docs/api/environment-variables.md index 3483c19d6431..a4df27504b57 100644 --- a/docs/api/environment-variables.md +++ b/docs/api/environment-variables.md @@ -1,56 +1,54 @@ -# Environment variables +# Environment Variables -Some behaviors of Electron are controlled by environment variables, because they -are initialized earlier than command line and the app's code. +> Control application configuration and behavior without changing code. -Examples on POSIX shells: +Certain Electron behaviors are controlled by environment variables because they +are initialized earlier than the command line flags and the app's code. + +POSIX shell example: ```bash $ export ELECTRON_ENABLE_LOGGING=true $ electron ``` -on Windows console: +Windows console example: ```powershell > set ELECTRON_ENABLE_LOGGING=true > electron ``` -## `ELECTRON_RUN_AS_NODE` +### `ELECTRON_RUN_AS_NODE` Starts the process as a normal Node.js process. -## `ELECTRON_ENABLE_LOGGING` +### `ELECTRON_ENABLE_LOGGING` -Prints Chrome's internal logging to console. +Prints Chrome's internal logging to the console. -## `ELECTRON_LOG_ASAR_READS` +### `ELECTRON_LOG_ASAR_READS` When Electron reads from an ASAR file, log the read offset and file path to the system `tmpdir`. The resulting file can be provided to the ASAR module to optimize file ordering. -## `ELECTRON_ENABLE_STACK_DUMPING` +### `ELECTRON_ENABLE_STACK_DUMPING` -When Electron crashed, prints the stack trace to console. +Prints the stack trace to the console when Electron crashes. -This environment variable will not work if `crashReporter` is started. +This environment variable will not work if the `crashReporter` is started. -## `ELECTRON_DEFAULT_ERROR_MODE` _Windows_ +### `ELECTRON_DEFAULT_ERROR_MODE` _Windows_ -Shows Windows's crash dialog when Electron crashed. +Shows the Windows's crash dialog when Electron crashes. -This environment variable will not work if `crashReporter` is started. +This environment variable will not work if the `crashReporter` is started. -## `ELECTRON_NO_ATTACH_CONSOLE` _Windows_ +### `ELECTRON_NO_ATTACH_CONSOLE` _Windows_ -Don't attach to current console session. +Don't attach to the current console session. -## `ELECTRON_FORCE_WINDOW_MENU_BAR` _Linux_ +### `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')`. +Don't use the global menu bar on Linux. diff --git a/docs/api/file-object.md b/docs/api/file-object.md index 01d49c39155f..d0f36e273dfa 100644 --- a/docs/api/file-object.md +++ b/docs/api/file-object.md @@ -1,4 +1,6 @@ -# `File` object +# `File` Object + +> Use the HTML5 `File` API to work natively with files on the filesystem. The DOM's File interface provides abstraction around native files in order to let users work on native files directly with the HTML5 file API. Electron has @@ -13,16 +15,16 @@ Example on getting a real path from a dragged-onto-the-app file: @@ -48,36 +48,29 @@ 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: +As of 0.37, you can use +[destructuring assignment][destructuring-assignment] to make it easier to use +built-in modules. ```javascript -const {app, BrowserWindow} = require('electron') +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: +If you need the entire `electron` module, you can require it and then using +destructuring to access the individual modules from `electron`. ```javascript -process.env.ELECTRON_HIDE_INTERNAL_MODULES = 'true' +const electron = require('electron'); +const {app, BrowserWindow} = electron; ``` -Or call the `hideInternalModules` API: +This is equivalent to the following code: ```javascript -require('electron').hideInternalModules() +const electron = require('electron'); +const app = electron.app; +const BrowserWindow = electron.BrowserWindow; ``` [gui]: https://en.wikipedia.org/wiki/Graphical_user_interface -[desctructuring-assignment]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment -[issue-387]: https://github.com/atom/electron/issues/387 +[destructuring-assignment]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment diff --git a/docs/api/system-preferences.md b/docs/api/system-preferences.md new file mode 100644 index 000000000000..7b2f0724a4aa --- /dev/null +++ b/docs/api/system-preferences.md @@ -0,0 +1,86 @@ +# systemPreferences + +> Get system preferences. + +## Methods + +### `systemPreferences.isDarkMode()` _OS X_ + +This method returns `true` if the system is in Dark Mode, and `false` otherwise. + +### `systemPreferences.subscribeNotification(event, callback)` _OS X_ + +* `event` String +* `callback` Function + +Subscribes to native notifications of OS X, `callback` will be called with +`callback(event, userInfo)` when the corresponding `event` happens. The +`userInfo` is an Object that contains the user information dictionary sent +along with the notification. + +The `id` of the subscriber is returned, which can be used to unsubscribe the +`event`. + +Under the hood this API subscribes to `NSDistributedNotificationCenter`, +example values of `event` are: + +* `AppleInterfaceThemeChangedNotification` +* `AppleAquaColorVariantChanged` +* `AppleColorPreferencesChangedNotification` +* `AppleShowScrollBarsSettingChanged` + +### `systemPreferences.unsubscribeNotification(id)` _OS X_ + +* `id` Integer + +Removes the subscriber with `id`. + +### `systemPreferences.getUserDefault(key, type)` _OS X_ + +* `key` String +* `type` String - Can be `string`, `boolean`, `integer`, `float`, `double`, + `url`, `array`, `dictionary` + +Get the value of `key` in system preferences. + +This API reads from `NSUserDefaults` on OS X, some popular `key` and `type`s +are: + +* `AppleInterfaceStyle: string` +* `AppleAquaColorVariant: integer` +* `AppleHighlightColor: string` +* `AppleShowScrollBars: string` +* `NSNavRecentPlaces: array` +* `NSPreferredWebServices: dictionary` +* `NSUserDictionaryReplacementItems: array` + +### `systemPreferences.isAeroGlassEnabled()` _Windows_ + +This method returns `true` if [DWM composition][dwm-composition] (Aero Glass) is +enabled, and `false` otherwise. + +An example of using it to determine if you should create a transparent window or +not (transparent windows won't work correctly when DWM composition is disabled): + +```javascript +let browserOptions = {width: 1000, height: 800}; + +// Make the window transparent only if the platform supports it. +if (process.platform !== 'win32' || systemPreferences.isAeroGlassEnabled()) { + browserOptions.transparent = true; + browserOptions.frame = false; +} + +// Create the window. +let win = new BrowserWindow(browserOptions); + +// Navigate. +if (browserOptions.transparent) { + win.loadURL('file://' + __dirname + '/index.html'); +} else { + // No transparency, so we load a fallback that uses basic styles. + win.loadURL('file://' + __dirname + '/fallback.html'); +} +``` + +[dwm-composition]:https://msdn.microsoft.com/en-us/library/windows/desktop/aa969540.aspx diff --git a/docs/api/tray.md b/docs/api/tray.md index 22ab9aea5b79..4c4891ef8960 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -1,27 +1,22 @@ # Tray -A `Tray` represents an icon in an operating system's notification area, it is -usually attached with a context menu. +> Add icons and context menus to the system's notification area. ```javascript -const electron = require('electron'); -const app = electron.app; -const Menu = electron.Menu; -const Tray = electron.Tray; +const {app, Menu, Tray} = require('electron'); -var appIcon = null; -app.on('ready', function(){ +let appIcon = null; +app.on('ready', () => { appIcon = new Tray('/path/to/my/icon'); - var contextMenu = Menu.buildFromTemplate([ - { label: 'Item1', type: 'radio' }, - { label: 'Item2', type: 'radio' }, - { label: 'Item3', type: 'radio', checked: true }, - { label: 'Item4', type: 'radio' } + const contextMenu = Menu.buildFromTemplate([ + {label: 'Item1', type: 'radio'}, + {label: 'Item2', type: 'radio'}, + {label: 'Item3', type: 'radio', checked: true}, + {label: 'Item4', type: 'radio'} ]); appIcon.setToolTip('This is my application.'); appIcon.setContextMenu(contextMenu); }); - ``` __Platform limitations:__ @@ -34,6 +29,7 @@ __Platform limitations:__ * When app indicator is used on Linux, the `click` event is ignored. * On Linux in order for changes made to individual `MenuItem`s to take effect, you have to call `setContextMenu` again. For example: +* On Windows it is recommended to use `ICO` icons to get best visual effects. ```javascript contextMenu.items[2].checked = false; @@ -75,7 +71,7 @@ labeled as such. Emitted when the tray icon is clicked. -__Note:__ The `bounds` payload is only implemented on OS X and Windows. +**Note:** The `bounds` payload is only implemented on OS X and Windows. ### Event: 'right-click' _OS X_ _Windows_ diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index dde701d7f488..c3f1f82366c7 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -1,19 +1,20 @@ # webContents +> Render and control web pages. + `webContents` is an [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter). - It is responsible for rendering and controlling a web page and is a property of the [`BrowserWindow`](browser-window.md) object. An example of accessing the `webContents` object: ```javascript -const BrowserWindow = require('electron').BrowserWindow; +const {BrowserWindow} = require('electron'); -var win = new BrowserWindow({width: 800, height: 1500}); -win.loadURL("http://github.com"); +let win = new BrowserWindow({width: 800, height: 1500}); +win.loadURL('http://github.com'); -var webContents = win.webContents; +let webContents = win.webContents; ``` ## Events @@ -33,10 +34,13 @@ Returns: * `errorCode` Integer * `errorDescription` String * `validatedURL` String +* `isMainFrame` Boolean 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). +Note that redirect responses will emit `errorCode` -3; you may want to ignore +that error explicitly. ### Event: 'did-frame-finish-load' @@ -67,6 +71,7 @@ Returns: * `requestMethod` String * `referrer` String * `headers` Object +* `resourceType` String Emitted when details regarding a requested resource are available. `status` indicates the socket connection to download the resource. @@ -259,6 +264,7 @@ Returns: * `result` Object * `requestId` Integer * `finalUpdate` Boolean - Indicates if more responses are to follow. + * `activeMatchOrdinal` Integer (optional) - Position of the active match. * `matches` Integer (optional) - Number of Matches. * `selectionArea` Object (optional) - Coordinates of first match region. @@ -275,7 +281,8 @@ Emitted when media is paused or done playing. ### Event: 'did-change-theme-color' -Emitted when a page's theme color changes. This is usually due to encountering a meta tag: +Emitted when a page's theme color changes. This is usually due to encountering +a meta tag: ```html @@ -303,6 +310,105 @@ If the `type` parameter is `custom`, the `image` parameter will hold the custom cursor image in a `NativeImage`, and the `scale` will hold scaling information for the image. +### Event: 'context-menu' + +Returns: + +* `event` Event +* `params` Object + * `x` Integer - x coodinate + * `y` Integer - y coodinate + * `linkURL` String - URL of the link that encloses the node the context menu + was invoked on. + * `linkText` String - Text associated with the link. May be an empty + string if the contents of the link are an image. + * `pageURL` String - URL of the top level page that the context menu was + invoked on. + * `frameURL` String - URL of the subframe that the context menu was invoked + on. + * `srcURL` String - Source URL for the element that the context menu + was invoked on. Elements with source URLs are images, audio and video. + * `mediaType` String - Type of the node the context menu was invoked on. Can + be `none`, `image`, `audio`, `video`, `canvas`, `file` or `plugin`. + * `hasImageContent` Boolean - Wether the context menu was invoked on an image + which has non-empty contents. + * `isEditable` Boolean - Wether the context is editable. + * `selectionText` String - Text of the selection that the context menu was + invoked on. + * `titleText` String - Title or alt text of the selection that the context + was invoked on. + * `misspelledWord` String - The misspelled word under the cursor, if any. + * `frameCharset` String - The character encoding of the frame on which the + menu was invoked. + * `inputFieldType` String - If the context menu was invoked on an input + field, the type of that field. Possible values are `none`, `plainText`, + `password`, `other`. + * `menuSourceType` String - Input source that invoked the context menu. + Can be `none`, `mouse`, `keyboard`, `touch`, `touchMenu`. + * `mediaFlags` Object - The flags for the media element the context menu was + invoked on. See more about this below. + * `editFlags` Object - These flags indicate wether the renderer believes it is + able to perform the corresponding action. See more about this below. + +The `mediaFlags` is an object with the following properties: + * `inError` Boolean - Wether the media element has crashed. + * `isPaused` Boolean - Wether the media element is paused. + * `isMuted` Boolean - Wether the media element is muted. + * `hasAudio` Boolean - Wether the media element has audio. + * `isLooping` Boolean - Wether the media element is looping. + * `isControlsVisible` Boolean - Wether the media element's controls are + visible. + * `canToggleControls` Boolean - Wether the media element's controls are + toggleable. + * `canRotate` Boolean - Wether the media element can be rotated. + +The `editFlags` is an object with the following properties: + * `canUndo` Boolean - Wether the renderer believes it can undo. + * `canRedo` Boolean - Wether the renderer believes it can redo. + * `canCut` Boolean - Wether the renderer believes it can cut. + * `canCopy` Boolean - Wether the renderer believes it can copy + * `canPaste` Boolean - Wether the renderer believes it can paste. + * `canDelete` Boolean - Wether the renderer believes it can delete. + * `canSelectAll` Boolean - Wether the renderer believes it can select all. + +Emitted when there is a new context menu that needs to be handled. + +### Event: 'select-bluetooth-device' + +Returns: + +* `event` Event +* `devices` [Objects] + * `deviceName` String + * `deviceId` String +* `callback` Function + * `deviceId` String + +Emitted when bluetooth device needs to be selected on call to +`navigator.bluetooth.requestDevice`. To use `navigator.bluetooth` api +`webBluetooth` should be enabled. If `event.preventDefault` is not called, +first available device will be selected. `callback` should be called with +`deviceId` to be selected, passing empty string to `callback` will +cancel the request. + +```javascript +app.commandLine.appendSwitch('enable-web-bluetooth') + +app.on('ready', () => { + webContents.on('select-bluetooth-device', (event, deviceList, callback) => { + event.preventDefault() + let result = deviceList.find((device) => { + return device.deviceName === 'test' + }) + if (!result) { + callback('') + } else { + callback(result.deviceId) + } + }) +}) +``` + ## Instance Methods The `webContents` object has the following instance methods: @@ -320,8 +426,8 @@ e.g. the `http://` or `file://`. If the load should bypass http cache then use the `pragma` header to achieve it. ```javascript -const options = {"extraHeaders" : "pragma: no-cache\n"} -webContents.loadURL(url, options) +const options = {extraHeaders: 'pragma: no-cache\n'}; +webContents.loadURL(url, options); ``` ### `webContents.downloadURL(url)` @@ -336,10 +442,10 @@ Initiates a download of the resource at `url` without navigating. The Returns URL of the current web page. ```javascript -var win = new BrowserWindow({width: 800, height: 600}); -win.loadURL("http://github.com"); +let win = new BrowserWindow({width: 800, height: 600}); +win.loadURL('http://github.com'); -var currentURL = win.webContents.getURL(); +let currentURL = win.webContents.getURL(); ``` ### `webContents.getTitle()` @@ -350,6 +456,11 @@ Returns the title of the current web page. Returns whether web page is still loading resources. +### `webContents.isLoadingMainFrame()` + +Returns whether the main frame (and not just iframes or frames within it) is +still loading. + ### `webContents.isWaitingForResponse()` Returns whether the web page is waiting for a first-response from the main @@ -425,10 +536,12 @@ Returns a `String` representing the user agent for this web page. Injects CSS into the current web page. -### `webContents.executeJavaScript(code[, userGesture])` +### `webContents.executeJavaScript(code[, userGesture, callback])` * `code` String * `userGesture` Boolean (optional) +* `callback` Function (optional) - Called after script has been executed. + * `result` Evaluates `code` in page. @@ -516,9 +629,10 @@ Inserts `text` to the focused element. uppercase letter followed by a lowercase or non-letter. Accepts several other intra-word matches, defaults to `false`. -Starts a request to find all matches for the `text` in the web page and returns an `Integer` -representing the request id used for the request. The result of the request can be -obtained by subscribing to [`found-in-page`](web-contents.md#event-found-in-page) event. +Starts a request to find all matches for the `text` in the web page and returns +an `Integer` representing the request id used for the request. The result of +the request can be obtained by subscribing to +[`found-in-page`](web-contents.md#event-found-in-page) event. ### `webContents.stopFindInPage(action)` @@ -531,12 +645,12 @@ obtained by subscribing to [`found-in-page`](web-contents.md#event-found-in-page Stops any `findInPage` request for the `webContents` with the provided `action`. ```javascript -webContents.on('found-in-page', function(event, result) { +webContents.on('found-in-page', (event, result) => { if (result.finalUpdate) - webContents.stopFindInPage("clearSelection"); + webContents.stopFindInPage('clearSelection'); }); -const requestId = webContents.findInPage("api"); +const requestId = webContents.findInPage('api'); ``` ### `webContents.hasServiceWorker(callback)` @@ -577,7 +691,8 @@ size. * `marginsType` Integer - Specifies the type of margins to use. Uses 0 for default margin, 1 for no margin, and 2 for minimum margin. * `pageSize` String - Specify page size of the generated PDF. Can be `A3`, - `A4`, `A5`, `Legal`, `Letter` and `Tabloid`. + `A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height` + and `width` in microns. * `printBackground` Boolean - Whether to print CSS backgrounds. * `printSelectionOnly` Boolean - Whether to print selection only. * `landscape` Boolean - `true` for landscape, `false` for portrait. @@ -600,23 +715,25 @@ By default, an empty `options` will be regarded as: } ``` +An example of `webContents.printToPDF`: + ```javascript -const BrowserWindow = require('electron').BrowserWindow; +const {BrowserWindow} = require('electron'); const fs = require('fs'); -var win = new BrowserWindow({width: 800, height: 600}); -win.loadURL("http://github.com"); +let win = new BrowserWindow({width: 800, height: 600}); +win.loadURL('http://github.com'); -win.webContents.on("did-finish-load", function() { +win.webContents.on('did-finish-load', () => { // Use default printing options - win.webContents.printToPDF({}, function(error, data) { + win.webContents.printToPDF({}, (error, data) => { if (error) throw error; - fs.writeFile("/tmp/print.pdf", data, function(error) { + fs.writeFile('/tmp/print.pdf', data, (error) => { if (error) throw error; - console.log("Write PDF successfully."); - }) - }) + console.log('Write PDF successfully.'); + }); + }); }); ``` @@ -628,8 +745,8 @@ Adds the specified path to DevTools workspace. Must be used after DevTools creation: ```javascript -mainWindow.webContents.on('devtools-opened', function() { - mainWindow.webContents.addWorkSpace(__dirname); +win.webContents.on('devtools-opened', () => { + win.webContents.addWorkSpace(__dirname); }); ``` @@ -642,7 +759,9 @@ Removes the specified path from DevTools workspace. ### `webContents.openDevTools([options])` * `options` Object (optional) - * `detach` Boolean - opens DevTools in a new window + * `mode` String - Opens the devtools with specified dock state, can be + `right`, `bottom`, `undocked`, `detach`. Defaults to last used dock state. + In `undocked` mode it's possible to dock back. In `detach` mode it's not. Opens the devtools. @@ -662,10 +781,6 @@ Returns whether the devtools view is focused . Toggles the developer tools. -### `webContents.isDevToolsFocused()` - -Returns whether the developer tools is focused. - ### `webContents.inspectElement(x, y)` * `x` Integer @@ -693,12 +808,13 @@ An example of sending messages from the main process to the renderer process: ```javascript // In the main process. -var window = null; -app.on('ready', function() { - window = new BrowserWindow({width: 800, height: 600}); - window.loadURL('file://' + __dirname + '/index.html'); - window.webContents.on('did-finish-load', function() { - window.webContents.send('ping', 'whoooooooh!'); +let win = null; + +app.on('ready', () => { + win = new BrowserWindow({width: 800, height: 600}); + win.loadURL(`file://${__dirname}/index.html`); + win.webContents.on('did-finish-load', () => { + win.webContents.send('ping', 'whoooooooh!'); }); }); ``` @@ -708,7 +824,7 @@ app.on('ready', function() { @@ -766,11 +882,9 @@ Sends an input `event` to the page. For keyboard events, the `event` object also have following properties: -* `keyCode` Char or String (**required**) - The character that will be sent - as the keyboard event. Can be a single UTF-8 character, or the name of the - key that generates the event. Accepted key names are `enter`, `backspace`, - `delete`, `tab`, `escape`, `control`, `alt`, `shift`, `end`, `home`, `insert`, - `left`, `up`, `right`, `down`, `pageUp`, `pageDown`, `printScreen` +* `keyCode` String (**required**) - The character that will be sent + as the keyboard event. Should only use the valid key codes in + [Accelerator](accelerator.md). For mouse events, the `event` object also have following properties: @@ -818,7 +932,7 @@ End subscribing for frame presentation events. * `HTMLOnly` - Save only the HTML of the page. * `HTMLComplete` - Save complete-html page. * `MHTML` - Save complete-html page as MHTML. -* `callback` Function - `function(error) {}`. +* `callback` Function - `(error) => {}`. * `error` Error Returns true if the process of saving page has been initiated successfully. @@ -826,10 +940,10 @@ 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) { +win.webContents.on('did-finish-load', () => { + win.webContents.savePage('/tmp/test.html', 'HTMLComplete', (error) => { if (!error) - console.log("Save page successfully"); + console.log('Save page successfully'); }); }); ``` @@ -838,6 +952,10 @@ win.webContents.on('did-finish-load', function() { `WebContents` objects also have the following properties: +### `webContents.id` + +The unique ID of this WebContents. + ### `webContents.session` Returns the [session](session.md) object used by this webContents. @@ -859,23 +977,23 @@ Debugger API serves as an alternate transport for [remote debugging protocol][rd ```javascript try { - win.webContents.debugger.attach("1.1"); + win.webContents.debugger.attach('1.1'); } catch(err) { - console.log("Debugger attach failed : ", err); + console.log('Debugger attach failed : ', err); }; -win.webContents.debugger.on('detach', function(event, reason) { - console.log("Debugger detached due to : ", reason); +win.webContents.debugger.on('detach', (event, reason) => { + console.log('Debugger detached due to : ', reason); }); -win.webContents.debugger.on('message', function(event, method, params) { - if (method == "Network.requestWillBeSent") { - if (params.request.url == "https://www.github.com") +win.webContents.debugger.on('message', (event, method, params) => { + if (method === 'Network.requestWillBeSent') { + if (params.request.url === 'https://www.github.com') win.webContents.debugger.detach(); } -}) +}); -win.webContents.debugger.sendCommand("Network.enable"); +win.webContents.debugger.sendCommand('Network.enable'); ``` #### `webContents.debugger.attach([protocolVersion])` diff --git a/docs/api/web-frame.md b/docs/api/web-frame.md index d9e02ac097e7..a933b8b48138 100644 --- a/docs/api/web-frame.md +++ b/docs/api/web-frame.md @@ -1,19 +1,18 @@ # webFrame -The `web-frame` module allows you to customize the rendering of the current -web page. +> Customize the rendering of the current web page. An example of zooming current page to 200%. ```javascript -var webFrame = require('electron').webFrame; +const {webFrame} = require('electron'); webFrame.setZoomFactor(2); ``` ## Methods -The `web-frame` module has the following methods: +The `webFrame` module has the following methods: ### `webFrame.setZoomFactor(factor)` @@ -59,8 +58,8 @@ whether the word passed is correctly spelled. An example of using [node-spellchecker][spellchecker] as provider: ```javascript -webFrame.setSpellCheckProvider("en-US", true, { - spellCheck: function(text) { +webFrame.setSpellCheckProvider('en-US', true, { + spellCheck(text) { return !(require('spellchecker').isMisspelled(text)); } }); @@ -107,4 +106,43 @@ In the browser window some HTML APIs like `requestFullScreen` can only be invoked by a gesture from the user. Setting `userGesture` to `true` will remove this limitation. +### `webFrame.getResourceUsage()` + +Returns an object describing usage information of Blink's internal memory +caches. + +```javascript +console.log(webFrame.getResourceUsage()) +``` + +This will generate: + +```javascript +{ + images: { + count: 22, + size: 2549, + liveSize: 2542, + decodedSize: 478, + purgedSize: 0, + purgeableSize: 0 + }, + cssStyleSheets: { /* same with "images" */ }, + xslStyleSheets: { /* same with "images" */ }, + fonts: { /* same with "images" */ }, + other: { /* same with "images" */ }, +} +``` + +### `webFrame.clearCache()` + +Attempts to free memory that is no longer being used (like images from a +previous navigation). + +Note that blindly calling this method probably makes Electron slower since it +will have to refill these emptied caches, you should only call it if an event +in your app has occured that makes you think your page is actually using less +memory (i.e. you have navigated from a super heavy page to a mostly empty one, +and intend to stay there). + [spellchecker]: https://github.com/atom/node-spellchecker diff --git a/docs/api/web-view-tag.md b/docs/api/web-view-tag.md index 4a0697ad843d..f99bb4fdb5a1 100644 --- a/docs/api/web-view-tag.md +++ b/docs/api/web-view-tag.md @@ -1,4 +1,6 @@ -# The `` tag +# `` Tag + +> Display external web content in an isolated frame and process. Use the `webview` tag to embed 'guest' content (such as web pages) in your Electron app. The guest content is contained within the `webview` container. @@ -10,6 +12,9 @@ app. It doesn't have the same permissions as your web page and all interactions between your app and embedded content will be asynchronous. This keeps your app safe from the embedded content. +For security purpose, `webview` can only be used in `BrowserWindow`s that have +`nodeIntegration` enabled. + ## Example To embed a web page in your app, add the `webview` tag to your app's embedder @@ -18,7 +23,7 @@ form, the `webview` tag includes the `src` of the web page and css styles that control the appearance of the `webview` container: ```html - + ``` If you want to control the guest content in any way, you can write JavaScript @@ -29,22 +34,54 @@ and displays a "loading..." message during the load time: ```html ``` +## CSS Styling Notes + +Please note that the `webview` tag's style uses `display:flex;` internally to +ensure the child `object` element fills the full height and width of its `webview` +container when used with traditional and flexbox layouts (since v0.36.11). Please +do not overwrite the default `display:flex;` CSS property, unless specifying +`display:inline-flex;` for inline layout. + +`webview` has issues being hidden using the `hidden` attribute or using `display: none;`. +It can cause unusual rendering behaviour within its child `browserplugin` object +and the web page is reloaded, when the `webview` is un-hidden, as opposed to just +becoming visible again. The recommended approach is to hide the `webview` using +CSS by zeroing the `width` & `height` and allowing the element to shrink to the 0px +dimensions via `flex`. + +```html + +``` + ## Tag Attributes The `webview` tag has the following attributes: @@ -84,6 +121,9 @@ than the minimum values or greater than the maximum. If "on", the guest page in `webview` will have node integration and can use node APIs like `require` and `process` to access low level system resources. +**Note:** Node integration will always be disabled in the `webview` if it is +disabled on the parent window. + ### `plugins` ```html @@ -131,7 +171,7 @@ page is loaded, use the `setUserAgent` method to change the user agent. If "on", the guest page will have web security disabled. -### partition +### `partition` ```html @@ -176,7 +216,7 @@ The `webview` tag has the following methods: **Example** ```javascript -webview.addEventListener("dom-ready", function() { +webview.addEventListener('dom-ready', () => { webview.openDevTools(); }); ``` @@ -279,10 +319,12 @@ Returns a `String` representing the user agent for guest page. Injects CSS into the guest page. -### `.executeJavaScript(code, userGesture)` +### `.executeJavaScript(code, userGesture, callback)` * `code` String * `userGesture` Boolean - Default `false`. +* `callback` Function (optional) - Called after script has been executed. + * `result` Evaluates `code` in page. If `userGesture` is set, it will create the user gesture context in the page. HTML APIs like `requestFullScreen`, which require @@ -403,8 +445,8 @@ obtained by subscribing to [`found-in-page`](web-view-tag.md#event-found-in-page * `action` String - Specifies the action to take place when ending [`.findInPage`](web-view-tag.md#webviewtagfindinpage) request. - * `clearSelection` - Translate the selection into a normal selection. - * `keepSelection` - Clear the selection. + * `clearSelection` - Clear the selection. + * `keepSelection` - Translate the selection into a normal selection. * `activateSelection` - Focus and click the selection node. Stops any `findInPage` request for the `webview` with the provided `action`. @@ -415,7 +457,7 @@ Prints `webview`'s web page. Same with `webContents.print([options])`. ### `.printToPDF(options, callback)` -Prints webview's web page as PDF, Same with `webContents.printToPDF(options, callback)` +Prints `webview`'s web page as PDF, Same with `webContents.printToPDF(options, callback)` ### `.send(channel[, arg1][, arg2][, ...])` @@ -469,6 +511,7 @@ Returns: * `errorCode` Integer * `errorDescription` String * `validatedURL` String +* `isMainFrame` Boolean This event is like `did-finish-load`, but fired when the load failed or was cancelled, e.g. `window.stop()` is invoked. @@ -500,6 +543,7 @@ Returns: * `requestMethod` String * `referrer` String * `headers` Object +* `resourceType` String Fired when details regarding a requested resource is available. `status` indicates socket connection to download the resource. @@ -559,7 +603,7 @@ The following example code forwards all log messages to the embedder's console without regard for log level or other properties. ```javascript -webview.addEventListener('console-message', function(e) { +webview.addEventListener('console-message', (e) => { console.log('Guest page logged a message:', e.message); }); ``` @@ -571,6 +615,7 @@ Returns: * `result` Object * `requestId` Integer * `finalUpdate` Boolean - Indicates if more responses are to follow. + * `activeMatchOrdinal` Integer (optional) - Position of the active match. * `matches` Integer (optional) - Number of Matches. * `selectionArea` Object (optional) - Coordinates of first match region. @@ -578,12 +623,12 @@ Fired when a result is available for [`webview.findInPage`](web-view-tag.md#webviewtagfindinpage) request. ```javascript -webview.addEventListener('found-in-page', function(e) { +webview.addEventListener('found-in-page', (e) => { if (e.result.finalUpdate) - webview.stopFindInPage("keepSelection"); + webview.stopFindInPage('keepSelection'); }); -const rquestId = webview.findInPage("test"); +const requestId = webview.findInPage('test'); ``` ### Event: 'new-window' @@ -602,8 +647,13 @@ Fired when the guest page attempts to open a new browser window. The following example code opens the new url in system's default browser. ```javascript -webview.addEventListener('new-window', function(e) { - require('electron').shell.openExternal(e.url); +const {shell} = require('electron'); + +webview.addEventListener('new-window', (e) => { + const protocol = require('url').parse(e.url).protocol; + if (protocol === 'http:' || protocol === 'https:') { + shell.openExternal(e.url); + } }); ``` @@ -657,7 +707,7 @@ The following example code navigates the `webview` to `about:blank` when the guest attempts to close itself. ```javascript -webview.addEventListener('close', function() { +webview.addEventListener('close', () => { webview.src = 'about:blank'; }); ``` @@ -676,7 +726,7 @@ between guest page and embedder page: ```javascript // In embedder page. -webview.addEventListener('ipc-message', function(event) { +webview.addEventListener('ipc-message', (event) => { console.log(event.channel); // Prints "pong" }); @@ -685,8 +735,8 @@ webview.send('ping'); ```javascript // In guest page. -var ipcRenderer = require('electron').ipcRenderer; -ipcRenderer.on('ping', function() { +const {ipcRenderer} = require('electron'); +ipcRenderer.on('ping', () => { ipcRenderer.sendToHost('pong'); }); ``` @@ -722,6 +772,10 @@ Emitted when media is paused or done playing. ### Event: 'did-change-theme-color' +Returns: + +* `themeColor` String + Emitted when a page's theme color changes. This is usually due to encountering a meta tag: ```html diff --git a/docs/api/window-open.md b/docs/api/window-open.md index 5d298e61e75b..b615a4b6488e 100644 --- a/docs/api/window-open.md +++ b/docs/api/window-open.md @@ -1,4 +1,6 @@ -# The `window.open` function +# `window.open` Function + +> Open a new window and load a URL. When `window.open` is called to create a new window in a web page, a new instance of `BrowserWindow` will be created for the `url` and a proxy will be returned @@ -23,6 +25,9 @@ Creates a new window and returns an instance of `BrowserWindowProxy` class. The `features` string follows the format of standard browser, but each feature has to be a field of `BrowserWindow`'s options. +**Note:** Node integration will always be disabled in the opened `window` if it +is disabled on the parent window. + ### `window.opener.postMessage(message, targetOrigin)` * `message` String @@ -58,6 +63,10 @@ Evaluates the code in the child window. Focuses the child window (brings the window to front). +### `BrowserWindowProxy.print()` + +Invokes the print dialog on the child window. + ### `BrowserWindowProxy.postMessage(message, targetOrigin)` * `message` String diff --git a/docs/development/atom-shell-vs-node-webkit.md b/docs/development/atom-shell-vs-node-webkit.md index 76fa5d57d289..3ec11b78b7d1 100644 --- a/docs/development/atom-shell-vs-node-webkit.md +++ b/docs/development/atom-shell-vs-node-webkit.md @@ -47,4 +47,4 @@ By using the [multi-context](http://strongloop.com/strongblog/whats-new-node-js- 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 +[node-bindings]: https://github.com/electron/electron/tree/master/atom/common diff --git a/docs/development/build-instructions-linux.md b/docs/development/build-instructions-linux.md index 9ddeed9809b0..d91b7192842f 100644 --- a/docs/development/build-instructions-linux.md +++ b/docs/development/build-instructions-linux.md @@ -4,6 +4,7 @@ Follow the guidelines below for building Electron on Linux. ## Prerequisites +* At least 25GB disk space and 8GB RAM. * Python 2.7.x. Some distributions like CentOS still use Python 2.6.x so you may need to check your Python version with `python -V`. * Node.js v0.12.x. There are various ways to install Node. You can download @@ -19,7 +20,7 @@ On Ubuntu, install the following libraries: $ sudo apt-get install build-essential clang libdbus-1-dev libgtk2.0-dev \ libnotify-dev libgnome-keyring-dev libgconf2-dev \ libasound2-dev libcap-dev libcups2-dev libxtst-dev \ - libxss1 libnss3-dev gcc-multilib g++-multilib + libxss1 libnss3-dev gcc-multilib g++-multilib curl ``` On Fedora, install the following libraries: @@ -33,15 +34,10 @@ $ sudo yum install clang dbus-devel gtk2-devel libnotify-devel libgnome-keyring- Other distributions may offer similar packages for installation via package managers such as pacman. Or one can compile from source code. -## If You Use Virtual Machines For Building - -If you plan to build Electron on a virtual machine you will need a fixed-size -device container of at least 25 gigabytes in size. - ## Getting the Code ```bash -$ git clone https://github.com/atom/electron.git +$ git clone https://github.com/electron/electron.git ``` ## Bootstrapping @@ -112,8 +108,6 @@ $ ./script/clean.py ## Troubleshooting -Make sure you have installed all of the build dependencies. - ### Error While Loading Shared Libraries: libtinfo.so.5 Prebulit `clang` will try to link to `libtinfo.so.5`. Depending on the host @@ -128,7 +122,7 @@ $ sudo ln -s /usr/lib/libncurses.so.5 /usr/lib/libtinfo.so.5 Test your changes conform to the project coding style using: ```bash -$ ./script/cpplint.py +$ npm run lint ``` Test functionality using: @@ -136,3 +130,74 @@ Test functionality using: ```bash $ ./script/test.py ``` + +## Advanced topics + +The default building configuration is targeted for major desktop Linux +distributions, to build for a specific distribution or device, following +information may help you. + +### Building `libchromiumcontent` locally + +To avoid using the prebuilt binaries of `libchromiumcontent`, you can pass the +`--build_libchromiumcontent` switch to `bootstrap.py` script: + +```bash +$ ./script/bootstrap.py -v --build_libchromiumcontent +``` + +Note that by default the `shared_library` configuration is not built, so you can +only build `Release` version of Electron if you use this mode: + +```bash +$ ./script/build.py -c R +``` + +### Using system `clang` instead of downloaded `clang` binaries + +By default Electron is built with prebuilt `clang` binaries provided by Chromium +project. If for some reason you want to build with the `clang` installed in your +system, you can call `bootstrap.py` with `--clang_dir=` switch. By passing +it the build script will assume the `clang` binaries reside in `/bin/`. + +For example if you installed `clang` under `/user/local/bin/clang`: + +```bash +$ ./script/bootstrap.py -v --build_libchromiumcontent --clang_dir /usr/local +$ ./script/build.py -c R +``` + +### Using other compilers other than `clang` + +To build Electron with compilers like `g++`, you first need to disable `clang` +with `--disable_clang` switch first, and then set `CC` and `CXX` environment +variables to the ones you want. + +For example building with GCC toolchain: + +```bash +$ env CC=gcc CXX=g++ ./script/bootstrap.py -v --build_libchromiumcontent --disable_clang +$ ./script/build.py -c R +``` + +### Environment variables + +Apart from `CC` and `CXX`, you can also set following environment variables to +custom the building configurations: + +* `CPPFLAGS` +* `CPPFLAGS_host` +* `CFLAGS` +* `CFLAGS_host` +* `CXXFLAGS` +* `CXXFLAGS_host` +* `AR` +* `AR_host` +* `CC` +* `CC_host` +* `CXX` +* `CXX_host` +* `LDFLAGS` + +The environment variables have to be set when executing the `bootstrap.py` +script, it won't work in the `build.py` script. diff --git a/docs/development/build-instructions-osx.md b/docs/development/build-instructions-osx.md index ac51bf0dc224..b703a75897d9 100644 --- a/docs/development/build-instructions-osx.md +++ b/docs/development/build-instructions-osx.md @@ -16,14 +16,14 @@ following python modules: ## Getting the Code ```bash -$ git clone https://github.com/atom/electron.git +$ git clone https://github.com/electron/electron.git ``` ## Bootstrapping The bootstrap script will download all necessary build dependencies and create -the build project files. Notice that we're using `ninja` to build Electron so -there is no Xcode project generated. +the build project files. Notice that we're using [ninja](https://ninja-build.org/) +to build Electron so there is no Xcode project generated. ```bash $ cd electron diff --git a/docs/development/build-instructions-windows.md b/docs/development/build-instructions-windows.md index 06ce696a9e3c..b3c2c4573462 100644 --- a/docs/development/build-instructions-windows.md +++ b/docs/development/build-instructions-windows.md @@ -5,13 +5,14 @@ Follow the guidelines below for building Electron on Windows. ## Prerequisites * Windows 7 / Server 2008 R2 or higher -* Visual Studio 2013 with Update 4 - [download VS 2013 Community Edition for - free](https://www.visualstudio.com/news/vs2013-community-vs). +* Visual Studio 2015 - [download VS 2015 Community Edition for + free](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx) * [Python 2.7](http://www.python.org/download/releases/2.7/) * [Node.js](http://nodejs.org/download/) * [Git](http://git-scm.com) -If you don't currently have a Windows installation, [modern.ie](https://www.modern.ie/en-us/virtualization-tools#downloads) +If you don't currently have a Windows installation, +[modern.ie](https://www.modern.ie/en-us/virtualization-tools#downloads) has timebombed versions of Windows that you can use to build Electron. Building Electron is done entirely with command-line scripts and cannot be done @@ -21,13 +22,12 @@ building with Visual Studio will come in the future. **Note:** Even though Visual Studio is not used for building, it's still **required** because we need the build toolchains it provides. -**Note:** Visual Studio 2015 will not work. Please make sure to get MSVS -**2013**. +**Note:** While older versions of Electron required Visual Studio 2013, Electron 1.1 and later does require Visual Studio 2015. ## Getting the Code ```powershell -$ git clone https://github.com/atom/electron.git +$ git clone https://github.com/electron/electron.git ``` ## Bootstrapping @@ -84,7 +84,7 @@ $ python script\test.py ``` Tests that include native modules (e.g. `runas`) can't be executed with the -debug build (see [#2558](https://github.com/atom/electron/issues/2558) for +debug build (see [#2558](https://github.com/electron/electron/issues/2558) for details), but they will work with the release build. To run the tests with the release build use: @@ -98,7 +98,7 @@ $ python script\test.py -R ### Command xxxx not found If you encountered an error like `Command xxxx not found`, you may try to use -the `VS2012 Command Prompt` console to execute the build scripts. +the `VS2015 Command Prompt` console to execute the build scripts. ### Fatal internal compiler error: C1001 @@ -147,4 +147,4 @@ $ mkdir ~\AppData\Roaming\npm ### node-gyp is not recognized as an internal or external command You may get this error if you are using Git Bash for building, you should use -PowerShell or VS2012 Command Prompt instead. +PowerShell or VS2015 Command Prompt instead. diff --git a/docs/development/build-system-overview.md b/docs/development/build-system-overview.md index 04067ce8c79c..bc27c1932fa6 100644 --- a/docs/development/build-system-overview.md +++ b/docs/development/build-system-overview.md @@ -1,7 +1,8 @@ # Build System Overview -Electron uses `gyp` for project generation and `ninja` for building. Project -configurations can be found in the `.gyp` and `.gypi` files. +Electron uses [gyp](https://gyp.gsrc.io/) for project generation and +[ninja](https://ninja-build.org/) for building. Project configurations can +be found in the `.gyp` and `.gypi` files. ## Gyp Files diff --git a/docs/development/coding-style.md b/docs/development/coding-style.md index a86ee32417db..1840700f5237 100644 --- a/docs/development/coding-style.md +++ b/docs/development/coding-style.md @@ -2,6 +2,9 @@ These are the style guidelines for coding in Electron. +You can run `npm run lint` to show any style issues detected by `cpplint` and +`eslint`. + ## C++ and Python For C++ and Python, we follow Chromium's [Coding @@ -17,21 +20,37 @@ document. The document mentions some special types, scoped types (that automatically release their memory when going out of scope), logging mechanisms etc. -## CoffeeScript - -For CoffeeScript, we follow GitHub's [Style -Guide](https://github.com/styleguide/javascript) and the following rules: +## JavaScript +* Write [standard](http://npm.im/standard) JavaScript style. * Files should **NOT** end with new line, because we want to match Google's styles. * File names should be concatenated with `-` instead of `_`, e.g. - `file-name.coffee` rather than `file_name.coffee`, because in + `file-name.js` rather than `file_name.js`, because in [github/atom](https://github.com/github/atom) module names are usually in - the `module-name` form. This rule only applies to `.coffee` files. + the `module-name` form. This rule only applies to `.js` files. +* Use newer ES6/ES2015 syntax where appropriate + * [`const`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const) + for requires and other constants + * [`let`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let) + for defining variables + * [Arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) + instead of `function () { }` + * [Template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) + instead of string concatenation using `+` -## API Names +## Naming Things -When creating a new API, we should prefer getters and setters instead of +Electron APIs uses the same capitalization scheme as Node.js: + +- When the module itself is a class like `BrowserWindow`, use `CamelCase`. +- When the module is a set of APIs, like `globalShortcut`, use `mixedCase`. +- When the API is a property of object, and it is complex enough to be in a + separate chapter like `win.webContents`, use `mixedCase`. +- For other non-module APIs, use natural titles, like ` Tag` or + `Process Object`. + +When creating a new API, it is preferred to use getters and setters instead of jQuery's one-function style. For example, `.getText()` and `.setText(text)` are preferred to `.text([text])`. There is a -[discussion](https://github.com/atom/electron/issues/46) on this. +[discussion](https://github.com/electron/electron/issues/46) on this. diff --git a/docs/development/debug-instructions-windows.md b/docs/development/debug-instructions-windows.md new file mode 100644 index 000000000000..b2d33473934a --- /dev/null +++ b/docs/development/debug-instructions-windows.md @@ -0,0 +1,93 @@ +# Debugging on Windows + +If you experience crashes or issues in Electron that you believe are not caused +by your JavaScript application, but instead by Electron itself, debugging can +be a little bit tricky, especially for developers not used to native/C++ +debugging. However, using Visual Studio, GitHub's hosted Electron Symbol Server, +and the Electron source code, it is fairly easy to enable step-through debugging +with breakpoints inside Electron's source code. + +## Requirements + +* **A debug build of Electron**: The easiest way is usually building it + yourself, using the tools and prerequisites listed in the + [build instructions for Windows](build-instructions-windows.md). While you can + easily attach to and debug Electron as you can download it directly, you will + find that it is heavily optimized, making debugging substantially more + difficult: The debugger will not be able to show you the content of all + variables and the execution path can seem strange because of inlining, + tail calls, and other compiler optimizations. + +* **Visual Studio with C++ Tools**: The free community editions of Visual + Studio 2013 and Visual Studio 2015 both work. Once installed, + [configure Visual Studio to use GitHub's Electron Symbol server](setting-up-symbol-server.md). + It will enable Visual Studio to gain a better understanding of what happens + inside Electron, making it easier to present variables in a human-readable + format. + +* **ProcMon**: The [free SysInternals tool][sys-internals] allows you to inspect + a processes parameters, file handles, and registry operations. + +## Attaching to and Debugging Electron + +To start a debugging session, open up PowerShell/CMD and execute your debug +build of Electron, using the application to open as a parameter. + +```powershell +$ ./out/D/electron.exe ~/my-electron-app/ +``` + +### Setting Breakpoints + +Then, open up Visual Studio. Electron is not built with Visual Studio and hence +does not contain a project file - you can however open up the source code files +"As File", meaning that Visual Studio will open them up by themselves. You can +still set breakpoints - Visual Studio will automatically figure out that the +source code matches the code running in the attached process and break +accordingly. + +Relevant code files can be found in `./atom/` as well as in Brightray, found in +`./vendor/brightray/browser` and `./vendor/brightray/common`. If you're hardcore, +you can also debug Chromium directly, which is obviously found in `chromium_src`. + +### Attaching + +You can attach the Visual Studio debugger to a running process on a local or +remote computer. After the process is running, click Debug / Attach to Process +(or press `CTRL+ALT+P`) to open the "Attach to Process" dialog box. You can use +this capability to debug apps that are running on a local or remote computer, +debug multiple processes simultaneously. + +If Electron is running under a different user account, select the +`Show processes from all users` check box. Notice that depending on how many +BrowserWindows your app opened, you will see multiple processes. A typical +one-window app will result in Visual Studio presenting you with two +`Electron.exe` entries - one for the main process and one for the renderer +process. Since the list only gives you names, there's currently no reliable +way of figuring out which is which. + +### Which Process Should I Attach to? + +Code executed within the main process (that is, code found in or eventually run +by your main JavaScript file) as well as code called using the remote +(`require('electron').remote`) will run inside the main process, while other +code will execute inside its respective renderer process. + +You can be attached to multiple programs when you are debugging, but only one +program is active in the debugger at any time. You can set the active program +in the `Debug Location` toolbar or the `Processes window`. + +## Using ProcMon to Observe a Process + +While Visual Studio is fantastic for inspecting specific code paths, ProcMon's +strength is really in observing everything your application is doing with the +operating system - it captures File, Registry, Network, Process, and Profiling +details of processes. It attempts to log **all** events occurring and can be +quite overwhelming, but if you seek to understand what and how your application +is doing to the operating system, it can be a valuable resource. + +For an introduction to ProcMon's basic and advanced debugging features, go check +out [this video tutorial][procmon-instructions] provided by Microsoft. + +[sys-internals]: https://technet.microsoft.com/en-us/sysinternals/processmonitor.aspx +[procmon-instructions]: https://channel9.msdn.com/shows/defrag-tools/defrag-tools-4-process-monitor diff --git a/docs/development/setting-up-symbol-server.md b/docs/development/setting-up-symbol-server.md index 1dc31f7056d2..073c752cbf46 100644 --- a/docs/development/setting-up-symbol-server.md +++ b/docs/development/setting-up-symbol-server.md @@ -25,7 +25,7 @@ appropriate cache directory on your machine. The Windbg symbol path is configured with a string value delimited with asterisk characters. To use only the Electron symbol server, add the following entry to -your symbol path (__Note:__ you can replace `c:\code\symbols` with any writable +your symbol path (**Note:** you can replace `c:\code\symbols` with any writable directory on your computer, if you'd prefer a different location for downloaded symbols): diff --git a/docs/development/source-code-directory-structure.md b/docs/development/source-code-directory-structure.md index 146edae822da..cc74fe69427a 100644 --- a/docs/development/source-code-directory-structure.md +++ b/docs/development/source-code-directory-structure.md @@ -11,37 +11,38 @@ to understand the source code better. ``` Electron -├──atom - Source code of Electron. -| ├── app - System entry code. -| ├── browser - The frontend including the main window, UI, and all of the -| | main process things. This talks to the renderer to manage web pages. -| |   ├── lib - Javascript part of the main process initialization code. -| | ├── ui - Implementation of UI stuff for different platforms. -| | | ├── cocoa - Cocoa specific source code. -| | | ├── gtk - GTK+ specific source code. -| | | └── win - Windows GUI specific source code. -| | ├── default_app - The default page to show when Electron is started -| | | without providing an app. -| | ├── api - The implementation of the main process APIs. -| | | └── lib - Javascript part of the API implementation. -| | ├── net - Network related code. -| | ├── mac - Mac specific Objective-C source code. -| | └── resources - Icons, platform-dependent files, etc. -| ├── renderer - Code that runs in renderer process. -| | ├── lib - Javascript part of renderer initialization code. -| | └── api - The implementation of renderer process APIs. -| | └── lib - Javascript part of the API implementation. -| └── common - Code that used by both the main and renderer processes, -| including some utility functions and code to integrate node's message -| loop into Chromium's message loop. -| ├── lib - Common Javascript initialization code. -| └── api - The implementation of common APIs, and foundations of -| Electron's built-in modules. -| └── lib - Javascript part of the API implementation. -├── chromium_src - Source code that copied from Chromium. -├── docs - Documentations. -├── spec - Automatic tests. -├── atom.gyp - Building rules of Electron. +├── atom/ - C++ source code. +| ├── app/ - System entry code. +| ├── browser/ - The frontend including the main window, UI, and all of the +| | main process things. This talks to the renderer to manage web pages. +| | ├── ui/ - Implementation of UI stuff for different platforms. +| | | ├── cocoa/ - Cocoa specific source code. +| | | ├── win/ - Windows GUI specific source code. +| | | └── x/ - X11 specific source code. +| | ├── api/ - The implementation of the main process APIs. +| | ├── net/ - Network related code. +| | ├── mac/ - Mac specific Objective-C source code. +| | └── resources/ - Icons, platform-dependent files, etc. +| ├── renderer/ - Code that runs in renderer process. +| | └── api/ - The implementation of renderer process APIs. +| └── common/ - Code that used by both the main and renderer processes, +| including some utility functions and code to integrate node's message +| loop into Chromium's message loop. +| └── api/ - The implementation of common APIs, and foundations of +| Electron's built-in modules. +├── chromium_src/ - Source code that copied from Chromium. +├── default_app/ - The default page to show when Electron is started without +| providing an app. +├── docs/ - Documentations. +├── lib/ - JavaScript source code. +| ├── browser/ - Javascript main process initialization code. +| | └── api/ - Javascript API implementation. +| ├── common/ - JavaScript used by both the main and renderer processes +| | └── api/ - Javascript API implementation. +| └── renderer/ - Javascript renderer process initialization code. +| └── api/ - Javascript API implementation. +├── spec/ - Automatic tests. +├── electron.gyp - Building rules of Electron. └── common.gypi - Compiler specific settings and building rules for other components like `node` and `breakpad`. ``` @@ -61,3 +62,32 @@ Electron when creating a distribution. * **external_binaries** - Downloaded binaries of third-party frameworks which do not support building with `gyp`. + +## Keeping Git Submodules Up to Date + +The Electron repository has a few vendored dependencies, found in the +[/vendor][vendor] directory. Occasionally you might see a message like this +when running `git status`: + +```sh +$ git status + + modified: vendor/brightray (new commits) + modified: vendor/node (new commits) +``` + +To update these vendored dependencies, run the following command: + +```sh +git submodule update --init --recursive +``` + +If you find yourself running this command often, you can create an alias for it +in your `~/.gitconfig` file: + +``` +[alias] + su = submodule update --init --recursive +``` + +[vendor]: https://github.com/electron/electron/tree/master/vendor diff --git a/docs/faq/electron-faq.md b/docs/faq/electron-faq.md index a58d31107b6d..cefc5a54a2c7 100644 --- a/docs/faq/electron-faq.md +++ b/docs/faq/electron-faq.md @@ -3,11 +3,14 @@ ## When will Electron upgrade to latest Chrome? The Chrome version of Electron is usually bumped within one or two weeks after -a new stable Chrome version gets released. +a new stable Chrome version gets released. This estimate is not guaranteed and +depends on the amount of work involved with upgrading. -Also we only use stable channel of Chrome, if an important fix is in beta or dev +Only the stable channel of Chrome is used. If an important fix is in beta or dev channel, we will back-port it. +For more information, please see the [security introduction](../tutorial/security.md). + ## When will Electron upgrade to latest Node.js? When a new version of Node.js gets released, we usually wait for about a month @@ -25,7 +28,7 @@ use HTML5 APIs which are already available in browsers. Good candidates are [Storage API][storage], [`localStorage`][local-storage], [`sessionStorage`][session-storage], and [IndexedDB][indexed-db]. -Or you can use the IPC system, which are specific to Electron, to store objects +Or you can use the IPC system, which is specific to Electron, to store objects in the main process as a global variable, and then to access them from the renderers through the `remote` module: @@ -51,8 +54,7 @@ console.log(require('remote').getGlobal('sharedObject').someProperty); This happens when the variable which is used to store the window/tray gets garbage collected. -It is recommended to have a reading of following articles you encountered this -problem: +If you encounter this problem, the following articles may prove helpful: * [Memory Management][memory-management] * [Variable Scope][variable-scope] @@ -61,31 +63,31 @@ If you want a quick fix, you can make the variables global by changing your code from this: ```javascript -app.on('ready', function() { - var tray = new Tray('/path/to/icon.png'); -}) +app.on('ready', () => { + const tray = new Tray('/path/to/icon.png'); +}); ``` to this: ```javascript -var tray = null; -app.on('ready', function() { +let tray = null; +app.on('ready', () => { tray = new Tray('/path/to/icon.png'); -}) +}); ``` ## I can not use jQuery/RequireJS/Meteor/AngularJS in Electron. Due to the Node.js integration of Electron, there are some extra symbols -inserted into DOM, like `module`, `exports`, `require`. This causes troubles for -some libraries since they want to insert the symbols with same names. +inserted into the DOM like `module`, `exports`, `require`. This causes problems +for some libraries since they want to insert the symbols with the same names. To solve this, you can turn off node integration in Electron: ```javascript // In the main process. -var mainWindow = new BrowserWindow({ +let win = new BrowserWindow({ webPreferences: { nodeIntegration: false } @@ -141,7 +143,7 @@ npm uninstall -g electron ``` However if your are using the built-in module but still getting this error, it -is very likely you are using the module in wrong process. For example +is very likely you are using the module in the wrong process. For example `electron.app` can only be used in the main process, while `electron.webFrame` is only available in renderer processes. diff --git a/docs/styleguide.md b/docs/styleguide.md index 6b745b988b36..a34889d2ca34 100644 --- a/docs/styleguide.md +++ b/docs/styleguide.md @@ -40,7 +40,7 @@ To add another set (or partial set): - Translate the files. - Update the `README.md` within your language directory to link to the files you have translated. -- Add a link to your translation directory on the main Electron [README](https://github.com/atom/electron#documentation-translations). +- Add a link to your translation directory on the main Electron [README](https://github.com/electron/electron#documentation-translations). ## Reading Electron Documentation @@ -92,7 +92,7 @@ a value it and its type is noted below. If you were to listen and respond to this event it might look something like this: ```javascript -Alarm.on('wake-up', function(time) { - console.log(time) -}) +Alarm.on('wake-up', (time) => { + console.log(time); +}); ``` diff --git a/docs/tutorial/application-distribution.md b/docs/tutorial/application-distribution.md index 313c924e881c..4d3e12db75fc 100644 --- a/docs/tutorial/application-distribution.md +++ b/docs/tutorial/application-distribution.md @@ -30,7 +30,7 @@ your distribution to deliver to final users. ## Packaging Your App into a File Apart from shipping your app by copying all of its source files, you can also -package your app into an [asar](https://github.com/atom/asar) archive to avoid +package your app into an [asar](https://github.com/electron/asar) archive to avoid exposing your app's source code to users. To use an `asar` archive to replace the `app` folder, you need to rename the @@ -102,6 +102,14 @@ MyApp.app/Contents You can rename the `electron` executable to any name you like. +## Packaging Tools + +Apart from packaging your app manually, you can also choose to use third party +packaging tools to do the work for you: + +* [electron-packager](https://github.com/maxogden/electron-packager) +* [electron-builder](https://github.com/loopline-systems/electron-builder) + ## Rebranding by Rebuilding Electron from Source It is also possible to rebrand Electron by changing the product name and @@ -118,10 +126,49 @@ This task will automatically handle editing the `.gyp` file, building from source, then rebuilding your app's native Node modules to match the new executable name. -## Packaging Tools +### Creating a Custom Electron Fork -Apart from packaging your app manually, you can also choose to use third party -packaging tools to do the work for you: +Creating a custom fork of Electron is almost certainly not something you will +need to do in order to build your app, even for "Production Level" applications. +Using a tool such as `electron-packager` or `electron-builder` will allow you to +"Rebrand" Electron without having to do these steps. -* [electron-packager](https://github.com/maxogden/electron-packager) -* [electron-builder](https://github.com/loopline-systems/electron-builder) +You need to fork Electron when you have custom C++ code that you have patched +directly into Electron, that either cannot be upstreamed, or has been rejected +from the official version. As maintainers of Electron, we very much would like +to make your scenario work, so please try as hard as you can to get your changes +into the official version of Electron, it will be much much easier on you, and +we appreciate your help. + +#### Creating a Custom Release with surf-build + +1. Install [Surf](https://github.com/surf-build/surf), via npm: + `npm install -g surf-build@latest` + +2. Create a new S3 bucket and create the following empty directory structure: + +``` +- atom-shell/ + - symbols/ + - dist/ +``` + +3. Set the following Environment Variables: + + * `ELECTRON_GITHUB_TOKEN` - a token that can create releases on GitHub + * `ELECTRON_S3_ACCESS_KEY`, `ELECTRON_S3_BUCKET`, `ELECTRON_S3_SECRET_KEY` - + the place where you'll upload node.js headers as well as symbols + * `ELECTRON_RELEASE` - Set to `true` and the upload part will run, leave unset + and `surf-build` will just do CI-type checks, appropriate to run for every + pull request. + * `CI` - Set to `true` or else it will fail + * `GITHUB_TOKEN` - set it to the same as `ELECTRON_GITHUB_TOKEN` + * `SURF_TEMP` - set to `C:\Temp` on Windows to prevent path too long issues + * `TARGET_ARCH` - set to `ia32` or `x64` + +4. In `script/upload.py`, you _must_ set `ELECTRON_REPO` to your fork (`MYORG/electron`), + especially if you are a contributor to Electron proper. + +5. `surf-build -r https://github.com/MYORG/electron -s YOUR_COMMIT -n 'surf-PLATFORM-ARCH'` + +6. Wait a very, very long time for the build to complete. diff --git a/docs/tutorial/application-packaging.md b/docs/tutorial/application-packaging.md index b42a2f9298ba..8a7f8701610f 100644 --- a/docs/tutorial/application-packaging.md +++ b/docs/tutorial/application-packaging.md @@ -71,8 +71,8 @@ require('/path/to/example.asar/dir/module.js'); You can also display a web page in an `asar` archive with `BrowserWindow`: ```javascript -const BrowserWindow = require('electron').BrowserWindow; -var win = new BrowserWindow({width: 800, height: 600}); +const {BrowserWindow} = require('electron'); +let win = new BrowserWindow({width: 800, height: 600}); win.loadURL('file:///path/to/example.asar/static/index.html'); ``` @@ -85,8 +85,8 @@ For example, to get a file with `$.get`: ```html @@ -99,7 +99,7 @@ content of `asar` archive as file. For this purpose you can use the built-in `original-fs` module which provides original `fs` APIs without `asar` support: ```javascript -var originalFs = require('original-fs'); +const originalFs = require('original-fs'); originalFs.readFileSync('/path/to/example.asar'); ``` @@ -181,4 +181,4 @@ After running the command, apart from the `app.asar`, there is also an `app.asar.unpacked` folder generated which contains the unpacked files, you should copy it together with `app.asar` when shipping it to users. -[asar]: https://github.com/atom/asar +[asar]: https://github.com/electron/asar diff --git a/docs/tutorial/debugging-main-process.md b/docs/tutorial/debugging-main-process.md index 6fbb858bc281..714b7100cf47 100644 --- a/docs/tutorial/debugging-main-process.md +++ b/docs/tutorial/debugging-main-process.md @@ -19,7 +19,7 @@ Like `--debug` but pauses the script on the first line. ## Use node-inspector for Debugging -__Note:__ Electron doesn't currently work very well +**Note:** Electron doesn't currently work very well with node-inspector, and the main process will crash if you inspect the `process` object under node-inspector's console. @@ -40,11 +40,11 @@ $ npm install git+https://git@github.com/enlight/node-pre-gyp.git#detect-electro ### 4. Recompile the `node-inspector` `v8` modules for electron (change the target to your electron version number) ```bash -$ node_modules/.bin/node-pre-gyp --target=0.36.2 --runtime=electron --fallback-to-build --directory node_modules/v8-debug/ --dist-url=https://atom.io/download/atom-shell reinstall -$ node_modules/.bin/node-pre-gyp --target=0.36.2 --runtime=electron --fallback-to-build --directory node_modules/v8-profiler/ --dist-url=https://atom.io/download/atom-shell reinstall +$ node_modules/.bin/node-pre-gyp --target=0.36.11 --runtime=electron --fallback-to-build --directory node_modules/v8-debug/ --dist-url=https://atom.io/download/atom-shell reinstall +$ node_modules/.bin/node-pre-gyp --target=0.36.11 --runtime=electron --fallback-to-build --directory node_modules/v8-profiler/ --dist-url=https://atom.io/download/atom-shell reinstall ``` -See also [How to install native modules](how-to-install-native-modules). +See also [How to install native modules][how-to-install-native-modules]. ### 5. Enable debug mode for Electron @@ -68,9 +68,10 @@ $ ELECTRON_RUN_AS_NODE=true path/to/electron.exe node_modules/node-inspector/bin ### 7. Load the debugger UI -Open http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 in the Chrome browser. You may have to click pause if starting with debug-brk to see the entry line. +Open http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 in the Chrome +browser. You may have to click pause if starting with debug-brk to see the +entry line. [node-inspector]: https://github.com/node-inspector/node-inspector [node-gyp-required-tools]: https://github.com/nodejs/node-gyp#installation [how-to-install-native-modules]: using-native-node-modules.md#how-to-install-native-modules - diff --git a/docs/tutorial/desktop-environment-integration.md b/docs/tutorial/desktop-environment-integration.md index a72d68b3cf30..c552abbf9496 100644 --- a/docs/tutorial/desktop-environment-integration.md +++ b/docs/tutorial/desktop-environment-integration.md @@ -15,14 +15,16 @@ to the user. Electron conveniently allows developers to send notifications with the [HTML5 Notification API](https://notifications.spec.whatwg.org/), using the currently running operating system's native notification APIs to display it. +**Note:** Since this is an HTML5 API it is only available in the renderer process. + ```javascript -var myNotification = new Notification('Title', { +let myNotification = new Notification('Title', { body: 'Lorem Ipsum Dolor Sit Amet' }); -myNotification.onclick = function () { - console.log('Notification clicked') -} +myNotification.onclick = () => { + console.log('Notification clicked'); +}; ``` While code and user experience across operating systems are similar, there @@ -51,8 +53,7 @@ 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). +[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. @@ -116,8 +117,8 @@ 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'); } }, +const dockMenu = Menu.buildFromTemplate([ + { label: 'New Window', click() { console.log('New Window'); } }, { label: 'New Window with Settings', submenu: [ { label: 'Basic' }, { label: 'Pro'} @@ -208,24 +209,25 @@ You can use [BrowserWindow.setThumbarButtons][setthumbarbuttons] to set thumbnail toolbar in your application: ```javascript -const BrowserWindow = require('electron').BrowserWindow; +const {BrowserWindow} = require('electron'); const path = require('path'); -var win = new BrowserWindow({ +let win = new BrowserWindow({ width: 800, height: 600 }); + win.setThumbarButtons([ { - tooltip: "button1", + tooltip: 'button1', icon: path.join(__dirname, 'button1.png'), - click: function() { console.log("button2 clicked"); } + click() { console.log('button2 clicked'); } }, { - tooltip: "button2", + tooltip: 'button2', icon: path.join(__dirname, 'button2.png'), - flags:['enabled', 'dismissonclick'], - click: function() { console.log("button2 clicked."); } + flags: ['enabled', 'dismissonclick'], + click() { console.log('button2 clicked.'); } } ]); ``` @@ -246,12 +248,14 @@ __Launcher shortcuts of Audacious:__ ![audacious](https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles?action=AttachFile&do=get&target=shortcuts.png) -## Progress Bar in Taskbar (Windows & Unity) +## Progress Bar in Taskbar (Windows, OS X, Unity) On Windows a taskbar button can be used to display a progress bar. This enables a window to provide progress information to the user without the user having to switch to the window itself. +On OS X the progress bar will be displayed as a part of the dock icon. + The Unity DE also has a similar feature that allows you to specify the progress bar in the launcher. @@ -259,16 +263,12 @@ __Progress bar in taskbar button:__ ![Taskbar Progress Bar](https://cloud.githubusercontent.com/assets/639601/5081682/16691fda-6f0e-11e4-9676-49b6418f1264.png) -__Progress bar in Unity launcher:__ - -![Unity Launcher](https://cloud.githubusercontent.com/assets/639601/5081747/4a0a589e-6f0f-11e4-803f-91594716a546.png) - To set the progress bar for a Window, you can use the [BrowserWindow.setProgressBar][setprogressbar] API: ```javascript -var window = new BrowserWindow({...}); -window.setProgressBar(0.5); +let win = new BrowserWindow({...}); +win.setProgressBar(0.5); ``` ## Icon Overlays in Taskbar (Windows) @@ -294,8 +294,8 @@ To set the overlay icon for a window, you can use the [BrowserWindow.setOverlayIcon][setoverlayicon] API: ```javascript -var window = new BrowserWindow({...}); -window.setOverlayIcon('path/to/overlay.png', 'Description for overlay'); +let win = new BrowserWindow({...}); +win.setOverlayIcon('path/to/overlay.png', 'Description for overlay'); ``` ## Represented File of Window (OS X) @@ -316,9 +316,9 @@ To set the represented file of window, you can use the [BrowserWindow.setDocumentEdited][setdocumentedited] APIs: ```javascript -var window = new BrowserWindow({...}); -window.setRepresentedFilename('/etc/passwd'); -window.setDocumentEdited(true); +let win = new BrowserWindow({...}); +win.setRepresentedFilename('/etc/passwd'); +win.setDocumentEdited(true); ``` [addrecentdocument]: ../api/app.md#appaddrecentdocumentpath-os-x-windows diff --git a/docs/tutorial/devtools-extension.md b/docs/tutorial/devtools-extension.md index 7c7ea7d64a24..94f5ac13bf6c 100644 --- a/docs/tutorial/devtools-extension.md +++ b/docs/tutorial/devtools-extension.md @@ -1,62 +1,56 @@ # DevTools Extension -To make debugging easier, Electron has basic support for the -[Chrome DevTools Extension][devtools-extension]. +Electron supports the [Chrome DevTools Extension][devtools-extension], which can +be used to extend the ability of devtools for debugging popular web frameworks. -For most DevTools extensions you can simply download the source code and use -the `BrowserWindow.addDevToolsExtension` API to load them. The loaded extensions -will be remembered so you don't need to call the API every time when creating -a window. +## How to load a DevTools Extension -** NOTE: React DevTools does not work, follow the issue here https://github.com/atom/electron/issues/915 ** +To load an extension in Electron, you need to download it in Chrome browser, +locate its filesystem path, and then load it by calling the +`BrowserWindow.addDevToolsExtension(extension)` API. -For example, to use the [React DevTools Extension](https://github.com/facebook/react-devtools) -, first you need to download its source code: +Using the [React Developer Tools][react-devtools] as example: -```bash -$ cd /some-directory -$ git clone --recursive https://github.com/facebook/react-devtools.git -``` +1. Install it in Chrome browser. +1. Navigate to `chrome://extensions`, and find its extension ID, which is a hash + string like `fmkadmapgofadopljbjfkapdkoienihi`. +1. Find out filesystem location used by Chrome for storing extensions: + * on Windows it is `%LOCALAPPDATA%\Google\Chrome\User Data\Default\Extensions`; + * on Linux it could be: + * `~/.config/google-chrome/Default/Extensions/` + * `~/.config/google-chrome-beta/Default/Extensions/` + * `~/.config/google-chrome-canary/Default/Extensions/` + * `~/.config/chromium/Default/Extensions/` + * on OS X it is `~/Library/Application Support/Google/Chrome/Default/Extensions`. +1. Pass the location of the extension to `BrowserWindow.addDevToolsExtension` + API, for the React Developer Tools, it is something like: + `~/Library/Application Support/Google/Chrome/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/0.14.10_0` -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. +The name of the extension is returned by `BrowserWindow.addDevToolsExtension`, +and you can pass the name of the extension to the `BrowserWindow.removeDevToolsExtension` +API to unload it. -Then you can load the extension in Electron by opening DevTools in any window, -and running the following code in the DevTools console: +## Supported DevTools Extensions -```javascript -const BrowserWindow = require('electron').remote.BrowserWindow; -BrowserWindow.addDevToolsExtension('/some-directory/react-devtools/shells/chrome'); -``` +Electron only supports a limited set of `chrome.*` APIs, so some extensions +using unsupported `chrome.*` APIs for chrome extension features may not work. +Following Devtools Extensions are tested and guaranteed to work in Electron: -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: +* [Ember Inspector](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) +* [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi) +* [Backbone Debugger](https://chrome.google.com/webstore/detail/backbone-debugger/bhljhndlimiafopmmhjlgfpnnchjjbhd) +* [jQuery Debugger](https://chrome.google.com/webstore/detail/jquery-debugger/dbhhnnnpaeobfddmlalhnehgclcmjimi) +* [AngularJS Batarang](https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk) +* [Vue.js devtools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) -```javascript -BrowserWindow.removeDevToolsExtension('React Developer Tools'); -``` +### What should I do if a DevTools Extension is not working? -## Format of DevTools Extension +Fist please make sure the extension is still being maintained, some extensions +can not even work for recent versions of Chrome browser, and we are not able to +do anything for them. -Ideally all DevTools extensions written for the Chrome browser can be loaded by -Electron, but they have to be in a plain directory. For those packaged with -`crx` extensions, there is no way for Electron to load them unless you find a -way to extract them into a directory. - -## Background Pages - -Currently Electron doesn't support the background pages feature in Chrome -extensions, so some DevTools extensions that rely on this feature may -not work in Electron. - -## `chrome.*` APIs - -Some Chrome extensions may use `chrome.*` APIs for features and while there has -been some effort to implement those APIs in Electron, not all have been -implemented. - -Given that not all `chrome.*` APIs are implemented if the DevTools extension is -using APIs other than `chrome.devtools.*`, the extension is very likely not to -work. You can report failing extensions in the issue tracker so that we can add -support for those APIs. +Then file a bug at Electron's issues list, and describe which part of the +extension is not working as expected. [devtools-extension]: https://developer.chrome.com/extensions/devtools +[react-devtools]: https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi diff --git a/docs/tutorial/electron-versioning.md b/docs/tutorial/electron-versioning.md new file mode 100644 index 000000000000..bf442041c2cc --- /dev/null +++ b/docs/tutorial/electron-versioning.md @@ -0,0 +1,21 @@ +# Electron Versioning + +If you are a seasoned Node developer, you are surely aware of `semver` - and +might be used to giving your dependency management systems only rough guidelines +rather than fixed version numbers. Due to the hard dependency on Node and +Chromium, Electron is in a slightly more difficult position and does not follow +semver. You should therefore always reference a specific version of Electron. + +Version numbers are bumped using the following rules: + +* Major: For breaking changes in Electron's API - if you upgrade from `0.37.0` + to `1.0.0`, you will have to update your app. +* Minor: For major Chrome and minor Node upgrades; or significant Electron + changes - if you upgrade from `1.0.0` to `1.1.0`, your app is supposed to + still work, but you might have to work around small changes. +* Patch: For new features and bug fixes - if you upgrade from `1.0.0` to + `1.0.1`, your app will continue to work as-is. + +If you are using `electron-prebuilt`, we recommend that you set a fixed version +number (`1.1.0` instead of `^1.1.0`) to ensure that all upgrades of Electron are +a manual operation made by you, the developer. diff --git a/docs/tutorial/mac-app-store-submission-guide.md b/docs/tutorial/mac-app-store-submission-guide.md index e94a43106183..d240cc13371b 100644 --- a/docs/tutorial/mac-app-store-submission-guide.md +++ b/docs/tutorial/mac-app-store-submission-guide.md @@ -4,11 +4,7 @@ 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:__ From v0.36.0 there was a bug preventing GPU process to start after -the app being sandboxed, so it is recommended to use v0.35.x before this bug -gets fixed. You can find more about this in [issue #3871][issue-3871]. - -__Note:__ Submitting an app to Mac App Store requires enrolling [Apple Developer +**Note:** Submitting an app to Mac App Store requires enrolling [Apple Developer Program][developer-program], which costs money. ## How to Submit Your App @@ -23,14 +19,33 @@ how to meet the Mac App Store requirements. 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. +### Get Team ID + +Before signing your app, you need to know the Team ID of your account. To locate +your Team ID, Sign in to [Apple Developer Center](https://developer.apple.com/account/), +and click Membership in the sidebar. Your Team ID appears in the Membership +Information section under the team name. + ### Sign Your App -After getting the certificate from Apple, you can package your app by following +After finishing the preparation work, 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. +signing your app. -First, you need to prepare two entitlements files. +First, you have to add a `ElectronTeamID` key to your app's `Info.plist`, which +has your Team ID as key: + +```xml + + + ... + ElectronTeamID + TEAM_ID + + +``` + +Then, you need to prepare two entitlements files. `child.plist`: @@ -56,10 +71,15 @@ First, you need to prepare two entitlements files. com.apple.security.app-sandbox + com.apple.security.application-groups + TEAM_ID.your.bundle.id ``` +You have to replace `TEAM_ID` with your Team ID, and replace `your.bundle.id` +with the Bundle ID of your app. + And then sign your app with the following script: ```bash @@ -67,27 +87,31 @@ And then sign your app with the following script: # Name of your app. APP="YourApp" -# The path of you app to sign. -APP_PATH="/path/to/YouApp.app" +# The path of your app to sign. +APP_PATH="/path/to/YourApp.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)" +# The path of your plist files. +CHILD_PLIST="/path/to/child.plist" +PARENT_PLIST="/path/to/parent.plist" FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper.app/" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper EH.app/" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper NP.app/" -if [ -d "$FRAMEWORKS_PATH/Squirrel.framework/Versions/A" ]; then - # Signing a non-MAS build. - codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Mantle.framework/Versions/A" - codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/ReactiveCocoa.framework/Versions/A" - codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Squirrel.framework/Versions/A" -fi -codesign -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Electron Framework" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libffmpeg.dylib" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libnode.dylib" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper.app/Contents/MacOS/$APP Helper" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper.app/" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper EH.app/Contents/MacOS/$APP Helper EH" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper EH.app/" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper NP.app/Contents/MacOS/$APP Helper NP" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper NP.app/" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$APP_PATH/Contents/MacOS/$APP" +codesign -s "$APP_KEY" -f --entitlements "$PARENT_PLIST" "$APP_PATH" productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH" ``` @@ -96,11 +120,15 @@ 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 +### Upload Your App 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]. +before uploading. + +### Submit Your App for Review + +After these steps, you can [submit your app for review][submit-for-review]. ## Limitations of MAS Build @@ -163,5 +191,5 @@ ERN)][ern-tutorial]. [create-record]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/CreatingiTunesConnectRecord.html [submit-for-review]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SubmittingTheApp.html [app-sandboxing]: https://developer.apple.com/app-sandboxing/ -[issue-3871]: https://github.com/atom/electron/issues/3871 [ern-tutorial]: https://carouselapps.com/2015/12/15/legally-submit-app-apples-app-store-uses-encryption-obtain-ern/ +[temporary-exception]: https://developer.apple.com/library/mac/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/AppSandboxTemporaryExceptionEntitlements.html diff --git a/docs/tutorial/online-offline-events.md b/docs/tutorial/online-offline-events.md index d143118e0158..2b7a6d7edc6a 100644 --- a/docs/tutorial/online-offline-events.md +++ b/docs/tutorial/online-offline-events.md @@ -10,10 +10,11 @@ const electron = require('electron'); const app = electron.app; const BrowserWindow = electron.BrowserWindow; -var onlineStatusWindow; -app.on('ready', function() { +let onlineStatusWindow; + +app.on('ready', () => { onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); + onlineStatusWindow.loadURL(`file://${__dirname}/online-status.html`); }); ``` @@ -24,7 +25,7 @@ _online-status.html_ ` + }).join('') + const html = new Buffer(`${scripts}`) + + const contents = webContents.create({ + commandLineSwitches: ['--background-page'] + }) + backgroundPages[manifest.extensionId] = { html: html, webContents: contents } + contents.loadURL(url.format({ + protocol: 'chrome-extension', + slashes: true, + hostname: manifest.extensionId, + pathname: '_generated_background_page.html' + })) +} + +const removeBackgroundPages = function (manifest) { + if (!backgroundPages[manifest.extensionId]) return + + backgroundPages[manifest.extensionId].webContents.destroy() + delete backgroundPages[manifest.extensionId] +} + +// Dispatch tabs events. +const hookWindowForTabEvents = function (win) { + const tabId = win.webContents.id + for (const page of objectValues(backgroundPages)) { + page.webContents.sendToAll('CHROME_TABS_ONCREATED', tabId) + } + + win.once('closed', () => { + for (const page of objectValues(backgroundPages)) { + page.webContents.sendToAll('CHROME_TABS_ONREMOVED', tabId) + } + }) +} + +// Handle the chrome.* API messages. +let nextId = 0 + +ipcMain.on('CHROME_RUNTIME_CONNECT', function (event, extensionId, connectInfo) { + const page = backgroundPages[extensionId] + if (!page) { + console.error(`Connect to unkown extension ${extensionId}`) + return + } + + const portId = ++nextId + event.returnValue = {tabId: page.webContents.id, portId: portId} + + event.sender.once('render-view-deleted', () => { + if (page.webContents.isDestroyed()) return + page.webContents.sendToAll(`CHROME_PORT_DISCONNECT_${portId}`) + }) + page.webContents.sendToAll(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, event.sender.id, portId, connectInfo) +}) + +ipcMain.on('CHROME_RUNTIME_SENDMESSAGE', function (event, extensionId, message) { + const page = backgroundPages[extensionId] + if (!page) { + console.error(`Connect to unkown extension ${extensionId}`) + return + } + + page.webContents.sendToAll(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, event.sender.id, message) +}) + +ipcMain.on('CHROME_TABS_SEND_MESSAGE', function (event, tabId, extensionId, isBackgroundPage, message) { + const contents = webContents.fromId(tabId) + if (!contents) { + console.error(`Sending message to unkown tab ${tabId}`) + return + } + + const senderTabId = isBackgroundPage ? null : event.sender.id + + contents.sendToAll(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, senderTabId, message) +}) + +ipcMain.on('CHROME_TABS_EXECUTESCRIPT', function (event, requestId, tabId, extensionId, details) { + const contents = webContents.fromId(tabId) + if (!contents) { + console.error(`Sending message to unkown tab ${tabId}`) + return + } + + let code, url + if (details.file) { + const manifest = manifestMap[extensionId] + code = String(fs.readFileSync(path.join(manifest.srcDirectory, details.file))) + url = `chrome-extension://${extensionId}${details.file}` + } else { + code = details.code + url = `chrome-extension://${extensionId}/${String(Math.random()).substr(2, 8)}.js` + } + + contents.send('CHROME_TABS_EXECUTESCRIPT', event.sender.id, requestId, extensionId, url, code) +}) + +// Transfer the content scripts to renderer. +const contentScripts = {} + +const injectContentScripts = function (manifest) { + if (contentScripts[manifest.name] || !manifest.content_scripts) return + + const readArrayOfFiles = function (relativePath) { + return { + url: `chrome-extension://${manifest.extensionId}/${relativePath}`, + code: String(fs.readFileSync(path.join(manifest.srcDirectory, relativePath))) + } + } + + const contentScriptToEntry = function (script) { + return { + matches: script.matches, + js: script.js.map(readArrayOfFiles), + runAt: script.run_at || 'document_idle' + } + } + + try { + const entry = { + extensionId: manifest.extensionId, + contentScripts: manifest.content_scripts.map(contentScriptToEntry) + } + contentScripts[manifest.name] = renderProcessPreferences.addEntry(entry) + } catch (e) { + console.error('Failed to read content scripts', e) + } +} + +const removeContentScripts = function (manifest) { + if (!contentScripts[manifest.name]) return + + renderProcessPreferences.removeEntry(contentScripts[manifest.name]) + delete contentScripts[manifest.name] +} + +// Transfer the |manifest| to a format that can be recognized by the +// |DevToolsAPI.addExtensions|. +const manifestToExtensionInfo = function (manifest) { + return { + startPage: manifest.startPage, + srcDirectory: manifest.srcDirectory, + name: manifest.name, + exposeExperimentalAPIs: true + } +} + +// Load the extensions for the window. +const loadExtension = function (manifest) { + startBackgroundPages(manifest) + injectContentScripts(manifest) +} + +const loadDevToolsExtensions = function (win, manifests) { + if (!win.devToolsWebContents) return + + manifests.forEach(loadExtension) + + const extensionInfoArray = manifests.map(manifestToExtensionInfo) + win.devToolsWebContents.executeJavaScript(`DevToolsAPI.addExtensions(${JSON.stringify(extensionInfoArray)})`) +} + +// The persistent path of "DevTools Extensions" preference file. +let loadedExtensionsPath = null + +app.on('will-quit', function () { + try { + const loadedExtensions = objectValues(manifestMap).map(function (manifest) { + return manifest.srcDirectory + }) + if (loadedExtensions.length > 0) { + try { + fs.mkdirSync(path.dirname(loadedExtensionsPath)) + } catch (error) { + // Ignore error + } + fs.writeFileSync(loadedExtensionsPath, JSON.stringify(loadedExtensions)) + } else { + fs.unlinkSync(loadedExtensionsPath) + } + } catch (error) { + // Ignore error + } +}) + +// We can not use protocol or BrowserWindow until app is ready. +app.once('ready', function () { + // The chrome-extension: can map a extension URL request to real file path. + const chromeExtensionHandler = function (request, callback) { + const parsed = url.parse(request.url) + if (!parsed.hostname || !parsed.path) return callback() + + const manifest = manifestMap[parsed.hostname] + if (!manifest) return callback() + + if (parsed.path === '/_generated_background_page.html' && + backgroundPages[parsed.hostname]) { + return callback({ + mimeType: 'text/html', + data: backgroundPages[parsed.hostname].html + }) + } + + fs.readFile(path.join(manifest.srcDirectory, parsed.path), function (err, content) { + if (err) { + return callback(-6) // FILE_NOT_FOUND + } else { + return callback(content) + } + }) + } + protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler, function (error) { + if (error) { + console.error(`Unable to register chrome-extension protocol: ${error}`) + } + }) + + // Load persisted extensions. + loadedExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions') + try { + const loadedExtensions = JSON.parse(fs.readFileSync(loadedExtensionsPath)) + if (Array.isArray(loadedExtensions)) { + for (const srcDirectory of loadedExtensions) { + // Start background pages and set content scripts. + const manifest = getManifestFromPath(srcDirectory) + loadExtension(manifest) + } + } + } catch (error) { + // Ignore error + } + + // The public API to add/remove extensions. + BrowserWindow.addDevToolsExtension = function (srcDirectory) { + const manifest = getManifestFromPath(srcDirectory) + if (manifest) { + for (const win of BrowserWindow.getAllWindows()) { + loadDevToolsExtensions(win, [manifest]) + } + return manifest.name + } + } + BrowserWindow.removeDevToolsExtension = function (name) { + const manifest = manifestNameMap[name] + if (!manifest) return + + removeBackgroundPages(manifest) + removeContentScripts(manifest) + delete manifestMap[manifest.extensionId] + delete manifestNameMap[name] + } + + // Load extensions automatically when devtools is opened. + const init = BrowserWindow.prototype._init + BrowserWindow.prototype._init = function () { + init.call(this) + hookWindowForTabEvents(this) + this.webContents.on('devtools-opened', () => { + loadDevToolsExtensions(this, objectValues(manifestMap)) + }) + } +}) diff --git a/lib/browser/desktop-capturer.js b/lib/browser/desktop-capturer.js new file mode 100644 index 000000000000..da1d481c6052 --- /dev/null +++ b/lib/browser/desktop-capturer.js @@ -0,0 +1,77 @@ +'use strict' + +const ipcMain = require('electron').ipcMain +const desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer + +var deepEqual = function (opt1, opt2) { + return JSON.stringify(opt1) === JSON.stringify(opt2) +} + +// A queue for holding all requests from renderer process. +var requestsQueue = [] + +ipcMain.on('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', function (event, captureWindow, captureScreen, thumbnailSize, id) { + var request + request = { + id: id, + options: { + captureWindow: captureWindow, + captureScreen: captureScreen, + thumbnailSize: thumbnailSize + }, + webContents: event.sender + } + requestsQueue.push(request) + if (requestsQueue.length === 1) { + desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize) + } + + // If the WebContents is destroyed before receiving result, just remove the + // reference from requestsQueue to make the module not send the result to it. + event.sender.once('destroyed', function () { + request.webContents = null + }) +}) + +desktopCapturer.emit = function (event, name, sources) { + // Receiving sources result from main process, now send them back to renderer. + var handledRequest, i, len, ref, ref1, request, result, source, unhandledRequestsQueue + handledRequest = requestsQueue.shift(0) + result = (function () { + var i, len, results + results = [] + for (i = 0, len = sources.length; i < len; i++) { + source = sources[i] + results.push({ + id: source.id, + name: source.name, + thumbnail: source.thumbnail.toDataURL() + }) + } + return results + })() + if ((ref = handledRequest.webContents) != null) { + ref.send('ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_' + handledRequest.id, result) + } + + // Check the queue to see whether there is other same request. If has, handle + // it for reducing redunplicated `desktopCaptuer.startHandling` calls. + unhandledRequestsQueue = [] + for (i = 0, len = requestsQueue.length; i < len; i++) { + request = requestsQueue[i] + if (deepEqual(handledRequest.options, request.options)) { + if ((ref1 = request.webContents) != null) { + ref1.send('ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_' + request.id, result) + } + } else { + unhandledRequestsQueue.push(request) + } + } + requestsQueue = unhandledRequestsQueue + + // If the requestsQueue is not empty, start a new request handling. + if (requestsQueue.length > 0) { + const {captureWindow, captureScreen, thumbnailSize} = requestsQueue[0].options + return desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize) + } +} diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.js new file mode 100644 index 000000000000..6876a8d22c12 --- /dev/null +++ b/lib/browser/guest-view-manager.js @@ -0,0 +1,236 @@ +'use strict' + +const ipcMain = require('electron').ipcMain +const webContents = require('electron').webContents + +// Doesn't exist in early initialization. +var webViewManager = null + +var supportedWebViewEvents = [ + 'load-commit', + 'did-finish-load', + 'did-fail-load', + 'did-frame-finish-load', + 'did-start-loading', + 'did-stop-loading', + 'did-get-response-details', + 'did-get-redirect-request', + 'dom-ready', + 'console-message', + 'devtools-opened', + 'devtools-closed', + 'devtools-focused', + 'new-window', + 'will-navigate', + 'did-navigate', + 'did-navigate-in-page', + 'close', + 'crashed', + 'gpu-crashed', + 'plugin-crashed', + 'destroyed', + 'page-title-updated', + 'page-favicon-updated', + 'enter-html-full-screen', + 'leave-html-full-screen', + 'media-started-playing', + 'media-paused', + 'found-in-page', + 'did-change-theme-color' +] + +var nextInstanceId = 0 +var guestInstances = {} +var embedderElementsMap = {} +var reverseEmbedderElementsMap = {} + +// Moves the last element of array to the first one. +var moveLastToFirst = function (list) { + return list.unshift(list.pop()) +} + +// Generate guestInstanceId. +var getNextInstanceId = function () { + return ++nextInstanceId +} + +// Create a new guest instance. +var createGuest = function (embedder, params) { + var destroy, destroyEvents, event, fn, guest, i, id, j, len, len1, listeners + if (webViewManager == null) { + webViewManager = process.atomBinding('web_view_manager') + } + id = getNextInstanceId(embedder) + guest = webContents.create({ + isGuest: true, + partition: params.partition, + embedder: embedder + }) + guestInstances[id] = { + guest: guest, + embedder: embedder + } + + // Destroy guest when the embedder is gone or navigated. + destroyEvents = ['will-destroy', 'crashed', 'did-navigate'] + destroy = function () { + if (guestInstances[id] != null) { + return destroyGuest(embedder, id) + } + } + for (i = 0, len = destroyEvents.length; i < len; i++) { + event = destroyEvents[i] + embedder.once(event, destroy) + + // Users might also listen to the crashed event, so We must ensure the guest + // is destroyed before users' listener gets called. It is done by moving our + // listener to the first one in queue. + listeners = embedder._events[event] + if (Array.isArray(listeners)) { + moveLastToFirst(listeners) + } + } + guest.once('destroyed', function () { + var j, len1, results + results = [] + for (j = 0, len1 = destroyEvents.length; j < len1; j++) { + event = destroyEvents[j] + results.push(embedder.removeListener(event, destroy)) + } + return results + }) + + // Init guest web view after attached. + guest.once('did-attach', function () { + var opts + params = this.attachParams + delete this.attachParams + this.viewInstanceId = params.instanceId + this.setSize({ + normal: { + width: params.elementWidth, + height: params.elementHeight + }, + enableAutoSize: params.autosize, + min: { + width: params.minwidth, + height: params.minheight + }, + max: { + width: params.maxwidth, + height: params.maxheight + } + }) + if (params.src) { + opts = {} + if (params.httpreferrer) { + opts.httpReferrer = params.httpreferrer + } + if (params.useragent) { + opts.userAgent = params.useragent + } + this.loadURL(params.src, opts) + } + guest.allowPopups = params.allowpopups + }) + + // Dispatch events to embedder. + fn = function (event) { + return guest.on(event, function (_, ...args) { + embedder.send.apply(embedder, ['ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-' + guest.viewInstanceId, event].concat(args)) + }) + } + for (j = 0, len1 = supportedWebViewEvents.length; j < len1; j++) { + event = supportedWebViewEvents[j] + fn(event) + } + + // Dispatch guest's IPC messages to embedder. + guest.on('ipc-message-host', function (_, [channel, ...args]) { + embedder.send.apply(embedder, ['ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-' + guest.viewInstanceId, channel].concat(args)) + }) + + // Autosize. + guest.on('size-changed', function (_, ...args) { + embedder.send.apply(embedder, ['ELECTRON_GUEST_VIEW_INTERNAL_SIZE_CHANGED-' + guest.viewInstanceId].concat(args)) + }) + return id +} + +// Attach the guest to an element of embedder. +var attachGuest = function (embedder, elementInstanceId, guestInstanceId, params) { + var guest, key, oldGuestInstanceId, ref1, webPreferences + guest = guestInstances[guestInstanceId].guest + + // Destroy the old guest when attaching. + key = (embedder.getId()) + '-' + elementInstanceId + oldGuestInstanceId = embedderElementsMap[key] + if (oldGuestInstanceId != null) { + // Reattachment to the same guest is not currently supported. + if (oldGuestInstanceId === guestInstanceId) { + return + } + if (guestInstances[oldGuestInstanceId] == null) { + return + } + destroyGuest(embedder, oldGuestInstanceId) + } + webPreferences = { + guestInstanceId: guestInstanceId, + nodeIntegration: (ref1 = params.nodeintegration) != null ? ref1 : false, + plugins: params.plugins, + zoomFactor: params.zoomFactor, + webSecurity: !params.disablewebsecurity, + blinkFeatures: params.blinkfeatures + } + + if (params.preload) { + webPreferences.preloadURL = params.preload + } + webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences) + guest.attachParams = params + embedderElementsMap[key] = guestInstanceId + reverseEmbedderElementsMap[guestInstanceId] = key +} + +// Destroy an existing guest instance. +var destroyGuest = function (embedder, id) { + var key + webViewManager.removeGuest(embedder, id) + guestInstances[id].guest.destroy() + delete guestInstances[id] + key = reverseEmbedderElementsMap[id] + if (key != null) { + delete reverseEmbedderElementsMap[id] + return delete embedderElementsMap[key] + } +} + +ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, params, requestId) { + event.sender.send('ELECTRON_RESPONSE_' + requestId, createGuest(event.sender, params)) +}) + +ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, elementInstanceId, guestInstanceId, params) { + attachGuest(event.sender, elementInstanceId, guestInstanceId, params) +}) + +ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', function (event, id) { + destroyGuest(event.sender, id) +}) + +ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_SET_SIZE', function (event, id, params) { + var ref1 + return (ref1 = guestInstances[id]) != null ? ref1.guest.setSize(params) : void 0 +}) + +// Returns WebContents from its guest id. +exports.getGuest = function (id) { + var ref1 + return (ref1 = guestInstances[id]) != null ? ref1.guest : void 0 +} + +// Returns the embedder of the guest. +exports.getEmbedder = function (id) { + var ref1 + return (ref1 = guestInstances[id]) != null ? ref1.embedder : void 0 +} diff --git a/lib/browser/guest-window-manager.js b/lib/browser/guest-window-manager.js new file mode 100644 index 000000000000..a3d60bee8add --- /dev/null +++ b/lib/browser/guest-window-manager.js @@ -0,0 +1,124 @@ +'use strict' + +const ipcMain = require('electron').ipcMain +const BrowserWindow = require('electron').BrowserWindow + +var hasProp = {}.hasOwnProperty +var frameToGuest = {} + +// Copy attribute of |parent| to |child| if it is not defined in |child|. +var mergeOptions = function (child, parent) { + var key, value + for (key in parent) { + if (!hasProp.call(parent, key)) continue + value = parent[key] + if (!(key in child)) { + if (typeof value === 'object') { + child[key] = mergeOptions({}, value) + } else { + child[key] = value + } + } + } + return child +} + +// Merge |options| with the |embedder|'s window's options. +var mergeBrowserWindowOptions = function (embedder, options) { + if (embedder.browserWindowOptions != null) { + // Inherit the original options if it is a BrowserWindow. + mergeOptions(options, embedder.browserWindowOptions) + } else { + // Or only inherit web-preferences if it is a webview. + if (options.webPreferences == null) { + options.webPreferences = {} + } + mergeOptions(options.webPreferences, embedder.getWebPreferences()) + } + + // Disable node integration on child window if disabled on parent window + if (embedder.getWebPreferences().nodeIntegration === false) { + options.webPreferences.nodeIntegration = false + } + + return options +} + +// Create a new guest created by |embedder| with |options|. +var createGuest = function (embedder, url, frameName, options) { + var closedByEmbedder, closedByUser, guest, guestId, ref1 + guest = frameToGuest[frameName] + if (frameName && (guest != null)) { + guest.loadURL(url) + return guest.id + } + + // Remember the embedder window's id. + if (options.webPreferences == null) { + options.webPreferences = {} + } + options.webPreferences.openerId = (ref1 = BrowserWindow.fromWebContents(embedder)) != null ? ref1.id : void 0 + guest = new BrowserWindow(options) + guest.loadURL(url) + + // When |embedder| is destroyed we should also destroy attached guest, and if + // guest is closed by user then we should prevent |embedder| from double + // closing guest. + guestId = guest.id + closedByEmbedder = function () { + guest.removeListener('closed', closedByUser) + return guest.destroy() + } + closedByUser = function () { + embedder.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId) + return embedder.removeListener('render-view-deleted', closedByEmbedder) + } + embedder.once('render-view-deleted', closedByEmbedder) + guest.once('closed', closedByUser) + if (frameName) { + frameToGuest[frameName] = guest + guest.frameName = frameName + guest.once('closed', function () { + delete frameToGuest[frameName] + }) + } + return guest.id +} + +// Routed window.open messages. +ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', function (event, url, frameName, disposition, options) { + options = mergeBrowserWindowOptions(event.sender, options) + event.sender.emit('new-window', event, url, frameName, disposition, options) + if ((event.sender.isGuest() && !event.sender.allowPopups) || event.defaultPrevented) { + event.returnValue = null + } else { + event.returnValue = createGuest(event.sender, url, frameName, options) + } +}) + +ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', function (event, guestId) { + var ref1 + (ref1 = BrowserWindow.fromId(guestId)) != null ? ref1.destroy() : void 0 +}) + +ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function (event, guestId, method, ...args) { + var ref1 + event.returnValue = (ref1 = BrowserWindow.fromId(guestId)) != null ? ref1[method].apply(ref1, args) : void 0 +}) + +ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function (event, guestId, message, targetOrigin, sourceOrigin) { + var guestContents, ref1, ref2, sourceId + sourceId = (ref1 = BrowserWindow.fromWebContents(event.sender)) != null ? ref1.id : void 0 + if (sourceId == null) { + return + } + guestContents = (ref2 = BrowserWindow.fromId(guestId)) != null ? ref2.webContents : void 0 + if ((guestContents != null ? guestContents.getURL().indexOf(targetOrigin) : void 0) === 0 || targetOrigin === '*') { + guestContents != null ? guestContents.send('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin) : void 0 + } +}) + +ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', function (event, guestId, method, ...args) { + var ref1, ref2 + (ref1 = BrowserWindow.fromId(guestId)) != null ? (ref2 = ref1.webContents) != null ? ref2[method].apply(ref2, args) : void 0 : void 0 +}) diff --git a/lib/browser/init.js b/lib/browser/init.js new file mode 100644 index 000000000000..924ad1420c2e --- /dev/null +++ b/lib/browser/init.js @@ -0,0 +1,180 @@ +'use strict' + +const fs = require('fs') +const path = require('path') +const util = require('util') +const Module = require('module') +const v8 = require('v8') + +// We modified the original process.argv to let node.js load the atom.js, +// we need to restore it here. +process.argv.splice(1, 1) + +// Clear search paths. +require('../common/reset-search-paths') + +// Import common settings. +require('../common/init') + +var globalPaths = Module.globalPaths + +// Expose public APIs. +globalPaths.push(path.join(__dirname, 'api', 'exports')) + +if (process.platform === 'win32') { + // Redirect node's console to use our own implementations, since node can not + // handle console output when running as GUI program. + var consoleLog = function (...args) { + return process.log(util.format.apply(util, args) + '\n') + } + var streamWrite = function (chunk, encoding, callback) { + if (Buffer.isBuffer(chunk)) { + chunk = chunk.toString(encoding) + } + process.log(chunk) + if (callback) { + callback() + } + return true + } + console.log = console.error = console.warn = consoleLog + process.stdout.write = process.stderr.write = streamWrite + + // Always returns EOF for stdin stream. + var Readable = require('stream').Readable + var stdin = new Readable() + stdin.push(null) + process.__defineGetter__('stdin', function () { + return stdin + }) +} + +// Don't quit on fatal error. +process.on('uncaughtException', function (error) { + // Do nothing if the user has a custom uncaught exception handler. + var dialog, message, ref, stack + if (process.listeners('uncaughtException').length > 1) { + return + } + + // Show error in GUI. + dialog = require('electron').dialog + stack = (ref = error.stack) != null ? ref : error.name + ': ' + error.message + message = 'Uncaught Exception:\n' + stack + dialog.showErrorBox('A JavaScript error occurred in the main process', message) +}) + +// Emit 'exit' event on quit. +var app = require('electron').app + +app.on('quit', function (event, exitCode) { + process.emit('exit', exitCode) +}) + +if (process.platform === 'win32') { + // If we are a Squirrel.Windows-installed app, set app user model ID + // so that users don't have to do this. + // + // Squirrel packages are always of the form: + // + // PACKAGE-NAME + // - Update.exe + // - app-VERSION + // - OUREXE.exe + // + // Squirrel itself will always set the shortcut's App User Model ID to the + // form `com.squirrel.PACKAGE-NAME.OUREXE`. We need to call + // app.setAppUserModelId with a matching identifier so that renderer processes + // will inherit this value. + var updateDotExe = path.join( + path.dirname(process.execPath), + '..', + 'update.exe') + + if (fs.statSyncNoException(updateDotExe)) { + var packageDir = path.dirname(path.resolve(updateDotExe)) + var packageName = path.basename(packageDir) + var exeName = path.basename(process.execPath).replace(/\.exe$/i, '') + + app.setAppUserModelId(`com.squirrel.${packageName}.${exeName}`) + } +} + +// Map process.exit to app.exit, which quits gracefully. +process.exit = app.exit + +// Load the RPC server. +require('./rpc-server') + +// Load the guest view manager. +require('./guest-view-manager') + +require('./guest-window-manager') + +// Now we try to load app's package.json. +var packageJson = null +var searchPaths = ['app', 'app.asar', 'default_app.asar'] +var i, len, packagePath +for (i = 0, len = searchPaths.length; i < len; i++) { + packagePath = searchPaths[i] + try { + packagePath = path.join(process.resourcesPath, packagePath) + packageJson = JSON.parse(fs.readFileSync(path.join(packagePath, 'package.json'))) + break + } catch (error) { + continue + } +} + +if (packageJson == null) { + process.nextTick(function () { + return process.exit(1) + }) + throw new Error('Unable to find a valid app') +} + +// Set application's version. +if (packageJson.version != null) { + app.setVersion(packageJson.version) +} + +// Set application's name. +if (packageJson.productName != null) { + app.setName(packageJson.productName) +} else if (packageJson.name != null) { + app.setName(packageJson.name) +} + +// Set application's desktop name. +if (packageJson.desktopName != null) { + app.setDesktopName(packageJson.desktopName) +} else { + app.setDesktopName((app.getName()) + '.desktop') +} + +// Set v8 flags +if (packageJson.v8Flags != null) { + v8.setFlagsFromString(packageJson.v8Flags) +} + +// Chrome 42 disables NPAPI plugins by default, reenable them here +app.commandLine.appendSwitch('enable-npapi') + +// Set the user path according to application's name. +app.setPath('userData', path.join(app.getPath('appData'), app.getName())) + +app.setPath('userCache', path.join(app.getPath('cache'), app.getName())) + +app.setAppPath(packagePath) + +// Load the chrome extension support. +require('./chrome-extension') + +// Load internal desktop-capturer module. +require('./desktop-capturer') + +// Set main startup script of the app. +var mainStartupScript = packageJson.main || 'index.js' + +// Finally load app's main.js and transfer control to C++. +Module._load(path.join(packagePath, mainStartupScript), Module, true) diff --git a/atom/browser/lib/objects-registry.js b/lib/browser/objects-registry.js similarity index 50% rename from atom/browser/lib/objects-registry.js rename to lib/browser/objects-registry.js index c3f70c976ecb..adbf6835554c 100644 --- a/atom/browser/lib/objects-registry.js +++ b/lib/browser/objects-registry.js @@ -1,94 +1,97 @@ -'use strict'; +'use strict' -const v8Util = process.atomBinding('v8_util'); +const v8Util = process.atomBinding('v8_util') class ObjectsRegistry { - constructor() { - this.nextId = 0; + constructor () { + this.nextId = 0 // Stores all objects by ref-counting. // (id) => {object, count} - this.storage = {}; + this.storage = {} // Stores the IDs of objects referenced by WebContents. // (webContentsId) => [id] - this.owners = {}; + this.owners = {} } // Register a new object and return its assigned ID. If the object is already // registered then the already assigned ID would be returned. - add(webContents, obj) { + add (webContents, obj) { // Get or assign an ID to the object. - let id = this.saveToStorage(obj); + let id = this.saveToStorage(obj) // Add object to the set of referenced objects. - let webContentsId = webContents.getId(); - let owner = this.owners[webContentsId]; + let webContentsId = webContents.getId() + let owner = this.owners[webContentsId] if (!owner) { - owner = this.owners[webContentsId] = new Set(); + owner = this.owners[webContentsId] = new Set() // Clear the storage when webContents is reloaded/navigated. webContents.once('render-view-deleted', (event, id) => { - this.clear(id); - }); + this.clear(id) + }) } if (!owner.has(id)) { - owner.add(id); + owner.add(id) // Increase reference count if not referenced before. - this.storage[id].count++; + this.storage[id].count++ } - return id; + return id } // Get an object according to its ID. - get(id) { - return this.storage[id].object; + get (id) { + return this.storage[id].object } // Dereference an object according to its ID. - remove(webContentsId, id) { + remove (webContentsId, id) { // Dereference from the storage. - this.dereference(id); + this.dereference(id) // Also remove the reference in owner. - this.owners[webContentsId].delete(id); + let owner = this.owners[webContentsId] + if (owner) { + owner.delete(id) + } } // Clear all references to objects refrenced by the WebContents. - clear(webContentsId) { - let owner = this.owners[webContentsId]; - if (!owner) - return; - for (let id of owner) - this.dereference(id); - delete this.owners[webContentsId]; + clear (webContentsId) { + let owner = this.owners[webContentsId] + if (!owner) return + + for (let id of owner) this.dereference(id) + + delete this.owners[webContentsId] } // Private: Saves the object into storage and assigns an ID for it. - saveToStorage(object) { - let id = v8Util.getHiddenValue(object, 'atomId'); + saveToStorage (object) { + let id = v8Util.getHiddenValue(object, 'atomId') if (!id) { - id = ++this.nextId; + id = ++this.nextId this.storage[id] = { count: 0, object: object - }; - v8Util.setHiddenValue(object, 'atomId', id); + } + v8Util.setHiddenValue(object, 'atomId', id) } - return id; + return id } // Private: Dereference the object from store. - dereference(id) { - let pointer = this.storage[id]; + dereference (id) { + let pointer = this.storage[id] if (pointer == null) { - return; + return } - pointer.count -= 1; + pointer.count -= 1 if (pointer.count === 0) { - v8Util.deleteHiddenValue(pointer.object, 'atomId'); - return delete this.storage[id]; + v8Util.deleteHiddenValue(pointer.object, 'atomId') + return delete this.storage[id] } } } -module.exports = new ObjectsRegistry; +module.exports = new ObjectsRegistry() diff --git a/lib/browser/rpc-server.js b/lib/browser/rpc-server.js new file mode 100644 index 000000000000..0b954e518b95 --- /dev/null +++ b/lib/browser/rpc-server.js @@ -0,0 +1,369 @@ +'use strict' + +const electron = require('electron') +const v8Util = process.atomBinding('v8_util') +const {ipcMain, isPromise, webContents} = electron + +const objectsRegistry = require('./objects-registry') + +const hasProp = {}.hasOwnProperty + +// The internal properties of Function. +const FUNCTION_PROPERTIES = [ + 'length', 'name', 'arguments', 'caller', 'prototype' +] + +// The remote functions in renderer processes. +// id => Function +let rendererFunctions = v8Util.createDoubleIDWeakMap() + +// Return the description of object's members: +let getObjectMembers = function (object) { + let names = Object.getOwnPropertyNames(object) + // For Function, we should not override following properties even though they + // are "own" properties. + if (typeof object === 'function') { + names = names.filter((name) => { + return !FUNCTION_PROPERTIES.includes(name) + }) + } + // Map properties to descriptors. + return names.map((name) => { + let descriptor = Object.getOwnPropertyDescriptor(object, name) + let member = {name, enumerable: descriptor.enumerable, writable: false} + if (descriptor.get === undefined && typeof object[name] === 'function') { + member.type = 'method' + } else { + if (descriptor.set || descriptor.writable) member.writable = true + member.type = 'get' + } + return member + }) +} + +// Return the description of object's prototype. +let getObjectPrototype = function (object) { + let proto = Object.getPrototypeOf(object) + if (proto === null || proto === Object.prototype) return null + return { + members: getObjectMembers(proto), + proto: getObjectPrototype(proto) + } +} + +// Convert a real value into meta data. +let valueToMeta = function (sender, value, optimizeSimpleObject = false) { + // Determine the type of value. + let meta = { type: typeof value } + if (meta.type === 'object') { + // Recognize certain types of objects. + if (value === null) { + meta.type = 'value' + } else if (Buffer.isBuffer(value)) { + meta.type = 'buffer' + } else if (Array.isArray(value)) { + meta.type = 'array' + } else if (value instanceof Error) { + meta.type = 'error' + } else if (value instanceof Date) { + meta.type = 'date' + } else if (isPromise(value)) { + meta.type = 'promise' + } else if (hasProp.call(value, 'callee') && value.length != null) { + // Treat the arguments object as array. + meta.type = 'array' + } else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) { + // Treat simple objects as value. + meta.type = 'value' + } + } + + // Fill the meta object according to value's type. + if (meta.type === 'array') { + meta.members = value.map((el) => valueToMeta(sender, el)) + } else if (meta.type === 'object' || meta.type === 'function') { + meta.name = value.constructor ? value.constructor.name : '' + + // Reference the original value if it's an object, because when it's + // passed to renderer we would assume the renderer keeps a reference of + // it. + meta.id = objectsRegistry.add(sender, value) + meta.members = getObjectMembers(value) + meta.proto = getObjectPrototype(value) + } else if (meta.type === 'buffer') { + meta.value = Array.prototype.slice.call(value, 0) + } else if (meta.type === 'promise') { + meta.then = valueToMeta(sender, function (onFulfilled, onRejected) { + value.then(onFulfilled, onRejected) + }) + } else if (meta.type === 'error') { + meta.members = plainObjectToMeta(value) + + // Error.name is not part of own properties. + meta.members.push({ + name: 'name', + value: value.name + }) + } else if (meta.type === 'date') { + meta.value = value.getTime() + } else { + meta.type = 'value' + meta.value = value + } + return meta +} + +// Convert object to meta by value. +var plainObjectToMeta = function (obj) { + return Object.getOwnPropertyNames(obj).map(function (name) { + return { + name: name, + value: obj[name] + } + }) +} + +// Convert Error into meta data. +var exceptionToMeta = function (error) { + return { + type: 'exception', + message: error.message, + stack: error.stack || error + } +} + +// Convert array of meta data from renderer into array of real values. +var unwrapArgs = function (sender, args) { + var metaToValue + metaToValue = function (meta) { + var i, len, member, ref, returnValue + switch (meta.type) { + case 'value': + return meta.value + case 'remote-object': + return objectsRegistry.get(meta.id) + case 'array': + return unwrapArgs(sender, meta.value) + case 'buffer': + return new Buffer(meta.value) + case 'date': + return new Date(meta.value) + case 'promise': + return Promise.resolve({ + then: metaToValue(meta.then) + }) + case 'object': { + let ret = {} + Object.defineProperty(ret.constructor, 'name', { value: meta.name }) + + ref = meta.members + for (i = 0, len = ref.length; i < len; i++) { + member = ref[i] + ret[member.name] = metaToValue(member.value) + } + return ret + } + case 'function-with-return-value': + returnValue = metaToValue(meta.value) + return function () { + return returnValue + } + case 'function': { + // Merge webContentsId and meta.id, since meta.id can be the same in + // different webContents. + const webContentsId = sender.getId() + const objectId = [webContentsId, meta.id] + + // Cache the callbacks in renderer. + if (rendererFunctions.has(objectId)) { + return rendererFunctions.get(objectId) + } + + let callIntoRenderer = function (...args) { + if (!sender.isDestroyed() && webContentsId === sender.getId()) { + sender.send('ELECTRON_RENDERER_CALLBACK', meta.id, valueToMeta(sender, args)) + } else { + throw new Error(`Attempting to call a function in a renderer window that has been closed or released. Function provided here: ${meta.location}.`) + } + } + + v8Util.setRemoteCallbackFreer(callIntoRenderer, meta.id, sender) + rendererFunctions.set(objectId, callIntoRenderer) + return callIntoRenderer + } + default: + throw new TypeError(`Unknown type: ${meta.type}`) + } + } + return args.map(metaToValue) +} + +// Call a function and send reply asynchronously if it's a an asynchronous +// style function and the caller didn't pass a callback. +var callFunction = function (event, func, caller, args) { + var funcMarkedAsync, funcName, funcPassedCallback, ref, ret + funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous') + funcPassedCallback = typeof args[args.length - 1] === 'function' + try { + if (funcMarkedAsync && !funcPassedCallback) { + args.push(function (ret) { + event.returnValue = valueToMeta(event.sender, ret, true) + }) + return func.apply(caller, args) + } else { + ret = func.apply(caller, args) + event.returnValue = valueToMeta(event.sender, ret, true) + } + } catch (error) { + // Catch functions thrown further down in function invocation and wrap + // them with the function name so it's easier to trace things like + // `Error processing argument -1.` + funcName = ((ref = func.name) != null) ? ref : 'anonymous' + throw new Error(`Could not call remote function '${funcName}'. Check that the function signature is correct. Underlying error: ${error.message}`) + } +} + +ipcMain.on('ELECTRON_BROWSER_REQUIRE', function (event, module) { + try { + event.returnValue = valueToMeta(event.sender, process.mainModule.require(module)) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ELECTRON_BROWSER_GET_BUILTIN', function (event, module) { + try { + event.returnValue = valueToMeta(event.sender, electron[module]) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ELECTRON_BROWSER_GLOBAL', function (event, name) { + try { + event.returnValue = valueToMeta(event.sender, global[name]) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ELECTRON_BROWSER_CURRENT_WINDOW', function (event) { + try { + event.returnValue = valueToMeta(event.sender, event.sender.getOwnerBrowserWindow()) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event) { + event.returnValue = valueToMeta(event.sender, event.sender) +}) + +ipcMain.on('ELECTRON_BROWSER_CONSTRUCTOR', function (event, id, args) { + try { + args = unwrapArgs(event.sender, args) + let constructor = objectsRegistry.get(id) + + // Call new with array of arguments. + // http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible + let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args))) + event.returnValue = valueToMeta(event.sender, obj) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ELECTRON_BROWSER_FUNCTION_CALL', function (event, id, args) { + try { + args = unwrapArgs(event.sender, args) + let func = objectsRegistry.get(id) + callFunction(event, func, global, args) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, id, method, args) { + try { + args = unwrapArgs(event.sender, args) + let constructor = objectsRegistry.get(id)[method] + + // Call new with array of arguments. + let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args))) + event.returnValue = valueToMeta(event.sender, obj) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ELECTRON_BROWSER_MEMBER_CALL', function (event, id, method, args) { + try { + args = unwrapArgs(event.sender, args) + let obj = objectsRegistry.get(id) + callFunction(event, obj[method], obj, args) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ELECTRON_BROWSER_MEMBER_SET', function (event, id, name, value) { + try { + let obj = objectsRegistry.get(id) + obj[name] = value + event.returnValue = null + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ELECTRON_BROWSER_MEMBER_GET', function (event, id, name) { + try { + let obj = objectsRegistry.get(id) + event.returnValue = valueToMeta(event.sender, obj[name]) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ELECTRON_BROWSER_DEREFERENCE', function (event, id) { + objectsRegistry.remove(event.sender.getId(), id) +}) + +ipcMain.on('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, guestInstanceId) { + try { + let guestViewManager = require('./guest-view-manager') + event.returnValue = valueToMeta(event.sender, guestViewManager.getGuest(guestInstanceId)) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function (event, requestId, guestInstanceId, method, ...args) { + try { + let guestViewManager = require('./guest-view-manager') + let guest = guestViewManager.getGuest(guestInstanceId) + if (requestId) { + const responseCallback = function (result) { + event.sender.send(`ELECTRON_RENDERER_ASYNC_CALL_TO_GUEST_VIEW_RESPONSE_${requestId}`, result) + } + args.push(responseCallback) + } + guest[method].apply(guest, args) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ELECTRON_BROWSER_SEND_TO', function (event, sendToAll, webContentsId, channel, ...args) { + let contents = webContents.fromId(webContentsId) + if (!contents) { + console.error(`Sending message to WebContents with unknown ID ${webContentsId}`) + return + } + + if (sendToAll) { + contents.sendToAll(channel, ...args) + } else { + contents.send(channel, ...args) + } +}) diff --git a/lib/common/api/callbacks-registry.js b/lib/common/api/callbacks-registry.js new file mode 100644 index 000000000000..7a9bf534f72d --- /dev/null +++ b/lib/common/api/callbacks-registry.js @@ -0,0 +1,62 @@ +'use strict' + +const v8Util = process.atomBinding('v8_util') + +class CallbacksRegistry { + constructor () { + this.nextId = 0 + this.callbacks = {} + } + + add (callback) { + // The callback is already added. + var filenameAndLine, id, location, match, ref, regexp, stackString + id = v8Util.getHiddenValue(callback, 'callbackId') + if (id != null) { + return id + } + id = ++this.nextId + + // Capture the location of the function and put it in the ID string, + // so that release errors can be tracked down easily. + regexp = /at (.*)/gi + stackString = (new Error()).stack + while ((match = regexp.exec(stackString)) !== null) { + location = match[1] + if (location.indexOf('(native)') !== -1) { + continue + } + if (location.indexOf('electron.asar') !== -1) { + continue + } + ref = /([^\/^\)]*)\)?$/gi.exec(location) + filenameAndLine = ref[1] + break + } + this.callbacks[id] = callback + v8Util.setHiddenValue(callback, 'callbackId', id) + v8Util.setHiddenValue(callback, 'location', filenameAndLine) + return id + } + + get (id) { + var ref + return (ref = this.callbacks[id]) != null ? ref : function () {} + } + + call (id, ...args) { + var ref + return (ref = this.get(id)).call.apply(ref, [global].concat(args)) + } + + apply (id, ...args) { + var ref + return (ref = this.get(id)).apply.apply(ref, [global].concat(args)) + } + + remove (id) { + return delete this.callbacks[id] + } +} + +module.exports = CallbacksRegistry diff --git a/atom/common/api/lib/clipboard.js b/lib/common/api/clipboard.js similarity index 56% rename from atom/common/api/lib/clipboard.js rename to lib/common/api/clipboard.js index cd1cb53e4001..7186f8b63412 100644 --- a/atom/common/api/lib/clipboard.js +++ b/lib/common/api/clipboard.js @@ -1,6 +1,6 @@ if (process.platform === 'linux' && process.type === 'renderer') { // On Linux we could not access clipboard in renderer process. - module.exports = require('electron').remote.clipboard; + module.exports = require('electron').remote.clipboard } else { - module.exports = process.atomBinding('clipboard'); + module.exports = process.atomBinding('clipboard') } diff --git a/lib/common/api/crash-reporter.js b/lib/common/api/crash-reporter.js new file mode 100644 index 000000000000..13d7a07ad9cf --- /dev/null +++ b/lib/common/api/crash-reporter.js @@ -0,0 +1,88 @@ +'use strict' + +const os = require('os') +const path = require('path') +const spawn = require('child_process').spawn +const electron = require('electron') +const binding = process.atomBinding('crash_reporter') + +var CrashReporter = (function () { + function CrashReporter () {} + + CrashReporter.prototype.start = function (options) { + var app, args, autoSubmit, companyName, env, extra, ignoreSystemCrashHandler, start, submitURL + if (options == null) { + options = {} + } + this.productName = options.productName + companyName = options.companyName + submitURL = options.submitURL + autoSubmit = options.autoSubmit + ignoreSystemCrashHandler = options.ignoreSystemCrashHandler + extra = options.extra + + app = (process.type === 'browser' ? electron : electron.remote).app + if (this.productName == null) { + this.productName = app.getName() + } + if (autoSubmit == null) { + autoSubmit = true + } + if (ignoreSystemCrashHandler == null) { + ignoreSystemCrashHandler = false + } + if (extra == null) { + extra = {} + } + if (extra._productName == null) { + extra._productName = this.productName + } + if (extra._companyName == null) { + extra._companyName = companyName + } + if (extra._version == null) { + extra._version = app.getVersion() + } + if (companyName == null) { + throw new Error('companyName is a required option to crashReporter.start') + } + if (submitURL == null) { + throw new Error('submitURL is a required option to crashReporter.start') + } + start = () => { + binding.start(this.productName, companyName, submitURL, autoSubmit, ignoreSystemCrashHandler, extra) + } + if (process.platform === 'win32') { + args = ['--reporter-url=' + submitURL, '--application-name=' + this.productName, '--v=1'] + env = { + ELECTRON_INTERNAL_CRASH_SERVICE: 1 + } + spawn(process.execPath, args, { + env: env, + detached: true + }) + } + return start() + } + + CrashReporter.prototype.getLastCrashReport = function () { + var reports + reports = this.getUploadedReports() + if (reports.length > 0) { + return reports[0] + } else { + return null + } + } + + CrashReporter.prototype.getUploadedReports = function () { + var log, tmpdir + tmpdir = process.platform === 'win32' ? os.tmpdir() : '/tmp' + log = process.platform === 'darwin' ? path.join(tmpdir, this.productName + ' Crashes') : path.join(tmpdir, this.productName + ' Crashes', 'uploads.log') + return binding._getUploadedReports(log) + } + + return CrashReporter +})() + +module.exports = new CrashReporter() diff --git a/lib/common/api/deprecate.js b/lib/common/api/deprecate.js new file mode 100644 index 000000000000..c146e03e6a80 --- /dev/null +++ b/lib/common/api/deprecate.js @@ -0,0 +1,107 @@ +// Deprecate a method. +const deprecate = function (oldName, newName, fn) { + var warned + warned = false + return function () { + if (!(warned || process.noDeprecation)) { + warned = true + deprecate.warn(oldName, newName) + } + return fn.apply(this, arguments) + } +} + +// The method is renamed. +deprecate.rename = function (object, oldName, newName) { + var newMethod, warned + warned = false + newMethod = function () { + if (!(warned || process.noDeprecation)) { + warned = true + deprecate.warn(oldName, newName) + } + return this[newName].apply(this, arguments) + } + if (typeof object === 'function') { + object.prototype[oldName] = newMethod + } else { + object[oldName] = newMethod + } +} + +// Forward the method to member. +deprecate.member = function (object, method, member) { + var warned + warned = false + object.prototype[method] = function () { + if (!(warned || process.noDeprecation)) { + warned = true + deprecate.warn(method, member + '.' + method) + } + return this[member][method].apply(this[member], arguments) + } +} + +// Deprecate a property. +deprecate.property = function (object, property, method) { + return Object.defineProperty(object, property, { + get: function () { + var warned + warned = false + if (!(warned || process.noDeprecation)) { + warned = true + deprecate.warn(property + ' property', method + ' method') + } + return this[method]() + } + }) +} + +// Deprecate an event. +deprecate.event = function (emitter, oldName, newName, fn) { + var warned = false + return emitter.on(newName, function (...args) { + // there is listeners for old API. + if (this.listenerCount(oldName) > 0) { + if (!(warned || process.noDeprecation)) { + warned = true + deprecate.warn("'" + oldName + "' event", "'" + newName + "' event") + } + if (fn != null) { + fn.apply(this, arguments) + } else { + this.emit.apply(this, [oldName].concat(args)) + } + } + }) +} + +// Print deprecation warning. +deprecate.warn = function (oldName, newName) { + return deprecate.log(oldName + ' is deprecated. Use ' + newName + ' instead.') +} + +var deprecationHandler = null + +// Print deprecation message. +deprecate.log = function (message) { + if (typeof deprecationHandler === 'function') { + deprecationHandler(message) + } else if (process.throwDeprecation) { + throw new Error(message) + } else if (process.traceDeprecation) { + return console.trace(message) + } else { + return console.warn('(electron) ' + message) + } +} + +deprecate.setHandler = function (handler) { + deprecationHandler = handler +} + +deprecate.getHandler = function () { + return deprecationHandler +} + +module.exports = deprecate diff --git a/lib/common/api/deprecations.js b/lib/common/api/deprecations.js new file mode 100644 index 000000000000..f4c4d74d6b92 --- /dev/null +++ b/lib/common/api/deprecations.js @@ -0,0 +1,11 @@ +'use strict' + +const deprecate = require('electron').deprecate + +exports.setHandler = function (deprecationHandler) { + deprecate.setHandler(deprecationHandler) +} + +exports.getHandler = function () { + return deprecate.getHandler() +} diff --git a/lib/common/api/exports/electron.js b/lib/common/api/exports/electron.js new file mode 100644 index 000000000000..efa44d452cc6 --- /dev/null +++ b/lib/common/api/exports/electron.js @@ -0,0 +1,53 @@ +// Attaches properties to |exports|. +exports.defineProperties = function (exports) { + return Object.defineProperties(exports, { + // Common modules, please sort with alphabet order. + clipboard: { + // Must be enumerable, otherwise it woulde be invisible to remote module. + enumerable: true, + get: function () { + return require('../clipboard') + } + }, + crashReporter: { + enumerable: true, + get: function () { + return require('../crash-reporter') + } + }, + nativeImage: { + enumerable: true, + get: function () { + return require('../native-image') + } + }, + shell: { + enumerable: true, + get: function () { + return require('../shell') + } + }, + + // The internal modules, invisible unless you know their names. + CallbacksRegistry: { + get: function () { + return require('../callbacks-registry') + } + }, + deprecate: { + get: function () { + return require('../deprecate') + } + }, + deprecations: { + get: function () { + return require('../deprecations') + } + }, + isPromise: { + get: function () { + return require('../is-promise') + } + } + }) +} diff --git a/lib/common/api/is-promise.js b/lib/common/api/is-promise.js new file mode 100644 index 000000000000..d6115a145585 --- /dev/null +++ b/lib/common/api/is-promise.js @@ -0,0 +1,14 @@ +'use strict' + +module.exports = function isPromise (val) { + return ( + val && + val.then && + val.then instanceof Function && + val.constructor && + val.constructor.reject && + val.constructor.reject instanceof Function && + val.constructor.resolve && + val.constructor.resolve instanceof Function + ) +} diff --git a/lib/common/api/native-image.js b/lib/common/api/native-image.js new file mode 100644 index 000000000000..614298f51a0e --- /dev/null +++ b/lib/common/api/native-image.js @@ -0,0 +1 @@ +module.exports = process.atomBinding('native_image') diff --git a/lib/common/api/shell.js b/lib/common/api/shell.js new file mode 100644 index 000000000000..78e2a0938ba0 --- /dev/null +++ b/lib/common/api/shell.js @@ -0,0 +1 @@ +module.exports = process.atomBinding('shell') diff --git a/lib/common/asar.js b/lib/common/asar.js new file mode 100644 index 000000000000..6adc24e8ded0 --- /dev/null +++ b/lib/common/asar.js @@ -0,0 +1,616 @@ +(function () { + const asar = process.binding('atom_common_asar') + const childProcess = require('child_process') + const path = require('path') + const util = require('util') + + var hasProp = {}.hasOwnProperty + + // Cache asar archive objects. + var cachedArchives = {} + + var getOrCreateArchive = function (p) { + var archive + archive = cachedArchives[p] + if (archive != null) { + return archive + } + archive = asar.createArchive(p) + if (!archive) { + return false + } + cachedArchives[p] = archive + return archive + } + + // Clean cache on quit. + process.on('exit', function () { + var archive, p + for (p in cachedArchives) { + if (!hasProp.call(cachedArchives, p)) continue + archive = cachedArchives[p] + archive.destroy() + } + }) + + // Separate asar package's path from full path. + var splitPath = function (p) { + var index + + // shortcut to disable asar. + if (process.noAsar) { + return [false] + } + + if (typeof p !== 'string') { + return [false] + } + if (p.substr(-5) === '.asar') { + return [true, p, ''] + } + p = path.normalize(p) + index = p.lastIndexOf('.asar' + path.sep) + if (index === -1) { + return [false] + } + return [true, p.substr(0, index + 5), p.substr(index + 6)] + } + + // Convert asar archive's Stats object to fs's Stats object. + var nextInode = 0 + + var uid = process.getuid != null ? process.getuid() : 0 + + var gid = process.getgid != null ? process.getgid() : 0 + + var fakeTime = new Date() + + var asarStatsToFsStats = function (stats) { + return { + dev: 1, + ino: ++nextInode, + mode: 33188, + nlink: 1, + uid: uid, + gid: gid, + rdev: 0, + atime: stats.atime || fakeTime, + birthtime: stats.birthtime || fakeTime, + mtime: stats.mtime || fakeTime, + ctime: stats.ctime || fakeTime, + size: stats.size, + isFile: function () { + return stats.isFile + }, + isDirectory: function () { + return stats.isDirectory + }, + isSymbolicLink: function () { + return stats.isLink + }, + isBlockDevice: function () { + return false + }, + isCharacterDevice: function () { + return false + }, + isFIFO: function () { + return false + }, + isSocket: function () { + return false + } + } + } + + // Create a ENOENT error. + var notFoundError = function (asarPath, filePath, callback) { + var error + error = new Error(`ENOENT, ${filePath} not found in ${asarPath}`) + error.code = 'ENOENT' + error.errno = -2 + if (typeof callback !== 'function') { + throw error + } + return process.nextTick(function () { + return callback(error) + }) + } + + // Create a ENOTDIR error. + var notDirError = function (callback) { + var error + error = new Error('ENOTDIR, not a directory') + error.code = 'ENOTDIR' + error.errno = -20 + if (typeof callback !== 'function') { + throw error + } + return process.nextTick(function () { + return callback(error) + }) + } + + // Create invalid archive error. + var invalidArchiveError = function (asarPath, callback) { + var error + error = new Error(`Invalid package ${asarPath}`) + if (typeof callback !== 'function') { + throw error + } + return process.nextTick(function () { + return callback(error) + }) + } + + // Override APIs that rely on passing file path instead of content to C++. + var overrideAPISync = function (module, name, arg) { + var old + if (arg == null) { + arg = 0 + } + old = module[name] + module[name] = function () { + var archive, newPath, p + p = arguments[arg] + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return old.apply(this, arguments) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + invalidArchiveError(asarPath) + } + newPath = archive.copyFileOut(filePath) + if (!newPath) { + notFoundError(asarPath, filePath) + } + arguments[arg] = newPath + return old.apply(this, arguments) + } + } + + var overrideAPI = function (module, name, arg) { + var old + if (arg == null) { + arg = 0 + } + old = module[name] + module[name] = function () { + var archive, callback, newPath, p + p = arguments[arg] + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return old.apply(this, arguments) + } + callback = arguments[arguments.length - 1] + if (typeof callback !== 'function') { + return overrideAPISync(module, name, arg) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + return invalidArchiveError(asarPath, callback) + } + newPath = archive.copyFileOut(filePath) + if (!newPath) { + return notFoundError(asarPath, filePath, callback) + } + arguments[arg] = newPath + return old.apply(this, arguments) + } + } + + // Override fs APIs. + exports.wrapFsWithAsar = function (fs) { + var exists, existsSync, internalModuleReadFile, internalModuleStat, lstat, lstatSync, mkdir, mkdirSync, readFile, readFileSync, readdir, readdirSync, realpath, realpathSync, stat, statSync, statSyncNoException, logFDs, logASARAccess + + logFDs = {} + logASARAccess = function (asarPath, filePath, offset) { + if (!process.env.ELECTRON_LOG_ASAR_READS) { + return + } + if (!logFDs[asarPath]) { + var logFilename, logPath + const path = require('path') + logFilename = path.basename(asarPath, '.asar') + '-access-log.txt' + logPath = path.join(require('os').tmpdir(), logFilename) + logFDs[asarPath] = fs.openSync(logPath, 'a') + console.log('Logging ' + asarPath + ' access to ' + logPath) + } + fs.writeSync(logFDs[asarPath], offset + ': ' + filePath + '\n') + } + + lstatSync = fs.lstatSync + fs.lstatSync = function (p) { + var archive, stats + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return lstatSync(p) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + invalidArchiveError(asarPath) + } + stats = archive.stat(filePath) + if (!stats) { + notFoundError(asarPath, filePath) + } + return asarStatsToFsStats(stats) + } + lstat = fs.lstat + fs.lstat = function (p, callback) { + var archive, stats + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return lstat(p, callback) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + return invalidArchiveError(asarPath, callback) + } + stats = getOrCreateArchive(asarPath).stat(filePath) + if (!stats) { + return notFoundError(asarPath, filePath, callback) + } + return process.nextTick(function () { + return callback(null, asarStatsToFsStats(stats)) + }) + } + statSync = fs.statSync + fs.statSync = function (p) { + const [isAsar] = splitPath(p) + if (!isAsar) { + return statSync(p) + } + + // Do not distinguish links for now. + return fs.lstatSync(p) + } + stat = fs.stat + fs.stat = function (p, callback) { + const [isAsar] = splitPath(p) + if (!isAsar) { + return stat(p, callback) + } + + // Do not distinguish links for now. + return process.nextTick(function () { + return fs.lstat(p, callback) + }) + } + statSyncNoException = fs.statSyncNoException + fs.statSyncNoException = function (p) { + var archive, stats + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return statSyncNoException(p) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + return false + } + stats = archive.stat(filePath) + if (!stats) { + return false + } + return asarStatsToFsStats(stats) + } + realpathSync = fs.realpathSync + fs.realpathSync = function (p) { + var archive, real + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return realpathSync.apply(this, arguments) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + invalidArchiveError(asarPath) + } + real = archive.realpath(filePath) + if (real === false) { + notFoundError(asarPath, filePath) + } + return path.join(realpathSync(asarPath), real) + } + realpath = fs.realpath + fs.realpath = function (p, cache, callback) { + var archive, real + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return realpath.apply(this, arguments) + } + if (typeof cache === 'function') { + callback = cache + cache = void 0 + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + return invalidArchiveError(asarPath, callback) + } + real = archive.realpath(filePath) + if (real === false) { + return notFoundError(asarPath, filePath, callback) + } + return realpath(asarPath, function (err, p) { + if (err) { + return callback(err) + } + return callback(null, path.join(p, real)) + }) + } + exists = fs.exists + fs.exists = function (p, callback) { + var archive + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return exists(p, callback) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + return invalidArchiveError(asarPath, callback) + } + return process.nextTick(function () { + return callback(archive.stat(filePath) !== false) + }) + } + existsSync = fs.existsSync + fs.existsSync = function (p) { + var archive + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return existsSync(p) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + return false + } + return archive.stat(filePath) !== false + } + readFile = fs.readFile + fs.readFile = function (p, options, callback) { + var archive, buffer, encoding, fd, info, realPath + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return readFile.apply(this, arguments) + } + if (typeof options === 'function') { + callback = options + options = void 0 + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + return invalidArchiveError(asarPath, callback) + } + info = archive.getFileInfo(filePath) + if (!info) { + return notFoundError(asarPath, filePath, callback) + } + if (info.size === 0) { + return process.nextTick(function () { + return callback(null, new Buffer(0)) + }) + } + if (info.unpacked) { + realPath = archive.copyFileOut(filePath) + return fs.readFile(realPath, options, callback) + } + if (!options) { + options = { + encoding: null + } + } else if (util.isString(options)) { + options = { + encoding: options + } + } else if (!util.isObject(options)) { + throw new TypeError('Bad arguments') + } + encoding = options.encoding + buffer = new Buffer(info.size) + fd = archive.getFd() + if (!(fd >= 0)) { + return notFoundError(asarPath, filePath, callback) + } + logASARAccess(asarPath, filePath, info.offset) + return fs.read(fd, buffer, 0, info.size, info.offset, function (error) { + return callback(error, encoding ? buffer.toString(encoding) : buffer) + }) + } + readFileSync = fs.readFileSync + fs.readFileSync = function (p, opts) { + // this allows v8 to optimize this function + var archive, buffer, encoding, fd, info, options, realPath + options = opts + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return readFileSync.apply(this, arguments) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + invalidArchiveError(asarPath) + } + info = archive.getFileInfo(filePath) + if (!info) { + notFoundError(asarPath, filePath) + } + if (info.size === 0) { + if (options) { + return '' + } else { + return new Buffer(0) + } + } + if (info.unpacked) { + realPath = archive.copyFileOut(filePath) + return fs.readFileSync(realPath, options) + } + if (!options) { + options = { + encoding: null + } + } else if (util.isString(options)) { + options = { + encoding: options + } + } else if (!util.isObject(options)) { + throw new TypeError('Bad arguments') + } + encoding = options.encoding + buffer = new Buffer(info.size) + fd = archive.getFd() + if (!(fd >= 0)) { + notFoundError(asarPath, filePath) + } + logASARAccess(asarPath, filePath, info.offset) + fs.readSync(fd, buffer, 0, info.size, info.offset) + if (encoding) { + return buffer.toString(encoding) + } else { + return buffer + } + } + readdir = fs.readdir + fs.readdir = function (p, callback) { + var archive, files + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return readdir.apply(this, arguments) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + return invalidArchiveError(asarPath, callback) + } + files = archive.readdir(filePath) + if (!files) { + return notFoundError(asarPath, filePath, callback) + } + return process.nextTick(function () { + return callback(null, files) + }) + } + readdirSync = fs.readdirSync + fs.readdirSync = function (p) { + var archive, files + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return readdirSync.apply(this, arguments) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + invalidArchiveError(asarPath) + } + files = archive.readdir(filePath) + if (!files) { + notFoundError(asarPath, filePath) + } + return files + } + internalModuleReadFile = process.binding('fs').internalModuleReadFile + process.binding('fs').internalModuleReadFile = function (p) { + var archive, buffer, fd, info, realPath + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return internalModuleReadFile(p) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + return void 0 + } + info = archive.getFileInfo(filePath) + if (!info) { + return void 0 + } + if (info.size === 0) { + return '' + } + if (info.unpacked) { + realPath = archive.copyFileOut(filePath) + return fs.readFileSync(realPath, { + encoding: 'utf8' + }) + } + buffer = new Buffer(info.size) + fd = archive.getFd() + if (!(fd >= 0)) { + return void 0 + } + logASARAccess(asarPath, filePath, info.offset) + fs.readSync(fd, buffer, 0, info.size, info.offset) + return buffer.toString('utf8') + } + internalModuleStat = process.binding('fs').internalModuleStat + process.binding('fs').internalModuleStat = function (p) { + var archive, stats + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return internalModuleStat(p) + } + archive = getOrCreateArchive(asarPath) + + // -ENOENT + if (!archive) { + return -34 + } + stats = archive.stat(filePath) + + // -ENOENT + if (!stats) { + return -34 + } + if (stats.isDirectory) { + return 1 + } else { + return 0 + } + } + + // Calling mkdir for directory inside asar archive should throw ENOTDIR + // error, but on Windows it throws ENOENT. + // This is to work around the recursive looping bug of mkdirp since it is + // widely used. + if (process.platform === 'win32') { + mkdir = fs.mkdir + fs.mkdir = function (p, mode, callback) { + if (typeof mode === 'function') { + callback = mode + } + const [isAsar, , filePath] = splitPath(p) + if (isAsar && filePath.length) { + return notDirError(callback) + } + return mkdir(p, mode, callback) + } + mkdirSync = fs.mkdirSync + fs.mkdirSync = function (p, mode) { + const [isAsar, , filePath] = splitPath(p) + if (isAsar && filePath.length) { + notDirError() + } + return mkdirSync(p, mode) + } + } + + // Executing a command string containing a path to an asar + // archive confuses `childProcess.execFile`, which is internally + // called by `childProcess.{exec,execSync}`, causing + // Electron to consider the full command as a single path + // to an archive. + [ 'exec', 'execSync' ].forEach(function (functionName) { + var old = childProcess[functionName] + childProcess[functionName] = function () { + var processNoAsarOriginalValue = process.noAsar + process.noAsar = true + var result = old.apply(this, arguments) + process.noAsar = processNoAsarOriginalValue + return result + } + }) + + overrideAPI(fs, 'open') + overrideAPI(childProcess, 'execFile') + overrideAPISync(process, 'dlopen', 1) + overrideAPISync(require('module')._extensions, '.node', 1) + overrideAPISync(fs, 'openSync') + return overrideAPISync(childProcess, 'execFileSync') + } +})() diff --git a/lib/common/asar_init.js b/lib/common/asar_init.js new file mode 100644 index 000000000000..b8488aefb3ca --- /dev/null +++ b/lib/common/asar_init.js @@ -0,0 +1,20 @@ +;(function () { + return function (process, require, asarSource) { + // Make asar.js accessible via "require". + process.binding('natives').ELECTRON_ASAR = asarSource + + // Monkey-patch the fs module. + require('ELECTRON_ASAR').wrapFsWithAsar(require('fs')) + + // Make graceful-fs work with asar. + var source = process.binding('natives') + source['original-fs'] = source.fs + source['fs'] = ` +var nativeModule = new process.NativeModule('original-fs') +nativeModule.cache() +nativeModule.compile() +var asar = require('ELECTRON_ASAR') +asar.wrapFsWithAsar(nativeModule.exports) +module.exports = nativeModule.exports` + } +})() diff --git a/atom/common/lib/init.js b/lib/common/init.js similarity index 51% rename from atom/common/lib/init.js rename to lib/common/init.js index a4766c4a7f96..257249f0a639 100644 --- a/atom/common/lib/init.js +++ b/lib/common/init.js @@ -1,47 +1,48 @@ -const path = require('path'); -const timers = require('timers'); -const Module = require('module'); +const timers = require('timers') -process.atomBinding = function(name) { +process.atomBinding = function (name) { try { - return process.binding("atom_" + process.type + "_" + name); + return process.binding('atom_' + process.type + '_' + name) } catch (error) { if (/No such module/.test(error.message)) { - return process.binding("atom_common_" + name); + return process.binding('atom_common_' + name) } } -}; - -if (!process.env.ELECTRON_HIDE_INTERNAL_MODULES) { - // Add common/api/lib to module search paths. - Module.globalPaths.push(path.resolve(__dirname, '..', 'api', 'lib')); } - // setImmediate and process.nextTick makes use of uv_check and uv_prepare to // run the callbacks, however since we only run uv loop on requests, the // callbacks wouldn't be called until something else activated the uv loop, // which would delay the callbacks for arbitrary long time. So we should // initiatively activate the uv loop once setImmediate and process.nextTick is // called. -var wrapWithActivateUvLoop = function(func) { - return function() { - process.activateUvLoop(); - return func.apply(this, arguments); - }; -}; +var wrapWithActivateUvLoop = function (func) { + return function () { + process.activateUvLoop() + return func.apply(this, arguments) + } +} -process.nextTick = wrapWithActivateUvLoop(process.nextTick); +process.nextTick = wrapWithActivateUvLoop(process.nextTick) -global.setImmediate = wrapWithActivateUvLoop(timers.setImmediate); +global.setImmediate = wrapWithActivateUvLoop(timers.setImmediate) -global.clearImmediate = timers.clearImmediate; +global.clearImmediate = timers.clearImmediate if (process.type === 'browser') { // setTimeout needs to update the polling timeout of the event loop, when // called under Chromium's event loop the node's event loop won't get a chance // to update the timeout, so we have to force the node's event loop to // recalculate the timeout in browser process. - global.setTimeout = wrapWithActivateUvLoop(timers.setTimeout); - global.setInterval = wrapWithActivateUvLoop(timers.setInterval); + global.setTimeout = wrapWithActivateUvLoop(timers.setTimeout) + global.setInterval = wrapWithActivateUvLoop(timers.setInterval) +} + +// If we're running as a Windows Store app, __dirname will be set +// to C:/Program Files/WindowsApps. +// +// Nobody else get's to install there, changing the path is forbidden +// We can therefore say that we're running as appx +if (process.platform === 'win32' && __dirname.indexOf('\\Program Files\\WindowsApps\\') === 2) { + process.windowsStore = true } diff --git a/lib/common/reset-search-paths.js b/lib/common/reset-search-paths.js new file mode 100644 index 000000000000..4d09f6c89ca6 --- /dev/null +++ b/lib/common/reset-search-paths.js @@ -0,0 +1,49 @@ +const path = require('path') +const Module = require('module') + +// Clear Node's global search paths. +Module.globalPaths.length = 0 + +// Clear current and parent(init.js)'s search paths. +module.paths = [] +module.parent.paths = [] + +// Prevent Node from adding paths outside this app to search paths. +Module._nodeModulePaths = function (from) { + from = path.resolve(from) + + // If "from" is outside the app then we do nothing. + const skipOutsidePaths = from.startsWith(process.resourcesPath) + + // Following logic is copied from module.js. + const splitRe = process.platform === 'win32' ? /[\/\\]/ : /\// + const paths = [] + const parts = from.split(splitRe) + + let tip + let i + for (tip = i = parts.length - 1; i >= 0; tip = i += -1) { + const part = parts[tip] + if (part === 'node_modules') { + continue + } + const dir = parts.slice(0, tip + 1).join(path.sep) + if (skipOutsidePaths && !dir.startsWith(process.resourcesPath)) { + break + } + paths.push(path.join(dir, 'node_modules')) + } + return paths +} + +// Patch Module._resolveFilename to always require the Electron API when +// require('electron') is done. +const electronPath = path.join(__dirname, '..', process.type, 'api', 'exports', 'electron.js') +const originalResolveFilename = Module._resolveFilename +Module._resolveFilename = function (request, parent, isMain) { + if (request === 'electron') { + return electronPath + } else { + return originalResolveFilename(request, parent, isMain) + } +} diff --git a/lib/renderer/api/desktop-capturer.js b/lib/renderer/api/desktop-capturer.js new file mode 100644 index 000000000000..e998c786a593 --- /dev/null +++ b/lib/renderer/api/desktop-capturer.js @@ -0,0 +1,47 @@ +const ipcRenderer = require('electron').ipcRenderer +const nativeImage = require('electron').nativeImage + +var nextId = 0 +var includes = [].includes + +var getNextId = function () { + return ++nextId +} + +// |options.type| can not be empty and has to include 'window' or 'screen'. +var isValid = function (options) { + return ((options != null ? options.types : void 0) != null) && Array.isArray(options.types) +} + +exports.getSources = function (options, callback) { + var captureScreen, captureWindow, id + if (!isValid(options)) { + return callback(new Error('Invalid options')) + } + captureWindow = includes.call(options.types, 'window') + captureScreen = includes.call(options.types, 'screen') + if (options.thumbnailSize == null) { + options.thumbnailSize = { + width: 150, + height: 150 + } + } + id = getNextId() + ipcRenderer.send('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, options.thumbnailSize, id) + return ipcRenderer.once('ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_' + id, function (event, sources) { + var source + callback(null, (function () { + var i, len, results + results = [] + for (i = 0, len = sources.length; i < len; i++) { + source = sources[i] + results.push({ + id: source.id, + name: source.name, + thumbnail: nativeImage.createFromDataURL(source.thumbnail) + }) + } + return results + })()) + }) +} diff --git a/lib/renderer/api/exports/electron.js b/lib/renderer/api/exports/electron.js new file mode 100644 index 000000000000..8e05adc980c4 --- /dev/null +++ b/lib/renderer/api/exports/electron.js @@ -0,0 +1,38 @@ +const common = require('../../../common/api/exports/electron') + +// Import common modules. +common.defineProperties(exports) + +Object.defineProperties(exports, { + // Renderer side modules, please sort with alphabet order. + desktopCapturer: { + enumerable: true, + get: function () { + return require('../desktop-capturer') + } + }, + ipcRenderer: { + enumerable: true, + get: function () { + return require('../ipc-renderer') + } + }, + remote: { + enumerable: true, + get: function () { + return require('../remote') + } + }, + screen: { + enumerable: true, + get: function () { + return require('../screen') + } + }, + webFrame: { + enumerable: true, + get: function () { + return require('../web-frame') + } + } +}) diff --git a/lib/renderer/api/ipc-renderer.js b/lib/renderer/api/ipc-renderer.js new file mode 100644 index 000000000000..66c40d311da2 --- /dev/null +++ b/lib/renderer/api/ipc-renderer.js @@ -0,0 +1,37 @@ +'use strict' + +const binding = process.atomBinding('ipc') +const v8Util = process.atomBinding('v8_util') + +// Created by init.js. +const ipcRenderer = v8Util.getHiddenValue(global, 'ipc') + +ipcRenderer.send = function (...args) { + return binding.send('ipc-message', args) +} + +ipcRenderer.sendSync = function (...args) { + return JSON.parse(binding.sendSync('ipc-message-sync', args)) +} + +ipcRenderer.sendToHost = function (...args) { + return binding.send('ipc-message-host', args) +} + +ipcRenderer.sendTo = function (webContentsId, channel, ...args) { + if (typeof webContentsId !== 'number') { + throw new TypeError('First argument has to be webContentsId') + } + + ipcRenderer.send('ELECTRON_BROWSER_SEND_TO', false, webContentsId, channel, ...args) +} + +ipcRenderer.sendToAll = function (webContentsId, channel, ...args) { + if (typeof webContentsId !== 'number') { + throw new TypeError('First argument has to be webContentsId') + } + + ipcRenderer.send('ELECTRON_BROWSER_SEND_TO', true, webContentsId, channel, ...args) +} + +module.exports = ipcRenderer diff --git a/lib/renderer/api/remote.js b/lib/renderer/api/remote.js new file mode 100644 index 000000000000..b5c9fe3a0e49 --- /dev/null +++ b/lib/renderer/api/remote.js @@ -0,0 +1,308 @@ +'use strict' + +const v8Util = process.atomBinding('v8_util') +const {ipcRenderer, isPromise, CallbacksRegistry} = require('electron') + +const callbacksRegistry = new CallbacksRegistry() + +var includes = [].includes + +var remoteObjectCache = v8Util.createIDWeakMap() + +// Check for circular reference. +var isCircular = function (field, visited) { + if (typeof field === 'object') { + if (includes.call(visited, field)) { + return true + } + visited.push(field) + } + return false +} + +// Convert the arguments object into an array of meta data. +var wrapArgs = function (args, visited) { + var valueToMeta + if (visited == null) { + visited = [] + } + valueToMeta = function (value) { + var field, prop, ret + if (Array.isArray(value)) { + return { + type: 'array', + value: wrapArgs(value, visited) + } + } else if (Buffer.isBuffer(value)) { + return { + type: 'buffer', + value: Array.prototype.slice.call(value, 0) + } + } else if (value instanceof Date) { + return { + type: 'date', + value: value.getTime() + } + } else if ((value != null) && typeof value === 'object') { + if (isPromise(value)) { + return { + type: 'promise', + then: valueToMeta(function (onFulfilled, onRejected) { + value.then(onFulfilled, onRejected) + }) + } + } else if (v8Util.getHiddenValue(value, 'atomId')) { + return { + type: 'remote-object', + id: v8Util.getHiddenValue(value, 'atomId') + } + } + + ret = { + type: 'object', + name: value.constructor.name, + members: [] + } + for (prop in value) { + field = value[prop] + ret.members.push({ + name: prop, + value: valueToMeta(isCircular(field, visited) ? null : field) + }) + } + return ret + } else if (typeof value === 'function' && v8Util.getHiddenValue(value, 'returnValue')) { + return { + type: 'function-with-return-value', + value: valueToMeta(value()) + } + } else if (typeof value === 'function') { + return { + type: 'function', + id: callbacksRegistry.add(value), + location: v8Util.getHiddenValue(value, 'location') + } + } else { + return { + type: 'value', + value: value + } + } + } + return Array.prototype.slice.call(args).map(valueToMeta) +} + +// Populate object's members from descriptors. +// The |ref| will be kept referenced by |members|. +// This matches |getObjectMemebers| in rpc-server. +let setObjectMembers = function (ref, object, metaId, members) { + for (let member of members) { + if (object.hasOwnProperty(member.name)) continue + + let descriptor = { enumerable: member.enumerable } + if (member.type === 'method') { + let remoteMemberFunction = function () { + if (this && this.constructor === remoteMemberFunction) { + // Constructor call. + let ret = ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', metaId, member.name, wrapArgs(arguments)) + return metaToValue(ret) + } else { + // Call member function. + let ret = ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_CALL', metaId, member.name, wrapArgs(arguments)) + return metaToValue(ret) + } + } + descriptor.get = function () { + remoteMemberFunction.ref = ref // The member should reference its object. + return remoteMemberFunction + } + // Enable monkey-patch the method + descriptor.set = function (value) { + remoteMemberFunction = value + return value + } + descriptor.configurable = true + } else if (member.type === 'get') { + descriptor.get = function () { + return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_GET', metaId, member.name)) + } + + // Only set setter when it is writable. + if (member.writable) { + descriptor.set = function (value) { + ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_SET', metaId, member.name, value) + return value + } + } + } + + Object.defineProperty(object, member.name, descriptor) + } +} + +// Populate object's prototype from descriptor. +// This matches |getObjectPrototype| in rpc-server. +let setObjectPrototype = function (ref, object, metaId, descriptor) { + if (descriptor === null) return + let proto = {} + setObjectMembers(ref, proto, metaId, descriptor.members) + setObjectPrototype(ref, proto, metaId, descriptor.proto) + Object.setPrototypeOf(object, proto) +} + +// Convert meta data from browser into real value. +let metaToValue = function (meta) { + var el, i, len, ref1, results, ret + switch (meta.type) { + case 'value': + return meta.value + case 'array': + ref1 = meta.members + results = [] + for (i = 0, len = ref1.length; i < len; i++) { + el = ref1[i] + results.push(metaToValue(el)) + } + return results + case 'buffer': + return new Buffer(meta.value) + case 'promise': + return Promise.resolve({ + then: metaToValue(meta.then) + }) + case 'error': + return metaToPlainObject(meta) + case 'date': + return new Date(meta.value) + case 'exception': + throw new Error(meta.message + '\n' + meta.stack) + default: + if (remoteObjectCache.has(meta.id)) return remoteObjectCache.get(meta.id) + + if (meta.type === 'function') { + // A shadow class to represent the remote function object. + let remoteFunction = function () { + if (this && this.constructor === remoteFunction) { + // Constructor call. + let obj = ipcRenderer.sendSync('ELECTRON_BROWSER_CONSTRUCTOR', meta.id, wrapArgs(arguments)) + // Returning object in constructor will replace constructed object + // with the returned object. + // http://stackoverflow.com/questions/1978049/what-values-can-a-constructor-return-to-avoid-returning-this + return metaToValue(obj) + } else { + // Function call. + let obj = ipcRenderer.sendSync('ELECTRON_BROWSER_FUNCTION_CALL', meta.id, wrapArgs(arguments)) + return metaToValue(obj) + } + } + ret = remoteFunction + } else { + ret = {} + } + + // Populate delegate members. + setObjectMembers(ret, ret, meta.id, meta.members) + // Populate delegate prototype. + setObjectPrototype(ret, ret, meta.id, meta.proto) + + // Set constructor.name to object's name. + Object.defineProperty(ret.constructor, 'name', { value: meta.name }) + + // Track delegate object's life time, and tell the browser to clean up + // when the object is GCed. + v8Util.setRemoteObjectFreer(ret, meta.id) + + // Remember object's id. + v8Util.setHiddenValue(ret, 'atomId', meta.id) + remoteObjectCache.set(meta.id, ret) + return ret + } +} + +// Construct a plain object from the meta. +var metaToPlainObject = function (meta) { + var i, len, obj, ref1 + obj = (function () { + switch (meta.type) { + case 'error': + return new Error() + default: + return {} + } + })() + ref1 = meta.members + for (i = 0, len = ref1.length; i < len; i++) { + let {name, value} = ref1[i] + obj[name] = value + } + return obj +} + +// Browser calls a callback in renderer. +ipcRenderer.on('ELECTRON_RENDERER_CALLBACK', function (event, id, args) { + callbacksRegistry.apply(id, metaToValue(args)) +}) + +// A callback in browser is released. +ipcRenderer.on('ELECTRON_RENDERER_RELEASE_CALLBACK', function (event, id) { + callbacksRegistry.remove(id) +}) + +// List all built-in modules in browser process. +const browserModules = require('../../browser/api/exports/electron') + +// And add a helper receiver for each one. +for (let name of Object.getOwnPropertyNames(browserModules)) { + Object.defineProperty(exports, name, { + get: function () { + return exports.getBuiltin(name) + } + }) +} + +// Get remote module. +exports.require = function (module) { + return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_REQUIRE', module)) +} + +// Alias to remote.require('electron').xxx. +exports.getBuiltin = function (module) { + return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_GET_BUILTIN', module)) +} + +// Get current BrowserWindow. +exports.getCurrentWindow = function () { + return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WINDOW')) +} + +// Get current WebContents object. +exports.getCurrentWebContents = function () { + return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS')) +} + +// Get a global object in browser. +exports.getGlobal = function (name) { + return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_GLOBAL', name)) +} + +// Get the process object in browser. +exports.__defineGetter__('process', function () { + return exports.getGlobal('process') +}) + +// Create a funtion that will return the specifed value when called in browser. +exports.createFunctionWithReturnValue = function (returnValue) { + var func + func = function () { + return returnValue + } + v8Util.setHiddenValue(func, 'returnValue', true) + return func +} + +// Get the guest WebContents from guestInstanceId. +exports.getGuestWebContents = function (guestInstanceId) { + var meta + meta = ipcRenderer.sendSync('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', guestInstanceId) + return metaToValue(meta) +} diff --git a/lib/renderer/api/screen.js b/lib/renderer/api/screen.js new file mode 100644 index 000000000000..9eecd49dc5bf --- /dev/null +++ b/lib/renderer/api/screen.js @@ -0,0 +1 @@ +module.exports = require('electron').remote.screen diff --git a/lib/renderer/api/web-frame.js b/lib/renderer/api/web-frame.js new file mode 100644 index 000000000000..81855c391f8f --- /dev/null +++ b/lib/renderer/api/web-frame.js @@ -0,0 +1,13 @@ +'use strict' + +const EventEmitter = require('events').EventEmitter + +const webFrame = process.atomBinding('web_frame').webFrame + +// webFrame is an EventEmitter. +Object.setPrototypeOf(webFrame, EventEmitter.prototype) + +// Lots of webview would subscribe to webFrame's events. +webFrame.setMaxListeners(0) + +module.exports = webFrame diff --git a/lib/renderer/chrome-api.js b/lib/renderer/chrome-api.js new file mode 100644 index 000000000000..61fce14c5ba9 --- /dev/null +++ b/lib/renderer/chrome-api.js @@ -0,0 +1,200 @@ +const {ipcRenderer} = require('electron') +const url = require('url') + +let nextId = 0 + +class Event { + constructor () { + this.listeners = [] + } + + addListener (callback) { + this.listeners.push(callback) + } + + removeListener (callback) { + const index = this.listeners.indexOf(callback) + if (index !== -1) { + this.listeners.splice(index, 1) + } + } + + emit (...args) { + for (const listener of this.listeners) { + listener(...args) + } + } +} + +class Tab { + constructor (tabId) { + this.id = tabId + } +} + +class MessageSender { + constructor (tabId, extensionId) { + this.tab = tabId ? new Tab(tabId) : null + this.id = extensionId + this.url = `chrome-extension://${extensionId}` + } +} + +class Port { + constructor (tabId, portId, extensionId, name) { + this.tabId = tabId + this.portId = portId + this.disconnected = false + + this.name = name + this.onDisconnect = new Event() + this.onMessage = new Event() + this.sender = new MessageSender(tabId, extensionId) + + ipcRenderer.once(`CHROME_PORT_DISCONNECT_${portId}`, () => { + this._onDisconnect() + }) + ipcRenderer.on(`CHROME_PORT_POSTMESSAGE_${portId}`, (event, message) => { + const sendResponse = function () { console.error('sendResponse is not implemented') } + this.onMessage.emit(message, this.sender, sendResponse) + }) + } + + disconnect () { + if (this.disconnected) return + + ipcRenderer.sendToAll(this.tabId, `CHROME_PORT_DISCONNECT_${this.portId}`) + this._onDisconnect() + } + + postMessage (message) { + ipcRenderer.sendToAll(this.tabId, `CHROME_PORT_POSTMESSAGE_${this.portId}`, message) + } + + _onDisconnect () { + this.disconnected = true + ipcRenderer.removeAllListeners(`CHROME_PORT_POSTMESSAGE_${this.portId}`) + this.onDisconnect.emit() + } +} + +// Inject chrome API to the |context| +exports.injectTo = function (extensionId, isBackgroundPage, context) { + const chrome = context.chrome = context.chrome || {} + + ipcRenderer.on(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, (event, tabId, portId, connectInfo) => { + chrome.runtime.onConnect.emit(new Port(tabId, portId, extensionId, connectInfo.name)) + }) + + ipcRenderer.on(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, (event, tabId, message) => { + chrome.runtime.onMessage.emit(message, new MessageSender(tabId, extensionId)) + }) + + ipcRenderer.on('CHROME_TABS_ONCREATED', (event, tabId) => { + chrome.tabs.onCreated.emit(new Tab(tabId)) + }) + + ipcRenderer.on('CHROME_TABS_ONREMOVED', (event, tabId) => { + chrome.tabs.onRemoved.emit(tabId) + }) + + chrome.runtime = { + getURL: function (path) { + return url.format({ + protocol: 'chrome-extension', + slashes: true, + hostname: extensionId, + pathname: path + }) + }, + + connect (...args) { + if (isBackgroundPage) { + console.error('chrome.runtime.connect is not supported in background page') + return + } + + // Parse the optional args. + let targetExtensionId = extensionId + let connectInfo = {name: ''} + if (args.length === 1) { + connectInfo = args[0] + } else if (args.length === 2) { + [targetExtensionId, connectInfo] = args + } + + const {tabId, portId} = ipcRenderer.sendSync('CHROME_RUNTIME_CONNECT', targetExtensionId, connectInfo) + return new Port(tabId, portId, extensionId, connectInfo.name) + }, + + sendMessage (...args) { + if (isBackgroundPage) { + console.error('chrome.runtime.sendMessage is not supported in background page') + return + } + + // Parse the optional args. + let targetExtensionId = extensionId + let message + if (args.length === 1) { + message = args[0] + } else if (args.length === 2) { + [targetExtensionId, message] = args + } else { + console.error('options and responseCallback are not supported') + } + + ipcRenderer.send('CHROME_RUNTIME_SENDMESSAGE', targetExtensionId, message) + }, + + onConnect: new Event(), + onMessage: new Event(), + onInstalled: new Event() + } + + chrome.tabs = { + executeScript (tabId, details, callback) { + const requestId = ++nextId + ipcRenderer.once(`CHROME_TABS_EXECUTESCRIPT_RESULT_${requestId}`, (event, result) => { + callback([event.result]) + }) + ipcRenderer.send('CHROME_TABS_EXECUTESCRIPT', requestId, tabId, extensionId, details) + }, + + sendMessage (tabId, message, options, responseCallback) { + if (responseCallback) { + console.error('responseCallback is not supported') + } + ipcRenderer.send('CHROME_TABS_SEND_MESSAGE', tabId, extensionId, isBackgroundPage, message) + }, + + onUpdated: new Event(), + onCreated: new Event(), + onRemoved: new Event() + } + + chrome.extension = { + getURL: chrome.runtime.getURL, + connect: chrome.runtime.connect, + onConnect: chrome.runtime.onConnect, + sendMessage: chrome.runtime.sendMessage, + onMessage: chrome.runtime.onMessage + } + + chrome.storage = { + sync: { + get () {}, + set () {} + } + } + + chrome.pageAction = { + show () {}, + hide () {}, + setTitle () {}, + getTitle () {}, + setIcon () {}, + setPopup () {}, + getPopup () {} + } +} diff --git a/lib/renderer/content-scripts-injector.js b/lib/renderer/content-scripts-injector.js new file mode 100644 index 000000000000..e4a801110f62 --- /dev/null +++ b/lib/renderer/content-scripts-injector.js @@ -0,0 +1,61 @@ +const {ipcRenderer} = require('electron') +const {runInThisContext} = require('vm') + +// Check whether pattern matches. +// https://developer.chrome.com/extensions/match_patterns +const matchesPattern = function (pattern) { + if (pattern === '') return true + + const regexp = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$') + return location.href.match(regexp) +} + +// Run the code with chrome API integrated. +const runContentScript = function (extensionId, url, code) { + const context = {} + require('./chrome-api').injectTo(extensionId, false, context) + const wrapper = `(function (chrome) {\n ${code}\n })` + const compiledWrapper = runInThisContext(wrapper, { + filename: url, + lineOffset: 1, + displayErrors: true + }) + return compiledWrapper.call(this, context.chrome) +} + +// Run injected scripts. +// https://developer.chrome.com/extensions/content_scripts +const injectContentScript = function (extensionId, script) { + for (const match of script.matches) { + if (!matchesPattern(match)) return + } + + for (const {url, code} of script.js) { + const fire = runContentScript.bind(window, extensionId, url, code) + if (script.runAt === 'document_start') { + process.once('document-start', fire) + } else if (script.runAt === 'document_end') { + process.once('document-end', fire) + } else if (script.runAt === 'document_idle') { + document.addEventListener('DOMContentLoaded', fire) + } + } +} + +// Handle the request of chrome.tabs.executeJavaScript. +ipcRenderer.on('CHROME_TABS_EXECUTESCRIPT', function (event, senderWebContentsId, requestId, extensionId, url, code) { + const result = runContentScript.call(window, extensionId, url, code) + ipcRenderer.sendToAll(senderWebContentsId, `CHROME_TABS_EXECUTESCRIPT_RESULT_${requestId}`, result) +}) + +// Read the renderer process preferences. +const preferences = process.getRenderProcessPreferences() +if (preferences) { + for (const pref of preferences) { + if (pref.contentScripts) { + for (const script of pref.contentScripts) { + injectContentScript(pref.extensionId, script) + } + } + } +} diff --git a/lib/renderer/init.js b/lib/renderer/init.js new file mode 100644 index 000000000000..f0875cae12c2 --- /dev/null +++ b/lib/renderer/init.js @@ -0,0 +1,133 @@ +'use strict' + +const events = require('events') +const path = require('path') +const Module = require('module') + +// We modified the original process.argv to let node.js load the +// atom-renderer.js, we need to restore it here. +process.argv.splice(1, 1) + +// Clear search paths. +require('../common/reset-search-paths') + +// Import common settings. +require('../common/init') + +var globalPaths = Module.globalPaths + +// Expose public APIs. +globalPaths.push(path.join(__dirname, 'api', 'exports')) + +// The global variable will be used by ipc for event dispatching +var v8Util = process.atomBinding('v8_util') + +v8Util.setHiddenValue(global, 'ipc', new events.EventEmitter()) + +// Use electron module after everything is ready. +const electron = require('electron') + +// Call webFrame method. +electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', (event, method, args) => { + electron.webFrame[method].apply(electron.webFrame, args) +}) + +electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', (event, requestId, method, args) => { + const responseCallback = function (result) { + event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, result) + } + args.push(responseCallback) + electron.webFrame[method].apply(electron.webFrame, args) +}) + +// Process command line arguments. +let nodeIntegration = 'false' +let preloadScript = null +let isBackgroundPage = false +for (let arg of process.argv) { + if (arg.indexOf('--guest-instance-id=') === 0) { + // This is a guest web view. + process.guestInstanceId = parseInt(arg.substr(arg.indexOf('=') + 1)) + } else if (arg.indexOf('--opener-id=') === 0) { + // This is a guest BrowserWindow. + process.openerId = parseInt(arg.substr(arg.indexOf('=') + 1)) + } else if (arg.indexOf('--node-integration=') === 0) { + nodeIntegration = arg.substr(arg.indexOf('=') + 1) + } else if (arg.indexOf('--preload=') === 0) { + preloadScript = arg.substr(arg.indexOf('=') + 1) + } else if (arg === '--background-page') { + isBackgroundPage = true + } +} + +if (window.location.protocol === 'chrome-devtools:') { + // Override some inspector APIs. + require('./inspector') + nodeIntegration = 'true' +} else if (window.location.protocol === 'chrome-extension:') { + // Add implementations of chrome API. + require('./chrome-api').injectTo(window.location.hostname, isBackgroundPage, window) + nodeIntegration = 'false' +} else { + // Override default web functions. + require('./override') + + // Inject content scripts. + require('./content-scripts-injector') + + // Load webview tag implementation. + if (nodeIntegration === 'true' && process.guestInstanceId == null) { + require('./web-view/web-view') + require('./web-view/web-view-attributes') + } +} + +if (nodeIntegration === 'true') { + // Export node bindings to global. + global.require = require + global.module = module + + // Set the __filename to the path of html file if it is file: protocol. + if (window.location.protocol === 'file:') { + var pathname = process.platform === 'win32' && window.location.pathname[0] === '/' ? window.location.pathname.substr(1) : window.location.pathname + global.__filename = path.normalize(decodeURIComponent(pathname)) + global.__dirname = path.dirname(global.__filename) + + // Set module's filename so relative require can work as expected. + module.filename = global.__filename + + // Also search for module under the html file. + module.paths = module.paths.concat(Module._nodeModulePaths(global.__dirname)) + } else { + global.__filename = __filename + global.__dirname = __dirname + } + + // Redirect window.onerror to uncaughtException. + window.onerror = function (message, filename, lineno, colno, error) { + if (global.process.listeners('uncaughtException').length > 0) { + global.process.emit('uncaughtException', error) + return true + } else { + return false + } + } +} else { + // Delete Node's symbols after the Environment has been loaded. + process.once('loaded', function () { + delete global.process + delete global.setImmediate + delete global.clearImmediate + return delete global.global + }) +} + +// Load the script specfied by the "preload" attribute. +if (preloadScript) { + try { + require(preloadScript) + } catch (error) { + console.error('Unable to load preload script: ' + preloadScript) + console.error(error.stack || error.message) + } +} diff --git a/lib/renderer/inspector.js b/lib/renderer/inspector.js new file mode 100644 index 000000000000..ca60c84d4be9 --- /dev/null +++ b/lib/renderer/inspector.js @@ -0,0 +1,81 @@ +window.onload = function () { + // Use menu API to show context menu. + window.InspectorFrontendHost.showContextMenuAtPoint = createMenu + + // Use dialog API to override file chooser dialog. + window.WebInspector.createFileSelectorElement = createFileSelectorElement +} + +var convertToMenuTemplate = function (items) { + var fn, i, item, len, template + template = [] + fn = function (item) { + var transformed + transformed = item.type === 'subMenu' ? { + type: 'submenu', + label: item.label, + enabled: item.enabled, + submenu: convertToMenuTemplate(item.subItems) + } : item.type === 'separator' ? { + type: 'separator' + } : item.type === 'checkbox' ? { + type: 'checkbox', + label: item.label, + enabled: item.enabled, + checked: item.checked + } : { + type: 'normal', + label: item.label, + enabled: item.enabled + } + if (item.id != null) { + transformed.click = function () { + window.DevToolsAPI.contextMenuItemSelected(item.id) + return window.DevToolsAPI.contextMenuCleared() + } + } + return template.push(transformed) + } + for (i = 0, len = items.length; i < len; i++) { + item = items[i] + fn(item) + } + return template +} + +var createMenu = function (x, y, items) { + const remote = require('electron').remote + const Menu = remote.Menu + const menu = Menu.buildFromTemplate(convertToMenuTemplate(items)) + + // The menu is expected to show asynchronously. + return setTimeout(function () { + return menu.popup(remote.getCurrentWindow()) + }) +} + +var showFileChooserDialog = function (callback) { + var dialog, files, remote + remote = require('electron').remote + dialog = remote.dialog + files = dialog.showOpenDialog({}) + if (files != null) { + return callback(pathToHtml5FileObject(files[0])) + } +} + +var pathToHtml5FileObject = function (path) { + var blob, fs + fs = require('fs') + blob = new Blob([fs.readFileSync(path)]) + blob.name = path + return blob +} + +var createFileSelectorElement = function (callback) { + var fileSelectorElement + fileSelectorElement = document.createElement('span') + fileSelectorElement.style.display = 'none' + fileSelectorElement.click = showFileChooserDialog.bind(this, callback) + return fileSelectorElement +} diff --git a/lib/renderer/override.js b/lib/renderer/override.js new file mode 100644 index 000000000000..5667e5e16f55 --- /dev/null +++ b/lib/renderer/override.js @@ -0,0 +1,253 @@ +'use strict' + +const ipcRenderer = require('electron').ipcRenderer +const remote = require('electron').remote + +// Helper function to resolve relative url. +var a = window.top.document.createElement('a') +var resolveURL = function (url) { + a.href = url + return a.href +} + +// Window object returned by "window.open". +var BrowserWindowProxy = (function () { + BrowserWindowProxy.proxies = {} + + BrowserWindowProxy.getOrCreate = function (guestId) { + var base = this.proxies + base[guestId] != null ? base[guestId] : base[guestId] = new BrowserWindowProxy(guestId) + return base[guestId] + } + + BrowserWindowProxy.remove = function (guestId) { + return delete this.proxies[guestId] + } + + function BrowserWindowProxy (guestId1) { + this.guestId = guestId1 + this.closed = false + ipcRenderer.once('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + this.guestId, () => { + BrowserWindowProxy.remove(this.guestId) + this.closed = true + }) + } + + BrowserWindowProxy.prototype.close = function () { + return ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', this.guestId) + } + + BrowserWindowProxy.prototype.focus = function () { + return ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'focus') + } + + BrowserWindowProxy.prototype.blur = function () { + return ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'blur') + } + + BrowserWindowProxy.prototype.print = function () { + return ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, 'print') + } + + Object.defineProperty(BrowserWindowProxy.prototype, 'location', { + get: function () { + return ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'getURL') + }, + set: function (url) { + return ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'loadURL', url) + } + }) + + BrowserWindowProxy.prototype.postMessage = function (message, targetOrigin) { + if (targetOrigin == null) { + targetOrigin = '*' + } + return ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', this.guestId, message, targetOrigin, window.location.origin) + } + + BrowserWindowProxy.prototype['eval'] = function (...args) { + return ipcRenderer.send.apply(ipcRenderer, ['ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, 'executeJavaScript'].concat(args)) + } + + return BrowserWindowProxy +})() + +if (process.guestInstanceId == null) { + // Override default window.close. + window.close = function () { + return remote.getCurrentWindow().close() + } +} + +// Make the browser window or guest view emit "new-window" event. +window.open = function (url, frameName, features) { + var feature, guestId, i, j, len, len1, name, options, ref1, ref2, value + if (frameName == null) { + frameName = '' + } + if (features == null) { + features = '' + } + options = {} + + const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor'] + const webPreferences = ['zoomFactor', 'nodeIntegration', 'preload'] + const disposition = 'new-window' + + // Make sure to get rid of excessive whitespace in the property name + ref1 = features.split(/,\s*/) + for (i = 0, len = ref1.length; i < len; i++) { + feature = ref1[i] + ref2 = feature.split(/\s*=/) + name = ref2[0] + value = ref2[1] + value = value === 'yes' || value === '1' ? true : value === 'no' || value === '0' ? false : value + if (webPreferences.includes(name)) { + if (options.webPreferences == null) { + options.webPreferences = {} + } + options.webPreferences[name] = value + } else { + options[name] = value + } + } + if (options.left) { + if (options.x == null) { + options.x = options.left + } + } + if (options.top) { + if (options.y == null) { + options.y = options.top + } + } + if (options.title == null) { + options.title = frameName + } + if (options.width == null) { + options.width = 800 + } + if (options.height == null) { + options.height = 600 + } + + // Resolve relative urls. + url = resolveURL(url) + for (j = 0, len1 = ints.length; j < len1; j++) { + name = ints[j] + if (options[name] != null) { + options[name] = parseInt(options[name], 10) + } + } + guestId = ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, disposition, options) + if (guestId) { + return BrowserWindowProxy.getOrCreate(guestId) + } else { + return null + } +} + +// Use the dialog API to implement alert(). +window.alert = function (message, title) { + var buttons + if (arguments.length === 0) { + message = '' + } + if (title == null) { + title = '' + } + buttons = ['OK'] + message = String(message) + remote.dialog.showMessageBox(remote.getCurrentWindow(), { + message: message, + title: title, + buttons: buttons + }) +} + +// And the confirm(). +window.confirm = function (message, title) { + var buttons, cancelId + if (title == null) { + title = '' + } + buttons = ['OK', 'Cancel'] + cancelId = 1 + return !remote.dialog.showMessageBox(remote.getCurrentWindow(), { + message: message, + title: title, + buttons: buttons, + cancelId: cancelId + }) +} + +// But we do not support prompt(). +window.prompt = function () { + throw new Error('prompt() is and will not be supported.') +} + +if (process.openerId != null) { + window.opener = BrowserWindowProxy.getOrCreate(process.openerId) +} + +ipcRenderer.on('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function (event, sourceId, message, sourceOrigin) { + // Manually dispatch event instead of using postMessage because we also need to + // set event.source. + event = document.createEvent('Event') + event.initEvent('message', false, false) + event.data = message + event.origin = sourceOrigin + event.source = BrowserWindowProxy.getOrCreate(sourceId) + window.dispatchEvent(event) +}) + +// Forward history operations to browser. +var sendHistoryOperation = function (...args) { + return ipcRenderer.send.apply(ipcRenderer, ['ELECTRON_NAVIGATION_CONTROLLER'].concat(args)) +} + +var getHistoryOperation = function (...args) { + return ipcRenderer.sendSync.apply(ipcRenderer, ['ELECTRON_SYNC_NAVIGATION_CONTROLLER'].concat(args)) +} + +window.history.back = function () { + return sendHistoryOperation('goBack') +} + +window.history.forward = function () { + return sendHistoryOperation('goForward') +} + +window.history.go = function (offset) { + return sendHistoryOperation('goToOffset', offset) +} + +Object.defineProperty(window.history, 'length', { + get: function () { + return getHistoryOperation('length') + } +}) + +// The initial visibilityState. +let cachedVisibilityState = process.argv.includes('--hidden-page') ? 'hidden' : 'visible' + +// Subscribe to visibilityState changes. +ipcRenderer.on('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', function (event, visibilityState) { + if (cachedVisibilityState !== visibilityState) { + cachedVisibilityState = visibilityState + document.dispatchEvent(new Event('visibilitychange')) + } +}) + +// Make document.hidden and document.visibilityState return the correct value. +Object.defineProperty(document, 'hidden', { + get: function () { + return cachedVisibilityState !== 'visible' + } +}) + +Object.defineProperty(document, 'visibilityState', { + get: function () { + return cachedVisibilityState + } +}) diff --git a/lib/renderer/web-view/guest-view-internal.js b/lib/renderer/web-view/guest-view-internal.js new file mode 100644 index 000000000000..7432444c6da6 --- /dev/null +++ b/lib/renderer/web-view/guest-view-internal.js @@ -0,0 +1,106 @@ +'use strict' + +const ipcRenderer = require('electron').ipcRenderer +const webFrame = require('electron').webFrame + +var requestId = 0 + +var WEB_VIEW_EVENTS = { + 'load-commit': ['url', 'isMainFrame'], + 'did-finish-load': [], + 'did-fail-load': ['errorCode', 'errorDescription', 'validatedURL', 'isMainFrame'], + 'did-frame-finish-load': ['isMainFrame'], + 'did-start-loading': [], + 'did-stop-loading': [], + 'did-get-response-details': ['status', 'newURL', 'originalURL', 'httpResponseCode', 'requestMethod', 'referrer', 'headers', 'resourceType'], + 'did-get-redirect-request': ['oldURL', 'newURL', 'isMainFrame'], + 'dom-ready': [], + 'console-message': ['level', 'message', 'line', 'sourceId'], + 'devtools-opened': [], + 'devtools-closed': [], + 'devtools-focused': [], + 'new-window': ['url', 'frameName', 'disposition', 'options'], + 'will-navigate': ['url'], + 'did-navigate': ['url'], + 'did-navigate-in-page': ['url'], + 'close': [], + 'crashed': [], + 'gpu-crashed': [], + 'plugin-crashed': ['name', 'version'], + 'media-started-playing': [], + 'media-paused': [], + 'did-change-theme-color': ['themeColor'], + 'destroyed': [], + 'page-title-updated': ['title', 'explicitSet'], + 'page-favicon-updated': ['favicons'], + 'enter-html-full-screen': [], + 'leave-html-full-screen': [], + 'found-in-page': ['result'] +} + +var DEPRECATED_EVENTS = { + 'page-title-updated': 'page-title-set' +} + +var dispatchEvent = function (webView, eventName, eventKey, ...args) { + var domEvent, f, i, j, len, ref1 + if (DEPRECATED_EVENTS[eventName] != null) { + dispatchEvent.apply(null, [webView, DEPRECATED_EVENTS[eventName], eventKey].concat(args)) + } + domEvent = new Event(eventName) + ref1 = WEB_VIEW_EVENTS[eventKey] + for (i = j = 0, len = ref1.length; j < len; i = ++j) { + f = ref1[i] + domEvent[f] = args[i] + } + webView.dispatchEvent(domEvent) + if (eventName === 'load-commit') { + return webView.onLoadCommit(domEvent) + } +} + +module.exports = { + registerEvents: function (webView, viewInstanceId) { + ipcRenderer.on('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-' + viewInstanceId, function (event, eventName, ...args) { + dispatchEvent.apply(null, [webView, eventName, eventName].concat(args)) + }) + + ipcRenderer.on('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-' + viewInstanceId, function (event, channel, ...args) { + var domEvent = new Event('ipc-message') + domEvent.channel = channel + domEvent.args = args + webView.dispatchEvent(domEvent) + }) + + return ipcRenderer.on('ELECTRON_GUEST_VIEW_INTERNAL_SIZE_CHANGED-' + viewInstanceId, function (event, ...args) { + var domEvent, f, i, j, len, ref1 + domEvent = new Event('size-changed') + ref1 = ['oldWidth', 'oldHeight', 'newWidth', 'newHeight'] + for (i = j = 0, len = ref1.length; j < len; i = ++j) { + f = ref1[i] + domEvent[f] = args[i] + } + webView.onSizeChanged(domEvent) + }) + }, + deregisterEvents: function (viewInstanceId) { + ipcRenderer.removeAllListeners('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-' + viewInstanceId) + ipcRenderer.removeAllListeners('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-' + viewInstanceId) + return ipcRenderer.removeAllListeners('ELECTRON_GUEST_VIEW_INTERNAL_SIZE_CHANGED-' + viewInstanceId) + }, + createGuest: function (params, callback) { + requestId++ + ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', params, requestId) + return ipcRenderer.once('ELECTRON_RESPONSE_' + requestId, callback) + }, + attachGuest: function (elementInstanceId, guestInstanceId, params) { + ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', elementInstanceId, guestInstanceId, params) + return webFrame.attachGuest(elementInstanceId) + }, + destroyGuest: function (guestInstanceId) { + return ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', guestInstanceId) + }, + setSize: function (guestInstanceId, params) { + return ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_SET_SIZE', guestInstanceId, params) + } +} diff --git a/atom/renderer/lib/web-view/web-view-attributes.js b/lib/renderer/web-view/web-view-attributes.js similarity index 54% rename from atom/renderer/lib/web-view/web-view-attributes.js rename to lib/renderer/web-view/web-view-attributes.js index 4ad4bd725012..c76e0d26f953 100644 --- a/atom/renderer/lib/web-view/web-view-attributes.js +++ b/lib/renderer/web-view/web-view-attributes.js @@ -1,113 +1,87 @@ -'use strict'; +'use strict' -const WebViewImpl = require('./web-view'); -const guestViewInternal = require('./guest-view-internal'); -const webViewConstants = require('./web-view-constants'); -const remote = require('electron').remote; +const WebViewImpl = require('./web-view') +const guestViewInternal = require('./guest-view-internal') +const webViewConstants = require('./web-view-constants') +const remote = require('electron').remote // Helper function to resolve url set in attribute. -var a = document.createElement('a'); +var a = document.createElement('a') -var resolveURL = function(url) { - a.href = url; - return a.href; -}; +var resolveURL = function (url) { + a.href = url + return a.href +} // Attribute objects. // Default implementation of a WebView attribute. class WebViewAttribute { - constructor(name, webViewImpl) { - this.name = name; - this.value = webViewImpl.webviewNode[name] || ''; - this.webViewImpl = webViewImpl; - this.ignoreMutation = false; - this.defineProperty(); + constructor (name, webViewImpl) { + this.name = name + this.value = webViewImpl.webviewNode[name] || '' + this.webViewImpl = webViewImpl + this.ignoreMutation = false + this.defineProperty() } // Retrieves and returns the attribute's value. - getValue() { - return this.webViewImpl.webviewNode.getAttribute(this.name) || this.value; + getValue () { + return this.webViewImpl.webviewNode.getAttribute(this.name) || this.value } // Sets the attribute's value. - setValue(value) { - return this.webViewImpl.webviewNode.setAttribute(this.name, value || ''); + setValue (value) { + return this.webViewImpl.webviewNode.setAttribute(this.name, value || '') } // Changes the attribute's value without triggering its mutation handler. - setValueIgnoreMutation(value) { - this.ignoreMutation = true; - this.setValue(value); - return this.ignoreMutation = false; + setValueIgnoreMutation (value) { + this.ignoreMutation = true + this.setValue(value) + this.ignoreMutation = false } // Defines this attribute as a property on the webview node. - defineProperty() { + defineProperty () { return Object.defineProperty(this.webViewImpl.webviewNode, this.name, { - get: (function(_this) { - return function() { - return _this.getValue(); - }; - })(this), - set: (function(_this) { - return function(value) { - return _this.setValue(value); - }; - })(this), + get: () => { + return this.getValue() + }, + set: (value) => { + return this.setValue(value) + }, enumerable: true - }); + }) } // Called when the attribute's value changes. - handleMutation() {} + handleMutation () {} } // An attribute that is treated as a Boolean. class BooleanAttribute extends WebViewAttribute { - constructor(name, webViewImpl) { - super(name, webViewImpl); + getValue () { + return this.webViewImpl.webviewNode.hasAttribute(this.name) } - getValue() { - return this.webViewImpl.webviewNode.hasAttribute(this.name); - } - - setValue(value) { + setValue (value) { if (!value) { - return this.webViewImpl.webviewNode.removeAttribute(this.name); + return this.webViewImpl.webviewNode.removeAttribute(this.name) } else { - return this.webViewImpl.webviewNode.setAttribute(this.name, ''); + return this.webViewImpl.webviewNode.setAttribute(this.name, '') } } } -// Attribute that specifies whether transparency is allowed in the webview. -class AllowTransparencyAttribute extends BooleanAttribute { - constructor(webViewImpl) { - super(webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY, webViewImpl); - } - - handleMutation() { - if (!this.webViewImpl.guestInstanceId) { - return; - } - return guestViewInternal.setAllowTransparency(this.webViewImpl.guestInstanceId, this.getValue()); - } -} - // Attribute used to define the demension limits of autosizing. class AutosizeDimensionAttribute extends WebViewAttribute { - constructor(name, webViewImpl) { - super(name, webViewImpl); + getValue () { + return parseInt(this.webViewImpl.webviewNode.getAttribute(this.name)) || 0 } - getValue() { - return parseInt(this.webViewImpl.webviewNode.getAttribute(this.name)) || 0; - } - - handleMutation() { + handleMutation () { if (!this.webViewImpl.guestInstanceId) { - return; + return } return guestViewInternal.setSize(this.webViewImpl.guestInstanceId, { enableAutoSize: this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue(), @@ -119,203 +93,194 @@ class AutosizeDimensionAttribute extends WebViewAttribute { width: parseInt(this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() || 0), height: parseInt(this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() || 0) } - }); + }) } } - // Attribute that specifies whether the webview should be autosized. class AutosizeAttribute extends BooleanAttribute { - constructor(webViewImpl) { - super(webViewConstants.ATTRIBUTE_AUTOSIZE, webViewImpl); + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_AUTOSIZE, webViewImpl) } } -AutosizeAttribute.prototype.handleMutation = AutosizeDimensionAttribute.prototype.handleMutation; +AutosizeAttribute.prototype.handleMutation = AutosizeDimensionAttribute.prototype.handleMutation // Attribute representing the state of the storage partition. class PartitionAttribute extends WebViewAttribute { - constructor(webViewImpl) { - super(webViewConstants.ATTRIBUTE_PARTITION, webViewImpl); - this.validPartitionId = true; + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_PARTITION, webViewImpl) + this.validPartitionId = true } - handleMutation(oldValue, newValue) { - newValue = newValue || ''; + handleMutation (oldValue, newValue) { + newValue = newValue || '' // The partition cannot change if the webview has already navigated. if (!this.webViewImpl.beforeFirstNavigation) { - window.console.error(webViewConstants.ERROR_MSG_ALREADY_NAVIGATED); - this.setValueIgnoreMutation(oldValue); - return; + window.console.error(webViewConstants.ERROR_MSG_ALREADY_NAVIGATED) + this.setValueIgnoreMutation(oldValue) + return } if (newValue === 'persist:') { - this.validPartitionId = false; - return window.console.error(webViewConstants.ERROR_MSG_INVALID_PARTITION_ATTRIBUTE); + this.validPartitionId = false + return window.console.error(webViewConstants.ERROR_MSG_INVALID_PARTITION_ATTRIBUTE) } } } // Attribute that handles the location and navigation of the webview. class SrcAttribute extends WebViewAttribute { - constructor(webViewImpl) { - super(webViewConstants.ATTRIBUTE_SRC, webViewImpl); - this.setupMutationObserver(); + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_SRC, webViewImpl) + this.setupMutationObserver() } - getValue() { + getValue () { if (this.webViewImpl.webviewNode.hasAttribute(this.name)) { - return resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name)); + return resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name)) } else { - return this.value; + return this.value } } - setValueIgnoreMutation(value) { - super.setValueIgnoreMutation(value); + setValueIgnoreMutation (value) { + super.setValueIgnoreMutation(value) // takeRecords() is needed to clear queued up src mutations. Without it, it // is possible for this change to get picked up asyncronously by src's // mutation observer |observer|, and then get handled even though we do not // want to handle this mutation. - return this.observer.takeRecords(); + return this.observer.takeRecords() } - handleMutation(oldValue, newValue) { - + handleMutation (oldValue, newValue) { // Once we have navigated, we don't allow clearing the src attribute. // Once enters a navigated state, it cannot return to a // placeholder state. if (!newValue && oldValue) { - // src attribute changes normally initiate a navigation. We suppress // the next src attribute handler call to avoid reloading the page // on every guest-initiated navigation. - this.setValueIgnoreMutation(oldValue); - return; + this.setValueIgnoreMutation(oldValue) + return } - return this.parse(); + return this.parse() } // The purpose of this mutation observer is to catch assignment to the src // attribute without any changes to its value. This is useful in the case // where the webview guest has crashed and navigating to the same address // spawns off a new process. - setupMutationObserver() { - var params; - this.observer = new MutationObserver((function(_this) { - return function(mutations) { - var i, len, mutation, newValue, oldValue; - for (i = 0, len = mutations.length; i < len; i++) { - mutation = mutations[i]; - oldValue = mutation.oldValue; - newValue = _this.getValue(); - if (oldValue !== newValue) { - return; - } - _this.handleMutation(oldValue, newValue); + setupMutationObserver () { + var params + this.observer = new MutationObserver((mutations) => { + var i, len, mutation, newValue, oldValue + for (i = 0, len = mutations.length; i < len; i++) { + mutation = mutations[i] + oldValue = mutation.oldValue + newValue = this.getValue() + if (oldValue !== newValue) { + return } - }; - })(this)); + this.handleMutation(oldValue, newValue) + } + }) params = { attributes: true, attributeOldValue: true, attributeFilter: [this.name] - }; - return this.observer.observe(this.webViewImpl.webviewNode, params); + } + return this.observer.observe(this.webViewImpl.webviewNode, params) } - parse() { - var guestContents, httpreferrer, opts, useragent; + parse () { + var guestContents, httpreferrer, opts, useragent if (!this.webViewImpl.elementAttached || !this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId || !this.getValue()) { - return; + return } if (this.webViewImpl.guestInstanceId == null) { if (this.webViewImpl.beforeFirstNavigation) { - this.webViewImpl.beforeFirstNavigation = false; - this.webViewImpl.createGuest(); + this.webViewImpl.beforeFirstNavigation = false + this.webViewImpl.createGuest() } - return; + return } // Navigate to |this.src|. - opts = {}; - httpreferrer = this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER].getValue(); + opts = {} + httpreferrer = this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER].getValue() if (httpreferrer) { - opts.httpReferrer = httpreferrer; + opts.httpReferrer = httpreferrer } - useragent = this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_USERAGENT].getValue(); + useragent = this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_USERAGENT].getValue() if (useragent) { - opts.userAgent = useragent; + opts.userAgent = useragent } - guestContents = remote.getGuestWebContents(this.webViewImpl.guestInstanceId); - return guestContents.loadURL(this.getValue(), opts); + guestContents = remote.getGuestWebContents(this.webViewImpl.guestInstanceId) + return guestContents.loadURL(this.getValue(), opts) } } // Attribute specifies HTTP referrer. class HttpReferrerAttribute extends WebViewAttribute { - constructor(webViewImpl) { - super(webViewConstants.ATTRIBUTE_HTTPREFERRER, webViewImpl); + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_HTTPREFERRER, webViewImpl) } } // Attribute specifies user agent class UserAgentAttribute extends WebViewAttribute { - constructor(webViewImpl) { - super(webViewConstants.ATTRIBUTE_USERAGENT, webViewImpl); + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_USERAGENT, webViewImpl) } } // Attribute that set preload script. class PreloadAttribute extends WebViewAttribute { - constructor(webViewImpl) { - super(webViewConstants.ATTRIBUTE_PRELOAD, webViewImpl); + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_PRELOAD, webViewImpl) } - getValue() { - var preload, protocol; + getValue () { + var preload, protocol if (!this.webViewImpl.webviewNode.hasAttribute(this.name)) { - return this.value; + return this.value } - preload = resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name)); - protocol = preload.substr(0, 5); + preload = resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name)) + protocol = preload.substr(0, 5) if (protocol !== 'file:') { - console.error(webViewConstants.ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE); - preload = ''; + console.error(webViewConstants.ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE) + preload = '' } - return preload; + return preload } } // Attribute that specifies the blink features to be enabled. class BlinkFeaturesAttribute extends WebViewAttribute { - constructor(webViewImpl) { - super(webViewConstants.ATTRIBUTE_BLINKFEATURES, webViewImpl); + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_BLINKFEATURES, webViewImpl) } } // Sets up all of the webview attributes. -WebViewImpl.prototype.setupWebViewAttributes = function() { - var attribute, autosizeAttributes, i, len, results; - this.attributes = {}; - this.attributes[webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY] = new AllowTransparencyAttribute(this); - this.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE] = new AutosizeAttribute(this); - this.attributes[webViewConstants.ATTRIBUTE_PARTITION] = new PartitionAttribute(this); - this.attributes[webViewConstants.ATTRIBUTE_SRC] = new SrcAttribute(this); - this.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER] = new HttpReferrerAttribute(this); - this.attributes[webViewConstants.ATTRIBUTE_USERAGENT] = new UserAgentAttribute(this); - this.attributes[webViewConstants.ATTRIBUTE_NODEINTEGRATION] = new BooleanAttribute(webViewConstants.ATTRIBUTE_NODEINTEGRATION, this); - this.attributes[webViewConstants.ATTRIBUTE_PLUGINS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_PLUGINS, this); - this.attributes[webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY, this); - this.attributes[webViewConstants.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_ALLOWPOPUPS, this); - this.attributes[webViewConstants.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this); - this.attributes[webViewConstants.ATTRIBUTE_BLINKFEATURES] = new BlinkFeaturesAttribute(this); - autosizeAttributes = [webViewConstants.ATTRIBUTE_MAXHEIGHT, webViewConstants.ATTRIBUTE_MAXWIDTH, webViewConstants.ATTRIBUTE_MINHEIGHT, webViewConstants.ATTRIBUTE_MINWIDTH]; - results = []; - for (i = 0, len = autosizeAttributes.length; i < len; i++) { - attribute = autosizeAttributes[i]; - results.push(this.attributes[attribute] = new AutosizeDimensionAttribute(attribute, this)); - } - return results; -}; +WebViewImpl.prototype.setupWebViewAttributes = function () { + this.attributes = {} + this.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE] = new AutosizeAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_PARTITION] = new PartitionAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_SRC] = new SrcAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER] = new HttpReferrerAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_USERAGENT] = new UserAgentAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_NODEINTEGRATION] = new BooleanAttribute(webViewConstants.ATTRIBUTE_NODEINTEGRATION, this) + this.attributes[webViewConstants.ATTRIBUTE_PLUGINS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_PLUGINS, this) + this.attributes[webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY, this) + this.attributes[webViewConstants.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_ALLOWPOPUPS, this) + this.attributes[webViewConstants.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_BLINKFEATURES] = new BlinkFeaturesAttribute(this) + + const autosizeAttributes = [webViewConstants.ATTRIBUTE_MAXHEIGHT, webViewConstants.ATTRIBUTE_MAXWIDTH, webViewConstants.ATTRIBUTE_MINHEIGHT, webViewConstants.ATTRIBUTE_MINWIDTH] + autosizeAttributes.forEach((attribute) => { + this.attributes[attribute] = new AutosizeDimensionAttribute(attribute, this) + }) +} diff --git a/atom/renderer/lib/web-view/web-view-constants.js b/lib/renderer/web-view/web-view-constants.js similarity index 95% rename from atom/renderer/lib/web-view/web-view-constants.js rename to lib/renderer/web-view/web-view-constants.js index de2a571f5d5f..5501f4f3599b 100644 --- a/atom/renderer/lib/web-view/web-view-constants.js +++ b/lib/renderer/web-view/web-view-constants.js @@ -1,6 +1,5 @@ module.exports = { // Attributes. - ATTRIBUTE_ALLOWTRANSPARENCY: 'allowtransparency', ATTRIBUTE_AUTOSIZE: 'autosize', ATTRIBUTE_MAXHEIGHT: 'maxheight', ATTRIBUTE_MAXWIDTH: 'maxwidth', @@ -26,4 +25,4 @@ module.exports = { ERROR_MSG_CANNOT_INJECT_SCRIPT: ': ' + 'Script cannot be injected into content until the page has loaded.', ERROR_MSG_INVALID_PARTITION_ATTRIBUTE: 'Invalid partition attribute.', ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE: 'Only "file:" protocol is supported in "preload" attribute.' -}; +} diff --git a/lib/renderer/web-view/web-view.js b/lib/renderer/web-view/web-view.js new file mode 100644 index 000000000000..e9d300aabbcb --- /dev/null +++ b/lib/renderer/web-view/web-view.js @@ -0,0 +1,464 @@ +'use strict' + +const webFrame = require('electron').webFrame +const remote = require('electron').remote +const ipcRenderer = require('electron').ipcRenderer + +const v8Util = process.atomBinding('v8_util') +const guestViewInternal = require('./guest-view-internal') +const webViewConstants = require('./web-view-constants') + +var hasProp = {}.hasOwnProperty + +// ID generator. +var nextId = 0 + +var getNextId = function () { + return ++nextId +} + +// Represents the internal state of the WebView node. +var WebViewImpl = (function () { + function WebViewImpl (webviewNode) { + var shadowRoot + this.webviewNode = webviewNode + v8Util.setHiddenValue(this.webviewNode, 'internal', this) + this.attached = false + this.elementAttached = false + this.beforeFirstNavigation = true + + // on* Event handlers. + this.on = {} + this.browserPluginNode = this.createBrowserPluginNode() + shadowRoot = this.webviewNode.createShadowRoot() + shadowRoot.innerHTML = '' + this.setupWebViewAttributes() + this.setupFocusPropagation() + this.viewInstanceId = getNextId() + shadowRoot.appendChild(this.browserPluginNode) + + // Subscribe to host's zoom level changes. + this.onZoomLevelChanged = (zoomLevel) => { + this.webviewNode.setZoomLevel(zoomLevel) + } + webFrame.on('zoom-level-changed', this.onZoomLevelChanged) + + this.onVisibilityChanged = (event, visibilityState) => { + this.webviewNode.send('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', visibilityState) + } + ipcRenderer.on('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', this.onVisibilityChanged) + } + + WebViewImpl.prototype.createBrowserPluginNode = function () { + // We create BrowserPlugin as a custom element in order to observe changes + // to attributes synchronously. + var browserPluginNode = new WebViewImpl.BrowserPlugin() + v8Util.setHiddenValue(browserPluginNode, 'internal', this) + return browserPluginNode + } + + // Resets some state upon reattaching element to the DOM. + WebViewImpl.prototype.reset = function () { + // Unlisten the zoom-level-changed event. + webFrame.removeListener('zoom-level-changed', this.onZoomLevelChanged) + ipcRenderer.removeListener('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', this.onVisibilityChanged) + + // If guestInstanceId is defined then the has navigated and has + // already picked up a partition ID. Thus, we need to reset the initialization + // state. However, it may be the case that beforeFirstNavigation is false BUT + // guestInstanceId has yet to be initialized. This means that we have not + // heard back from createGuest yet. We will not reset the flag in this case so + // that we don't end up allocating a second guest. + if (this.guestInstanceId) { + guestViewInternal.destroyGuest(this.guestInstanceId) + this.webContents = null + this.guestInstanceId = void 0 + this.beforeFirstNavigation = true + this.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId = true + } + this.internalInstanceId = 0 + } + + // Sets the .request property. + WebViewImpl.prototype.setRequestPropertyOnWebViewNode = function (request) { + return Object.defineProperty(this.webviewNode, 'request', { + value: request, + enumerable: true + }) + } + + WebViewImpl.prototype.setupFocusPropagation = function () { + if (!this.webviewNode.hasAttribute('tabIndex')) { + // needs a tabIndex in order to be focusable. + // TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute + // to allow to be focusable. + // See http://crbug.com/231664. + this.webviewNode.setAttribute('tabIndex', -1) + } + + // Focus the BrowserPlugin when the takes focus. + this.webviewNode.addEventListener('focus', () => { + this.browserPluginNode.focus() + }) + + // Blur the BrowserPlugin when the loses focus. + this.webviewNode.addEventListener('blur', () => { + this.browserPluginNode.blur() + }) + } + + // This observer monitors mutations to attributes of the and + // updates the BrowserPlugin properties accordingly. In turn, updating + // a BrowserPlugin property will update the corresponding BrowserPlugin + // attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more + // details. + WebViewImpl.prototype.handleWebviewAttributeMutation = function (attributeName, oldValue, newValue) { + if (!this.attributes[attributeName] || this.attributes[attributeName].ignoreMutation) { + return + } + + // Let the changed attribute handle its own mutation + return this.attributes[attributeName].handleMutation(oldValue, newValue) + } + + WebViewImpl.prototype.handleBrowserPluginAttributeMutation = function (attributeName, oldValue, newValue) { + if (attributeName === webViewConstants.ATTRIBUTE_INTERNALINSTANCEID && !oldValue && !!newValue) { + this.browserPluginNode.removeAttribute(webViewConstants.ATTRIBUTE_INTERNALINSTANCEID) + this.internalInstanceId = parseInt(newValue) + + // Track when the element resizes using the element resize callback. + webFrame.registerElementResizeCallback(this.internalInstanceId, this.onElementResize.bind(this)) + if (!this.guestInstanceId) { + return + } + return guestViewInternal.attachGuest(this.internalInstanceId, this.guestInstanceId, this.buildParams()) + } + } + + WebViewImpl.prototype.onSizeChanged = function (webViewEvent) { + var maxHeight, maxWidth, minHeight, minWidth, newHeight, newWidth, node, width + newWidth = webViewEvent.newWidth + newHeight = webViewEvent.newHeight + node = this.webviewNode + width = node.offsetWidth + + // Check the current bounds to make sure we do not resize + // outside of current constraints. + maxWidth = this.attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() | width + maxHeight = this.attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() | width + minWidth = this.attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() | width + minHeight = this.attributes[webViewConstants.ATTRIBUTE_MINHEIGHT].getValue() | width + minWidth = Math.min(minWidth, maxWidth) + minHeight = Math.min(minHeight, maxHeight) + if (!this.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue() || (newWidth >= minWidth && newWidth <= maxWidth && newHeight >= minHeight && newHeight <= maxHeight)) { + node.style.width = newWidth + 'px' + node.style.height = newHeight + 'px' + + // Only fire the DOM event if the size of the has actually + // changed. + return this.dispatchEvent(webViewEvent) + } + } + + WebViewImpl.prototype.onElementResize = function (newSize) { + // Dispatch the 'resize' event. + var resizeEvent + resizeEvent = new Event('resize', { + bubbles: true + }) + resizeEvent.newWidth = newSize.width + resizeEvent.newHeight = newSize.height + this.dispatchEvent(resizeEvent) + if (this.guestInstanceId) { + return guestViewInternal.setSize(this.guestInstanceId, { + normal: newSize + }) + } + } + + WebViewImpl.prototype.createGuest = function () { + return guestViewInternal.createGuest(this.buildParams(), (event, guestInstanceId) => { + this.attachWindow(guestInstanceId) + }) + } + + WebViewImpl.prototype.dispatchEvent = function (webViewEvent) { + return this.webviewNode.dispatchEvent(webViewEvent) + } + + // Adds an 'on' property on the webview, which can be used to set/unset + // an event handler. + WebViewImpl.prototype.setupEventProperty = function (eventName) { + var propertyName + propertyName = 'on' + eventName.toLowerCase() + return Object.defineProperty(this.webviewNode, propertyName, { + get: () => { + return this.on[propertyName] + }, + set: (value) => { + if (this.on[propertyName]) { + this.webviewNode.removeEventListener(eventName, this.on[propertyName]) + } + this.on[propertyName] = value + if (value) { + return this.webviewNode.addEventListener(eventName, value) + } + }, + enumerable: true + }) + } + + // Updates state upon loadcommit. + WebViewImpl.prototype.onLoadCommit = function (webViewEvent) { + var newValue, oldValue + oldValue = this.webviewNode.getAttribute(webViewConstants.ATTRIBUTE_SRC) + newValue = webViewEvent.url + if (webViewEvent.isMainFrame && (oldValue !== newValue)) { + // Touching the src attribute triggers a navigation. To avoid + // triggering a page reload on every guest-initiated navigation, + // we do not handle this mutation. + return this.attributes[webViewConstants.ATTRIBUTE_SRC].setValueIgnoreMutation(newValue) + } + } + + WebViewImpl.prototype.onAttach = function (storagePartitionId) { + return this.attributes[webViewConstants.ATTRIBUTE_PARTITION].setValue(storagePartitionId) + } + + WebViewImpl.prototype.buildParams = function () { + var attribute, attributeName, css, elementRect, params, ref1 + params = { + instanceId: this.viewInstanceId, + userAgentOverride: this.userAgentOverride, + zoomFactor: webFrame.getZoomFactor() + } + ref1 = this.attributes + for (attributeName in ref1) { + if (!hasProp.call(ref1, attributeName)) continue + attribute = ref1[attributeName] + params[attributeName] = attribute.getValue() + } + + // When the WebView is not participating in layout (display:none) + // then getBoundingClientRect() would report a width and height of 0. + // However, in the case where the WebView has a fixed size we can + // use that value to initially size the guest so as to avoid a relayout of + // the on display:block. + css = window.getComputedStyle(this.webviewNode, null) + elementRect = this.webviewNode.getBoundingClientRect() + params.elementWidth = parseInt(elementRect.width) || parseInt(css.getPropertyValue('width')) + params.elementHeight = parseInt(elementRect.height) || parseInt(css.getPropertyValue('height')) + return params + } + + WebViewImpl.prototype.attachWindow = function (guestInstanceId) { + this.guestInstanceId = guestInstanceId + this.webContents = remote.getGuestWebContents(this.guestInstanceId) + if (!this.internalInstanceId) { + return true + } + return guestViewInternal.attachGuest(this.internalInstanceId, this.guestInstanceId, this.buildParams()) + } + + return WebViewImpl +})() + +// Registers browser plugin custom element. +var registerBrowserPluginElement = function () { + var proto = Object.create(HTMLObjectElement.prototype) + proto.createdCallback = function () { + this.setAttribute('type', 'application/browser-plugin') + this.setAttribute('id', 'browser-plugin-' + getNextId()) + + // The node fills in the container. + this.style.flex = '1 1 auto' + } + proto.attributeChangedCallback = function (name, oldValue, newValue) { + var internal + internal = v8Util.getHiddenValue(this, 'internal') + if (!internal) { + return + } + return internal.handleBrowserPluginAttributeMutation(name, oldValue, newValue) + } + proto.attachedCallback = function () { + // Load the plugin immediately. + return this.nonExistentAttribute + } + WebViewImpl.BrowserPlugin = webFrame.registerEmbedderCustomElement('browserplugin', { + 'extends': 'object', + prototype: proto + }) + delete proto.createdCallback + delete proto.attachedCallback + delete proto.detachedCallback + return delete proto.attributeChangedCallback +} + +// Registers custom element. +var registerWebViewElement = function () { + var createBlockHandler, createNonBlockHandler, i, j, len, len1, m, methods, nonblockMethods, proto + proto = Object.create(HTMLObjectElement.prototype) + proto.createdCallback = function () { + return new WebViewImpl(this) + } + proto.attributeChangedCallback = function (name, oldValue, newValue) { + var internal + internal = v8Util.getHiddenValue(this, 'internal') + if (!internal) { + return + } + return internal.handleWebviewAttributeMutation(name, oldValue, newValue) + } + proto.detachedCallback = function () { + var internal + internal = v8Util.getHiddenValue(this, 'internal') + if (!internal) { + return + } + guestViewInternal.deregisterEvents(internal.viewInstanceId) + internal.elementAttached = false + return internal.reset() + } + proto.attachedCallback = function () { + var internal + internal = v8Util.getHiddenValue(this, 'internal') + if (!internal) { + return + } + if (!internal.elementAttached) { + guestViewInternal.registerEvents(internal, internal.viewInstanceId) + internal.elementAttached = true + return internal.attributes[webViewConstants.ATTRIBUTE_SRC].parse() + } + } + + // Public-facing API methods. + methods = [ + 'getURL', + 'loadURL', + 'getTitle', + 'isLoading', + 'isLoadingMainFrame', + 'isWaitingForResponse', + 'stop', + 'reload', + 'reloadIgnoringCache', + 'canGoBack', + 'canGoForward', + 'canGoToOffset', + 'clearHistory', + 'goBack', + 'goForward', + 'goToIndex', + 'goToOffset', + 'isCrashed', + 'setUserAgent', + 'getUserAgent', + 'openDevTools', + 'closeDevTools', + 'isDevToolsOpened', + 'isDevToolsFocused', + 'inspectElement', + 'setAudioMuted', + 'isAudioMuted', + 'undo', + 'redo', + 'cut', + 'copy', + 'paste', + 'pasteAndMatchStyle', + 'delete', + 'selectAll', + 'unselect', + 'replace', + 'replaceMisspelling', + 'findInPage', + 'stopFindInPage', + 'getId', + 'downloadURL', + 'inspectServiceWorker', + 'print', + 'printToPDF' + ] + nonblockMethods = [ + 'insertCSS', + 'insertText', + 'send', + 'sendInputEvent', + 'setZoomFactor', + 'setZoomLevel', + 'setZoomLevelLimits' + ] + + // Forward proto.foo* method calls to WebViewImpl.foo*. + createBlockHandler = function (m) { + return function (...args) { + const internal = v8Util.getHiddenValue(this, 'internal') + if (internal.webContents) { + return internal.webContents[m].apply(internal.webContents, args) + } else { + throw new Error(`Cannot call ${m} because the webContents is unavailable. The WebView must be attached to the DOM and the dom-ready event emitted before this method can be called.`) + } + } + } + for (i = 0, len = methods.length; i < len; i++) { + m = methods[i] + proto[m] = createBlockHandler(m) + } + createNonBlockHandler = function (m) { + return function (...args) { + const internal = v8Util.getHiddenValue(this, 'internal') + return ipcRenderer.send.apply(ipcRenderer, ['ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', null, internal.guestInstanceId, m].concat(args)) + } + } + for (j = 0, len1 = nonblockMethods.length; j < len1; j++) { + m = nonblockMethods[j] + proto[m] = createNonBlockHandler(m) + } + + proto.executeJavaScript = function (code, hasUserGesture, callback) { + var internal = v8Util.getHiddenValue(this, 'internal') + if (typeof hasUserGesture === 'function') { + callback = hasUserGesture + hasUserGesture = false + } + let requestId = getNextId() + ipcRenderer.send('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', requestId, internal.guestInstanceId, 'executeJavaScript', code, hasUserGesture) + ipcRenderer.once(`ELECTRON_RENDERER_ASYNC_CALL_TO_GUEST_VIEW_RESPONSE_${requestId}`, function (event, result) { + if (callback) callback(result) + }) + } + + // WebContents associated with this webview. + proto.getWebContents = function () { + var internal = v8Util.getHiddenValue(this, 'internal') + return internal.webContents + } + + window.WebView = webFrame.registerEmbedderCustomElement('webview', { + prototype: proto + }) + + // Delete the callbacks so developers cannot call them and produce unexpected + // behavior. + delete proto.createdCallback + delete proto.attachedCallback + delete proto.detachedCallback + return delete proto.attributeChangedCallback +} + +var useCapture = true + +var listener = function (event) { + if (document.readyState === 'loading') { + return + } + registerBrowserPluginElement() + registerWebViewElement() + return window.removeEventListener(event.type, listener, useCapture) +} + +window.addEventListener('readystatechange', listener, true) + +module.exports = WebViewImpl diff --git a/package.json b/package.json index e6d399fb3be4..6f873786e3f0 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,33 @@ { "name": "electron", - "version": "0.36.8", + "version": "1.2.1", "devDependencies": { - "asar": "^0.10.0", - "eslint": "^2.1.0", - "request": "*" + "asar": "^0.11.0", + "request": "*", + "standard": "^7.1.0" }, "optionalDependencies": { "runas": "^3.0.0" }, + "standard": { + "ignore": [ + "/out", + "/spec", + "/vendor" + ], + "env": { + "browser": true + } + }, "private": true, "scripts": { "bootstrap": "python ./script/bootstrap.py", "build": "python ./script/build.py -c D", - "lint": "python ./script/eslint.py && python ./script/cpplint.py", + "lint": "npm run lint-js && npm run lint-cpp", + "lint-js": "standard && standard spec", + "lint-cpp": "python ./script/cpplint.py", "preinstall": "node -e 'process.exit(0)'", + "repl": "python ./script/start.py --interactive", "start": "python ./script/start.py", "test": "python ./script/test.py" } diff --git a/script/bootstrap.py b/script/bootstrap.py index 6eaf635bfd86..6380ffe27c7a 100755 --- a/script/bootstrap.py +++ b/script/bootstrap.py @@ -7,7 +7,7 @@ import sys from lib.config import LIBCHROMIUMCONTENT_COMMIT, BASE_URL, PLATFORM, \ enable_verbose_mode, is_verbose_mode, get_target_arch -from lib.util import execute_stdout, get_atom_shell_version, scoped_cwd +from lib.util import execute_stdout, get_electron_version, scoped_cwd SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) @@ -26,6 +26,7 @@ def main(): os.chdir(SOURCE_ROOT) args = parse_args() + defines = args_to_defines(args) if not args.yes and PLATFORM != 'win32': check_root() if args.verbose: @@ -33,22 +34,38 @@ def main(): if sys.platform == 'cygwin': update_win32_python() - if PLATFORM != 'win32': - update_clang() - update_submodules() + + libcc_source_path = args.libcc_source_path + libcc_shared_library_path = args.libcc_shared_library_path + libcc_static_library_path = args.libcc_static_library_path + + # Redirect to use local libchromiumcontent build. + if args.build_libchromiumcontent: + build_libchromiumcontent(args.verbose, args.target_arch, defines) + dist_dir = os.path.join(SOURCE_ROOT, 'vendor', 'brightray', 'vendor', + 'libchromiumcontent', 'dist', 'main') + libcc_source_path = os.path.join(dist_dir, 'src') + libcc_shared_library_path = os.path.join(dist_dir, 'shared_library') + libcc_static_library_path = os.path.join(dist_dir, 'static_library') + + if PLATFORM != 'win32': + if not args.disable_clang and args.clang_dir == '': + # Download prebuilt clang binaries. + update_clang() + setup_python_libs() update_node_modules('.') bootstrap_brightray(args.dev, args.url, args.target_arch, - args.libcc_source_path, args.libcc_shared_library_path, - args.libcc_static_library_path) + libcc_source_path, libcc_shared_library_path, + libcc_static_library_path) - if args.target_arch in ['arm', 'ia32'] and PLATFORM == 'linux': + if PLATFORM == 'linux': download_sysroot(args.target_arch) create_chrome_version_h() touch_config_gypi() - run_update() + run_update(defines, args.disable_clang, args.clang_dir) update_electron_modules('spec', args.target_arch) @@ -71,6 +88,11 @@ def parse_args(): 'prompts.') parser.add_argument('--target_arch', default=get_target_arch(), help='Manually specify the arch to build for') + parser.add_argument('--clang_dir', default='', help='Path to clang binaries') + parser.add_argument('--disable_clang', action='store_true', + help='Use compilers other than clang for building') + parser.add_argument('--build_libchromiumcontent', action='store_true', + help='Build local version of libchromiumcontent') parser.add_argument('--libcc_source_path', required=False, help='The source path of libchromiumcontent. ' \ 'NOTE: All options of libchromiumcontent are ' \ @@ -82,6 +104,16 @@ def parse_args(): return parser.parse_args() +def args_to_defines(args): + defines = '' + if args.disable_clang: + defines += ' clang=0' + if args.clang_dir: + defines += ' make_clang_dir=' + args.clang_dir + defines += ' clang_use_chrome_plugins=0' + return defines + + def check_root(): if os.geteuid() == 0: print "We suggest not running this as root, unless you're really sure." @@ -91,7 +123,7 @@ def check_root(): def update_submodules(): - execute_stdout(['git', 'submodule', 'sync']) + execute_stdout(['git', 'submodule', 'sync', '--recursive']) execute_stdout(['git', 'submodule', 'update', '--init', '--recursive']) @@ -121,15 +153,19 @@ def bootstrap_brightray(is_dev, url, target_arch, libcc_source_path, execute_stdout([sys.executable, bootstrap] + args) +def set_clang_env(env): + llvm_dir = os.path.join(SOURCE_ROOT, 'vendor', 'llvm-build', + 'Release+Asserts', 'bin') + env['CC'] = os.path.join(llvm_dir, 'clang') + env['CXX'] = os.path.join(llvm_dir, 'clang++') + + def update_node_modules(dirname, env=None): if env is None: - env = os.environ + env = os.environ.copy() if PLATFORM == 'linux': # Use prebuilt clang for building native modules. - llvm_dir = os.path.join(SOURCE_ROOT, 'vendor', 'llvm-build', - 'Release+Asserts', 'bin') - env['CC'] = os.path.join(llvm_dir, 'clang') - env['CXX'] = os.path.join(llvm_dir, 'clang++') + set_clang_env(env) env['npm_config_clang'] = '1' with scoped_cwd(dirname): args = [NPM, 'install'] @@ -148,7 +184,7 @@ def update_node_modules(dirname, env=None): def update_electron_modules(dirname, target_arch): env = os.environ.copy() env['npm_config_arch'] = target_arch - env['npm_config_target'] = get_atom_shell_version() + env['npm_config_target'] = get_electron_version() env['npm_config_disturl'] = 'https://atom.io/download/atom-shell' update_node_modules(dirname, env) @@ -159,6 +195,15 @@ def update_win32_python(): execute_stdout(['git', 'clone', PYTHON_26_URL]) +def build_libchromiumcontent(verbose, target_arch, defines): + args = [os.path.join(SOURCE_ROOT, 'script', 'build-libchromiumcontent.py')] + if verbose: + args += ['-v'] + if defines: + args += ['--defines', defines] + execute_stdout(args + ['--target_arch', target_arch]) + + def update_clang(): execute_stdout([os.path.join(SOURCE_ROOT, 'script', 'update-clang.sh')]) @@ -166,10 +211,12 @@ def update_clang(): def download_sysroot(target_arch): if target_arch == 'ia32': target_arch = 'i386' - execute_stdout([os.path.join(SOURCE_ROOT, 'script', 'install-sysroot.py'), + if target_arch == 'x64': + target_arch = 'amd64' + execute_stdout([sys.executable, + os.path.join(SOURCE_ROOT, 'script', 'install-sysroot.py'), '--arch', target_arch]) - def create_chrome_version_h(): version_file = os.path.join(SOURCE_ROOT, 'vendor', 'brightray', 'vendor', 'libchromiumcontent', 'VERSION') @@ -201,9 +248,14 @@ def touch_config_gypi(): f.write(content) -def run_update(): +def run_update(defines, disable_clang, clang_dir): + env = os.environ.copy() + if not disable_clang and clang_dir == '': + # Build with prebuilt clang. + set_clang_env(env) + update = os.path.join(SOURCE_ROOT, 'script', 'update.py') - execute_stdout([sys.executable, update]) + execute_stdout([sys.executable, update, '--defines', defines], env) if __name__ == '__main__': diff --git a/script/build-libchromiumcontent.py b/script/build-libchromiumcontent.py new file mode 100755 index 000000000000..e0a95f69a465 --- /dev/null +++ b/script/build-libchromiumcontent.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +import argparse +import os +import sys + +from lib.config import enable_verbose_mode, get_target_arch +from lib.util import execute_stdout + + +SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) + + +def main(): + os.chdir(SOURCE_ROOT) + + args = parse_args() + if args.verbose: + enable_verbose_mode() + + # ./script/bootstrap + # ./script/update -t x64 --defines='' + # ./script/build --no_shared_library -t x64 + # ./script/create-dist -c static_library -t x64 --no_zip + script_dir = os.path.join(SOURCE_ROOT, 'vendor', 'brightray', 'vendor', + 'libchromiumcontent', 'script') + bootstrap = os.path.join(script_dir, 'bootstrap') + update = os.path.join(script_dir, 'update') + build = os.path.join(script_dir, 'build') + create_dist = os.path.join(script_dir, 'create-dist') + execute_stdout([sys.executable, bootstrap]) + execute_stdout([sys.executable, update, '-t', args.target_arch, + '--defines', args.defines]) + execute_stdout([sys.executable, build, '-R', '-t', args.target_arch]) + execute_stdout([sys.executable, create_dist, '-c', 'static_library', + '--no_zip', '-t', args.target_arch]) + + +def parse_args(): + parser = argparse.ArgumentParser(description='Build libchromiumcontent') + parser.add_argument('--target_arch', + help='Specify the arch to build for') + parser.add_argument('--defines', default='', + help='The definetions passed to gyp') + parser.add_argument('-v', '--verbose', action='store_true', + help='Prints the output of the subprocesses') + return parser.parse_args() + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/script/build.py b/script/build.py index c428b95f329f..1a7378bf9758 100755 --- a/script/build.py +++ b/script/build.py @@ -5,7 +5,8 @@ import os import subprocess import sys -from lib.util import atom_gyp +from lib.config import get_target_arch +from lib.util import electron_gyp, import_vs_env CONFIGURATIONS = ['Release', 'Debug'] @@ -15,6 +16,9 @@ SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) def main(): os.chdir(SOURCE_ROOT) + # Update the VS build env. + import_vs_env(get_target_arch()) + ninja = os.path.join('vendor', 'depot_tools', 'ninja') if sys.platform == 'win32': ninja += '.exe' @@ -36,7 +40,7 @@ def parse_args(): required=False) parser.add_argument('-t', '--target', help='Build specified target', - default=atom_gyp()['project_name%'], + default=electron_gyp()['project_name%'], required=False) return parser.parse_args() diff --git a/script/bump-version.py b/script/bump-version.py index e2c22e8cd9d4..4db30b71fca9 100755 --- a/script/bump-version.py +++ b/script/bump-version.py @@ -4,7 +4,7 @@ import os import re import sys -from lib.util import execute, get_atom_shell_version, parse_version, scoped_cwd +from lib.util import execute, get_electron_version, parse_version, scoped_cwd SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) @@ -18,7 +18,7 @@ def main(): option = sys.argv[1] increments = ['major', 'minor', 'patch', 'build'] if option in increments: - version = get_atom_shell_version() + version = get_electron_version() versions = parse_version(version.split('-')[0]) versions = increase_version(versions, increments.index(option)) else: @@ -27,7 +27,7 @@ def main(): version = '.'.join(versions[:3]) with scoped_cwd(SOURCE_ROOT): - update_atom_gyp(version) + update_electron_gyp(version) update_win_rc(version, versions) update_version_h(versions) update_info_plist(version) @@ -42,15 +42,15 @@ def increase_version(versions, index): return versions -def update_atom_gyp(version): +def update_electron_gyp(version): pattern = re.compile(" *'version%' *: *'[0-9.]+'") - with open('atom.gyp', 'r') as f: + with open('electron.gyp', 'r') as f: lines = f.readlines() for i in range(0, len(lines)): if pattern.match(lines[i]): lines[i] = " 'version%': '{0}',\n".format(version) - with open('atom.gyp', 'w') as f: + with open('electron.gyp', 'w') as f: f.write(''.join(lines)) return diff --git a/script/cibuild b/script/cibuild index 391b859a3d2e..7d025b4e82cd 100755 --- a/script/cibuild +++ b/script/cibuild @@ -32,6 +32,9 @@ LINUX_DEPS_ARM = [ def main(): os.environ['CI'] = '1' + if os.environ.has_key('JANKY_SHA1'): + setup_nodenv() + target_arch = 'x64' if os.environ.has_key('TARGET_ARCH'): target_arch = os.environ['TARGET_ARCH'] @@ -50,33 +53,34 @@ def main(): if PLATFORM == 'linux': os.environ['DISPLAY'] = ':99.0' - run_script('clean.py') - # CI's npm is not reliable. npm = 'npm.cmd' if PLATFORM == 'win32' else 'npm' execute([npm, 'install', 'npm@2.12.1']) + log_versions() + is_release = os.environ.has_key('ELECTRON_RELEASE') args = ['--target_arch=' + target_arch] if not is_release: args += ['--dev'] run_script('bootstrap.py', args) - run_script('cpplint.py') - run_script('eslint.py') if PLATFORM != 'win32': + sys.stderr.write('\nRunning `npm run lint`\n') + sys.stderr.flush() + execute([npm, 'run', 'lint']) run_script('pylint.py') if is_release: run_script('build.py', ['-c', 'R']) run_script('create-dist.py') run_script('upload.py') else: + if PLATFORM == 'win32': + os.environ['OUTPUT_TO_FILE'] = 'output.log' run_script('build.py', ['-c', 'D']) - if PLATFORM != 'win32' and target_arch == 'x64': + if PLATFORM == 'win32' or target_arch == 'x64': run_script('test.py', ['--ci']) - run_script('clean.py') - def run_script(script, args=[]): sys.stderr.write('\nRunning ' + script +'\n') @@ -85,5 +89,23 @@ def run_script(script, args=[]): subprocess.check_call([sys.executable, script] + args) +def log_versions(): + sys.stderr.write('\nnode --version\n') + sys.stderr.flush() + subprocess.call(['node', '--version']) + + sys.stderr.write('\nnpm --version\n') + sys.stderr.flush() + npm = 'npm.cmd' if PLATFORM == 'win32' else 'npm' + subprocess.call([npm, '--version']) + + +def setup_nodenv(): + if os.path.isdir('/usr/local/share/nodenv'): + os.environ['NODENV_ROOT'] = '/usr/local/share/nodenv' + os.environ['PATH'] = '/usr/local/share/nodenv/bin:/usr/local/share/nodenv/shims:' + os.environ['PATH'] + os.environ['NODENV_VERSION'] = 'v0.10.21' + + if __name__ == '__main__': sys.exit(main()) diff --git a/script/cibuild.ps1 b/script/cibuild.ps1 new file mode 100644 index 000000000000..b06d48d1a8be --- /dev/null +++ b/script/cibuild.ps1 @@ -0,0 +1,2 @@ +$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition +& python "$scriptPath/cibuild" diff --git a/script/create-dist.py b/script/create-dist.py index 32d8f52aff37..3f95cf2281d6 100755 --- a/script/create-dist.py +++ b/script/create-dist.py @@ -11,11 +11,11 @@ import stat from lib.config import LIBCHROMIUMCONTENT_COMMIT, BASE_URL, PLATFORM, \ 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 +from lib.util import scoped_cwd, rm_rf, get_electron_version, make_zip, \ + execute, electron_gyp -ATOM_SHELL_VERSION = get_atom_shell_version() +ELECTRON_VERSION = get_electron_version() SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) DIST_DIR = os.path.join(SOURCE_ROOT, 'dist') @@ -23,8 +23,8 @@ OUT_DIR = os.path.join(SOURCE_ROOT, 'out', 'R') CHROMIUM_DIR = os.path.join(SOURCE_ROOT, 'vendor', 'brightray', 'vendor', 'download', 'libchromiumcontent', 'static_library') -PROJECT_NAME = atom_gyp()['project_name%'] -PRODUCT_NAME = atom_gyp()['product_name%'] +PROJECT_NAME = electron_gyp()['project_name%'] +PRODUCT_NAME = electron_gyp()['product_name%'] TARGET_BINARIES = { 'darwin': [ @@ -36,17 +36,13 @@ TARGET_BINARIES = { 'icudtl.dat', 'libEGL.dll', 'libGLESv2.dll', - 'msvcp120.dll', - 'msvcr120.dll', 'ffmpeg.dll', 'node.dll', - 'pdf.dll', 'content_resources_200_percent.pak', 'ui_resources_200_percent.pak', 'xinput1_3.dll', 'natives_blob.bin', 'snapshot_blob.bin', - 'vccorlib120.dll', ], 'linux': [ PROJECT_NAME, # 'electron' @@ -72,17 +68,11 @@ TARGET_DIRECTORIES = { ], } -SYSTEM_LIBRARIES = [ - 'libgcrypt.so', -] - def main(): rm_rf(DIST_DIR) os.makedirs(DIST_DIR) - target_arch = get_target_arch() - force_build() create_symbols() copy_binaries() @@ -92,13 +82,11 @@ def main(): if PLATFORM == 'linux': strip_binaries() - if target_arch != 'arm': - copy_system_libraries() create_version() create_dist_zip() create_chrome_binary_zip('chromedriver', get_chromedriver_version()) - create_chrome_binary_zip('mksnapshot', ATOM_SHELL_VERSION) + create_chrome_binary_zip('mksnapshot', ELECTRON_VERSION) create_ffmpeg_zip() create_symbols_zip() @@ -136,34 +124,23 @@ def copy_license(): def strip_binaries(): - if get_target_arch() == 'arm': - strip = 'arm-linux-gnueabihf-strip' - else: - strip = 'strip' for binary in TARGET_BINARIES[PLATFORM]: if binary.endswith('.so') or '.' not in binary: - execute([strip, os.path.join(DIST_DIR, binary)]) + strip_binary(os.path.join(DIST_DIR, binary)) -def copy_system_libraries(): - executable_path = os.path.join(OUT_DIR, PROJECT_NAME) # our/R/electron - ldd = execute(['ldd', executable_path]) - lib_re = re.compile('\t(.*) => (.+) \(.*\)$') - for line in ldd.splitlines(): - m = lib_re.match(line) - if not m: - continue - for i, library in enumerate(SYSTEM_LIBRARIES): - real_library = m.group(1) - if real_library.startswith(library): - shutil.copyfile(m.group(2), os.path.join(DIST_DIR, real_library)) - SYSTEM_LIBRARIES[i] = real_library +def strip_binary(binary_path): + if get_target_arch() == 'arm': + strip = 'arm-linux-gnueabihf-strip' + else: + strip = 'strip' + execute([strip, binary_path]) def create_version(): version_path = os.path.join(SOURCE_ROOT, 'dist', 'version') with open(version_path, 'w') as version_file: - version_file.write(ATOM_SHELL_VERSION) + version_file.write(ELECTRON_VERSION) def create_symbols(): @@ -178,7 +155,7 @@ def create_symbols(): def create_dist_zip(): - dist_name = '{0}-{1}-{2}-{3}.zip'.format(PROJECT_NAME, ATOM_SHELL_VERSION, + dist_name = '{0}-{1}-{2}-{3}.zip'.format(PROJECT_NAME, ELECTRON_VERSION, get_platform_key(), get_target_arch()) zip_file = os.path.join(SOURCE_ROOT, 'dist', dist_name) @@ -186,8 +163,6 @@ def create_dist_zip(): with scoped_cwd(DIST_DIR): files = TARGET_BINARIES[PLATFORM] + ['LICENSE', 'LICENSES.chromium.html', 'version'] - if PLATFORM == 'linux': - files += [lib for lib in SYSTEM_LIBRARIES if os.path.exists(lib)] dirs = TARGET_DIRECTORIES[PLATFORM] make_zip(zip_file, files, dirs) @@ -208,7 +183,7 @@ def create_chrome_binary_zip(binary, version): def create_ffmpeg_zip(): dist_name = 'ffmpeg-{0}-{1}-{2}.zip'.format( - ATOM_SHELL_VERSION, get_platform_key(), get_target_arch()) + ELECTRON_VERSION, get_platform_key(), get_target_arch()) zip_file = os.path.join(SOURCE_ROOT, 'dist', dist_name) if PLATFORM == 'darwin': @@ -220,13 +195,17 @@ def create_ffmpeg_zip(): shutil.copy2(os.path.join(CHROMIUM_DIR, '..', 'ffmpeg', ffmpeg_name), DIST_DIR) + + if PLATFORM == 'linux': + strip_binary(os.path.join(DIST_DIR, ffmpeg_name)) + with scoped_cwd(DIST_DIR): make_zip(zip_file, [ffmpeg_name, 'LICENSE', 'LICENSES.chromium.html'], []) def create_symbols_zip(): dist_name = '{0}-{1}-{2}-{3}-symbols.zip'.format(PROJECT_NAME, - ATOM_SHELL_VERSION, + ELECTRON_VERSION, get_platform_key(), get_target_arch()) zip_file = os.path.join(DIST_DIR, dist_name) @@ -238,7 +217,7 @@ def create_symbols_zip(): if PLATFORM == 'darwin': dsym_name = '{0}-{1}-{2}-{3}-dsym.zip'.format(PROJECT_NAME, - ATOM_SHELL_VERSION, + ELECTRON_VERSION, get_platform_key(), get_target_arch()) with scoped_cwd(DIST_DIR): diff --git a/script/dump-symbols.py b/script/dump-symbols.py index 5c98d38a31f8..76949e95aa92 100755 --- a/script/dump-symbols.py +++ b/script/dump-symbols.py @@ -4,7 +4,7 @@ import os import sys from lib.config import PLATFORM -from lib.util import atom_gyp, execute, rm_rf +from lib.util import electron_gyp, execute, rm_rf SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) @@ -56,7 +56,7 @@ def register_required_dll(): def get_names_from_gyp(): - variables = atom_gyp() + variables = electron_gyp() return (variables['project_name%'], variables['product_name%']) diff --git a/script/eslint.py b/script/eslint.py deleted file mode 100755 index 7b912e0e1d6c..000000000000 --- a/script/eslint.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python - -import glob -import os -import sys - -from lib.config import PLATFORM -from lib.util import execute - - -SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) - - -def main(): - os.chdir(SOURCE_ROOT) - - # Skip eslint on our Windows build machine for now. - if PLATFORM == 'win32' and os.getenv('JANKY_SHA1'): - return - - eslint = os.path.join(SOURCE_ROOT, 'node_modules', '.bin', 'eslint') - if sys.platform in ['win32', 'cygwin']: - eslint += '.cmd' - settings = ['--quiet', '--config'] - - sourceConfig = os.path.join('script', 'eslintrc-base.json') - sourceFiles = ['atom'] - execute([eslint] + settings + [sourceConfig] + sourceFiles) - - specConfig = os.path.join('script', 'eslintrc-spec.json') - specFiles = glob.glob('spec/*.js') - execute([eslint] + settings + [specConfig] + specFiles) - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/script/eslintrc-base.json b/script/eslintrc-base.json deleted file mode 100644 index f968a2d20c78..000000000000 --- a/script/eslintrc-base.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "rules": { - "indent": [ - 2, - 2, - { - "SwitchCase": 1 - } - ], - "quotes": [ - 0, - "single" - ], - "semi": [ - 2, - "always" - ], - "comma-dangle": 0, - "linebreak-style": 0, - "no-console": 0, - "no-undef": 2, - "no-unused-vars": 2 - }, - "env": { - "es6": true, - "node": true, - "browser": true - }, - "extends": "eslint:recommended", - "globals": { - "DevToolsAPI": false, - "InspectorFrontendHost": false, - "WebInspector": false, - "WebView": false - } -} diff --git a/script/eslintrc-spec.json b/script/eslintrc-spec.json deleted file mode 100644 index 5aa9df265d6b..000000000000 --- a/script/eslintrc-spec.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "env": { - "jquery": true, - "mocha": true - }, - "extends": "./eslintrc-base.json" -} diff --git a/script/install-sysroot.py b/script/install-sysroot.py index 69acfb13269a..be68fbad0ad7 100755 --- a/script/install-sysroot.py +++ b/script/install-sysroot.py @@ -30,15 +30,15 @@ from lib.util import get_host_arch SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) URL_PREFIX = 'https://github.com' URL_PATH = 'atom/debian-sysroot-image-creator/releases/download' -REVISION_AMD64 = 264817 -REVISION_I386 = 'v0.2.0' -REVISION_ARM = 'v0.1.0' +REVISION_AMD64 = 'v0.4.0' +REVISION_I386 = 'v0.4.0' +REVISION_ARM = 'v0.4.0' TARBALL_AMD64 = 'debian_wheezy_amd64_sysroot.tgz' TARBALL_I386 = 'debian_wheezy_i386_sysroot.tgz' TARBALL_ARM = 'debian_wheezy_arm_sysroot.tgz' -TARBALL_AMD64_SHA1SUM = '74b7231e12aaf45c5c5489d9aebb56bd6abb3653' -TARBALL_I386_SHA1SUM = 'f5b2ceaeb3f7e6bc2058733585fe877d002b5fa7' -TARBALL_ARM_SHA1SUM = '72e668c57b8591e108759584942ddb6f6cee1322' +TARBALL_AMD64_SHA1SUM = 'a7e8faa99b681317969ac450a27233925bdeed62' +TARBALL_I386_SHA1SUM = '9fc827eddc26e562c0a0b2586be5dc075e570e10' +TARBALL_ARM_SHA1SUM = 'bfa4233708ab937d682a14e8d87ddba3cadb6eae' SYSROOT_DIR_AMD64 = 'debian_wheezy_amd64-sysroot' SYSROOT_DIR_I386 = 'debian_wheezy_i386-sysroot' SYSROOT_DIR_ARM = 'debian_wheezy_arm-sysroot' diff --git a/script/lib/config.py b/script/lib/config.py index e3404918ce4e..43423e514368 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = '599c8941e1884e155218ec1013cba9fc5c7c7072' +LIBCHROMIUMCONTENT_COMMIT = '8f2a0aa0a9dd107e4d574cc9d552781c55c86bab' PLATFORM = { 'cygwin': 'win32', @@ -38,23 +38,29 @@ def get_target_arch(): if e.errno != errno.ENOENT: raise - if PLATFORM == 'win32': - return 'ia32' - else: - return 'x64' + return 'x64' def get_chromedriver_version(): - return 'v2.15' + return 'v2.21' + +def get_env_var(name): + value = os.environ.get('ELECTRON_' + name, '') + if not value: + # TODO Remove ATOM_SHELL_* fallback values + value = os.environ.get('ATOM_SHELL_' + name, '') + if value: + print 'Warning: Use $ELECTRON_' + name + ' instead of $ATOM_SHELL_' + name + return value def s3_config(): - config = (os.environ.get('ATOM_SHELL_S3_BUCKET', ''), - os.environ.get('ATOM_SHELL_S3_ACCESS_KEY', ''), - os.environ.get('ATOM_SHELL_S3_SECRET_KEY', '')) - message = ('Error: Please set the $ATOM_SHELL_S3_BUCKET, ' - '$ATOM_SHELL_S3_ACCESS_KEY, and ' - '$ATOM_SHELL_S3_SECRET_KEY environment variables') + config = (get_env_var('S3_BUCKET'), + get_env_var('S3_ACCESS_KEY'), + get_env_var('S3_SECRET_KEY')) + message = ('Error: Please set the $ELECTRON_S3_BUCKET, ' + '$ELECTRON_S3_ACCESS_KEY, and ' + '$ELECTRON_S3_SECRET_KEY environment variables') assert all(len(c) for c in config), message return config diff --git a/script/lib/env_util.py b/script/lib/env_util.py new file mode 100644 index 000000000000..df759241fe80 --- /dev/null +++ b/script/lib/env_util.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python + +from __future__ import print_function + +import itertools +import subprocess +import sys + + +def validate_pair(ob): + if not (len(ob) == 2): + print("Unexpected result:", ob, file=sys.stderr) + return False + else: + return True + + +def consume(iter): + try: + while True: next(iter) + except StopIteration: + pass + + +def get_environment_from_batch_command(env_cmd, initial=None): + """ + Take a command (either a single command or list of arguments) + and return the environment created after running that command. + Note that if the command must be a batch file or .cmd file, or the + changes to the environment will not be captured. + + If initial is supplied, it is used as the initial environment passed + to the child process. + """ + if not isinstance(env_cmd, (list, tuple)): + env_cmd = [env_cmd] + # Construct the command that will alter the environment. + env_cmd = subprocess.list2cmdline(env_cmd) + # Create a tag so we can tell in the output when the proc is done. + tag = 'END OF BATCH COMMAND' + # Construct a cmd.exe command to do accomplish this. + cmd = 'cmd.exe /s /c "{env_cmd} && echo "{tag}" && set"'.format(**vars()) + # Launch the process. + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=initial) + # Parse the output sent to stdout. + lines = proc.stdout + # Consume whatever output occurs until the tag is reached. + consume(itertools.takewhile(lambda l: tag not in l, lines)) + # Define a way to handle each KEY=VALUE line. + handle_line = lambda l: l.rstrip().split('=',1) + # Parse key/values into pairs. + pairs = map(handle_line, lines) + # Make sure the pairs are valid. + valid_pairs = filter(validate_pair, pairs) + # Construct a dictionary of the pairs. + result = dict(valid_pairs) + # Let the process finish. + proc.communicate() + return result + + +def get_vs_env(vs_version, arch): + """ + Returns the env object for VS building environment. + + The vs_version can be strings like "12.0" (e.g. VS2013), the arch has to + be one of "x86", "amd64", "arm", "x86_amd64", "x86_arm", "amd64_x86", + "amd64_arm", e.g. the args passed to vcvarsall.bat. + """ + vsvarsall = "C:\\Program Files (x86)\\Microsoft Visual Studio {0}\\VC\\vcvarsall.bat".format(vs_version) + return get_environment_from_batch_command([vsvarsall, arch]) diff --git a/script/lib/util.py b/script/lib/util.py index fc52d316d30c..4db4e5fa6748 100644 --- a/script/lib/util.py +++ b/script/lib/util.py @@ -16,6 +16,7 @@ import os import zipfile from config import is_verbose_mode +from env_util import get_vs_env def get_host_arch(): @@ -178,16 +179,16 @@ def execute_stdout(argv, env=os.environ): execute(argv, env) -def atom_gyp(): +def electron_gyp(): SOURCE_ROOT = os.path.abspath(os.path.join(__file__, '..', '..', '..')) - gyp = os.path.join(SOURCE_ROOT, 'atom.gyp') + gyp = os.path.join(SOURCE_ROOT, 'electron.gyp') with open(gyp) as f: obj = eval(f.read()); return obj['variables'] -def get_atom_shell_version(): - return 'v' + atom_gyp()['version%'] +def get_electron_version(): + return 'v' + electron_gyp()['version%'] def parse_version(version): @@ -223,3 +224,15 @@ def s3put(bucket, access_key, secret_key, prefix, key_prefix, files): ] + files execute(args, env) + + +def import_vs_env(target_arch): + if sys.platform != 'win32': + return + + if target_arch == 'ia32': + vs_arch = 'amd64_x86' + else: + vs_arch = 'x86_amd64' + env = get_vs_env('14.0', vs_arch) + os.environ.update(env) diff --git a/script/start.py b/script/start.py index 76a079cb26ea..839ddf997348 100644 --- a/script/start.py +++ b/script/start.py @@ -4,13 +4,13 @@ import os import subprocess import sys -from lib.util import atom_gyp +from lib.util import electron_gyp SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) -PROJECT_NAME = atom_gyp()['project_name%'] -PRODUCT_NAME = atom_gyp()['product_name%'] +PROJECT_NAME = electron_gyp()['project_name%'] +PRODUCT_NAME = electron_gyp()['product_name%'] def main(): diff --git a/script/test.py b/script/test.py index 28aeac9dc1ff..02377f481c5b 100755 --- a/script/test.py +++ b/script/test.py @@ -4,13 +4,13 @@ import os import subprocess import sys -from lib.util import atom_gyp +from lib.util import electron_gyp, rm_rf SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) -PROJECT_NAME = atom_gyp()['project_name%'] -PRODUCT_NAME = atom_gyp()['product_name%'] +PROJECT_NAME = electron_gyp()['project_name%'] +PRODUCT_NAME = electron_gyp()['product_name%'] def main(): @@ -21,16 +21,29 @@ def main(): config = 'R' if sys.platform == 'darwin': - atom_shell = os.path.join(SOURCE_ROOT, 'out', config, + electron = os.path.join(SOURCE_ROOT, 'out', config, '{0}.app'.format(PRODUCT_NAME), 'Contents', 'MacOS', PRODUCT_NAME) elif sys.platform == 'win32': - atom_shell = os.path.join(SOURCE_ROOT, 'out', config, + electron = os.path.join(SOURCE_ROOT, 'out', config, '{0}.exe'.format(PROJECT_NAME)) else: - atom_shell = os.path.join(SOURCE_ROOT, 'out', config, PROJECT_NAME) + electron = os.path.join(SOURCE_ROOT, 'out', config, PROJECT_NAME) - subprocess.check_call([atom_shell, 'spec'] + sys.argv[1:]) + returncode = 0 + try: + subprocess.check_call([electron, 'spec'] + sys.argv[1:]) + except subprocess.CalledProcessError as e: + returncode = e.returncode + + if os.environ.has_key('OUTPUT_TO_FILE'): + output_to_file = os.environ['OUTPUT_TO_FILE'] + with open(output_to_file, 'r') as f: + print f.read() + rm_rf(output_to_file) + + + return returncode if __name__ == '__main__': diff --git a/script/update-clang.sh b/script/update-clang.sh index 801d60397145..0175bc2a0730 100755 --- a/script/update-clang.sh +++ b/script/update-clang.sh @@ -8,7 +8,7 @@ # Do NOT CHANGE this if you don't know what you're doing -- see # https://code.google.com/p/chromium/wiki/UpdatingClang # Reverting problematic clang rolls is safe, though. -CLANG_REVISION=247874 +CLANG_REVISION=261368 # This is incremented when pushing a new build of Clang at the same revision. CLANG_SUB_REVISION=1 diff --git a/script/update-external-binaries.py b/script/update-external-binaries.py index fae268ea8cb7..470f735fdb42 100755 --- a/script/update-external-binaries.py +++ b/script/update-external-binaries.py @@ -8,9 +8,9 @@ from lib.config import get_target_arch from lib.util import safe_mkdir, rm_rf, extract_zip, tempdir, download -VERSION = 'v0.8.0' +VERSION = 'v1.0.0' SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) -FRAMEWORKS_URL = 'http://github.com/atom/atom-shell-frameworks/releases' \ +FRAMEWORKS_URL = 'http://github.com/electron/electron-frameworks/releases' \ '/download/' + VERSION @@ -30,7 +30,6 @@ def main(): download_and_unzip('Squirrel') elif sys.platform in ['cygwin', 'win32']: download_and_unzip('directxsdk-' + get_target_arch()) - download_and_unzip('vs2012-crt-' + get_target_arch()) with open(version_file, 'w') as f: f.write(VERSION) diff --git a/script/update.py b/script/update.py index e91e8401cbff..a67a49e7ab51 100755 --- a/script/update.py +++ b/script/update.py @@ -1,12 +1,13 @@ #!/usr/bin/env python +import argparse import os import platform import subprocess import sys from lib.config import get_target_arch, PLATFORM -from lib.util import get_host_arch +from lib.util import get_host_arch, import_vs_env SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) @@ -23,6 +24,13 @@ def main(): return update_gyp() +def parse_args(): + parser = argparse.ArgumentParser(description='Update build configurations') + parser.add_argument('--defines', default='', + help='The definetions passed to gyp') + return parser.parse_args() + + def update_external_binaries(): uf = os.path.join('script', 'update-external-binaries.py') subprocess.check_call([sys.executable, uf]) @@ -41,11 +49,14 @@ def update_gyp(): def run_gyp(target_arch, component): + # Update the VS build env. + import_vs_env(target_arch) + env = os.environ.copy() if PLATFORM == 'linux' and target_arch != get_host_arch(): env['GYP_CROSSCOMPILE'] = '1' elif PLATFORM == 'win32': - env['GYP_MSVS_VERSION'] = '2013' + env['GYP_MSVS_VERSION'] = '2015' python = sys.executable if sys.platform == 'cygwin': # Force using win32 python on cygwin. @@ -60,6 +71,7 @@ def run_gyp(target_arch, component): mas_build = 1 else: mas_build = 0 + defines = [ '-Dlibchromiumcontent_component={0}'.format(component), '-Dtarget_arch={0}'.format(target_arch), @@ -67,8 +79,15 @@ def run_gyp(target_arch, component): '-Dlibrary=static_library', '-Dmas_build={0}'.format(mas_build), ] + + # Add the defines passed from command line. + args = parse_args() + for define in [d.strip() for d in args.defines.split(' ')]: + if define: + defines += ['-D' + define] + return subprocess.call([python, gyp, '-f', 'ninja', '--depth', '.', - 'atom.gyp', '-Icommon.gypi'] + defines, env=env) + 'electron.gyp', '-Icommon.gypi'] + defines, env=env) if __name__ == '__main__': diff --git a/script/upload-index-json.py b/script/upload-index-json.py index 671408fa9219..f5e5b3c7a6ad 100755 --- a/script/upload-index-json.py +++ b/script/upload-index-json.py @@ -4,28 +4,28 @@ import os import sys from lib.config import PLATFORM, s3_config -from lib.util import atom_gyp, execute, s3put, scoped_cwd +from lib.util import electron_gyp, execute, s3put, scoped_cwd SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) OUT_DIR = os.path.join(SOURCE_ROOT, 'out', 'D') -PROJECT_NAME = atom_gyp()['project_name%'] -PRODUCT_NAME = atom_gyp()['product_name%'] +PROJECT_NAME = electron_gyp()['project_name%'] +PRODUCT_NAME = electron_gyp()['product_name%'] def main(): # Upload the index.json. with scoped_cwd(SOURCE_ROOT): if sys.platform == 'darwin': - atom_shell = os.path.join(OUT_DIR, '{0}.app'.format(PRODUCT_NAME), + electron = os.path.join(OUT_DIR, '{0}.app'.format(PRODUCT_NAME), 'Contents', 'MacOS', PRODUCT_NAME) elif sys.platform == 'win32': - atom_shell = os.path.join(OUT_DIR, '{0}.exe'.format(PROJECT_NAME)) + electron = os.path.join(OUT_DIR, '{0}.exe'.format(PROJECT_NAME)) else: - atom_shell = os.path.join(OUT_DIR, PROJECT_NAME) + electron = os.path.join(OUT_DIR, PROJECT_NAME) index_json = os.path.relpath(os.path.join(OUT_DIR, 'index.json')) - execute([atom_shell, + execute([electron, os.path.join('tools', 'dump-version-info.js'), index_json]) diff --git a/script/upload-windows-pdb.py b/script/upload-windows-pdb.py index 12186aad2edc..6e8030337f3c 100755 --- a/script/upload-windows-pdb.py +++ b/script/upload-windows-pdb.py @@ -5,15 +5,15 @@ import glob import sys from lib.config import s3_config -from lib.util import atom_gyp, execute, rm_rf, safe_mkdir, s3put +from lib.util import electron_gyp, execute, rm_rf, safe_mkdir, s3put SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) SYMBOLS_DIR = 'dist\\symbols' DOWNLOAD_DIR = 'vendor\\brightray\\vendor\\download\\libchromiumcontent' -PROJECT_NAME = atom_gyp()['project_name%'] -PRODUCT_NAME = atom_gyp()['product_name%'] +PROJECT_NAME = electron_gyp()['project_name%'] +PRODUCT_NAME = electron_gyp()['product_name%'] PDB_LIST = [ 'out\\R\\{0}.exe.pdb'.format(PROJECT_NAME), diff --git a/script/upload.py b/script/upload.py index d23bc554c3cc..9faf4fa8d05b 100755 --- a/script/upload.py +++ b/script/upload.py @@ -8,31 +8,31 @@ import sys import tempfile 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 + get_platform_key, get_env_var +from lib.util import electron_gyp, execute, get_electron_version, \ + parse_version, scoped_cwd from lib.github import GitHub -ATOM_SHELL_REPO = 'atom/electron' -ATOM_SHELL_VERSION = get_atom_shell_version() +ELECTRON_REPO = 'electron/electron' +ELECTRON_VERSION = get_electron_version() -PROJECT_NAME = atom_gyp()['project_name%'] -PRODUCT_NAME = atom_gyp()['product_name%'] +PROJECT_NAME = electron_gyp()['project_name%'] +PRODUCT_NAME = electron_gyp()['product_name%'] SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) 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, + ELECTRON_VERSION, get_platform_key(), get_target_arch()) SYMBOLS_NAME = '{0}-{1}-{2}-{3}-symbols.zip'.format(PROJECT_NAME, - ATOM_SHELL_VERSION, + ELECTRON_VERSION, get_platform_key(), get_target_arch()) DSYM_NAME = '{0}-{1}-{2}-{3}-dsym.zip'.format(PROJECT_NAME, - ATOM_SHELL_VERSION, + ELECTRON_VERSION, get_platform_key(), get_target_arch()) @@ -45,16 +45,16 @@ def main(): create_dist = os.path.join(SOURCE_ROOT, 'script', 'create-dist.py') execute([sys.executable, create_dist]) - build_version = get_atom_shell_build_version() - if not ATOM_SHELL_VERSION.startswith(build_version): + build_version = get_electron_build_version() + if not ELECTRON_VERSION.startswith(build_version): error = 'Tag name ({0}) should match build version ({1})\n'.format( - ATOM_SHELL_VERSION, build_version) + ELECTRON_VERSION, build_version) sys.stderr.write(error) sys.stderr.flush() return 1 github = GitHub(auth_token()) - releases = github.repos(ATOM_SHELL_REPO).releases.get() + releases = github.repos(ELECTRON_REPO).releases.get() tag_exists = False for release in releases: if not release['draft'] and release['tag_name'] == args.version: @@ -68,7 +68,7 @@ def main(): # Upload the SHASUMS.txt. execute([sys.executable, os.path.join(SOURCE_ROOT, 'script', 'upload-checksums.py'), - '-v', ATOM_SHELL_VERSION]) + '-v', ELECTRON_VERSION]) # Upload the index.json. execute([sys.executable, @@ -80,27 +80,31 @@ def main(): # Do not upload other files when passed "-p". return - # 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)) + # Upload Electron with GitHub Releases API. + upload_electron(github, release, os.path.join(DIST_DIR, DIST_NAME)) + upload_electron(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_electron(github, release, os.path.join(DIST_DIR, DSYM_NAME)) # Upload free version of ffmpeg. ffmpeg = 'ffmpeg-{0}-{1}-{2}.zip'.format( - ATOM_SHELL_VERSION, get_platform_key(), get_target_arch()) - upload_atom_shell(github, release, os.path.join(DIST_DIR, ffmpeg)) + ELECTRON_VERSION, get_platform_key(), get_target_arch()) + upload_electron(github, release, os.path.join(DIST_DIR, ffmpeg)) # Upload chromedriver and mksnapshot for minor version update. if parse_version(args.version)[2] == '0': chromedriver = 'chromedriver-{0}-{1}-{2}.zip'.format( get_chromedriver_version(), get_platform_key(), get_target_arch()) - upload_atom_shell(github, release, os.path.join(DIST_DIR, chromedriver)) + upload_electron(github, release, os.path.join(DIST_DIR, chromedriver)) mksnapshot = 'mksnapshot-{0}-{1}-{2}.zip'.format( - ATOM_SHELL_VERSION, get_platform_key(), get_target_arch()) - upload_atom_shell(github, release, os.path.join(DIST_DIR, mksnapshot)) + ELECTRON_VERSION, get_platform_key(), get_target_arch()) + upload_electron(github, release, os.path.join(DIST_DIR, mksnapshot)) if PLATFORM == 'win32' and not tag_exists: + # Upload PDBs to Windows symbol server. + execute([sys.executable, + os.path.join(SOURCE_ROOT, 'script', 'upload-windows-pdb.py')]) + # Upload node headers. execute([sys.executable, os.path.join(SOURCE_ROOT, 'script', 'upload-node-headers.py'), @@ -110,28 +114,28 @@ def main(): def parse_args(): parser = argparse.ArgumentParser(description='upload distribution file') parser.add_argument('-v', '--version', help='Specify the version', - default=ATOM_SHELL_VERSION) + default=ELECTRON_VERSION) parser.add_argument('-p', '--publish-release', help='Publish the release', action='store_true') return parser.parse_args() -def get_atom_shell_build_version(): +def get_electron_build_version(): if get_target_arch() == 'arm' or os.environ.has_key('CI'): # In CI we just build as told. - return ATOM_SHELL_VERSION + return ELECTRON_VERSION if PLATFORM == 'darwin': - atom_shell = os.path.join(SOURCE_ROOT, 'out', 'R', + electron = os.path.join(SOURCE_ROOT, 'out', 'R', '{0}.app'.format(PRODUCT_NAME), 'Contents', 'MacOS', PRODUCT_NAME) elif PLATFORM == 'win32': - atom_shell = os.path.join(SOURCE_ROOT, 'out', 'R', + electron = os.path.join(SOURCE_ROOT, 'out', 'R', '{0}.exe'.format(PROJECT_NAME)) else: - atom_shell = os.path.join(SOURCE_ROOT, 'out', 'R', PROJECT_NAME) + electron = os.path.join(SOURCE_ROOT, 'out', 'R', PROJECT_NAME) - return subprocess.check_output([atom_shell, '--version']).strip() + return subprocess.check_output([electron, '--version']).strip() def dist_newer_than_head(): @@ -188,17 +192,17 @@ def create_release_draft(github, tag): sys.exit(0) data = dict(tag_name=tag, name=name, body=body, draft=True) - r = github.repos(ATOM_SHELL_REPO).releases.post(data=data) + r = github.repos(ELECTRON_REPO).releases.post(data=data) return r -def upload_atom_shell(github, release, file_path): +def upload_electron(github, release, file_path): # Delete the original file before uploading in CI. if os.environ.has_key('CI'): try: for asset in release['assets']: if asset['name'] == os.path.basename(file_path): - github.repos(ATOM_SHELL_REPO).releases.assets(asset['id']).delete() + github.repos(ELECTRON_REPO).releases.assets(asset['id']).delete() break except Exception: pass @@ -207,18 +211,18 @@ def upload_atom_shell(github, release, file_path): params = {'name': os.path.basename(file_path)} headers = {'Content-Type': 'application/zip'} with open(file_path, 'rb') as f: - github.repos(ATOM_SHELL_REPO).releases(release['id']).assets.post( + github.repos(ELECTRON_REPO).releases(release['id']).assets.post( params=params, headers=headers, data=f, verify=False) def publish_release(github, release_id): data = dict(draft=False) - github.repos(ATOM_SHELL_REPO).releases(release_id).patch(data=data) + github.repos(ELECTRON_REPO).releases(release_id).patch(data=data) def auth_token(): - token = os.environ.get('ATOM_SHELL_GITHUB_TOKEN') - message = ('Error: Please set the $ATOM_SHELL_GITHUB_TOKEN ' + token = get_env_var('GITHUB_TOKEN') + message = ('Error: Please set the $ELECTRON_GITHUB_TOKEN ' 'environment variable, which is your personal token') assert token, message return token diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index cbf8223401ce..260bdd31545d 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -1,131 +1,281 @@ -const assert = require('assert'); -const ChildProcess = require('child_process'); -const path = require('path'); -const remote = require('electron').remote; +const assert = require('assert') +const ChildProcess = require('child_process') +const https = require('https') +const net = require('net') +const fs = require('fs') +const path = require('path') +const {remote} = require('electron') -const app = remote.require('electron').app; -const BrowserWindow = remote.require('electron').BrowserWindow; +const {app, BrowserWindow, ipcMain} = remote +const isCI = remote.getGlobal('isCi') -describe('electron module', function() { - it ('can prevent exposing internal modules to require', function(done) { - const electron = require('electron'); - const clipboard = require('clipboard'); - assert.equal(typeof clipboard, 'object'); - electron.hideInternalModules(); - try { - require('clipboard'); - } catch(err) { - assert.equal(err.message, 'Cannot find module \'clipboard\''); - done(); - } - }); -}); +describe('electron module', function () { + it('does not expose internal modules to require', function () { + assert.throws(function () { + require('clipboard') + }, /Cannot find module 'clipboard'/) + }) -describe('app module', function() { - describe('app.getVersion()', function() { - it('returns the version field of package.json', function() { - assert.equal(app.getVersion(), '0.1.0'); - }); - }); + describe('require("electron")', function () { + let window = null - describe('app.setVersion(version)', function() { - it('overrides the version', function() { - assert.equal(app.getVersion(), '0.1.0'); - app.setVersion('test-version'); - assert.equal(app.getVersion(), 'test-version'); - app.setVersion('0.1.0'); - }); - }); - - describe('app.getName()', function() { - it('returns the name field of package.json', function() { - assert.equal(app.getName(), 'Electron Test'); - }); - }); - - describe('app.setName(name)', function() { - it('overrides the name', function() { - assert.equal(app.getName(), 'Electron Test'); - app.setName('test-name'); - assert.equal(app.getName(), 'test-name'); - app.setName('Electron Test'); - }); - }); - - describe('app.getLocale()', function() { - it('should not be empty', function() { - assert.notEqual(app.getLocale(), ''); - }); - }); - - describe('app.exit(exitCode)', function() { - var appProcess = null; - - afterEach(function() { - appProcess != null ? appProcess.kill() : void 0; - }); - - it('emits a process exit event with the code', function(done) { - var appPath = path.join(__dirname, 'fixtures', 'api', 'quit-app'); - var electronPath = remote.getGlobal('process').execPath; - var output = ''; - appProcess = ChildProcess.spawn(electronPath, [appPath]); - appProcess.stdout.on('data', function(data) { - output += data; - }); - appProcess.on('close', function(code) { - if (process.platform !== 'win32') { - assert.notEqual(output.indexOf('Exit event with code: 123'), -1); - } - assert.equal(code, 123); - done(); - }); - }); - }); - - describe('BrowserWindow events', function() { - var w = null; - - afterEach(function() { - if (w != null) { - w.destroy(); + beforeEach(function () { + if (window != null) { + window.destroy() + } + window = new BrowserWindow({ + show: false, + width: 400, + height: 400 + }) + }) + + afterEach(function () { + if (window != null) { + window.destroy() + } + window = null + }) + + it('always returns the internal electron module', function (done) { + ipcMain.once('answer', function () { + done() + }) + window.loadURL('file://' + path.join(__dirname, 'fixtures', 'api', 'electron-module-app', 'index.html')) + }) + }) + +}) + +describe('app module', function () { + describe('app.getVersion()', function () { + it('returns the version field of package.json', function () { + assert.equal(app.getVersion(), '0.1.0') + }) + }) + + describe('app.setVersion(version)', function () { + it('overrides the version', function () { + assert.equal(app.getVersion(), '0.1.0') + app.setVersion('test-version') + assert.equal(app.getVersion(), 'test-version') + app.setVersion('0.1.0') + }) + }) + + describe('app.getName()', function () { + it('returns the name field of package.json', function () { + assert.equal(app.getName(), 'Electron Test') + }) + }) + + describe('app.setName(name)', function () { + it('overrides the name', function () { + assert.equal(app.getName(), 'Electron Test') + app.setName('test-name') + assert.equal(app.getName(), 'test-name') + app.setName('Electron Test') + }) + }) + + describe('app.getLocale()', function () { + it('should not be empty', function () { + assert.notEqual(app.getLocale(), '') + }) + }) + + describe('app.exit(exitCode)', function () { + var appProcess = null + + afterEach(function () { + appProcess != null ? appProcess.kill() : void 0 + }) + + it('emits a process exit event with the code', function (done) { + var appPath = path.join(__dirname, 'fixtures', 'api', 'quit-app') + var electronPath = remote.getGlobal('process').execPath + var output = '' + appProcess = ChildProcess.spawn(electronPath, [appPath]) + appProcess.stdout.on('data', function (data) { + output += data + }) + appProcess.on('close', function (code) { + if (process.platform !== 'win32') { + assert.notEqual(output.indexOf('Exit event with code: 123'), -1) + } + assert.equal(code, 123) + done() + }) + }) + }) + + describe('app.relaunch', function () { + let server = null + const socketPath = process.platform === 'win32' ? + '\\\\.\\pipe\\electron-app-relaunch' : + '/tmp/electron-app-relaunch' + + beforeEach(function (done) { + fs.unlink(socketPath, (error) => { + server = net.createServer() + server.listen(socketPath) + done() + }) + }) + + afterEach(function (done) { + server.close(() => { + if (process.platform === 'win32') { + done() + } else { + fs.unlink(socketPath, (error) => { + done() + }) + } + }) + }) + + it('relaunches the app', function (done) { + this.timeout(100000) + let state = 'none' + server.once('error', (error) => { + done(error) + }) + server.on('connection', (client) => { + client.once('data', function (data) { + if (String(data) === 'false' && state === 'none') { + state = 'first-launch' + } else if (String(data) === 'true' && state === 'first-launch') { + done() + } else { + done(`Unexpected state: ${state}`) + } + }) + }) + + const appPath = path.join(__dirname, 'fixtures', 'api', 'relaunch') + ChildProcess.spawn(remote.process.execPath, [appPath]) + }) + }) + + describe('app.setUserActivity(type, userInfo)', function () { + if (process.platform !== 'darwin') { + return + } + + it('sets the current activity', function () { + app.setUserActivity('com.electron.testActivity', {testData: '123'}); + assert.equal(app.getCurrentActivityType(), 'com.electron.testActivity'); + }) + }) + + describe('app.importCertificate', function () { + if (process.platform !== 'linux') + return + + this.timeout(5000) + + var w = null + var certPath = path.join(__dirname, 'fixtures', 'certificates') + var options = { + key: fs.readFileSync(path.join(certPath, 'server.key')), + cert: fs.readFileSync(path.join(certPath, 'server.pem')), + ca: [ + fs.readFileSync(path.join(certPath, 'rootCA.pem')), + fs.readFileSync(path.join(certPath, 'intermediateCA.pem')) + ], + requestCert: true, + rejectUnauthorized: false + } + + var server = https.createServer(options, function (req, res) { + if (req.client.authorized) { + res.writeHead(200); + res.end('authorized'); + } + }) + + afterEach(function () { + if (w != null) { + w.destroy() + } + w = null + }) + + it('can import certificate into platform cert store', function (done) { + let options = { + certificate: path.join(certPath, 'client.p12'), + password: 'electron' } - w = null; - }); - it('should emit browser-window-focus event when window is focused', function(done) { - app.once('browser-window-focus', function(e, window) { - assert.equal(w.id, window.id); - done(); - }); w = new BrowserWindow({ show: false - }); - w.emit('focus'); - }); + }) - it('should emit browser-window-blur event when window is blured', function(done) { - app.once('browser-window-blur', function(e, window) { - assert.equal(w.id, window.id); - done(); - }); + w.webContents.on('did-finish-load', function () { + server.close() + done() + }) + + app.on('select-client-certificate', function (event, webContents, url, list, callback) { + assert.equal(list.length, 1) + assert.equal(list[0].issuerName, 'Intermediate CA') + callback(list[0]) + }) + + app.importCertificate(options, function (result) { + assert(!result) + server.listen(0, '127.0.0.1', function () { + var port = server.address().port + w.loadURL(`https://127.0.0.1:${port}`) + }) + }) + }) + }) + + describe('BrowserWindow events', function () { + var w = null + + afterEach(function () { + if (w != null) { + w.destroy() + } + w = null + }) + + it('should emit browser-window-focus event when window is focused', function (done) { + app.once('browser-window-focus', function (e, window) { + assert.equal(w.id, window.id) + done() + }) w = new BrowserWindow({ show: false - }); - w.emit('blur'); - }); + }) + w.emit('focus') + }) - it('should emit browser-window-created event when window is created', function(done) { - app.once('browser-window-created', function(e, window) { - setImmediate(function() { - assert.equal(w.id, window.id); - done(); - }); - }); + it('should emit browser-window-blur event when window is blured', function (done) { + app.once('browser-window-blur', function (e, window) { + assert.equal(w.id, window.id) + done() + }) w = new BrowserWindow({ show: false - }); - w.emit('blur'); - }); - }); -}); + }) + w.emit('blur') + }) + + it('should emit browser-window-created event when window is created', function (done) { + app.once('browser-window-created', function (e, window) { + setImmediate(function () { + assert.equal(w.id, window.id) + done() + }) + }) + w = new BrowserWindow({ + show: false + }) + w.emit('blur') + }) + }) +}) diff --git a/spec/api-auto-updater-spec.js b/spec/api-auto-updater-spec.js index efd3afe3882f..6335f3359438 100644 --- a/spec/api-auto-updater-spec.js +++ b/spec/api-auto-updater-spec.js @@ -1,38 +1,37 @@ -const assert = require('assert'); -const autoUpdater = require('electron').remote.autoUpdater; -const ipcRenderer = require('electron').ipcRenderer; +const assert = require('assert') +const autoUpdater = require('electron').remote.autoUpdater +const ipcRenderer = require('electron').ipcRenderer // Skip autoUpdater tests in MAS build. -if (process.mas) - return; +if (!process.mas) { + describe('autoUpdater module', function () { + describe('checkForUpdates', function () { + it('emits an error on Windows when called the feed URL is not set', function (done) { + if (process.platform !== 'win32') { + return done() + } -describe('autoUpdater module', function() { - describe('checkForUpdates', function() { - it('emits an error on Windows when called the feed URL is not set', function (done) { - if (process.platform !== 'win32') { - return done(); - } + ipcRenderer.once('auto-updater-error', function (event, message) { + assert.equal(message, 'Update URL is not set') + done() + }) + autoUpdater.setFeedURL('') + autoUpdater.checkForUpdates() + }) + }) - ipcRenderer.once('auto-updater-error', function(event, message) { - assert.equal(message, 'Update URL is not set'); - done(); - }); - autoUpdater.setFeedURL(''); - autoUpdater.checkForUpdates(); - }); - }); + describe('setFeedURL', function () { + it('emits an error on Mac OS X when the application is unsigned', function (done) { + if (process.platform !== 'darwin') { + return done() + } - describe('setFeedURL', function() { - it('emits an error on Mac OS X when the application is unsigned', function (done) { - if (process.platform !== 'darwin') { - return done(); - } - - ipcRenderer.once('auto-updater-error', function(event, message) { - assert.equal(message, 'Could not get code signature for running application'); - done(); - }); - autoUpdater.setFeedURL(''); - }); - }); -}); + ipcRenderer.once('auto-updater-error', function (event, message) { + assert.equal(message, 'Could not get code signature for running application') + done() + }) + autoUpdater.setFeedURL('') + }) + }) + }) +} diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 58cf57cc8d0c..8af2fb826574 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -1,693 +1,940 @@ -'use strict'; +'use strict' -const assert = require('assert'); -const fs = require('fs'); -const path = require('path'); -const os = require('os'); +const assert = require('assert') +const fs = require('fs') +const path = require('path') +const os = require('os') +const http = require('http') -const remote = require('electron').remote; -const screen = require('electron').screen; +const remote = require('electron').remote +const screen = require('electron').screen -const app = remote.require('electron').app; -const ipcMain = remote.require('electron').ipcMain; -const BrowserWindow = remote.require('electron').BrowserWindow; +const app = remote.require('electron').app +const ipcMain = remote.require('electron').ipcMain +const ipcRenderer = require('electron').ipcRenderer +const BrowserWindow = remote.require('electron').BrowserWindow -const isCI = remote.getGlobal('isCi'); +const isCI = remote.getGlobal('isCi') -describe('browser-window module', function() { - var fixtures = path.resolve(__dirname, 'fixtures'); - var w = null; +describe('browser-window module', function () { + var fixtures = path.resolve(__dirname, 'fixtures') + var w = null + var server - beforeEach(function() { + before(function (done) { + server = http.createServer(function (req, res) { + function respond() { res.end(''); } + setTimeout(respond, req.url.includes('slow') ? 200 : 0) + }); + server.listen(0, '127.0.0.1', function () { + server.url = 'http://127.0.0.1:' + server.address().port + done() + }) + }) + + after(function () { + server.close() + server = null + }) + + beforeEach(function () { if (w != null) { - w.destroy(); + w.destroy() } w = new BrowserWindow({ show: false, width: 400, height: 400 - }); - }); + }) + }) - afterEach(function() { + afterEach(function () { if (w != null) { - w.destroy(); + w.destroy() } - w = null; - }); + w = null + }) - describe('BrowserWindow.close()', function() { - it('should emit unload handler', function(done) { - w.webContents.on('did-finish-load', function() { - w.close(); - }); - w.on('closed', function() { - var test = path.join(fixtures, 'api', 'unload'); - var content = fs.readFileSync(test); - fs.unlinkSync(test); - assert.equal(String(content), 'unload'); - done(); - }); - w.loadURL('file://' + path.join(fixtures, 'api', 'unload.html')); - }); + describe('BrowserWindow.close()', function () { + it('should emit unload handler', function (done) { + w.webContents.on('did-finish-load', function () { + w.close() + }) + w.on('closed', function () { + var test = path.join(fixtures, 'api', 'unload') + var content = fs.readFileSync(test) + fs.unlinkSync(test) + assert.equal(String(content), 'unload') + done() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'unload.html')) + }) - it('should emit beforeunload handler', function(done) { - w.on('onbeforeunload', function() { - done(); - }); - w.webContents.on('did-finish-load', function() { - w.close(); - }); - w.loadURL('file://' + path.join(fixtures, 'api', 'beforeunload-false.html')); - }); - }); + it('should emit beforeunload handler', function (done) { + w.on('onbeforeunload', function () { + done() + }) + w.webContents.on('did-finish-load', function () { + w.close() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'beforeunload-false.html')) + }) + }) - describe('window.close()', function() { - it('should emit unload handler', function(done) { - w.on('closed', function() { - var test = path.join(fixtures, 'api', 'close'); - var content = fs.readFileSync(test); - fs.unlinkSync(test); - assert.equal(String(content), 'close'); - done(); - }); - w.loadURL('file://' + path.join(fixtures, 'api', 'close.html')); - }); + describe('window.close()', function () { + it('should emit unload handler', function (done) { + w.on('closed', function () { + var test = path.join(fixtures, 'api', 'close') + var content = fs.readFileSync(test) + fs.unlinkSync(test) + assert.equal(String(content), 'close') + done() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'close.html')) + }) - it('should emit beforeunload handler', function(done) { - w.on('onbeforeunload', function() { - done(); - }); - w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html')); - }); - }); + it('should emit beforeunload handler', function (done) { + w.on('onbeforeunload', function () { + done() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html')) + }) + }) - describe('BrowserWindow.destroy()', function() { - it('prevents users to access methods of webContents', function() { - var webContents = w.webContents; - w.destroy(); - assert.throws((function() { - webContents.getId(); - }), /Object has been destroyed/); - }); - }); + describe('BrowserWindow.destroy()', function () { + it('prevents users to access methods of webContents', function () { + var webContents = w.webContents + w.destroy() + assert.throws(function () { + webContents.getId() + }, /Object has been destroyed/) + }) + }) - describe('BrowserWindow.loadURL(url)', function() { - it('should emit did-start-loading event', function(done) { - w.webContents.on('did-start-loading', function() { - done(); - }); - w.loadURL('about:blank'); - }); + describe('BrowserWindow.loadURL(url)', function () { + it('should emit did-start-loading event', function (done) { + w.webContents.on('did-start-loading', function () { + done() + }) + w.loadURL('about:blank') + }) - it('should emit did-fail-load event for files that do not exist', function(done) { - w.webContents.on('did-fail-load', function(event, code) { - assert.equal(code, -6); - done(); - }); - w.loadURL('file://a.txt'); - }); - - it('should emit did-fail-load event for invalid URL', function(done) { - w.webContents.on('did-fail-load', function(event, code, desc) { - assert.equal(desc, 'ERR_INVALID_URL'); - assert.equal(code, -300); - done(); - }); - w.loadURL('http://example:port'); - }); - }); - - describe('BrowserWindow.show()', function() { - it('should focus on window', function() { - if (isCI) { - return; + it('should emit did-get-response-details event', function (done) { + // expected {fileName: resourceType} pairs + var expectedResources = { + 'did-get-response-details.html': 'mainFrame', + 'logo.png': 'image' } + var responses = 0; + w.webContents.on('did-get-response-details', function (event, status, newUrl, oldUrl, responseCode, method, referrer, headers, resourceType) { + responses++ + var fileName = newUrl.slice(newUrl.lastIndexOf('/') + 1) + var expectedType = expectedResources[fileName] + assert(!!expectedType, `Unexpected response details for ${newUrl}`) + assert(typeof status === 'boolean', 'status should be boolean') + assert.equal(responseCode, 200) + assert.equal(method, 'GET') + assert(typeof referrer === 'string', 'referrer should be string') + assert(!!headers, 'headers should be present') + assert(typeof headers === 'object', 'headers should be object') + assert.equal(resourceType, expectedType, 'Incorrect resourceType') + if (responses === Object.keys(expectedResources).length) { + done() + } + }) + w.loadURL('file://' + path.join(fixtures, 'pages', 'did-get-response-details.html')) + }) - w.show(); - assert(w.isFocused()); - }); - }); + it('should emit did-fail-load event for files that do not exist', function (done) { + w.webContents.on('did-fail-load', function (event, code, desc, url, isMainFrame) { + assert.equal(code, -6) + assert.equal(desc, 'ERR_FILE_NOT_FOUND') + assert.equal(isMainFrame, true) + done() + }) + w.loadURL('file://a.txt') + }) - describe('BrowserWindow.showInactive()', function() { - it('should not focus on window', function() { - w.showInactive(); - assert(!w.isFocused()); - }); - }); + it('should emit did-fail-load event for invalid URL', function (done) { + w.webContents.on('did-fail-load', function (event, code, desc, url, isMainFrame) { + assert.equal(desc, 'ERR_INVALID_URL') + assert.equal(code, -300) + assert.equal(isMainFrame, true) + done() + }) + w.loadURL('http://example:port') + }) - describe('BrowserWindow.focus()', function() { - it('does not make the window become visible', function() { - assert.equal(w.isVisible(), false); - w.focus(); - assert.equal(w.isVisible(), false); - }); - }); + it('should set `mainFrame = false` on did-fail-load events in iframes', function (done) { + w.webContents.on('did-fail-load', function (event, code, desc, url, isMainFrame) { + assert.equal(isMainFrame, false) + done() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'did-fail-load-iframe.html')) + }) - describe('BrowserWindow.capturePage(rect, callback)', function() { - it('calls the callback with a Buffer', function(done) { + it('does not crash in did-fail-provisional-load handler', function (done) { + this.timeout(10000) + w.webContents.once('did-fail-provisional-load', function () { + w.loadURL('http://127.0.0.1:11111') + done() + }) + w.loadURL('http://127.0.0.1:11111') + }) + }) + + describe('BrowserWindow.show()', function () { + if (isCI) { + return + } + + it('should focus on window', function () { + w.show() + assert(w.isFocused()) + }) + + it('should make the window visible', function () { + w.show() + assert(w.isVisible()) + }) + + it('emits when window is shown', function (done) { + this.timeout(10000) + w.once('show', function () { + assert.equal(w.isVisible(), true) + done() + }) + w.show() + }) + }) + + describe('BrowserWindow.hide()', function () { + if (isCI) { + return + } + + it('should defocus on window', function () { + w.hide() + assert(!w.isFocused()) + }) + + it('should make the window not visible', function () { + w.show() + w.hide() + assert(!w.isVisible()) + }) + + it('emits when window is hidden', function (done) { + this.timeout(10000) + w.show() + w.once('hide', function () { + assert.equal(w.isVisible(), false) + done() + }) + w.hide() + }) + }) + + describe('BrowserWindow.showInactive()', function () { + it('should not focus on window', function () { + w.showInactive() + assert(!w.isFocused()) + }) + }) + + describe('BrowserWindow.focus()', function () { + it('does not make the window become visible', function () { + assert.equal(w.isVisible(), false) + w.focus() + assert.equal(w.isVisible(), false) + }) + }) + + describe('BrowserWindow.blur()', function () { + it('removes focus from window', function () { + w.blur() + assert(!w.isFocused()) + }) + }) + + describe('BrowserWindow.capturePage(rect, callback)', function () { + it('calls the callback with a Buffer', function (done) { w.capturePage({ x: 0, y: 0, width: 100, height: 100 - }, function(image) { - assert.equal(image.isEmpty(), true); - done(); - }); - }); - }); + }, function (image) { + assert.equal(image.isEmpty(), true) + done() + }) + }) + }) - describe('BrowserWindow.setSize(width, height)', function() { - it('sets the window size', function(done) { - var size = [300, 400]; - w.once('resize', function() { - var newSize = w.getSize(); - assert.equal(newSize[0], size[0]); - assert.equal(newSize[1], size[1]); - done(); - }); - w.setSize(size[0], size[1]); - }); - }); + describe('BrowserWindow.setSize(width, height)', function () { + it('sets the window size', function (done) { + var size = [300, 400] + w.once('resize', function () { + var newSize = w.getSize() + assert.equal(newSize[0], size[0]) + assert.equal(newSize[1], size[1]) + done() + }) + w.setSize(size[0], size[1]) + }) + }) - describe('BrowserWindow.setPosition(x, y)', function() { - it('sets the window position', function(done) { - var pos = [10, 10]; - w.once('move', function() { - var newPos = w.getPosition(); - assert.equal(newPos[0], pos[0]); - assert.equal(newPos[1], pos[1]); - done(); - }); - w.setPosition(pos[0], pos[1]); - }); - }); + describe('BrowserWindow.setAspectRatio(ratio)', function () { + it('resets the behaviour when passing in 0', function (done) { + var size = [300, 400] + w.setAspectRatio(1/2) + w.setAspectRatio(0) + w.once('resize', function () { + var newSize = w.getSize() + assert.equal(newSize[0], size[0]) + assert.equal(newSize[1], size[1]) + done() + }) + w.setSize(size[0], size[1]) + }) + }) - describe('BrowserWindow.setContentSize(width, height)', function() { - it('sets the content size', function() { - var size = [400, 400]; - w.setContentSize(size[0], size[1]); - var after = w.getContentSize(); - assert.equal(after[0], size[0]); - assert.equal(after[1], size[1]); - }); + describe('BrowserWindow.setPosition(x, y)', function () { + it('sets the window position', function (done) { + var pos = [10, 10] + w.once('move', function () { + var newPos = w.getPosition() + assert.equal(newPos[0], pos[0]) + assert.equal(newPos[1], pos[1]) + done() + }) + w.setPosition(pos[0], pos[1]) + }) + }) - it('works for framless window', function() { - w.destroy(); + describe('BrowserWindow.setContentSize(width, height)', function () { + it('sets the content size', function () { + var size = [400, 400] + w.setContentSize(size[0], size[1]) + var after = w.getContentSize() + assert.equal(after[0], size[0]) + assert.equal(after[1], size[1]) + }) + + it('works for framless window', function () { + w.destroy() w = new BrowserWindow({ show: false, frame: false, width: 400, height: 400 - }); - var size = [400, 400]; - w.setContentSize(size[0], size[1]); - var after = w.getContentSize(); - assert.equal(after[0], size[0]); - assert.equal(after[1], size[1]); - }); - }); + }) + var size = [400, 400] + w.setContentSize(size[0], size[1]) + var after = w.getContentSize() + assert.equal(after[0], size[0]) + assert.equal(after[1], size[1]) + }) + }) - describe('BrowserWindow.fromId(id)', function() { - it('returns the window with id', function() { - assert.equal(w.id, BrowserWindow.fromId(w.id).id); - }); - }); + describe('BrowserWindow.fromId(id)', function () { + it('returns the window with id', function () { + assert.equal(w.id, BrowserWindow.fromId(w.id).id) + }) + }) - describe('"useContentSize" option', function() { - it('make window created with content size when used', function() { - w.destroy(); + describe('"useContentSize" option', function () { + it('make window created with content size when used', function () { + w.destroy() w = new BrowserWindow({ show: false, width: 400, height: 400, useContentSize: true - }); - var contentSize = w.getContentSize(); - assert.equal(contentSize[0], 400); - assert.equal(contentSize[1], 400); - }); + }) + var contentSize = w.getContentSize() + assert.equal(contentSize[0], 400) + assert.equal(contentSize[1], 400) + }) - it('make window created with window size when not used', function() { - var size = w.getSize(); - assert.equal(size[0], 400); - assert.equal(size[1], 400); - }); + it('make window created with window size when not used', function () { + var size = w.getSize() + assert.equal(size[0], 400) + assert.equal(size[1], 400) + }) - it('works for framless window', function() { - w.destroy(); + it('works for framless window', function () { + w.destroy() w = new BrowserWindow({ show: false, frame: false, width: 400, height: 400, useContentSize: true - }); - var contentSize = w.getContentSize(); - assert.equal(contentSize[0], 400); - assert.equal(contentSize[1], 400); - var size = w.getSize(); - assert.equal(size[0], 400); - assert.equal(size[1], 400); - }); - }); + }) + var contentSize = w.getContentSize() + assert.equal(contentSize[0], 400) + assert.equal(contentSize[1], 400) + var size = w.getSize() + assert.equal(size[0], 400) + assert.equal(size[1], 400) + }) + }) - describe('"title-bar-style" option', function() { + describe('"title-bar-style" option', function () { if (process.platform !== 'darwin') { - return; + return } if (parseInt(os.release().split('.')[0]) < 14) { - return; + return } - it('creates browser window with hidden title bar', function() { - w.destroy(); + it('creates browser window with hidden title bar', function () { + w.destroy() w = new BrowserWindow({ show: false, width: 400, height: 400, titleBarStyle: 'hidden' - }); - var contentSize = w.getContentSize(); - assert.equal(contentSize[1], 400); - }); + }) + var contentSize = w.getContentSize() + assert.equal(contentSize[1], 400) + }) - it('creates browser window with hidden inset title bar', function() { - w.destroy(); + it('creates browser window with hidden inset title bar', function () { + w.destroy() w = new BrowserWindow({ show: false, width: 400, height: 400, titleBarStyle: 'hidden-inset' - }); - var contentSize = w.getContentSize(); - assert.equal(contentSize[1], 400); - }); - }); + }) + var contentSize = w.getContentSize() + assert.equal(contentSize[1], 400) + }) + }) - describe('"enableLargerThanScreen" option', function() { + describe('"enableLargerThanScreen" option', function () { if (process.platform === 'linux') { - return; + return } - beforeEach(function() { - w.destroy(); + beforeEach(function () { + w.destroy() w = new BrowserWindow({ show: true, width: 400, height: 400, enableLargerThanScreen: true - }); - }); + }) + }) - it('can move the window out of screen', function() { - w.setPosition(-10, -10); - var after = w.getPosition(); - assert.equal(after[0], -10); - assert.equal(after[1], -10); - }); + it('can move the window out of screen', function () { + w.setPosition(-10, -10) + var after = w.getPosition() + assert.equal(after[0], -10) + assert.equal(after[1], -10) + }) - it('can set the window larger than screen', function() { - var size = screen.getPrimaryDisplay().size; - size.width += 100; - size.height += 100; - w.setSize(size.width, size.height); - var after = w.getSize(); - assert.equal(after[0], size.width); - assert.equal(after[1], size.height); - }); - }); + it('can set the window larger than screen', function () { + var size = screen.getPrimaryDisplay().size + size.width += 100 + size.height += 100 + w.setSize(size.width, size.height) + var after = w.getSize() + assert.equal(after[0], size.width) + assert.equal(after[1], size.height) + }) + }) - describe('"web-preferences" option', function() { - afterEach(function() { - ipcMain.removeAllListeners('answer'); - }); + describe('"web-preferences" option', function () { + afterEach(function () { + ipcMain.removeAllListeners('answer') + }) - describe('"preload" option', function() { - it('loads the script before other scripts in window', function(done) { - var preload = path.join(fixtures, 'module', 'set-global.js'); - ipcMain.once('answer', function(event, test) { - assert.equal(test, 'preload'); - done(); - }); - w.destroy(); + describe('"preload" option', function () { + it('loads the script before other scripts in window', function (done) { + var preload = path.join(fixtures, 'module', 'set-global.js') + ipcMain.once('answer', function (event, test) { + assert.equal(test, 'preload') + done() + }) + w.destroy() w = new BrowserWindow({ show: false, webPreferences: { preload: preload } - }); - w.loadURL('file://' + path.join(fixtures, 'api', 'preload.html')); - }); - }); + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'preload.html')) + }) + }) - describe('"node-integration" option', function() { - it('disables node integration when specified to false', function(done) { - var preload = path.join(fixtures, 'module', 'send-later.js'); - ipcMain.once('answer', function(event, test) { - assert.equal(test, 'undefined'); - done(); - }); - w.destroy(); + describe('"node-integration" option', function () { + it('disables node integration when specified to false', function (done) { + var preload = path.join(fixtures, 'module', 'send-later.js') + ipcMain.once('answer', function (event, test) { + assert.equal(test, 'undefined') + done() + }) + w.destroy() w = new BrowserWindow({ show: false, webPreferences: { preload: preload, nodeIntegration: false } - }); - w.loadURL('file://' + path.join(fixtures, 'api', 'blank.html')); - }); - }); - }); + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'blank.html')) + }) + }) + }) - describe('beforeunload handler', function() { - it('returning true would not prevent close', function(done) { - w.on('closed', function() { - done(); - }); - w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-true.html')); - }); + describe('beforeunload handler', function () { + it('returning undefined would not prevent close', function (done) { + w.on('closed', function () { + done() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-undefined.html')) + }) - it('returning non-empty string would not prevent close', function(done) { - w.on('closed', function() { - done(); - }); - w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-string.html')); - }); + it('returning false would prevent close', function (done) { + w.on('onbeforeunload', function () { + done() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html')) + }) - it('returning false would prevent close', function(done) { - w.on('onbeforeunload', function() { - done(); - }); - w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html')); - }); + it('returning empty string would prevent close', function (done) { + w.on('onbeforeunload', function () { + done() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-empty-string.html')) + }) + }) - it('returning empty string would prevent close', function(done) { - w.on('onbeforeunload', function() { - done(); - }); - w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-empty-string.html')); - }); - }); - - describe('new-window event', function() { + describe('new-window event', function () { if (isCI && process.platform === 'darwin') { - return; + return } - it('emits when window.open is called', function(done) { - w.webContents.once('new-window', function(e, url, frameName) { - e.preventDefault(); - assert.equal(url, 'http://host/'); - assert.equal(frameName, 'host'); - done(); - }); - w.loadURL("file://" + fixtures + "/pages/window-open.html"); - }); + it('emits when window.open is called', function (done) { + w.webContents.once('new-window', function (e, url, frameName) { + e.preventDefault() + assert.equal(url, 'http://host/') + assert.equal(frameName, 'host') + done() + }) + w.loadURL('file://' + fixtures + '/pages/window-open.html') + }) - it('emits when link with target is called', function(done) { - this.timeout(10000); - w.webContents.once('new-window', function(e, url, frameName) { - e.preventDefault(); - assert.equal(url, 'http://host/'); - assert.equal(frameName, 'target'); - done(); - }); - w.loadURL("file://" + fixtures + "/pages/target-name.html"); - }); - }); + it('emits when link with target is called', function (done) { + this.timeout(10000) + w.webContents.once('new-window', function (e, url, frameName) { + e.preventDefault() + assert.equal(url, 'http://host/') + assert.equal(frameName, 'target') + done() + }) + w.loadURL('file://' + fixtures + '/pages/target-name.html') + }) + }) - describe('maximize event', function() { + describe('maximize event', function () { if (isCI) { - return; + return } - it('emits when window is maximized', function(done) { - this.timeout(10000); - w.once('maximize', function() { - done(); - }); - w.show(); - w.maximize(); - }); - }); + it('emits when window is maximized', function (done) { + this.timeout(10000) + w.once('maximize', function () { + done() + }) + w.show() + w.maximize() + }) + }) - describe('unmaximize event', function() { + describe('unmaximize event', function () { if (isCI) { - return; + return } - it('emits when window is unmaximized', function(done) { - this.timeout(10000); - w.once('unmaximize', function() { - done(); - }); - w.show(); - w.maximize(); - w.unmaximize(); - }); - }); + it('emits when window is unmaximized', function (done) { + this.timeout(10000) + w.once('unmaximize', function () { + done() + }) + w.show() + w.maximize() + w.unmaximize() + }) + }) - describe('minimize event', function() { + describe('minimize event', function () { if (isCI) { - return; + return } - it('emits when window is minimized', function(done) { - this.timeout(10000); - w.once('minimize', function() { - done(); - }); - w.show(); - w.minimize(); - }); - }); + it('emits when window is minimized', function (done) { + this.timeout(10000) + w.once('minimize', function () { + done() + }) + w.show() + w.minimize() + }) + }) - describe('beginFrameSubscription method', function() { - this.timeout(20000); + describe('beginFrameSubscription method', function () { + this.timeout(20000) - it('subscribes frame updates', function(done) { - let called = false; - w.loadURL("file://" + fixtures + "/api/blank.html"); - w.webContents.beginFrameSubscription(function(data) { + it('subscribes frame updates', function (done) { + let called = false + w.loadURL('file://' + fixtures + '/api/blank.html') + w.webContents.beginFrameSubscription(function (data) { // This callback might be called twice. - if (called) - return; - called = true; + if (called) return + called = true - assert.notEqual(data.length, 0); - w.webContents.endFrameSubscription(); - done(); - }); - }); - }); + assert.notEqual(data.length, 0) + w.webContents.endFrameSubscription() + done() + }) + }) + }) - describe('savePage method', function() { - const savePageDir = path.join(fixtures, 'save_page'); - const savePageHtmlPath = path.join(savePageDir, 'save_page.html'); - const savePageJsPath = path.join(savePageDir, 'save_page_files', 'test.js'); - const savePageCssPath = path.join(savePageDir, 'save_page_files', 'test.css'); + describe('savePage method', function () { + const savePageDir = path.join(fixtures, 'save_page') + const savePageHtmlPath = path.join(savePageDir, 'save_page.html') + const savePageJsPath = path.join(savePageDir, 'save_page_files', 'test.js') + const savePageCssPath = path.join(savePageDir, 'save_page_files', 'test.css') - after(function() { + after(function () { try { - fs.unlinkSync(savePageCssPath); - fs.unlinkSync(savePageJsPath); - fs.unlinkSync(savePageHtmlPath); - fs.rmdirSync(path.join(savePageDir, 'save_page_files')); - fs.rmdirSync(savePageDir); + fs.unlinkSync(savePageCssPath) + fs.unlinkSync(savePageJsPath) + fs.unlinkSync(savePageHtmlPath) + fs.rmdirSync(path.join(savePageDir, 'save_page_files')) + fs.rmdirSync(savePageDir) } catch (e) { // Ignore error } - }); + }) - it('should save page to disk', function(done) { - w.webContents.on('did-finish-load', function() { - w.webContents.savePage(savePageHtmlPath, 'HTMLComplete', function(error) { - assert.equal(error, null); - assert(fs.existsSync(savePageHtmlPath)); - assert(fs.existsSync(savePageJsPath)); - assert(fs.existsSync(savePageCssPath)); - done(); - }); - }); - w.loadURL("file://" + fixtures + "/pages/save_page/index.html"); - }); - }); + it('should save page to disk', function (done) { + w.webContents.on('did-finish-load', function () { + w.webContents.savePage(savePageHtmlPath, 'HTMLComplete', function (error) { + assert.equal(error, null) + assert(fs.existsSync(savePageHtmlPath)) + assert(fs.existsSync(savePageJsPath)) + assert(fs.existsSync(savePageCssPath)) + done() + }) + }) + w.loadURL('file://' + fixtures + '/pages/save_page/index.html') + }) + }) - describe('BrowserWindow options argument is optional', function() { - it('should create a window with default size (800x600)', function() { - w.destroy(); - w = new BrowserWindow(); - var size = w.getSize(); - assert.equal(size[0], 800); - assert.equal(size[1], 600); - }); - }); + describe('BrowserWindow options argument is optional', function () { + it('should create a window with default size (800x600)', function () { + w.destroy() + w = new BrowserWindow() + var size = w.getSize() + assert.equal(size[0], 800) + assert.equal(size[1], 600) + }) + }) - describe('window states', function() { + describe('window states', function () { + describe('resizable state', function () { + it('can be changed with resizable option', function () { + w.destroy() + w = new BrowserWindow({show: false, resizable: false}) + assert.equal(w.isResizable(), false) + }) + + it('can be changed with setResizable method', function () { + assert.equal(w.isResizable(), true) + w.setResizable(false) + assert.equal(w.isResizable(), false) + w.setResizable(true) + assert.equal(w.isResizable(), true) + }) + }) + + describe('loading main frame state', function () { + it('is true when the main frame is loading', function (done) { + w.webContents.on('did-start-loading', function() { + assert.equal(w.webContents.isLoadingMainFrame(), true) + done() + }) + w.webContents.loadURL(server.url) + }) + + it('is false when only a subframe is loading', function (done) { + w.webContents.once('did-finish-load', function() { + assert.equal(w.webContents.isLoadingMainFrame(), false) + w.webContents.on('did-start-loading', function() { + assert.equal(w.webContents.isLoadingMainFrame(), false) + done() + }) + w.webContents.executeJavaScript(` + var iframe = document.createElement('iframe') + iframe.src = '${server.url}/page2' + document.body.appendChild(iframe) + `) + }) + w.webContents.loadURL(server.url) + }) + + it('is true when navigating to pages from the same origin', function (done) { + w.webContents.once('did-finish-load', function() { + assert.equal(w.webContents.isLoadingMainFrame(), false) + w.webContents.on('did-start-loading', function() { + assert.equal(w.webContents.isLoadingMainFrame(), true) + done() + }) + w.webContents.loadURL(`${server.url}/page2`) + }) + w.webContents.loadURL(server.url) + }) + }) + }) + + describe('window states (excluding Linux)', function () { // Not implemented on Linux. - if (process.platform == 'linux') - return; + if (process.platform === 'linux') return - describe('movable state', function() { - it('can be changed with movable option', function() { - w.destroy(); - w = new BrowserWindow({show: false, movable: false}); - assert.equal(w.isMovable(), false); - }); + describe('movable state', function () { + it('can be changed with movable option', function () { + w.destroy() + w = new BrowserWindow({show: false, movable: false}) + assert.equal(w.isMovable(), false) + }) - it('can be changed with setMovable method', function() { - assert.equal(w.isMovable(), true); - w.setMovable(false); - assert.equal(w.isMovable(), false); - w.setMovable(true); - assert.equal(w.isMovable(), true); - }); - }); + it('can be changed with setMovable method', function () { + assert.equal(w.isMovable(), true) + w.setMovable(false) + assert.equal(w.isMovable(), false) + w.setMovable(true) + assert.equal(w.isMovable(), true) + }) + }) - describe('minimizable state', function() { - it('can be changed with minimizable option', function() { - w.destroy(); - w = new BrowserWindow({show: false, minimizable: false}); - assert.equal(w.isMinimizable(), false); - }); + describe('minimizable state', function () { + it('can be changed with minimizable option', function () { + w.destroy() + w = new BrowserWindow({show: false, minimizable: false}) + assert.equal(w.isMinimizable(), false) + }) - it('can be changed with setMinimizable method', function() { - assert.equal(w.isMinimizable(), true); - w.setMinimizable(false); - assert.equal(w.isMinimizable(), false); - w.setMinimizable(true); - assert.equal(w.isMinimizable(), true); - }); - }); + it('can be changed with setMinimizable method', function () { + assert.equal(w.isMinimizable(), true) + w.setMinimizable(false) + assert.equal(w.isMinimizable(), false) + w.setMinimizable(true) + assert.equal(w.isMinimizable(), true) + }) + }) - describe('maximizable state', function() { - it('can be changed with maximizable option', function() { - w.destroy(); - w = new BrowserWindow({show: false, maximizable: false}); - assert.equal(w.isMaximizable(), false); - }); + describe('maximizable state', function () { + it('can be changed with maximizable option', function () { + w.destroy() + w = new BrowserWindow({show: false, maximizable: false}) + assert.equal(w.isMaximizable(), false) + }) - it('can be changed with setMaximizable method', function() { - assert.equal(w.isMaximizable(), true); - w.setMaximizable(false); - assert.equal(w.isMaximizable(), false); - w.setMaximizable(true); - assert.equal(w.isMaximizable(), true); - }); + it('can be changed with setMaximizable method', function () { + assert.equal(w.isMaximizable(), true) + w.setMaximizable(false) + assert.equal(w.isMaximizable(), false) + w.setMaximizable(true) + assert.equal(w.isMaximizable(), true) + }) - it('is not affected when changing other states', function() { - w.setMaximizable(false); - assert.equal(w.isMaximizable(), false); - w.setMinimizable(false); - assert.equal(w.isMaximizable(), false); - w.setClosable(false); - assert.equal(w.isMaximizable(), false); - }); - }); + it('is not affected when changing other states', function () { + w.setMaximizable(false) + assert.equal(w.isMaximizable(), false) + w.setMinimizable(false) + assert.equal(w.isMaximizable(), false) + w.setClosable(false) + assert.equal(w.isMaximizable(), false) + }) + }) - describe('fullscreenable state', function() { + describe('fullscreenable state', function () { // Only implemented on OS X. - if (process.platform != 'darwin') - return; + if (process.platform !== 'darwin') return - it('can be changed with fullscreenable option', function() { - w.destroy(); - w = new BrowserWindow({show: false, fullscreenable: false}); - assert.equal(w.isFullScreenable(), false); - }); + it('can be changed with fullscreenable option', function () { + w.destroy() + w = new BrowserWindow({show: false, fullscreenable: false}) + assert.equal(w.isFullScreenable(), false) + }) - it('can be changed with setFullScreenable method', function() { - assert.equal(w.isFullScreenable(), true); - w.setFullScreenable(false); - assert.equal(w.isFullScreenable(), false); - w.setFullScreenable(true); - assert.equal(w.isFullScreenable(), true); - }); - }); + it('can be changed with setFullScreenable method', function () { + assert.equal(w.isFullScreenable(), true) + w.setFullScreenable(false) + assert.equal(w.isFullScreenable(), false) + w.setFullScreenable(true) + assert.equal(w.isFullScreenable(), true) + }) + }) - describe('closable state', function() { - it('can be changed with closable option', function() { - w.destroy(); - w = new BrowserWindow({show: false, closable: false}); - assert.equal(w.isClosable(), false); - }); + describe('closable state', function () { + it('can be changed with closable option', function () { + w.destroy() + w = new BrowserWindow({show: false, closable: false}) + assert.equal(w.isClosable(), false) + }) - it('can be changed with setClosable method', function() { - assert.equal(w.isClosable(), true); - w.setClosable(false); - assert.equal(w.isClosable(), false); - w.setClosable(true); - assert.equal(w.isClosable(), true); - }); - }); + it('can be changed with setClosable method', function () { + assert.equal(w.isClosable(), true) + w.setClosable(false) + assert.equal(w.isClosable(), false) + w.setClosable(true) + assert.equal(w.isClosable(), true) + }) + }) - describe('resizable state', function() { - it('can be changed with resizable option', function() { - w.destroy(); - w = new BrowserWindow({show: false, resizable: false}); - assert.equal(w.isResizable(), false); - }); - - it('can be changed with setResizable method', function() { - assert.equal(w.isResizable(), true); - w.setResizable(false); - assert.equal(w.isResizable(), false); - w.setResizable(true); - assert.equal(w.isResizable(), true); - }); - }); - - describe('hasShadow state', function() { + describe('hasShadow state', function () { // On Window there is no shadow by default and it can not be changed // dynamically. - it('can be changed with hasShadow option', function() { - w.destroy(); - let hasShadow = process.platform == 'darwin' ? false : true; - w = new BrowserWindow({show: false, hasShadow: hasShadow}); - assert.equal(w.hasShadow(), hasShadow); - }); + it('can be changed with hasShadow option', function () { + w.destroy() + let hasShadow = process.platform !== 'darwin' + w = new BrowserWindow({show: false, hasShadow: hasShadow}) + assert.equal(w.hasShadow(), hasShadow) + }) - it('can be changed with setHasShadow method', function() { - if (process.platform != 'darwin') - return; + it('can be changed with setHasShadow method', function () { + if (process.platform !== 'darwin') return - assert.equal(w.hasShadow(), true); - w.setHasShadow(false); - assert.equal(w.hasShadow(), false); - w.setHasShadow(true); - assert.equal(w.hasShadow(), true); - }); - }); - }); + assert.equal(w.hasShadow(), true) + w.setHasShadow(false) + assert.equal(w.hasShadow(), false) + w.setHasShadow(true) + assert.equal(w.hasShadow(), true) + }) + }) + }) - describe('window.webContents.send(channel, args...)', function() { - it('throws an error when the channel is missing', function() { - assert.throws(function() { - w.webContents.send(); - }, 'Missing required channel argument'); + describe('window.webContents.send(channel, args...)', function () { + it('throws an error when the channel is missing', function () { + assert.throws(function () { + w.webContents.send() + }, 'Missing required channel argument') - assert.throws(function() { - w.webContents.send(null); - }, 'Missing required channel argument'); - }); - }); + assert.throws(function () { + w.webContents.send(null) + }, 'Missing required channel argument') + }) + }) describe('dev tool extensions', function () { + describe('BrowserWindow.addDevToolsExtension', function () { + this.timeout(10000) + + beforeEach(function () { + BrowserWindow.removeDevToolsExtension('foo') + + var extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', 'foo') + BrowserWindow.addDevToolsExtension(extensionPath) + + w.webContents.on('devtools-opened', function () { + var showPanelIntevalId = setInterval(function () { + if (w && w.devToolsWebContents) { + w.devToolsWebContents.executeJavaScript('(' + (function () { + var lastPanelId = WebInspector.inspectorView._tabbedPane._tabs.peekLast().id + WebInspector.inspectorView.showPanel(lastPanelId) + }).toString() + ')()') + } else { + clearInterval(showPanelIntevalId) + } + }, 100) + }) + + w.loadURL('about:blank') + }) + + describe('when the devtools is docked', function () { + it('creates the extension', function (done) { + w.webContents.openDevTools({mode: 'bottom'}) + + ipcMain.once('answer', function (event, message) { + assert.equal(message, 'extension loaded') + done() + }) + }) + }) + + describe('when the devtools is undocked', function () { + it('creates the extension', function (done) { + w.webContents.openDevTools({mode: 'undocked'}) + + ipcMain.once('answer', function (event, message) { + assert.equal(message, 'extension loaded') + done() + }) + }) + }) + }) + it('serializes the registered extensions on quit', function () { - var extensionName = 'foo'; - var extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', extensionName); - var serializedPath = path.join(app.getPath('userData'), 'DevTools Extensions'); + var extensionName = 'foo' + var extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', extensionName) + var serializedPath = path.join(app.getPath('userData'), 'DevTools Extensions') - BrowserWindow.addDevToolsExtension(extensionPath); - app.emit('will-quit'); - assert.deepEqual(JSON.parse(fs.readFileSync(serializedPath)), [extensionPath]); + BrowserWindow.addDevToolsExtension(extensionPath) + app.emit('will-quit') + assert.deepEqual(JSON.parse(fs.readFileSync(serializedPath)), [extensionPath]) - BrowserWindow.removeDevToolsExtension(extensionName); - app.emit('will-quit'); - assert.equal(fs.existsSync(serializedPath), false); - }); - }); -}); + BrowserWindow.removeDevToolsExtension(extensionName) + app.emit('will-quit') + assert.equal(fs.existsSync(serializedPath), false) + }) + }) + + describe('window.webContents.executeJavaScript', function () { + var expected = 'hello, world!' + var code = '(() => "' + expected + '")()' + + it('doesnt throw when no calback is provided', function () { + const result = ipcRenderer.sendSync('executeJavaScript', code, false) + assert.equal(result, 'success') + }) + + it('returns result when calback is provided', function (done) { + ipcRenderer.send('executeJavaScript', code, true) + ipcRenderer.once('executeJavaScript-response', function (event, result) { + assert.equal(result, expected) + done() + }) + }) + + it('works after page load and during subframe load', function (done) { + w.webContents.once('did-finish-load', function() { + // initiate a sub-frame load, then try and execute script during it + w.webContents.executeJavaScript(` + var iframe = document.createElement('iframe') + iframe.src = '${server.url}/slow' + document.body.appendChild(iframe) + `, function() { + w.webContents.executeJavaScript(`console.log('hello')`, function() { + done() + }) + }) + }) + w.loadURL(server.url) + }) + + it('executes after page load', function (done) { + w.webContents.executeJavaScript(code, function(result) { + assert.equal(result, expected) + done() + }) + w.loadURL(server.url) + }) + }) +}) diff --git a/spec/api-clipboard-spec.js b/spec/api-clipboard-spec.js index 0b4a9f9521cf..344e01452fba 100644 --- a/spec/api-clipboard-spec.js +++ b/spec/api-clipboard-spec.js @@ -1,63 +1,63 @@ -const assert = require('assert'); -const path = require('path'); +const assert = require('assert') +const path = require('path') -const clipboard = require('electron').clipboard; -const nativeImage = require('electron').nativeImage; +const clipboard = require('electron').clipboard +const nativeImage = require('electron').nativeImage -describe('clipboard module', function() { - var fixtures = path.resolve(__dirname, 'fixtures'); +describe('clipboard module', function () { + var fixtures = path.resolve(__dirname, 'fixtures') - describe('clipboard.readImage()', function() { - it('returns NativeImage intance', function() { - var p = path.join(fixtures, 'assets', 'logo.png'); - var i = nativeImage.createFromPath(p); - clipboard.writeImage(p); - assert.equal(clipboard.readImage().toDataURL(), i.toDataURL()); - }); - }); + describe('clipboard.readImage()', function () { + it('returns NativeImage intance', function () { + var p = path.join(fixtures, 'assets', 'logo.png') + var i = nativeImage.createFromPath(p) + clipboard.writeImage(p) + assert.equal(clipboard.readImage().toDataURL(), i.toDataURL()) + }) + }) - describe('clipboard.readText()', function() { - it('returns unicode string correctly', function() { - var text = '千江有水千江月,万里无云万里天'; - clipboard.writeText(text); - assert.equal(clipboard.readText(), text); - }); - }); + describe('clipboard.readText()', function () { + it('returns unicode string correctly', function () { + var text = '千江有水千江月,万里无云万里天' + clipboard.writeText(text) + assert.equal(clipboard.readText(), text) + }) + }) - describe('clipboard.readHtml()', function() { - it('returns markup correctly', function() { - var text = 'Hi'; - var markup = process.platform === 'darwin' ? 'Hi' : process.platform === 'linux' ? 'Hi' : 'Hi'; - clipboard.writeHtml(text); - assert.equal(clipboard.readHtml(), markup); - }); - }); + describe('clipboard.readHTML()', function () { + it('returns markup correctly', function () { + var text = 'Hi' + var markup = process.platform === 'darwin' ? "Hi" : process.platform === 'linux' ? 'Hi' : 'Hi' + clipboard.writeHTML(text) + assert.equal(clipboard.readHTML(), markup) + }) + }) - describe('clipboard.readRtf', function() { - it('returns rtf text correctly', function() { - var rtf = "{\\rtf1\\ansi{\\fonttbl\\f0\\fswiss Helvetica;}\\f0\\pard\nThis is some {\\b bold} text.\\par\n}"; - clipboard.writeRtf(rtf); - assert.equal(clipboard.readRtf(), rtf); - }); - }); + describe('clipboard.readRTF', function () { + it('returns rtf text correctly', function () { + var rtf = '{\\rtf1\\ansi{\\fonttbl\\f0\\fswiss Helvetica;}\\f0\\pard\nThis is some {\\b bold} text.\\par\n}' + clipboard.writeRTF(rtf) + assert.equal(clipboard.readRTF(), rtf) + }) + }) - describe('clipboard.write()', function() { - it('returns data correctly', function() { - var text = 'test'; - var rtf = '{\\rtf1\\utf8 text}'; - var p = path.join(fixtures, 'assets', 'logo.png'); - var i = nativeImage.createFromPath(p); - var markup = process.platform === 'darwin' ? 'Hi' : process.platform === 'linux' ? 'Hi' : 'Hi'; + describe('clipboard.write()', function () { + it('returns data correctly', function () { + var text = 'test' + var rtf = '{\\rtf1\\utf8 text}' + var p = path.join(fixtures, 'assets', 'logo.png') + var i = nativeImage.createFromPath(p) + var markup = process.platform === 'darwin' ? "Hi" : process.platform === 'linux' ? 'Hi' : 'Hi' clipboard.write({ - text: "test", + text: 'test', html: 'Hi', rtf: '{\\rtf1\\utf8 text}', image: p - }); - assert.equal(clipboard.readText(), text); - assert.equal(clipboard.readHtml(), markup); - assert.equal(clipboard.readRtf(), rtf); - assert.equal(clipboard.readImage().toDataURL(), i.toDataURL()); - }); - }); -}); + }) + assert.equal(clipboard.readText(), text) + assert.equal(clipboard.readHTML(), markup) + assert.equal(clipboard.readRTF(), rtf) + assert.equal(clipboard.readImage().toDataURL(), i.toDataURL()) + }) + }) +}) diff --git a/spec/api-crash-reporter-spec.js b/spec/api-crash-reporter-spec.js index 68dc1375fc39..b4f63fe1ef27 100644 --- a/spec/api-crash-reporter-spec.js +++ b/spec/api-crash-reporter-spec.js @@ -1,93 +1,92 @@ -const assert = require('assert'); -const http = require('http'); -const multiparty = require('multiparty'); -const path = require('path'); -const url = require('url'); +const assert = require('assert') +const http = require('http') +const multiparty = require('multiparty') +const path = require('path') +const url = require('url') -const remote = require('electron').remote; -const app = remote.require('electron').app; -const crashReporter = remote.require('electron').crashReporter; -const BrowserWindow = remote.require('electron').BrowserWindow; +const remote = require('electron').remote +const app = remote.require('electron').app +const crashReporter = remote.require('electron').crashReporter +const BrowserWindow = remote.require('electron').BrowserWindow -describe('crash-reporter module', function() { - var fixtures = path.resolve(__dirname, 'fixtures'); - var w = null; +describe('crash-reporter module', function () { + var fixtures = path.resolve(__dirname, 'fixtures') + var w = null - beforeEach(function() { + beforeEach(function () { w = new BrowserWindow({ show: false - }); - }); + }) + }) - afterEach(function() { - w.destroy(); - }); + afterEach(function () { + w.destroy() + }) if (process.mas) { - return; + return } - var isCI = remote.getGlobal('isCi'); + var isCI = remote.getGlobal('isCi') if (isCI) { - return; + return } - it('should send minidump when renderer crashes', function(done) { - this.timeout(120000); + it('should send minidump when renderer crashes', function (done) { + this.timeout(120000) - var called = false; - var server = http.createServer(function(req, res) { - server.close(); - var form = new multiparty.Form(); - form.parse(req, function(error, fields) { - if (called) { - return; - } - called = true; - assert.equal(fields['prod'], 'Electron'); - assert.equal(fields['ver'], process.versions['electron']); - assert.equal(fields['process_type'], 'renderer'); - assert.equal(fields['platform'], process.platform); - assert.equal(fields['extra1'], 'extra1'); - assert.equal(fields['extra2'], 'extra2'); - assert.equal(fields['_productName'], 'Zombies'); - assert.equal(fields['_companyName'], 'Umbrella Corporation'); - assert.equal(fields['_version'], app.getVersion()); - res.end('abc-123-def'); - done(); - }); - }); - var port = remote.process.port; - server.listen(port, '127.0.0.1', function() { - port = server.address().port; - remote.process.port = port; + var called = false + var server = http.createServer(function (req, res) { + server.close() + var form = new multiparty.Form() + form.parse(req, function (error, fields) { + if (error) throw error + if (called) return + called = true + assert.equal(fields['prod'], 'Electron') + assert.equal(fields['ver'], process.versions.electron) + assert.equal(fields['process_type'], 'renderer') + assert.equal(fields['platform'], process.platform) + assert.equal(fields['extra1'], 'extra1') + assert.equal(fields['extra2'], 'extra2') + assert.equal(fields['_productName'], 'Zombies') + assert.equal(fields['_companyName'], 'Umbrella Corporation') + assert.equal(fields['_version'], app.getVersion()) + res.end('abc-123-def') + done() + }) + }) + var port = remote.process.port + server.listen(port, '127.0.0.1', function () { + port = server.address().port + remote.process.port = port const crashUrl = url.format({ protocol: 'file', pathname: path.join(fixtures, 'api', 'crash.html'), - search: "?port=" + port - }); + search: '?port=' + port + }) if (process.platform === 'darwin') { crashReporter.start({ companyName: 'Umbrella Corporation', - submitURL: "http://127.0.0.1:" + port - }); + submitURL: 'http://127.0.0.1:' + port + }) } - w.loadURL(crashUrl); - }); - }); + w.loadURL(crashUrl) + }) + }) - describe(".start(options)", function() { - it('requires that the companyName and submitURL options be specified', function() { - assert.throws(function() { + describe('.start(options)', function () { + it('requires that the companyName and submitURL options be specified', function () { + assert.throws(function () { crashReporter.start({ companyName: 'Missing submitURL' - }); - }); - assert.throws(function() { + }) + }, /submitURL is a required option to crashReporter\.start/) + assert.throws(function () { crashReporter.start({ submitURL: 'Missing companyName' - }); - }); - }); - }); -}); + }) + }, /companyName is a required option to crashReporter\.start/) + }) + }) +}) diff --git a/spec/api-debugger-spec.js b/spec/api-debugger-spec.js index 56b642e76e43..27aacc7671cf 100644 --- a/spec/api-debugger-spec.js +++ b/spec/api-debugger-spec.js @@ -1,133 +1,134 @@ -const assert = require('assert'); -const path = require('path'); -const BrowserWindow = require('electron').remote.BrowserWindow; +const assert = require('assert') +const path = require('path') +const BrowserWindow = require('electron').remote.BrowserWindow -describe('debugger module', function() { - var fixtures = path.resolve(__dirname, 'fixtures'); - var w = null; +describe('debugger module', function () { + var fixtures = path.resolve(__dirname, 'fixtures') + var w = null - beforeEach(function() { + beforeEach(function () { if (w != null) { - w.destroy(); + w.destroy() } w = new BrowserWindow({ show: false, width: 400, height: 400 - }); - }); + }) + }) - afterEach(function() { + afterEach(function () { if (w != null) { - w.destroy(); + w.destroy() } - w = null; - }); + w = null + }) - describe('debugger.attach', function() { - it('fails when devtools is already open', function(done) { - w.webContents.on('did-finish-load', function() { - w.webContents.openDevTools(); + describe('debugger.attach', function () { + it('fails when devtools is already open', function (done) { + w.webContents.on('did-finish-load', function () { + w.webContents.openDevTools() try { - w.webContents.debugger.attach(); - } catch(err) { - assert(w.webContents.debugger.isAttached()); - done(); + w.webContents.debugger.attach() + } catch (err) { + assert(w.webContents.debugger.isAttached()) + done() } - }); - w.webContents.loadURL('file://' + path.join(fixtures, 'pages', 'a.html')); - }); + }) + w.webContents.loadURL('file://' + path.join(fixtures, 'pages', 'a.html')) + }) - it('fails when protocol version is not supported', function(done) { + it('fails when protocol version is not supported', function (done) { try { - w.webContents.debugger.attach("2.0"); - } catch(err) { - assert(!w.webContents.debugger.isAttached()); - done(); + w.webContents.debugger.attach('2.0') + } catch (err) { + assert(!w.webContents.debugger.isAttached()) + done() } - }); + }) - it('attaches when no protocol version is specified', function(done) { + it('attaches when no protocol version is specified', function (done) { try { - w.webContents.debugger.attach(); - } catch(err) { - done('unexpected error : ' + err); + w.webContents.debugger.attach() + } catch (err) { + done('unexpected error : ' + err) } - assert(w.webContents.debugger.isAttached()); - done(); - }); - }); + assert(w.webContents.debugger.isAttached()) + done() + }) + }) - describe('debugger.detach', function() { - it('fires detach event', function(done) { - w.webContents.debugger.on('detach', function(e, reason) { - assert.equal(reason, 'target closed'); - assert(!w.webContents.debugger.isAttached()); - done(); - }); + describe('debugger.detach', function () { + it('fires detach event', function (done) { + w.webContents.debugger.on('detach', function (e, reason) { + assert.equal(reason, 'target closed') + assert(!w.webContents.debugger.isAttached()) + done() + }) try { - w.webContents.debugger.attach(); - } catch(err) { - done('unexpected error : ' + err); + w.webContents.debugger.attach() + } catch (err) { + done('unexpected error : ' + err) } - w.webContents.debugger.detach(); - }); - }); + w.webContents.debugger.detach() + }) + }) - describe('debugger.sendCommand', function() { - it('retuns response', function(done) { - w.webContents.loadURL('about:blank'); + describe('debugger.sendCommand', function () { + it('retuns response', function (done) { + w.webContents.loadURL('about:blank') try { - w.webContents.debugger.attach(); - } catch(err) { - done('unexpected error : ' + err); + w.webContents.debugger.attach() + } catch (err) { + return done('unexpected error : ' + err) + } + var callback = function (err, res) { + assert(!err.message) + assert(!res.wasThrown) + assert.equal(res.result.value, 6) + w.webContents.debugger.detach() + done() } - var callback = function(err, res) { - assert(!res.wasThrown); - assert.equal(res.result.value, 6); - w.webContents.debugger.detach(); - done(); - }; const params = { - "expression": "4+2", - }; - w.webContents.debugger.sendCommand("Runtime.evaluate", params, callback); - }); - - it('fires message event', function(done) { - var url = process.platform != 'win32' ? - 'file://' + path.join(fixtures, 'pages', 'a.html') : - 'file:///' + path.join(fixtures, 'pages', 'a.html').replace(/\\/g, '/'); - w.webContents.loadURL(url); - try { - w.webContents.debugger.attach(); - } catch(err) { - done('unexpected error : ' + err); + 'expression': '4+2' } - w.webContents.debugger.on('message', function(e, method, params) { - if(method == "Console.messageAdded") { - assert.equal(params.message.type, 'log'); - assert.equal(params.message.url, url); - assert.equal(params.message.text, 'a'); - w.webContents.debugger.detach(); - done(); + w.webContents.debugger.sendCommand('Runtime.evaluate', params, callback) + }) + + it('fires message event', function (done) { + var url = process.platform !== 'win32' + ? 'file://' + path.join(fixtures, 'pages', 'a.html') + : 'file:///' + path.join(fixtures, 'pages', 'a.html').replace(/\\/g, '/') + w.webContents.loadURL(url) + try { + w.webContents.debugger.attach() + } catch (err) { + done('unexpected error : ' + err) + } + w.webContents.debugger.on('message', function (e, method, params) { + if (method === 'Console.messageAdded') { + assert.equal(params.message.type, 'log') + assert.equal(params.message.url, url) + assert.equal(params.message.text, 'a') + w.webContents.debugger.detach() + done() } - }); - w.webContents.debugger.sendCommand("Console.enable"); - }); + }) + w.webContents.debugger.sendCommand('Console.enable') + }) - it('returns error message when command fails', function(done) { - w.webContents.loadURL('about:blank'); + it('returns error message when command fails', function (done) { + w.webContents.loadURL('about:blank') try { - w.webContents.debugger.attach(); - } catch(err) { - done('unexpected error : ' + err); + w.webContents.debugger.attach() + } catch (err) { + done('unexpected error : ' + err) } - w.webContents.debugger.sendCommand("Test", function(err) { - assert.equal(err.message, '\'Test\' wasn\'t found'); - w.webContents.debugger.detach(); - done(); - }); - }); - }); -}); + w.webContents.debugger.sendCommand('Test', function (err) { + assert.equal(err.message, "'Test' wasn't found") + w.webContents.debugger.detach() + done() + }) + }) + }) +}) diff --git a/spec/api-deprecations-spec.js b/spec/api-deprecations-spec.js index 2f0100590598..5b618ac90165 100644 --- a/spec/api-deprecations-spec.js +++ b/spec/api-deprecations-spec.js @@ -1,27 +1,27 @@ -const assert = require('assert'); -const deprecations = require('electron').deprecations; +const assert = require('assert') +const deprecations = require('electron').deprecations -describe('deprecations', function() { - beforeEach(function() { - deprecations.setHandler(null); - process.throwDeprecation = true; - }); +describe('deprecations', function () { + beforeEach(function () { + deprecations.setHandler(null) + process.throwDeprecation = true + }) - it('allows a deprecation handler function to be specified', function() { - var messages = []; + it('allows a deprecation handler function to be specified', function () { + var messages = [] deprecations.setHandler(function (message) { - messages.push(message); - }); + messages.push(message) + }) - require('electron').webFrame.registerUrlSchemeAsSecure('some-scheme'); + require('electron').deprecate.log('this is deprecated') - assert.deepEqual(messages, ['registerUrlSchemeAsSecure is deprecated. Use registerURLSchemeAsSecure instead.']); - }); + assert.deepEqual(messages, ['this is deprecated']) + }) - it('throws an exception if no deprecation handler is specified', function() { - assert.throws(function() { - require('electron').webFrame.registerUrlSchemeAsPrivileged('some-scheme'); - }, "registerUrlSchemeAsPrivileged is deprecated. Use registerURLSchemeAsPrivileged instead."); - }); -}); + it('throws an exception if no deprecation handler is specified', function () { + assert.throws(function () { + require('electron').deprecate.log('this is deprecated') + }, /this is deprecated/) + }) +}) diff --git a/spec/api-desktop-capturer-spec.js b/spec/api-desktop-capturer-spec.js index 02eda9003b89..35b7248ed5ee 100644 --- a/spec/api-desktop-capturer-spec.js +++ b/spec/api-desktop-capturer-spec.js @@ -1,27 +1,45 @@ -const assert = require('assert'); -const desktopCapturer = require('electron').desktopCapturer; +const assert = require('assert') +const desktopCapturer = require('electron').desktopCapturer -describe('desktopCapturer', function() { - it('should return a non-empty array of sources', function(done) { +const isCI = require('electron').remote.getGlobal('isCi') + +describe('desktopCapturer', function () { + if (isCI && process.platform === 'win32') { + return + } + + it('should return a non-empty array of sources', function (done) { desktopCapturer.getSources({ types: ['window', 'screen'] - }, function(error, sources) { - assert.equal(error, null); - assert.notEqual(sources.length, 0); - done(); - }); - }); + }, function (error, sources) { + assert.equal(error, null) + assert.notEqual(sources.length, 0) + done() + }) + }) - it('does not throw an error when called more than once (regression)', function(done) { - var callCount = 0; + it('does not throw an error when called more than once (regression)', function (done) { + var callCount = 0 var callback = function (error, sources) { - callCount++; - assert.equal(error, null); - assert.notEqual(sources.length, 0); - if (callCount === 2) done(); - }; + callCount++ + assert.equal(error, null) + assert.notEqual(sources.length, 0) + if (callCount === 2) done() + } - desktopCapturer.getSources({types: ['window', 'screen']}, callback); - desktopCapturer.getSources({types: ['window', 'screen']}, callback); - }); -}); + desktopCapturer.getSources({types: ['window', 'screen']}, callback) + desktopCapturer.getSources({types: ['window', 'screen']}, callback) + }) + + it('responds to subsequest calls of different options', function (done) { + var callCount = 0 + var callback = function (error, sources) { + callCount++ + assert.equal(error, null) + if (callCount === 2) done() + } + + desktopCapturer.getSources({types: ['window']}, callback) + desktopCapturer.getSources({types: ['screen']}, callback) + }) +}) diff --git a/spec/api-ipc-spec.js b/spec/api-ipc-spec.js index 90e099fa612e..e88815873af9 100644 --- a/spec/api-ipc-spec.js +++ b/spec/api-ipc-spec.js @@ -1,191 +1,286 @@ -'use strict'; +'use strict' -const assert = require('assert'); -const path = require('path'); +const assert = require('assert') +const path = require('path') -const ipcRenderer = require('electron').ipcRenderer; -const remote = require('electron').remote; +const {ipcRenderer, remote} = require('electron') +const {ipcMain, webContents, BrowserWindow} = remote -const ipcMain = remote.require('electron').ipcMain; -const BrowserWindow = remote.require('electron').BrowserWindow; - -const comparePaths = function(path1, path2) { +const comparePaths = function (path1, path2) { if (process.platform === 'win32') { - path1 = path1.toLowerCase(); - path2 = path2.toLowerCase(); + path1 = path1.toLowerCase() + path2 = path2.toLowerCase() } - assert.equal(path1, path2); -}; + assert.equal(path1, path2) +} -describe('ipc module', function() { - var fixtures = path.join(__dirname, 'fixtures'); +describe('ipc module', function () { + var fixtures = path.join(__dirname, 'fixtures') - describe('remote.require', function() { - it('should returns same object for the same module', function() { - var dialog1 = remote.require('electron'); - var dialog2 = remote.require('electron'); - assert.equal(dialog1, dialog2); + describe('remote.require', function () { + it('should returns same object for the same module', function () { + var dialog1 = remote.require('electron') + var dialog2 = remote.require('electron') + assert.equal(dialog1, dialog2) + }) + + it('should work when object contains id property', function () { + var a = remote.require(path.join(fixtures, 'module', 'id.js')) + assert.equal(a.id, 1127) + }) + + it('should work when object has no prototype', function () { + var a = remote.require(path.join(fixtures, 'module', 'no-prototype.js')) + assert.equal(a.foo.bar, 'baz') + assert.equal(a.foo.baz, false) + assert.equal(a.bar, 1234) + }) + + it('should search module from the user app', function () { + comparePaths(path.normalize(remote.process.mainModule.filename), path.resolve(__dirname, 'static', 'main.js')) + comparePaths(path.normalize(remote.process.mainModule.paths[0]), path.resolve(__dirname, 'static', 'node_modules')) + }) + }) + + describe('remote.createFunctionWithReturnValue', function () { + it('should be called in browser synchronously', function () { + var buf = new Buffer('test') + var call = remote.require(path.join(fixtures, 'module', 'call.js')) + var result = call.call(remote.createFunctionWithReturnValue(buf)) + assert.equal(result.constructor.name, 'Buffer') + }) + }) + + describe('remote object in renderer', function () { + it('can change its properties', function () { + var property = remote.require(path.join(fixtures, 'module', 'property.js')) + assert.equal(property.property, 1127) + property.property = 1007 + assert.equal(property.property, 1007) + var property2 = remote.require(path.join(fixtures, 'module', 'property.js')) + assert.equal(property2.property, 1007) + property.property = 1127 + }) + + it('can construct an object from its member', function () { + var call = remote.require(path.join(fixtures, 'module', 'call.js')) + var obj = new call.constructor() + assert.equal(obj.test, 'test') + }) + + it('can reassign and delete its member functions', function () { + var remoteFunctions = remote.require(path.join(fixtures, 'module', 'function.js')) + assert.equal(remoteFunctions.aFunction(), 1127) + + remoteFunctions.aFunction = function () { return 1234 } + assert.equal(remoteFunctions.aFunction(), 1234) + + assert.equal(delete remoteFunctions.aFunction, true) + }) + + it('is referenced by its members', function () { + let stringify = remote.getGlobal('JSON').stringify + gc(); + stringify({}) }); + }) - it('should work when object contains id property', function() { - var a = remote.require(path.join(fixtures, 'module', 'id.js')); - assert.equal(a.id, 1127); + describe('remote value in browser', function () { + var print = path.join(fixtures, 'module', 'print_name.js') + + it('keeps its constructor name for objects', function () { + var buf = new Buffer('test') + var print_name = remote.require(print) + assert.equal(print_name.print(buf), 'Buffer') + }) + + it('supports instanceof Date', function () { + var now = new Date() + var print_name = remote.require(print) + assert.equal(print_name.print(now), 'Date') + assert.deepEqual(print_name.echo(now), now) + }) + }) + + describe('remote promise', function () { + it('can be used as promise in each side', function (done) { + var promise = remote.require(path.join(fixtures, 'module', 'promise.js')) + promise.twicePromise(Promise.resolve(1234)).then(function (value) { + assert.equal(value, 2468) + done() + }) + }) + + it('handles rejections via catch(onRejected)', function (done) { + var promise = remote.require(path.join(fixtures, 'module', 'rejected-promise.js')) + promise.reject(Promise.resolve(1234)).catch(function (error) { + assert.equal(error.message, 'rejected') + done() + }) + }) + + it('handles rejections via then(onFulfilled, onRejected)', function (done) { + var promise = remote.require(path.join(fixtures, 'module', 'rejected-promise.js')) + promise.reject(Promise.resolve(1234)).then(function () {}, function (error) { + assert.equal(error.message, 'rejected') + done() + }) + }) + }) + + describe('remote webContents', function () { + it('can return same object with different getters', function () { + var contents1 = remote.getCurrentWindow().webContents + var contents2 = remote.getCurrentWebContents() + assert(contents1 === contents2) + }) + }) + + describe('remote class', function () { + let cl = remote.require(path.join(fixtures, 'module', 'class.js')) + let base = cl.base + let derived = cl.derived + + it('can get methods', function () { + assert.equal(base.method(), 'method') + }) + + it('can get properties', function () { + assert.equal(base.readonly, 'readonly') + }) + + it('can change properties', function () { + assert.equal(base.value, 'old') + base.value = 'new' + assert.equal(base.value, 'new') + base.value = 'old' + }) + + it('has unenumerable methods', function () { + assert(!base.hasOwnProperty('method')) + assert(Object.getPrototypeOf(base).hasOwnProperty('method')) + }) + + it('keeps prototype chain in derived class', function () { + assert.equal(derived.method(), 'method') + assert.equal(derived.readonly, 'readonly') + assert(!derived.hasOwnProperty('method')) + let proto = Object.getPrototypeOf(derived) + assert(!proto.hasOwnProperty('method')) + assert(Object.getPrototypeOf(proto).hasOwnProperty('method')) + }) + + it('is referenced by methods in prototype chain', function () { + let method = derived.method + derived = null + gc() + assert.equal(method(), 'method') }); + }) - it('should search module from the user app', function() { - comparePaths(path.normalize(remote.process.mainModule.filename), path.resolve(__dirname, 'static', 'main.js')); - comparePaths(path.normalize(remote.process.mainModule.paths[0]), path.resolve(__dirname, 'static', 'node_modules')); - }); - }); - - describe('remote.createFunctionWithReturnValue', function() { - it('should be called in browser synchronously', function() { - var buf = new Buffer('test'); - var call = remote.require(path.join(fixtures, 'module', 'call.js')); - var result = call.call(remote.createFunctionWithReturnValue(buf)); - assert.equal(result.constructor.name, 'Buffer'); - }); - }); - - describe('remote object in renderer', function() { - it('can change its properties', function() { - var property = remote.require(path.join(fixtures, 'module', 'property.js')); - assert.equal(property.property, 1127); - property.property = 1007; - assert.equal(property.property, 1007); - var property2 = remote.require(path.join(fixtures, 'module', 'property.js')); - assert.equal(property2.property, 1007); - property.property = 1127; - }); - - it('can construct an object from its member', function() { - var call = remote.require(path.join(fixtures, 'module', 'call.js')); - var obj = new call.constructor; - assert.equal(obj.test, 'test'); - }); - }); - - describe('remote value in browser', function() { - var print = path.join(fixtures, 'module', 'print_name.js'); - - it('keeps its constructor name for objects', function() { - var buf = new Buffer('test'); - var print_name = remote.require(print); - assert.equal(print_name.print(buf), 'Buffer'); - }); - - it('supports instanceof Date', function() { - var now = new Date(); - var print_name = remote.require(print); - assert.equal(print_name.print(now), 'Date'); - assert.deepEqual(print_name.echo(now), now); - }); - }); - - describe('remote promise', function() { - it('can be used as promise in each side', function(done) { - var promise = remote.require(path.join(fixtures, 'module', 'promise.js')); - promise.twicePromise(Promise.resolve(1234)).then(function(value) { - assert.equal(value, 2468); - done(); - }); - }); - }); - - describe('remote webContents', function() { - it('can return same object with different getters', function() { - var contents1 = remote.getCurrentWindow().webContents; - var contents2 = remote.getCurrentWebContents(); - assert(contents1 == contents2); - }); - }); - - describe('remote class', function() { - let cl = remote.require(path.join(fixtures, 'module', 'class.js')); - let base = cl.base; - let derived = cl.derived; - - it('can get methods', function() { - assert.equal(base.method(), 'method'); - }); - - it('can get properties', function() { - assert.equal(base.readonly, 'readonly'); - }); - - it('can change properties', function() { - assert.equal(base.value, 'old'); - base.value = 'new'; - assert.equal(base.value, 'new'); - base.value = 'old'; - }); - - it('has unenumerable methods', function() { - assert(!base.hasOwnProperty('method')); - assert(Object.getPrototypeOf(base).hasOwnProperty('method')); - }); - - it('keeps prototype chain in derived class', function() { - assert.equal(derived.method(), 'method'); - assert.equal(derived.readonly, 'readonly'); - assert(!derived.hasOwnProperty('method')); - let proto = Object.getPrototypeOf(derived); - assert(!proto.hasOwnProperty('method')); - assert(Object.getPrototypeOf(proto).hasOwnProperty('method')); - }); - }); - - describe('ipc.sender.send', function() { - it('should work when sending an object containing id property', function(done) { + describe('ipc.sender.send', function () { + it('should work when sending an object containing id property', function (done) { var obj = { id: 1, name: 'ly' - }; - ipcRenderer.once('message', function(event, message) { - assert.deepEqual(message, obj); - done(); - }); - ipcRenderer.send('message', obj); - }); - }); + } + ipcRenderer.once('message', function (event, message) { + assert.deepEqual(message, obj) + done() + }) + ipcRenderer.send('message', obj) + }) - describe('ipc.sendSync', function() { - it('can be replied by setting event.returnValue', function() { - var msg = ipcRenderer.sendSync('echo', 'test'); - assert.equal(msg, 'test'); - }); + it('can send instance of Date', function (done) { + const currentDate = new Date() + ipcRenderer.once('message', function (event, value) { + assert.equal(value, currentDate.toISOString()) + done() + }) + ipcRenderer.send('message', currentDate) + }) + }) - it('does not crash when reply is not sent and browser is destroyed', function(done) { - this.timeout(10000); + describe('ipc.sendSync', function () { + afterEach(function () { + ipcMain.removeAllListeners('send-sync-message') + }) + + it('can be replied by setting event.returnValue', function () { + var msg = ipcRenderer.sendSync('echo', 'test') + assert.equal(msg, 'test') + }) + + it('does not crash when reply is not sent and browser is destroyed', function (done) { + this.timeout(10000) var w = new BrowserWindow({ show: false - }); - ipcMain.once('send-sync-message', function(event) { - event.returnValue = null; - w.destroy(); - done(); - }); - w.loadURL('file://' + path.join(fixtures, 'api', 'send-sync-message.html')); - }); - }); + }) + ipcMain.once('send-sync-message', function (event) { + event.returnValue = null + w.destroy() + done() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'send-sync-message.html')) + }) - describe('remote listeners', function() { - var w = null; + it('does not crash when reply is sent by multiple listeners', function (done) { + var w = new BrowserWindow({ + show: false + }) + ipcMain.on('send-sync-message', function (event) { + event.returnValue = null + }) + ipcMain.on('send-sync-message', function (event) { + event.returnValue = null + w.destroy() + done() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'send-sync-message.html')) + }) + }) - afterEach(function() { - w.destroy(); - }); + describe('ipcRenderer.sendTo', function () { + let contents = null + beforeEach(function () { + contents = webContents.create({}) + }) + afterEach(function () { + ipcRenderer.removeAllListeners('pong') + contents.destroy() + contents = null + }) - it('can be added and removed correctly', function() { + it('sends message to WebContents', function (done) { + const webContentsId = remote.getCurrentWebContents().id + ipcRenderer.once('pong', function (event, id) { + assert.equal(webContentsId, id) + done() + }) + contents.once('did-finish-load', function () { + ipcRenderer.sendTo(contents.id, 'ping', webContentsId) + }) + contents.loadURL('file://' + path.join(fixtures, 'pages', 'ping-pong.html')) + }) + }) + + describe('remote listeners', function () { + var w = null + + afterEach(function () { + w.destroy() + }) + + it('can be added and removed correctly', function () { w = new BrowserWindow({ show: false - }); - var listener = function() {}; - w.on('test', listener); - assert.equal(w.listenerCount('test'), 1); - w.removeListener('test', listener); - assert.equal(w.listenerCount('test'), 0); - }); - }); -}); + }) + var listener = function () {} + w.on('test', listener) + assert.equal(w.listenerCount('test'), 1) + w.removeListener('test', listener) + assert.equal(w.listenerCount('test'), 0) + }) + }) +}) diff --git a/spec/api-menu-spec.js b/spec/api-menu-spec.js index b27840ec4f2e..6866448e0fd1 100644 --- a/spec/api-menu-spec.js +++ b/spec/api-menu-spec.js @@ -1,25 +1,25 @@ -const assert = require('assert'); +const assert = require('assert') -const remote = require('electron').remote; -const ipcRenderer = require('electron').ipcRenderer; +const remote = require('electron').remote +const ipcRenderer = require('electron').ipcRenderer -const Menu = remote.require('electron').Menu; -const MenuItem = remote.require('electron').MenuItem; +const Menu = remote.require('electron').Menu +const MenuItem = remote.require('electron').MenuItem -describe('menu module', function() { - describe('Menu.buildFromTemplate', function() { - it('should be able to attach extra fields', function() { +describe('menu module', function () { + describe('Menu.buildFromTemplate', function () { + it('should be able to attach extra fields', function () { var menu = Menu.buildFromTemplate([ { label: 'text', extra: 'field' } - ]); - assert.equal(menu.items[0].extra, 'field'); - }); + ]) + assert.equal(menu.items[0].extra, 'field') + }) - it('does not modify the specified template', function() { - var template = ipcRenderer.sendSync('eval', "var template = [{label: 'text', submenu: [{label: 'sub'}]}];\nrequire('electron').Menu.buildFromTemplate(template);\ntemplate;"); + it('does not modify the specified template', function () { + var template = ipcRenderer.sendSync('eval', "var template = [{label: 'text', submenu: [{label: 'sub'}]}];\nrequire('electron').Menu.buildFromTemplate(template);\ntemplate;") assert.deepStrictEqual(template, [ { label: 'text', @@ -29,11 +29,26 @@ describe('menu module', function() { } ] } - ]); - }); + ]) + }) - describe('Menu.buildFromTemplate should reorder based on item position specifiers', function() { - it('should position before existing item', function() { + it('does not throw exceptions for undefined/null values', function () { + assert.doesNotThrow(function () { + Menu.buildFromTemplate([ + { + label: 'text', + accelerator: undefined + }, + { + label: 'text again', + accelerator: null + } + ]) + }) + }) + + describe('Menu.buildFromTemplate should reorder based on item position specifiers', function () { + it('should position before existing item', function () { var menu = Menu.buildFromTemplate([ { label: '2', @@ -46,13 +61,13 @@ describe('menu module', function() { id: '1', position: 'before=2' } - ]); - assert.equal(menu.items[0].label, '1'); - assert.equal(menu.items[1].label, '2'); - assert.equal(menu.items[2].label, '3'); - }); + ]) + assert.equal(menu.items[0].label, '1') + assert.equal(menu.items[1].label, '2') + assert.equal(menu.items[2].label, '3') + }) - it('should position after existing item', function() { + it('should position after existing item', function () { var menu = Menu.buildFromTemplate([ { label: '1', @@ -65,13 +80,13 @@ describe('menu module', function() { id: '2', position: 'after=1' } - ]); - assert.equal(menu.items[0].label, '1'); - assert.equal(menu.items[1].label, '2'); - assert.equal(menu.items[2].label, '3'); - }); + ]) + assert.equal(menu.items[0].label, '1') + assert.equal(menu.items[1].label, '2') + assert.equal(menu.items[2].label, '3') + }) - it('should position at endof existing separator groups', function() { + it('should position at endof existing separator groups', function () { var menu = Menu.buildFromTemplate([ { type: 'separator', @@ -104,18 +119,18 @@ describe('menu module', function() { id: '3', position: 'endof=numbers' } - ]); - assert.equal(menu.items[0].id, 'numbers'); - assert.equal(menu.items[1].label, '1'); - assert.equal(menu.items[2].label, '2'); - assert.equal(menu.items[3].label, '3'); - assert.equal(menu.items[4].id, 'letters'); - assert.equal(menu.items[5].label, 'a'); - assert.equal(menu.items[6].label, 'b'); - assert.equal(menu.items[7].label, 'c'); - }); + ]) + assert.equal(menu.items[0].id, 'numbers') + assert.equal(menu.items[1].label, '1') + assert.equal(menu.items[2].label, '2') + assert.equal(menu.items[3].label, '3') + assert.equal(menu.items[4].id, 'letters') + assert.equal(menu.items[5].label, 'a') + assert.equal(menu.items[6].label, 'b') + assert.equal(menu.items[7].label, 'c') + }) - it('should create separator group if endof does not reference existing separator group', function() { + it('should create separator group if endof does not reference existing separator group', function () { var menu = Menu.buildFromTemplate([ { label: 'a', @@ -142,18 +157,18 @@ describe('menu module', function() { id: '3', position: 'endof=numbers' } - ]); - assert.equal(menu.items[0].id, 'letters'); - assert.equal(menu.items[1].label, 'a'); - assert.equal(menu.items[2].label, 'b'); - assert.equal(menu.items[3].label, 'c'); - assert.equal(menu.items[4].id, 'numbers'); - assert.equal(menu.items[5].label, '1'); - assert.equal(menu.items[6].label, '2'); - assert.equal(menu.items[7].label, '3'); - }); + ]) + assert.equal(menu.items[0].id, 'letters') + assert.equal(menu.items[1].label, 'a') + assert.equal(menu.items[2].label, 'b') + assert.equal(menu.items[3].label, 'c') + assert.equal(menu.items[4].id, 'numbers') + assert.equal(menu.items[5].label, '1') + assert.equal(menu.items[6].label, '2') + assert.equal(menu.items[7].label, '3') + }) - it('should continue inserting items at next index when no specifier is present', function() { + it('should continue inserting items at next index when no specifier is present', function () { var menu = Menu.buildFromTemplate([ { label: '4', @@ -172,18 +187,18 @@ describe('menu module', function() { label: '3', id: '3' } - ]); - assert.equal(menu.items[0].label, '1'); - assert.equal(menu.items[1].label, '2'); - assert.equal(menu.items[2].label, '3'); - assert.equal(menu.items[3].label, '4'); - assert.equal(menu.items[4].label, '5'); - }); - }); - }); + ]) + assert.equal(menu.items[0].label, '1') + assert.equal(menu.items[1].label, '2') + assert.equal(menu.items[2].label, '3') + assert.equal(menu.items[3].label, '4') + assert.equal(menu.items[4].label, '5') + }) + }) + }) - describe('Menu.insert', function() { - it('should store item in @items by its index', function() { + describe('Menu.insert', function () { + it('should store item in @items by its index', function () { var menu = Menu.buildFromTemplate([ { label: '1' @@ -192,156 +207,156 @@ describe('menu module', function() { }, { label: '3' } - ]); + ]) var item = new MenuItem({ label: 'inserted' - }); - menu.insert(1, item); - assert.equal(menu.items[0].label, '1'); - assert.equal(menu.items[1].label, 'inserted'); - assert.equal(menu.items[2].label, '2'); - assert.equal(menu.items[3].label, '3'); - }); - }); + }) + menu.insert(1, item) + assert.equal(menu.items[0].label, '1') + assert.equal(menu.items[1].label, 'inserted') + assert.equal(menu.items[2].label, '2') + assert.equal(menu.items[3].label, '3') + }) + }) - describe('MenuItem.click', function() { - it('should be called with the item object passed', function(done) { + describe('MenuItem.click', function () { + it('should be called with the item object passed', function (done) { var menu = Menu.buildFromTemplate([ { label: 'text', - click: function(item) { - assert.equal(item.constructor.name, 'MenuItem'); - assert.equal(item.label, 'text'); - done(); + click: function (item) { + assert.equal(item.constructor.name, 'MenuItem') + assert.equal(item.label, 'text') + done() } } - ]); - menu.delegate.executeCommand(menu.items[0].commandId); - }); - }); + ]) + menu.delegate.executeCommand(menu.items[0].commandId) + }) + }) - describe('MenuItem with checked property', function() { - it('clicking an checkbox item should flip the checked property', function() { + describe('MenuItem with checked property', function () { + it('clicking an checkbox item should flip the checked property', function () { var menu = Menu.buildFromTemplate([ { label: 'text', type: 'checkbox' } - ]); - assert.equal(menu.items[0].checked, false); - menu.delegate.executeCommand(menu.items[0].commandId); - assert.equal(menu.items[0].checked, true); - }); + ]) + assert.equal(menu.items[0].checked, false) + menu.delegate.executeCommand(menu.items[0].commandId) + assert.equal(menu.items[0].checked, true) + }) - it('clicking an radio item should always make checked property true', function() { + it('clicking an radio item should always make checked property true', function () { var menu = Menu.buildFromTemplate([ { label: 'text', type: 'radio' } - ]); - menu.delegate.executeCommand(menu.items[0].commandId); - assert.equal(menu.items[0].checked, true); - menu.delegate.executeCommand(menu.items[0].commandId); - assert.equal(menu.items[0].checked, true); - }); + ]) + menu.delegate.executeCommand(menu.items[0].commandId) + assert.equal(menu.items[0].checked, true) + menu.delegate.executeCommand(menu.items[0].commandId) + assert.equal(menu.items[0].checked, true) + }) - it('at least have one item checked in each group', function() { - var i, j, k, menu, template; - template = []; + it('at least have one item checked in each group', function () { + var i, j, k, menu, template + template = [] for (i = j = 0; j <= 10; i = ++j) { template.push({ - label: "" + i, + label: '' + i, type: 'radio' - }); + }) } template.push({ type: 'separator' - }); + }) for (i = k = 12; k <= 20; i = ++k) { template.push({ - label: "" + i, + label: '' + i, type: 'radio' - }); + }) } - menu = Menu.buildFromTemplate(template); - menu.delegate.menuWillShow(); - assert.equal(menu.items[0].checked, true); - assert.equal(menu.items[12].checked, true); - }); + menu = Menu.buildFromTemplate(template) + menu.delegate.menuWillShow() + assert.equal(menu.items[0].checked, true) + assert.equal(menu.items[12].checked, true) + }) - it('should assign groupId automatically', function() { - var groupId, i, j, k, l, m, menu, template; - template = []; + it('should assign groupId automatically', function () { + var groupId, i, j, k, l, m, menu, template + template = [] for (i = j = 0; j <= 10; i = ++j) { template.push({ - label: "" + i, + label: '' + i, type: 'radio' - }); + }) } template.push({ type: 'separator' - }); + }) for (i = k = 12; k <= 20; i = ++k) { template.push({ - label: "" + i, + label: '' + i, type: 'radio' - }); + }) } - menu = Menu.buildFromTemplate(template); - groupId = menu.items[0].groupId; + menu = Menu.buildFromTemplate(template) + groupId = menu.items[0].groupId for (i = l = 0; l <= 10; i = ++l) { - assert.equal(menu.items[i].groupId, groupId); + assert.equal(menu.items[i].groupId, groupId) } for (i = m = 12; m <= 20; i = ++m) { - assert.equal(menu.items[i].groupId, groupId + 1); + assert.equal(menu.items[i].groupId, groupId + 1) } - }); + }) - it("setting 'checked' should flip other items' 'checked' property", function() { - var i, j, k, l, m, menu, n, o, p, q, template; - template = []; + it("setting 'checked' should flip other items' 'checked' property", function () { + var i, j, k, l, m, menu, n, o, p, q, template + template = [] for (i = j = 0; j <= 10; i = ++j) { template.push({ - label: "" + i, + label: '' + i, type: 'radio' - }); + }) } template.push({ type: 'separator' - }); + }) for (i = k = 12; k <= 20; i = ++k) { template.push({ - label: "" + i, + label: '' + i, type: 'radio' - }); + }) } - menu = Menu.buildFromTemplate(template); + menu = Menu.buildFromTemplate(template) for (i = l = 0; l <= 10; i = ++l) { - assert.equal(menu.items[i].checked, false); + assert.equal(menu.items[i].checked, false) } - menu.items[0].checked = true; - assert.equal(menu.items[0].checked, true); + menu.items[0].checked = true + assert.equal(menu.items[0].checked, true) for (i = m = 1; m <= 10; i = ++m) { - assert.equal(menu.items[i].checked, false); + assert.equal(menu.items[i].checked, false) } - menu.items[10].checked = true; - assert.equal(menu.items[10].checked, true); + menu.items[10].checked = true + assert.equal(menu.items[10].checked, true) for (i = n = 0; n <= 9; i = ++n) { - assert.equal(menu.items[i].checked, false); + assert.equal(menu.items[i].checked, false) } for (i = o = 12; o <= 20; i = ++o) { - assert.equal(menu.items[i].checked, false); + assert.equal(menu.items[i].checked, false) } - menu.items[12].checked = true; - assert.equal(menu.items[10].checked, true); + menu.items[12].checked = true + assert.equal(menu.items[10].checked, true) for (i = p = 0; p <= 9; i = ++p) { - assert.equal(menu.items[i].checked, false); + assert.equal(menu.items[i].checked, false) } - assert.equal(menu.items[12].checked, true); + assert.equal(menu.items[12].checked, true) for (i = q = 13; q <= 20; i = ++q) { - assert.equal(menu.items[i].checked, false); + assert.equal(menu.items[i].checked, false) } - }); - }); -}); + }) + }) +}) diff --git a/spec/api-native-image-spec.js b/spec/api-native-image-spec.js new file mode 100644 index 000000000000..196ed1356d70 --- /dev/null +++ b/spec/api-native-image-spec.js @@ -0,0 +1,51 @@ +'use strict' + +const assert = require('assert') +const nativeImage = require('electron').nativeImage +const path = require('path') + +describe('nativeImage module', () => { + describe('createFromPath(path)', () => { + it('returns an empty image for invalid paths', () => { + assert(nativeImage.createFromPath('').isEmpty()) + assert(nativeImage.createFromPath('does-not-exist.png').isEmpty()) + }) + + it('loads images from paths relative to the current working directory', () => { + const imagePath = `.${path.sep}${path.join('spec', 'fixtures', 'assets', 'logo.png')}` + const image = nativeImage.createFromPath(imagePath) + assert(!image.isEmpty()) + assert.equal(image.getSize().height, 190) + assert.equal(image.getSize().width, 538) + }) + + it('loads images from paths with `.` segments', () => { + const imagePath = `${path.join(__dirname, 'fixtures')}${path.sep}.${path.sep}${path.join('assets', 'logo.png')}` + const image = nativeImage.createFromPath(imagePath) + assert(!image.isEmpty()) + assert.equal(image.getSize().height, 190) + assert.equal(image.getSize().width, 538) + }) + + it('loads images from paths with `..` segments', () => { + const imagePath = `${path.join(__dirname, 'fixtures', 'api')}${path.sep}..${path.sep}${path.join('assets', 'logo.png')}` + const image = nativeImage.createFromPath(imagePath) + assert(!image.isEmpty()) + assert.equal(image.getSize().height, 190) + assert.equal(image.getSize().width, 538) + }) + + it('Gets an NSImage pointer on OS X', () => { + if (process.platform !== 'darwin') return + + const imagePath = `${path.join(__dirname, 'fixtures', 'api')}${path.sep}..${path.sep}${path.join('assets', 'logo.png')}` + const image = nativeImage.createFromPath(imagePath) + const nsimage = image.getNativeHandle() + + assert.equal(nsimage.length, 8) + + // If all bytes are null, that's Bad + assert.equal(nsimage.reduce((acc, x) => acc || (x !== 0), false), true) + }) + }) +}) diff --git a/spec/api-protocol-spec.js b/spec/api-protocol-spec.js index 215868bfdc8a..803511917104 100644 --- a/spec/api-protocol-spec.js +++ b/spec/api-protocol-spec.js @@ -1,772 +1,809 @@ -const assert = require('assert'); -const http = require('http'); -const path = require('path'); -const qs = require('querystring'); -const remote = require('electron').remote; -const protocol = remote.require('electron').protocol; +const assert = require('assert') +const http = require('http') +const path = require('path') +const qs = require('querystring') +const remote = require('electron').remote +const {BrowserWindow, protocol, webContents} = remote -describe('protocol module', function() { - var protocolName = 'sp'; - var text = 'valar morghulis'; +describe('protocol module', function () { + var protocolName = 'sp' + var text = 'valar morghulis' var postData = { name: 'post test', type: 'string' - }; + } - afterEach(function(done) { - protocol.unregisterProtocol(protocolName, function() { - protocol.uninterceptProtocol('http', function() { - done(); - }); - }); - }); + afterEach(function (done) { + protocol.unregisterProtocol(protocolName, function () { + protocol.uninterceptProtocol('http', function () { + done() + }) + }) + }) - describe('protocol.register(Any)Protocol', function() { - var emptyHandler = function(request, callback) { - callback(); - }; + describe('protocol.register(Any)Protocol', function () { + var emptyHandler = function (request, callback) { + callback() + } - it('throws error when scheme is already registered', function(done) { - protocol.registerStringProtocol(protocolName, emptyHandler, function(error) { - assert.equal(error, null); - protocol.registerBufferProtocol(protocolName, emptyHandler, function(error) { - assert.notEqual(error, null); - done(); - }); - }); - }); + it('throws error when scheme is already registered', function (done) { + protocol.registerStringProtocol(protocolName, emptyHandler, function (error) { + assert.equal(error, null) + protocol.registerBufferProtocol(protocolName, emptyHandler, function (error) { + assert.notEqual(error, null) + done() + }) + }) + }) - it('does not crash when handler is called twice', function(done) { - var doubleHandler = function(request, callback) { + it('does not crash when handler is called twice', function (done) { + var doubleHandler = function (request, callback) { try { - callback(text); - callback(); + callback(text) + callback() } catch (error) { // Ignore error } - }; - protocol.registerStringProtocol(protocolName, doubleHandler, function(error) { + } + protocol.registerStringProtocol(protocolName, doubleHandler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: protocolName + "://fake-host", - success: function(data) { - assert.equal(data, text); - done(); + url: protocolName + '://fake-host', + success: function (data) { + assert.equal(data, text) + done() }, - error: function(xhr, errorType, error) { - done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); + }) + }) + }) - it('sends error when callback is called with nothing', function(done) { - protocol.registerBufferProtocol(protocolName, emptyHandler, function(error) { + it('sends error when callback is called with nothing', function (done) { + protocol.registerBufferProtocol(protocolName, emptyHandler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: protocolName + "://fake-host", - success: function() { - return done('request succeeded but it should not'); + url: protocolName + '://fake-host', + success: function () { + return done('request succeeded but it should not') }, - error: function(xhr, errorType) { - assert.equal(errorType, 'error'); - return done(); + error: function (xhr, errorType) { + assert.equal(errorType, 'error') + return done() } - }); - }); - }); + }) + }) + }) - it('does not crash when callback is called in next tick', function(done) { - var handler = function(request, callback) { - setImmediate(function() { - callback(text); - }); - }; - protocol.registerStringProtocol(protocolName, handler, function(error) { + it('does not crash when callback is called in next tick', function (done) { + var handler = function (request, callback) { + setImmediate(function () { + callback(text) + }) + } + protocol.registerStringProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: protocolName + "://fake-host", - success: function(data) { - assert.equal(data, text); - done(); + url: protocolName + '://fake-host', + success: function (data) { + assert.equal(data, text) + done() }, - error: function(xhr, errorType, error) { - done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - }); + }) + }) + }) + }) - describe('protocol.unregisterProtocol', function() { - it('returns error when scheme does not exist', function(done) { - protocol.unregisterProtocol('not-exist', function(error) { - assert.notEqual(error, null); - done(); - }); - }); - }); + describe('protocol.unregisterProtocol', function () { + it('returns error when scheme does not exist', function (done) { + protocol.unregisterProtocol('not-exist', function (error) { + assert.notEqual(error, null) + done() + }) + }) + }) - describe('protocol.registerStringProtocol', function() { - it('sends string as response', function(done) { - var handler = function(request, callback) { - callback(text); - }; - protocol.registerStringProtocol(protocolName, handler, function(error) { + describe('protocol.registerStringProtocol', function () { + it('sends string as response', function (done) { + var handler = function (request, callback) { + callback(text) + } + protocol.registerStringProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: protocolName + "://fake-host", - success: function(data) { - assert.equal(data, text); - done(); + url: protocolName + '://fake-host', + success: function (data) { + assert.equal(data, text) + done() }, - error: function(xhr, errorType, error) { - done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); + }) + }) + }) - it('sets Access-Control-Allow-Origin', function(done) { - var handler = function(request, callback) { - callback(text); - }; - protocol.registerStringProtocol(protocolName, handler, function(error) { + it('sets Access-Control-Allow-Origin', function (done) { + var handler = function (request, callback) { + callback(text) + } + protocol.registerStringProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: protocolName + "://fake-host", - success: function(data, status, request) { - assert.equal(data, text); - assert.equal(request.getResponseHeader('Access-Control-Allow-Origin'), '*'); - done(); + url: protocolName + '://fake-host', + success: function (data, status, request) { + assert.equal(data, text) + assert.equal(request.getResponseHeader('Access-Control-Allow-Origin'), '*') + done() }, - error: function(xhr, errorType, error) { - done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); + }) + }) + }) - it('sends object as response', function(done) { - var handler = function(request, callback) { + it('sends object as response', function (done) { + var handler = function (request, callback) { callback({ data: text, mimeType: 'text/html' - }); - }; - protocol.registerStringProtocol(protocolName, handler, function(error) { + }) + } + protocol.registerStringProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: protocolName + "://fake-host", - success: function(data) { - assert.equal(data, text); - done(); + url: protocolName + '://fake-host', + success: function (data) { + assert.equal(data, text) + done() }, - error: function(xhr, errorType, error) { - done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); + }) + }) + }) - it('fails when sending object other than string', function(done) { - var handler = function(request, callback) { - callback(new Date); - }; - protocol.registerBufferProtocol(protocolName, handler, function(error) { + it('fails when sending object other than string', function (done) { + var handler = function (request, callback) { + callback(new Date()) + } + protocol.registerBufferProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: protocolName + "://fake-host", - success: function() { - done('request succeeded but it should not'); + url: protocolName + '://fake-host', + success: function () { + done('request succeeded but it should not') }, - error: function(xhr, errorType) { - assert.equal(errorType, 'error'); - done(); + error: function (xhr, errorType) { + assert.equal(errorType, 'error') + done() } - }); - }); - }); - }); + }) + }) + }) + }) - describe('protocol.registerBufferProtocol', function() { - var buffer = new Buffer(text); + describe('protocol.registerBufferProtocol', function () { + var buffer = new Buffer(text) - it('sends Buffer as response', function(done) { - var handler = function(request, callback) { - callback(buffer); - }; - protocol.registerBufferProtocol(protocolName, handler, function(error) { + it('sends Buffer as response', function (done) { + var handler = function (request, callback) { + callback(buffer) + } + protocol.registerBufferProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: protocolName + "://fake-host", - success: function(data) { - assert.equal(data, text); - done(); + url: protocolName + '://fake-host', + success: function (data) { + assert.equal(data, text) + done() }, - error: function(xhr, errorType, error) { - done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); + }) + }) + }) - it('sets Access-Control-Allow-Origin', function(done) { - var handler = function(request, callback) { - callback(buffer); - }; + it('sets Access-Control-Allow-Origin', function (done) { + var handler = function (request, callback) { + callback(buffer) + } - protocol.registerBufferProtocol(protocolName, handler, function(error) { + protocol.registerBufferProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: protocolName + "://fake-host", - success: function(data, status, request) { - assert.equal(data, text); - assert.equal(request.getResponseHeader('Access-Control-Allow-Origin'), '*'); - done(); + url: protocolName + '://fake-host', + success: function (data, status, request) { + assert.equal(data, text) + assert.equal(request.getResponseHeader('Access-Control-Allow-Origin'), '*') + done() }, - error: function(xhr, errorType, error) { - done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); + }) + }) + }) - it('sends object as response', function(done) { - var handler = function(request, callback) { + it('sends object as response', function (done) { + var handler = function (request, callback) { callback({ data: buffer, mimeType: 'text/html' - }); - }; - protocol.registerBufferProtocol(protocolName, handler, function(error) { + }) + } + protocol.registerBufferProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: protocolName + "://fake-host", - success: function(data) { - assert.equal(data, text); - done(); + url: protocolName + '://fake-host', + success: function (data) { + assert.equal(data, text) + done() }, - error: function(xhr, errorType, error) { - done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); + }) + }) + }) - it('fails when sending string', function(done) { - var handler = function(request, callback) { - callback(text); - }; - protocol.registerBufferProtocol(protocolName, handler, function(error) { + it('fails when sending string', function (done) { + var handler = function (request, callback) { + callback(text) + } + protocol.registerBufferProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: protocolName + "://fake-host", - success: function() { - done('request succeeded but it should not'); + url: protocolName + '://fake-host', + success: function () { + done('request succeeded but it should not') }, - error: function(xhr, errorType) { - assert.equal(errorType, 'error'); - done(); + error: function (xhr, errorType) { + assert.equal(errorType, 'error') + done() } - }); - }); - }); - }); + }) + }) + }) + }) - describe('protocol.registerFileProtocol', function() { - var filePath = path.join(__dirname, 'fixtures', 'asar', 'a.asar', 'file1'); - var fileContent = require('fs').readFileSync(filePath); - var normalPath = path.join(__dirname, 'fixtures', 'pages', 'a.html'); - var normalContent = require('fs').readFileSync(normalPath); + describe('protocol.registerFileProtocol', function () { + var filePath = path.join(__dirname, 'fixtures', 'asar', 'a.asar', 'file1') + var fileContent = require('fs').readFileSync(filePath) + var normalPath = path.join(__dirname, 'fixtures', 'pages', 'a.html') + var normalContent = require('fs').readFileSync(normalPath) - it('sends file path as response', function(done) { - var handler = function(request, callback) { - callback(filePath); - }; - protocol.registerFileProtocol(protocolName, handler, function(error) { + it('sends file path as response', function (done) { + var handler = function (request, callback) { + callback(filePath) + } + protocol.registerFileProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: protocolName + "://fake-host", - success: function(data) { - assert.equal(data, String(fileContent)); - return done(); + url: protocolName + '://fake-host', + success: function (data) { + assert.equal(data, String(fileContent)) + return done() }, - error: function(xhr, errorType, error) { - return done(error); + error: function (xhr, errorType, error) { + return done(error) } - }); - }); - }); + }) + }) + }) - it('sets Access-Control-Allow-Origin', function(done) { - var handler = function(request, callback) { - callback(filePath); - }; - protocol.registerFileProtocol(protocolName, handler, function(error) { + it('sets Access-Control-Allow-Origin', function (done) { + var handler = function (request, callback) { + callback(filePath) + } + protocol.registerFileProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: protocolName + "://fake-host", - success: function(data, status, request) { - assert.equal(data, String(fileContent)); - assert.equal(request.getResponseHeader('Access-Control-Allow-Origin'), '*'); - done(); + url: protocolName + '://fake-host', + success: function (data, status, request) { + assert.equal(data, String(fileContent)) + assert.equal(request.getResponseHeader('Access-Control-Allow-Origin'), '*') + done() }, - error: function(xhr, errorType, error) { - done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - it('sends object as response', function(done) { - var handler = function(request, callback) { + }) + }) + }) + + it('sends object as response', function (done) { + var handler = function (request, callback) { callback({ path: filePath - }); - }; - protocol.registerFileProtocol(protocolName, handler, function(error) { + }) + } + protocol.registerFileProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: protocolName + "://fake-host", - success: function(data) { - assert.equal(data, String(fileContent)); - done(); + url: protocolName + '://fake-host', + success: function (data) { + assert.equal(data, String(fileContent)) + done() }, - error: function(xhr, errorType, error) { - done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); + }) + }) + }) - it('can send normal file', function(done) { - var handler = function(request, callback) { - callback(normalPath); - }; + it('can send normal file', function (done) { + var handler = function (request, callback) { + callback(normalPath) + } - protocol.registerFileProtocol(protocolName, handler, function(error) { + protocol.registerFileProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: protocolName + "://fake-host", - success: function(data) { - assert.equal(data, String(normalContent)); - done(); + url: protocolName + '://fake-host', + success: function (data) { + assert.equal(data, String(normalContent)) + done() }, - error: function(xhr, errorType, error) { - done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); + }) + }) + }) - it('fails when sending unexist-file', function(done) { - var fakeFilePath = path.join(__dirname, 'fixtures', 'asar', 'a.asar', 'not-exist'); - var handler = function(request, callback) { - callback(fakeFilePath); - }; - protocol.registerBufferProtocol(protocolName, handler, function(error) { + it('fails when sending unexist-file', function (done) { + var fakeFilePath = path.join(__dirname, 'fixtures', 'asar', 'a.asar', 'not-exist') + var handler = function (request, callback) { + callback(fakeFilePath) + } + protocol.registerFileProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: protocolName + "://fake-host", - success: function() { - done('request succeeded but it should not'); + url: protocolName + '://fake-host', + success: function () { + done('request succeeded but it should not') }, - error: function(xhr, errorType) { - assert.equal(errorType, 'error'); - done(); + error: function (xhr, errorType) { + assert.equal(errorType, 'error') + done() } - }); - }); - }); + }) + }) + }) - it('fails when sending unsupported content', function(done) { - var handler = function(request, callback) { - callback(new Date); - }; - protocol.registerBufferProtocol(protocolName, handler, function(error) { + it('fails when sending unsupported content', function (done) { + var handler = function (request, callback) { + callback(new Date()) + } + protocol.registerFileProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: protocolName + "://fake-host", - success: function() { - done('request succeeded but it should not'); + url: protocolName + '://fake-host', + success: function () { + done('request succeeded but it should not') }, - error: function(xhr, errorType) { - assert.equal(errorType, 'error'); - done(); + error: function (xhr, errorType) { + assert.equal(errorType, 'error') + done() } - }); - }); - }); - }); + }) + }) + }) + }) - describe('protocol.registerHttpProtocol', function() { - it('sends url as response', function(done) { - var server = http.createServer(function(req, res) { - assert.notEqual(req.headers.accept, ''); - res.end(text); - server.close(); - }); - server.listen(0, '127.0.0.1', function() { - var port = server.address().port; - var url = "http://127.0.0.1:" + port; - var handler = function(request, callback) { + describe('protocol.registerHttpProtocol', function () { + it('sends url as response', function (done) { + var server = http.createServer(function (req, res) { + assert.notEqual(req.headers.accept, '') + res.end(text) + server.close() + }) + server.listen(0, '127.0.0.1', function () { + var port = server.address().port + var url = 'http://127.0.0.1:' + port + var handler = function (request, callback) { callback({ url: url - }); - }; - protocol.registerHttpProtocol(protocolName, handler, function(error) { + }) + } + protocol.registerHttpProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: protocolName + "://fake-host", - success: function(data) { - assert.equal(data, text); - done(); + url: protocolName + '://fake-host', + success: function (data) { + assert.equal(data, text) + done() }, - error: function(xhr, errorType, error) { - done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - }); + }) + }) + }) + }) - it('fails when sending invalid url', function(done) { - var handler = function(request, callback) { + it('fails when sending invalid url', function (done) { + var handler = function (request, callback) { callback({ url: 'url' - }); - }; - protocol.registerHttpProtocol(protocolName, handler, function(error) { + }) + } + protocol.registerHttpProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: protocolName + "://fake-host", - success: function() { - done('request succeeded but it should not'); + url: protocolName + '://fake-host', + success: function () { + done('request succeeded but it should not') }, - error: function(xhr, errorType) { - assert.equal(errorType, 'error'); - done(); + error: function (xhr, errorType) { + assert.equal(errorType, 'error') + done() } - }); - }); - }); + }) + }) + }) - it('fails when sending unsupported content', function(done) { - var handler = function(request, callback) { - callback(new Date); - }; - protocol.registerHttpProtocol(protocolName, handler, function(error) { + it('fails when sending unsupported content', function (done) { + var handler = function (request, callback) { + callback(new Date()) + } + protocol.registerHttpProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: protocolName + "://fake-host", - success: function() { - done('request succeeded but it should not'); + url: protocolName + '://fake-host', + success: function () { + done('request succeeded but it should not') }, - error: function(xhr, errorType) { - assert.equal(errorType, 'error'); - done(); + error: function (xhr, errorType) { + assert.equal(errorType, 'error') + done() } - }); - }); - }); - }); + }) + }) + }) - describe('protocol.isProtocolHandled', function() { - it('returns true for file:', function(done) { - protocol.isProtocolHandled('file', function(result) { - assert.equal(result, true); - done(); - }); - }); + it('works when target URL redirects', function (done) { + var contents = null + var server = http.createServer(function (req, res) { + if (req.url == '/serverRedirect') { + res.statusCode = 301 + res.setHeader('Location', 'http://' + req.rawHeaders[1]) + res.end() + } else { + res.end(text) + } + }) + server.listen(0, '127.0.0.1', function () { + var port = server.address().port + var url = `${protocolName}://fake-host` + var redirectURL = `http://127.0.0.1:${port}/serverRedirect` + var handler = function (request, callback) { + callback({ + url: redirectURL + }) + } + protocol.registerHttpProtocol(protocolName, handler, function (error) { + if (error) { + return done(error) + } + contents = webContents.create({}) + contents.on('did-finish-load', function () { + assert.equal(contents.getURL(), url) + server.close() + contents.destroy() + done() + }) + contents.loadURL(url) + }) + }) + }) + }) - it('returns true for http:', function(done) { - protocol.isProtocolHandled('http', function(result) { - assert.equal(result, true); - done(); - }); - }); + describe('protocol.isProtocolHandled', function () { + it('returns true for file:', function (done) { + protocol.isProtocolHandled('file', function (result) { + assert.equal(result, true) + done() + }) + }) - it('returns true for https:', function(done) { - protocol.isProtocolHandled('https', function(result) { - assert.equal(result, true); - done(); - }); - }); + it('returns true for http:', function (done) { + protocol.isProtocolHandled('http', function (result) { + assert.equal(result, true) + done() + }) + }) - it('returns false when scheme is not registred', function(done) { - protocol.isProtocolHandled('no-exist', function(result) { - assert.equal(result, false); - done(); - }); - }); + it('returns true for https:', function (done) { + protocol.isProtocolHandled('https', function (result) { + assert.equal(result, true) + done() + }) + }) - it('returns true for custom protocol', function(done) { - var emptyHandler = function(request, callback) { - callback(); - }; - protocol.registerStringProtocol(protocolName, emptyHandler, function(error) { - assert.equal(error, null); - protocol.isProtocolHandled(protocolName, function(result) { - assert.equal(result, true); - done(); - }); - }); - }); + it('returns false when scheme is not registred', function (done) { + protocol.isProtocolHandled('no-exist', function (result) { + assert.equal(result, false) + done() + }) + }) - it('returns true for intercepted protocol', function(done) { - var emptyHandler = function(request, callback) { - callback(); - }; - protocol.interceptStringProtocol('http', emptyHandler, function(error) { - assert.equal(error, null); - protocol.isProtocolHandled('http', function(result) { - assert.equal(result, true); - done(); - }); - }); - }); - }); + it('returns true for custom protocol', function (done) { + var emptyHandler = function (request, callback) { + callback() + } + protocol.registerStringProtocol(protocolName, emptyHandler, function (error) { + assert.equal(error, null) + protocol.isProtocolHandled(protocolName, function (result) { + assert.equal(result, true) + done() + }) + }) + }) - describe('protocol.intercept(Any)Protocol', function() { - var emptyHandler = function(request, callback) { - callback(); - }; + it('returns true for intercepted protocol', function (done) { + var emptyHandler = function (request, callback) { + callback() + } + protocol.interceptStringProtocol('http', emptyHandler, function (error) { + assert.equal(error, null) + protocol.isProtocolHandled('http', function (result) { + assert.equal(result, true) + done() + }) + }) + }) + }) - it('throws error when scheme is already intercepted', function(done) { - protocol.interceptStringProtocol('http', emptyHandler, function(error) { - assert.equal(error, null); - protocol.interceptBufferProtocol('http', emptyHandler, function(error) { - assert.notEqual(error, null); - done(); - }); - }); - }); + describe('protocol.intercept(Any)Protocol', function () { + var emptyHandler = function (request, callback) { + callback() + } - it('does not crash when handler is called twice', function(done) { - var doubleHandler = function(request, callback) { + it('throws error when scheme is already intercepted', function (done) { + protocol.interceptStringProtocol('http', emptyHandler, function (error) { + assert.equal(error, null) + protocol.interceptBufferProtocol('http', emptyHandler, function (error) { + assert.notEqual(error, null) + done() + }) + }) + }) + + it('does not crash when handler is called twice', function (done) { + var doubleHandler = function (request, callback) { try { - callback(text); - callback(); + callback(text) + callback() } catch (error) { // Ignore error } - }; - protocol.interceptStringProtocol('http', doubleHandler, function(error) { - if (error) { - return done(error); - } - $.ajax({ - url: 'http://fake-host', - success: function(data) { - assert.equal(data, text); - done(); - }, - error: function(xhr, errorType, error) { - done(error); - } - }); - }); - }); - - it('sends error when callback is called with nothing', function(done) { - if (process.env.TRAVIS === 'true') { - return done(); } - protocol.interceptBufferProtocol('http', emptyHandler, function(error) { + protocol.interceptStringProtocol('http', doubleHandler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ url: 'http://fake-host', - success: function() { - done('request succeeded but it should not'); + success: function (data) { + assert.equal(data, text) + done() }, - error: function(xhr, errorType) { - assert.equal(errorType, 'error'); - done(); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - }); + }) + }) + }) - describe('protocol.interceptStringProtocol', function() { - it('can intercept http protocol', function(done) { - var handler = function(request, callback) { - callback(text); - }; - protocol.interceptStringProtocol('http', handler, function(error) { + it('sends error when callback is called with nothing', function (done) { + if (process.env.TRAVIS === 'true') { + return done() + } + protocol.interceptBufferProtocol('http', emptyHandler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ url: 'http://fake-host', - success: function(data) { - assert.equal(data, text); - done(); + success: function () { + done('request succeeded but it should not') }, - error: function(xhr, errorType, error) { - done(error); + error: function (xhr, errorType) { + assert.equal(errorType, 'error') + done() } - }); - }); - }); + }) + }) + }) + }) - it('can set content-type', function(done) { - var handler = function(request, callback) { + describe('protocol.interceptStringProtocol', function () { + it('can intercept http protocol', function (done) { + var handler = function (request, callback) { + callback(text) + } + protocol.interceptStringProtocol('http', handler, function (error) { + if (error) { + return done(error) + } + $.ajax({ + url: 'http://fake-host', + success: function (data) { + assert.equal(data, text) + done() + }, + error: function (xhr, errorType, error) { + done(error) + } + }) + }) + }) + + it('can set content-type', function (done) { + var handler = function (request, callback) { callback({ mimeType: 'application/json', data: '{"value": 1}' - }); - }; - protocol.interceptStringProtocol('http', handler, function(error) { + }) + } + protocol.interceptStringProtocol('http', handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ url: 'http://fake-host', - success: function(data) { - assert.equal(typeof data, 'object'); - assert.equal(data.value, 1); - done(); + success: function (data) { + assert.equal(typeof data, 'object') + assert.equal(data.value, 1) + done() }, - error: function(xhr, errorType, error) { - done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); + }) + }) + }) - it('can receive post data', function(done) { - var handler = function(request, callback) { - var uploadData = request.uploadData[0].bytes.toString(); + it('can receive post data', function (done) { + var handler = function (request, callback) { + var uploadData = request.uploadData[0].bytes.toString() callback({ data: uploadData - }); - }; - protocol.interceptStringProtocol('http', handler, function(error) { + }) + } + protocol.interceptStringProtocol('http', handler, function (error) { if (error) { - return done(error); - } - $.ajax({ - url: "http://fake-host", - type: "POST", - data: postData, - success: function(data) { - assert.deepEqual(qs.parse(data), postData); - done(); - }, - error: function(xhr, errorType, error) { - done(error); - } - }); - }); - }); - }); - - describe('protocol.interceptBufferProtocol', function() { - it('can intercept http protocol', function(done) { - var handler = function(request, callback) { - callback(new Buffer(text)); - }; - protocol.interceptBufferProtocol('http', handler, function(error) { - if (error) { - return done(error); + return done(error) } $.ajax({ url: 'http://fake-host', - success: function(data) { - assert.equal(data, text); - done(); + type: 'POST', + data: postData, + success: function (data) { + assert.deepEqual(qs.parse(data), postData) + done() }, - error: function(xhr, errorType, error) { - done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); + }) + }) + }) + }) - it('can receive post data', function(done) { - var handler = function(request, callback) { - var uploadData = request.uploadData[0].bytes; - callback(uploadData); - }; - protocol.interceptBufferProtocol('http', handler, function(error) { + describe('protocol.interceptBufferProtocol', function () { + it('can intercept http protocol', function (done) { + var handler = function (request, callback) { + callback(new Buffer(text)) + } + protocol.interceptBufferProtocol('http', handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: "http://fake-host", - type: "POST", - data: postData, - success: function(data) { - assert.equal(data, $.param(postData)); - done(); + url: 'http://fake-host', + success: function (data) { + assert.equal(data, text) + done() }, - error: function(xhr, errorType, error) { - done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - }); + }) + }) + }) - describe('protocol.interceptHttpProtocol', function() { - it('can send POST request', function(done) { - var server = http.createServer(function(req, res) { - var body = ''; - req.on('data', function(chunk) { - body += chunk; - }); - req.on('end', function() { - res.end(body); - }); - server.close(); - }); - server.listen(0, '127.0.0.1', function() { - var port = server.address().port; - var url = "http://127.0.0.1:" + port; - var handler = function(request, callback) { + it('can receive post data', function (done) { + var handler = function (request, callback) { + var uploadData = request.uploadData[0].bytes + callback(uploadData) + } + protocol.interceptBufferProtocol('http', handler, function (error) { + if (error) { + return done(error) + } + $.ajax({ + url: 'http://fake-host', + type: 'POST', + data: postData, + success: function (data) { + assert.equal(data, $.param(postData)) + done() + }, + error: function (xhr, errorType, error) { + done(error) + } + }) + }) + }) + }) + + describe('protocol.interceptHttpProtocol', function () { + it('can send POST request', function (done) { + var server = http.createServer(function (req, res) { + var body = '' + req.on('data', function (chunk) { + body += chunk + }) + req.on('end', function () { + res.end(body) + }) + server.close() + }) + server.listen(0, '127.0.0.1', function () { + var port = server.address().port + var url = 'http://127.0.0.1:' + port + var handler = function (request, callback) { var data = { url: url, method: 'POST', @@ -775,43 +812,113 @@ describe('protocol module', function() { data: request.uploadData[0].bytes.toString() }, session: null - }; - callback(data); - }; - protocol.interceptHttpProtocol('http', handler, function(error) { + } + callback(data) + } + protocol.interceptHttpProtocol('http', handler, function (error) { if (error) { - return done(error); + return done(error) } $.ajax({ - url: "http://fake-host", - type: "POST", + url: 'http://fake-host', + type: 'POST', data: postData, - success: function(data) { - assert.deepEqual(qs.parse(data), postData); - done(); + success: function (data) { + assert.deepEqual(qs.parse(data), postData) + done() }, - error: function(xhr, errorType, error) { - done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - }); - }); + }) + }) + }) + }) + }) - describe('protocol.uninterceptProtocol', function() { - it('returns error when scheme does not exist', function(done) { - protocol.uninterceptProtocol('not-exist', function(error) { - assert.notEqual(error, null); - done(); - }); - }); + describe('protocol.uninterceptProtocol', function () { + it('returns error when scheme does not exist', function (done) { + protocol.uninterceptProtocol('not-exist', function (error) { + assert.notEqual(error, null) + done() + }) + }) - it('returns error when scheme is not intercepted', function(done) { - protocol.uninterceptProtocol('http', function(error) { - assert.notEqual(error, null); - done(); - }); - }); - }); -}); + it('returns error when scheme is not intercepted', function (done) { + protocol.uninterceptProtocol('http', function (error) { + assert.notEqual(error, null) + done() + }) + }) + }) + + describe('protocol.registerStandardSchemes', function () { + const standardScheme = remote.getGlobal('standardScheme') + const origin = standardScheme + '://fake-host' + const imageURL = origin + '/test.png' + const filePath = path.join(__dirname, 'fixtures', 'pages', 'b.html') + const fileContent = '' + var w = null + var success = null + + beforeEach(function () { + w = new BrowserWindow({show: false}) + success = false + }) + + afterEach(function (done) { + protocol.unregisterProtocol(standardScheme, function () { + if (w != null) { + w.destroy() + } + w = null + done() + }) + }) + + it('resolves relative resources', function (done) { + var handler = function (request, callback) { + if (request.url === imageURL) { + success = true + callback() + } else { + callback(filePath) + } + } + protocol.registerFileProtocol(standardScheme, handler, function (error) { + if (error) { + return done(error) + } + w.webContents.on('did-finish-load', function () { + assert(success) + done() + }) + w.loadURL(origin) + }) + }) + + it('resolves absolute resources', function (done) { + var handler = function (request, callback) { + if (request.url === imageURL) { + success = true + callback() + } else { + callback({ + data: fileContent, + mimeType: 'text/html' + }) + } + } + protocol.registerStringProtocol(standardScheme, handler, function (error) { + if (error) { + return done(error) + } + w.webContents.on('did-finish-load', function () { + assert(success) + done() + }) + w.loadURL(origin) + }) + }) + }) +}) diff --git a/spec/api-screen-spec.js b/spec/api-screen-spec.js index 34828e863e82..8c4f4305baff 100644 --- a/spec/api-screen-spec.js +++ b/spec/api-screen-spec.js @@ -1,21 +1,21 @@ -const assert = require('assert'); -const screen = require('electron').screen; +const assert = require('assert') +const screen = require('electron').screen -describe('screen module', function() { - describe('screen.getCursorScreenPoint()', function() { - it('returns a point object', function() { - var point = screen.getCursorScreenPoint(); - assert.equal(typeof point.x, 'number'); - assert.equal(typeof point.y, 'number'); - }); - }); +describe('screen module', function () { + describe('screen.getCursorScreenPoint()', function () { + it('returns a point object', function () { + var point = screen.getCursorScreenPoint() + assert.equal(typeof point.x, 'number') + assert.equal(typeof point.y, 'number') + }) + }) - describe('screen.getPrimaryDisplay()', function() { - it('returns a display object', function() { - var display = screen.getPrimaryDisplay(); - assert.equal(typeof display.scaleFactor, 'number'); - assert(display.size.width > 0); - assert(display.size.height > 0); - }); - }); -}); + describe('screen.getPrimaryDisplay()', function () { + it('returns a display object', function () { + var display = screen.getPrimaryDisplay() + assert.equal(typeof display.scaleFactor, 'number') + assert(display.size.width > 0) + assert(display.size.height > 0) + }) + }) +}) diff --git a/spec/api-session-spec.js b/spec/api-session-spec.js index 9fedcc29e779..d655788c79b3 100644 --- a/spec/api-session-spec.js +++ b/spec/api-session-spec.js @@ -1,265 +1,265 @@ -const assert = require('assert'); -const http = require('http'); -const path = require('path'); -const fs = require('fs'); +const assert = require('assert') +const http = require('http') +const path = require('path') +const fs = require('fs') -const ipcRenderer = require('electron').ipcRenderer; -const remote = require('electron').remote; +const ipcRenderer = require('electron').ipcRenderer +const remote = require('electron').remote -const ipcMain = remote.ipcMain; -const session = remote.session; -const BrowserWindow = remote.BrowserWindow; +const ipcMain = remote.ipcMain +const session = remote.session +const BrowserWindow = remote.BrowserWindow -describe('session module', function() { - this.timeout(10000); +describe('session module', function () { + this.timeout(10000) - var fixtures = path.resolve(__dirname, 'fixtures'); - var w = null; - var url = "http://127.0.0.1"; + var fixtures = path.resolve(__dirname, 'fixtures') + var w = null + var url = 'http://127.0.0.1' - beforeEach(function() { + beforeEach(function () { w = new BrowserWindow({ show: false, width: 400, height: 400 - }); - }); + }) + }) - afterEach(function() { - w.destroy(); - }); + afterEach(function () { + w.destroy() + }) - describe('session.cookies', function() { - it('should get cookies', function(done) { - var server = http.createServer(function(req, res) { - res.setHeader('Set-Cookie', ['0=0']); - res.end('finished'); - server.close(); - }); - server.listen(0, '127.0.0.1', function() { - var port = server.address().port; - w.loadURL(url + ":" + port); - w.webContents.on('did-finish-load', function() { + describe('session.cookies', function () { + it('should get cookies', function (done) { + var server = http.createServer(function (req, res) { + res.setHeader('Set-Cookie', ['0=0']) + res.end('finished') + server.close() + }) + server.listen(0, '127.0.0.1', function () { + var port = server.address().port + w.loadURL(url + ':' + port) + w.webContents.on('did-finish-load', function () { w.webContents.session.cookies.get({ url: url - }, function(error, list) { - var cookie, i, len; + }, function (error, list) { + var cookie, i, len if (error) { - return done(error); + return done(error) } for (i = 0, len = list.length; i < len; i++) { - cookie = list[i]; + cookie = list[i] if (cookie.name === '0') { if (cookie.value === '0') { - return done(); + return done() } else { - return done("cookie value is " + cookie.value + " while expecting 0"); + return done('cookie value is ' + cookie.value + ' while expecting 0') } } } - done('Can not find cookie'); - }); - }); - }); - }); + done('Can not find cookie') + }) + }) + }) + }) - it('should over-write the existent cookie', function(done) { + it('should over-write the existent cookie', function (done) { session.defaultSession.cookies.set({ url: url, name: '1', value: '1' - }, function(error) { + }, function (error) { if (error) { - return done(error); + return done(error) } session.defaultSession.cookies.get({ url: url - }, function(error, list) { - var cookie, i, len; + }, function (error, list) { + var cookie, i, len if (error) { - return done(error); + return done(error) } for (i = 0, len = list.length; i < len; i++) { - cookie = list[i]; + cookie = list[i] if (cookie.name === '1') { if (cookie.value === '1') { - return done(); + return done() } else { - return done("cookie value is " + cookie.value + " while expecting 1"); + return done('cookie value is ' + cookie.value + ' while expecting 1') } } } - done('Can not find cookie'); - }); - }); - }); + done('Can not find cookie') + }) + }) + }) - it('should remove cookies', function(done) { + it('should remove cookies', function (done) { session.defaultSession.cookies.set({ url: url, name: '2', value: '2' - }, function(error) { + }, function (error) { if (error) { - return done(error); + return done(error) } - session.defaultSession.cookies.remove(url, '2', function() { + session.defaultSession.cookies.remove(url, '2', function () { session.defaultSession.cookies.get({ url: url - }, function(error, list) { - var cookie, i, len; + }, function (error, list) { + var cookie, i, len if (error) { - return done(error); + return done(error) } for (i = 0, len = list.length; i < len; i++) { - cookie = list[i]; + cookie = list[i] if (cookie.name === '2') { - return done('Cookie not deleted'); + return done('Cookie not deleted') } } - done(); - }); - }); - }); - }); - }); + done() + }) + }) + }) + }) + }) - describe('session.clearStorageData(options)', function() { - fixtures = path.resolve(__dirname, 'fixtures'); - it('clears localstorage data', function(done) { - ipcMain.on('count', function(event, count) { - ipcMain.removeAllListeners('count'); - assert(!count); - done(); - }); - w.loadURL('file://' + path.join(fixtures, 'api', 'localstorage.html')); - w.webContents.on('did-finish-load', function() { + describe('session.clearStorageData(options)', function () { + fixtures = path.resolve(__dirname, 'fixtures') + it('clears localstorage data', function (done) { + ipcMain.on('count', function (event, count) { + ipcMain.removeAllListeners('count') + assert(!count) + done() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'localstorage.html')) + w.webContents.on('did-finish-load', function () { var options = { - origin: "file://", + origin: 'file://', storages: ['localstorage'], quotas: ['persistent'] - }; - w.webContents.session.clearStorageData(options, function() { - w.webContents.send('getcount'); - }); - }); - }); - }); + } + w.webContents.session.clearStorageData(options, function () { + w.webContents.send('getcount') + }) + }) + }) + }) - describe('session will-download event', function() { - var w = null; + describe('session will-download event', function () { + var w = null - beforeEach(function() { + beforeEach(function () { w = new BrowserWindow({ show: false, width: 400, height: 400 - }); - }); + }) + }) - afterEach(function() { - w.destroy(); - }); + afterEach(function () { + w.destroy() + }) - it('can cancel default download behavior', function(done) { - const mockFile = new Buffer(1024); - const contentDisposition = 'inline; filename="mockFile.txt"'; - const downloadServer = http.createServer(function(req, res) { + it('can cancel default download behavior', function (done) { + const mockFile = new Buffer(1024) + const contentDisposition = 'inline; filename="mockFile.txt"' + const downloadServer = http.createServer(function (req, res) { res.writeHead(200, { 'Content-Length': mockFile.length, 'Content-Type': 'application/plain', 'Content-Disposition': contentDisposition - }); - res.end(mockFile); - downloadServer.close(); - }); + }) + res.end(mockFile) + downloadServer.close() + }) - downloadServer.listen(0, '127.0.0.1', function() { - const port = downloadServer.address().port; - const url = "http://127.0.0.1:" + port + '/'; + downloadServer.listen(0, '127.0.0.1', function () { + const port = downloadServer.address().port + const url = 'http://127.0.0.1:' + port + '/' - ipcRenderer.sendSync('set-download-option', false, true); - w.loadURL(url); - ipcRenderer.once('download-error', function(event, downloadUrl, filename, error) { - assert.equal(downloadUrl, url); - assert.equal(filename, 'mockFile.txt'); - assert.equal(error, 'Object has been destroyed'); - done(); - }); - }); - }); - }); + ipcRenderer.sendSync('set-download-option', false, true) + w.loadURL(url) + ipcRenderer.once('download-error', function (event, downloadUrl, filename, error) { + assert.equal(downloadUrl, url) + assert.equal(filename, 'mockFile.txt') + assert.equal(error, 'Object has been destroyed') + done() + }) + }) + }) + }) - describe('DownloadItem', function() { - var mockPDF = new Buffer(1024 * 1024 * 5); - var contentDisposition = 'inline; filename="mock.pdf"'; - var downloadFilePath = path.join(fixtures, 'mock.pdf'); - var downloadServer = http.createServer(function(req, res) { + describe('DownloadItem', function () { + var mockPDF = new Buffer(1024 * 1024 * 5) + var contentDisposition = 'inline; filename="mock.pdf"' + var downloadFilePath = path.join(fixtures, 'mock.pdf') + var downloadServer = http.createServer(function (req, res) { res.writeHead(200, { 'Content-Length': mockPDF.length, 'Content-Type': 'application/pdf', 'Content-Disposition': contentDisposition - }); - res.end(mockPDF); - downloadServer.close(); - }); - var assertDownload = function(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port) { - assert.equal(state, 'completed'); - assert.equal(filename, 'mock.pdf'); - assert.equal(url, "http://127.0.0.1:" + port + "/"); - assert.equal(mimeType, 'application/pdf'); - assert.equal(receivedBytes, mockPDF.length); - assert.equal(totalBytes, mockPDF.length); - assert.equal(disposition, contentDisposition); - assert(fs.existsSync(downloadFilePath)); - fs.unlinkSync(downloadFilePath); - }; + }) + res.end(mockPDF) + downloadServer.close() + }) + var assertDownload = function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port) { + assert.equal(state, 'completed') + assert.equal(filename, 'mock.pdf') + assert.equal(url, 'http://127.0.0.1:' + port + '/') + assert.equal(mimeType, 'application/pdf') + assert.equal(receivedBytes, mockPDF.length) + assert.equal(totalBytes, mockPDF.length) + assert.equal(disposition, contentDisposition) + assert(fs.existsSync(downloadFilePath)) + fs.unlinkSync(downloadFilePath) + } - it('can download using BrowserWindow.loadURL', function(done) { - downloadServer.listen(0, '127.0.0.1', function() { - var port = downloadServer.address().port; - ipcRenderer.sendSync('set-download-option', false, false); - w.loadURL(url + ":" + port); - ipcRenderer.once('download-done', function(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) { - assertDownload(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port); - done(); - }); - }); - }); + it('can download using BrowserWindow.loadURL', function (done) { + downloadServer.listen(0, '127.0.0.1', function () { + var port = downloadServer.address().port + ipcRenderer.sendSync('set-download-option', false, false) + w.loadURL(url + ':' + port) + ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) { + assertDownload(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port) + done() + }) + }) + }) - it('can download using WebView.downloadURL', function(done) { - downloadServer.listen(0, '127.0.0.1', function() { - var port = downloadServer.address().port; - ipcRenderer.sendSync('set-download-option', false, false); - var webview = new WebView; - webview.src = "file://" + fixtures + "/api/blank.html"; - webview.addEventListener('did-finish-load', function() { - webview.downloadURL(url + ":" + port + "/"); - }); - ipcRenderer.once('download-done', function(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) { - assertDownload(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port); - document.body.removeChild(webview); - done(); - }); - document.body.appendChild(webview); - }); - }); + it('can download using WebView.downloadURL', function (done) { + downloadServer.listen(0, '127.0.0.1', function () { + var port = downloadServer.address().port + ipcRenderer.sendSync('set-download-option', false, false) + var webview = new WebView() + webview.src = 'file://' + fixtures + '/api/blank.html' + webview.addEventListener('did-finish-load', function () { + webview.downloadURL(url + ':' + port + '/') + }) + ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) { + assertDownload(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port) + document.body.removeChild(webview) + done() + }) + document.body.appendChild(webview) + }) + }) - it('can cancel download', function(done) { - downloadServer.listen(0, '127.0.0.1', function() { - var port = downloadServer.address().port; - ipcRenderer.sendSync('set-download-option', true, false); - w.loadURL(url + ":" + port + "/"); - ipcRenderer.once('download-done', function(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) { - assert.equal(state, 'cancelled'); - assert.equal(filename, 'mock.pdf'); - assert.equal(mimeType, 'application/pdf'); - assert.equal(receivedBytes, 0); - assert.equal(totalBytes, mockPDF.length); - assert.equal(disposition, contentDisposition); - done(); - }); - }); - }); - }); -}); + it('can cancel download', function (done) { + downloadServer.listen(0, '127.0.0.1', function () { + var port = downloadServer.address().port + ipcRenderer.sendSync('set-download-option', true, false) + w.loadURL(url + ':' + port + '/') + ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) { + assert.equal(state, 'cancelled') + assert.equal(filename, 'mock.pdf') + assert.equal(mimeType, 'application/pdf') + assert.equal(receivedBytes, 0) + assert.equal(totalBytes, mockPDF.length) + assert.equal(disposition, contentDisposition) + done() + }) + }) + }) + }) +}) diff --git a/spec/api-system-preferences-spec.js b/spec/api-system-preferences-spec.js new file mode 100644 index 000000000000..333c4dbacb4e --- /dev/null +++ b/spec/api-system-preferences-spec.js @@ -0,0 +1,22 @@ +const assert = require('assert') +const {remote} = require('electron') +const {systemPreferences} = remote + +describe('systemPreferences module', function () { + if (process.platform !== 'darwin') { + return + } + + describe('systemPreferences.getUserDefault(key, type)', function () { + it('returns values for known user defaults', function () { + let locale = systemPreferences.getUserDefault('AppleLocale', 'string') + assert.notEqual(locale, null) + assert(locale.length > 0) + + let languages = systemPreferences.getUserDefault('AppleLanguages', 'array') + assert.notEqual(languages, null) + assert(languages.length > 0) + }) + }) + +}) diff --git a/spec/api-web-frame-spec.js b/spec/api-web-frame-spec.js index 15f31aa8f095..450bf1c33ec1 100644 --- a/spec/api-web-frame-spec.js +++ b/spec/api-web-frame-spec.js @@ -1,19 +1,19 @@ -const assert = require('assert'); -const path = require('path'); -const webFrame = require('electron').webFrame; +const assert = require('assert') +const path = require('path') +const webFrame = require('electron').webFrame -describe('webFrame module', function() { - var fixtures = path.resolve(__dirname, 'fixtures'); - describe('webFrame.registerURLSchemeAsPrivileged', function() { - it('supports fetch api', function(done) { - webFrame.registerURLSchemeAsPrivileged('file'); - var url = "file://" + fixtures + "/assets/logo.png"; - fetch(url).then(function(response) { - assert(response.ok); - done(); - }).catch(function(err) { - done('unexpected error : ' + err); - }); - }); - }); -}); +describe('webFrame module', function () { + var fixtures = path.resolve(__dirname, 'fixtures') + describe('webFrame.registerURLSchemeAsPrivileged', function () { + it('supports fetch api', function (done) { + webFrame.registerURLSchemeAsPrivileged('file') + var url = 'file://' + fixtures + '/assets/logo.png' + window.fetch(url).then(function (response) { + assert(response.ok) + done() + }).catch(function (err) { + done('unexpected error : ' + err) + }) + }) + }) +}) diff --git a/spec/api-web-request-spec.js b/spec/api-web-request-spec.js index 77f160a20041..211c69a14765 100644 --- a/spec/api-web-request-spec.js +++ b/spec/api-web-request-spec.js @@ -1,413 +1,456 @@ -const assert = require('assert'); -const http = require('http'); -const qs = require('querystring'); -const remote = require('electron').remote; -const session = remote.session; +const assert = require('assert') +const http = require('http') +const qs = require('querystring') +const remote = require('electron').remote +const session = remote.session -describe('webRequest module', function() { - var ses = session.defaultSession; - var server = http.createServer(function(req, res) { - res.setHeader('Custom', ['Header']); - var content = req.url; - if (req.headers.accept === '*/*;test/header') { - content += 'header/received'; +describe('webRequest module', function () { + var ses = session.defaultSession + var server = http.createServer(function (req, res) { + if (req.url == '/serverRedirect') { + res.statusCode = 301 + res.setHeader('Location', 'http://' + req.rawHeaders[1]) + res.end() + } else { + res.setHeader('Custom', ['Header']) + var content = req.url + if (req.headers.accept === '*/*;test/header') { + content += 'header/received' + } + res.end(content) } - res.end(content); - }); - var defaultURL = null; + }) + var defaultURL = null - before(function(done) { - server.listen(0, '127.0.0.1', function() { - var port = server.address().port; - defaultURL = "http://127.0.0.1:" + port + "/"; - done(); - }); - }); + before(function (done) { + server.listen(0, '127.0.0.1', function () { + var port = server.address().port + defaultURL = 'http://127.0.0.1:' + port + '/' + done() + }) + }) - after(function() { - server.close(); - }); + after(function () { + server.close() + }) - describe('webRequest.onBeforeRequest', function() { - afterEach(function() { - ses.webRequest.onBeforeRequest(null); - }); + describe('webRequest.onBeforeRequest', function () { + afterEach(function () { + ses.webRequest.onBeforeRequest(null) + }) - it('can cancel the request', function(done) { - ses.webRequest.onBeforeRequest(function(details, callback) { + it('can cancel the request', function (done) { + ses.webRequest.onBeforeRequest(function (details, callback) { callback({ cancel: true - }); - }); + }) + }) $.ajax({ url: defaultURL, - success: function() { - done('unexpected success'); + success: function () { + done('unexpected success') }, - error: function() { - done(); + error: function () { + done() } - }); - }); + }) + }) - it('can filter URLs', function(done) { + it('can filter URLs', function (done) { var filter = { - urls: [defaultURL + "filter/*"] - }; - ses.webRequest.onBeforeRequest(filter, function(details, callback) { + urls: [defaultURL + 'filter/*'] + } + ses.webRequest.onBeforeRequest(filter, function (details, callback) { callback({ cancel: true - }); - }); + }) + }) $.ajax({ - url: defaultURL + "nofilter/test", - success: function(data) { - assert.equal(data, '/nofilter/test'); + url: defaultURL + 'nofilter/test', + success: function (data) { + assert.equal(data, '/nofilter/test') $.ajax({ - url: defaultURL + "filter/test", - success: function() { - done('unexpected success'); + url: defaultURL + 'filter/test', + success: function () { + done('unexpected success') }, - error: function() { - done(); + error: function () { + done() } - }); + }) }, - error: function(xhr, errorType) { - done(errorType); + error: function (xhr, errorType) { + done(errorType) } - }); - }); + }) + }) - it('receives details object', function(done) { - ses.webRequest.onBeforeRequest(function(details, callback) { - assert.equal(typeof details.id, 'number'); - assert.equal(typeof details.timestamp, 'number'); - assert.equal(details.url, defaultURL); - assert.equal(details.method, 'GET'); - assert.equal(details.resourceType, 'xhr'); - assert(!details.uploadData); - callback({}); - }); + it('receives details object', function (done) { + ses.webRequest.onBeforeRequest(function (details, callback) { + assert.equal(typeof details.id, 'number') + assert.equal(typeof details.timestamp, 'number') + assert.equal(details.url, defaultURL) + assert.equal(details.method, 'GET') + assert.equal(details.resourceType, 'xhr') + assert(!details.uploadData) + callback({}) + }) $.ajax({ url: defaultURL, - success: function(data) { - assert.equal(data, '/'); - done(); + success: function (data) { + assert.equal(data, '/') + done() }, - error: function(xhr, errorType) { - done(errorType); + error: function (xhr, errorType) { + done(errorType) } - }); - }); + }) + }) - it('receives post data in details object', function(done) { + it('receives post data in details object', function (done) { var postData = { name: 'post test', type: 'string' - }; - ses.webRequest.onBeforeRequest(function(details, callback) { - assert.equal(details.url, defaultURL); - assert.equal(details.method, 'POST'); - assert.equal(details.uploadData.length, 1); - var data = qs.parse(details.uploadData[0].bytes.toString()); - assert.deepEqual(data, postData); + } + ses.webRequest.onBeforeRequest(function (details, callback) { + assert.equal(details.url, defaultURL) + assert.equal(details.method, 'POST') + assert.equal(details.uploadData.length, 1) + var data = qs.parse(details.uploadData[0].bytes.toString()) + assert.deepEqual(data, postData) callback({ cancel: true - }); - }); + }) + }) $.ajax({ url: defaultURL, type: 'POST', data: postData, - success: function() { - }, - error: function() { - done(); + success: function () {}, + error: function () { + done() } - }); - }); + }) + }) - it('can redirect the request', function(done) { - ses.webRequest.onBeforeRequest(function(details, callback) { + it('can redirect the request', function (done) { + ses.webRequest.onBeforeRequest(function (details, callback) { if (details.url === defaultURL) { callback({ - redirectURL: defaultURL + "redirect" - }); + redirectURL: defaultURL + 'redirect' + }) } else { - callback({}); + callback({}) } - }); + }) $.ajax({ url: defaultURL, - success: function(data) { - assert.equal(data, '/redirect'); - done(); + success: function (data) { + assert.equal(data, '/redirect') + done() }, - error: function(xhr, errorType) { - done(errorType); + error: function (xhr, errorType) { + done(errorType) } - }); - }); - }); + }) + }) + }) - describe('webRequest.onBeforeSendHeaders', function() { - afterEach(function() { - ses.webRequest.onBeforeSendHeaders(null); - }); + describe('webRequest.onBeforeSendHeaders', function () { + afterEach(function () { + ses.webRequest.onBeforeSendHeaders(null) + }) - it('receives details object', function(done) { - ses.webRequest.onBeforeSendHeaders(function(details, callback) { - assert.equal(typeof details.requestHeaders, 'object'); - callback({}); - }); + it('receives details object', function (done) { + ses.webRequest.onBeforeSendHeaders(function (details, callback) { + assert.equal(typeof details.requestHeaders, 'object') + callback({}) + }) $.ajax({ url: defaultURL, - success: function(data) { - assert.equal(data, '/'); - done(); + success: function (data) { + assert.equal(data, '/') + done() }, - error: function(xhr, errorType) { - done(errorType); + error: function (xhr, errorType) { + done(errorType) } - }); - }); + }) + }) - it('can change the request headers', function(done) { - ses.webRequest.onBeforeSendHeaders(function(details, callback) { - var requestHeaders = details.requestHeaders; - requestHeaders.Accept = '*/*;test/header'; + it('can change the request headers', function (done) { + ses.webRequest.onBeforeSendHeaders(function (details, callback) { + var requestHeaders = details.requestHeaders + requestHeaders.Accept = '*/*;test/header' callback({ requestHeaders: requestHeaders - }); - }); + }) + }) $.ajax({ url: defaultURL, - success: function(data) { - assert.equal(data, '/header/received'); - done(); + success: function (data) { + assert.equal(data, '/header/received') + done() }, - error: function(xhr, errorType) { - done(errorType); + error: function (xhr, errorType) { + done(errorType) } - }); - }); + }) + }) - it('resets the whole headers', function(done) { + it('resets the whole headers', function (done) { var requestHeaders = { Test: 'header' - }; - ses.webRequest.onBeforeSendHeaders(function(details, callback) { + } + ses.webRequest.onBeforeSendHeaders(function (details, callback) { callback({ requestHeaders: requestHeaders - }); - }); - ses.webRequest.onSendHeaders(function(details) { - assert.deepEqual(details.requestHeaders, requestHeaders); - done(); - }); + }) + }) + ses.webRequest.onSendHeaders(function (details) { + assert.deepEqual(details.requestHeaders, requestHeaders) + done() + }) $.ajax({ url: defaultURL, - error: function(xhr, errorType) { - done(errorType); + error: function (xhr, errorType) { + done(errorType) } - }); - }); - }); + }) + }) + }) - describe('webRequest.onSendHeaders', function() { - afterEach(function() { - ses.webRequest.onSendHeaders(null); - }); + describe('webRequest.onSendHeaders', function () { + afterEach(function () { + ses.webRequest.onSendHeaders(null) + }) - it('receives details object', function(done) { - ses.webRequest.onSendHeaders(function(details) { - assert.equal(typeof details.requestHeaders, 'object'); - }); + it('receives details object', function (done) { + ses.webRequest.onSendHeaders(function (details) { + assert.equal(typeof details.requestHeaders, 'object') + }) $.ajax({ url: defaultURL, - success: function(data) { - assert.equal(data, '/'); - done(); + success: function (data) { + assert.equal(data, '/') + done() }, - error: function(xhr, errorType) { - done(errorType); + error: function (xhr, errorType) { + done(errorType) } - }); - }); - }); + }) + }) + }) - describe('webRequest.onHeadersReceived', function() { - afterEach(function() { - ses.webRequest.onHeadersReceived(null); - }); + describe('webRequest.onHeadersReceived', function () { + afterEach(function () { + ses.webRequest.onHeadersReceived(null) + }) - it('receives details object', function(done) { - ses.webRequest.onHeadersReceived(function(details, callback) { - assert.equal(details.statusLine, 'HTTP/1.1 200 OK'); - assert.equal(details.statusCode, 200); - assert.equal(details.responseHeaders['Custom'], 'Header'); - callback({}); - }); + it('receives details object', function (done) { + ses.webRequest.onHeadersReceived(function (details, callback) { + assert.equal(details.statusLine, 'HTTP/1.1 200 OK') + assert.equal(details.statusCode, 200) + assert.equal(details.responseHeaders['Custom'], 'Header') + callback({}) + }) $.ajax({ url: defaultURL, - success: function(data) { - assert.equal(data, '/'); - done(); + success: function (data) { + assert.equal(data, '/') + done() }, - error: function(xhr, errorType) { - done(errorType); + error: function (xhr, errorType) { + done(errorType) } - }); - }); + }) + }) - it('can change the response header', function(done) { - ses.webRequest.onHeadersReceived(function(details, callback) { - var responseHeaders = details.responseHeaders; - responseHeaders['Custom'] = ['Changed']; + it('can change the response header', function (done) { + ses.webRequest.onHeadersReceived(function (details, callback) { + var responseHeaders = details.responseHeaders + responseHeaders['Custom'] = ['Changed'] callback({ responseHeaders: responseHeaders - }); - }); + }) + }) $.ajax({ url: defaultURL, - success: function(data, status, xhr) { - assert.equal(xhr.getResponseHeader('Custom'), 'Changed'); - assert.equal(data, '/'); - done(); + success: function (data, status, xhr) { + assert.equal(xhr.getResponseHeader('Custom'), 'Changed') + assert.equal(data, '/') + done() }, - error: function(xhr, errorType) { - done(errorType); + error: function (xhr, errorType) { + done(errorType) } - }); - }); + }) + }) - it('does not change header by default', function(done) { - ses.webRequest.onHeadersReceived(function(details, callback) { - callback({}); - }); + it('does not change header by default', function (done) { + ses.webRequest.onHeadersReceived(function (details, callback) { + callback({}) + }) $.ajax({ url: defaultURL, - success: function(data, status, xhr) { - assert.equal(xhr.getResponseHeader('Custom'), 'Header'); - assert.equal(data, '/'); - done(); + success: function (data, status, xhr) { + assert.equal(xhr.getResponseHeader('Custom'), 'Header') + assert.equal(data, '/') + done() }, - error: function(xhr, errorType) { - done(errorType); + error: function (xhr, errorType) { + done(errorType) } - }); - }); - }); + }) + }) - describe('webRequest.onResponseStarted', function() { - afterEach(function() { - ses.webRequest.onResponseStarted(null); - }); + it('follows server redirect', function (done) { + ses.webRequest.onHeadersReceived(function (details, callback) { + var responseHeaders = details.responseHeaders + callback({ + responseHeaders: responseHeaders, + }) + }) + $.ajax({ + url: defaultURL + 'serverRedirect', + success: function (data, status, xhr) { + assert.equal(xhr.getResponseHeader('Custom'), 'Header') + done() + }, + error: function (xhr, errorType) { + done(errorType) + } + }) + }) - it('receives details object', function(done) { - ses.webRequest.onResponseStarted(function(details) { - assert.equal(typeof details.fromCache, 'boolean'); - assert.equal(details.statusLine, 'HTTP/1.1 200 OK'); - assert.equal(details.statusCode, 200); - assert.equal(details.responseHeaders['Custom'], 'Header'); - }); + it('can change the header status', function (done) { + ses.webRequest.onHeadersReceived(function (details, callback) { + var responseHeaders = details.responseHeaders + callback({ + responseHeaders: responseHeaders, + statusLine: "HTTP/1.1 404 Not Found" + }) + }) $.ajax({ url: defaultURL, - success: function(data, status, xhr) { - assert.equal(xhr.getResponseHeader('Custom'), 'Header'); - assert.equal(data, '/'); - done(); + success: function (data, status, xhr) { }, - error: function(xhr, errorType) { - done(errorType); + error: function (xhr, errorType) { + assert.equal(xhr.getResponseHeader('Custom'), 'Header') + done() } - }); - }); - }); + }) + }) + }) - describe('webRequest.onBeforeRedirect', function() { - afterEach(function() { - ses.webRequest.onBeforeRedirect(null); - ses.webRequest.onBeforeRequest(null); - }); + describe('webRequest.onResponseStarted', function () { + afterEach(function () { + ses.webRequest.onResponseStarted(null) + }) - it('receives details object', function(done) { - var redirectURL = defaultURL + "redirect"; - ses.webRequest.onBeforeRequest(function(details, callback) { + it('receives details object', function (done) { + ses.webRequest.onResponseStarted(function (details) { + assert.equal(typeof details.fromCache, 'boolean') + assert.equal(details.statusLine, 'HTTP/1.1 200 OK') + assert.equal(details.statusCode, 200) + assert.equal(details.responseHeaders['Custom'], 'Header') + }) + $.ajax({ + url: defaultURL, + success: function (data, status, xhr) { + assert.equal(xhr.getResponseHeader('Custom'), 'Header') + assert.equal(data, '/') + done() + }, + error: function (xhr, errorType) { + done(errorType) + } + }) + }) + }) + + describe('webRequest.onBeforeRedirect', function () { + afterEach(function () { + ses.webRequest.onBeforeRedirect(null) + ses.webRequest.onBeforeRequest(null) + }) + + it('receives details object', function (done) { + var redirectURL = defaultURL + 'redirect' + ses.webRequest.onBeforeRequest(function (details, callback) { if (details.url === defaultURL) { callback({ redirectURL: redirectURL - }); + }) } else { - callback({}); + callback({}) } - }); - ses.webRequest.onBeforeRedirect(function(details) { - assert.equal(typeof details.fromCache, 'boolean'); - assert.equal(details.statusLine, 'HTTP/1.1 307 Internal Redirect'); - assert.equal(details.statusCode, 307); - assert.equal(details.redirectURL, redirectURL); - }); + }) + ses.webRequest.onBeforeRedirect(function (details) { + assert.equal(typeof details.fromCache, 'boolean') + assert.equal(details.statusLine, 'HTTP/1.1 307 Internal Redirect') + assert.equal(details.statusCode, 307) + assert.equal(details.redirectURL, redirectURL) + }) $.ajax({ url: defaultURL, - success: function(data) { - assert.equal(data, '/redirect'); - done(); + success: function (data) { + assert.equal(data, '/redirect') + done() }, - error: function(xhr, errorType) { - done(errorType); + error: function (xhr, errorType) { + done(errorType) } - }); - }); - }); + }) + }) + }) - describe('webRequest.onCompleted', function() { - afterEach(function() { - ses.webRequest.onCompleted(null); - }); + describe('webRequest.onCompleted', function () { + afterEach(function () { + ses.webRequest.onCompleted(null) + }) - it('receives details object', function(done) { - ses.webRequest.onCompleted(function(details) { - assert.equal(typeof details.fromCache, 'boolean'); - assert.equal(details.statusLine, 'HTTP/1.1 200 OK'); - assert.equal(details.statusCode, 200); - }); + it('receives details object', function (done) { + ses.webRequest.onCompleted(function (details) { + assert.equal(typeof details.fromCache, 'boolean') + assert.equal(details.statusLine, 'HTTP/1.1 200 OK') + assert.equal(details.statusCode, 200) + }) $.ajax({ url: defaultURL, - success: function(data) { - assert.equal(data, '/'); - done(); + success: function (data) { + assert.equal(data, '/') + done() }, - error: function(xhr, errorType) { - done(errorType); + error: function (xhr, errorType) { + done(errorType) } - }); - }); - }); + }) + }) + }) - describe('webRequest.onErrorOccurred', function() { - afterEach(function() { - ses.webRequest.onErrorOccurred(null); - ses.webRequest.onBeforeRequest(null); - }); + describe('webRequest.onErrorOccurred', function () { + afterEach(function () { + ses.webRequest.onErrorOccurred(null) + ses.webRequest.onBeforeRequest(null) + }) - it('receives details object', function(done) { - ses.webRequest.onBeforeRequest(function(details, callback) { + it('receives details object', function (done) { + ses.webRequest.onBeforeRequest(function (details, callback) { callback({ cancel: true - }); - }); - ses.webRequest.onErrorOccurred(function(details) { - assert.equal(details.error, 'net::ERR_BLOCKED_BY_CLIENT'); - done(); - }); + }) + }) + ses.webRequest.onErrorOccurred(function (details) { + assert.equal(details.error, 'net::ERR_BLOCKED_BY_CLIENT') + done() + }) $.ajax({ url: defaultURL, - success: function() { - done('unexpected success'); + success: function () { + done('unexpected success') } - }); - }); - }); -}); + }) + }) + }) +}) diff --git a/spec/asar-spec.js b/spec/asar-spec.js index 060074390f1b..ac0779d2f266 100644 --- a/spec/asar-spec.js +++ b/spec/asar-spec.js @@ -1,795 +1,839 @@ -const assert = require('assert'); -const child_process = require('child_process'); -const fs = require('fs'); -const path = require('path'); +const assert = require('assert') +const child_process = require('child_process') +const fs = require('fs') +const path = require('path') -const nativeImage = require('electron').nativeImage; -const remote = require('electron').remote; +const nativeImage = require('electron').nativeImage +const remote = require('electron').remote -const ipcMain = remote.require('electron').ipcMain; -const BrowserWindow = remote.require('electron').BrowserWindow; +const ipcMain = remote.require('electron').ipcMain +const BrowserWindow = remote.require('electron').BrowserWindow -describe('asar package', function() { - var fixtures = path.join(__dirname, 'fixtures'); +describe('asar package', function () { + var fixtures = path.join(__dirname, 'fixtures') - describe('node api', function() { - describe('fs.readFileSync', function() { - it('does not leak fd', function() { - var readCalls = 1; - while(readCalls <= 10000) { - fs.readFileSync(path.join(process.resourcesPath, 'atom.asar', 'renderer', 'api', 'lib', 'ipc.js')); - readCalls++; + describe('node api', function () { + describe('fs.readFileSync', function () { + it('does not leak fd', function () { + var readCalls = 1 + while (readCalls <= 10000) { + fs.readFileSync(path.join(process.resourcesPath, 'electron.asar', 'renderer', 'api', 'ipc-renderer.js')) + readCalls++ } - }); + }) - it('reads a normal file', function() { - var file1 = path.join(fixtures, 'asar', 'a.asar', 'file1'); - assert.equal(fs.readFileSync(file1).toString().trim(), 'file1'); - var file2 = path.join(fixtures, 'asar', 'a.asar', 'file2'); - assert.equal(fs.readFileSync(file2).toString().trim(), 'file2'); - var file3 = path.join(fixtures, 'asar', 'a.asar', 'file3'); - assert.equal(fs.readFileSync(file3).toString().trim(), 'file3'); - }); + it('reads a normal file', function () { + var file1 = path.join(fixtures, 'asar', 'a.asar', 'file1') + assert.equal(fs.readFileSync(file1).toString().trim(), 'file1') + var file2 = path.join(fixtures, 'asar', 'a.asar', 'file2') + assert.equal(fs.readFileSync(file2).toString().trim(), 'file2') + var file3 = path.join(fixtures, 'asar', 'a.asar', 'file3') + assert.equal(fs.readFileSync(file3).toString().trim(), 'file3') + }) - it('reads from a empty file', function() { - var file = path.join(fixtures, 'asar', 'empty.asar', 'file1'); - var buffer = fs.readFileSync(file); - assert.equal(buffer.length, 0); - assert.equal(buffer.toString(), ''); - }); + it('reads from a empty file', function () { + var file = path.join(fixtures, 'asar', 'empty.asar', 'file1') + var buffer = fs.readFileSync(file) + assert.equal(buffer.length, 0) + assert.equal(buffer.toString(), '') + }) - it('reads a linked file', function() { - var p = path.join(fixtures, 'asar', 'a.asar', 'link1'); - assert.equal(fs.readFileSync(p).toString().trim(), 'file1'); - }); + it('reads a linked file', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'link1') + assert.equal(fs.readFileSync(p).toString().trim(), 'file1') + }) - it('reads a file from linked directory', function() { - var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'file1'); - assert.equal(fs.readFileSync(p).toString().trim(), 'file1'); - p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1'); - assert.equal(fs.readFileSync(p).toString().trim(), 'file1'); - }); + it('reads a file from linked directory', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'file1') + assert.equal(fs.readFileSync(p).toString().trim(), 'file1') + p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1') + assert.equal(fs.readFileSync(p).toString().trim(), 'file1') + }) - it('throws ENOENT error when can not find file', function() { - var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); - var throws = function() { - fs.readFileSync(p); - }; - assert.throws(throws, /ENOENT/); - }); + it('throws ENOENT error when can not find file', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + var throws = function () { + fs.readFileSync(p) + } + assert.throws(throws, /ENOENT/) + }) - it('passes ENOENT error to callback when can not find file', function() { - var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); - var async = false; - fs.readFile(p, function(e) { - assert(async); - assert(/ENOENT/.test(e)); - }); - async = true; - }); + it('passes ENOENT error to callback when can not find file', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + var async = false + fs.readFile(p, function (e) { + assert(async) + assert(/ENOENT/.test(e)) + }) + async = true + }) - it('reads a normal file with unpacked files', function() { - var p = path.join(fixtures, 'asar', 'unpack.asar', 'a.txt'); - assert.equal(fs.readFileSync(p).toString().trim(), 'a'); - }); - }); + it('reads a normal file with unpacked files', function () { + var p = path.join(fixtures, 'asar', 'unpack.asar', 'a.txt') + assert.equal(fs.readFileSync(p).toString().trim(), 'a') + }) + }) - describe('fs.readFile', function() { - it('reads a normal file', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'file1'); - fs.readFile(p, function(err, content) { - assert.equal(err, null); - assert.equal(String(content).trim(), 'file1'); - done(); - }); - }); + describe('fs.readFile', function () { + it('reads a normal file', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'file1') + fs.readFile(p, function (err, content) { + assert.equal(err, null) + assert.equal(String(content).trim(), 'file1') + done() + }) + }) - it('reads from a empty file', function(done) { - var p = path.join(fixtures, 'asar', 'empty.asar', 'file1'); - fs.readFile(p, function(err, content) { - assert.equal(err, null); - assert.equal(String(content), ''); - done(); - }); - }); + it('reads from a empty file', function (done) { + var p = path.join(fixtures, 'asar', 'empty.asar', 'file1') + fs.readFile(p, function (err, content) { + assert.equal(err, null) + assert.equal(String(content), '') + done() + }) + }) - it('reads a linked file', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'link1'); - fs.readFile(p, function(err, content) { - assert.equal(err, null); - assert.equal(String(content).trim(), 'file1'); - done(); - }); - }); + it('reads a linked file', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'link1') + fs.readFile(p, function (err, content) { + assert.equal(err, null) + assert.equal(String(content).trim(), 'file1') + done() + }) + }) - it('reads a file from linked directory', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1'); - fs.readFile(p, function(err, content) { - assert.equal(err, null); - assert.equal(String(content).trim(), 'file1'); - done(); - }); - }); + it('reads a file from linked directory', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1') + fs.readFile(p, function (err, content) { + assert.equal(err, null) + assert.equal(String(content).trim(), 'file1') + done() + }) + }) - it('throws ENOENT error when can not find file', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); - fs.readFile(p, function(err) { - assert.equal(err.code, 'ENOENT'); - done(); - }); - }); - }); + it('throws ENOENT error when can not find file', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + fs.readFile(p, function (err) { + assert.equal(err.code, 'ENOENT') + done() + }) + }) + }) - describe('fs.lstatSync', function() { - it('handles path with trailing slash correctly', function() { - var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1'); - fs.lstatSync(p); - fs.lstatSync(p + '/'); - }); + describe('fs.lstatSync', function () { + it('handles path with trailing slash correctly', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1') + fs.lstatSync(p) + fs.lstatSync(p + '/') + }) - it('returns information of root', function() { - var p = path.join(fixtures, 'asar', 'a.asar'); - var stats = fs.lstatSync(p); - assert.equal(stats.isFile(), false); - assert.equal(stats.isDirectory(), true); - assert.equal(stats.isSymbolicLink(), false); - assert.equal(stats.size, 0); - }); + it('returns information of root', function () { + var p = path.join(fixtures, 'asar', 'a.asar') + var stats = fs.lstatSync(p) + assert.equal(stats.isFile(), false) + assert.equal(stats.isDirectory(), true) + assert.equal(stats.isSymbolicLink(), false) + assert.equal(stats.size, 0) + }) - it('returns information of a normal file', function() { - var file, j, len, p, ref2, stats; - ref2 = ['file1', 'file2', 'file3', path.join('dir1', 'file1'), path.join('link2', 'file1')]; + it('returns information of a normal file', function () { + var file, j, len, p, ref2, stats + ref2 = ['file1', 'file2', 'file3', path.join('dir1', 'file1'), path.join('link2', 'file1')] for (j = 0, len = ref2.length; j < len; j++) { - file = ref2[j]; - p = path.join(fixtures, 'asar', 'a.asar', file); - stats = fs.lstatSync(p); - assert.equal(stats.isFile(), true); - assert.equal(stats.isDirectory(), false); - assert.equal(stats.isSymbolicLink(), false); - assert.equal(stats.size, 6); + file = ref2[j] + p = path.join(fixtures, 'asar', 'a.asar', file) + stats = fs.lstatSync(p) + assert.equal(stats.isFile(), true) + assert.equal(stats.isDirectory(), false) + assert.equal(stats.isSymbolicLink(), false) + assert.equal(stats.size, 6) } - }); + }) - it('returns information of a normal directory', function() { - var file, j, len, p, ref2, stats; - ref2 = ['dir1', 'dir2', 'dir3']; + it('returns information of a normal directory', function () { + var file, j, len, p, ref2, stats + ref2 = ['dir1', 'dir2', 'dir3'] for (j = 0, len = ref2.length; j < len; j++) { - file = ref2[j]; - p = path.join(fixtures, 'asar', 'a.asar', file); - stats = fs.lstatSync(p); - assert.equal(stats.isFile(), false); - assert.equal(stats.isDirectory(), true); - assert.equal(stats.isSymbolicLink(), false); - assert.equal(stats.size, 0); + file = ref2[j] + p = path.join(fixtures, 'asar', 'a.asar', file) + stats = fs.lstatSync(p) + assert.equal(stats.isFile(), false) + assert.equal(stats.isDirectory(), true) + assert.equal(stats.isSymbolicLink(), false) + assert.equal(stats.size, 0) } - }); + }) - it('returns information of a linked file', function() { - var file, j, len, p, ref2, stats; - ref2 = ['link1', path.join('dir1', 'link1'), path.join('link2', 'link2')]; + it('returns information of a linked file', function () { + var file, j, len, p, ref2, stats + ref2 = ['link1', path.join('dir1', 'link1'), path.join('link2', 'link2')] for (j = 0, len = ref2.length; j < len; j++) { - file = ref2[j]; - p = path.join(fixtures, 'asar', 'a.asar', file); - stats = fs.lstatSync(p); - assert.equal(stats.isFile(), false); - assert.equal(stats.isDirectory(), false); - assert.equal(stats.isSymbolicLink(), true); - assert.equal(stats.size, 0); + file = ref2[j] + p = path.join(fixtures, 'asar', 'a.asar', file) + stats = fs.lstatSync(p) + assert.equal(stats.isFile(), false) + assert.equal(stats.isDirectory(), false) + assert.equal(stats.isSymbolicLink(), true) + assert.equal(stats.size, 0) } - }); + }) - it('returns information of a linked directory', function() { - var file, j, len, p, ref2, stats; - ref2 = ['link2', path.join('dir1', 'link2'), path.join('link2', 'link2')]; + it('returns information of a linked directory', function () { + var file, j, len, p, ref2, stats + ref2 = ['link2', path.join('dir1', 'link2'), path.join('link2', 'link2')] for (j = 0, len = ref2.length; j < len; j++) { - file = ref2[j]; - p = path.join(fixtures, 'asar', 'a.asar', file); - stats = fs.lstatSync(p); - assert.equal(stats.isFile(), false); - assert.equal(stats.isDirectory(), false); - assert.equal(stats.isSymbolicLink(), true); - assert.equal(stats.size, 0); + file = ref2[j] + p = path.join(fixtures, 'asar', 'a.asar', file) + stats = fs.lstatSync(p) + assert.equal(stats.isFile(), false) + assert.equal(stats.isDirectory(), false) + assert.equal(stats.isSymbolicLink(), true) + assert.equal(stats.size, 0) } - }); + }) - it('throws ENOENT error when can not find file', function() { - var file, j, len, p, ref2, throws; - ref2 = ['file4', 'file5', path.join('dir1', 'file4')]; + it('throws ENOENT error when can not find file', function () { + var file, j, len, p, ref2, throws + ref2 = ['file4', 'file5', path.join('dir1', 'file4')] for (j = 0, len = ref2.length; j < len; j++) { - file = ref2[j]; - p = path.join(fixtures, 'asar', 'a.asar', file); - throws = function() { - fs.lstatSync(p); - }; - assert.throws(throws, /ENOENT/); + file = ref2[j] + p = path.join(fixtures, 'asar', 'a.asar', file) + throws = function () { + fs.lstatSync(p) + } + assert.throws(throws, /ENOENT/) } - }); - }); + }) + }) - describe('fs.lstat', function() { - it('handles path with trailing slash correctly', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1'); - fs.lstat(p + '/', done); - }); + describe('fs.lstat', function () { + it('handles path with trailing slash correctly', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1') + fs.lstat(p + '/', done) + }) - it('returns information of root', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar'); - fs.lstat(p, function(err, stats) { - assert.equal(err, null); - assert.equal(stats.isFile(), false); - assert.equal(stats.isDirectory(), true); - assert.equal(stats.isSymbolicLink(), false); - assert.equal(stats.size, 0); - done(); - }); - }); + it('returns information of root', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar') + fs.lstat(p, function (err, stats) { + assert.equal(err, null) + assert.equal(stats.isFile(), false) + assert.equal(stats.isDirectory(), true) + assert.equal(stats.isSymbolicLink(), false) + assert.equal(stats.size, 0) + done() + }) + }) - it('returns information of a normal file', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'file1'); - fs.lstat(p, function(err, stats) { - assert.equal(err, null); - assert.equal(stats.isFile(), true); - assert.equal(stats.isDirectory(), false); - assert.equal(stats.isSymbolicLink(), false); - assert.equal(stats.size, 6); - done(); - }); - }); + it('returns information of a normal file', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'file1') + fs.lstat(p, function (err, stats) { + assert.equal(err, null) + assert.equal(stats.isFile(), true) + assert.equal(stats.isDirectory(), false) + assert.equal(stats.isSymbolicLink(), false) + assert.equal(stats.size, 6) + done() + }) + }) - it('returns information of a normal directory', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'dir1'); - fs.lstat(p, function(err, stats) { - assert.equal(err, null); - assert.equal(stats.isFile(), false); - assert.equal(stats.isDirectory(), true); - assert.equal(stats.isSymbolicLink(), false); - assert.equal(stats.size, 0); - done(); - }); - }); + it('returns information of a normal directory', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'dir1') + fs.lstat(p, function (err, stats) { + assert.equal(err, null) + assert.equal(stats.isFile(), false) + assert.equal(stats.isDirectory(), true) + assert.equal(stats.isSymbolicLink(), false) + assert.equal(stats.size, 0) + done() + }) + }) - it('returns information of a linked file', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link1'); - fs.lstat(p, function(err, stats) { - assert.equal(err, null); - assert.equal(stats.isFile(), false); - assert.equal(stats.isDirectory(), false); - assert.equal(stats.isSymbolicLink(), true); - assert.equal(stats.size, 0); - done(); - }); - }); + it('returns information of a linked file', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link1') + fs.lstat(p, function (err, stats) { + assert.equal(err, null) + assert.equal(stats.isFile(), false) + assert.equal(stats.isDirectory(), false) + assert.equal(stats.isSymbolicLink(), true) + assert.equal(stats.size, 0) + done() + }) + }) - it('returns information of a linked directory', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2'); - fs.lstat(p, function(err, stats) { - assert.equal(err, null); - assert.equal(stats.isFile(), false); - assert.equal(stats.isDirectory(), false); - assert.equal(stats.isSymbolicLink(), true); - assert.equal(stats.size, 0); - done(); - }); - }); + it('returns information of a linked directory', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2') + fs.lstat(p, function (err, stats) { + assert.equal(err, null) + assert.equal(stats.isFile(), false) + assert.equal(stats.isDirectory(), false) + assert.equal(stats.isSymbolicLink(), true) + assert.equal(stats.size, 0) + done() + }) + }) - it('throws ENOENT error when can not find file', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'file4'); - fs.lstat(p, function(err) { - assert.equal(err.code, 'ENOENT'); - done(); - }); - }); - }); + it('throws ENOENT error when can not find file', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'file4') + fs.lstat(p, function (err) { + assert.equal(err.code, 'ENOENT') + done() + }) + }) + }) - describe('fs.realpathSync', function() { - it('returns real path root', function() { - var parent = fs.realpathSync(path.join(fixtures, 'asar')); - var p = 'a.asar'; - var r = fs.realpathSync(path.join(parent, p)); - assert.equal(r, path.join(parent, p)); - }); + describe('fs.realpathSync', function () { + it('returns real path root', function () { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = 'a.asar' + var r = fs.realpathSync(path.join(parent, p)) + assert.equal(r, path.join(parent, p)) + }) - it('returns real path of a normal file', function() { - var parent = fs.realpathSync(path.join(fixtures, 'asar')); - var p = path.join('a.asar', 'file1'); - var r = fs.realpathSync(path.join(parent, p)); - assert.equal(r, path.join(parent, p)); - }); + it('returns real path of a normal file', function () { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('a.asar', 'file1') + var r = fs.realpathSync(path.join(parent, p)) + assert.equal(r, path.join(parent, p)) + }) - it('returns real path of a normal directory', function() { - var parent = fs.realpathSync(path.join(fixtures, 'asar')); - var p = path.join('a.asar', 'dir1'); - var r = fs.realpathSync(path.join(parent, p)); - assert.equal(r, path.join(parent, p)); - }); + it('returns real path of a normal directory', function () { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('a.asar', 'dir1') + var r = fs.realpathSync(path.join(parent, p)) + assert.equal(r, path.join(parent, p)) + }) - it('returns real path of a linked file', function() { - var parent = fs.realpathSync(path.join(fixtures, 'asar')); - var p = path.join('a.asar', 'link2', 'link1'); - var r = fs.realpathSync(path.join(parent, p)); - assert.equal(r, path.join(parent, 'a.asar', 'file1')); - }); + it('returns real path of a linked file', function () { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('a.asar', 'link2', 'link1') + var r = fs.realpathSync(path.join(parent, p)) + assert.equal(r, path.join(parent, 'a.asar', 'file1')) + }) - it('returns real path of a linked directory', function() { - var parent = fs.realpathSync(path.join(fixtures, 'asar')); - var p = path.join('a.asar', 'link2', 'link2'); - var r = fs.realpathSync(path.join(parent, p)); - assert.equal(r, path.join(parent, 'a.asar', 'dir1')); - }); + it('returns real path of a linked directory', function () { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('a.asar', 'link2', 'link2') + var r = fs.realpathSync(path.join(parent, p)) + assert.equal(r, path.join(parent, 'a.asar', 'dir1')) + }) - it('throws ENOENT error when can not find file', function() { - var parent = fs.realpathSync(path.join(fixtures, 'asar')); - var p = path.join('a.asar', 'not-exist'); - var throws = function() { - fs.realpathSync(path.join(parent, p)); - }; - assert.throws(throws, /ENOENT/); - }); - }); + it('returns real path of an unpacked file', function () { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('unpack.asar', 'a.txt') + var r = fs.realpathSync(path.join(parent, p)) + assert.equal(r, path.join(parent, p)) + }) - describe('fs.realpath', function() { - it('returns real path root', function(done) { - var parent = fs.realpathSync(path.join(fixtures, 'asar')); - var p = 'a.asar'; - fs.realpath(path.join(parent, p), function(err, r) { - assert.equal(err, null); - assert.equal(r, path.join(parent, p)); - done(); - }); - }); + it('throws ENOENT error when can not find file', function () { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('a.asar', 'not-exist') + var throws = function () { + fs.realpathSync(path.join(parent, p)) + } + assert.throws(throws, /ENOENT/) + }) + }) - it('returns real path of a normal file', function(done) { - var parent = fs.realpathSync(path.join(fixtures, 'asar')); - var p = path.join('a.asar', 'file1'); - fs.realpath(path.join(parent, p), function(err, r) { - assert.equal(err, null); - assert.equal(r, path.join(parent, p)); - done(); - }); - }); + describe('fs.realpath', function () { + it('returns real path root', function (done) { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = 'a.asar' + fs.realpath(path.join(parent, p), function (err, r) { + assert.equal(err, null) + assert.equal(r, path.join(parent, p)) + done() + }) + }) - it('returns real path of a normal directory', function(done) { - var parent = fs.realpathSync(path.join(fixtures, 'asar')); - var p = path.join('a.asar', 'dir1'); - fs.realpath(path.join(parent, p), function(err, r) { - assert.equal(err, null); - assert.equal(r, path.join(parent, p)); - done(); - }); - }); + it('returns real path of a normal file', function (done) { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('a.asar', 'file1') + fs.realpath(path.join(parent, p), function (err, r) { + assert.equal(err, null) + assert.equal(r, path.join(parent, p)) + done() + }) + }) - it('returns real path of a linked file', function(done) { - var parent = fs.realpathSync(path.join(fixtures, 'asar')); - var p = path.join('a.asar', 'link2', 'link1'); - fs.realpath(path.join(parent, p), function(err, r) { - assert.equal(err, null); - assert.equal(r, path.join(parent, 'a.asar', 'file1')); - done(); - }); - }); + it('returns real path of a normal directory', function (done) { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('a.asar', 'dir1') + fs.realpath(path.join(parent, p), function (err, r) { + assert.equal(err, null) + assert.equal(r, path.join(parent, p)) + done() + }) + }) - it('returns real path of a linked directory', function(done) { - var parent = fs.realpathSync(path.join(fixtures, 'asar')); - var p = path.join('a.asar', 'link2', 'link2'); - fs.realpath(path.join(parent, p), function(err, r) { - assert.equal(err, null); - assert.equal(r, path.join(parent, 'a.asar', 'dir1')); - done(); - }); - }); - it('throws ENOENT error when can not find file', function(done) { - var parent = fs.realpathSync(path.join(fixtures, 'asar')); - var p = path.join('a.asar', 'not-exist'); - fs.realpath(path.join(parent, p), function(err) { - assert.equal(err.code, 'ENOENT'); - done(); - }); - }); - }); - describe('fs.readdirSync', function() { - it('reads dirs from root', function() { - var p = path.join(fixtures, 'asar', 'a.asar'); - var dirs = fs.readdirSync(p); - assert.deepEqual(dirs, ['dir1', 'dir2', 'dir3', 'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js']); - }); + it('returns real path of a linked file', function (done) { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('a.asar', 'link2', 'link1') + fs.realpath(path.join(parent, p), function (err, r) { + assert.equal(err, null) + assert.equal(r, path.join(parent, 'a.asar', 'file1')) + done() + }) + }) - it('reads dirs from a normal dir', function() { - var p = path.join(fixtures, 'asar', 'a.asar', 'dir1'); - var dirs = fs.readdirSync(p); - assert.deepEqual(dirs, ['file1', 'file2', 'file3', 'link1', 'link2']); - }); + it('returns real path of a linked directory', function (done) { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('a.asar', 'link2', 'link2') + fs.realpath(path.join(parent, p), function (err, r) { + assert.equal(err, null) + assert.equal(r, path.join(parent, 'a.asar', 'dir1')) + done() + }) + }) - it('reads dirs from a linked dir', function() { - var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2'); - var dirs = fs.readdirSync(p); - assert.deepEqual(dirs, ['file1', 'file2', 'file3', 'link1', 'link2']); - }); + it('returns real path of an unpacked file', function (done) { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('unpack.asar', 'a.txt') + fs.realpath(path.join(parent, p), function (err, r) { + assert.equal(err, null) + assert.equal(r, path.join(parent, p)) + done() + }) + }) - it('throws ENOENT error when can not find file', function() { - var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); - var throws = function() { - fs.readdirSync(p); - }; - assert.throws(throws, /ENOENT/); - }); - }); + it('throws ENOENT error when can not find file', function (done) { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('a.asar', 'not-exist') + fs.realpath(path.join(parent, p), function (err) { + assert.equal(err.code, 'ENOENT') + done() + }) + }) + }) + describe('fs.readdirSync', function () { + it('reads dirs from root', function () { + var p = path.join(fixtures, 'asar', 'a.asar') + var dirs = fs.readdirSync(p) + assert.deepEqual(dirs, ['dir1', 'dir2', 'dir3', 'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js']) + }) - describe('fs.readdir', function() { - it('reads dirs from root', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar'); - fs.readdir(p, function(err, dirs) { - assert.equal(err, null); - assert.deepEqual(dirs, ['dir1', 'dir2', 'dir3', 'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js']); - done(); - }); - }); + it('reads dirs from a normal dir', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'dir1') + var dirs = fs.readdirSync(p) + assert.deepEqual(dirs, ['file1', 'file2', 'file3', 'link1', 'link2']) + }) - it('reads dirs from a normal dir', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'dir1'); - fs.readdir(p, function(err, dirs) { - assert.equal(err, null); - assert.deepEqual(dirs, ['file1', 'file2', 'file3', 'link1', 'link2']); - done(); - }); - }); - it('reads dirs from a linked dir', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2'); - fs.readdir(p, function(err, dirs) { - assert.equal(err, null); - assert.deepEqual(dirs, ['file1', 'file2', 'file3', 'link1', 'link2']); - done(); - }); - }); + it('reads dirs from a linked dir', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2') + var dirs = fs.readdirSync(p) + assert.deepEqual(dirs, ['file1', 'file2', 'file3', 'link1', 'link2']) + }) - it('throws ENOENT error when can not find file', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); - fs.readdir(p, function(err) { - assert.equal(err.code, 'ENOENT'); - done(); - }); - }); - }); + it('throws ENOENT error when can not find file', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + var throws = function () { + fs.readdirSync(p) + } + assert.throws(throws, /ENOENT/) + }) + }) - describe('fs.openSync', function() { - it('opens a normal/linked/under-linked-directory file', function() { - var buffer, fd, file, j, len, p, ref2; - ref2 = ['file1', 'link1', path.join('link2', 'file1')]; + describe('fs.readdir', function () { + it('reads dirs from root', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar') + fs.readdir(p, function (err, dirs) { + assert.equal(err, null) + assert.deepEqual(dirs, ['dir1', 'dir2', 'dir3', 'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js']) + done() + }) + }) + + it('reads dirs from a normal dir', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'dir1') + fs.readdir(p, function (err, dirs) { + assert.equal(err, null) + assert.deepEqual(dirs, ['file1', 'file2', 'file3', 'link1', 'link2']) + done() + }) + }) + it('reads dirs from a linked dir', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2') + fs.readdir(p, function (err, dirs) { + assert.equal(err, null) + assert.deepEqual(dirs, ['file1', 'file2', 'file3', 'link1', 'link2']) + done() + }) + }) + + it('throws ENOENT error when can not find file', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + fs.readdir(p, function (err) { + assert.equal(err.code, 'ENOENT') + done() + }) + }) + }) + + describe('fs.openSync', function () { + it('opens a normal/linked/under-linked-directory file', function () { + var buffer, fd, file, j, len, p, ref2 + ref2 = ['file1', 'link1', path.join('link2', 'file1')] for (j = 0, len = ref2.length; j < len; j++) { - file = ref2[j]; - p = path.join(fixtures, 'asar', 'a.asar', file); - fd = fs.openSync(p, 'r'); - buffer = new Buffer(6); - fs.readSync(fd, buffer, 0, 6, 0); - assert.equal(String(buffer).trim(), 'file1'); - fs.closeSync(fd); + file = ref2[j] + p = path.join(fixtures, 'asar', 'a.asar', file) + fd = fs.openSync(p, 'r') + buffer = new Buffer(6) + fs.readSync(fd, buffer, 0, 6, 0) + assert.equal(String(buffer).trim(), 'file1') + fs.closeSync(fd) } - }); + }) - it('throws ENOENT error when can not find file', function() { - var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); - var throws = function() { - fs.openSync(p); - }; - assert.throws(throws, /ENOENT/); - }); - }); + it('throws ENOENT error when can not find file', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + var throws = function () { + fs.openSync(p) + } + assert.throws(throws, /ENOENT/) + }) + }) - describe('fs.open', function() { - it('opens a normal file', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'file1'); - fs.open(p, 'r', function(err, fd) { - assert.equal(err, null); - var buffer = new Buffer(6); - fs.read(fd, buffer, 0, 6, 0, function(err) { - assert.equal(err, null); - assert.equal(String(buffer).trim(), 'file1'); - fs.close(fd, done); - }); - }); - }); + describe('fs.open', function () { + it('opens a normal file', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'file1') + fs.open(p, 'r', function (err, fd) { + assert.equal(err, null) + var buffer = new Buffer(6) + fs.read(fd, buffer, 0, 6, 0, function (err) { + assert.equal(err, null) + assert.equal(String(buffer).trim(), 'file1') + fs.close(fd, done) + }) + }) + }) - it('throws ENOENT error when can not find file', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); - fs.open(p, 'r', function(err) { - assert.equal(err.code, 'ENOENT'); - done(); - }); - }); - }); + it('throws ENOENT error when can not find file', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + fs.open(p, 'r', function (err) { + assert.equal(err.code, 'ENOENT') + done() + }) + }) + }) - describe('fs.mkdir', function() { - it('throws error when calling inside asar archive', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); - fs.mkdir(p, function(err) { - assert.equal(err.code, 'ENOTDIR'); - done(); - }); - }); - }); + describe('fs.mkdir', function () { + it('throws error when calling inside asar archive', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + fs.mkdir(p, function (err) { + assert.equal(err.code, 'ENOTDIR') + done() + }) + }) + }) - describe('fs.mkdirSync', function() { - it('throws error when calling inside asar archive', function() { - var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); - assert.throws((function() { - fs.mkdirSync(p); - }), new RegExp('ENOTDIR')); - }); - }); + describe('fs.mkdirSync', function () { + it('throws error when calling inside asar archive', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + assert.throws(function () { + fs.mkdirSync(p) + }, new RegExp('ENOTDIR')) + }) + }) - describe('child_process.fork', function() { - it('opens a normal js file', function(done) { - var child = child_process.fork(path.join(fixtures, 'asar', 'a.asar', 'ping.js')); - child.on('message', function(msg) { - assert.equal(msg, 'message'); - done(); - }); - child.send('message'); - }); + describe('child_process.fork', function () { + it('opens a normal js file', function (done) { + var child = child_process.fork(path.join(fixtures, 'asar', 'a.asar', 'ping.js')) + child.on('message', function (msg) { + assert.equal(msg, 'message') + done() + }) + child.send('message') + }) - it('supports asar in the forked js', function(done) { - var file = path.join(fixtures, 'asar', 'a.asar', 'file1'); - var child = child_process.fork(path.join(fixtures, 'module', 'asar.js')); - child.on('message', function(content) { - assert.equal(content, fs.readFileSync(file).toString()); - done(); - }); - child.send(file); - }); - }); + it('supports asar in the forked js', function (done) { + var file = path.join(fixtures, 'asar', 'a.asar', 'file1') + var child = child_process.fork(path.join(fixtures, 'module', 'asar.js')) + child.on('message', function (content) { + assert.equal(content, fs.readFileSync(file).toString()) + done() + }) + child.send(file) + }) + }) - describe('child_process.execFile', function() { - var echo, execFile, execFileSync, ref2; + describe('child_process.exec', function () { + var child_process = require('child_process'); + var echo = path.join(fixtures, 'asar', 'echo.asar', 'echo') + + it('should not try to extract the command if there is a reference to a file inside an .asar', function (done) { + child_process.exec('echo ' + echo + ' foo bar', function (error, stdout) { + assert.equal(error, null) + assert.equal(stdout.toString().replace(/\r/g, ''), echo + ' foo bar\n') + done() + }) + }) + }) + + describe('child_process.execSync', function () { + var child_process = require('child_process'); + var echo = path.join(fixtures, 'asar', 'echo.asar', 'echo') + + it('should not try to extract the command if there is a reference to a file inside an .asar', function (done) { + var stdout = child_process.execSync('echo ' + echo + ' foo bar') + assert.equal(stdout.toString().replace(/\r/g, ''), echo + ' foo bar\n') + done() + }) + }) + + describe('child_process.execFile', function () { + var echo, execFile, execFileSync, ref2 if (process.platform !== 'darwin') { - return; + return } - ref2 = require('child_process'), execFile = ref2.execFile, execFileSync = ref2.execFileSync; - echo = path.join(fixtures, 'asar', 'echo.asar', 'echo'); + ref2 = require('child_process') + execFile = ref2.execFile + execFileSync = ref2.execFileSync + echo = path.join(fixtures, 'asar', 'echo.asar', 'echo') - it('executes binaries', function(done) { - execFile(echo, ['test'], function(error, stdout) { - assert.equal(error, null); - assert.equal(stdout, 'test\n'); - done(); - }); - }); + it('executes binaries', function (done) { + execFile(echo, ['test'], function (error, stdout) { + assert.equal(error, null) + assert.equal(stdout, 'test\n') + done() + }) + }) - xit('execFileSync executes binaries', function() { - var output = execFileSync(echo, ['test']); - assert.equal(String(output), 'test\n'); - }); - }); + xit('execFileSync executes binaries', function () { + var output = execFileSync(echo, ['test']) + assert.equal(String(output), 'test\n') + }) + }) - describe('internalModuleReadFile', function() { - var internalModuleReadFile = process.binding('fs').internalModuleReadFile; + describe('internalModuleReadFile', function () { + var internalModuleReadFile = process.binding('fs').internalModuleReadFile - it('read a normal file', function() { - var file1 = path.join(fixtures, 'asar', 'a.asar', 'file1'); - assert.equal(internalModuleReadFile(file1).toString().trim(), 'file1'); - var file2 = path.join(fixtures, 'asar', 'a.asar', 'file2'); - assert.equal(internalModuleReadFile(file2).toString().trim(), 'file2'); - var file3 = path.join(fixtures, 'asar', 'a.asar', 'file3'); - assert.equal(internalModuleReadFile(file3).toString().trim(), 'file3'); - }); + it('read a normal file', function () { + var file1 = path.join(fixtures, 'asar', 'a.asar', 'file1') + assert.equal(internalModuleReadFile(file1).toString().trim(), 'file1') + var file2 = path.join(fixtures, 'asar', 'a.asar', 'file2') + assert.equal(internalModuleReadFile(file2).toString().trim(), 'file2') + var file3 = path.join(fixtures, 'asar', 'a.asar', 'file3') + assert.equal(internalModuleReadFile(file3).toString().trim(), 'file3') + }) - it('reads a normal file with unpacked files', function() { - var p = path.join(fixtures, 'asar', 'unpack.asar', 'a.txt'); - assert.equal(internalModuleReadFile(p).toString().trim(), 'a'); - }); - }); + it('reads a normal file with unpacked files', function () { + var p = path.join(fixtures, 'asar', 'unpack.asar', 'a.txt') + assert.equal(internalModuleReadFile(p).toString().trim(), 'a') + }) + }) - describe('process.noAsar', function() { - var errorName = process.platform === 'win32' ? 'ENOENT' : 'ENOTDIR'; + describe('process.noAsar', function () { + var errorName = process.platform === 'win32' ? 'ENOENT' : 'ENOTDIR' - beforeEach(function() { - process.noAsar = true; - }); + beforeEach(function () { + process.noAsar = true + }) - afterEach(function() { - process.noAsar = false; - }); + afterEach(function () { + process.noAsar = false + }) - it('disables asar support in sync API', function() { - var file = path.join(fixtures, 'asar', 'a.asar', 'file1'); - var dir = path.join(fixtures, 'asar', 'a.asar', 'dir1'); - assert.throws((function() { - fs.readFileSync(file); - }), new RegExp(errorName)); - assert.throws((function() { - fs.lstatSync(file); - }), new RegExp(errorName)); - assert.throws((function() { - fs.realpathSync(file); - }), new RegExp(errorName)); - assert.throws((function() { - fs.readdirSync(dir); - }), new RegExp(errorName)); - }); + it('disables asar support in sync API', function () { + var file = path.join(fixtures, 'asar', 'a.asar', 'file1') + var dir = path.join(fixtures, 'asar', 'a.asar', 'dir1') + assert.throws(function () { + fs.readFileSync(file) + }, new RegExp(errorName)) + assert.throws(function () { + fs.lstatSync(file) + }, new RegExp(errorName)) + assert.throws(function () { + fs.realpathSync(file) + }, new RegExp(errorName)) + assert.throws(function () { + fs.readdirSync(dir) + }, new RegExp(errorName)) + }) - it('disables asar support in async API', function(done) { - var file = path.join(fixtures, 'asar', 'a.asar', 'file1'); - var dir = path.join(fixtures, 'asar', 'a.asar', 'dir1'); - fs.readFile(file, function(error) { - assert.equal(error.code, errorName); - fs.lstat(file, function(error) { - assert.equal(error.code, errorName); - fs.realpath(file, function(error) { - assert.equal(error.code, errorName); - fs.readdir(dir, function(error) { - assert.equal(error.code, errorName); - done(); - }); - }); - }); - }); - }); + it('disables asar support in async API', function (done) { + var file = path.join(fixtures, 'asar', 'a.asar', 'file1') + var dir = path.join(fixtures, 'asar', 'a.asar', 'dir1') + fs.readFile(file, function (error) { + assert.equal(error.code, errorName) + fs.lstat(file, function (error) { + assert.equal(error.code, errorName) + fs.realpath(file, function (error) { + assert.equal(error.code, errorName) + fs.readdir(dir, function (error) { + assert.equal(error.code, errorName) + done() + }) + }) + }) + }) + }) - it('treats *.asar as normal file', function() { - var originalFs = require('original-fs'); - var asar = path.join(fixtures, 'asar', 'a.asar'); - var content1 = fs.readFileSync(asar); - var content2 = originalFs.readFileSync(asar); - assert.equal(content1.compare(content2), 0); - assert.throws((function() { - fs.readdirSync(asar); - }), /ENOTDIR/); - }); - }); - }); + it('treats *.asar as normal file', function () { + var originalFs = require('original-fs') + var asar = path.join(fixtures, 'asar', 'a.asar') + var content1 = fs.readFileSync(asar) + var content2 = originalFs.readFileSync(asar) + assert.equal(content1.compare(content2), 0) + assert.throws(function () { + fs.readdirSync(asar) + }, /ENOTDIR/) + }) + }) + }) - describe('asar protocol', function() { - var url = require('url'); + describe('asar protocol', function () { + var url = require('url') - it('can request a file in package', function(done) { - var p = path.resolve(fixtures, 'asar', 'a.asar', 'file1'); - $.get("file://" + p, function(data) { - assert.equal(data.trim(), 'file1'); - done(); - }); - }); + it('can request a file in package', function (done) { + var p = path.resolve(fixtures, 'asar', 'a.asar', 'file1') + $.get('file://' + p, function (data) { + assert.equal(data.trim(), 'file1') + done() + }) + }) - it('can request a file in package with unpacked files', function(done) { - var p = path.resolve(fixtures, 'asar', 'unpack.asar', 'a.txt'); - $.get("file://" + p, function(data) { - assert.equal(data.trim(), 'a'); - done(); - }); - }); + it('can request a file in package with unpacked files', function (done) { + var p = path.resolve(fixtures, 'asar', 'unpack.asar', 'a.txt') + $.get('file://' + p, function (data) { + assert.equal(data.trim(), 'a') + done() + }) + }) - it('can request a linked file in package', function(done) { - var p = path.resolve(fixtures, 'asar', 'a.asar', 'link2', 'link1'); - $.get("file://" + p, function(data) { - assert.equal(data.trim(), 'file1'); - done(); - }); - }); + it('can request a linked file in package', function (done) { + var p = path.resolve(fixtures, 'asar', 'a.asar', 'link2', 'link1') + $.get('file://' + p, function (data) { + assert.equal(data.trim(), 'file1') + done() + }) + }) - it('can request a file in filesystem', function(done) { - var p = path.resolve(fixtures, 'asar', 'file'); - $.get("file://" + p, function(data) { - assert.equal(data.trim(), 'file'); - done(); - }); - }); + it('can request a file in filesystem', function (done) { + var p = path.resolve(fixtures, 'asar', 'file') + $.get('file://' + p, function (data) { + assert.equal(data.trim(), 'file') + done() + }) + }) - it('gets 404 when file is not found', function(done) { - var p = path.resolve(fixtures, 'asar', 'a.asar', 'no-exist'); + it('gets 404 when file is not found', function (done) { + var p = path.resolve(fixtures, 'asar', 'a.asar', 'no-exist') $.ajax({ - url: "file://" + p, - error: function(err) { - assert.equal(err.status, 404); - done(); + url: 'file://' + p, + error: function (err) { + assert.equal(err.status, 404) + done() } - }); - }); + }) + }) - it('sets __dirname correctly', function(done) { - after(function() { - w.destroy(); - ipcMain.removeAllListeners('dirname'); - }); + it('sets __dirname correctly', function (done) { + after(function () { + w.destroy() + ipcMain.removeAllListeners('dirname') + }) var w = new BrowserWindow({ show: false, width: 400, height: 400 - }); - var p = path.resolve(fixtures, 'asar', 'web.asar', 'index.html'); + }) + var p = path.resolve(fixtures, 'asar', 'web.asar', 'index.html') var u = url.format({ protocol: 'file', slashed: true, pathname: p - }); - ipcMain.once('dirname', function(event, dirname) { - assert.equal(dirname, path.dirname(p)); - done(); - }); - w.loadURL(u); - }); + }) + ipcMain.once('dirname', function (event, dirname) { + assert.equal(dirname, path.dirname(p)) + done() + }) + w.loadURL(u) + }) - it('loads script tag in html', function(done) { - after(function() { - w.destroy(); - ipcMain.removeAllListeners('ping'); - }); + it('loads script tag in html', function (done) { + after(function () { + w.destroy() + ipcMain.removeAllListeners('ping') + }) var w = new BrowserWindow({ show: false, width: 400, height: 400 - }); - var p = path.resolve(fixtures, 'asar', 'script.asar', 'index.html'); + }) + var p = path.resolve(fixtures, 'asar', 'script.asar', 'index.html') var u = url.format({ protocol: 'file', slashed: true, pathname: p - }); - w.loadURL(u); - ipcMain.once('ping', function(event, message) { - assert.equal(message, 'pong'); - done(); - }); - }); - }); + }) + w.loadURL(u) + ipcMain.once('ping', function (event, message) { + assert.equal(message, 'pong') + done() + }) + }) + }) - describe('original-fs module', function() { - var originalFs = require('original-fs'); + describe('original-fs module', function () { + var originalFs = require('original-fs') - it('treats .asar as file', function() { - var file = path.join(fixtures, 'asar', 'a.asar'); - var stats = originalFs.statSync(file); - assert(stats.isFile()); - }); + it('treats .asar as file', function () { + var file = path.join(fixtures, 'asar', 'a.asar') + var stats = originalFs.statSync(file) + assert(stats.isFile()) + }) - it('is available in forked scripts', function(done) { - var child = child_process.fork(path.join(fixtures, 'module', 'original-fs.js')); - child.on('message', function(msg) { - assert.equal(msg, 'object'); - done(); - }); - child.send('message'); - }); - }); + it('is available in forked scripts', function (done) { + var child = child_process.fork(path.join(fixtures, 'module', 'original-fs.js')) + child.on('message', function (msg) { + assert.equal(msg, 'object') + done() + }) + child.send('message') + }) + }) - describe('graceful-fs module', function() { - var gfs = require('graceful-fs'); + describe('graceful-fs module', function () { + var gfs = require('graceful-fs') - it('recognize asar archvies', function() { - var p = path.join(fixtures, 'asar', 'a.asar', 'link1'); - assert.equal(gfs.readFileSync(p).toString().trim(), 'file1'); - }); - it('does not touch global fs object', function() { - assert.notEqual(fs.readdir, gfs.readdir); - }); - }); + it('recognize asar archvies', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'link1') + assert.equal(gfs.readFileSync(p).toString().trim(), 'file1') + }) + it('does not touch global fs object', function () { + assert.notEqual(fs.readdir, gfs.readdir) + }) + }) - describe('mkdirp module', function() { - var mkdirp = require('mkdirp'); + describe('mkdirp module', function () { + var mkdirp = require('mkdirp') - it('throws error when calling inside asar archive', function() { - var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); - assert.throws((function() { - mkdirp.sync(p); - }), new RegExp('ENOTDIR')); - }); - }); + it('throws error when calling inside asar archive', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + assert.throws(function () { + mkdirp.sync(p) + }, new RegExp('ENOTDIR')) + }) + }) - describe('native-image', function() { - it('reads image from asar archive', function() { - var p = path.join(fixtures, 'asar', 'logo.asar', 'logo.png'); - var logo = nativeImage.createFromPath(p); + describe('native-image', function () { + it('reads image from asar archive', function () { + var p = path.join(fixtures, 'asar', 'logo.asar', 'logo.png') + var logo = nativeImage.createFromPath(p) assert.deepEqual(logo.getSize(), { width: 55, height: 55 - }); - }); + }) + }) - it('reads image from asar archive with unpacked files', function() { - var p = path.join(fixtures, 'asar', 'unpack.asar', 'atom.png'); - var logo = nativeImage.createFromPath(p); + it('reads image from asar archive with unpacked files', function () { + var p = path.join(fixtures, 'asar', 'unpack.asar', 'atom.png') + var logo = nativeImage.createFromPath(p) assert.deepEqual(logo.getSize(), { width: 1024, height: 1024 - }); - }); - }); -}); + }) + }) + }) +}) diff --git a/spec/chromium-spec.js b/spec/chromium-spec.js index e4444f55b11d..cd046ab29abf 100644 --- a/spec/chromium-spec.js +++ b/spec/chromium-spec.js @@ -1,384 +1,474 @@ -const assert = require('assert'); -const http = require('http'); -const path = require('path'); -const ws = require('ws'); -const remote = require('electron').remote; -const BrowserWindow = remote.require('electron').BrowserWindow; -const session = remote.require('electron').session; +const assert = require('assert') +const http = require('http') +const path = require('path') +const ws = require('ws') +const remote = require('electron').remote -describe('chromium feature', function() { - var fixtures = path.resolve(__dirname, 'fixtures'); - var listener = null; +const BrowserWindow = remote.require('electron').BrowserWindow +const session = remote.require('electron').session - afterEach(function() { +const isCI = remote.getGlobal('isCi') + +describe('chromium feature', function () { + var fixtures = path.resolve(__dirname, 'fixtures') + var listener = null + + afterEach(function () { if (listener != null) { - window.removeEventListener('message', listener); + window.removeEventListener('message', listener) } - listener = null; - }); + listener = null + }) - xdescribe('heap snapshot', function() { - it('does not crash', function() { - process.atomBinding('v8_util').takeHeapSnapshot(); - }); - }); + xdescribe('heap snapshot', function () { + it('does not crash', function () { + process.atomBinding('v8_util').takeHeapSnapshot() + }) + }) - describe('sending request of http protocol urls', function() { - it('does not crash', function(done) { - this.timeout(5000); + describe('sending request of http protocol urls', function () { + it('does not crash', function (done) { + this.timeout(5000) - var server = http.createServer(function(req, res) { - res.end(); - server.close(); - done(); - }); - server.listen(0, '127.0.0.1', function() { - var port = server.address().port; - $.get("http://127.0.0.1:" + port); - }); - }); - }); + var server = http.createServer(function (req, res) { + res.end() + server.close() + done() + }) + server.listen(0, '127.0.0.1', function () { + var port = server.address().port + $.get('http://127.0.0.1:' + port) + }) + }) + }) - describe('document.hidden', function() { - var url = "file://" + fixtures + "/pages/document-hidden.html"; - var w = null; + describe('document.hidden', function () { + var url = 'file://' + fixtures + '/pages/document-hidden.html' + var w = null - afterEach(function() { - w != null ? w.destroy() : void 0; - }); + afterEach(function () { + w != null ? w.destroy() : void 0 + }) - it('is set correctly when window is not shown', function(done) { + it('is set correctly when window is not shown', function (done) { w = new BrowserWindow({ show: false - }); - w.webContents.on('ipc-message', function(event, args) { - assert.deepEqual(args, ['hidden', true]); - done(); - }); - w.loadURL(url); - }); + }) + w.webContents.on('ipc-message', function (event, args) { + assert.deepEqual(args, ['hidden', true]) + done() + }) + w.loadURL(url) + }) - it('is set correctly when window is inactive', function(done) { + if (isCI && process.platform === 'win32') { + return + } + + it('is set correctly when window is inactive', function (done) { w = new BrowserWindow({ show: false - }); - w.webContents.on('ipc-message', function(event, args) { - assert.deepEqual(args, ['hidden', false]); - done(); - }); - w.showInactive(); - w.loadURL(url); - }); - }); + }) + w.webContents.on('ipc-message', function (event, args) { + assert.deepEqual(args, ['hidden', false]) + done() + }) + w.showInactive() + w.loadURL(url) + }) + }) - xdescribe('navigator.webkitGetUserMedia', function() { - it('calls its callbacks', function(done) { - this.timeout(5000); + xdescribe('navigator.webkitGetUserMedia', function () { + it('calls its callbacks', function (done) { + this.timeout(5000) navigator.webkitGetUserMedia({ audio: true, video: false - }, function() { - done(); - }, function() { - done(); - }); - }); - }); + }, function () { + done() + }, function () { + done() + }) + }) + }) - describe('navigator.language', function() { - it('should not be empty', function() { - assert.notEqual(navigator.language, ''); - }); - }); + describe('navigator.mediaDevices', function () { + if (process.env.TRAVIS === 'true') { + return + } + if (isCI && process.platform === 'linux') { + return + } + if (isCI && process.platform === 'win32') { + return + } - describe('navigator.serviceWorker', function() { - var url = "file://" + fixtures + "/pages/service-worker/index.html"; - var w = null; + it('can return labels of enumerated devices', function (done) { + navigator.mediaDevices.enumerateDevices().then((devices) => { + const labels = devices.map((device) => device.label) + const labelFound = labels.some((label) => !!label) + if (labelFound) { + done() + } else { + done('No device labels found: ' + JSON.stringify(labels)) + } + }).catch(done) + }) + }) - afterEach(function() { - w != null ? w.destroy() : void 0; - }); + describe('navigator.language', function () { + it('should not be empty', function () { + assert.notEqual(navigator.language, '') + }) + }) - it('should register for file scheme', function(done) { + describe('navigator.serviceWorker', function () { + var url = 'file://' + fixtures + '/pages/service-worker/index.html' + var w = null + + afterEach(function () { + w != null ? w.destroy() : void 0 + }) + + it('should register for file scheme', function (done) { w = new BrowserWindow({ show: false - }); - w.webContents.on('ipc-message', function(event, args) { + }) + w.webContents.on('ipc-message', function (event, args) { if (args[0] === 'reload') { - w.webContents.reload(); + w.webContents.reload() } else if (args[0] === 'error') { - done('unexpected error : ' + args[1]); + done('unexpected error : ' + args[1]) } else if (args[0] === 'response') { - assert.equal(args[1], 'Hello from serviceWorker!'); + assert.equal(args[1], 'Hello from serviceWorker!') session.defaultSession.clearStorageData({ storages: ['serviceworkers'] - }, function() { - done(); - }); + }, function () { + done() + }) } - }); - w.loadURL(url); - }); - }); + }) + w.loadURL(url) + }) + }) - describe('window.open', function() { - this.timeout(20000); + describe('window.open', function () { + if (process.env.TRAVIS === 'true' && process.platform === 'darwin') { + return + } - it('returns a BrowserWindowProxy object', function() { - var b = window.open('about:blank', '', 'show=no'); - assert.equal(b.closed, false); - assert.equal(b.constructor.name, 'BrowserWindowProxy'); - b.close(); - }); + this.timeout(20000) - it('accepts "node-integration" as feature', function(done) { - var b; - listener = function(event) { - assert.equal(event.data, 'undefined'); - b.close(); - done(); - }; - window.addEventListener('message', listener); - b = window.open("file://" + fixtures + "/pages/window-opener-node.html", '', 'nodeIntegration=no,show=no'); - }); + it('returns a BrowserWindowProxy object', function () { + var b = window.open('about:blank', '', 'show=no') + assert.equal(b.closed, false) + assert.equal(b.constructor.name, 'BrowserWindowProxy') + b.close() + }) - it('inherit options of parent window', function(done) { - var b; - listener = function(event) { - var height, ref1, width; - ref1 = remote.getCurrentWindow().getSize(), width = ref1[0], height = ref1[1]; - assert.equal(event.data, "size: " + width + " " + height); - b.close(); - done(); - }; - window.addEventListener('message', listener); - b = window.open("file://" + fixtures + "/pages/window-open-size.html", '', 'show=no'); - }); + it('accepts "nodeIntegration" as feature', function (done) { + var b + listener = function (event) { + assert.equal(event.data.isProcessGlobalUndefined, true) + b.close() + done() + } + window.addEventListener('message', listener) + b = window.open('file://' + fixtures + '/pages/window-opener-node.html', '', 'nodeIntegration=no,show=no') + }) - it('does not override child options', function(done) { - var b, size; + it('inherit options of parent window', function (done) { + var b + listener = function (event) { + var ref1 = remote.getCurrentWindow().getSize() + var width = ref1[0] + var height = ref1[1] + assert.equal(event.data, 'size: ' + width + ' ' + height) + b.close() + done() + } + window.addEventListener('message', listener) + b = window.open('file://' + fixtures + '/pages/window-open-size.html', '', 'show=no') + }) + + it('disables node integration when it is disabled on the parent window', function (done) { + var b + listener = function (event) { + assert.equal(event.data.isProcessGlobalUndefined, true) + b.close() + done() + } + window.addEventListener('message', listener) + + var windowUrl = require('url').format({ + pathname: `${fixtures}/pages/window-opener-no-node-integration.html`, + protocol: 'file', + query: { + p: `${fixtures}/pages/window-opener-node.html` + }, + slashes: true + }) + b = window.open(windowUrl, '', 'nodeIntegration=no,show=no') + }) + + it('does not override child options', function (done) { + var b, size size = { width: 350, height: 450 - }; - listener = function(event) { - assert.equal(event.data, "size: " + size.width + " " + size.height); - b.close(); - done(); - }; - window.addEventListener('message', listener); - b = window.open("file://" + fixtures + "/pages/window-open-size.html", '', "show=no,width=" + size.width + ",height=" + size.height); - }); - }); + } + listener = function (event) { + assert.equal(event.data, 'size: ' + size.width + ' ' + size.height) + b.close() + done() + } + window.addEventListener('message', listener) + b = window.open('file://' + fixtures + '/pages/window-open-size.html', '', 'show=no,width=' + size.width + ',height=' + size.height) + }) - describe('window.opener', function() { - this.timeout(10000); + it('defines a window.location getter', function (done) { + var b, targetURL + if (process.platform == 'win32') + targetURL = 'file:///' + fixtures.replace(/\\/g, '/') + '/pages/base-page.html' + else + targetURL = 'file://' + fixtures + '/pages/base-page.html' + b = window.open(targetURL) + BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function () { + assert.equal(b.location, targetURL) + b.close() + done() + }) + }) - var url = "file://" + fixtures + "/pages/window-opener.html"; - var w = null; + it('defines a window.location setter', function (done) { + // Load a page that definitely won't redirect + var b + b = window.open('about:blank') + BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function () { + // When it loads, redirect + b.location = 'file://' + fixtures + '/pages/base-page.html' + BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function () { + // After our second redirect, cleanup and callback + b.close() + done() + }) + }) + }) + }) - afterEach(function() { - w != null ? w.destroy() : void 0; - }); + describe('window.opener', function () { + this.timeout(10000) - it('is null for main window', function(done) { + var url = 'file://' + fixtures + '/pages/window-opener.html' + var w = null + + afterEach(function () { + w != null ? w.destroy() : void 0 + }) + + it('is null for main window', function (done) { w = new BrowserWindow({ show: false - }); - w.webContents.on('ipc-message', function(event, args) { - assert.deepEqual(args, ['opener', null]); - done(); - }); - w.loadURL(url); - }); + }) + w.webContents.on('ipc-message', function (event, args) { + assert.deepEqual(args, ['opener', null]) + done() + }) + w.loadURL(url) + }) - it('is not null for window opened by window.open', function(done) { - var b; - listener = function(event) { - assert.equal(event.data, 'object'); - b.close(); - done(); - }; - window.addEventListener('message', listener); - b = window.open(url, '', 'show=no'); - }); - }); - - describe('window.postMessage', function() { - it('sets the source and origin correctly', function(done) { - var b, sourceId; - sourceId = remote.getCurrentWindow().id; - listener = function(event) { - window.removeEventListener('message', listener); - b.close(); - var message = JSON.parse(event.data); - assert.equal(message.data, 'testing'); - assert.equal(message.origin, 'file://'); - assert.equal(message.sourceEqualsOpener, true); - assert.equal(message.sourceId, sourceId); - assert.equal(event.origin, 'file://'); - done(); - }; - window.addEventListener('message', listener); - b = window.open("file://" + fixtures + "/pages/window-open-postMessage.html", '', 'show=no'); - BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function() { - b.postMessage('testing', '*'); - }); - }); - }); - - describe('window.opener.postMessage', function() { - it('sets source and origin correctly', function(done) { - var b; - listener = function(event) { - window.removeEventListener('message', listener); - b.close(); - assert.equal(event.source, b); - assert.equal(event.origin, 'file://'); - done(); - }; - window.addEventListener('message', listener); - b = window.open("file://" + fixtures + "/pages/window-opener-postMessage.html", '', 'show=no'); - }); - }); - - describe('creating a Uint8Array under browser side', function() { - it('does not crash', function() { - var RUint8Array = remote.getGlobal('Uint8Array'); - new RUint8Array; - }); - }); - - describe('webgl', function() { - it('can be get as context in canvas', function() { - if (process.platform === 'linux') { - return; + it('is not null for window opened by window.open', function (done) { + var b + listener = function (event) { + assert.equal(event.data, 'object') + b.close() + done() } - var webgl = document.createElement('canvas').getContext('webgl'); - assert.notEqual(webgl, null); - }); - }); + window.addEventListener('message', listener) + b = window.open(url, '', 'show=no') + }) + }) - describe('web workers', function() { - it('Worker can work', function(done) { - var worker = new Worker('../fixtures/workers/worker.js'); - var message = 'ping'; - worker.onmessage = function(event) { - assert.equal(event.data, message); - worker.terminate(); - done(); - }; - worker.postMessage(message); - }); + describe('window.postMessage', function () { + it('sets the source and origin correctly', function (done) { + var b, sourceId + sourceId = remote.getCurrentWindow().id + listener = function (event) { + window.removeEventListener('message', listener) + b.close() + var message = JSON.parse(event.data) + assert.equal(message.data, 'testing') + assert.equal(message.origin, 'file://') + assert.equal(message.sourceEqualsOpener, true) + assert.equal(message.sourceId, sourceId) + assert.equal(event.origin, 'file://') + done() + } + window.addEventListener('message', listener) + b = window.open('file://' + fixtures + '/pages/window-open-postMessage.html', '', 'show=no') + BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function () { + b.postMessage('testing', '*') + }) + }) + }) - it('SharedWorker can work', function(done) { - var worker = new SharedWorker('../fixtures/workers/shared_worker.js'); - var message = 'ping'; - worker.port.onmessage = function(event) { - assert.equal(event.data, message); - done(); - }; - worker.port.postMessage(message); - }); - }); + describe('window.opener.postMessage', function () { + it('sets source and origin correctly', function (done) { + var b + listener = function (event) { + window.removeEventListener('message', listener) + b.close() + assert.equal(event.source, b) + assert.equal(event.origin, 'file://') + done() + } + window.addEventListener('message', listener) + b = window.open('file://' + fixtures + '/pages/window-opener-postMessage.html', '', 'show=no') + }) + }) - describe('iframe', function() { - var iframe = null; + describe('creating a Uint8Array under browser side', function () { + it('does not crash', function () { + var RUint8Array = remote.getGlobal('Uint8Array') + var arr = new RUint8Array() + assert(arr) + }) + }) - beforeEach(function() { - iframe = document.createElement('iframe'); - }); + describe('webgl', function () { + if (isCI && process.platform === 'win32') { + return + } - afterEach(function() { - document.body.removeChild(iframe); - }); + it('can be get as context in canvas', function () { + if (process.platform === 'linux') return - it('does not have node integration', function(done) { - iframe.src = "file://" + fixtures + "/pages/set-global.html"; - document.body.appendChild(iframe); - iframe.onload = function() { - assert.equal(iframe.contentWindow.test, 'undefined undefined undefined'); - done(); - }; - }); - }); + var webgl = document.createElement('canvas').getContext('webgl') + assert.notEqual(webgl, null) + }) + }) - describe('storage', function() { - it('requesting persitent quota works', function(done) { - navigator.webkitPersistentStorage.requestQuota(1024 * 1024, function(grantedBytes) { - assert.equal(grantedBytes, 1048576); - done(); - }); - }); - }); + describe('web workers', function () { + it('Worker can work', function (done) { + var worker = new Worker('../fixtures/workers/worker.js') + var message = 'ping' + worker.onmessage = function (event) { + assert.equal(event.data, message) + worker.terminate() + done() + } + worker.postMessage(message) + }) - describe('websockets', function() { - var wss = null; - var server = null; - var WebSocketServer = ws.Server; + it('SharedWorker can work', function (done) { + var worker = new SharedWorker('../fixtures/workers/shared_worker.js') + var message = 'ping' + worker.port.onmessage = function (event) { + assert.equal(event.data, message) + done() + } + worker.port.postMessage(message) + }) + }) - afterEach(function() { - wss.close(); - server.close(); - }); + describe('iframe', function () { + var iframe = null - it('has user agent', function(done) { - server = http.createServer(); - server.listen(0, '127.0.0.1', function() { - var port = server.address().port; + beforeEach(function () { + iframe = document.createElement('iframe') + }) + + afterEach(function () { + document.body.removeChild(iframe) + }) + + it('does not have node integration', function (done) { + iframe.src = 'file://' + fixtures + '/pages/set-global.html' + document.body.appendChild(iframe) + iframe.onload = function () { + assert.equal(iframe.contentWindow.test, 'undefined undefined undefined') + done() + } + }) + }) + + describe('storage', function () { + it('requesting persitent quota works', function (done) { + navigator.webkitPersistentStorage.requestQuota(1024 * 1024, function (grantedBytes) { + assert.equal(grantedBytes, 1048576) + done() + }) + }) + }) + + describe('websockets', function () { + var wss = null + var server = null + var WebSocketServer = ws.Server + + afterEach(function () { + wss.close() + server.close() + }) + + it('has user agent', function (done) { + server = http.createServer() + server.listen(0, '127.0.0.1', function () { + var port = server.address().port wss = new WebSocketServer({ server: server - }); - wss.on('error', done); - wss.on('connection', function(ws) { + }) + wss.on('error', done) + wss.on('connection', function (ws) { if (ws.upgradeReq.headers['user-agent']) { - done(); + done() } else { - done('user agent is empty'); + done('user agent is empty') } - }); - new WebSocket("ws://127.0.0.1:" + port); - }); - }); - }); + }) + var socket = new WebSocket(`ws://127.0.0.1:${port}`) + assert(socket) + }) + }) + }) - describe('Promise', function() { - it('resolves correctly in Node.js calls', function(done) { + describe('Promise', function () { + it('resolves correctly in Node.js calls', function (done) { document.registerElement('x-element', { prototype: Object.create(HTMLElement.prototype, { createdCallback: { - value: function() {} + value: function () {} } }) - }); - setImmediate(function() { - var called = false; - Promise.resolve().then(function() { - done(called ? void 0 : new Error('wrong sequence')); - }); - document.createElement('x-element'); - called = true; - }); - }); + }) + setImmediate(function () { + var called = false + Promise.resolve().then(function () { + done(called ? void 0 : new Error('wrong sequence')) + }) + document.createElement('x-element') + called = true + }) + }) - it('resolves correctly in Electron calls', function(done) { + it('resolves correctly in Electron calls', function (done) { document.registerElement('y-element', { prototype: Object.create(HTMLElement.prototype, { createdCallback: { - value: function() {} + value: function () {} } }) - }); - remote.getGlobal('setImmediate')(function() { - var called = false; - Promise.resolve().then(function() { - done(called ? void 0 : new Error('wrong sequence')); - }); - document.createElement('y-element'); - called = true; - }); - }); - }); -}); + }) + remote.getGlobal('setImmediate')(function () { + var called = false + Promise.resolve().then(function () { + done(called ? void 0 : new Error('wrong sequence')) + }) + document.createElement('y-element') + called = true + }) + }) + }) +}) diff --git a/spec/fixtures/api/close-beforeunload-string.html b/spec/fixtures/api/close-beforeunload-string.html deleted file mode 100644 index c8c6c6beacdd..000000000000 --- a/spec/fixtures/api/close-beforeunload-string.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/spec/fixtures/api/close-beforeunload-true.html b/spec/fixtures/api/close-beforeunload-undefined.html similarity index 93% rename from spec/fixtures/api/close-beforeunload-true.html rename to spec/fixtures/api/close-beforeunload-undefined.html index 6de0c6cd3989..b4e4178f6a1d 100644 --- a/spec/fixtures/api/close-beforeunload-true.html +++ b/spec/fixtures/api/close-beforeunload-undefined.html @@ -5,7 +5,6 @@ setTimeout(function() { require('electron').remote.getCurrentWindow().emit('onbeforeunload'); }, 0); - return true; } window.close(); diff --git a/spec/fixtures/api/did-fail-load-iframe.html b/spec/fixtures/api/did-fail-load-iframe.html new file mode 100644 index 000000000000..293041b65ecb --- /dev/null +++ b/spec/fixtures/api/did-fail-load-iframe.html @@ -0,0 +1,5 @@ + + + + + diff --git a/spec/fixtures/api/electron-module-app/index.html b/spec/fixtures/api/electron-module-app/index.html new file mode 100644 index 000000000000..02bfee958747 --- /dev/null +++ b/spec/fixtures/api/electron-module-app/index.html @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/spec/fixtures/api/electron-module-app/node_modules/electron/index.js b/spec/fixtures/api/electron-module-app/node_modules/electron/index.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/spec/fixtures/api/electron-module-app/node_modules/electron/package.json b/spec/fixtures/api/electron-module-app/node_modules/electron/package.json new file mode 100644 index 000000000000..07e3804ebb16 --- /dev/null +++ b/spec/fixtures/api/electron-module-app/node_modules/electron/package.json @@ -0,0 +1,4 @@ +{ + "name": "electron", + "main": "index.js" +} diff --git a/spec/fixtures/api/electron-module-app/node_modules/foo/index.js b/spec/fixtures/api/electron-module-app/node_modules/foo/index.js new file mode 100644 index 000000000000..11d763e51742 --- /dev/null +++ b/spec/fixtures/api/electron-module-app/node_modules/foo/index.js @@ -0,0 +1 @@ +exports.bar = function () {} diff --git a/spec/fixtures/api/electron-module-app/node_modules/foo/package.json b/spec/fixtures/api/electron-module-app/node_modules/foo/package.json new file mode 100644 index 000000000000..596ac286ecad --- /dev/null +++ b/spec/fixtures/api/electron-module-app/node_modules/foo/package.json @@ -0,0 +1,4 @@ +{ + "name": "foo", + "main": "index.js" +} diff --git a/spec/fixtures/api/quit-app/main.js b/spec/fixtures/api/quit-app/main.js index 114e830076a0..e2f97affe6de 100644 --- a/spec/fixtures/api/quit-app/main.js +++ b/spec/fixtures/api/quit-app/main.js @@ -1,12 +1,12 @@ -var app = require('electron').app; +var app = require('electron').app app.on('ready', function () { // This setImmediate call gets the spec passing on Linux setImmediate(function () { - app.exit(123); - }); -}); + app.exit(123) + }) +}) process.on('exit', function (code) { - console.log('Exit event with code: ' + code); -}); + console.log('Exit event with code: ' + code) +}) diff --git a/spec/fixtures/api/quit-app/package.json b/spec/fixtures/api/quit-app/package.json index ea5bb1643b9b..fbdee7f90f95 100644 --- a/spec/fixtures/api/quit-app/package.json +++ b/spec/fixtures/api/quit-app/package.json @@ -1,4 +1,4 @@ { - "name": "quit-app", + "name": "electron-quit-app", "main": "main.js" } diff --git a/spec/fixtures/api/relaunch/main.js b/spec/fixtures/api/relaunch/main.js new file mode 100644 index 000000000000..74cafc6f0d53 --- /dev/null +++ b/spec/fixtures/api/relaunch/main.js @@ -0,0 +1,25 @@ +const {app, dialog} = require('electron') +const net = require('net') + +const socketPath = process.platform === 'win32' ? + '\\\\.\\pipe\\electron-app-relaunch' : + '/tmp/electron-app-relaunch' + +process.on('uncaughtException', () => { + app.exit(1) +}) + +app.once('ready', () => { + let lastArg = process.argv[process.argv.length - 1] + const client = net.connect(socketPath) + client.once('connect', () => { + client.end(String(lastArg === '--second')) + }) + client.once('end', () => { + app.exit(0) + }) + + if (lastArg !== '--second') { + app.relaunch({args: process.argv.slice(1).concat('--second')}) + } +}) diff --git a/spec/fixtures/api/relaunch/package.json b/spec/fixtures/api/relaunch/package.json new file mode 100644 index 000000000000..dbaabc84896d --- /dev/null +++ b/spec/fixtures/api/relaunch/package.json @@ -0,0 +1,5 @@ +{ + "name": "electron-app-relaunch", + "main": "main.js" +} + diff --git a/spec/fixtures/certificates/certs.cnf b/spec/fixtures/certificates/certs.cnf new file mode 100644 index 000000000000..76ef8d073f9d --- /dev/null +++ b/spec/fixtures/certificates/certs.cnf @@ -0,0 +1,68 @@ +ID=1 +CA_DIR=out + +[ca] +default_ca = ca_settings + +[ca_settings] +dir = ${ENV::CA_DIR} +database = $dir/${ENV::ID}-index.txt +new_certs_dir = $dir +serial = $dir/${ENV::ID}-serial +certificate = $dir/${ENV::ID}.pem +private_key = $dir/${ENV::ID}.key +RANDFILE = $dir/rand +default_md = sha256 +default_days = 3650 +policy = policy_anything +preserve = no + +[policy_anything] +# Default signing policy +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[req] +default_bits = 2048 +default_md = sha256 +string_mask = utf8only +distinguished_name = req_env_dn +prompt = no + +[user_cert] +basicConstraints = CA:FALSE +nsCertType = client +nsComment = "OpenSSL Generated Client Certificate" +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer +keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, emailProtection + +[server_cert] +basicConstraints = CA:FALSE +nsCertType = server +nsComment = "OpenSSL Generated Server Certificate" +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always +keyUsage = critical, digitalSignature, keyEncipherment +extendedKeyUsage = serverAuth + +[ca_cert] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer +basicConstraints = critical, CA:true +keyUsage = critical, digitalSignature, cRLSign, keyCertSign + +[ca_intermediate_cert] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer +basicConstraints = critical, CA:true, pathlen:0 +keyUsage = critical, digitalSignature, cRLSign, keyCertSign + +[req_env_dn] +commonName = ${ENV::COMMON_NAME} diff --git a/spec/fixtures/certificates/client.p12 b/spec/fixtures/certificates/client.p12 new file mode 100644 index 000000000000..7543c7d38904 Binary files /dev/null and b/spec/fixtures/certificates/client.p12 differ diff --git a/spec/fixtures/certificates/generate_certs.sh b/spec/fixtures/certificates/generate_certs.sh new file mode 100755 index 000000000000..c8e6217e66fd --- /dev/null +++ b/spec/fixtures/certificates/generate_certs.sh @@ -0,0 +1,127 @@ +#!/bin/bash +# This script generates certificates that can be used to test SSL client +# authentication. +# +# 1. A (end-entity) -> B -> C (self-signed root) +# 2. D (end-entity) -> B -> C (self-signed root) + +try () { + echo "$@" + "$@" || exit 1 +} + +try mkdir out + +echo Create the serial number files and indices. +serial=1000 +for i in B C +do + try /bin/sh -c "echo $serial > out/$i-serial" + serial=$(expr $serial + 1) + touch out/$i-index.txt + touch out/$i-index.txt.attr +done + +echo Generate the keys. +for i in A B C D +do + try openssl genrsa -out out/$i.key 2048 +done + +echo Generate the C CSR +COMMON_NAME="Root CA" \ + CA_DIR=out \ + ID=C \ + try openssl req \ + -new \ + -key out/C.key \ + -out out/C.csr \ + -config certs.cnf + +echo C signs itself. +COMMON_NAME="Root CA" \ + CA_DIR=out \ + ID=C \ + try openssl x509 \ + -req -days 3650 \ + -in out/C.csr \ + -extensions ca_cert \ + -extfile certs.cnf \ + -signkey out/C.key \ + -out out/C.pem + +echo Generate the intermediates +COMMON_NAME="Intermediate CA" \ + CA_DIR=out \ + ID=B \ + try openssl req \ + -new \ + -key out/B.key \ + -out out/B.csr \ + -config certs.cnf + +COMMON_NAME="Root CA" \ + CA_DIR=out \ + ID=C \ + try openssl ca \ + -batch \ + -extensions ca_intermediate_cert \ + -in out/B.csr \ + -out out/B.pem \ + -config certs.cnf + +echo Generate the leaf certs +COMMON_NAME="Client Cert" \ + ID=A \ + try openssl req \ + -new \ + -key out/A.key \ + -out out/A.csr \ + -config certs.cnf + +echo B signs A +COMMON_NAME="Intermediate CA" \ + CA_DIR=out \ + ID=B \ + try openssl ca \ + -batch \ + -extensions user_cert \ + -in out/A.csr \ + -out out/A.pem \ + -config certs.cnf + +COMMON_NAME="localhost" \ + ID=D \ + try openssl req \ + -new \ + -key out/D.key \ + -out out/D.csr \ + -config certs.cnf + +echo B signs D +COMMON_NAME="Intermediate CA" \ + CA_DIR=out \ + ID=B \ + try openssl ca \ + -batch \ + -extensions server_cert \ + -in out/D.csr \ + -out out/D.pem \ + -config certs.cnf + +echo Package the client cert and private key into PKCS12 file +try /bin/sh -c "cat out/A.pem out/A.key out/B.pem out/C.pem > out/A-chain.pem" + +try openssl pkcs12 \ + -in out/A-chain.pem \ + -out client.p12 \ + -export \ + -passout pass:electron + +echo Package the certs +try cp out/C.pem rootCA.pem +try cp out/B.pem intermediateCA.pem +try cp out/D.key server.key +try cp out/D.pem server.pem + +try rm -rf out diff --git a/spec/fixtures/certificates/intermediateCA.pem b/spec/fixtures/certificates/intermediateCA.pem new file mode 100644 index 000000000000..58293f76da0b --- /dev/null +++ b/spec/fixtures/certificates/intermediateCA.pem @@ -0,0 +1,78 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4097 (0x1001) + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=Root CA + Validity + Not Before: Apr 18 16:14:29 2016 GMT + Not After : Apr 16 16:14:29 2026 GMT + Subject: CN=Intermediate CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:b6:42:02:13:25:40:13:a6:05:99:69:da:0c:c9: + a8:bf:86:3b:fc:c6:51:ba:64:65:7e:33:11:31:d5: + 03:45:30:4c:ca:49:d2:96:42:52:2f:f9:e6:6c:9a: + 50:1c:fe:fa:e2:e8:63:36:14:47:f7:49:9f:78:28: + 5e:1f:0b:9d:9e:f8:d3:33:77:06:4d:6d:14:c0:57: + 01:83:2b:ef:99:06:48:21:ec:c1:d7:05:48:2c:ea: + 83:06:6a:20:df:73:ce:8a:a5:e4:81:00:41:84:cf: + 89:81:78:2e:3a:bd:1b:fd:3e:96:08:8d:44:1b:00: + c8:d6:4e:7a:6a:75:c0:9b:3c:e0:fa:aa:3a:82:5b: + 3c:39:32:ca:4a:ba:82:bc:60:47:6f:e4:4a:fd:dc: + a0:72:8a:1b:fe:cd:2e:10:f4:27:4c:08:4e:d1:ed: + dc:08:b0:f8:1f:e4:fc:45:72:43:58:6e:dd:05:37: + 8c:04:a1:fb:64:f4:3f:90:bb:85:f2:4c:97:46:fd: + 1f:29:e5:19:d0:0f:24:fd:d1:00:c5:b6:be:da:84: + 62:77:be:db:67:f6:ec:98:5d:97:f5:df:0a:bd:b8: + 07:7f:0a:d5:92:29:1f:c4:b0:97:4f:e4:87:d7:a9: + 00:c9:61:d5:6c:cd:6a:fc:56:c3:f3:b7:ca:53:70: + 02:3f + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + A9:75:99:CF:9C:92:54:A4:4B:65:CD:3D:FC:93:98:8D:9E:09:1F:47 + X509v3 Authority Key Identifier: + keyid:E3:51:87:E3:CD:7A:B3:26:9F:8F:EC:62:D1:0E:15:0C:39:36:47:4F + + X509v3 Basic Constraints: critical + CA:TRUE, pathlen:0 + X509v3 Key Usage: critical + Digital Signature, Certificate Sign, CRL Sign + Signature Algorithm: sha256WithRSAEncryption + 55:69:d6:1d:33:ad:ab:40:46:fd:34:02:c1:43:50:7b:90:ea: + f3:5f:4f:b6:2c:28:aa:72:e0:4b:36:2e:8f:44:93:15:52:14: + f6:61:b3:50:e0:ba:43:91:ba:a9:5d:ac:43:b7:52:ca:91:a3: + d7:0e:ac:a7:9e:ee:28:7f:2d:0f:93:b5:d9:23:35:68:54:29: + 2a:e7:3a:4c:41:24:d0:5e:2d:f3:1e:b9:52:f1:3e:16:76:93: + 89:6d:a1:4c:63:f5:4a:cc:08:36:61:29:0a:29:5f:f4:5a:55: + 98:10:b3:de:b3:90:f9:03:e5:bd:1b:61:01:a7:22:03:ae:0f: + 77:c4:a8:bf:31:b4:af:c8:c7:e3:25:a1:2b:b9:43:37:3b:08: + ea:c4:46:60:b8:5f:ee:2a:0d:ce:18:75:63:ba:32:28:84:f4: + 56:95:1b:c5:f9:46:7e:14:2e:83:5e:a9:ff:b2:80:ca:25:fd: + 22:90:b5:de:bd:e6:f1:0c:ee:7e:09:71:0d:82:6a:ca:2f:9c: + 96:45:73:3a:65:bc:d8:9d:e0:61:01:5d:a8:de:de:61:8c:82: + 52:0c:ef:97:39:b3:13:c6:7d:d0:c0:f5:6d:c8:70:5b:96:e8: + 99:31:d8:75:3a:21:58:ab:01:21:9e:38:8e:53:ff:f8:48:a7: + af:01:9a:93 +-----BEGIN CERTIFICATE----- +MIIDDjCCAfagAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UEAwwHUm9v +dCBDQTAeFw0xNjA0MTgxNjE0MjlaFw0yNjA0MTYxNjE0MjlaMBoxGDAWBgNVBAMM +D0ludGVybWVkaWF0ZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALZCAhMlQBOmBZlp2gzJqL+GO/zGUbpkZX4zETHVA0UwTMpJ0pZCUi/55myaUBz+ ++uLoYzYUR/dJn3goXh8LnZ740zN3Bk1tFMBXAYMr75kGSCHswdcFSCzqgwZqIN9z +zoql5IEAQYTPiYF4Ljq9G/0+lgiNRBsAyNZOemp1wJs84PqqOoJbPDkyykq6grxg +R2/kSv3coHKKG/7NLhD0J0wITtHt3Aiw+B/k/EVyQ1hu3QU3jASh+2T0P5C7hfJM +l0b9HynlGdAPJP3RAMW2vtqEYne+22f27Jhdl/XfCr24B38K1ZIpH8Swl0/kh9ep +AMlh1WzNavxWw/O3ylNwAj8CAwEAAaNmMGQwHQYDVR0OBBYEFKl1mc+cklSkS2XN +PfyTmI2eCR9HMB8GA1UdIwQYMBaAFONRh+PNerMmn4/sYtEOFQw5NkdPMBIGA1Ud +EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB +AQBVadYdM62rQEb9NALBQ1B7kOrzX0+2LCiqcuBLNi6PRJMVUhT2YbNQ4LpDkbqp +XaxDt1LKkaPXDqynnu4ofy0Pk7XZIzVoVCkq5zpMQSTQXi3zHrlS8T4WdpOJbaFM +Y/VKzAg2YSkKKV/0WlWYELPes5D5A+W9G2EBpyIDrg93xKi/MbSvyMfjJaEruUM3 +OwjqxEZguF/uKg3OGHVjujIohPRWlRvF+UZ+FC6DXqn/soDKJf0ikLXevebxDO5+ +CXENgmrKL5yWRXM6ZbzYneBhAV2o3t5hjIJSDO+XObMTxn3QwPVtyHBbluiZMdh1 +OiFYqwEhnjiOU//4SKevAZqT +-----END CERTIFICATE----- diff --git a/spec/fixtures/certificates/rootCA.pem b/spec/fixtures/certificates/rootCA.pem new file mode 100644 index 000000000000..5c77fe61cd0f --- /dev/null +++ b/spec/fixtures/certificates/rootCA.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCjCCAfKgAwIBAgIJAOcWbv0WHll0MA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV +BAMMB1Jvb3QgQ0EwHhcNMTYwNDE4MTYxNDI5WhcNMjYwNDE2MTYxNDI5WjASMRAw +DgYDVQQDDAdSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +0iZvelv6MjxqdmQsAmKqIfc5gvQjYB3CqWlEH3g5czPFBMMtmOI9czlk+0jc1VEf +t1SKst7zwe1rpxFArgudV45NBHQH3ZlzkLeO7Ol2kPzlyMHNJ70vT3CBitKnLl4B +bg7xf6kDQQlC3/QeWxvbR5cvp131uwcpXKdJ9k4dwpfS2BKiRb5Uk46DgX5kGaka +q/tQ2F7b6AlAoTq608tZBuOInkg2tTbGe9PDWSL8oMZRwCSbF543SAR45zjWBa0k +ymY31VvlYbEd/3lfE5Mrn/JwZQpTKOfcOI//kUkcClJVpSMObh4eiy1oNjqcJ4KR +/4hkY7oTQCA7zWD34jQpkQIDAQABo2MwYTAdBgNVHQ4EFgQU41GH4816syafj+xi +0Q4VDDk2R08wHwYDVR0jBBgwFoAU41GH4816syafj+xi0Q4VDDk2R08wDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggEBADrt +1bSbOYxDmB1x9KaHNFVSzs0XsipsbZW/XTqtNMPMFXh+I57NVptVanCwcfU5zmOF +AjL+R8v0NkPU9+6nSM2PYqWxEavf0f6pkxIj+ThFwtVwXEKocPG9RFUvZNZv+rSH +yAnzuAzFI71EsT9VgJGHI0pgPjrGbSlNfb0OJFOlwtbGWGofmg+N6hHcx5nVKlgL +ZWLtYT+/mT2pSGuIpJtdnuUv0vcrRa4mxAa8NPF4+Qpi6yErkfogE+T4RYf2L4rp +CaRIFicLoNUmwK0nCerJaPFLwGkiNGNX81CHnw3+xLisSPvxze2ZRA0DhUWUGInq +grjWDMO9P1hPWu5jmbo= +-----END CERTIFICATE----- diff --git a/spec/fixtures/certificates/server.key b/spec/fixtures/certificates/server.key new file mode 100644 index 000000000000..719bfc8797df --- /dev/null +++ b/spec/fixtures/certificates/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAuByk7Ib6anz0xOFRqyogGsTTEAAGduquIckVFajDHrk7vdq+ +TflkDp+UhbISqu1rj0OuaqODxiPSSw5fHClqNKpjhHS1ry86knS847BGwrByY0oa +zBdN0UgtWiXOK+iCuSI9p0KSIORJ3Y70pUZmm12EHRwb0tANv4ogYxjAxwnyqYgn +PnrDsoyCh/Cb4Fu8XucrnepYbInXi6yOdwTuivTx9qy7tlQzvYJ2LLEUIJdBtCUZ +dZkxky9CJUbfu5rm6PFvbLII5YCSlpXLxg9bumZCR1z9IXE6rLYcJIp3HIquR2cN +tAs9M8OHuR5V6vhUG51bP3aTkg3asJVdUe10dwIDAQABAoIBAQCJGINSwZv86blW +VbX7r+2iIUhNVMd7i3tJGzQBId7RpPswf49P/tIb9YaiG5y8/PgoAS0CqWn5hDkW +vMfj747vUqWyPzn/DjseTaFOJrg6RyuWddsIeJ3wpj9nLlmc5pFZDH8+alrn9TZv +rgDMhWTocjVre7/YNibWpyNAx3DdhG5DzNVLnu1R68d5k3JutQVqm01xCAV9ne9n +xE1RB5Z1xLvpQfW2qLYT0yFB7Xxw8awGyzVesPhGW1aa5F4urQjdCt2baa06Xolu +T3wXJ6wA9BuF2KOCi8DxELDaXoB//+82HafgWbOWIhJFOzEZaMNqZkfS/GbCgpEr +mE2r8zGBAoGBAOHNcUPgnIIeGdgFZZvL3Ge3Hp5mi2Vd2KBkAPNCjVSXeCu57yRC +SetlYuZlIhd7o+wdxUmWtg73DU19eDXJsOjXgNAoJfT9Zsyi4RClmJ0FRcSzvFU/ +m/TKrBbnFFAI+1pKwDnQ7envuRiTECFSsvKqdr8hddx0cPCgDtbe+75BAoGBANC7 +4ozkgsUTtdojz0DYBYBwUjN1gUETIhl91tt+48hmmEedROWDQDCT9gJfpAVFe1I6 +RyKKJnBcgNDJ7mqPUB1f5xb5rtaZS1owPNYTi3GrdVVg3lAf0j5ch8XoRJn/plnL +M0Sj5lLMviHJjyk8CPHbnE2k2vERAW4/SgzfA3S3AoGAHx55Jamm6CfN1/+maTpH +PeP2zE3FmEq+uBwQJXZek/HsFdqiIpUgKtjmMGpvsFzR0pCnx+SFYrqZkrxf/Mm3 +H9/TWNyvnnvt1vX7npez2LAJVXqP0g/aJnpoDR/7pKwYN/FlXJJ2t27aS5C5AF6t +WtQzWVP7Mk654e+tG9/PQgECgYEAiTCT7EpccK9NvLwAgfv5UbuBK3U1qNGsfdip +mMZDa/mSaK9DEx462DLHZDP8F8LdFORc0KTAMuV5fMDbxInA/C2GMyGT+lPypKpD +sehSpDku+xiZxUvE4VvrmPXZ8OWILkhRv/GBdjY/WPGi+FUPA/d1Ocr6Y6rrp8xN +HTyOhu0CgYBKxTSH6RCQsm8Q8uqmcP7cwe6fciTC0c2CRRqlzuXeseG72MBRDk/8 +P1jtOIlIsax/8NwNm5ReAiLgVn/h6/YgN4fpMkV1XIaH4j7HiGf5CWgOTWxS9jWA +cV09H22BaNkT0fZ71IlXQI11cVRodX0g4cJXeuyTxY9OkMd6cGs8+A== +-----END RSA PRIVATE KEY----- diff --git a/spec/fixtures/certificates/server.pem b/spec/fixtures/certificates/server.pem new file mode 100644 index 000000000000..117be807e528 --- /dev/null +++ b/spec/fixtures/certificates/server.pem @@ -0,0 +1,88 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4097 (0x1001) + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=Intermediate CA + Validity + Not Before: Apr 18 16:14:29 2016 GMT + Not After : Apr 16 16:14:29 2026 GMT + Subject: CN=localhost + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:b8:1c:a4:ec:86:fa:6a:7c:f4:c4:e1:51:ab:2a: + 20:1a:c4:d3:10:00:06:76:ea:ae:21:c9:15:15:a8: + c3:1e:b9:3b:bd:da:be:4d:f9:64:0e:9f:94:85:b2: + 12:aa:ed:6b:8f:43:ae:6a:a3:83:c6:23:d2:4b:0e: + 5f:1c:29:6a:34:aa:63:84:74:b5:af:2f:3a:92:74: + bc:e3:b0:46:c2:b0:72:63:4a:1a:cc:17:4d:d1:48: + 2d:5a:25:ce:2b:e8:82:b9:22:3d:a7:42:92:20:e4: + 49:dd:8e:f4:a5:46:66:9b:5d:84:1d:1c:1b:d2:d0: + 0d:bf:8a:20:63:18:c0:c7:09:f2:a9:88:27:3e:7a: + c3:b2:8c:82:87:f0:9b:e0:5b:bc:5e:e7:2b:9d:ea: + 58:6c:89:d7:8b:ac:8e:77:04:ee:8a:f4:f1:f6:ac: + bb:b6:54:33:bd:82:76:2c:b1:14:20:97:41:b4:25: + 19:75:99:31:93:2f:42:25:46:df:bb:9a:e6:e8:f1: + 6f:6c:b2:08:e5:80:92:96:95:cb:c6:0f:5b:ba:66: + 42:47:5c:fd:21:71:3a:ac:b6:1c:24:8a:77:1c:8a: + ae:47:67:0d:b4:0b:3d:33:c3:87:b9:1e:55:ea:f8: + 54:1b:9d:5b:3f:76:93:92:0d:da:b0:95:5d:51:ed: + 74:77 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Cert Type: + SSL Server + Netscape Comment: + OpenSSL Generated Server Certificate + X509v3 Subject Key Identifier: + 1D:60:82:FA:3A:EC:27:91:BA:8D:F5:ED:B2:E3:85:0B:22:5A:8E:38 + X509v3 Authority Key Identifier: + keyid:A9:75:99:CF:9C:92:54:A4:4B:65:CD:3D:FC:93:98:8D:9E:09:1F:47 + DirName:/CN=Root CA + serial:10:01 + + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication + Signature Algorithm: sha256WithRSAEncryption + 89:90:3d:2c:b8:0d:36:63:68:9a:cd:f9:14:56:94:d9:18:11: + b5:08:35:af:f9:34:cd:70:db:7d:66:06:e3:57:9b:06:8f:11: + d6:ea:ac:a6:07:db:ae:a2:c0:66:69:84:d8:2d:3c:cc:d7:4d: + 3c:75:60:4f:98:fc:56:df:30:39:c6:55:2c:73:92:9e:0c:b5: + 7c:75:40:5d:21:aa:01:c1:8a:03:86:eb:d7:02:7d:f5:7b:12: + cc:18:90:23:ad:8f:d7:05:18:6d:f0:11:a8:6b:27:fd:4c:07: + 07:53:f5:7f:f7:a2:e5:18:1e:4e:90:1b:10:5f:f3:5c:cb:c7: + 37:63:d0:d5:1d:3a:65:66:24:ee:0e:ce:7f:b1:fb:ee:17:d0: + b5:4d:64:2f:5a:9c:bc:7a:1c:c0:b4:0f:32:c9:a9:5c:cb:57: + 26:fd:49:39:8d:f2:89:54:c4:92:b5:35:ec:fe:cf:87:07:a6: + 84:01:98:00:e4:2a:44:26:b7:48:00:11:d3:e4:5a:c1:ad:46: + 36:53:f9:28:b7:e4:c5:bb:66:88:ab:8e:cc:30:d0:96:aa:3e: + c1:12:6a:8f:fa:6d:19:15:f4:90:66:54:62:84:97:06:2d:5c: + b9:18:71:90:f4:ca:4c:8c:a5:8b:32:14:93:89:f1:93:f4:00: + bd:1d:42:4f +-----BEGIN CERTIFICATE----- +MIIDgjCCAmqgAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwGjEYMBYGA1UEAwwPSW50 +ZXJtZWRpYXRlIENBMB4XDTE2MDQxODE2MTQyOVoXDTI2MDQxNjE2MTQyOVowFDES +MBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAuByk7Ib6anz0xOFRqyogGsTTEAAGduquIckVFajDHrk7vdq+TflkDp+UhbIS +qu1rj0OuaqODxiPSSw5fHClqNKpjhHS1ry86knS847BGwrByY0oazBdN0UgtWiXO +K+iCuSI9p0KSIORJ3Y70pUZmm12EHRwb0tANv4ogYxjAxwnyqYgnPnrDsoyCh/Cb +4Fu8XucrnepYbInXi6yOdwTuivTx9qy7tlQzvYJ2LLEUIJdBtCUZdZkxky9CJUbf +u5rm6PFvbLII5YCSlpXLxg9bumZCR1z9IXE6rLYcJIp3HIquR2cNtAs9M8OHuR5V +6vhUG51bP3aTkg3asJVdUe10dwIDAQABo4HXMIHUMAkGA1UdEwQCMAAwEQYJYIZI +AYb4QgEBBAQDAgZAMDMGCWCGSAGG+EIBDQQmFiRPcGVuU1NMIEdlbmVyYXRlZCBT +ZXJ2ZXIgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFB1ggvo67CeRuo317bLjhQsiWo44 +MDsGA1UdIwQ0MDKAFKl1mc+cklSkS2XNPfyTmI2eCR9HoRakFDASMRAwDgYDVQQD +DAdSb290IENBggIQATAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUH +AwEwDQYJKoZIhvcNAQELBQADggEBAImQPSy4DTZjaJrN+RRWlNkYEbUINa/5NM1w +231mBuNXmwaPEdbqrKYH266iwGZphNgtPMzXTTx1YE+Y/FbfMDnGVSxzkp4MtXx1 +QF0hqgHBigOG69cCffV7EswYkCOtj9cFGG3wEahrJ/1MBwdT9X/3ouUYHk6QGxBf +81zLxzdj0NUdOmVmJO4Ozn+x++4X0LVNZC9anLx6HMC0DzLJqVzLVyb9STmN8olU +xJK1Nez+z4cHpoQBmADkKkQmt0gAEdPkWsGtRjZT+Si35MW7Zoirjsww0JaqPsES +ao/6bRkV9JBmVGKElwYtXLkYcZD0ykyMpYsyFJOJ8ZP0AL0dQk8= +-----END CERTIFICATE----- diff --git a/spec/fixtures/devtools-extensions/foo/foo.html b/spec/fixtures/devtools-extensions/foo/foo.html new file mode 100644 index 000000000000..a326639c38de --- /dev/null +++ b/spec/fixtures/devtools-extensions/foo/foo.html @@ -0,0 +1,10 @@ + + + + + foo + + + diff --git a/spec/fixtures/devtools-extensions/foo/index.html b/spec/fixtures/devtools-extensions/foo/index.html new file mode 100644 index 000000000000..4e7439422e47 --- /dev/null +++ b/spec/fixtures/devtools-extensions/foo/index.html @@ -0,0 +1,14 @@ + + + + + + + + + a custom devtools extension + + diff --git a/spec/fixtures/devtools-extensions/foo/manifest.json b/spec/fixtures/devtools-extensions/foo/manifest.json index bde99de9287a..413853e8d351 100644 --- a/spec/fixtures/devtools-extensions/foo/manifest.json +++ b/spec/fixtures/devtools-extensions/foo/manifest.json @@ -1,3 +1,5 @@ { - "name": "foo" + "name": "foo", + "version": "1.0", + "devtools_page": "foo.html" } diff --git a/spec/fixtures/module/answer.js b/spec/fixtures/module/answer.js new file mode 100644 index 000000000000..d592d6cda214 --- /dev/null +++ b/spec/fixtures/module/answer.js @@ -0,0 +1,4 @@ +var ipcRenderer = require('electron').ipcRenderer +window.answer = function (answer) { + ipcRenderer.send('answer', answer) +} diff --git a/spec/fixtures/module/asar.js b/spec/fixtures/module/asar.js index 1fb8750878a3..e01b64a2eca8 100644 --- a/spec/fixtures/module/asar.js +++ b/spec/fixtures/module/asar.js @@ -1,4 +1,4 @@ -var fs = require('fs'); -process.on('message', function(file) { - process.send(fs.readFileSync(file).toString()); -}); +var fs = require('fs') +process.on('message', function (file) { + process.send(fs.readFileSync(file).toString()) +}) diff --git a/spec/fixtures/module/call.js b/spec/fixtures/module/call.js index ce0eb6324df4..d09d677199b1 100644 --- a/spec/fixtures/module/call.js +++ b/spec/fixtures/module/call.js @@ -1,7 +1,7 @@ -exports.call = function(func) { - return func(); -}; +exports.call = function (func) { + return func() +} -exports.constructor = function() { - this.test = 'test'; -}; +exports.constructor = function () { + this.test = 'test' +} diff --git a/spec/fixtures/module/class.js b/spec/fixtures/module/class.js index f25eb2593ff3..9b971e52335b 100644 --- a/spec/fixtures/module/class.js +++ b/spec/fixtures/module/class.js @@ -1,22 +1,22 @@ -'use strict'; +'use strict' -let value = 'old'; +let value = 'old' class BaseClass { - method() { - return 'method'; + method () { + return 'method' } - get readonly() { - return 'readonly'; + get readonly () { + return 'readonly' } - get value() { - return value; + get value () { + return value } - set value(val) { - value = val; + set value (val) { + value = val } } @@ -24,6 +24,6 @@ class DerivedClass extends BaseClass { } module.exports = { - base: new BaseClass, - derived: new DerivedClass, + base: new BaseClass(), + derived: new DerivedClass() } diff --git a/spec/fixtures/module/create_socket.js b/spec/fixtures/module/create_socket.js index 2a8b475c5192..2528b993d655 100644 --- a/spec/fixtures/module/create_socket.js +++ b/spec/fixtures/module/create_socket.js @@ -1,4 +1,4 @@ -var net = require('net'); -var server = net.createServer(function() {}); -server.listen(process.argv[2]); -process.exit(0); +var net = require('net') +var server = net.createServer(function () {}) +server.listen(process.argv[2]) +process.exit(0) diff --git a/spec/fixtures/module/fork_ping.js b/spec/fixtures/module/fork_ping.js index a43f7d8dce7d..aa515333400a 100644 --- a/spec/fixtures/module/fork_ping.js +++ b/spec/fixtures/module/fork_ping.js @@ -1,14 +1,16 @@ -process.on('uncaughtException', function(error) { - process.send(error.stack); -}); +const path = require('path') -var child = require('child_process').fork(__dirname + '/ping.js'); -process.on('message', function(msg) { - child.send(msg); -}); +process.on('uncaughtException', function (error) { + process.send(error.stack) +}) + +var child = require('child_process').fork(path.join(__dirname, '/ping.js')) +process.on('message', function (msg) { + child.send(msg) +}) child.on('message', function (msg) { - process.send(msg); -}); -child.on('exit', function(code) { - process.exit(code); -}); + process.send(msg) +}) +child.on('exit', function (code) { + process.exit(code) +}) diff --git a/spec/fixtures/module/function.js b/spec/fixtures/module/function.js new file mode 100644 index 000000000000..8a2bb6c421ec --- /dev/null +++ b/spec/fixtures/module/function.js @@ -0,0 +1 @@ +exports.aFunction = function () { return 1127 } diff --git a/spec/fixtures/module/id.js b/spec/fixtures/module/id.js index 5bfae457fe0b..2faec9d38321 100644 --- a/spec/fixtures/module/id.js +++ b/spec/fixtures/module/id.js @@ -1 +1 @@ -exports.id = 1127; +exports.id = 1127 diff --git a/spec/fixtures/module/locale-compare.js b/spec/fixtures/module/locale-compare.js index 32dfb309926b..402754004434 100644 --- a/spec/fixtures/module/locale-compare.js +++ b/spec/fixtures/module/locale-compare.js @@ -2,6 +2,6 @@ process.on('message', function () { process.send([ 'a'.localeCompare('a'), 'ä'.localeCompare('z', 'de'), - 'ä'.localeCompare('a', 'sv', { sensitivity: 'base' }), - ]); -}); + 'ä'.localeCompare('a', 'sv', { sensitivity: 'base' }) + ]) +}) diff --git a/spec/fixtures/module/no-prototype.js b/spec/fixtures/module/no-prototype.js new file mode 100644 index 000000000000..f298925b80f2 --- /dev/null +++ b/spec/fixtures/module/no-prototype.js @@ -0,0 +1,4 @@ +const foo = Object.create(null) +foo.bar = 'baz' +foo.baz = false +module.exports = {foo: foo, bar: 1234} diff --git a/spec/fixtures/module/original-fs.js b/spec/fixtures/module/original-fs.js index 7a527c633585..341dcb2e0de8 100644 --- a/spec/fixtures/module/original-fs.js +++ b/spec/fixtures/module/original-fs.js @@ -1,3 +1,3 @@ process.on('message', function () { - process.send(typeof require('original-fs')); -}); + process.send(typeof require('original-fs')) +}) diff --git a/spec/fixtures/module/ping.js b/spec/fixtures/module/ping.js index fdb9d1ec4ce7..90b3d1fb20a1 100644 --- a/spec/fixtures/module/ping.js +++ b/spec/fixtures/module/ping.js @@ -1,4 +1,4 @@ -process.on('message', function(msg) { - process.send(msg); - process.exit(0); -}); +process.on('message', function (msg) { + process.send(msg) + process.exit(0) +}) diff --git a/spec/fixtures/module/preload-ipc.js b/spec/fixtures/module/preload-ipc.js index ed95055c1249..3f7e5ea35ce6 100644 --- a/spec/fixtures/module/preload-ipc.js +++ b/spec/fixtures/module/preload-ipc.js @@ -1,4 +1,4 @@ -var ipcRenderer = require('electron').ipcRenderer; -ipcRenderer.on('ping', function(event, message) { - ipcRenderer.sendToHost('pong', message); -}); +var ipcRenderer = require('electron').ipcRenderer +ipcRenderer.on('ping', function (event, message) { + ipcRenderer.sendToHost('pong', message) +}) diff --git a/spec/fixtures/module/preload-node-off.js b/spec/fixtures/module/preload-node-off.js index 9020f4513a10..54fe343a9ca0 100644 --- a/spec/fixtures/module/preload-node-off.js +++ b/spec/fixtures/module/preload-node-off.js @@ -1,7 +1,7 @@ -setImmediate(function() { +setImmediate(function () { try { - console.log([typeof process, typeof setImmediate, typeof global].join(' ')); + console.log([typeof process, typeof setImmediate, typeof global].join(' ')) } catch (e) { - console.log(e.message); + console.log(e.message) } -}); +}) diff --git a/spec/fixtures/module/preload-webview.js b/spec/fixtures/module/preload-webview.js new file mode 100644 index 000000000000..273065cdaacd --- /dev/null +++ b/spec/fixtures/module/preload-webview.js @@ -0,0 +1,5 @@ +const {ipcRenderer} = require('electron') + +window.onload = function () { + ipcRenderer.send('webview', typeof WebView) +} diff --git a/spec/fixtures/module/preload.js b/spec/fixtures/module/preload.js index 205a077ddb10..39c8b11fbe38 100644 --- a/spec/fixtures/module/preload.js +++ b/spec/fixtures/module/preload.js @@ -1 +1 @@ -console.log([typeof require, typeof module, typeof process].join(' ')); +console.log([typeof require, typeof module, typeof process].join(' ')) diff --git a/spec/fixtures/module/print_name.js b/spec/fixtures/module/print_name.js index 96ac2d6f3cb5..db4f71d407b6 100644 --- a/spec/fixtures/module/print_name.js +++ b/spec/fixtures/module/print_name.js @@ -1,7 +1,7 @@ -exports.print = function(obj) { - return obj.constructor.name; -}; +exports.print = function (obj) { + return obj.constructor.name +} -exports.echo = function(obj) { - return obj; -}; +exports.echo = function (obj) { + return obj +} diff --git a/spec/fixtures/module/process-stdout.js b/spec/fixtures/module/process-stdout.js new file mode 100644 index 000000000000..953750a247f9 --- /dev/null +++ b/spec/fixtures/module/process-stdout.js @@ -0,0 +1 @@ +process.stdout.write('pipes stdio') diff --git a/spec/fixtures/module/process_args.js b/spec/fixtures/module/process_args.js index bba351154f6d..56e3906c5534 100644 --- a/spec/fixtures/module/process_args.js +++ b/spec/fixtures/module/process_args.js @@ -1,4 +1,4 @@ -process.on('message', function() { - process.send(process.argv); - process.exit(0); -}); +process.on('message', function () { + process.send(process.argv) + process.exit(0) +}) diff --git a/spec/fixtures/module/promise.js b/spec/fixtures/module/promise.js index b9b568855e30..d34058cc8033 100644 --- a/spec/fixtures/module/promise.js +++ b/spec/fixtures/module/promise.js @@ -1,5 +1,5 @@ exports.twicePromise = function (promise) { return promise.then(function (value) { - return value * 2; - }); -}; + return value * 2 + }) +} diff --git a/spec/fixtures/module/property.js b/spec/fixtures/module/property.js index 36286d800e16..88e596f73083 100644 --- a/spec/fixtures/module/property.js +++ b/spec/fixtures/module/property.js @@ -1 +1 @@ -exports.property = 1127; +exports.property = 1127 diff --git a/spec/fixtures/module/rejected-promise.js b/spec/fixtures/module/rejected-promise.js new file mode 100644 index 000000000000..93dd9accc030 --- /dev/null +++ b/spec/fixtures/module/rejected-promise.js @@ -0,0 +1,5 @@ +exports.reject = function (promise) { + return promise.then(function () { + throw Error('rejected') + }) +} diff --git a/spec/fixtures/module/runas.js b/spec/fixtures/module/runas.js index c4845e026f23..6422fce052a8 100644 --- a/spec/fixtures/module/runas.js +++ b/spec/fixtures/module/runas.js @@ -1,6 +1,6 @@ -process.on('uncaughtException', function(err) { - process.send(err.message); -}); +process.on('uncaughtException', function (err) { + process.send(err.message) +}) -require('runas'); -process.send('ok'); +require('runas') +process.send('ok') diff --git a/spec/fixtures/module/send-later.js b/spec/fixtures/module/send-later.js index 704f47d328df..8eb16f72f8cc 100644 --- a/spec/fixtures/module/send-later.js +++ b/spec/fixtures/module/send-later.js @@ -1,4 +1,4 @@ -var ipcRenderer = require('electron').ipcRenderer; -window.onload = function() { - ipcRenderer.send('answer', typeof window.process); -}; +var ipcRenderer = require('electron').ipcRenderer +window.onload = function () { + ipcRenderer.send('answer', typeof window.process) +} diff --git a/spec/fixtures/module/set-global.js b/spec/fixtures/module/set-global.js index f39919ff9d8f..ba4f30d155b7 100644 --- a/spec/fixtures/module/set-global.js +++ b/spec/fixtures/module/set-global.js @@ -1 +1 @@ -window.test = 'preload'; +window.test = 'preload' diff --git a/spec/fixtures/module/set-immediate.js b/spec/fixtures/module/set-immediate.js index d36355ee23b4..69563fd0a832 100644 --- a/spec/fixtures/module/set-immediate.js +++ b/spec/fixtures/module/set-immediate.js @@ -1,11 +1,11 @@ -process.on('uncaughtException', function(error) { - process.send(error.message); - process.exit(1); -}); +process.on('uncaughtException', function (error) { + process.send(error.message) + process.exit(1) +}) -process.on('message', function() { - setImmediate(function() { - process.send('ok'); - process.exit(0); - }); -}); +process.on('message', function () { + setImmediate(function () { + process.send('ok') + process.exit(0) + }) +}) diff --git a/spec/fixtures/pages/b.html b/spec/fixtures/pages/b.html index 812431f2b45b..d35c863b42c5 100644 --- a/spec/fixtures/pages/b.html +++ b/spec/fixtures/pages/b.html @@ -1,8 +1,8 @@ + - diff --git a/spec/fixtures/pages/did-get-response-details.html b/spec/fixtures/pages/did-get-response-details.html new file mode 100644 index 000000000000..c98458c8ffaa --- /dev/null +++ b/spec/fixtures/pages/did-get-response-details.html @@ -0,0 +1,5 @@ + + + + + diff --git a/spec/fixtures/pages/ping-pong.html b/spec/fixtures/pages/ping-pong.html new file mode 100644 index 000000000000..d10e7898653b --- /dev/null +++ b/spec/fixtures/pages/ping-pong.html @@ -0,0 +1,11 @@ + + + + + + diff --git a/spec/fixtures/pages/ping.html b/spec/fixtures/pages/ping.html new file mode 100644 index 000000000000..5bd6a8772a95 --- /dev/null +++ b/spec/fixtures/pages/ping.html @@ -0,0 +1,7 @@ + + + + + diff --git a/spec/fixtures/pages/service-worker/service-worker.js b/spec/fixtures/pages/service-worker/service-worker.js index 854b3558a95c..7d80f45e2df4 100644 --- a/spec/fixtures/pages/service-worker/service-worker.js +++ b/spec/fixtures/pages/service-worker/service-worker.js @@ -1,9 +1,9 @@ -self.addEventListener('fetch', function(event) { - var requestUrl = new URL(event.request.url); +self.addEventListener('fetch', function (event) { + var requestUrl = new URL(event.request.url) if (requestUrl.pathname === '/echo' && - event.request.headers.has('X-Mock-Response')) { - var mockResponse = new Response('Hello from serviceWorker!'); - event.respondWith(mockResponse); + event.request.headers.has('X-Mock-Response')) { + var mockResponse = new Response('Hello from serviceWorker!') + event.respondWith(mockResponse) } -}); +}) diff --git a/spec/fixtures/pages/visibilitychange.html b/spec/fixtures/pages/visibilitychange.html new file mode 100644 index 000000000000..9f49f520de1f --- /dev/null +++ b/spec/fixtures/pages/visibilitychange.html @@ -0,0 +1,11 @@ + + + + + diff --git a/spec/fixtures/pages/web-view-log-process.html b/spec/fixtures/pages/web-view-log-process.html new file mode 100644 index 000000000000..9e75edb393e2 --- /dev/null +++ b/spec/fixtures/pages/web-view-log-process.html @@ -0,0 +1,13 @@ + + + + + test + + + + test? + + diff --git a/spec/fixtures/pages/webview-no-node-integration-on-window.html b/spec/fixtures/pages/webview-no-node-integration-on-window.html new file mode 100644 index 000000000000..0af03cf1c497 --- /dev/null +++ b/spec/fixtures/pages/webview-no-node-integration-on-window.html @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/spec/fixtures/pages/webview-no-script.html b/spec/fixtures/pages/webview-no-script.html new file mode 100644 index 000000000000..00b8f21bde70 --- /dev/null +++ b/spec/fixtures/pages/webview-no-script.html @@ -0,0 +1,5 @@ + + + + + diff --git a/spec/fixtures/pages/webview-opener-no-node-integration.html b/spec/fixtures/pages/webview-opener-no-node-integration.html new file mode 100644 index 000000000000..1275a99b0bfd --- /dev/null +++ b/spec/fixtures/pages/webview-opener-no-node-integration.html @@ -0,0 +1,8 @@ + + + + + diff --git a/spec/fixtures/pages/webview-visibilitychange.html b/spec/fixtures/pages/webview-visibilitychange.html new file mode 100644 index 000000000000..7c8b7cef9df5 --- /dev/null +++ b/spec/fixtures/pages/webview-visibilitychange.html @@ -0,0 +1,5 @@ + + + + + diff --git a/spec/fixtures/pages/webview-zoom-factor.html b/spec/fixtures/pages/webview-zoom-factor.html new file mode 100644 index 000000000000..006b416cfef5 --- /dev/null +++ b/spec/fixtures/pages/webview-zoom-factor.html @@ -0,0 +1,5 @@ + + + + + diff --git a/spec/fixtures/pages/window-opener-no-node-integration.html b/spec/fixtures/pages/window-opener-no-node-integration.html new file mode 100644 index 000000000000..b587e9792270 --- /dev/null +++ b/spec/fixtures/pages/window-opener-no-node-integration.html @@ -0,0 +1,15 @@ + + + + + diff --git a/spec/fixtures/pages/window-opener-node.html b/spec/fixtures/pages/window-opener-node.html index 118603c82d3b..130360728767 100644 --- a/spec/fixtures/pages/window-opener-node.html +++ b/spec/fixtures/pages/window-opener-node.html @@ -1,7 +1,7 @@ diff --git a/spec/fixtures/pages/zoom-factor.html b/spec/fixtures/pages/zoom-factor.html new file mode 100644 index 000000000000..b9f8f988caea --- /dev/null +++ b/spec/fixtures/pages/zoom-factor.html @@ -0,0 +1,8 @@ + + + + + diff --git a/spec/fixtures/workers/shared_worker.js b/spec/fixtures/workers/shared_worker.js index 402207939793..35cb40d03b54 100644 --- a/spec/fixtures/workers/shared_worker.js +++ b/spec/fixtures/workers/shared_worker.js @@ -1,7 +1,7 @@ -this.onconnect = function(event) { - var port = event.ports[0]; - port.start(); - port.onmessage = function(event) { - port.postMessage(event.data); - }; -}; +this.onconnect = function (event) { + var port = event.ports[0] + port.start() + port.onmessage = function (event) { + port.postMessage(event.data) + } +} diff --git a/spec/fixtures/workers/worker.js b/spec/fixtures/workers/worker.js index 4f445470b9ad..884c17ac86ea 100644 --- a/spec/fixtures/workers/worker.js +++ b/spec/fixtures/workers/worker.js @@ -1,3 +1,3 @@ -this.onmessage = function(msg) { - this.postMessage(msg.data); -}; +this.onmessage = function (msg) { + this.postMessage(msg.data) +} diff --git a/spec/modules-spec.js b/spec/modules-spec.js index fb53a90cf735..fbc66a082512 100644 --- a/spec/modules-spec.js +++ b/spec/modules-spec.js @@ -1,47 +1,49 @@ -const assert = require('assert'); -const path = require('path'); -const temp = require('temp'); +const assert = require('assert') +const path = require('path') +const temp = require('temp') -describe('third-party module', function() { - var fixtures = path.join(__dirname, 'fixtures'); - temp.track(); +describe('third-party module', function () { + var fixtures = path.join(__dirname, 'fixtures') + temp.track() if (process.platform !== 'win32' || process.execPath.toLowerCase().indexOf('\\out\\d\\') === -1) { - describe('runas', function() { - it('can be required in renderer', function() { - require('runas'); - }); + describe('runas', function () { + it('can be required in renderer', function () { + require('runas') + }) - it('can be required in node binary', function(done) { - var runas = path.join(fixtures, 'module', 'runas.js'); - var child = require('child_process').fork(runas); - child.on('message', function(msg) { - assert.equal(msg, 'ok'); - done(); - }); - }); - }); + it('can be required in node binary', function (done) { + var runas = path.join(fixtures, 'module', 'runas.js') + var child = require('child_process').fork(runas) + child.on('message', function (msg) { + assert.equal(msg, 'ok') + done() + }) + }) + }) - describe('ffi', function() { - it('does not crash', function() { - var ffi = require('ffi'); + describe('ffi', function () { + if (process.platform === 'win32') return + + it('does not crash', function () { + var ffi = require('ffi') var libm = ffi.Library('libm', { ceil: ['double', ['double']] - }); - assert.equal(libm.ceil(1.5), 2); - }); - }); + }) + assert.equal(libm.ceil(1.5), 2) + }) + }) } - describe('q', function() { - var Q = require('q'); - describe('Q.when', function() { - it('emits the fullfil callback', function(done) { - Q(true).then(function(val) { - assert.equal(val, true); - done(); - }); - }); - }); - }); -}); + describe('q', function () { + var Q = require('q') + describe('Q.when', function () { + it('emits the fullfil callback', function (done) { + Q(true).then(function (val) { + assert.equal(val, true) + done() + }) + }) + }) + }) +}) diff --git a/spec/node-spec.js b/spec/node-spec.js index 83f685d95f04..93a247ef4ff6 100644 --- a/spec/node-spec.js +++ b/spec/node-spec.js @@ -1,220 +1,244 @@ -const assert = require('assert'); -const child_process = require('child_process'); -const fs = require('fs'); -const path = require('path'); -const os = require('os'); -const remote = require('electron').remote; +const assert = require('assert') +const child_process = require('child_process') +const fs = require('fs') +const path = require('path') +const os = require('os') +const {remote} = require('electron') -describe('node feature', function() { - var fixtures = path.join(__dirname, 'fixtures'); +describe('node feature', function () { + var fixtures = path.join(__dirname, 'fixtures') - describe('child_process', function() { - describe('child_process.fork', function() { - it('works in current process', function(done) { - var child = child_process.fork(path.join(fixtures, 'module', 'ping.js')); - child.on('message', function(msg) { - assert.equal(msg, 'message'); - done(); - }); - child.send('message'); - }); + describe('child_process', function () { + describe('child_process.fork', function () { + it('works in current process', function (done) { + var child = child_process.fork(path.join(fixtures, 'module', 'ping.js')) + child.on('message', function (msg) { + assert.equal(msg, 'message') + done() + }) + child.send('message') + }) - it('preserves args', function(done) { - var args = ['--expose_gc', '-test', '1']; - var child = child_process.fork(path.join(fixtures, 'module', 'process_args.js'), args); - child.on('message', function(msg) { - assert.deepEqual(args, msg.slice(2)); - done(); - }); - child.send('message'); - }); + it('preserves args', function (done) { + var args = ['--expose_gc', '-test', '1'] + var child = child_process.fork(path.join(fixtures, 'module', 'process_args.js'), args) + child.on('message', function (msg) { + assert.deepEqual(args, msg.slice(2)) + done() + }) + child.send('message') + }) - it('works in forked process', function(done) { - var child = child_process.fork(path.join(fixtures, 'module', 'fork_ping.js')); - child.on('message', function(msg) { - assert.equal(msg, 'message'); - done(); - }); - child.send('message'); - }); + it('works in forked process', function (done) { + var child = child_process.fork(path.join(fixtures, 'module', 'fork_ping.js')) + child.on('message', function (msg) { + assert.equal(msg, 'message') + done() + }) + child.send('message') + }) - it('works in forked process when options.env is specifed', function(done) { + it('works in forked process when options.env is specifed', function (done) { var child = child_process.fork(path.join(fixtures, 'module', 'fork_ping.js'), [], { path: process.env['PATH'] - }); - child.on('message', function(msg) { - assert.equal(msg, 'message'); - done(); - }); - child.send('message'); - }); + }) + child.on('message', function (msg) { + assert.equal(msg, 'message') + done() + }) + child.send('message') + }) - it('works in browser process', function(done) { - var fork = remote.require('child_process').fork; - var child = fork(path.join(fixtures, 'module', 'ping.js')); - child.on('message', function(msg) { - assert.equal(msg, 'message'); - done(); - }); - child.send('message'); - }); + it('works in browser process', function (done) { + var fork = remote.require('child_process').fork + var child = fork(path.join(fixtures, 'module', 'ping.js')) + child.on('message', function (msg) { + assert.equal(msg, 'message') + done() + }) + child.send('message') + }) - it('has String::localeCompare working in script', function(done) { - var child = child_process.fork(path.join(fixtures, 'module', 'locale-compare.js')); - child.on('message', function(msg) { - assert.deepEqual(msg, [0, -1, 1]); - done(); - }); - child.send('message'); - }); + it('has String::localeCompare working in script', function (done) { + var child = child_process.fork(path.join(fixtures, 'module', 'locale-compare.js')) + child.on('message', function (msg) { + assert.deepEqual(msg, [0, -1, 1]) + done() + }) + child.send('message') + }) - it('has setImmediate working in script', function(done) { - var child = child_process.fork(path.join(fixtures, 'module', 'set-immediate.js')); - child.on('message', function(msg) { - assert.equal(msg, 'ok'); - done(); - }); - child.send('message'); - }); - }); - }); + it('has setImmediate working in script', function (done) { + var child = child_process.fork(path.join(fixtures, 'module', 'set-immediate.js')) + child.on('message', function (msg) { + assert.equal(msg, 'ok') + done() + }) + child.send('message') + }) - describe('contexts', function() { - describe('setTimeout in fs callback', function() { + it('pipes stdio', function (done) { + let child = child_process.fork(path.join(fixtures, 'module', 'process-stdout.js'), {silent: true}) + let data = '' + child.stdout.on('data', (chunk) => { + data += String(chunk) + }) + child.on('exit', (code) => { + assert.equal(code, 0) + assert.equal(data, 'pipes stdio') + done() + }) + }) + }) + }) + + describe('contexts', function () { + describe('setTimeout in fs callback', function () { if (process.env.TRAVIS === 'true') { - return; + return } - it('does not crash', function(done) { - fs.readFile(__filename, function() { - setTimeout(done, 0); - }); - }); - }); + it('does not crash', function (done) { + fs.readFile(__filename, function () { + setTimeout(done, 0) + }) + }) + }) - describe('throw error in node context', function() { - it('gets caught', function(done) { - var error = new Error('boo!'); - var lsts = process.listeners('uncaughtException'); - process.removeAllListeners('uncaughtException'); - process.on('uncaughtException', function() { - var i, len, lst; - process.removeAllListeners('uncaughtException'); + describe('throw error in node context', function () { + it('gets caught', function (done) { + var error = new Error('boo!') + var lsts = process.listeners('uncaughtException') + process.removeAllListeners('uncaughtException') + process.on('uncaughtException', function () { + var i, len, lst + process.removeAllListeners('uncaughtException') for (i = 0, len = lsts.length; i < len; i++) { - lst = lsts[i]; - process.on('uncaughtException', lst); + lst = lsts[i] + process.on('uncaughtException', lst) } - done(); - }); - fs.readFile(__filename, function() { - throw error; - }); - }); - }); + done() + }) + fs.readFile(__filename, function () { + throw error + }) + }) + }) - describe('setTimeout called under Chromium event loop in browser process', function() { - it('can be scheduled in time', function(done) { - remote.getGlobal('setTimeout')(done, 0); - }); - }); + describe('setTimeout called under Chromium event loop in browser process', function () { + it('can be scheduled in time', function (done) { + remote.getGlobal('setTimeout')(done, 0) + }) + }) - describe('setInterval called under Chromium event loop in browser process', function() { - it('can be scheduled in time', function(done) { - var clear, interval; - clear = function() { - remote.getGlobal('clearInterval')(interval); - done(); - }; - interval = remote.getGlobal('setInterval')(clear, 10); - }); - }); - }); + describe('setInterval called under Chromium event loop in browser process', function () { + it('can be scheduled in time', function (done) { + var clear, interval + clear = function () { + remote.getGlobal('clearInterval')(interval) + done() + } + interval = remote.getGlobal('setInterval')(clear, 10) + }) + }) + }) - describe('message loop', function() { - describe('process.nextTick', function() { - it('emits the callback', function(done) { - process.nextTick(done); - }); + describe('message loop', function () { + describe('process.nextTick', function () { + it('emits the callback', function (done) { + process.nextTick(done) + }) - it('works in nested calls', function(done) { - process.nextTick(function() { - process.nextTick(function() { - process.nextTick(done); - }); - }); - }); - }); + it('works in nested calls', function (done) { + process.nextTick(function () { + process.nextTick(function () { + process.nextTick(done) + }) + }) + }) + }) - describe('setImmediate', function() { - it('emits the callback', function(done) { - setImmediate(done); - }); + describe('setImmediate', function () { + it('emits the callback', function (done) { + setImmediate(done) + }) - it('works in nested calls', function(done) { - setImmediate(function() { - setImmediate(function() { - setImmediate(done); - }); - }); - }); - }); - }); + it('works in nested calls', function (done) { + setImmediate(function () { + setImmediate(function () { + setImmediate(done) + }) + }) + }) + }) + }) - describe('net.connect', function() { + describe('net.connect', function () { if (process.platform !== 'darwin') { - return; + return } - it('emit error when connect to a socket path without listeners', function(done) { - var socketPath = path.join(os.tmpdir(), 'atom-shell-test.sock'); - var script = path.join(fixtures, 'module', 'create_socket.js'); - var child = child_process.fork(script, [socketPath]); - child.on('exit', function(code) { - assert.equal(code, 0); - var client = require('net').connect(socketPath); - client.on('error', function(error) { - assert.equal(error.code, 'ECONNREFUSED'); - done(); - }); - }); - }); - }); + it('emit error when connect to a socket path without listeners', function (done) { + var socketPath = path.join(os.tmpdir(), 'atom-shell-test.sock') + var script = path.join(fixtures, 'module', 'create_socket.js') + var child = child_process.fork(script, [socketPath]) + child.on('exit', function (code) { + assert.equal(code, 0) + var client = require('net').connect(socketPath) + client.on('error', function (error) { + assert.equal(error.code, 'ECONNREFUSED') + done() + }) + }) + }) + }) - describe('Buffer', function() { - it('can be created from WebKit external string', function() { - var p = document.createElement('p'); - p.innerText = '闲云潭影日悠悠,物换星移几度秋'; - var b = new Buffer(p.innerText); - assert.equal(b.toString(), '闲云潭影日悠悠,物换星移几度秋'); - assert.equal(Buffer.byteLength(p.innerText), 45); - }); + describe('Buffer', function () { + it('can be created from WebKit external string', function () { + var p = document.createElement('p') + p.innerText = '闲云潭影日悠悠,物换星移几度秋' + var b = new Buffer(p.innerText) + assert.equal(b.toString(), '闲云潭影日悠悠,物换星移几度秋') + assert.equal(Buffer.byteLength(p.innerText), 45) + }) - it('correctly parses external one-byte UTF8 string', function() { - var p = document.createElement('p'); - p.innerText = 'Jøhänñéß'; - var b = new Buffer(p.innerText); - assert.equal(b.toString(), 'Jøhänñéß'); - assert.equal(Buffer.byteLength(p.innerText), 13); - }); - }); + it('correctly parses external one-byte UTF8 string', function () { + var p = document.createElement('p') + p.innerText = 'Jøhänñéß' + var b = new Buffer(p.innerText) + assert.equal(b.toString(), 'Jøhänñéß') + assert.equal(Buffer.byteLength(p.innerText), 13) + }) - describe('process.stdout', function() { - it('should not throw exception', function() { - process.stdout; - }); + it('does not crash when creating large Buffers', function () { + new Buffer(new Array(4096).join(' ')); + new Buffer(new Array(4097).join(' ')); + }) + }) - it('should not throw exception when calling write()', function() { - process.stdout.write('test'); - }); + describe('process.stdout', function () { + it('should not throw exception', function () { + process.stdout + }) - xit('should have isTTY defined', function() { - assert.equal(typeof process.stdout.isTTY, 'boolean'); - }); - }); + it('should not throw exception when calling write()', function () { + process.stdout.write('test') + }) - describe('vm.createContext', function() { - it('should not crash', function() { - require('vm').runInNewContext(''); - }); - }); -}); + xit('should have isTTY defined', function () { + assert.equal(typeof process.stdout.isTTY, 'boolean') + }) + }) + + describe('process.version', function () { + it('should not have -pre', function () { + assert(!process.version.endsWith('-pre')) + }) + }) + + describe('vm.createContext', function () { + it('should not crash', function () { + require('vm').runInNewContext('') + }) + }) +}) diff --git a/spec/package.json b/spec/package.json index 6d49f0da8b8a..5abf89f57e74 100644 --- a/spec/package.json +++ b/spec/package.json @@ -13,10 +13,20 @@ "temp": "0.8.1", "walkdir": "0.0.7", "ws": "0.7.2", - "yargs": "^3.31.0" + "yargs": "^4.7.1" }, "optionalDependencies": { "ffi": "2.0.0", "runas": "3.x" + }, + "standard": { + "env": { + "mocha": true, + "jquery": true, + "serviceworker": true + }, + "globals": [ + "WebView" + ] } } diff --git a/spec/static/main.js b/spec/static/main.js index 48fdf17c3dcf..33c3bb48136a 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -1,141 +1,163 @@ // Disable use of deprecated functions. -process.throwDeprecation = true; +process.throwDeprecation = true -const electron = require('electron'); -const app = electron.app; -const ipcMain = electron.ipcMain; -const dialog = electron.dialog; -const BrowserWindow = electron.BrowserWindow; +const electron = require('electron') +const app = electron.app +const ipcMain = electron.ipcMain +const dialog = electron.dialog +const BrowserWindow = electron.BrowserWindow +const protocol = electron.protocol -const path = require('path'); -const url = require('url'); +const fs = require('fs') +const path = require('path') +const url = require('url') +const util = require('util') var argv = require('yargs') .boolean('ci') .string('g').alias('g', 'grep') .boolean('i').alias('i', 'invert') - .argv; + .argv -var window = null; -process.port = 0; // will be used by crash-reporter spec. +var window = null +process.port = 0 // will be used by crash-reporter spec. -app.commandLine.appendSwitch('js-flags', '--expose_gc'); -app.commandLine.appendSwitch('ignore-certificate-errors'); -app.commandLine.appendSwitch('disable-renderer-backgrounding'); +app.commandLine.appendSwitch('js-flags', '--expose_gc') +app.commandLine.appendSwitch('ignore-certificate-errors') +app.commandLine.appendSwitch('disable-renderer-backgrounding') // Accessing stdout in the main process will result in the process.stdout // throwing UnknownSystemError in renderer process sometimes. This line makes // sure we can reproduce it in renderer process. -process.stdout; +process.stdout // Access console to reproduce #3482. -console; +console -ipcMain.on('message', function(event, arg) { - event.sender.send('message', arg); -}); +ipcMain.on('message', function (event, arg) { + event.sender.send('message', arg) +}) -ipcMain.on('console.log', function(event, args) { - console.error.apply(console, args); -}); +// Write output to file if OUTPUT_TO_FILE is defined. +const outputToFile = process.env.OUTPUT_TO_FILE +const print = function (_, args) { + let output = util.format.apply(null, args) + if (outputToFile) { + fs.appendFileSync(outputToFile, output + '\n') + } else { + console.error(output) + } +} +ipcMain.on('console.log', print) +ipcMain.on('console.error', print) -ipcMain.on('console.error', function(event, args) { - console.error.apply(console, args); -}); +ipcMain.on('process.exit', function (event, code) { + process.exit(code) +}) -ipcMain.on('process.exit', function(event, code) { - process.exit(code); -}); +ipcMain.on('eval', function (event, script) { + event.returnValue = eval(script) // eslint-disable-line +}) -ipcMain.on('eval', function(event, script) { - event.returnValue = eval(script); -}); +ipcMain.on('echo', function (event, msg) { + event.returnValue = msg +}) -ipcMain.on('echo', function(event, msg) { - event.returnValue = msg; -}); - -global.isCi = !!argv.ci; +global.isCi = !!argv.ci if (global.isCi) { - process.removeAllListeners('uncaughtException'); - process.on('uncaughtException', function(error) { - console.error(error, error.stack); - process.exit(1); - }); + process.removeAllListeners('uncaughtException') + process.on('uncaughtException', function (error) { + console.error(error, error.stack) + process.exit(1) + }) } -app.on('window-all-closed', function() { - app.quit(); -}); +// Register app as standard scheme. +global.standardScheme = 'app' +protocol.registerStandardSchemes([global.standardScheme]) -app.on('ready', function() { +app.on('window-all-closed', function () { + app.quit() +}) + +app.on('ready', function () { // Test if using protocol module would crash. - electron.protocol.registerStringProtocol('test-if-crashes', function() {}); + electron.protocol.registerStringProtocol('test-if-crashes', function () {}) // Send auto updater errors to window to be verified in specs electron.autoUpdater.on('error', function (error) { - window.send('auto-updater-error', error.message); - }); + window.send('auto-updater-error', error.message) + }) window = new BrowserWindow({ title: 'Electron Tests', show: false, width: 800, height: 600, - 'web-preferences': { - javascript: true // Test whether web-preferences crashes. - }, - }); + webPreferences: { + backgroundThrottling: false, + } + }) window.loadURL(url.format({ - pathname: __dirname + '/index.html', + pathname: path.join(__dirname, '/index.html'), protocol: 'file', query: { grep: argv.grep, - invert: argv.invert ? 'true': '' + invert: argv.invert ? 'true' : '' } - })); - window.on('unresponsive', function() { + })) + window.on('unresponsive', function () { var chosen = dialog.showMessageBox(window, { type: 'warning', buttons: ['Close', 'Keep Waiting'], message: 'Window is not responsing', detail: 'The window is not responding. Would you like to force close it or just keep waiting?' - }); - if (chosen === 0) window.destroy(); - }); + }) + if (chosen === 0) window.destroy() + }) // For session's download test, listen 'will-download' event in browser, and // reply the result to renderer for verifying - var downloadFilePath = path.join(__dirname, '..', 'fixtures', 'mock.pdf'); - ipcMain.on('set-download-option', function(event, need_cancel, prevent_default) { - window.webContents.session.once('will-download', function(e, item) { + var downloadFilePath = path.join(__dirname, '..', 'fixtures', 'mock.pdf') + ipcMain.on('set-download-option', function (event, need_cancel, prevent_default) { + window.webContents.session.once('will-download', function (e, item) { if (prevent_default) { - e.preventDefault(); - const url = item.getURL(); - const filename = item.getFilename(); - setImmediate(function() { + e.preventDefault() + const url = item.getURL() + const filename = item.getFilename() + setImmediate(function () { try { - item.getURL(); - } catch(err) { - window.webContents.send('download-error', url, filename, err.message); + item.getURL() + } catch (err) { + window.webContents.send('download-error', url, filename, err.message) } - }); + }) } else { - item.setSavePath(downloadFilePath); - item.on('done', function(e, state) { + item.setSavePath(downloadFilePath) + item.on('done', function (e, state) { window.webContents.send('download-done', - state, - item.getURL(), - item.getMimeType(), - item.getReceivedBytes(), - item.getTotalBytes(), - item.getContentDisposition(), - item.getFilename()); - }); - if (need_cancel) - item.cancel(); + state, + item.getURL(), + item.getMimeType(), + item.getReceivedBytes(), + item.getTotalBytes(), + item.getContentDisposition(), + item.getFilename()) + }) + if (need_cancel) item.cancel() } - }); - event.returnValue = "done"; - }); -}); + }) + event.returnValue = 'done' + }) + + ipcMain.on('executeJavaScript', function (event, code, hasCallback) { + if (hasCallback) { + window.webContents.executeJavaScript(code, (result) => { + window.webContents.send('executeJavaScript-response', result) + }) + } else { + window.webContents.executeJavaScript(code) + event.returnValue = 'success' + } + }) +}) diff --git a/spec/webview-spec.js b/spec/webview-spec.js index d73a177d0970..b1fa4405ada8 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -1,719 +1,915 @@ -const assert = require('assert'); -const path = require('path'); -const http = require('http'); -const url = require('url'); +const assert = require('assert') +const path = require('path') +const http = require('http') +const url = require('url') +const {app, session, ipcMain, BrowserWindow} = require('electron').remote -describe(' tag', function() { - this.timeout(10000); +describe(' tag', function () { + this.timeout(20000) - var fixtures = path.join(__dirname, 'fixtures'); - var webview = null; + var fixtures = path.join(__dirname, 'fixtures') - beforeEach(function() { - webview = new WebView; - }); + var webview = null + let w = null - afterEach(function() { + beforeEach(function () { + webview = new WebView() + }) + + afterEach(function () { if (document.body.contains(webview)) { - document.body.removeChild(webview); + document.body.removeChild(webview) } - }); + if (w) { + w.destroy() + w = null + } + }) - describe('src attribute', function() { - it('specifies the page to load', function(done) { - webview.addEventListener('console-message', function(e) { - assert.equal(e.message, 'a'); - done(); - }); - webview.src = "file://" + fixtures + "/pages/a.html"; - document.body.appendChild(webview); - }); + it('works without script tag in page', function (done) { + w = new BrowserWindow({show: false}) + ipcMain.once('pong', function () { + done() + }) + w.loadURL('file://' + fixtures + '/pages/webview-no-script.html') + }) - it('navigates to new page when changed', function(done) { - var listener = function() { - webview.src = "file://" + fixtures + "/pages/b.html"; - webview.addEventListener('console-message', function(e) { - assert.equal(e.message, 'b'); - done(); - }); - webview.removeEventListener('did-finish-load', listener); - }; - webview.addEventListener('did-finish-load', listener); - webview.src = "file://" + fixtures + "/pages/a.html"; - document.body.appendChild(webview); - }); - }); + it('is disabled when nodeIntegration is disabled', function (done) { + w = new BrowserWindow({ + show: false, + webPreferences: { + nodeIntegration: false, + preload: path.join(fixtures, 'module', 'preload-webview.js') + }, + }) + ipcMain.once('webview', function (event, type) { + if (type === 'undefined') { + done() + } else { + done('WebView still exists') + } + }) + w.loadURL('file://' + fixtures + '/pages/webview-no-script.html') + }) - describe('nodeintegration attribute', function() { - it('inserts no node symbols when not set', function(done) { - webview.addEventListener('console-message', function(e) { - assert.equal(e.message, 'undefined undefined undefined undefined'); - done(); - }); - webview.src = "file://" + fixtures + "/pages/c.html"; - document.body.appendChild(webview); - }); + describe('src attribute', function () { + it('specifies the page to load', function (done) { + webview.addEventListener('console-message', function (e) { + assert.equal(e.message, 'a') + done() + }) + webview.src = 'file://' + fixtures + '/pages/a.html' + document.body.appendChild(webview) + }) - it('inserts node symbols when set', function(done) { - webview.addEventListener('console-message', function(e) { - assert.equal(e.message, 'function object object'); - done(); - }); - webview.setAttribute('nodeintegration', 'on'); - webview.src = "file://" + fixtures + "/pages/d.html"; - document.body.appendChild(webview); - }); + it('navigates to new page when changed', function (done) { + var listener = function () { + webview.src = 'file://' + fixtures + '/pages/b.html' + webview.addEventListener('console-message', function (e) { + assert.equal(e.message, 'b') + done() + }) + webview.removeEventListener('did-finish-load', listener) + } + webview.addEventListener('did-finish-load', listener) + webview.src = 'file://' + fixtures + '/pages/a.html' + document.body.appendChild(webview) + }) + }) - it('loads node symbols after POST navigation when set', function(done) { - webview.addEventListener('console-message', function(e) { - assert.equal(e.message, 'function object object'); - done(); - }); - webview.setAttribute('nodeintegration', 'on'); - webview.src = "file://" + fixtures + "/pages/post.html"; - document.body.appendChild(webview); - }); + describe('nodeintegration attribute', function () { + it('inserts no node symbols when not set', function (done) { + webview.addEventListener('console-message', function (e) { + assert.equal(e.message, 'undefined undefined undefined undefined') + done() + }) + webview.src = 'file://' + fixtures + '/pages/c.html' + document.body.appendChild(webview) + }) + + it('inserts node symbols when set', function (done) { + webview.addEventListener('console-message', function (e) { + assert.equal(e.message, 'function object object') + done() + }) + webview.setAttribute('nodeintegration', 'on') + webview.src = 'file://' + fixtures + '/pages/d.html' + document.body.appendChild(webview) + }) + + it('loads node symbols after POST navigation when set', function (done) { + webview.addEventListener('console-message', function (e) { + assert.equal(e.message, 'function object object') + done() + }) + webview.setAttribute('nodeintegration', 'on') + webview.src = 'file://' + fixtures + '/pages/post.html' + document.body.appendChild(webview) + }) + + it('disables node integration on child windows when it is disabled on the webview', function (done) { + app.once('browser-window-created', function (event, window) { + assert.equal(window.webContents.getWebPreferences().nodeIntegration, false) + done() + }) + + webview.setAttribute('allowpopups', 'on') + + webview.src = url.format({ + pathname: `${fixtures}/pages/webview-opener-no-node-integration.html`, + protocol: 'file', + query: { + p: `${fixtures}/pages/window-opener-node.html` + }, + slashes: true + }) + document.body.appendChild(webview) + }) if (process.platform !== 'win32' || process.execPath.toLowerCase().indexOf('\\out\\d\\') === -1) { - it('loads native modules when navigation happens', function(done) { - var listener = function() { - webview.removeEventListener('did-finish-load', listener); - var listener2 = function(e) { - assert.equal(e.message, 'function'); - done(); - }; - webview.addEventListener('console-message', listener2); - webview.reload(); - }; - webview.addEventListener('did-finish-load', listener); - webview.setAttribute('nodeintegration', 'on'); - webview.src = "file://" + fixtures + "/pages/native-module.html"; - document.body.appendChild(webview); - }); + it('loads native modules when navigation happens', function (done) { + var listener = function () { + webview.removeEventListener('did-finish-load', listener) + var listener2 = function (e) { + assert.equal(e.message, 'function') + done() + } + webview.addEventListener('console-message', listener2) + webview.reload() + } + webview.addEventListener('did-finish-load', listener) + webview.setAttribute('nodeintegration', 'on') + webview.src = 'file://' + fixtures + '/pages/native-module.html' + document.body.appendChild(webview) + }) } - }); + }) - describe('preload attribute', function() { - it('loads the script before other scripts in window', function(done) { - var listener = function(e) { - assert.equal(e.message, 'function object object'); - webview.removeEventListener('console-message', listener); - done(); - }; - webview.addEventListener('console-message', listener); - webview.setAttribute('preload', fixtures + "/module/preload.js"); - webview.src = "file://" + fixtures + "/pages/e.html"; - document.body.appendChild(webview); - }); + describe('preload attribute', function () { + it('loads the script before other scripts in window', function (done) { + var listener = function (e) { + assert.equal(e.message, 'function object object') + webview.removeEventListener('console-message', listener) + done() + } + webview.addEventListener('console-message', listener) + webview.setAttribute('preload', fixtures + '/module/preload.js') + webview.src = 'file://' + fixtures + '/pages/e.html' + document.body.appendChild(webview) + }) - it('preload script can still use "process" in required modules when nodeintegration is off', function(done) { - webview.addEventListener('console-message', function(e) { - assert.equal(e.message, 'object undefined object'); - done(); - }); - webview.setAttribute('preload', fixtures + "/module/preload-node-off.js"); - webview.src = "file://" + fixtures + "/api/blank.html"; - document.body.appendChild(webview); - }); + it('preload script can still use "process" in required modules when nodeintegration is off', function (done) { + webview.addEventListener('console-message', function (e) { + assert.equal(e.message, 'object undefined object') + done() + }) + webview.setAttribute('preload', fixtures + '/module/preload-node-off.js') + webview.src = 'file://' + fixtures + '/api/blank.html' + document.body.appendChild(webview) + }) - it('receives ipc message in preload script', function(done) { - var message = 'boom!'; - var listener = function(e) { - assert.equal(e.channel, 'pong'); - assert.deepEqual(e.args, [message]); - webview.removeEventListener('ipc-message', listener); - done(); - }; - var listener2 = function() { - webview.send('ping', message); - webview.removeEventListener('did-finish-load', listener2); - }; - webview.addEventListener('ipc-message', listener); - webview.addEventListener('did-finish-load', listener2); - webview.setAttribute('preload', fixtures + "/module/preload-ipc.js"); - webview.src = "file://" + fixtures + "/pages/e.html"; - document.body.appendChild(webview); - }); - }); + it('receives ipc message in preload script', function (done) { + var message = 'boom!' + var listener = function (e) { + assert.equal(e.channel, 'pong') + assert.deepEqual(e.args, [message]) + webview.removeEventListener('ipc-message', listener) + done() + } + var listener2 = function () { + webview.send('ping', message) + webview.removeEventListener('did-finish-load', listener2) + } + webview.addEventListener('ipc-message', listener) + webview.addEventListener('did-finish-load', listener2) + webview.setAttribute('preload', fixtures + '/module/preload-ipc.js') + webview.src = 'file://' + fixtures + '/pages/e.html' + document.body.appendChild(webview) + }) - describe('httpreferrer attribute', function() { - it('sets the referrer url', function(done) { - var referrer = 'http://github.com/'; - var listener = function(e) { - assert.equal(e.message, referrer); - webview.removeEventListener('console-message', listener); - done(); - }; - webview.addEventListener('console-message', listener); - webview.setAttribute('httpreferrer', referrer); - webview.src = "file://" + fixtures + "/pages/referrer.html"; - document.body.appendChild(webview); - }); - }); + it('works without script tag in page', function (done) { + var listener = function (e) { + assert.equal(e.message, 'function object object') + webview.removeEventListener('console-message', listener) + done() + } + webview.addEventListener('console-message', listener) + webview.setAttribute('preload', fixtures + '/module/preload.js') + webview.src = 'file://' + fixtures + '/pages/base-page.html' + document.body.appendChild(webview) + }) + }) - describe('useragent attribute', function() { - it('sets the user agent', function(done) { - var referrer = 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko'; - var listener = function(e) { - assert.equal(e.message, referrer); - webview.removeEventListener('console-message', listener); - done(); - }; - webview.addEventListener('console-message', listener); - webview.setAttribute('useragent', referrer); - webview.src = "file://" + fixtures + "/pages/useragent.html"; - document.body.appendChild(webview); - }); - }); + describe('httpreferrer attribute', function () { + it('sets the referrer url', function (done) { + var referrer = 'http://github.com/' + var listener = function (e) { + assert.equal(e.message, referrer) + webview.removeEventListener('console-message', listener) + done() + } + webview.addEventListener('console-message', listener) + webview.setAttribute('httpreferrer', referrer) + webview.src = 'file://' + fixtures + '/pages/referrer.html' + document.body.appendChild(webview) + }) + }) - describe('disablewebsecurity attribute', function() { - it('does not disable web security when not set', function(done) { - var src = " "; - var encoded = btoa(unescape(encodeURIComponent(src))); - var listener = function(e) { - assert(/Not allowed to load local resource/.test(e.message)); - webview.removeEventListener('console-message', listener); - done(); - }; - webview.addEventListener('console-message', listener); - webview.src = "data:text/html;base64," + encoded; - document.body.appendChild(webview); - }); + describe('useragent attribute', function () { + it('sets the user agent', function (done) { + var referrer = 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko' + var listener = function (e) { + assert.equal(e.message, referrer) + webview.removeEventListener('console-message', listener) + done() + } + webview.addEventListener('console-message', listener) + webview.setAttribute('useragent', referrer) + webview.src = 'file://' + fixtures + '/pages/useragent.html' + document.body.appendChild(webview) + }) + }) - it('disables web security when set', function(done) { - var src = " "; - var encoded = btoa(unescape(encodeURIComponent(src))); - var listener = function(e) { - assert.equal(e.message, 'ok'); - webview.removeEventListener('console-message', listener); - done(); - }; - webview.addEventListener('console-message', listener); - webview.setAttribute('disablewebsecurity', ''); - webview.src = "data:text/html;base64," + encoded; - document.body.appendChild(webview); - }); - }); - describe('partition attribute', function() { - it('inserts no node symbols when not set', function(done) { - webview.addEventListener('console-message', function(e) { - assert.equal(e.message, 'undefined undefined undefined undefined'); - done(); - }); - webview.src = "file://" + fixtures + "/pages/c.html"; - webview.partition = 'test1'; - document.body.appendChild(webview); - }); + describe('disablewebsecurity attribute', function () { + it('does not disable web security when not set', function (done) { + var jqueryPath = path.join(__dirname, '/static/jquery-2.0.3.min.js') + var src = ` ` + var encoded = btoa(unescape(encodeURIComponent(src))) + var listener = function (e) { + assert(/Not allowed to load local resource/.test(e.message)) + webview.removeEventListener('console-message', listener) + done() + } + webview.addEventListener('console-message', listener) + webview.src = 'data:text/html;base64,' + encoded + document.body.appendChild(webview) + }) - it('inserts node symbols when set', function(done) { - webview.addEventListener('console-message', function(e) { - assert.equal(e.message, 'function object object'); - done(); - }); - webview.setAttribute('nodeintegration', 'on'); - webview.src = "file://" + fixtures + "/pages/d.html"; - webview.partition = 'test2'; - document.body.appendChild(webview); - }); + it('disables web security when set', function (done) { + var jqueryPath = path.join(__dirname, '/static/jquery-2.0.3.min.js') + var src = ` ` + var encoded = btoa(unescape(encodeURIComponent(src))) + var listener = function (e) { + assert.equal(e.message, 'ok') + webview.removeEventListener('console-message', listener) + done() + } + webview.addEventListener('console-message', listener) + webview.setAttribute('disablewebsecurity', '') + webview.src = 'data:text/html;base64,' + encoded + document.body.appendChild(webview) + }) - it('isolates storage for different id', function(done) { - var listener = function(e) { - assert.equal(e.message, " 0"); - webview.removeEventListener('console-message', listener); - done(); - }; - window.localStorage.setItem('test', 'one'); - webview.addEventListener('console-message', listener); - webview.src = "file://" + fixtures + "/pages/partition/one.html"; - webview.partition = 'test3'; - document.body.appendChild(webview); - }); + it('does not break node integration', function (done) { + webview.addEventListener('console-message', function (e) { + assert.equal(e.message, 'function object object') + done() + }) + webview.setAttribute('nodeintegration', 'on') + webview.setAttribute('disablewebsecurity', '') + webview.src = 'file://' + fixtures + '/pages/d.html' + document.body.appendChild(webview) + }) - it('uses current session storage when no id is provided', function(done) { - var listener = function(e) { - assert.equal(e.message, "one 1"); - webview.removeEventListener('console-message', listener); - done(); - }; - window.localStorage.setItem('test', 'one'); - webview.addEventListener('console-message', listener); - webview.src = "file://" + fixtures + "/pages/partition/one.html"; - document.body.appendChild(webview); - }); - }); + it('does not break preload script', function (done) { + var listener = function (e) { + assert.equal(e.message, 'function object object') + webview.removeEventListener('console-message', listener) + done() + } + webview.addEventListener('console-message', listener) + webview.setAttribute('disablewebsecurity', '') + webview.setAttribute('preload', fixtures + '/module/preload.js') + webview.src = 'file://' + fixtures + '/pages/e.html' + document.body.appendChild(webview) + }) + }) - describe('allowpopups attribute', function() { - it('can not open new window when not set', function(done) { - var listener = function(e) { - assert.equal(e.message, 'null'); - webview.removeEventListener('console-message', listener); - done(); - }; - webview.addEventListener('console-message', listener); - webview.src = "file://" + fixtures + "/pages/window-open-hide.html"; - document.body.appendChild(webview); - }); + describe('partition attribute', function () { + it('inserts no node symbols when not set', function (done) { + webview.addEventListener('console-message', function (e) { + assert.equal(e.message, 'undefined undefined undefined undefined') + done() + }) + webview.src = 'file://' + fixtures + '/pages/c.html' + webview.partition = 'test1' + document.body.appendChild(webview) + }) - it('can open new window when set', function(done) { - var listener = function(e) { - assert.equal(e.message, 'window'); - webview.removeEventListener('console-message', listener); - done(); - }; - webview.addEventListener('console-message', listener); - webview.setAttribute('allowpopups', 'on'); - webview.src = "file://" + fixtures + "/pages/window-open-hide.html"; - document.body.appendChild(webview); - }); - }); + it('inserts node symbols when set', function (done) { + webview.addEventListener('console-message', function (e) { + assert.equal(e.message, 'function object object') + done() + }) + webview.setAttribute('nodeintegration', 'on') + webview.src = 'file://' + fixtures + '/pages/d.html' + webview.partition = 'test2' + document.body.appendChild(webview) + }) - describe('new-window event', function() { - it('emits when window.open is called', function(done) { - webview.addEventListener('new-window', function(e) { - assert.equal(e.url, 'http://host/'); - assert.equal(e.frameName, 'host'); - done(); - }); - webview.src = "file://" + fixtures + "/pages/window-open.html"; - document.body.appendChild(webview); - }); + it('isolates storage for different id', function (done) { + var listener = function (e) { + assert.equal(e.message, ' 0') + webview.removeEventListener('console-message', listener) + done() + } + window.localStorage.setItem('test', 'one') + webview.addEventListener('console-message', listener) + webview.src = 'file://' + fixtures + '/pages/partition/one.html' + webview.partition = 'test3' + document.body.appendChild(webview) + }) - it('emits when link with target is called', function(done) { - webview.addEventListener('new-window', function(e) { - assert.equal(e.url, 'http://host/'); - assert.equal(e.frameName, 'target'); - done(); - }); - webview.src = "file://" + fixtures + "/pages/target-name.html"; - document.body.appendChild(webview); - }); - }); + it('uses current session storage when no id is provided', function (done) { + var listener = function (e) { + assert.equal(e.message, 'one 1') + webview.removeEventListener('console-message', listener) + done() + } + window.localStorage.setItem('test', 'one') + webview.addEventListener('console-message', listener) + webview.src = 'file://' + fixtures + '/pages/partition/one.html' + document.body.appendChild(webview) + }) + }) - describe('ipc-message event', function() { - it('emits when guest sends a ipc message to browser', function(done) { - webview.addEventListener('ipc-message', function(e) { - assert.equal(e.channel, 'channel'); - assert.deepEqual(e.args, ['arg1', 'arg2']); - done(); - }); - webview.src = "file://" + fixtures + "/pages/ipc-message.html"; - webview.setAttribute('nodeintegration', 'on'); - document.body.appendChild(webview); - }); - }); + describe('allowpopups attribute', function () { + if (process.env.TRAVIS === 'true' && process.platform === 'darwin') { + return + } - describe('page-title-set event', function() { - it('emits when title is set', function(done) { - webview.addEventListener('page-title-set', function(e) { - assert.equal(e.title, 'test'); - assert(e.explicitSet); - done(); - }); - webview.src = "file://" + fixtures + "/pages/a.html"; - document.body.appendChild(webview); - }); - }); + it('can not open new window when not set', function (done) { + var listener = function (e) { + assert.equal(e.message, 'null') + webview.removeEventListener('console-message', listener) + done() + } + webview.addEventListener('console-message', listener) + webview.src = 'file://' + fixtures + '/pages/window-open-hide.html' + document.body.appendChild(webview) + }) - describe('page-favicon-updated event', function() { - it('emits when favicon urls are received', function(done) { - webview.addEventListener('page-favicon-updated', function(e) { - assert.equal(e.favicons.length, 2); - var pageUrl = process.platform === 'win32' ? 'file:///C:/favicon.png' : 'file:///favicon.png'; - assert.equal(e.favicons[0], pageUrl); - done(); - }); - webview.src = "file://" + fixtures + "/pages/a.html"; - document.body.appendChild(webview); - }); - }); + it('can open new window when set', function (done) { + var listener = function (e) { + assert.equal(e.message, 'window') + webview.removeEventListener('console-message', listener) + done() + } + webview.addEventListener('console-message', listener) + webview.setAttribute('allowpopups', 'on') + webview.src = 'file://' + fixtures + '/pages/window-open-hide.html' + document.body.appendChild(webview) + }) + }) - describe('will-navigate event', function() { - it('emits when a url that leads to oustide of the page is clicked', function(done) { - webview.addEventListener('will-navigate', function(e) { - assert.equal(e.url, "http://host/"); - done(); - }); - webview.src = "file://" + fixtures + "/pages/webview-will-navigate.html"; - document.body.appendChild(webview); - }); - }); + describe('new-window event', function () { + if (process.env.TRAVIS === 'true' && process.platform === 'darwin') { + return + } - describe('did-navigate event', function() { - var p = path.join(fixtures, 'pages', 'webview-will-navigate.html'); - p = p.replace(/\\/g, '/'); + it('emits when window.open is called', function (done) { + webview.addEventListener('new-window', function (e) { + assert.equal(e.url, 'http://host/') + assert.equal(e.frameName, 'host') + done() + }) + webview.src = 'file://' + fixtures + '/pages/window-open.html' + document.body.appendChild(webview) + }) + + it('emits when link with target is called', function (done) { + webview.addEventListener('new-window', function (e) { + assert.equal(e.url, 'http://host/') + assert.equal(e.frameName, 'target') + done() + }) + webview.src = 'file://' + fixtures + '/pages/target-name.html' + document.body.appendChild(webview) + }) + }) + + describe('ipc-message event', function () { + it('emits when guest sends a ipc message to browser', function (done) { + webview.addEventListener('ipc-message', function (e) { + assert.equal(e.channel, 'channel') + assert.deepEqual(e.args, ['arg1', 'arg2']) + done() + }) + webview.src = 'file://' + fixtures + '/pages/ipc-message.html' + webview.setAttribute('nodeintegration', 'on') + document.body.appendChild(webview) + }) + }) + + describe('page-title-set event', function () { + it('emits when title is set', function (done) { + webview.addEventListener('page-title-set', function (e) { + assert.equal(e.title, 'test') + assert(e.explicitSet) + done() + }) + webview.src = 'file://' + fixtures + '/pages/a.html' + document.body.appendChild(webview) + }) + }) + + describe('page-favicon-updated event', function () { + it('emits when favicon urls are received', function (done) { + webview.addEventListener('page-favicon-updated', function (e) { + assert.equal(e.favicons.length, 2) + var pageUrl = process.platform === 'win32' ? 'file:///C:/favicon.png' : 'file:///favicon.png' + assert.equal(e.favicons[0], pageUrl) + done() + }) + webview.src = 'file://' + fixtures + '/pages/a.html' + document.body.appendChild(webview) + }) + }) + + describe('will-navigate event', function () { + it('emits when a url that leads to oustide of the page is clicked', function (done) { + webview.addEventListener('will-navigate', function (e) { + assert.equal(e.url, 'http://host/') + done() + }) + webview.src = 'file://' + fixtures + '/pages/webview-will-navigate.html' + document.body.appendChild(webview) + }) + }) + + describe('did-navigate event', function () { + var p = path.join(fixtures, 'pages', 'webview-will-navigate.html') + p = p.replace(/\\/g, '/') var pageUrl = url.format({ protocol: 'file', slashes: true, pathname: p - }); + }) - it('emits when a url that leads to outside of the page is clicked', function(done) { - webview.addEventListener('did-navigate', function(e) { - assert.equal(e.url, pageUrl); - done(); - }); - webview.src = pageUrl; - document.body.appendChild(webview); - }); - }); - describe('did-navigate-in-page event', function() { - it('emits when an anchor link is clicked', function(done) { - var p = path.join(fixtures, 'pages', 'webview-did-navigate-in-page.html'); - p = p.replace(/\\/g, '/'); + it('emits when a url that leads to outside of the page is clicked', function (done) { + webview.addEventListener('did-navigate', function (e) { + assert.equal(e.url, pageUrl) + done() + }) + webview.src = pageUrl + document.body.appendChild(webview) + }) + }) + + describe('did-navigate-in-page event', function () { + it('emits when an anchor link is clicked', function (done) { + var p = path.join(fixtures, 'pages', 'webview-did-navigate-in-page.html') + p = p.replace(/\\/g, '/') var pageUrl = url.format({ protocol: 'file', slashes: true, pathname: p - }); - webview.addEventListener('did-navigate-in-page', function(e) { - assert.equal(e.url, pageUrl + "#test_content"); - done(); - }); - webview.src = pageUrl; - document.body.appendChild(webview); - }); + }) + webview.addEventListener('did-navigate-in-page', function (e) { + assert.equal(e.url, pageUrl + '#test_content') + done() + }) + webview.src = pageUrl + document.body.appendChild(webview) + }) - it('emits when window.history.replaceState is called', function(done) { - webview.addEventListener('did-navigate-in-page', function(e) { - assert.equal(e.url, "http://host/"); - done(); - }); - webview.src = "file://" + fixtures + "/pages/webview-did-navigate-in-page-with-history.html"; - document.body.appendChild(webview); - }); + it('emits when window.history.replaceState is called', function (done) { + webview.addEventListener('did-navigate-in-page', function (e) { + assert.equal(e.url, 'http://host/') + done() + }) + webview.src = 'file://' + fixtures + '/pages/webview-did-navigate-in-page-with-history.html' + document.body.appendChild(webview) + }) - it('emits when window.location.hash is changed', function(done) { - var p = path.join(fixtures, 'pages', 'webview-did-navigate-in-page-with-hash.html'); - p = p.replace(/\\/g, '/'); + it('emits when window.location.hash is changed', function (done) { + var p = path.join(fixtures, 'pages', 'webview-did-navigate-in-page-with-hash.html') + p = p.replace(/\\/g, '/') var pageUrl = url.format({ protocol: 'file', slashes: true, pathname: p - }); - webview.addEventListener('did-navigate-in-page', function(e) { - assert.equal(e.url, pageUrl + "#test"); - done(); - }); - webview.src = pageUrl; - document.body.appendChild(webview); - }); - }); + }) + webview.addEventListener('did-navigate-in-page', function (e) { + assert.equal(e.url, pageUrl + '#test') + done() + }) + webview.src = pageUrl + document.body.appendChild(webview) + }) + }) - describe('close event', function() { - it('should fire when interior page calls window.close', function(done) { - webview.addEventListener('close', function() { - done(); - }); - webview.src = "file://" + fixtures + "/pages/close.html"; - document.body.appendChild(webview); - }); - }); + describe('close event', function () { + it('should fire when interior page calls window.close', function (done) { + webview.addEventListener('close', function () { + done() + }) + webview.src = 'file://' + fixtures + '/pages/close.html' + document.body.appendChild(webview) + }) + }) - describe('devtools-opened event', function() { - it('should fire when webview.openDevTools() is called', function(done) { - var listener = function() { - webview.removeEventListener('devtools-opened', listener); - webview.closeDevTools(); - done(); - }; - webview.addEventListener('devtools-opened', listener); - webview.addEventListener('dom-ready', function() { - webview.openDevTools(); - }); - webview.src = "file://" + fixtures + "/pages/base-page.html"; - document.body.appendChild(webview); - }); - }); + describe('devtools-opened event', function () { + it('should fire when webview.openDevTools() is called', function (done) { + var listener = function () { + webview.removeEventListener('devtools-opened', listener) + webview.closeDevTools() + done() + } + webview.addEventListener('devtools-opened', listener) + webview.addEventListener('dom-ready', function () { + webview.openDevTools() + }) + webview.src = 'file://' + fixtures + '/pages/base-page.html' + document.body.appendChild(webview) + }) + }) - describe('devtools-closed event', function() { - it('should fire when webview.closeDevTools() is called', function(done) { - var listener2 = function() { - webview.removeEventListener('devtools-closed', listener2); - done(); - }; - var listener = function() { - webview.removeEventListener('devtools-opened', listener); - webview.closeDevTools(); - }; - webview.addEventListener('devtools-opened', listener); - webview.addEventListener('devtools-closed', listener2); - webview.addEventListener('dom-ready', function() { - webview.openDevTools(); - }); - webview.src = "file://" + fixtures + "/pages/base-page.html"; - document.body.appendChild(webview); - }); - }); + describe('devtools-closed event', function () { + it('should fire when webview.closeDevTools() is called', function (done) { + var listener2 = function () { + webview.removeEventListener('devtools-closed', listener2) + done() + } + var listener = function () { + webview.removeEventListener('devtools-opened', listener) + webview.closeDevTools() + } + webview.addEventListener('devtools-opened', listener) + webview.addEventListener('devtools-closed', listener2) + webview.addEventListener('dom-ready', function () { + webview.openDevTools() + }) + webview.src = 'file://' + fixtures + '/pages/base-page.html' + document.body.appendChild(webview) + }) + }) - describe('devtools-focused event', function() { - it('should fire when webview.openDevTools() is called', function(done) { - var listener = function() { - webview.removeEventListener('devtools-focused', listener); - webview.closeDevTools(); - done(); - }; - webview.addEventListener('devtools-focused', listener); - webview.addEventListener('dom-ready', function() { - webview.openDevTools(); - }); - webview.src = "file://" + fixtures + "/pages/base-page.html"; - document.body.appendChild(webview); - }); - }); + describe('devtools-focused event', function () { + it('should fire when webview.openDevTools() is called', function (done) { + var listener = function () { + webview.removeEventListener('devtools-focused', listener) + webview.closeDevTools() + done() + } + webview.addEventListener('devtools-focused', listener) + webview.addEventListener('dom-ready', function () { + webview.openDevTools() + }) + webview.src = 'file://' + fixtures + '/pages/base-page.html' + document.body.appendChild(webview) + }) + }) - describe('.reload()', function() { - it('should emit beforeunload handler', function(done) { - var listener = function(e) { - assert.equal(e.channel, 'onbeforeunload'); - webview.removeEventListener('ipc-message', listener); - done(); - }; - var listener2 = function() { - webview.reload(); - webview.removeEventListener('did-finish-load', listener2); - }; - webview.addEventListener('ipc-message', listener); - webview.addEventListener('did-finish-load', listener2); - webview.setAttribute('nodeintegration', 'on'); - webview.src = "file://" + fixtures + "/pages/beforeunload-false.html"; - document.body.appendChild(webview); - }); - }); + describe('.reload()', function () { + it('should emit beforeunload handler', function (done) { + var listener = function (e) { + assert.equal(e.channel, 'onbeforeunload') + webview.removeEventListener('ipc-message', listener) + done() + } + var listener2 = function () { + webview.reload() + webview.removeEventListener('did-finish-load', listener2) + } + webview.addEventListener('ipc-message', listener) + webview.addEventListener('did-finish-load', listener2) + webview.setAttribute('nodeintegration', 'on') + webview.src = 'file://' + fixtures + '/pages/beforeunload-false.html' + document.body.appendChild(webview) + }) + }) - describe('.clearHistory()', function() { - it('should clear the navigation history', function(done) { - var listener = function(e) { - assert.equal(e.channel, 'history'); - assert.equal(e.args[0], 2); - assert(webview.canGoBack()); - webview.clearHistory(); - assert(!webview.canGoBack()); - webview.removeEventListener('ipc-message', listener); - done(); - }; - webview.addEventListener('ipc-message', listener); - webview.setAttribute('nodeintegration', 'on'); - webview.src = "file://" + fixtures + "/pages/history.html"; - document.body.appendChild(webview); - }); - }); + describe('.clearHistory()', function () { + it('should clear the navigation history', function (done) { + var listener = function (e) { + assert.equal(e.channel, 'history') + assert.equal(e.args[0], 2) + assert(webview.canGoBack()) + webview.clearHistory() + assert(!webview.canGoBack()) + webview.removeEventListener('ipc-message', listener) + done() + } + webview.addEventListener('ipc-message', listener) + webview.setAttribute('nodeintegration', 'on') + webview.src = 'file://' + fixtures + '/pages/history.html' + document.body.appendChild(webview) + }) + }) - describe('basic auth', function() { - var auth = require('basic-auth'); + describe('basic auth', function () { + var auth = require('basic-auth') - it('should authenticate with correct credentials', function(done) { - var message = 'Authenticated'; - var server = http.createServer(function(req, res) { - var credentials = auth(req); + it('should authenticate with correct credentials', function (done) { + var message = 'Authenticated' + var server = http.createServer(function (req, res) { + var credentials = auth(req) if (credentials.name === 'test' && credentials.pass === 'test') { - res.end(message); + res.end(message) } else { - res.end('failed'); + res.end('failed') } - server.close(); - }); - server.listen(0, '127.0.0.1', function() { - var port = server.address().port; - webview.addEventListener('ipc-message', function(e) { - assert.equal(e.channel, message); - done(); - }); - webview.src = "file://" + fixtures + "/pages/basic-auth.html?port=" + port; - webview.setAttribute('nodeintegration', 'on'); - document.body.appendChild(webview); - }); - }); - }); + server.close() + }) + server.listen(0, '127.0.0.1', function () { + var port = server.address().port + webview.addEventListener('ipc-message', function (e) { + assert.equal(e.channel, message) + done() + }) + webview.src = 'file://' + fixtures + '/pages/basic-auth.html?port=' + port + webview.setAttribute('nodeintegration', 'on') + document.body.appendChild(webview) + }) + }) + }) - describe('dom-ready event', function() { - it('emits when document is loaded', function(done) { - var server = http.createServer(function() {}); - server.listen(0, '127.0.0.1', function() { - var port = server.address().port; - webview.addEventListener('dom-ready', function() { - done(); - }); - webview.src = "file://" + fixtures + "/pages/dom-ready.html?port=" + port; - document.body.appendChild(webview); - }); - }); + describe('dom-ready event', function () { + it('emits when document is loaded', function (done) { + var server = http.createServer(function () {}) + server.listen(0, '127.0.0.1', function () { + var port = server.address().port + webview.addEventListener('dom-ready', function () { + done() + }) + webview.src = 'file://' + fixtures + '/pages/dom-ready.html?port=' + port + document.body.appendChild(webview) + }) + }) - it('throws a custom error when an API method is called before the event is emitted', function() { + it('throws a custom error when an API method is called before the event is emitted', function () { assert.throws(function () { - webview.stop(); - }, 'Cannot call stop because the webContents is unavailable. The WebView must be attached to the DOM and the dom-ready event emitted before this method can be called.'); - }); - }); + webview.stop() + }, 'Cannot call stop because the webContents is unavailable. The WebView must be attached to the DOM and the dom-ready event emitted before this method can be called.') + }) + }) - describe('executeJavaScript', function() { - if (process.env.TRAVIS !== 'true') { - return; - } + describe('executeJavaScript', function () { + it('should support user gesture', function (done) { + if (process.env.TRAVIS !== 'true' || process.platform === 'darwin') return done() - it('should support user gesture', function(done) { - var listener = function() { - webview.removeEventListener('enter-html-full-screen', listener); - done(); - }; - var listener2 = function() { - var jsScript = 'document.getElementsByTagName("video")[0].webkitRequestFullScreen()'; - webview.executeJavaScript(jsScript, true); - webview.removeEventListener('did-finish-load', listener2); - }; - webview.addEventListener('enter-html-full-screen', listener); - webview.addEventListener('did-finish-load', listener2); - webview.src = "file://" + fixtures + "/pages/fullscreen.html"; - document.body.appendChild(webview); - }); - }); + var listener = function () { + webview.removeEventListener('enter-html-full-screen', listener) + done() + } + var listener2 = function () { + var jsScript = "document.querySelector('video').webkitRequestFullscreen()" + webview.executeJavaScript(jsScript, true) + webview.removeEventListener('did-finish-load', listener2) + } + webview.addEventListener('enter-html-full-screen', listener) + webview.addEventListener('did-finish-load', listener2) + webview.src = 'file://' + fixtures + '/pages/fullscreen.html' + document.body.appendChild(webview) + }) - describe('sendInputEvent', function() { - it('can send keyboard event', function(done) { - webview.addEventListener('ipc-message', function(e) { - assert.equal(e.channel, 'keyup'); - assert.deepEqual(e.args, [67, true, false]); - done(); - }); - webview.addEventListener('dom-ready', function() { + it('can return the result of the executed script', function (done) { + if (process.env.TRAVIS === 'true' && process.platform === 'darwin') return done() + + var listener = function () { + var jsScript = "'4'+2" + webview.executeJavaScript(jsScript, false, function (result) { + assert.equal(result, '42') + done() + }) + webview.removeEventListener('did-finish-load', listener) + } + webview.addEventListener('did-finish-load', listener) + webview.src = 'about:blank' + document.body.appendChild(webview) + }) + }) + + describe('sendInputEvent', function () { + it('can send keyboard event', function (done) { + webview.addEventListener('ipc-message', function (e) { + assert.equal(e.channel, 'keyup') + assert.deepEqual(e.args, [67, true, false]) + done() + }) + webview.addEventListener('dom-ready', function () { webview.sendInputEvent({ type: 'keyup', keyCode: 'c', modifiers: ['shift'] - }); - }); - webview.src = "file://" + fixtures + "/pages/onkeyup.html"; - webview.setAttribute('nodeintegration', 'on'); - document.body.appendChild(webview); - }); + }) + }) + webview.src = 'file://' + fixtures + '/pages/onkeyup.html' + webview.setAttribute('nodeintegration', 'on') + document.body.appendChild(webview) + }) - it('can send mouse event', function(done) { - webview.addEventListener('ipc-message', function(e) { - assert.equal(e.channel, 'mouseup'); - assert.deepEqual(e.args, [10, 20, false, true]); - done(); - }); - webview.addEventListener('dom-ready', function() { + it('can send mouse event', function (done) { + webview.addEventListener('ipc-message', function (e) { + assert.equal(e.channel, 'mouseup') + assert.deepEqual(e.args, [10, 20, false, true]) + done() + }) + webview.addEventListener('dom-ready', function () { webview.sendInputEvent({ type: 'mouseup', modifiers: ['ctrl'], x: 10, y: 20 - }); - }); - webview.src = "file://" + fixtures + "/pages/onmouseup.html"; - webview.setAttribute('nodeintegration', 'on'); - document.body.appendChild(webview); - }); - }); + }) + }) + webview.src = 'file://' + fixtures + '/pages/onmouseup.html' + webview.setAttribute('nodeintegration', 'on') + document.body.appendChild(webview) + }) + }) - describe('media-started-playing media-paused events', function() { - it('emits when audio starts and stops playing', function(done) { - var audioPlayed = false; - webview.addEventListener('media-started-playing', function() { - audioPlayed = true; - }); - webview.addEventListener('media-paused', function() { - assert(audioPlayed); - done(); - }); - webview.src = "file://" + fixtures + "/pages/audio.html"; - document.body.appendChild(webview); - }); - }); + describe('media-started-playing media-paused events', function () { + it('emits when audio starts and stops playing', function (done) { + var audioPlayed = false + webview.addEventListener('media-started-playing', function () { + audioPlayed = true + }) + webview.addEventListener('media-paused', function () { + assert(audioPlayed) + done() + }) + webview.src = 'file://' + fixtures + '/pages/audio.html' + document.body.appendChild(webview) + }) + }) - describe('found-in-page event', function() { - it('emits when a request is made', function(done) { - var requestId = null; - var listener = function(e) { - assert.equal(e.result.requestId, requestId); + describe('found-in-page event', function () { + it('emits when a request is made', function (done) { + var requestId = null + var totalMatches = null + var activeMatchOrdinal = [] + var listener = function (e) { + assert.equal(e.result.requestId, requestId) if (e.result.finalUpdate) { - assert.equal(e.result.matches, 3); - webview.stopFindInPage("clearSelection"); - done(); + assert.equal(e.result.matches, 3) + totalMatches = e.result.matches + listener2() + } else { + activeMatchOrdinal.push(e.result.activeMatchOrdinal) + if (e.result.activeMatchOrdinal === totalMatches) { + assert.deepEqual(activeMatchOrdinal, [1, 2, 3]) + webview.stopFindInPage('clearSelection') + done() + } } - }; - var listener2 = function() { - requestId = webview.findInPage("virtual"); - }; - webview.addEventListener('found-in-page', listener); - webview.addEventListener('did-finish-load', listener2); - webview.src = "file://" + fixtures + "/pages/content.html"; - document.body.appendChild(webview); - }); - }); + } + var listener2 = function () { + requestId = webview.findInPage('virtual') + } + webview.addEventListener('found-in-page', listener) + webview.addEventListener('did-finish-load', listener2) + webview.src = 'file://' + fixtures + '/pages/content.html' + document.body.appendChild(webview) + }) + }) - xdescribe('did-change-theme-color event', function() { - it('emits when theme color changes', function(done) { - webview.addEventListener('did-change-theme-color', function() { - done(); - }); - webview.src = "file://" + fixtures + "/pages/theme-color.html"; - document.body.appendChild(webview); - }); - }); + xdescribe('did-change-theme-color event', function () { + it('emits when theme color changes', function (done) { + webview.addEventListener('did-change-theme-color', function () { + done() + }) + webview.src = 'file://' + fixtures + '/pages/theme-color.html' + document.body.appendChild(webview) + }) + }) - describe('permission-request event', function() { - function setUpRequestHandler(webview, requested_permission) { - const session = require('electron').remote.session; - var listener = function(webContents, permission, callback) { - if (webContents.getId() === webview.getId() ) { - assert.equal(permission, requested_permission); - callback(false); + describe('permission-request event', function () { + function setUpRequestHandler (webview, requested_permission, completed) { + var listener = function (webContents, permission, callback) { + if (webContents.getId() === webview.getId()) { + assert.equal(permission, requested_permission) + callback(false) + if (completed) + completed() } - }; - session.fromPartition(webview.partition).setPermissionRequestHandler(listener); + } + session.fromPartition(webview.partition).setPermissionRequestHandler(listener) } - it('emits when using navigator.getUserMedia api', function(done) { - webview.addEventListener('ipc-message', function(e) { - assert(e.channel, 'message'); - assert(e.args, ['PermissionDeniedError']); - done(); - }); - webview.src = "file://" + fixtures + "/pages/permissions/media.html"; - webview.partition = "permissionTest"; - webview.setAttribute('nodeintegration', 'on'); - setUpRequestHandler(webview, "media"); - document.body.appendChild(webview); - }); + it('emits when using navigator.getUserMedia api', function (done) { + webview.addEventListener('ipc-message', function (e) { + assert(e.channel, 'message') + assert(e.args, ['PermissionDeniedError']) + done() + }) + webview.src = 'file://' + fixtures + '/pages/permissions/media.html' + webview.partition = 'permissionTest' + webview.setAttribute('nodeintegration', 'on') + setUpRequestHandler(webview, 'media') + document.body.appendChild(webview) + }) - it('emits when using navigator.geolocation api', function(done) { - webview.addEventListener('ipc-message', function(e) { - assert(e.channel, 'message'); - assert(e.args, ['ERROR(1): User denied Geolocation']); - done(); - }); - webview.src = "file://" + fixtures + "/pages/permissions/geolocation.html"; - webview.partition = "permissionTest"; - webview.setAttribute('nodeintegration', 'on'); - setUpRequestHandler(webview, "geolocation"); - document.body.appendChild(webview); - }); + it('emits when using navigator.geolocation api', function (done) { + webview.addEventListener('ipc-message', function (e) { + assert(e.channel, 'message') + assert(e.args, ['ERROR(1): User denied Geolocation']) + done() + }) + webview.src = 'file://' + fixtures + '/pages/permissions/geolocation.html' + webview.partition = 'permissionTest' + webview.setAttribute('nodeintegration', 'on') + setUpRequestHandler(webview, 'geolocation') + document.body.appendChild(webview) + }) - it('emits when using navigator.requestMIDIAccess api', function(done) { - webview.addEventListener('ipc-message', function(e) { - assert(e.channel, 'message'); - assert(e.args, ['SecurityError']); - done(); - }); - webview.src = "file://" + fixtures + "/pages/permissions/midi.html"; - webview.partition = "permissionTest"; - webview.setAttribute('nodeintegration', 'on'); - setUpRequestHandler(webview, "midiSysex"); - document.body.appendChild(webview); - }); - }); + it('emits when using navigator.requestMIDIAccess api', function (done) { + webview.addEventListener('ipc-message', function (e) { + assert(e.channel, 'message') + assert(e.args, ['SecurityError']) + done() + }) + webview.src = 'file://' + fixtures + '/pages/permissions/midi.html' + webview.partition = 'permissionTest' + webview.setAttribute('nodeintegration', 'on') + setUpRequestHandler(webview, 'midiSysex') + document.body.appendChild(webview) + }) - describe('.getWebContents', function() { - it('can return the webcontents associated', function(done) { - webview.addEventListener('did-finish-load', function() { - const webviewContents = webview.getWebContents(); - assert(webviewContents); - assert.equal(webviewContents.getURL(), 'about:blank'); - done(); - }); - webview.src = "about:blank"; - document.body.appendChild(webview); - }); - }); -}); + it('emits when accessing external protocol', function (done) { + webview.src = 'magnet:test' + webview.partition = 'permissionTest' + setUpRequestHandler(webview, 'openExternal', done) + document.body.appendChild(webview) + }) + }) + + describe('.getWebContents', function () { + it('can return the webcontents associated', function (done) { + webview.addEventListener('did-finish-load', function () { + const webviewContents = webview.getWebContents() + assert(webviewContents) + assert.equal(webviewContents.getURL(), 'about:blank') + done() + }) + webview.src = 'about:blank' + document.body.appendChild(webview) + }) + }) + + describe('did-get-response-details event', function () { + it('emits for the page and its resources', function (done) { + // expected {fileName: resourceType} pairs + var expectedResources = { + 'did-get-response-details.html': 'mainFrame', + 'logo.png': 'image' + } + var responses = 0; + webview.addEventListener('did-get-response-details', function (event) { + responses++ + var fileName = event.newURL.slice(event.newURL.lastIndexOf('/') + 1) + var expectedType = expectedResources[fileName] + assert(!!expectedType, `Unexpected response details for ${event.newURL}`) + assert(typeof event.status === 'boolean', 'status should be boolean') + assert.equal(event.httpResponseCode, 200) + assert.equal(event.requestMethod, 'GET') + assert(typeof event.referrer === 'string', 'referrer should be string') + assert(!!event.headers, 'headers should be present') + assert(typeof event.headers === 'object', 'headers should be object') + assert.equal(event.resourceType, expectedType, 'Incorrect resourceType') + if (responses === Object.keys(expectedResources).length) { + done() + } + }) + webview.src = 'file://' + path.join(fixtures, 'pages', 'did-get-response-details.html') + document.body.appendChild(webview) + }) + }) + + it('inherits the zoomFactor of the parent window', function (done) { + w = new BrowserWindow({ + show: false, + webPreferences: { + zoomFactor: 1.2 + } + }) + ipcMain.once('pong', function (event, zoomFactor, zoomLevel) { + assert.equal(zoomFactor, 1.2) + assert.equal(zoomLevel, 1) + done() + }) + w.loadURL('file://' + fixtures + '/pages/webview-zoom-factor.html') + }) + + it('inherits the parent window visibility state and receives visibilitychange events', function (done) { + w = new BrowserWindow({ + show: false + }) + + ipcMain.once('pong', function (event, visibilityState, hidden) { + assert.equal(visibilityState, 'hidden') + assert.equal(hidden, true) + + w.webContents.send('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', 'visible') + + ipcMain.once('pong', function (event, visibilityState, hidden) { + assert.equal(visibilityState, 'visible') + assert.equal(hidden, false) + done() + }) + }) + + w.loadURL('file://' + fixtures + '/pages/webview-visibilitychange.html') + }) +}) diff --git a/toolchain.gypi b/toolchain.gypi index 23592d0473a7..11da28f70233 100644 --- a/toolchain.gypi +++ b/toolchain.gypi @@ -5,9 +5,6 @@ # Set this to true when building with Clang. 'clang%': 1, - # Path to sysroot dir. - 'sysroot%': '', - 'variables': { # The minimum OS X SDK version to use. 'mac_sdk_min%': '10.10', @@ -17,12 +14,16 @@ # Set NEON compilation flags. 'arm_neon%': 1, + + # Abosulte path to source root. + 'source_root%': '&2 + exit 1 +fi + +python2=$(which python2) +if [ ! -x "$python2" ] ; then + python2=python +fi + +rewrite=`dirname $0`/rewrite_dirs.py +package=${!#} + +libdir=$root/usr/$libpath/pkgconfig:$root/usr/share/pkgconfig + +set -e +# Some sysroots, like the Chromium OS ones, may generate paths that are not +# relative to the sysroot. For example, +# /path/to/chroot/build/x86-generic/usr/lib/pkgconfig/pkg.pc may have all paths +# relative to /path/to/chroot (i.e. prefix=/build/x86-generic/usr) instead of +# relative to /path/to/chroot/build/x86-generic (i.e prefix=/usr). +# To support this correctly, it's necessary to extract the prefix to strip from +# pkg-config's |prefix| variable. +prefix=`PKG_CONFIG_LIBDIR=$libdir pkg-config --variable=prefix "$package" | sed -e 's|/usr$||'` +result=`PKG_CONFIG_LIBDIR=$libdir pkg-config "$@"` +echo "$result"| $python2 $rewrite --sysroot "$root" --strip-prefix "$prefix" diff --git a/tools/linux/rewrite_dirs.py b/tools/linux/rewrite_dirs.py new file mode 100755 index 000000000000..30f22f0cd617 --- /dev/null +++ b/tools/linux/rewrite_dirs.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Rewrites paths in -I, -L and other option to be relative to a sysroot.""" + +import sys +import os +import optparse + +REWRITE_PREFIX = ['-I', + '-idirafter', + '-imacros', + '-imultilib', + '-include', + '-iprefix', + '-iquote', + '-isystem', + '-L'] + +def RewritePath(path, opts): + """Rewrites a path by stripping the prefix and prepending the sysroot.""" + sysroot = opts.sysroot + prefix = opts.strip_prefix + if os.path.isabs(path) and not path.startswith(sysroot): + if path.startswith(prefix): + path = path[len(prefix):] + path = path.lstrip('/') + return os.path.join(sysroot, path) + else: + return path + + +def RewriteLine(line, opts): + """Rewrites all the paths in recognized options.""" + args = line.split() + count = len(args) + i = 0 + while i < count: + for prefix in REWRITE_PREFIX: + # The option can be either in the form "-I /path/to/dir" or + # "-I/path/to/dir" so handle both. + if args[i] == prefix: + i += 1 + try: + args[i] = RewritePath(args[i], opts) + except IndexError: + sys.stderr.write('Missing argument following %s\n' % prefix) + break + elif args[i].startswith(prefix): + args[i] = prefix + RewritePath(args[i][len(prefix):], opts) + i += 1 + + return ' '.join(args) + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option('-s', '--sysroot', default='/', help='sysroot to prepend') + parser.add_option('-p', '--strip-prefix', default='', help='prefix to strip') + opts, args = parser.parse_args(argv[1:]) + + for line in sys.stdin.readlines(): + line = RewriteLine(line.strip(), opts) + print line + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/tools/win/register_msdia80_dll.js b/tools/win/register_msdia80_dll.js index 5691ef9caf5a..e90b9714d1f1 100644 --- a/tools/win/register_msdia80_dll.js +++ b/tools/win/register_msdia80_dll.js @@ -1,12 +1,12 @@ -var fs = require('fs'); -var path = require('path'); -var runas = require('runas'); +var fs = require('fs') +var path = require('path') +var runas = require('runas') -var source = path.resolve(__dirname, '..', '..', 'vendor', 'breakpad', 'msdia80.dll'); -var target = 'C:\\Program Files\\Common Files\\Microsoft Shared\\VC\\msdia80.dll'; -if (fs.existsSync(target)) - return; +var source = path.resolve(__dirname, '..', '..', 'vendor', 'breakpad', 'msdia80.dll') +var target = 'C:\\Program Files\\Common Files\\Microsoft Shared\\VC\\msdia80.dll' -runas('cmd', - ['/K', 'copy', source, target, '&', 'regsvr32', '/s', target, '&', 'exit'], - {admin: true}); +if (!fs.existsSync(target)) { + runas('cmd', + ['/K', 'copy', source, target, '&', 'regsvr32', '/s', target, '&', 'exit'], + {admin: true}) +} diff --git a/vendor/breakpad b/vendor/breakpad index 4ee7e1a703d0..c566c50d81f7 160000 --- a/vendor/breakpad +++ b/vendor/breakpad @@ -1 +1 @@ -Subproject commit 4ee7e1a703d066861b7bf6fce28526f8ed07dcd6 +Subproject commit c566c50d81f7b1edeaee9f11f5d07bda858d6b64 diff --git a/vendor/brightray b/vendor/brightray index d06de26dff8b..9ccab3c9a87c 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit d06de26dff8b641d9aee4c78ee830b416710f554 +Subproject commit 9ccab3c9a87cc57b4a37c4a03e17b589ac81d6ae diff --git a/vendor/crashpad b/vendor/crashpad index 5b777419c303..51f78aefa872 160000 --- a/vendor/crashpad +++ b/vendor/crashpad @@ -1 +1 @@ -Subproject commit 5b777419c303d8aa7930239d8ef755475f1ede57 +Subproject commit 51f78aefa872211022ae2d44260cbfd07a6aeb6a diff --git a/vendor/native_mate b/vendor/native_mate index e719eab878c2..4ad6ecd19617 160000 --- a/vendor/native_mate +++ b/vendor/native_mate @@ -1 +1 @@ -Subproject commit e719eab878c264bb03188d0cd6eb9ad6882bc13a +Subproject commit 4ad6ecd19617ac33c09e93ccb6d8e652ac1ac126 diff --git a/vendor/node b/vendor/node index a507a3c3816d..d4528c219df8 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit a507a3c3816d6ac085ed46250c489a3d76ab8b3c +Subproject commit d4528c219df8f442d769bae054883e1af79f105e