diff --git a/.circleci/config.yml b/.circleci/config.yml index 64821736d77..d4f98eab95b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2 jobs: electron-linux-arm: docker: - - image: electronbuilds/electron:0.0.3 + - image: electronbuilds/electron:0.0.4 environment: TARGET_ARCH: arm resource_class: 2xlarge @@ -58,9 +58,45 @@ jobs: else echo 'Skipping upload distribution because build is not for release' fi + - run: + name: Zip out directory + command: | + if [ "$ELECTRON_RELEASE" != "1" ]; then + zip -r electron.zip out/D + fi + - persist_to_workspace: + root: /home/builduser + paths: + - project/out + - store_artifacts: + path: electron.zip + electron-linux-arm-test: + machine: true + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - run: + name: Test in ARM docker container + command: | + if [ "$ELECTRON_RELEASE" != "1" ]; then + docker run --rm --privileged multiarch/qemu-user-static:register --reset + docker run -it \ + --mount type=bind,source=/tmp/workspace,target=/tmp/workspace \ + --rm electronbuilds/electronarm7:0.0.4 > version.txt + cat version.txt + if grep -q `script/get-version.py` version.txt; then + echo "Versions match" + else + echo "Versions do not match" + exit 1 + fi + else + echo "Skipping test for release build" + fi electron-linux-arm64: docker: - - image: electronbuilds/electron:0.0.3 + - image: electronbuilds/electron:0.0.4 environment: TARGET_ARCH: arm64 resource_class: 2xlarge @@ -115,14 +151,54 @@ jobs: else echo 'Skipping upload distribution because build is not for release' fi + - run: + name: Zip out directory + command: | + if [ "$ELECTRON_RELEASE" != "1" ]; then + zip -r electron.zip out/D + fi + - persist_to_workspace: + root: /home/builduser + paths: + - project/out + - store_artifacts: + path: electron.zip + electron-linux-arm64-test: + machine: true + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - run: + name: Test in ARM64 docker container + command: | + if [ "$ELECTRON_RELEASE" != "1" ]; then + docker run --rm --privileged multiarch/qemu-user-static:register --reset + docker run -it \ + --mount type=bind,source=/tmp/workspace,target=/tmp/workspace \ + --rm electronbuilds/electronarm64:0.0.5 > version.txt + cat version.txt + if grep -q `script/get-version.py` version.txt; then + echo "Versions match" + else + echo "Versions do not match" + exit 1 + fi + else + echo "Skipping test for release build" + fi electron-linux-ia32: docker: - - image: electronbuilds/electron:0.0.3 + - image: electronbuilds/electron:0.0.4 environment: TARGET_ARCH: ia32 + DISPLAY: ':99.0' resource_class: xlarge steps: - checkout + - run: + name: Setup for headless testing + command: sh -e /etc/init.d/xvfb start - run: name: Check for release command: | @@ -172,9 +248,32 @@ jobs: else echo 'Skipping upload distribution because build is not for release' fi + - run: + name: Test + environment: + MOCHA_FILE: junit/test-results.xml + MOCHA_REPORTER: mocha-junit-reporter + command: | + if [ "$ELECTRON_RELEASE" != "1" ]; then + echo 'Testing Electron debug build' + out/D/electron --version + mkdir junit + script/test.py --ci --rebuild_native_modules + else + echo 'Skipping testing on release build' + fi + - run: + name: Verify FFmpeg + command: | + if [ "$ELECTRON_RELEASE" != "1" ]; then + echo 'Verifying ffmpeg on debug build' + script/verify-ffmpeg.py + else + echo 'Skipping verify ffmpeg on release build' + fi electron-linux-mips64el: docker: - - image: electronbuilds/electron:0.0.3 + - image: electronbuilds/electron:0.0.4 environment: TARGET_ARCH: mips64el resource_class: xlarge @@ -232,7 +331,7 @@ jobs: electron-linux-x64: docker: - - image: electronbuilds/electron:0.0.3 + - image: electronbuilds/electron:0.0.4 environment: TARGET_ARCH: x64 DISPLAY: ':99.0' @@ -330,12 +429,18 @@ workflows: build-arm: jobs: - electron-linux-arm + - electron-linux-arm-test: + requires: + - electron-linux-arm build-arm64: jobs: - electron-linux-arm64 + - electron-linux-arm64-test: + requires: + - electron-linux-arm64 build-ia32: jobs: - electron-linux-ia32 build-x64: - jobs: - - electron-linux-x64 + jobs: + - electron-linux-x64 diff --git a/.dockerignore b/.dockerignore index 7307e769482..2e67a161a98 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,3 @@ * !tools/xvfb-init.sh +!tools/run-electron.sh diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..154052e6132 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/.github/config.yml b/.github/config.yml index 5c689ca5a67..9d1d5fefdc1 100644 --- a/.github/config.yml +++ b/.github/config.yml @@ -3,7 +3,7 @@ # Comment to be posted to on first time issues newIssueWelcomeComment: | ๐Ÿ‘‹ Thanks for opening your first issue here! If you're reporting a ๐Ÿž bug, please make sure you include steps to reproduce it. We get a lot of issues on this repo, so please be patient and we will get back to you as soon as we can. - + To help make it easier for us to investigate your issue, please follow the [contributing guidelines](https://github.com/electron/electron/blob/master/CONTRIBUTING.md#submitting-issues). # Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome @@ -11,7 +11,9 @@ newIssueWelcomeComment: | # Comment to be posted to on PRs from first time contributors in your repository newPRWelcomeComment: | ๐Ÿ’– Thanks for opening this pull request! ๐Ÿ’– - + + ![typing cat](https://user-images.githubusercontent.com/2289/36400158-2c7c589e-1584-11e8-81c7-bd34fd3c392b.gif) + Here is a list of things that will help get it across the finish line: - Follow the JavaScript, C++, and Python [coding style](https://github.com/electron/electron/blob/master/docs/development/coding-style.md). - Run `npm run lint` locally to catch formatting errors earlier. @@ -26,4 +28,4 @@ newPRWelcomeComment: | firstPRMergeComment: > Congrats on merging your first pull request! ๐ŸŽ‰๐ŸŽ‰๐ŸŽ‰ -# It is recommend to include as many gifs and emojis as possiblec +# It is recommend to include as many gifs and emojis as possible diff --git a/.gitignore b/.gitignore index eb045368b7a..415b319a383 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ .DS_Store .env .gclient_done -.npmrc +**/.npmrc .tags* .vs/ .vscode/ @@ -28,21 +28,18 @@ /external_binaries/ /out/ /vendor/.gclient -/vendor/debian_jessie_amd64-sysroot/ -/vendor/debian_jessie_arm-sysroot/ -/vendor/debian_jessie_arm64-sysroot/ -/vendor/debian_jessie_i386-sysroot/ /vendor/debian_jessie_mips64-sysroot/ -/vendor/debian_wheezy_amd64-sysroot/ -/vendor/debian_wheezy_arm-sysroot/ -/vendor/debian_wheezy_i386-sysroot/ +/vendor/debian_stretch_amd64-sysroot/ +/vendor/debian_stretch_arm-sysroot/ +/vendor/debian_stretch_arm64-sysroot/ +/vendor/debian_stretch_i386-sysroot/ /vendor/gcc-4.8.3-d197-n64-loongson/ /vendor/readme-gcc483-loongson.txt /vendor/download/ /vendor/llvm-build/ /vendor/llvm/ -/vendor/node/deps/node-inspect/.npmrc /vendor/npm/ /vendor/python_26/ node_modules/ SHASUMS256.txt +**/package-lock.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bff9a648f5b..5d04da05397 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,21 +10,15 @@ The following is a set of guidelines for contributing to Electron. These are just guidelines, not rules, use your best judgment and feel free to propose changes to this document in a pull request. -## Submitting Issues +## [Issues](https://electronjs.org/docs/development/issues) -### Creating Issues -* 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 - * The operating system you are using - * If applicable, what you were doing when the issue arose and what you - expected to happen -* Other things that will help resolve your issue: - * Screenshots and animated GIFs - * Error output that appears in your terminal, dev tools or as an alert - * Perform a [cursory search](https://github.com/electron/electron/issues?utf8=โœ“&q=is%3Aissue+) - to see if a similar issue has already been submitted +Issues are created [here](https://github.com/electron/electron/issues/new). + +* [How to Contribute in Issues](https://electronjs.org/docs/development/issues#how-to-contribute-in-issues) +* [Asking for General Help](https://electronjs.org/docs/development/issues#asking-for-general-help) +* [Submitting a Bug Report](https://electronjs.org/docs/development/issues#submitting-a-bug-report) +* [Triaging a Bug Report](https://electronjs.org/docs/development/issues#triaging-a-bug-report) +* [Resolving a Bug Report](https://electronjs.org/docs/development/issues#resolving-a-bug-report) ### Issue Maintenance and Closure * If an issue is inactive for 45 days (no activity of any kind), it will be @@ -34,54 +28,29 @@ the issue will be closed. * If an issue has been closed and you still feel it's relevant, feel free to ping a maintainer or add a comment! +## [Pull Requests](https://electronjs.org/docs/development/pull-requests) -## Submitting Pull Requests +Pull Requests are the way concrete changes are made to the code, documentation, +dependencies, and tools contained in the `electron/electron` repository. -* Include screenshots and animated GIFs in your pull request whenever possible. -* 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). +* [Setting up your local environment](https://electronjs.org/docs/development/pull-requests#setting-up-your-local-environment) + * [Step 1: Fork](https://electronjs.org/docs/development/pull-requests#step-1-fork) + * [Step 2: Build](https://electronjs.org/docs/development/pull-requests#step-2-build) + * [Step 3: Branch](https://electronjs.org/docs/development/pull-requests#step-3-branch) +* [The Process of Making Changes](https://electronjs.org/docs/development/pull-requests#the-process-of-making-changes) + * [Step 4: Code](https://electronjs.org/docs/development/pull-requests#step-4-code) + * [Step 5: Commit](https://electronjs.org/docs/development/pull-requests#step-5-commit) + * [Commit message guidelines](https://electronjs.org/docs/development/pull-requests#commit-message-guidelines) + * [Step 6: Rebase](https://electronjs.org/docs/development/pull-requests#step-6-rebase) + * [Step 7: Test](https://electronjs.org/docs/development/pull-requests#step-7-test) + * [Step 8: Push](https://electronjs.org/docs/development/pull-requests#step-8-push) + * [Step 8: Opening the Pull Request](https://electronjs.org/docs/development/pull-requests#step-8-opening-the-pull-request) + * [Step 9: Discuss and Update](#step-9-discuss-and-update) + * [Approval and Request Changes Workflow](https://electronjs.org/docs/development/pull-requests#approval-and-request-changes-workflow) + * [Step 10: Landing](https://electronjs.org/docs/development/pull-requests#step-10-landing) + * [Continuous Integration Testing](https://electronjs.org/docs/development/pull-requests#continuous-integration-testing) -## Styleguides +## Style Guides -### General Code +See [Coding Style](https://electronjs.org/docs/development/coding-style) for information about which standards Electron adheres to in different parts of its codebase. -* End files with a newline. -* Place requires in the following order: - * Built in Node Modules (such as `path`) - * Built in Electron Modules (such as `ipc`, `app`) - * Local Modules (using relative paths) -* Place class properties in the following order: - * Class methods and properties (methods starting with a `@`) - * Instance methods and properties -* Avoid platform-dependent code: - * Use `path.join()` to concatenate filenames. - * Use `os.tmpdir()` rather than `/tmp` when you need to reference the - temporary directory. -* Using a plain `return` when returning explicitly at the end of a function. - * Not `return null`, `return undefined`, `null`, or `undefined` - -### Git Commit Messages - -* Use the present tense ("Add feature" not "Added feature") -* Use the imperative mood ("Move cursor to..." not "Moves cursor to...") -* Limit the first line to 72 characters or less -* Reference issues and pull requests liberally -* When only changing documentation, include `[ci skip]` in the commit description -* Consider starting the commit message with an applicable emoji: - * :art: `:art:` when improving the format/structure of the code - * :racehorse: `:racehorse:` when improving performance - * :non-potable_water: `:non-potable_water:` when plugging memory leaks - * :memo: `:memo:` when writing docs - * :penguin: `:penguin:` when fixing something on Linux - * :apple: `:apple:` when fixing something on macOS - * :checkered_flag: `:checkered_flag:` when fixing something on Windows - * :bug: `:bug:` when fixing a bug - * :fire: `:fire:` when removing code or files - * :green_heart: `:green_heart:` when fixing the CI build - * :white_check_mark: `:white_check_mark:` when adding tests - * :lock: `:lock:` when dealing with security - * :arrow_up: `:arrow_up:` when upgrading dependencies - * :arrow_down: `:arrow_down:` when downgrading dependencies - * :shirt: `:shirt:` when removing linter warnings diff --git a/Dockerfile b/Dockerfile index 58da91e1250..4cfea0e70f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,10 +8,13 @@ RUN chmod a+rwx /home # Install node.js RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - -RUN apt-get update && apt-get install -y --force-yes nodejs +RUN apt-get update && apt-get install -y nodejs # Install wget used by crash reporter -RUN apt-get install -y --force-yes wget +RUN apt-get install -y wget + +# Install python-dbusmock +RUN apt-get install -y python-dbusmock # Add xvfb init script ADD tools/xvfb-init.sh /etc/init.d/xvfb diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 new file mode 100644 index 00000000000..30e31019e8b --- /dev/null +++ b/Dockerfile.arm64 @@ -0,0 +1,34 @@ +FROM multiarch/debian-debootstrap:arm64-jessie + +RUN apt-get update && apt-get install -y\ + bison \ + build-essential \ + clang \ + curl \ + gperf \ + libasound2 \ + libasound2-dev \ + libcap-dev \ + libcups2-dev \ + libdbus-1-dev \ + libgconf-2-4 \ + libgconf2-dev \ + libgnome-keyring-dev \ + libgtk-3-0 \ + libgtk-3-dev \ + libnotify-dev \ + libnss3 \ + libnss3-dev \ + libx11-xcb-dev \ + libxss1 \ + libxtst-dev \ + libxtst6 \ + python-dbusmock \ + wget \ + xvfb + +ADD tools/xvfb-init.sh /etc/init.d/xvfb +RUN chmod a+x /etc/init.d/xvfb +ADD tools/run-electron.sh /run-electron.sh +RUN chmod a+x /run-electron.sh +CMD sh /run-electron.sh diff --git a/Dockerfile.armv7 b/Dockerfile.armv7 new file mode 100644 index 00000000000..bfbc6fb6806 --- /dev/null +++ b/Dockerfile.armv7 @@ -0,0 +1,40 @@ +FROM multiarch/debian-debootstrap:armhf-jessie + +RUN apt-get update && apt-get install -y\ + bison \ + build-essential \ + clang \ + curl \ + gperf \ + libasound2 \ + libasound2-dev \ + libcap-dev \ + libcups2-dev \ + libdbus-1-dev \ + libgconf-2-4 \ + libgconf2-dev \ + libgnome-keyring-dev \ + libgtk-3-0 \ + libgtk-3-dev \ + libnotify-dev \ + libnss3 \ + libnss3-dev \ + libx11-xcb-dev \ + libxss1 \ + libxtst-dev \ + libxtst6 \ + python-dbusmock \ + git \ + wget \ + xvfb + +# Install node.js +RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - +RUN apt-get update && apt-get install -y nodejs + +ADD tools/xvfb-init.sh /etc/init.d/xvfb +RUN chmod a+x /etc/init.d/xvfb +ADD tools/run-electron.sh /run-electron.sh +RUN chmod a+x /run-electron.sh + +CMD sh /run-electron.sh diff --git a/Dockerfile.circleci b/Dockerfile.circleci index 3c3a610ecab..271947c56e3 100644 --- a/Dockerfile.circleci +++ b/Dockerfile.circleci @@ -4,10 +4,13 @@ USER root # Install node.js RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - -RUN apt-get update && apt-get install -y --force-yes nodejs +RUN apt-get update && apt-get install -y nodejs # Install wget used by crash reporter -RUN apt-get install -y --force-yes wget +RUN apt-get install -y wget + +# Install python-dbusmock +RUN apt-get install -y python-dbusmock # Add xvfb init script ADD tools/xvfb-init.sh /etc/init.d/xvfb diff --git a/Jenkinsfile b/Jenkinsfile index 51ad1b492dc..bf0200b1bcf 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,10 +8,12 @@ pipeline { label 'osx' } steps { - sh 'script/bootstrap.py --target_arch=x64 --dev' - sh 'npm run lint' - sh 'script/build.py -c D' - sh 'script/test.py --ci --rebuild_native_modules' + timeout(60) { + sh 'script/bootstrap.py --target_arch=x64 --dev' + sh 'npm run lint' + sh 'script/build.py -c D' + sh 'script/test.py --ci --rebuild_native_modules' + } } post { always { @@ -27,10 +29,12 @@ pipeline { MAS_BUILD = '1' } steps { - sh 'script/bootstrap.py --target_arch=x64 --dev' - sh 'npm run lint' - sh 'script/build.py -c D' - sh 'script/test.py --ci --rebuild_native_modules' + timeout(60) { + sh 'script/bootstrap.py --target_arch=x64 --dev' + sh 'npm run lint' + sh 'script/build.py -c D' + sh 'script/test.py --ci --rebuild_native_modules' + } } post { always { diff --git a/LICENSE b/LICENSE index 38f215c3033..7a40eacafdf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013-2017 GitHub Inc. +Copyright (c) 2013-2018 GitHub Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index d9ad098a6ac..e5c1601d042 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ [![Electron Logo](https://electronjs.org/images/electron-logo.svg)](https://electronjs.org) -[![Travis Build Status](https://travis-ci.org/electron/electron.svg?branch=master)](https://travis-ci.org/electron/electron) -[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/bc56v83355fi3369/branch/master?svg=true)](https://ci.appveyor.com/project/electron-bot/electron/branch/master) + +[![CircleCI Build Status](https://circleci.com/gh/electron/electron/tree/master.svg?style=shield)](https://circleci.com/gh/electron/electron/tree/master) +[![AppVeyor Build Status](https://windows-ci.electronjs.org/api/projects/status/nilyf07hcef14dvj/branch/master?svg=true)](https://windows-ci.electronjs.org/project/AppVeyor/electron/branch/master) +[![Jenkins Build Status](https://mac-ci.electronjs.org/buildStatus/icon?job=Electron%20org/electron/master)](https://mac-ci.electronjs.org/blue/organizations/jenkins/Electron%20org%2Felectron/activity?branch=master) [![devDependency Status](https://david-dm.org/electron/electron/dev-status.svg)](https://david-dm.org/electron/electron?type=dev) [![Join the Electron Community on Slack](https://atom-slack.herokuapp.com/badge.svg)](https://atom-slack.herokuapp.com/) :memo: Available Translations: ๐Ÿ‡จ๐Ÿ‡ณ ๐Ÿ‡น๐Ÿ‡ผ ๐Ÿ‡ง๐Ÿ‡ท ๐Ÿ‡ช๐Ÿ‡ธ ๐Ÿ‡ฐ๐Ÿ‡ท ๐Ÿ‡ฏ๐Ÿ‡ต ๐Ÿ‡ท๐Ÿ‡บ ๐Ÿ‡ซ๐Ÿ‡ท ๐Ÿ‡น๐Ÿ‡ญ ๐Ÿ‡ณ๐Ÿ‡ฑ ๐Ÿ‡น๐Ÿ‡ท ๐Ÿ‡ฎ๐Ÿ‡ฉ ๐Ÿ‡บ๐Ÿ‡ฆ ๐Ÿ‡จ๐Ÿ‡ฟ ๐Ÿ‡ฎ๐Ÿ‡น. -View these docs in other languages at [electron/electron-i18n](https://github.com/electron/electron-i18n/tree/master/content/). +View these docs in other languages at [electron/i18n](https://github.com/electron/i18n/tree/master/content/). 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 @@ -33,10 +35,10 @@ npm install electron --save-dev --save-exact The `--save-exact` flag is recommended as Electron does not follow semantic versioning. For info on how to manage Electron versions in your apps, see -[Electron versioning](https://electronjs.org/docs/tutorial/electron-versioning). +[Electron versioning](docs/tutorial/electron-versioning.md). For more installation options and troubleshooting tips, see -[installation](https://electronjs.org/docs/tutorial/installation). +[installation](docs/tutorial/installation.md). ## Quick start @@ -83,7 +85,7 @@ const child = proc.spawn(electron) ## Documentation Translations -Find documentation translations in [electron/electron-i18n](https://github.com/electron/electron-i18n). +Find documentation translations in [electron/i18n](https://github.com/electron/i18n). ## Community @@ -99,6 +101,7 @@ forums - [`electron-jp`](https://electron-jp.slack.com) *(Japanese)* - [`electron-tr`](https://electron-tr.herokuapp.com) *(Turkish)* - [`electron-id`](https://electron-id.slack.com) *(Indonesia)* +- [`electron-pl`](https://electronpl.github.io) *(Poland)* Check out [awesome-electron](https://github.com/sindresorhus/awesome-electron) for a community maintained list of useful example apps, tools and resources. diff --git a/SECURITY.md b/SECURITY.md index ff2f1018423..edea5ffeb02 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -7,3 +7,6 @@ To report a security issue, email [electron@github.com](mailto:electron@github.c The Electron team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. Report security bugs in third-party modules to the person or team maintaining the module. You can also report a vulnerability through the [Node Security Project](https://nodesecurity.io/report). + +## Learning More About Security +To learn more about securing an Electron application, please see the [security tutorial](docs/tutorial/security.md). diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index ce0baaf989b..00000000000 --- a/appveyor.yml +++ /dev/null @@ -1,25 +0,0 @@ -# appveyor file -# http://www.appveyor.com/docs/appveyor-yml -version: "{build}" - -os: Visual Studio 2015 - -init: - - git config --global core.autocrlf input - -platform: - - x86 - - x64 - -install: - - cmd: SET PATH=C:\Program Files (x86)\MSBuild\14.0\bin\;%PATH% - - cmd: SET PATH=C:\python27;%PATH% - - cmd: python script/cibuild - -branches: - only: - - master - -# disable build and test phases -build: off -test: off diff --git a/atom/app/atom_content_client.cc b/atom/app/atom_content_client.cc index 09683211c58..0cc22bec2c1 100644 --- a/atom/app/atom_content_client.cc +++ b/atom/app/atom_content_client.cc @@ -19,13 +19,14 @@ #include "content/public/common/content_constants.h" #include "content/public/common/pepper_plugin_info.h" #include "content/public/common/user_agent.h" +#include "media/media_features.h" #include "pdf/pdf.h" #include "ppapi/shared_impl/ppapi_permissions.h" #include "third_party/widevine/cdm/stub/widevine_cdm_version.h" #include "ui/base/l10n/l10n_util.h" #include "url/url_constants.h" -#if defined(WIDEVINE_CDM_AVAILABLE) && BUILDFLAG(ENABLE_PEPPER_CDMS) +#if defined(WIDEVINE_CDM_AVAILABLE) && BUILDFLAG(ENABLE_LIBRARY_CDMS) #include "chrome/common/widevine_cdm_constants.h" #endif @@ -73,7 +74,7 @@ content::PepperPluginInfo CreatePepperFlashInfo(const base::FilePath& path, return plugin; } -#if defined(WIDEVINE_CDM_AVAILABLE) && BUILDFLAG(ENABLE_PEPPER_CDMS) +#if defined(WIDEVINE_CDM_AVAILABLE) && BUILDFLAG(ENABLE_LIBRARY_CDMS) content::PepperPluginInfo CreateWidevineCdmInfo(const base::FilePath& path, const std::string& version) { content::PepperPluginInfo widevine_cdm; @@ -108,7 +109,7 @@ content::PepperPluginInfo CreateWidevineCdmInfo(const base::FilePath& path, return widevine_cdm; } -#endif +#endif // defined(WIDEVINE_CDM_AVAILABLE) && BUILDFLAG(ENABLE_LIBRARY_CDMS) void ComputeBuiltInPlugins(std::vector* plugins) { content::PepperPluginInfo pdf_info; @@ -156,7 +157,7 @@ void AddPepperFlashFromCommandLine( plugins->push_back(CreatePepperFlashInfo(flash_path, flash_version)); } -#if defined(WIDEVINE_CDM_AVAILABLE) && BUILDFLAG(ENABLE_PEPPER_CDMS) +#if defined(WIDEVINE_CDM_AVAILABLE) && BUILDFLAG(ENABLE_LIBRARY_CDMS) void AddWidevineCdmFromCommandLine( std::vector* plugins) { auto command_line = base::CommandLine::ForCurrentProcess(); @@ -176,7 +177,7 @@ void AddWidevineCdmFromCommandLine( plugins->push_back(CreateWidevineCdmInfo(widevine_cdm_path, widevine_cdm_version)); } -#endif +#endif // defined(WIDEVINE_CDM_AVAILABLE) && BUILDFLAG(ENABLE_LIBRARY_CDMS) AtomContentClient::AtomContentClient() { } @@ -216,9 +217,9 @@ void AtomContentClient::AddAdditionalSchemes(Schemes* schemes) { void AtomContentClient::AddPepperPlugins( std::vector* plugins) { AddPepperFlashFromCommandLine(plugins); -#if defined(WIDEVINE_CDM_AVAILABLE) && BUILDFLAG(ENABLE_PEPPER_CDMS) +#if defined(WIDEVINE_CDM_AVAILABLE) && BUILDFLAG(ENABLE_LIBRARY_CDMS) AddWidevineCdmFromCommandLine(plugins); -#endif +#endif // defined(WIDEVINE_CDM_AVAILABLE) && BUILDFLAG(ENABLE_LIBRARY_CDMS) ComputeBuiltInPlugins(plugins); } diff --git a/atom/app/atom_library_main.h b/atom/app/atom_library_main.h index e1603fa5942..63d285bb5e4 100644 --- a/atom/app/atom_library_main.h +++ b/atom/app/atom_library_main.h @@ -10,7 +10,7 @@ #if defined(OS_MACOSX) extern "C" { __attribute__((visibility("default"))) -int AtomMain(int argc, const char* argv[]); +int AtomMain(int argc, char* argv[]); __attribute__((visibility("default"))) int AtomInitializeICUandStartNode(int argc, char *argv[]); diff --git a/atom/app/atom_library_main.mm b/atom/app/atom_library_main.mm index 7ee75229346..ae096acd4a9 100644 --- a/atom/app/atom_library_main.mm +++ b/atom/app/atom_library_main.mm @@ -15,11 +15,11 @@ #include "content/public/app/content_main.h" #if defined(OS_MACOSX) -int AtomMain(int argc, const char* argv[]) { +int AtomMain(int argc, char* argv[]) { atom::AtomMainDelegate delegate; content::ContentMainParams params(&delegate); params.argc = argc; - params.argv = argv; + params.argv = const_cast(argv); atom::AtomCommandLine::Init(argc, argv); return content::ContentMain(params); } diff --git a/atom/app/atom_main.cc b/atom/app/atom_main.cc index fa854ca1637..1f702cac459 100644 --- a/atom/app/atom_main.cc +++ b/atom/app/atom_main.cc @@ -4,7 +4,8 @@ #include "atom/app/atom_main.h" -#include +#include +#include #if defined(OS_WIN) #include // windows.h must be included first @@ -15,9 +16,11 @@ #include #include "atom/app/atom_main_delegate.h" +#include "atom/app/command_line_args.h" #include "atom/common/crash_reporter/win/crash_service_main.h" #include "base/environment.h" #include "base/process/launch.h" +#include "base/strings/utf_string_conversions.h" #include "base/win/windows_version.h" #include "content/public/app/sandbox_helper_win.h" #include "sandbox/win/src/sandbox_types.h" @@ -35,7 +38,9 @@ namespace { +#ifdef ENABLE_RUN_AS_NODE const auto kRunAsNode = "ELECTRON_RUN_AS_NODE"; +#endif bool IsEnvSet(const char* name) { #if defined(OS_WIN) @@ -52,18 +57,23 @@ bool IsEnvSet(const char* name) { #if defined(OS_WIN) int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { - int argc = 0; - wchar_t** wargv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + struct Arguments { + int argc = 0; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); - bool run_as_node = IsEnvSet(kRunAsNode); + ~Arguments() { LocalFree(argv); } + } arguments; + + if (!arguments.argv) + return -1; #ifdef _DEBUG // Don't display assert dialog boxes in CI test runs static const auto kCI = "ELECTRON_CI"; bool is_ci = IsEnvSet(kCI); if (!is_ci) { - for (int i = 0; i < argc; ++i) { - if (!_wcsicmp(wargv[i], L"--ci")) { + for (int i = 0; i < arguments.argc; ++i) { + if (!_wcsicmp(arguments.argv[i], L"--ci")) { is_ci = true; _putenv_s(kCI, "1"); // set flag for child processes break; @@ -81,44 +91,16 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { } #endif +#ifdef ENABLE_RUN_AS_NODE + bool run_as_node = IsEnvSet(kRunAsNode); +#else + bool run_as_node = false; +#endif + // 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]; - for (int i = 0; i < argc; i++) { - // Compute the size of the required buffer - DWORD size = WideCharToMultiByte(CP_UTF8, - 0, - wargv[i], - -1, - NULL, - 0, - NULL, - NULL); - if (size == 0) { - // This should never happen. - fprintf(stderr, "Could not convert arguments to utf8."); - exit(1); - } - // Do the actual conversion - argv[i] = new char[size]; - DWORD result = WideCharToMultiByte(CP_UTF8, - 0, - wargv[i], - -1, - argv[i], - size, - NULL, - NULL); - if (result == 0) { - // This should never happen. - fprintf(stderr, "Could not convert arguments to utf8."); - exit(1); - } - } - #ifndef DEBUG // Chromium has its own TLS subsystem which supports automatic destruction // of thread-local data, and also depends on memory allocation routines @@ -138,15 +120,28 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { }); #endif +#ifdef ENABLE_RUN_AS_NODE if (run_as_node) { - // Now that argv conversion is done, we can finally start. + std::vector argv(arguments.argc); + std::transform( + arguments.argv, arguments.argv + arguments.argc, argv.begin(), + [](auto& a) { return _strdup(base::WideToUTF8(a).c_str()); }); + base::AtExitManager atexit_manager; base::i18n::InitializeICU(); - return atom::NodeMain(argc, argv); - } else if (IsEnvSet("ELECTRON_INTERNAL_CRASH_SERVICE")) { + auto ret = atom::NodeMain(argv.size(), argv.data()); + std::for_each(argv.begin(), argv.end(), free); + return ret; + } +#endif + + if (IsEnvSet("ELECTRON_INTERNAL_CRASH_SERVICE")) { return crash_service::Main(cmd); } + if (!atom::CheckCommandLineArguments(arguments.argc, arguments.argv)) + return -1; + sandbox::SandboxInterfaceInfo sandbox_info = {0}; content::InitializeSandboxInfo(&sandbox_info); atom::AtomMainDelegate delegate; @@ -154,34 +149,37 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { content::ContentMainParams params(&delegate); params.instance = instance; params.sandbox_info = &sandbox_info; - atom::AtomCommandLine::Init(argc, argv); - atom::AtomCommandLine::InitW(argc, wargv); + atom::AtomCommandLine::Init(arguments.argc, arguments.argv); return content::ContentMain(params); } #elif defined(OS_LINUX) // defined(OS_WIN) -int main(int argc, const char* argv[]) { +int main(int argc, char* argv[]) { +#ifdef ENABLE_RUN_AS_NODE if (IsEnvSet(kRunAsNode)) { base::i18n::InitializeICU(); base::AtExitManager atexit_manager; - return atom::NodeMain(argc, const_cast(argv)); + return atom::NodeMain(argc, argv); } +#endif atom::AtomMainDelegate delegate; content::ContentMainParams params(&delegate); params.argc = argc; - params.argv = argv; + params.argv = const_cast(argv); atom::AtomCommandLine::Init(argc, argv); return content::ContentMain(params); } #else // defined(OS_LINUX) -int main(int argc, const char* argv[]) { +int main(int argc, char* argv[]) { +#ifdef ENABLE_RUN_AS_NODE if (IsEnvSet(kRunAsNode)) { - return AtomInitializeICUandStartNode(argc, const_cast(argv)); + return AtomInitializeICUandStartNode(argc, argv); } +#endif return AtomMain(argc, argv); } diff --git a/atom/app/atom_main_delegate.cc b/atom/app/atom_main_delegate.cc index fdf76f3cb29..929b641d446 100644 --- a/atom/app/atom_main_delegate.cc +++ b/atom/app/atom_main_delegate.cc @@ -21,9 +21,18 @@ #include "base/logging.h" #include "chrome/common/chrome_paths.h" #include "content/public/common/content_switches.h" +#include "ipc/ipc_features.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" +#if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED) +#define IPC_MESSAGE_MACROS_LOG_ENABLED +#include "content/public/common/content_ipc_logging.h" +#define IPC_LOG_TABLE_ADD_ENTRY(msg_id, logger) \ + content::RegisterIPCLogger(msg_id, logger) +#include "atom/common/common_message_generator.h" +#endif + namespace atom { namespace { @@ -86,9 +95,15 @@ bool AtomMainDelegate::BasicStartupComplete(int* exit_code) { logging::SetLogItems(true, false, true, false); // Enable convient stack printing. - bool enable_stack_dumping = env->HasVar("ELECTRON_ENABLE_STACK_DUMPING"); #if defined(DEBUG) && defined(OS_LINUX) - enable_stack_dumping = true; + bool enable_stack_dumping = true; +#else + bool enable_stack_dumping = env->HasVar("ELECTRON_ENABLE_STACK_DUMPING"); +#endif +#if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS) + // For 32bit ARM enabling stack printing would end up crashing. + // https://github.com/electron/electron/pull/11230#issuecomment-363232482 + enable_stack_dumping = false; #endif if (enable_stack_dumping) base::debug::EnableInProcessStackDumping(); diff --git a/atom/app/atom_main_delegate.h b/atom/app/atom_main_delegate.h index 64207ae7925..03256bc9f93 100644 --- a/atom/app/atom_main_delegate.h +++ b/atom/app/atom_main_delegate.h @@ -44,7 +44,6 @@ class AtomMainDelegate : public brightray::MainDelegate { void SetUpBundleOverrides(); #endif - brightray::ContentClient content_client_; std::unique_ptr browser_client_; std::unique_ptr renderer_client_; std::unique_ptr utility_client_; diff --git a/atom/app/command_line_args.cc b/atom/app/command_line_args.cc new file mode 100644 index 00000000000..9472d1cb9fd --- /dev/null +++ b/atom/app/command_line_args.cc @@ -0,0 +1,1427 @@ +// Copyright (c) 2018 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/app/command_line_args.h" + +#include +#include + +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "content/public/common/content_switches.h" + +namespace { + +bool IsUrlArg(const base::CommandLine::CharType* arg) { + // the first character must be a letter for this to be a URL + auto c = *arg; + if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')) { + for (auto p = arg + 1; *p; ++p) { + c = *p; + + // colon indicates that the argument starts with a URI scheme + if (c == ':') { + // it could also be a Windows filesystem path + if (p == arg + 1) + break; + + return true; + } + + // white-space before a colon means it's not a URL + if (c == ' ' || (0x9 <= c && c <= 0xD)) + break; + } + } + + return false; +} + +/* + * The blacklist of command line switches, must be sorted. + * Update the list by pasting the following command into bash + * in libchromiumcontent/src/: + + (find ./ -name "*switches.cc" | \ + xargs grep -P --no-filename "\"\S+\";" | \ + perl -pe 's|^.*?"(\S+)";| "$1",|'; \ + echo ' "inspect",'; \ + echo ' "inspect-brk",') | \ + LANG=C sort | \ + uniq > blacklist-switches.txt + + */ +const char* kBlacklist[] = { + "/prefetch:1", + "/prefetch:2", + "/prefetch:3", + "/prefetch:4", + "/prefetch:5", + "/prefetch:6", + "/prefetch:8", + "0", + "?", + "ChromeOSMemoryPressureHandling", + "SafeSites", + "accept-resource-provider", + "account-consistency", + "adaboost", + "aec-refined-adaptive-filter", + "agc-startup-min-volume", + "aggressive", + "aggressive-cache-discard", + "aggressive-tab-discard", + "all", + "all-toolchains", + "allarticles", + "allow-cross-origin-auth-prompt", + "allow-external-pages", + "allow-failed-policy-fetch-for-test", + "allow-file-access-from-files", + "allow-hidden-media-playback", + "allow-http-background-page", + "allow-http-screen-capture", + "allow-insecure-localhost", + "allow-legacy-extension-manifests", + "allow-loopback-in-peer-connection", + "allow-nacl-crxfs-api", + "allow-nacl-file-handle-api", + "allow-nacl-socket-api", + "allow-no-sandbox-job", + "allow-outdated-plugins", + "allow-ra-in-dev-mode", + "allow-running-insecure-content", + "allow-sandbox-debugging", + "allow-silent-push", + "alsa-check-close-timeout", + "alsa-enable-upsampling", + "alsa-fixed-output-sample-rate", + "alsa-input-device", + "alsa-mute-device-name", + "alsa-mute-element-name", + "alsa-output-avail-min", + "alsa-output-buffer-size", + "alsa-output-device", + "alsa-output-period-size", + "alsa-output-start-threshold", + "alsa-volume-device-name", + "alsa-volume-element-name", + "also-emit-success-logs", + "alternative", + "always-authorize-plugins", + "always-on", + "always-use-complex-text", + "alwaystrue", + "amd-switchable", + "android-fonts-path", + "android-stderr-port", + "android-stdin-port", + "android-stdout-port", + "angle", + "app", + "app-auto-launched", + "app-id", + "app-mode-auth-code", + "app-mode-oauth-token", + "app-mode-oem-manifest", + "app-shell-allow-roaming", + "app-shell-host-window-size", + "app-shell-preferred-network", + "app-shell-refresh-token", + "app-shell-user", + "apple", + "apps-gallery-download-url", + "apps-gallery-update-url", + "apps-gallery-url", + "apps-keep-chrome-alive-in-tests", + "arc-availability", + "arc-available", + "arc-start-mode", + "arc-transition-migration-required", + "args", + "artifacts-dir", + "ash-constrain-pointer-to-root", + "ash-debug-shortcuts", + "ash-dev-shortcuts", + "ash-disable-smooth-screen-rotation", + "ash-disable-tablet-autohide-titlebars", + "ash-disable-touch-exploration-mode", + "ash-enable-magnifier-key-scroller", + "ash-enable-mirrored-screen", + "ash-enable-night-light", + "ash-enable-palette-on-all-displays", + "ash-enable-scale-settings-tray", + "ash-enable-software-mirroring", + "ash-enable-unified-desktop", + "ash-estimated-presentation-delay", + "ash-hide-notifications-for-factory", + "ash-host-window-bounds", + "ash-shelf-color", + "ash-shelf-color-scheme", + "ash-touch-hud", + "ash-webui-init", + "attestation-server", + "audio-buffer-size", + "audio-output-channels", + "aura-legacy-power-button", + "auth-ext-path", + "auth-server-whitelist", + "auth-spnego-account-type", + "auto", + "auto-open-devtools-for-tabs", + "auto-select-desktop-capture-source", + "autoplay-policy", + "blink-settings", + "bootstrap", + "browser", + "browser-startup-dialog", + "browser-subprocess-path", + "browser-test", + "bwsi", + "bypass-app-banner-engagement-checks", + "canvas-msaa-sample-count", + "cast-initial-screen-height", + "cast-initial-screen-width", + "cc-layer-tree-test-long-timeout", + "cc-layer-tree-test-no-timeout", + "cc-rebaseline-pixeltests", + "cellular-first", + "cellular-only", + "check-for-update-interval", + "check-layout-test-sys-deps", + "child-wallpaper-large", + "child-wallpaper-small", + "chrome-home-swipe-logic", + "cipher-suite-blacklist", + "clamshell", + "class", + "clear-token-service", + "cloud-print-file", + "cloud-print-file-type", + "cloud-print-job-title", + "cloud-print-print-ticket", + "cloud-print-setup-proxy", + "cloud-print-url", + "cloud-print-xmpp-endpoint", + "color", + "compensate-for-unstable-pinch-zoom", + "compile-shader-always-succeeds", + "component-updater", + "connectivity-check-url", + "conservative", + "content-image-texture-target", + "content-shell-host-window-size", + "controller", + "crash-dumps-dir", + "crash-on-failure", + "crash-on-hang-threads", + "crash-server-url", + "crash-test", + "crashpad-handler", + "create-browser-on-startup-for-tests", + "cros-gaia-api-v1", + "cros-region", + "cros-regions-mode", + "crosh-command", + "cryptauth-http-host", + "custom-devtools-frontend", + "custom-launcher-page", + "custom_summary", + "d3d-support", + "d3d11", + "d3d9", + "daemon", + "dark_muted", + "dark_vibrant", + "data-path", + "data-reduction-proxy-config-url", + "data-reduction-proxy-experiment", + "data-reduction-proxy-http-proxies", + "data-reduction-proxy-lo-fi", + "data-reduction-proxy-pingback-url", + "data-reduction-proxy-secure-proxy-check-url", + "data-reduction-proxy-server-experiments-disabled", + "dbus-stub", + "debug-devtools", + "debug-enable-frame-toggle", + "debug-packed-apps", + "debug-print", + "default", + "default-background-color", + "default-tile-height", + "default-tile-width", + "default-wallpaper-is-oem", + "default-wallpaper-large", + "default-wallpaper-small", + "demo", + "derelict-detection-timeout", + "derelict-idle-timeout", + "desktop", + "desktop-window-1080p", + "deterministic-fetch", + "device-management-url", + "device-scale-factor", + "devtools-flags", + "diagnostics", + "diagnostics-format", + "diagnostics-recovery", + "dice", + "dice_fix_auth_errors", + "disable", + "disable-2d-canvas-clip-aa", + "disable-2d-canvas-image-chromium", + "disable-3d-apis", + "disable-accelerated-2d-canvas", + "disable-accelerated-jpeg-decoding", + "disable-accelerated-mjpeg-decode", + "disable-accelerated-video-decode", + "disable-app-info-dialog-mac", + "disable-app-list-dismiss-on-blur", + "disable-app-window-cycling", + "disable-appcontainer", + "disable-arc-data-wipe", + "disable-arc-opt-in-verification", + "disable-audio-support-for-desktop-share", + "disable-avfoundation-overlays", + "disable-background-networking", + "disable-background-timer-throttling", + "disable-backgrounding-occluded-windows", + "disable-backing-store-limit", + "disable-blink-features", + "disable-boot-animation", + "disable-breakpad", + "disable-browser-task-scheduler", + "disable-bundled-ppapi-flash", + "disable-canvas-aa", + "disable-captive-portal-bypass-proxy", + "disable-cast-streaming-hw-encoding", + "disable-checker-imaging", + "disable-clear-browsing-data-counters", + "disable-client-side-phishing-detection", + "disable-cloud-import", + "disable-component-cloud-policy", + "disable-component-extensions-with-background-pages", + "disable-component-update", + "disable-composited-antialiasing", + "disable-contextual-search", + "disable-d3d11", + "disable-databases", + "disable-datasaver-prompt", + "disable-default-apps", + "disable-demo-mode", + "disable-device-disabling", + "disable-device-discovery-notifications", + "disable-dinosaur-easter-egg", + "disable-direct-composition", + "disable-direct-composition-layers", + "disable-directwrite-for-ui", + "disable-display-list-2d-canvas", + "disable-distance-field-text", + "disable-domain-blocking-for-3d-apis", + "disable-domain-reliability", + "disable-drive-search-in-app-launcher", + "disable-dwm-composition", + "disable-encryption-migration", + "disable-eol-notification", + "disable-es3-apis", + "disable-es3-gl-context", + "disable-extensions", + "disable-extensions-except", + "disable-extensions-file-access-check", + "disable-extensions-http-throttling", + "disable-features", + "disable-field-trial-config", + "disable-file-manager-touch-mode", + "disable-file-system", + "disable-flash-3d", + "disable-flash-stage3d", + "disable-fullscreen-low-power-mode", + "disable-fullscreen-tab-detaching", + "disable-gaia-services", + "disable-gesture-editing", + "disable-gesture-requirement-for-presentation", + "disable-gesture-typing", + "disable-gl-drawing-for-tests", + "disable-gl-error-limit", + "disable-gl-extensions", + "disable-glsl-translator", + "disable-gpu", + "disable-gpu-compositing", + "disable-gpu-driver-bug-workarounds", + "disable-gpu-early-init", + "disable-gpu-memory-buffer-compositor-resources", + "disable-gpu-memory-buffer-video-frames", + "disable-gpu-process-crash-limit", + "disable-gpu-program-cache", + "disable-gpu-rasterization", + "disable-gpu-sandbox", + "disable-gpu-shader-disk-cache", + "disable-gpu-vsync", + "disable-gpu-watchdog", + "disable-hang-monitor", + "disable-hid-detection-on-oobe", + "disable-histogram-customizer", + "disable-hosted-app-shim-creation", + "disable-hosted-apps-in-windows", + "disable-in-process-stack-traces", + "disable-infobars", + "disable-input-ime-api", + "disable-input-view", + "disable-ios-password-suggestions", + "disable-javascript-harmony-shipping", + "disable-kill-after-bad-ipc", + "disable-lcd-text", + "disable-legacy-window", + "disable-local-storage", + "disable-lock-screen-apps", + "disable-logging", + "disable-logging-redirect", + "disable-login-animations", + "disable-login-screen-apps", + "disable-low-end-device-mode", + "disable-low-latency-dxva", + "disable-low-res-tiling", + "disable-mac-overlays", + "disable-mac-views-native-app-windows", + "disable-machine-cert-request", + "disable-main-frame-before-activation", + "disable-md-error-screen", + "disable-md-oobe", + "disable-media-session-api", + "disable-media-suspend", + "disable-merge-key-char-events", + "disable-mojo-local-storage", + "disable-mojo-renderer", + "disable-mtp-write-support", + "disable-multi-display-layout", + "disable-namespace-sandbox", + "disable-native-gpu-memory-buffers", + "disable-network-portal-notification", + "disable-new-korean-ime", + "disable-new-virtual-keyboard-behavior", + "disable-new-zip-unpacker", + "disable-notifications", + "disable-ntp-most-likely-favicons-from-server", + "disable-ntp-popular-sites", + "disable-nv12-dxgi-video", + "disable-offer-store-unmasked-wallet-cards", + "disable-offer-upload-credit-cards", + "disable-office-editing-component-extension", + "disable-offline-auto-reload", + "disable-offline-auto-reload-visible-only", + "disable-origin-trial-controlled-blink-features", + "disable-overscroll-edge-effect", + "disable-panel-fitting", + "disable-partial-raster", + "disable-password-generation", + "disable-pepper-3d", + "disable-pepper-3d-image-chromium", + "disable-per-user-timezone", + "disable-permission-action-reporting", + "disable-permissions-api", + "disable-physical-keyboard-autocorrect", + "disable-pinch", + "disable-pnacl-crash-throttling", + "disable-popup-blocking", + "disable-prefer-compositing-to-lcd-text", + "disable-presentation-api", + "disable-print-preview", + "disable-prompt-on-repost", + "disable-proximity-auth-bluetooth-low-energy-discovery", + "disable-pull-to-refresh-effect", + "disable-push-api-background-mode", + "disable-reading-from-canvas", + "disable-remote-core-animation", + "disable-remote-fonts", + "disable-remote-playback-api", + "disable-renderer-accessibility", + "disable-renderer-backgrounding", + "disable-resize-lock", + "disable-rgba-4444-textures", + "disable-rollback-option", + "disable-rtc-smoothness-algorithm", + "disable-screen-orientation-lock", + "disable-search-geolocation-disclosure", + "disable-seccomp-filter-sandbox", + "disable-setuid-sandbox", + "disable-shader-name-hashing", + "disable-shared-workers", + "disable-signin-promo", + "disable-signin-scoped-device-id", + "disable-single-click-autofill", + "disable-skia-runtime-opts", + "disable-slim-navigation-manager", + "disable-slimming-paint-invalidation", + "disable-smooth-scrolling", + "disable-software-rasterizer", + "disable-speech-api", + "disable-suggestions-ui", + "disable-surface-references", + "disable-sync", + "disable-sync-app-list", + "disable-sync-types", + "disable-system-timezone-automatic-detection", + "disable-tab-for-desktop-share", + "disable-third-party-keyboard-workaround", + "disable-threaded-animation", + "disable-threaded-compositing", + "disable-threaded-scrolling", + "disable-timeouts-for-profiling", + "disable-touch-adjustment", + "disable-touch-drag-drop", + "disable-translate-new-ux", + "disable-usb-keyboard-detect", + "disable-v8-idle-tasks", + "disable-vaapi-accelerated-video-encode", + "disable-virtual-keyboard-overscroll", + "disable-voice-input", + "disable-volume-adjust-sound", + "disable-wake-on-wifi", + "disable-web-notification-custom-layouts", + "disable-web-security", + "disable-webgl", + "disable-webgl-image-chromium", + "disable-webrtc-encryption", + "disable-webrtc-hw-decoding", + "disable-webrtc-hw-encoding", + "disable-win32k-lockdown", + "disable-xss-auditor", + "disable-zero-browsers-open-for-tests", + "disable-zero-copy", + "disable-zero-copy-dxgi-video", + "disabled", + "disabled-new-style-notification", + "disallow-non-exact-resource-reuse", + "disk-cache-dir", + "disk-cache-size", + "display", + "dmg-device", + "dns-log-details", + "document-user-activation-required", + "dom-automation", + "dotfile", + "draft", + "draw-view-bounds-rects", + "duck-flash", + "dump-blink-runtime-call-stats", + "dump-browser-histograms", + "dump-dom", + "eafe-path", + "eafe-url", + "easy-unlock-app-path", + "edge-touch-filtering", + "egl", + "elevate", + "embedded-extension-options", + "emphasize-titles-in-omnibox-dropdown", + "emulate-shader-precision", + "enable-accelerated-2d-canvas", + "enable-accelerated-vpx-decode", + "enable-accessibility-tab-switcher", + "enable-adaptive-selection-handle-orientation", + "enable-aggressive-domstorage-flushing", + "enable-android-wallpapers-app", + "enable-app-info-dialog-mac", + "enable-app-list", + "enable-app-window-cycling", + "enable-appcontainer", + "enable-arc", + "enable-arc-oobe-optin", + "enable-async-event-targeting", + "enable-audio-debug-recordings-from-extension", + "enable-audio-focus", + "enable-automation", + "enable-background-fetch-persistence", + "enable-benchmarking", + "enable-ble-advertising-in-apps", + "enable-blink-features", + "enable-bookmark-undo", + "enable-browser-side-navigation", + "enable-browser-task-scheduler", + "enable-cast-receiver", + "enable-checker-imaging", + "enable-chromevox-arc-support", + "enable-clear-browsing-data-counters", + "enable-cloud-print-proxy", + "enable-cloud-print-xps", + "enable-consumer-kiosk", + "enable-contextual-search", + "enable-crash-reporter", + "enable-crash-reporter-for-testing", + "enable-crx-hash-check", + "enable-data-reduction-proxy-bypass-warning", + "enable-data-reduction-proxy-force-pingback", + "enable-data-reduction-proxy-lite-page", + "enable-data-reduction-proxy-savings-promo", + "enable-datasaver-prompt", + "enable-device-discovery-notifications", + "enable-devtools-experiments", + "enable-direct-composition-layers", + "enable-display-list-2d-canvas", + "enable-distance-field-text", + "enable-distillability-service", + "enable-dom-distiller", + "enable-domain-reliability", + "enable-drive-search-in-app-launcher", + "enable-drm-atomic", + "enable-embedded-extension-options", + "enable-encryption-migration", + "enable-encryption-selection", + "enable-es3-apis", + "enable-exclusive-audio", + "enable-experimental-accessibility-features", + "enable-experimental-canvas-features", + "enable-experimental-extension-apis", + "enable-experimental-fullscreen-exit-ui", + "enable-experimental-input-view-features", + "enable-experimental-web-platform-features", + "enable-extension-activity-log-testing", + "enable-extension-activity-logging", + "enable-extension-assets-sharing", + "enable-external-drive-rename", + "enable-fast-unload", + "enable-features", + "enable-file-manager-touch-mode", + "enable-first-run-ui-transitions", + "enable-floating-virtual-keyboard", + "enable-font-antialiasing", + "enable-fullscreen-tab-detaching", + "enable-fullscreen-toolbar-reveal", + "enable-google-branded-context-menu", + "enable-gpu-async-worker-context", + "enable-gpu-benchmarking", + "enable-gpu-client-logging", + "enable-gpu-client-tracing", + "enable-gpu-command-logging", + "enable-gpu-debugging", + "enable-gpu-driver-debug-logging", + "enable-gpu-memory-buffer-compositor-resources", + "enable-gpu-memory-buffer-video-frames", + "enable-gpu-rasterization", + "enable-gpu-service-logging", + "enable-gpu-service-tracing", + "enable-hardware-overlays", + "enable-harfbuzz-rendertext", + "enable-heap-profiling", + "enable-hosted-app-quit-notification", + "enable-hosted-apps-in-windows", + "enable-hotword-hardware", + "enable-hung-renderer-infobar", + "enable-inband-text-tracks", + "enable-input-ime-api", + "enable-instant-tethering", + "enable-internal-media-session", + "enable-ios-handoff-to-other-devices", + "enable-layer-lists", + "enable-lcd-text", + "enable-leak-detection", + "enable-local-file-accesses", + "enable-local-sync-backend", + "enable-logging", + "enable-longpress-drag-selection", + "enable-low-end-device-mode", + "enable-low-res-tiling", + "enable-mac-views-native-app-windows", + "enable-main-frame-before-activation", + "enable-md-feedback", + "enable-media-suspend", + "enable-merge-key-char-events", + "enable-message-center-always-scroll-up-upon-notification-removal", + "enable-nacl", + "enable-nacl-debug", + "enable-nacl-nonsfi-mode", + "enable-native-gpu-memory-buffers", + "enable-natural-scroll-default", + "enable-navigation-tracing", + "enable-net-benchmarking", + "enable-network-information-downlink-max", + "enable-network-portal-notification", + "enable-new-app-menu-icon", + "enable-ntp-most-likely-favicons-from-server", + "enable-ntp-popular-sites", + "enable-ntp-search-engine-country-detection", + "enable-offer-store-unmasked-wallet-cards", + "enable-offer-upload-credit-cards", + "enable-offline-auto-reload", + "enable-offline-auto-reload-visible-only", + "enable-oop-rasterization", + "enable-osk-overscroll", + "enable-override-bookmarks-ui", + "enable-partial-raster", + "enable-password-generation", + "enable-pepper-testing", + "enable-permission-action-reporting", + "enable-physical-keyboard-autocorrect", + "enable-picture-in-picture", + "enable-pinch", + "enable-pixel-canvas-recording", + "enable-pixel-output-in-tests", + "enable-plugin-placeholder-testing", + "enable-potentially-annoying-security-features", + "enable-power-overlay", + "enable-precise-memory-info", + "enable-prefer-compositing-to-lcd-text", + "enable-print-browser", + "enable-print-preview-register-promos", + "enable-profile-shortcut-manager", + "enable-profiling", + "enable-push-api-background-mode", + "enable-refresh-token-annotation-request", + "enable-request-tablet-site", + "enable-rgba-4444-textures", + "enable-sandbox", + "enable-sandbox-logging", + "enable-screenshot-testing-with-mode", + "enable-scripts-require-action", + "enable-scroll-prediction", + "enable-service-manager-tracing", + "enable-sgi-video-sync", + "enable-signin-promo", + "enable-single-click-autofill", + "enable-site-settings", + "enable-skia-benchmarking", + "enable-slim-navigation-manager", + "enable-slimming-paint-invalidation", + "enable-slimming-paint-v2", + "enable-smooth-scrolling", + "enable-spatial-navigation", + "enable-spdy-proxy-auth", + "enable-speech-dispatcher", + "enable-spelling-feedback-field-trial", + "enable-spotlight-actions", + "enable-stats-collection-bindings", + "enable-stats-table", + "enable-strict-mixed-content-checking", + "enable-strict-powerful-feature-restrictions", + "enable-suggestions-ui", + "enable-suggestions-with-substring-match", + "enable-supervised-user-managed-bookmarks-folder", + "enable-surface-synchronization", + "enable-swap-buffers-with-bounds", + "enable-sync-app-list", + "enable-sync-articles", + "enable-tab-audio-muting", + "enable-tablet-splitview", + "enable-tcp-fastopen", + "enable-third-party-keyboard-workaround", + "enable-threaded-compositing", + "enable-threaded-texture-mailboxes", + "enable-tile-compression", + "enable-touch-calibration-setting", + "enable-touch-drag-drop", + "enable-touchpad-three-finger-click", + "enable-touchview", + "enable-trace-app-source", + "enable-tracing", + "enable-tracing-output", + "enable-translate-new-ux", + "enable-ui-devtools", + "enable-use-zoom-for-dsf", + "enable-user-metrics", + "enable-usermedia-screen-capturing", + "enable-video-player-chromecast-support", + "enable-viewport", + "enable-virtual-keyboard", + "enable-voice-interaction", + "enable-vtune-support", + "enable-vulkan", + "enable-wayland-server", + "enable-web-notification-custom-layouts", + "enable-webfonts-intervention-trigger", + "enable-webfonts-intervention-v2", + "enable-webgl-draft-extensions", + "enable-webgl-image-chromium", + "enable-webrtc-event-logging-from-extension", + "enable-webrtc-srtp-aes-gcm", + "enable-webrtc-srtp-encrypted-headers", + "enable-webrtc-stun-origin", + "enable-webview-variations", + "enable-webvr", + "enable-wifi-credential-sync", + "enable-win7-webrtc-hw-h264-decoding", + "enable-zero-copy", + "enable-zip-archiver-on-file-manager", + "enabled", + "enabled-2g", + "enabled-3g", + "enabled-new-style-notification", + "enabled-slow2g", + "encode-binary", + "enforce", + "enforce-gl-minimums", + "enforce-webrtc-ip-permission-check", + "enforce_strict", + "enterprise-disable-arc", + "enterprise-enable-forced-re-enrollment", + "enterprise-enable-license-type-selection", + "enterprise-enable-zero-touch-enrollment", + "enterprise-enrollment-initial-modulus", + "enterprise-enrollment-modulus-limit", + "error-console", + "evaluate-type", + "evaluate_capability", + "experiment", + "explicitly-allowed-ports", + "expose-internals-for-testing", + "extension-content-verification", + "extension-process", + "extensions-install-verification", + "extensions-multi-account", + "extensions-not-webstore", + "extensions-on-chrome-urls", + "extensions-update-frequency", + "extra-search-query-params", + "fail-on-unused-args", + "fake-variations-channel", + "false", + "fast", + "fast-start", + "feedback-server", + "field-trial-handle", + "first-exec-after-boot", + "flag-switches-begin", + "flag-switches-end", + "font-cache-shared-handle", + "force-android-app-mode", + "force-app-mode", + "force-clamshell-power-button", + "force-color-profile", + "force-desktop-ios-promotion", + "force-dev-mode-highlighting", + "force-device-scale-factor", + "force-display-list-2d-canvas", + "force-effective-connection-type", + "force-enable-metrics-reporting", + "force-enable-stylus-tools", + "force-fieldtrial-params", + "force-fieldtrials", + "force-first-run", + "force-first-run-ui", + "force-gpu-mem-available-mb", + "force-gpu-rasterization", + "force-happiness-tracking-system", + "force-load-easy-unlock-app-in-tests", + "force-local-ntp", + "force-login-manager-in-tests", + "force-mediafoundation", + "force-overlay-fullscreen-video", + "force-password-reauth", + "force-pnacl-subzero", + "force-presentation-receiver-for-testing", + "force-renderer-accessibility", + "force-show-update-menu-badge", + "force-show-update-menu-item", + "force-system-compositor-mode", + "force-tablet-mode", + "force-text-direction", + "force-ui-direction", + "force-variation-ids", + "force-video-overlays", + "force-wave-audio", + "force-webrtc-ip-handling-policy", + "full-memory-crash-report", + "gaia-url", + "gcm-checkin-url", + "gcm-mcs-endpoint", + "gcm-registration-url", + "generate-accessibility-test-expectations", + "gl", + "gl-composited-overlay-candidate-quad-border", + "gl-shader-interm-output", + "gles", + "golden-screenshots-dir", + "google-apis-url", + "google-base-url", + "google-doodle-url", + "google-url", + "gpu-active-device-id", + "gpu-active-vendor-id", + "gpu-device-id", + "gpu-driver-date", + "gpu-driver-vendor", + "gpu-driver-version", + "gpu-launcher", + "gpu-no-complete-info-collection", + "gpu-no-context-lost", + "gpu-process", + "gpu-program-cache-size-kb", + "gpu-rasterization-msaa-sample-count", + "gpu-sandbox-allow-sysv-shm", + "gpu-sandbox-failures-fatal", + "gpu-sandbox-start-early", + "gpu-secondary-device-ids", + "gpu-secondary-vendor-ids", + "gpu-startup-dialog", + "gpu-testing-device-id", + "gpu-testing-driver-date", + "gpu-testing-gl-renderer", + "gpu-testing-gl-vendor", + "gpu-testing-gl-version", + "gpu-testing-os-version", + "gpu-testing-secondary-device-ids", + "gpu-testing-secondary-vendor-ids", + "gpu-testing-vendor-id", + "gpu-vendor-id", + "graphics-buffer-count", + "guest-wallpaper-large", + "guest-wallpaper-small", + "h", + "has-chromeos-diamond-key", + "has-chromeos-keyboard", + "has-internal-stylus", + "headless", + "help", + "hide", + "hide-icons", + "hide-scrollbars", + "history-entry-requires-user-gesture", + "homedir", + "homepage", + "host", + "host-pairing-oobe", + "host-resolver-rules", + "icu-data-dir", + "ignore-autocomplete-off-autofill", + "ignore-autoplay-restrictions", + "ignore-certificate-errors", + "ignore-certificate-errors-spki-list", + "ignore-gpu-blacklist", + "ignore-urlfetcher-cert-requests", + "ignore-user-profile-mapping-for-tests", + "in-process-gpu", + "incognito", + "input", + "inspect", + "inspect-brk", + "install-chrome-app", + "install-supervised-user-whitelists", + "instant-process", + "invalidation-use-gcm-channel", + "ipc-connection-timeout", + "ipc-dump-directory", + "ipc-fuzzer-testcase", + "is-running-in-mash", + "isolate-origins", + "isolate-sites-for-testing", + "javascript-harmony", + "js-flags", + "keep-alive-for-test", + "kiosk", + "kiosk-printing", + "lang", + "last-launched-app", + "layer", + "light_muted", + "light_vibrant", + "limit-fps", + "load-and-launch-app", + "load-apps", + "load-extension", + "load-media-router-component-extension", + "local-heuristics-only-for-password-generation", + "local-ntp-reload", + "local-sync-backend-dir", + "log-gpu-control-list-decisions", + "log-level", + "log-net-log", + "login-manager", + "login-profile", + "login-user", + "loopback-i2s-bits", + "loopback-i2s-bus-name", + "loopback-i2s-channels", + "loopback-i2s-rate-hz", + "lso-url", + "ltr", + "main-frame-resizes-are-orientation-changes", + "make-chrome-default", + "make-default-browser", + "managed-user-id", + "managed-user-sync-token", + "mark-non-secure-as", + "markdown", + "market-url-for-testing", + "mash", + "material", + "material-design-ink-drop-animation-speed", + "material-hybrid", + "max-gum-fps", + "max-output-volume-dba1m", + "max-untiled-layer-height", + "max-untiled-layer-width", + "media-cache-size", + "mem-pressure-system-reserved-kb", + "memlog", + "memory-pressure-off", + "memory-pressure-thresholds", + "memory-pressure-thresholds-mb", + "message-center-changes-while-open", + "method", + "metrics-client-id", + "metrics-recording-only", + "mhtml-generator-option", + "mirror", + "mock", + "mojo-local-storage", + "mojo-pipe-token", + "monitoring-destination-id", + "mse-audio-buffer-size-limit", + "mse-video-buffer-size-limit", + "mus", + "mus-config", + "mute-audio", + "nacl-broker", + "nacl-dangerous-no-sandbox-nonsfi", + "nacl-debug-mask", + "nacl-gdb", + "nacl-gdb-script", + "nacl-loader", + "nacl-loader-nonsfi", + "native", + "native-crx-bindings", + "need-arc-migration-policy-check", + "net-log-capture-mode", + "netifs-to-ignore", + "network-country-iso", + "network-settings-config", + "new-window", + "no-default-browser-check", + "no-experiments", + "no-first-run", + "no-managed-user-acknowledgment-check", + "no-network-profile-warning", + "no-pings", + "no-proxy-server", + "no-referrers", + "no-sandbox", + "no-service-autorun", + "no-session-id", + "no-startup-window", + "no-user-gesture-required", + "no-wifi", + "no-zygote", + "nocolor", + "noerrdialogs", + "non-material", + "non-secure", + "non-secure-after-editing", + "non-secure-while-incognito", + "non-secure-while-incognito-or-editing", + "none", + "normal_muted", + "normal_vibrant", + "note-taking-app-ids", + "ntp-snippets-add-incomplete", + "null", + "num-raster-threads", + "oauth2-client-id", + "oauth2-client-secret", + "off", + "on", + "oobe-bootstrapping-master", + "oobe-force-show-screen", + "oobe-guest-session", + "oobe-skip-postlogin", + "oobe-timer-interval", + "open-ash", + "opengraph", + "origin-trial-disabled-features", + "origin-trial-disabled-tokens", + "origin-trial-public-key", + "original-process-start-time", + "osmesa", + "output", + "override", + "override-metrics-upload-url", + "override-plugin-power-saver-for-testing", + "override-use-software-gl-for-tests", + "overscroll-history-navigation", + "overscroll-start-threshold", + "ozone-dump-file", + "ozone-platform", + "pack-extension", + "pack-extension-key", + "parent-profile", + "parent-window", + "passive-listeners-default", + "password-store", + "permission-request-api-scope", + "permission-request-api-url", + "ppapi", + "ppapi-antialiased-text-enabled", + "ppapi-broker", + "ppapi-flash-args", + "ppapi-flash-path", + "ppapi-flash-version", + "ppapi-in-process", + "ppapi-plugin-launcher", + "ppapi-startup-dialog", + "ppapi-subpixel-rendering-setting", + "previous-app", + "primary", + "print-to-pdf", + "privet-ipv6-only", + "process-per-site", + "process-per-tab", + "product-version", + "profile-directory", + "profiler-timing", + "profiling-at-start", + "profiling-file", + "profiling-flush", + "progress-bar-animation", + "progress-bar-completion", + "prompt-for-external-extensions", + "proxy-auto-detect", + "proxy-bypass-list", + "proxy-pac-url", + "proxy-server", + "pull-to-refresh", + "q", + "rdp_desktop_session", + "reader-mode-feedback", + "reader-mode-heuristics", + "rebaseline-pixel-tests", + "record-type", + "reduce-security-for-testing", + "reduced-referrer-granularity", + "register-font-files", + "register-pepper-plugins", + "relauncher", + "remote-debugging-address", + "remote-debugging-port", + "remote-debugging-socket-fd", + "remote-debugging-socket-name", + "remote-debugging-targets", + "renderer", + "renderer-client-id", + "renderer-cmd-prefix", + "renderer-process-limit", + "renderer-startup-dialog", + "renderer-wait-for-java-debugger", + "renderpass", + "repl", + "report-vp9-as-an-unsupported-mime-type", + "require-audio-hardware-for-testing", + "reset-app-list-install-state", + "reset-variation-state", + "restore-last-session", + "root", + "root-layer-scrolls", + "rtl", + "run-all-compositor-stages-before-draw", + "run-layout-test", + "runtime-deps-list-file", + "safebrowsing-disable-auto-update", + "safebrowsing-disable-download-protection", + "safebrowsing-disable-extension-blacklist", + "safebrowsing-manual-download-blacklist", + "sandbox-ipc", + "save-page-as-mhtml", + "screen-config", + "screenshot", + "script-executable", + "scripts-require-action", + "search-provider-logo-url", + "secondary", + "secondary-display-layout", + "secondary-ui-md", + "service", + "service-manager", + "service-name", + "service-pipe-token", + "service-request-channel-token", + "service-runner", + "shared-files", + "shill-stub", + "show-app-list", + "show-autofill-signatures", + "show-autofill-type-predictions", + "show-cert-link", + "show-component-extension-options", + "show-composited-layer-borders", + "show-fps-counter", + "show-icons", + "show-layer-animation-bounds", + "show-login-dev-overlay", + "show-mac-overlay-borders", + "show-md-login", + "show-non-md-login", + "show-overdraw-feedback", + "show-paint-rects", + "show-property-changed-rects", + "show-saved-copy", + "show-screenspace-rects", + "show-surface-damage-rects", + "silent-debugger-extension-api", + "silent-launch", + "simulate-critical-update", + "simulate-elevated-recovery", + "simulate-outdated", + "simulate-outdated-no-au", + "simulate-upgrade", + "single-process", + "site-per-process", + "skip-gpu-data-loading", + "skip-nostore-all", + "skip-nostore-main", + "skip-reencoding-on-skp-capture", + "slow", + "slow-connections-only", + "slow-down-compositing-scale-factor", + "slow-down-raster-scale-factor", + "sms-test-messages", + "spdy-proxy-auth-fallback", + "spdy-proxy-auth-origin", + "spdy-proxy-auth-value", + "spelling-service-feedback-interval-seconds", + "spelling-service-feedback-url", + "spurious-power-button-accel-count", + "spurious-power-button-keyboard-accel", + "spurious-power-button-lid-angle-change", + "spurious-power-button-screen-accel", + "spurious-power-button-window", + "ssl-key-log-file", + "ssl-version-max", + "ssl-version-min", + "stable-release-mode", + "start-fullscreen", + "start-maximized", + "start-stack-profiler", + "started", + "stub", + "stub-cros-settings", + "surface", + "swiftshader", + "swiftshader-webgl", + "sync-allow-insecure-xmpp-connection", + "sync-deferred-startup-timeout-seconds", + "sync-disable-deferred-startup", + "sync-enable-get-update-avoidance", + "sync-notification-host-port", + "sync-on-draw-hardware", + "sync-short-initial-retry-override", + "sync-short-nudge-delay-for-test", + "sync-url", + "system-developer-mode", + "system-log-upload-frequency", + "tab-management-experiment-type-disabled", + "tab-management-experiment-type-elderberry", + "task-manager-show-extra-renderers", + "task-profiler", + "team-drives", + "test-auto-update-ui", + "test-child-process", + "test-cros-gaia-id-migration", + "test-do-not-initialize-icu", + "test-encryption-migration-ui", + "test-gl-lib", + "test-launcher-batch-limit", + "test-launcher-bot-mode", + "test-launcher-debug-launcher", + "test-launcher-filter-file", + "test-launcher-force-run-broken-tests", + "test-launcher-jobs", + "test-launcher-list-tests", + "test-launcher-output", + "test-launcher-print-test-stdio", + "test-launcher-print-writable-path", + "test-launcher-retry-limit", + "test-launcher-shard-index", + "test-launcher-summary-output", + "test-launcher-test-part-results-limit", + "test-launcher-timeout", + "test-launcher-total-shards", + "test-launcher-trace", + "test-name", + "test-tiny-timeout", + "test-type", + "testing-fixed-http-port", + "testing-fixed-https-port", + "tether-stub", + "third-party-doodle-url", + "threads", + "time", + "timeout", + "tls1", + "tls1.1", + "tls1.2", + "tls1.3", + "tls13-variant", + "top-chrome-md", + "top-controls-hide-threshold", + "top-controls-show-threshold", + "touch-calibration", + "touch-devices", + "touch-events", + "touch-noise-filtering", + "touch-selection-strategy", + "touch_view", + "trace-config-file", + "trace-export-events-to-etw", + "trace-shutdown", + "trace-shutdown-file", + "trace-startup", + "trace-startup-duration", + "trace-startup-file", + "trace-to-console", + "trace-to-file", + "trace-to-file-name", + "trace-upload-url", + "tracelog", + "translate-ranker-model-url", + "translate-script-url", + "translate-security-origin", + "true", + "trusted-download-sources", + "try-chrome-again", + "try-supported-channel-layouts", + "type", + "ui-disable-partial-swap", + "ui-enable-layer-lists", + "ui-enable-rgba-4444-textures", + "ui-enable-zero-copy", + "ui-prioritize-in-gpu-process", + "ui-show-composited-layer-borders", + "ui-show-fps-counter", + "ui-show-layer-animation-bounds", + "ui-show-paint-rects", + "ui-show-property-changed-rects", + "ui-show-screenspace-rects", + "ui-show-surface-damage-rects", + "ui-slow-animations", + "ui-test-action-max-timeout", + "ui-test-action-timeout", + "uninstall", + "unlimited-storage", + "unsafe-pac-url", + "unsafely-allow-protected-media-identifier-for-domain", + "unsafely-treat-insecure-origin-as-secure", + "use-angle", + "use-cras", + "use-fake-device-for-media-stream", + "use-fake-jpeg-decode-accelerator", + "use-fake-ui-for-media-stream", + "use-file-for-fake-audio-capture", + "use-file-for-fake-video-capture", + "use-first-display-as-internal", + "use-gl", + "use-gpu-in-tests", + "use-ime-service", + "use-mobile-user-agent", + "use-mock-keychain", + "use-passthrough-cmd-decoder", + "use-skia-renderer", + "use-system-default-printer", + "use-test-config", + "use-viz-hit-test", + "user-agent", + "user-always-affiliated", + "user-data-dir", + "user-gesture-required", + "user-gesture-required-for-cross-origin", + "utility", + "utility-allowed-dir", + "utility-cmd-prefix", + "utility-run-elevated", + "utility-sandbox-type", + "utility-startup-dialog", + "v", + "v2-sandbox", + "v2-sandbox-enabled", + "v8-cache-options", + "v8-cache-strategies-for-cache-storage", + "validate-crx", + "validate-input-event-stream", + "variations-override-country", + "variations-server-url", + "version", + "video-image-texture-target", + "video-threads", + "video-underflow-threshold-ms", + "virtual-time-budget", + "vmodule", + "voice-interaction-supported-locales", + "wait-for-debugger", + "wait-for-debugger-children", + "wake-on-wifi-packet", + "wallet-service-use-sandbox", + "watcher", + "waveout-buffers", + "webapk-server-url", + "webrtc-stun-probe-trial", + "webview-enable-safebrowsing-support", + "webview-sandboxed-renderer", + "whitelisted-extension-id", + "win-jumplist-action", + "window-position", + "window-size", + "window-workspace", + "windows10-custom-titlebar", + "winhttp-proxy-resolver", + "wm-window-animations-disabled", + "yield-between-content-script-runs", + "zygote", + "zygote-cmd-prefix", +}; + +bool IsBlacklistedArg(const base::CommandLine::CharType* arg) { +#if defined(OS_WIN) + const auto converted = base::WideToUTF8(arg); + const char* a = converted.c_str(); +#else + const char* a = arg; +#endif + + static const char* prefixes[] = {"--", "-", "/"}; + + int prefix_length = 0; + for (auto& prefix : prefixes) { + if (base::StartsWith(a, prefix, base::CompareCase::SENSITIVE)) { + prefix_length = strlen(prefix); + break; + } + } + + if (prefix_length > 0) { + a += prefix_length; + std::string switch_name = + base::ToLowerASCII(base::StringPiece(a, strcspn(a, "="))); + return std::binary_search(std::begin(kBlacklist), std::end(kBlacklist), + switch_name); + } + + return false; +} + +} // namespace + +namespace atom { + +bool CheckCommandLineArguments(int argc, base::CommandLine::CharType** argv) { + DCHECK(std::is_sorted(std::begin(kBlacklist), std::end(kBlacklist), + [](const char* a, const char* b) { + return base::StringPiece(a) < base::StringPiece(b); + })) + << "The kBlacklist must be in sorted order"; + DCHECK(std::binary_search(std::begin(kBlacklist), std::end(kBlacklist), + base::StringPiece("inspect"))) + << "Remember to add Node command line flags to kBlacklist"; + + const base::CommandLine::StringType dashdash(2, '-'); + bool block_blacklisted_args = false; + for (int i = 0; i < argc; ++i) { + if (argv[i] == dashdash) + break; + if (block_blacklisted_args) { + if (IsBlacklistedArg(argv[i])) + return false; + } else if (IsUrlArg(argv[i])) { + block_blacklisted_args = true; + } + } + return true; +} + +} // namespace atom diff --git a/atom/app/command_line_args.h b/atom/app/command_line_args.h new file mode 100644 index 00000000000..1f5fd756868 --- /dev/null +++ b/atom/app/command_line_args.h @@ -0,0 +1,17 @@ +// Copyright (c) 2018 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_APP_COMMAND_LINE_ARGS_H_ +#define ATOM_APP_COMMAND_LINE_ARGS_H_ + +#include "base/command_line.h" + +namespace atom { + +bool CheckCommandLineArguments(int argc, base::CommandLine::CharType** argv); + +} // namespace atom + +#endif // ATOM_APP_COMMAND_LINE_ARGS_H_ + diff --git a/atom/app/node_main.cc b/atom/app/node_main.cc index cc8df0e0c8f..8e0089ad95c 100644 --- a/atom/app/node_main.cc +++ b/atom/app/node_main.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. +#ifdef ENABLE_RUN_AS_NODE + #include "atom/app/node_main.h" #include "atom/app/uv_task_runner.h" @@ -10,6 +12,7 @@ #include "atom/common/api/atom_bindings.h" #include "atom/common/crash_reporter/crash_reporter.h" #include "atom/common/native_mate_converters/string16_converter.h" +#include "atom/common/node_bindings.h" #include "base/command_line.h" #include "base/feature_list.h" #include "base/task_scheduler/task_scheduler.h" @@ -45,20 +48,23 @@ int NodeMain(int argc, char *argv[]) { // V8 requires a task scheduler apparently base::TaskScheduler::CreateAndStartWithDefaultParams("Electron"); + // Initialize gin::IsolateHolder. JavascriptEnvironment gin_env; + // Explicitly register electron's builtin modules. + NodeBindings::RegisterBuiltinModules(); + int exec_argc; const char** exec_argv; node::Init(&argc, const_cast(argv), &exec_argc, &exec_argv); - node::IsolateData isolate_data(gin_env.isolate(), loop); node::Environment* env = node::CreateEnvironment( - &isolate_data, gin_env.context(), argc, argv, - exec_argc, exec_argv); + node::CreateIsolateData(gin_env.isolate(), loop, gin_env.platform()), + gin_env.context(), argc, argv, exec_argc, exec_argv); // Enable support for v8 inspector. NodeDebugger node_debugger(env); - node_debugger.Start(); + node_debugger.Start(gin_env.platform()); mate::Dictionary process(gin_env.isolate(), env->process_object()); #if defined(OS_WIN) @@ -76,6 +82,7 @@ int NodeMain(int argc, char *argv[]) { bool more; do { more = uv_run(env->event_loop(), UV_RUN_ONCE); + gin_env.platform()->DrainBackgroundTasks(env->isolate()); if (more == false) { node::EmitBeforeExit(env); @@ -89,6 +96,8 @@ int NodeMain(int argc, char *argv[]) { exit_code = node::EmitExit(env); node::RunAtExit(env); + gin_env.platform()->DrainBackgroundTasks(env->isolate()); + gin_env.platform()->CancelPendingDelayedTasks(env->isolate()); node::FreeEnvironment(env); } @@ -106,3 +115,5 @@ int NodeMain(int argc, char *argv[]) { } } // namespace atom + +#endif // ENABLE_RUN_AS_NODE diff --git a/atom/app/node_main.h b/atom/app/node_main.h index a4e047de39f..e77bccbc1d7 100644 --- a/atom/app/node_main.h +++ b/atom/app/node_main.h @@ -5,10 +5,14 @@ #ifndef ATOM_APP_NODE_MAIN_H_ #define ATOM_APP_NODE_MAIN_H_ +#ifdef ENABLE_RUN_AS_NODE + namespace atom { int NodeMain(int argc, char *argv[]); } // namespace atom +#endif // ENABLE_RUN_AS_NODE + #endif // ATOM_APP_NODE_MAIN_H_ diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index ada6de3479f..77112113569 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -532,7 +532,6 @@ App::App(v8::Isolate* isolate) { static_cast(AtomBrowserClient::Get())->set_delegate(this); Browser::Get()->AddObserver(this); content::GpuDataManager::GetInstance()->AddObserver(this); - content::BrowserChildProcessObserver::Add(this); base::ProcessId pid = base::GetCurrentProcId(); std::unique_ptr process_metric( new atom::ProcessMetric( @@ -599,6 +598,7 @@ void App::OnFinishLaunching(const base::DictionaryValue& launch_info) { } void App::OnPreMainMessageLoopRun() { + content::BrowserChildProcessObserver::Add(this); if (process_singleton_) { process_singleton_->OnBrowserReady(); } @@ -667,25 +667,33 @@ void App::OnLogin(LoginHandler* login_handler, login_handler->CancelAuth(); } -void App::OnCreateWindow( +bool App::CanCreateWindow( + content::RenderFrameHost* opener, + const GURL& opener_url, + const GURL& opener_top_level_frame_url, + const GURL& source_origin, + content::mojom::WindowContainerType container_type, const GURL& target_url, + const content::Referrer& referrer, const std::string& frame_name, WindowOpenDisposition disposition, - const std::vector& features, + const blink::mojom::WindowFeatures& features, + const std::vector& additional_features, const scoped_refptr& body, - content::RenderFrameHost* opener) { + bool user_gesture, + bool opener_suppressed, + bool* no_javascript_access) { v8::Locker locker(isolate()); v8::HandleScope handle_scope(isolate()); content::WebContents* web_contents = content::WebContents::FromRenderFrameHost(opener); if (web_contents) { auto api_web_contents = WebContents::CreateFrom(isolate(), web_contents); - api_web_contents->OnCreateWindow(target_url, - frame_name, - disposition, - features, - body); + api_web_contents->OnCreateWindow(target_url, frame_name, disposition, + additional_features, body); } + + return false; } void App::AllowCertificateError( @@ -843,7 +851,7 @@ void App::SetDesktopName(const std::string& desktop_name) { } std::string App::GetLocale() { - return l10n_util::GetApplicationLocale(""); + return g_browser_process->GetApplicationLocale(); } bool App::MakeSingleInstance( @@ -859,9 +867,10 @@ bool App::MakeSingleInstance( switch (process_singleton_->NotifyOtherProcessOrCreate()) { case ProcessSingleton::NotifyResult::LOCK_ERROR: case ProcessSingleton::NotifyResult::PROFILE_IN_USE: - case ProcessSingleton::NotifyResult::PROCESS_NOTIFIED: + case ProcessSingleton::NotifyResult::PROCESS_NOTIFIED: { process_singleton_.reset(); return true; + } case ProcessSingleton::NotifyResult::PROCESS_NONE: default: // Shouldn't be needed, but VS warns if it is not there. return false; @@ -888,11 +897,7 @@ bool App::Relaunch(mate::Arguments* js_args) { } 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); } @@ -1128,8 +1133,8 @@ std::vector App::GetAppMetrics(v8::Isolate* isolate) { v8::Local App::GetGPUFeatureStatus(v8::Isolate* isolate) { auto status = content::GetFeatureStatus(); - return mate::ConvertToV8(isolate, - status ? *status : base::DictionaryValue()); + base::DictionaryValue temp; + return mate::ConvertToV8(isolate, status ? *status : temp); } void App::EnableMixedSandbox(mate::Arguments* args) { @@ -1251,13 +1256,16 @@ void App::BuildPrototype( .SetMethod("getFileIcon", &App::GetFileIcon) .SetMethod("getAppMetrics", &App::GetAppMetrics) .SetMethod("getGPUFeatureStatus", &App::GetGPUFeatureStatus) - .SetMethod("enableMixedSandbox", &App::EnableMixedSandbox) // TODO(juturu): Remove in 2.0, deprecate before then with warnings #if defined(OS_MACOSX) .SetMethod("moveToApplicationsFolder", &App::MoveToApplicationsFolder) .SetMethod("isInApplicationsFolder", &App::IsInApplicationsFolder) #endif - .SetMethod("getAppMemoryInfo", &App::GetAppMetrics); + #if defined(MAS_BUILD) + .SetMethod("startAccessingSecurityScopedResource", + &App::StartAccessingSecurityScopedResource) + #endif + .SetMethod("enableMixedSandbox", &App::EnableMixedSandbox); } } // namespace api @@ -1334,4 +1342,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_app, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_app, Initialize) diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index bdd1233ae60..cf7641c213b 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -75,15 +75,6 @@ class App : public AtomBrowserClient::Delegate, 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, - const std::vector& features, - const scoped_refptr& body, - content::RenderFrameHost* opener); - #if defined(USE_NSS_CERTS) void OnCertificateManagerModelCreated( std::unique_ptr options, @@ -152,6 +143,21 @@ class App : public AtomBrowserClient::Delegate, net::SSLCertRequestInfo* cert_request_info, net::ClientCertIdentityList client_certs, std::unique_ptr delegate) override; + bool CanCreateWindow(content::RenderFrameHost* opener, + const GURL& opener_url, + const GURL& opener_top_level_frame_url, + const GURL& source_origin, + content::mojom::WindowContainerType container_type, + const GURL& target_url, + const content::Referrer& referrer, + const std::string& frame_name, + WindowOpenDisposition disposition, + const blink::mojom::WindowFeatures& features, + const std::vector& additional_features, + const scoped_refptr& body, + bool user_gesture, + bool opener_suppressed, + bool* no_javascript_access) override; // content::GpuDataManagerObserver: void OnGpuProcessCrashed(base::TerminationStatus status) override; @@ -203,6 +209,10 @@ class App : public AtomBrowserClient::Delegate, bool MoveToApplicationsFolder(mate::Arguments* args); bool IsInApplicationsFolder(); #endif +#if defined(MAS_BUILD) + base::Callback StartAccessingSecurityScopedResource( + mate::Arguments* args); +#endif #if defined(OS_WIN) // Get the current Jump List settings. diff --git a/atom/browser/api/atom_api_app_mas.mm b/atom/browser/api/atom_api_app_mas.mm new file mode 100644 index 00000000000..bd386e246d1 --- /dev/null +++ b/atom/browser/api/atom_api_app_mas.mm @@ -0,0 +1,59 @@ +// Copyright (c) 2013 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/api/atom_api_app.h" + +#import + +#include "base/strings/sys_string_conversions.h" + +namespace atom { + +namespace api { + +// Callback passed to js which will stop accessing the given bookmark. +void OnStopAccessingSecurityScopedResource(NSURL* bookmarkUrl) { + [bookmarkUrl stopAccessingSecurityScopedResource]; + [bookmarkUrl release]; +} + +// Get base64 encoded NSData, create a bookmark for it and start accessing it. +base::Callback App::StartAccessingSecurityScopedResource(mate::Arguments* args) { + std::string data; + args->GetNext(&data); + NSString *base64str = base::SysUTF8ToNSString(data); + NSData *bookmarkData = [[NSData alloc] initWithBase64EncodedString: base64str options: 0]; + + // Create bookmarkUrl from NSData. + BOOL isStale = false; + NSError *error = nil; + NSURL *bookmarkUrl = [NSURL URLByResolvingBookmarkData: bookmarkData + options: NSURLBookmarkResolutionWithSecurityScope + relativeToURL: nil + bookmarkDataIsStale: &isStale + error: &error]; + + if (error != nil) { + NSString *err = [NSString stringWithFormat: @"NSError: %@ %@", error, [error userInfo]]; + args->ThrowError(base::SysNSStringToUTF8(err)); + } + + if (isStale) { + args->ThrowError("bookmarkDataIsStale - try recreating the bookmark"); + } + + if (error == nil && isStale == false) { + [bookmarkUrl startAccessingSecurityScopedResource]; + } + + // Stop the NSURL from being GC'd. + [bookmarkUrl retain]; + + // Return a js callback which will close the bookmark. + return base::Bind(&OnStopAccessingSecurityScopedResource, bookmarkUrl); +} + +} // namespace atom + +} // namespace api diff --git a/atom/browser/api/atom_api_auto_updater.cc b/atom/browser/api/atom_api_auto_updater.cc index bc708b128f7..88a58f6bca6 100644 --- a/atom/browser/api/atom_api_auto_updater.cc +++ b/atom/browser/api/atom_api_auto_updater.cc @@ -99,10 +99,8 @@ void AutoUpdater::OnWindowAllClosed() { QuitAndInstall(); } -void AutoUpdater::SetFeedURL(const std::string& url, mate::Arguments* args) { - auto_updater::AutoUpdater::HeaderMap headers; - args->GetNext(&headers); - auto_updater::AutoUpdater::SetFeedURL(url, headers); +void AutoUpdater::SetFeedURL(mate::Arguments* args) { + auto_updater::AutoUpdater::SetFeedURL(args); } void AutoUpdater::QuitAndInstall() { @@ -152,4 +150,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_auto_updater, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_auto_updater, Initialize) diff --git a/atom/browser/api/atom_api_auto_updater.h b/atom/browser/api/atom_api_auto_updater.h index e9e5c53ade0..305313df52e 100644 --- a/atom/browser/api/atom_api_auto_updater.h +++ b/atom/browser/api/atom_api_auto_updater.h @@ -47,7 +47,7 @@ class AutoUpdater : public mate::EventEmitter, private: std::string GetFeedURL(); - void SetFeedURL(const std::string& url, mate::Arguments* args); + void SetFeedURL(mate::Arguments* args); void QuitAndInstall(); DISALLOW_COPY_AND_ASSIGN(AutoUpdater); diff --git a/atom/browser/api/atom_api_browser_view.cc b/atom/browser/api/atom_api_browser_view.cc index 4bd88a5a43c..c64054cdac2 100644 --- a/atom/browser/api/atom_api_browser_view.cc +++ b/atom/browser/api/atom_api_browser_view.cc @@ -162,4 +162,4 @@ void Initialize(v8::Local exports, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_browser_view, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_browser_view, Initialize) diff --git a/atom/browser/api/atom_api_browser_window.cc b/atom/browser/api/atom_api_browser_window.cc new file mode 100644 index 00000000000..57c06dccca1 --- /dev/null +++ b/atom/browser/api/atom_api_browser_window.cc @@ -0,0 +1,1390 @@ +// Copyright (c) 2013 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/api/atom_api_browser_window.h" + +#include "atom/browser/api/atom_api_browser_view.h" +#include "atom/browser/api/atom_api_menu.h" +#include "atom/browser/api/atom_api_web_contents.h" +#include "atom/browser/browser.h" +#include "atom/browser/native_window.h" +#include "atom/browser/unresponsive_suppressor.h" +#include "atom/browser/web_contents_preferences.h" +#include "atom/browser/window_list.h" +#include "atom/common/api/api_messages.h" +#include "atom/common/color_util.h" +#include "atom/common/native_mate_converters/callback.h" +#include "atom/common/native_mate_converters/file_path_converter.h" +#include "atom/common/native_mate_converters/gfx_converter.h" +#include "atom/common/native_mate_converters/gurl_converter.h" +#include "atom/common/native_mate_converters/image_converter.h" +#include "atom/common/native_mate_converters/string16_converter.h" +#include "atom/common/native_mate_converters/value_converter.h" +#include "atom/common/options_switches.h" +#include "base/command_line.h" +#include "base/threading/thread_task_runner_handle.h" +#include "content/browser/renderer_host/render_widget_host_impl.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/common/content_switches.h" +#include "native_mate/constructor.h" +#include "native_mate/dictionary.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gl/gpu_switching_manager.h" + +#if defined(TOOLKIT_VIEWS) +#include "atom/browser/native_window_views.h" +#endif + +#if defined(OS_WIN) +#include "atom/browser/ui/win/taskbar_host.h" +#include "ui/base/win/shell.h" +#endif + +#include "atom/common/node_includes.h" + +#if defined(OS_WIN) +namespace mate { + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Handle val, + atom::TaskbarHost::ThumbarButton* out) { + mate::Dictionary dict; + if (!ConvertFromV8(isolate, val, &dict)) + return false; + dict.Get("click", &(out->clicked_callback)); + dict.Get("tooltip", &(out->tooltip)); + dict.Get("flags", &out->flags); + return dict.Get("icon", &(out->icon)); + } +}; + +} // namespace mate +#endif + +namespace atom { + +namespace api { + +namespace { + +// 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); + if (buffer.IsEmpty()) + return v8::Null(isolate); + else + return buffer.ToLocalChecked(); +} + +} // namespace + + +BrowserWindow::BrowserWindow(v8::Isolate* isolate, + v8::Local wrapper, + const mate::Dictionary& options) + : weak_factory_(this) { + mate::Handle web_contents; + + // Use options.webPreferences in WebContents. + mate::Dictionary web_preferences = mate::Dictionary::CreateEmpty(isolate); + options.Get(options::kWebPreferences, &web_preferences); + + // Copy the backgroundColor to webContents. + v8::Local value; + if (options.Get(options::kBackgroundColor, &value)) + web_preferences.Set(options::kBackgroundColor, value); + + v8::Local transparent; + if (options.Get("transparent", &transparent)) + web_preferences.Set("transparent", transparent); + +#if defined(ENABLE_OSR) + // Offscreen windows are always created frameless. + bool offscreen; + if (web_preferences.Get("offscreen", &offscreen) && offscreen) { + auto window_options = const_cast(options); + window_options.Set(options::kFrame, false); + } +#endif + + if (options.Get("webContents", &web_contents) && !web_contents.IsEmpty()) { + // Set webPreferences from options if using an existing webContents. + // These preferences will be used when the webContent launches new + // render processes. + auto* existing_preferences = + WebContentsPreferences::FromWebContents(web_contents->web_contents()); + base::DictionaryValue web_preferences_dict; + if (mate::ConvertFromV8(isolate, web_preferences.GetHandle(), + &web_preferences_dict)) { + existing_preferences->web_preferences()->Clear(); + existing_preferences->Merge(web_preferences_dict); + } + + } else { + // Creates the WebContents used by BrowserWindow. + web_contents = WebContents::Create(isolate, web_preferences); + } + + Init(isolate, wrapper, options, web_contents); +} + +void BrowserWindow::Init(v8::Isolate* isolate, + v8::Local wrapper, + const mate::Dictionary& options, + mate::Handle web_contents) { + web_contents_.Reset(isolate, web_contents.ToV8()); + api_web_contents_ = web_contents.get(); + api_web_contents_->AddObserver(this); + Observe(api_web_contents_->web_contents()); + + // Keep a copy of the options for later use. + mate::Dictionary(isolate, web_contents->GetWrapper()).Set( + "browserWindowOptions", options); + + // The parent window. + mate::Handle parent; + if (options.Get("parent", &parent) && !parent.IsEmpty()) + parent_window_.Reset(isolate, parent.ToV8()); + + // Creates BrowserWindow. + window_.reset(NativeWindow::Create( + web_contents->managed_web_contents(), + options, + parent.IsEmpty() ? nullptr : parent->window_.get())); + web_contents->SetOwnerWindow(window_.get()); + window_->set_is_offscreen_dummy(api_web_contents_->IsOffScreen()); + + // Tell the content module to initialize renderer widget with transparent + // mode. + ui::GpuSwitchingManager::SetTransparent(window_->transparent()); + +#if defined(TOOLKIT_VIEWS) + // Sets the window icon. + mate::Handle icon; + if (options.Get(options::kIcon, &icon) && !icon.IsEmpty()) + SetIcon(icon); +#endif + + window_->InitFromOptions(options); + window_->AddObserver(this); + + InitWith(isolate, wrapper); + AttachAsUserData(window_.get()); + + // We can only append this window to parent window's child windows after this + // window's JS wrapper gets initialized. + if (!parent.IsEmpty()) + parent->child_windows_.Set(isolate, ID(), wrapper); + + auto* host = web_contents->web_contents()->GetRenderViewHost(); + if (host) + host->GetWidget()->AddInputEventObserver(this); +} + +BrowserWindow::~BrowserWindow() { + if (!window_->IsClosed()) + window_->CloseImmediately(); + + api_web_contents_->RemoveObserver(this); + + // Destroy the native window in next tick because the native code might be + // iterating all windows. + base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, window_.release()); +} + +void BrowserWindow::OnInputEvent(const blink::WebInputEvent& event) { + switch (event.GetType()) { + case blink::WebInputEvent::kGestureScrollBegin: + case blink::WebInputEvent::kGestureScrollUpdate: + case blink::WebInputEvent::kGestureScrollEnd: + Emit("scroll-touch-edge"); + break; + default: + break; + } +} + +void BrowserWindow::RenderViewHostChanged(content::RenderViewHost* old_host, + content::RenderViewHost* new_host) { + if (old_host) + old_host->GetWidget()->RemoveInputEventObserver(this); + if (new_host) + new_host->GetWidget()->AddInputEventObserver(this); +} + +void BrowserWindow::RenderViewCreated( + content::RenderViewHost* render_view_host) { + if (!window_->transparent()) + return; + + content::RenderWidgetHostImpl* impl = content::RenderWidgetHostImpl::FromID( + render_view_host->GetProcess()->GetID(), + render_view_host->GetRoutingID()); + if (impl) + impl->SetBackgroundOpaque(false); +} + +void BrowserWindow::DidFirstVisuallyNonEmptyPaint() { + if (window_->IsVisible()) + return; + + // When there is a non-empty first paint, resize the RenderWidget to force + // Chromium to draw. + const auto view = web_contents()->GetRenderWidgetHostView(); + view->Show(); + view->SetSize(window_->GetContentSize()); + + // Emit the ReadyToShow event in next tick in case of pending drawing work. + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind([](base::WeakPtr self) { + if (self) + self->Emit("ready-to-show"); + }, GetWeakPtr())); +} + +void BrowserWindow::BeforeUnloadDialogCancelled() { + WindowList::WindowCloseCancelled(window_.get()); + // Cancel unresponsive event when window close is cancelled. + window_unresponsive_closure_.Cancel(); +} + +void BrowserWindow::OnRendererUnresponsive(content::RenderWidgetHost*) { + // Schedule the unresponsive shortly later, since we may receive the + // responsive event soon. This could happen after the whole application had + // blocked for a while. + // Also notice that when closing this event would be ignored because we have + // 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); +} + +bool BrowserWindow::OnMessageReceived(const IPC::Message& message, + content::RenderFrameHost* rfh) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(BrowserWindow, message, rfh) + IPC_MESSAGE_HANDLER(AtomFrameHostMsg_UpdateDraggableRegions, + UpdateDraggableRegions) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void BrowserWindow::OnCloseContents() { + DCHECK(web_contents()); + + // Close all child windows before closing current window. + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + for (v8::Local value : child_windows_.Values(isolate())) { + mate::Handle child; + if (mate::ConvertFromV8(isolate(), value, &child) && !child.IsEmpty()) + child->window_->CloseImmediately(); + } + + // When the web contents is gone, close the window immediately, but the + // memory will not be freed until you call delete. + // In this way, it would be safe to manage windows via smart pointers. If you + // want to free memory when the window is closed, you can do deleting by + // overriding the OnWindowClosed method in the observer. + window_->CloseImmediately(); + + // Do not sent "unresponsive" event after window is closed. + window_unresponsive_closure_.Cancel(); +} + +void BrowserWindow::OnRendererResponsive() { + window_unresponsive_closure_.Cancel(); + Emit("responsive"); +} + +void BrowserWindow::WillCloseWindow(bool* prevent_default) { + *prevent_default = Emit("close"); +} + +void BrowserWindow::RequestPreferredWidth(int* width) { + *width = web_contents()->GetPreferredSize().width(); +} + +void BrowserWindow::OnCloseButtonClicked(bool* prevent_default) { + // When user tries to close the window by clicking the close button, we do + // not close the window immediately, instead we try to close the web page + // first, and when the web page is closed the window will also be closed. + *prevent_default = true; + + // Assume the window is not responding if it doesn't cancel the close and is + // not closed in 5s, in this way we can quickly show the unresponsive + // dialog when the window is busy executing some script withouth waiting for + // the unresponsive timeout. + if (window_unresponsive_closure_.IsCancelled()) + ScheduleUnresponsiveEvent(5000); + + if (!web_contents()) + // Already closed by renderer + return; + + if (web_contents()->NeedToFireBeforeUnload()) + web_contents()->DispatchBeforeUnload(); + else + web_contents()->Close(); +} + +void BrowserWindow::OnWindowClosed() { + auto* host = web_contents()->GetRenderViewHost(); + if (host) + host->GetWidget()->RemoveInputEventObserver(this); + + api_web_contents_->DestroyWebContents(true /* async */); + + Observe(nullptr); + RemoveFromWeakMap(); + window_->RemoveObserver(this); + + // We can not call Destroy here because we need to call Emit first, but we + // also do not want any method to be used, so just mark as destroyed here. + MarkDestroyed(); + + Emit("closed"); + + RemoveFromParentChildWindows(); + + ResetBrowserView(); + + // Destroy the native class when window is closed. + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, GetDestroyClosure()); +} + +void BrowserWindow::OnWindowEndSession() { + Emit("session-end"); +} + +void BrowserWindow::OnWindowBlur() { + web_contents()->StoreFocus(); +#if defined(OS_MACOSX) + auto* rwhv = web_contents()->GetRenderWidgetHostView(); + if (rwhv) + rwhv->SetActive(false); +#endif + + Emit("blur"); +} + +void BrowserWindow::OnWindowFocus() { + web_contents()->RestoreFocus(); +#if defined(OS_MACOSX) + auto* rwhv = web_contents()->GetRenderWidgetHostView(); + if (rwhv) + rwhv->SetActive(true); +#else + if (!api_web_contents_->IsDevToolsOpened()) + web_contents()->Focus(); +#endif + + Emit("focus"); +} + +void BrowserWindow::OnWindowShow() { + Emit("show"); +} + +void BrowserWindow::OnWindowHide() { + Emit("hide"); +} + +void BrowserWindow::OnWindowMaximize() { + Emit("maximize"); +} + +void BrowserWindow::OnWindowUnmaximize() { + Emit("unmaximize"); +} + +void BrowserWindow::OnWindowMinimize() { + Emit("minimize"); +} + +void BrowserWindow::OnWindowRestore() { + Emit("restore"); +} + +void BrowserWindow::OnWindowResize() { +#if defined(OS_MACOSX) + if (!draggable_regions_.empty()) + UpdateDraggableRegions(nullptr, draggable_regions_); +#endif + Emit("resize"); +} + +void BrowserWindow::OnWindowMove() { + Emit("move"); +} + +void BrowserWindow::OnWindowMoved() { + Emit("moved"); +} + +void BrowserWindow::OnWindowEnterFullScreen() { + Emit("enter-full-screen"); +} + +void BrowserWindow::OnWindowLeaveFullScreen() { + Emit("leave-full-screen"); +} + +void BrowserWindow::OnWindowScrollTouchBegin() { + Emit("scroll-touch-begin"); +} + +void BrowserWindow::OnWindowScrollTouchEnd() { + Emit("scroll-touch-end"); +} + +void BrowserWindow::OnWindowSwipe(const std::string& direction) { + Emit("swipe", direction); +} + +void BrowserWindow::OnWindowSheetBegin() { + Emit("sheet-begin"); +} + +void BrowserWindow::OnWindowSheetEnd() { + Emit("sheet-end"); +} + +void BrowserWindow::OnWindowEnterHtmlFullScreen() { + Emit("enter-html-full-screen"); +} + +void BrowserWindow::OnWindowLeaveHtmlFullScreen() { + Emit("leave-html-full-screen"); +} + +void BrowserWindow::OnExecuteWindowsCommand(const std::string& command_name) { + Emit("app-command", command_name); +} + +void BrowserWindow::OnTouchBarItemResult(const std::string& item_id, + const base::DictionaryValue& details) { + Emit("-touch-bar-interaction", item_id, details); +} + +void BrowserWindow::OnNewWindowForTab() { + Emit("new-window-for-tab"); +} + +#if defined(OS_WIN) +void BrowserWindow::OnWindowMessage(UINT message, + WPARAM w_param, + LPARAM l_param) { + if (IsWindowMessageHooked(message)) { + messages_callback_map_[message].Run( + ToBuffer(isolate(), static_cast(&w_param), sizeof(WPARAM)), + ToBuffer(isolate(), static_cast(&l_param), sizeof(LPARAM))); + } +} +#endif + +// static +mate::WrappableBase* BrowserWindow::New(mate::Arguments* args) { + if (!Browser::Get()->is_ready()) { + args->ThrowError("Cannot create BrowserWindow before app is ready"); + return nullptr; + } + + if (args->Length() > 1) { + args->ThrowError(); + return nullptr; + } + + mate::Dictionary options; + if (!(args->Length() == 1 && args->GetNext(&options))) { + options = mate::Dictionary::CreateEmpty(args->isolate()); + } + + return new BrowserWindow(args->isolate(), args->GetThis(), options); +} + +void BrowserWindow::Close() { + window_->Close(); +} + +void BrowserWindow::Focus() { + window_->Focus(true); +} + +void BrowserWindow::Blur() { + window_->Focus(false); +} + +bool BrowserWindow::IsFocused() { + return window_->IsFocused(); +} + +void BrowserWindow::Show() { + window_->Show(); +} + +void BrowserWindow::ShowInactive() { + // This method doesn't make sense for modal window.. + if (IsModal()) + return; + + window_->ShowInactive(); +} + +void BrowserWindow::Hide() { + window_->Hide(); +} + +bool BrowserWindow::IsVisible() { + return window_->IsVisible(); +} + +bool BrowserWindow::IsEnabled() { + return window_->IsEnabled(); +} + +void BrowserWindow::SetEnabled(bool enable) { + window_->SetEnabled(enable); +} + +void BrowserWindow::Maximize() { + window_->Maximize(); +} + +void BrowserWindow::Unmaximize() { + window_->Unmaximize(); +} + +bool BrowserWindow::IsMaximized() { + return window_->IsMaximized(); +} + +void BrowserWindow::Minimize() { + window_->Minimize(); +} + +void BrowserWindow::Restore() { + window_->Restore(); +} + +bool BrowserWindow::IsMinimized() { + return window_->IsMinimized(); +} + +void BrowserWindow::SetFullScreen(bool fullscreen) { + window_->SetFullScreen(fullscreen); +} + +bool BrowserWindow::IsFullscreen() { + return window_->IsFullscreen(); +} + +void BrowserWindow::SetBounds(const gfx::Rect& bounds, mate::Arguments* args) { + bool animate = false; + args->GetNext(&animate); + window_->SetBounds(bounds, animate); +} + +gfx::Rect BrowserWindow::GetBounds() { + return window_->GetBounds(); +} + +void BrowserWindow::SetContentBounds(const gfx::Rect& bounds, + mate::Arguments* args) { + bool animate = false; + args->GetNext(&animate); + window_->SetContentBounds(bounds, animate); +} + +gfx::Rect BrowserWindow::GetContentBounds() { + return window_->GetContentBounds(); +} + +void BrowserWindow::SetSize(int width, int height, mate::Arguments* args) { + bool animate = false; + args->GetNext(&animate); + window_->SetSize(gfx::Size(width, height), animate); +} + +std::vector BrowserWindow::GetSize() { + std::vector result(2); + gfx::Size size = window_->GetSize(); + result[0] = size.width(); + result[1] = size.height(); + return result; +} + +void BrowserWindow::SetContentSize(int width, int height, + mate::Arguments* args) { + bool animate = false; + args->GetNext(&animate); + window_->SetContentSize(gfx::Size(width, height), animate); +} + +std::vector BrowserWindow::GetContentSize() { + std::vector result(2); + gfx::Size size = window_->GetContentSize(); + result[0] = size.width(); + result[1] = size.height(); + return result; +} + +void BrowserWindow::SetMinimumSize(int width, int height) { + window_->SetMinimumSize(gfx::Size(width, height)); +} + +std::vector BrowserWindow::GetMinimumSize() { + std::vector result(2); + gfx::Size size = window_->GetMinimumSize(); + result[0] = size.width(); + result[1] = size.height(); + return result; +} + +void BrowserWindow::SetMaximumSize(int width, int height) { + window_->SetMaximumSize(gfx::Size(width, height)); +} + +std::vector BrowserWindow::GetMaximumSize() { + std::vector result(2); + gfx::Size size = window_->GetMaximumSize(); + result[0] = size.width(); + result[1] = size.height(); + return result; +} + +void BrowserWindow::SetSheetOffset(double offsetY, mate::Arguments* args) { + double offsetX = 0.0; + args->GetNext(&offsetX); + window_->SetSheetOffset(offsetX, offsetY); +} + +void BrowserWindow::SetResizable(bool resizable) { + window_->SetResizable(resizable); +} + +bool BrowserWindow::IsResizable() { + return window_->IsResizable(); +} + +void BrowserWindow::SetMovable(bool movable) { + window_->SetMovable(movable); +} + +bool BrowserWindow::IsMovable() { + return window_->IsMovable(); +} + +void BrowserWindow::SetMinimizable(bool minimizable) { + window_->SetMinimizable(minimizable); +} + +bool BrowserWindow::IsMinimizable() { + return window_->IsMinimizable(); +} + +void BrowserWindow::SetMaximizable(bool maximizable) { + window_->SetMaximizable(maximizable); +} + +bool BrowserWindow::IsMaximizable() { + return window_->IsMaximizable(); +} + +void BrowserWindow::SetFullScreenable(bool fullscreenable) { + window_->SetFullScreenable(fullscreenable); +} + +bool BrowserWindow::IsFullScreenable() { + return window_->IsFullScreenable(); +} + +void BrowserWindow::SetClosable(bool closable) { + window_->SetClosable(closable); +} + +bool BrowserWindow::IsClosable() { + return window_->IsClosable(); +} + +void BrowserWindow::SetAlwaysOnTop(bool top, mate::Arguments* args) { + std::string level = "floating"; + int relativeLevel = 0; + std::string error; + + args->GetNext(&level); + args->GetNext(&relativeLevel); + + window_->SetAlwaysOnTop(top, level, relativeLevel, &error); + + if (!error.empty()) { + args->ThrowError(error); + } +} + +bool BrowserWindow::IsAlwaysOnTop() { + return window_->IsAlwaysOnTop(); +} + +void BrowserWindow::Center() { + window_->Center(); +} + +void BrowserWindow::SetPosition(int x, int y, mate::Arguments* args) { + bool animate = false; + args->GetNext(&animate); + window_->SetPosition(gfx::Point(x, y), animate); +} + +std::vector BrowserWindow::GetPosition() { + std::vector result(2); + gfx::Point pos = window_->GetPosition(); + result[0] = pos.x(); + result[1] = pos.y(); + return result; +} + +void BrowserWindow::SetTitle(const std::string& title) { + window_->SetTitle(title); +} + +std::string BrowserWindow::GetTitle() { + return window_->GetTitle(); +} + +void BrowserWindow::FlashFrame(bool flash) { + window_->FlashFrame(flash); +} + +void BrowserWindow::SetSkipTaskbar(bool skip) { + window_->SetSkipTaskbar(skip); +} + +void BrowserWindow::SetSimpleFullScreen(bool simple_fullscreen) { + window_->SetSimpleFullScreen(simple_fullscreen); +} + +bool BrowserWindow::IsSimpleFullScreen() { + return window_->IsSimpleFullScreen(); +} + +void BrowserWindow::SetKiosk(bool kiosk) { + window_->SetKiosk(kiosk); +} + +bool BrowserWindow::IsKiosk() { + return window_->IsKiosk(); +} + +void BrowserWindow::SetBackgroundColor(const std::string& color_name) { + SkColor color = ParseHexColor(color_name); + window_->SetBackgroundColor(color); + auto* view = web_contents()->GetRenderWidgetHostView(); + if (view) + view->SetBackgroundColor(color); +} + +void BrowserWindow::SetHasShadow(bool has_shadow) { + window_->SetHasShadow(has_shadow); +} + +bool BrowserWindow::HasShadow() { + return window_->HasShadow(); +} + +void BrowserWindow::SetOpacity(const double opacity) { + window_->SetOpacity(opacity); +} + +double BrowserWindow::GetOpacity() { + return window_->GetOpacity(); +} + +void BrowserWindow::FocusOnWebView() { + web_contents()->GetRenderViewHost()->GetWidget()->Focus(); +} + +void BrowserWindow::BlurWebView() { + web_contents()->GetRenderViewHost()->GetWidget()->Blur(); +} + +bool BrowserWindow::IsWebViewFocused() { + auto host_view = web_contents()->GetRenderViewHost()->GetWidget()->GetView(); + return host_view && host_view->HasFocus(); +} + +void BrowserWindow::SetRepresentedFilename(const std::string& filename) { + window_->SetRepresentedFilename(filename); +} + +std::string BrowserWindow::GetRepresentedFilename() { + return window_->GetRepresentedFilename(); +} + +void BrowserWindow::SetDocumentEdited(bool edited) { + window_->SetDocumentEdited(edited); +} + +bool BrowserWindow::IsDocumentEdited() { + return window_->IsDocumentEdited(); +} + +void BrowserWindow::SetIgnoreMouseEvents(bool ignore, mate::Arguments* args) { + mate::Dictionary options; + bool forward = false; + args->GetNext(&options) && options.Get("forward", &forward); + return window_->SetIgnoreMouseEvents(ignore, forward); +} + +void BrowserWindow::SetContentProtection(bool enable) { + return window_->SetContentProtection(enable); +} + +void BrowserWindow::SetFocusable(bool focusable) { + return window_->SetFocusable(focusable); +} + +void BrowserWindow::SetProgressBar(double progress, mate::Arguments* args) { + mate::Dictionary options; + std::string mode; + NativeWindow::ProgressState state = NativeWindow::PROGRESS_NORMAL; + + args->GetNext(&options) && options.Get("mode", &mode); + + if (mode == "error") { + state = NativeWindow::PROGRESS_ERROR; + } else if (mode == "paused") { + state = NativeWindow::PROGRESS_PAUSED; + } else if (mode == "indeterminate") { + state = NativeWindow::PROGRESS_INDETERMINATE; + } else if (mode == "none") { + state = NativeWindow::PROGRESS_NONE; + } + + window_->SetProgressBar(progress, state); +} + +void BrowserWindow::SetOverlayIcon(const gfx::Image& overlay, + const std::string& description) { + window_->SetOverlayIcon(overlay, description); +} + +bool BrowserWindow::SetThumbarButtons(mate::Arguments* args) { +#if defined(OS_WIN) + std::vector buttons; + if (!args->GetNext(&buttons)) { + args->ThrowError(); + return false; + } + auto window = static_cast(window_.get()); + return window->taskbar_host().SetThumbarButtons( + window_->GetAcceleratedWidget(), buttons); +#else + return false; +#endif +} + +void BrowserWindow::SetMenu(v8::Isolate* isolate, v8::Local value) { + mate::Handle menu; + if (value->IsObject() && + mate::V8ToString(value->ToObject()->GetConstructorName()) == "Menu" && + mate::ConvertFromV8(isolate, value, &menu) && !menu.IsEmpty()) { + menu_.Reset(isolate, menu.ToV8()); + window_->SetMenu(menu->model()); + } else if (value->IsNull()) { + menu_.Reset(); + window_->SetMenu(nullptr); + } else { + isolate->ThrowException(v8::Exception::TypeError( + mate::StringToV8(isolate, "Invalid Menu"))); + } +} + +void BrowserWindow::SetAutoHideMenuBar(bool auto_hide) { + window_->SetAutoHideMenuBar(auto_hide); +} + +bool BrowserWindow::IsMenuBarAutoHide() { + return window_->IsMenuBarAutoHide(); +} + +void BrowserWindow::SetMenuBarVisibility(bool visible) { + window_->SetMenuBarVisibility(visible); +} + +bool BrowserWindow::IsMenuBarVisible() { + return window_->IsMenuBarVisible(); +} + +#if defined(OS_WIN) +bool BrowserWindow::HookWindowMessage(UINT message, + const MessageCallback& callback) { + messages_callback_map_[message] = callback; + return true; +} + +void BrowserWindow::UnhookWindowMessage(UINT message) { + if (!ContainsKey(messages_callback_map_, message)) + return; + + messages_callback_map_.erase(message); +} + +bool BrowserWindow::IsWindowMessageHooked(UINT message) { + return ContainsKey(messages_callback_map_, message); +} + +void BrowserWindow::UnhookAllWindowMessages() { + messages_callback_map_.clear(); +} + +bool BrowserWindow::SetThumbnailClip(const gfx::Rect& region) { + auto window = static_cast(window_.get()); + return window->taskbar_host().SetThumbnailClip( + window_->GetAcceleratedWidget(), region); +} + +bool BrowserWindow::SetThumbnailToolTip(const std::string& tooltip) { + auto window = static_cast(window_.get()); + return window->taskbar_host().SetThumbnailToolTip( + window_->GetAcceleratedWidget(), tooltip); +} + +void BrowserWindow::SetAppDetails(const mate::Dictionary& options) { + base::string16 app_id; + base::FilePath app_icon_path; + int app_icon_index = 0; + base::string16 relaunch_command; + base::string16 relaunch_display_name; + + options.Get("appId", &app_id); + options.Get("appIconPath", &app_icon_path); + options.Get("appIconIndex", &app_icon_index); + options.Get("relaunchCommand", &relaunch_command); + options.Get("relaunchDisplayName", &relaunch_display_name); + + ui::win::SetAppDetailsForWindow( + app_id, app_icon_path, app_icon_index, + relaunch_command, relaunch_display_name, + window_->GetAcceleratedWidget()); +} +#endif + +#if defined(TOOLKIT_VIEWS) +void BrowserWindow::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 BrowserWindow::SetAspectRatio(double aspect_ratio, mate::Arguments* args) { + gfx::Size extra_size; + args->GetNext(&extra_size); + window_->SetAspectRatio(aspect_ratio, extra_size); +} + +void BrowserWindow::PreviewFile(const std::string& path, + mate::Arguments* args) { + std::string display_name; + if (!args->GetNext(&display_name)) + display_name = path; + window_->PreviewFile(path, display_name); +} + +void BrowserWindow::CloseFilePreview() { + window_->CloseFilePreview(); +} + +void BrowserWindow::SetParentWindow(v8::Local value, + mate::Arguments* args) { + if (IsModal()) { + args->ThrowError("Can not be called for modal window"); + return; + } + + mate::Handle parent; + if (value->IsNull() || value->IsUndefined()) { + RemoveFromParentChildWindows(); + parent_window_.Reset(); + window_->SetParentWindow(nullptr); + } else if (mate::ConvertFromV8(isolate(), value, &parent)) { + parent_window_.Reset(isolate(), value); + window_->SetParentWindow(parent->window_.get()); + parent->child_windows_.Set(isolate(), ID(), GetWrapper()); + } else { + args->ThrowError("Must pass BrowserWindow instance or null"); + } +} + +v8::Local BrowserWindow::GetParentWindow() const { + if (parent_window_.IsEmpty()) + return v8::Null(isolate()); + else + return v8::Local::New(isolate(), parent_window_); +} + +std::vector> BrowserWindow::GetChildWindows() const { + return child_windows_.Values(isolate()); +} + +v8::Local BrowserWindow::GetBrowserView() const { + if (browser_view_.IsEmpty()) { + return v8::Null(isolate()); + } + + return v8::Local::New(isolate(), browser_view_); +} + +void BrowserWindow::SetBrowserView(v8::Local value) { + ResetBrowserView(); + + mate::Handle browser_view; + if (value->IsNull() || value->IsUndefined()) { + window_->SetBrowserView(nullptr); + } else if (mate::ConvertFromV8(isolate(), value, &browser_view)) { + window_->SetBrowserView(browser_view->view()); + browser_view->web_contents()->SetOwnerWindow(window_.get()); + browser_view_.Reset(isolate(), value); + } +} + +void BrowserWindow::ResetBrowserView() { + if (browser_view_.IsEmpty()) { + return; + } + + mate::Handle browser_view; + if (mate::ConvertFromV8(isolate(), GetBrowserView(), &browser_view) + && !browser_view.IsEmpty()) { + browser_view->web_contents()->SetOwnerWindow(nullptr); + } + + browser_view_.Reset(); +} + +bool BrowserWindow::IsModal() const { + return window_->is_modal(); +} + +v8::Local BrowserWindow::GetNativeWindowHandle() { + gfx::AcceleratedWidget handle = window_->GetAcceleratedWidget(); + return ToBuffer( + isolate(), static_cast(&handle), sizeof(gfx::AcceleratedWidget)); +} + +void BrowserWindow::SetVisibleOnAllWorkspaces(bool visible) { + return window_->SetVisibleOnAllWorkspaces(visible); +} + +bool BrowserWindow::IsVisibleOnAllWorkspaces() { + return window_->IsVisibleOnAllWorkspaces(); +} + +void BrowserWindow::SetAutoHideCursor(bool auto_hide) { + window_->SetAutoHideCursor(auto_hide); +} + +void BrowserWindow::SelectPreviousTab() { + window_->SelectPreviousTab(); +} + +void BrowserWindow::SelectNextTab() { + window_->SelectNextTab(); +} + +void BrowserWindow::MergeAllWindows() { + window_->MergeAllWindows(); +} + +void BrowserWindow::MoveTabToNewWindow() { + window_->MoveTabToNewWindow(); +} + +void BrowserWindow::ToggleTabBar() { + window_->ToggleTabBar(); +} + +void BrowserWindow::AddTabbedWindow(NativeWindow* window, + mate::Arguments* args) { + const bool windowAdded = window_->AddTabbedWindow(window); + if (!windowAdded) + args->ThrowError("AddTabbedWindow cannot be called by a window on itself."); +} + +void BrowserWindow::SetVibrancy(mate::Arguments* args) { + std::string type; + args->GetNext(&type); + + auto* render_view_host = web_contents()->GetRenderViewHost(); + if (render_view_host) { + auto* impl = content::RenderWidgetHostImpl::FromID( + render_view_host->GetProcess()->GetID(), + render_view_host->GetRoutingID()); + if (impl) + impl->SetBackgroundOpaque(type.empty() ? !window_->transparent() : false); + } + + window_->SetVibrancy(type); +} + +void BrowserWindow::SetTouchBar( + const std::vector& items) { + window_->SetTouchBar(items); +} + +void BrowserWindow::RefreshTouchBarItem(const std::string& item_id) { + window_->RefreshTouchBarItem(item_id); +} + +void BrowserWindow::SetEscapeTouchBarItem( + const mate::PersistentDictionary& item) { + window_->SetEscapeTouchBarItem(item); +} + +int32_t BrowserWindow::ID() const { + return weak_map_id(); +} + +v8::Local BrowserWindow::WebContents(v8::Isolate* isolate) { + if (web_contents_.IsEmpty()) { + return v8::Null(isolate); + } + + return v8::Local::New(isolate, web_contents_); +} + +void BrowserWindow::RemoveFromParentChildWindows() { + if (parent_window_.IsEmpty()) + return; + + mate::Handle parent; + if (!mate::ConvertFromV8(isolate(), GetParentWindow(), &parent) + || parent.IsEmpty()) { + return; + } + + parent->child_windows_.Remove(ID()); +} + +// Convert draggable regions in raw format to SkRegion format. +std::unique_ptr BrowserWindow::DraggableRegionsToSkRegion( + const std::vector& regions) { + std::unique_ptr sk_region(new SkRegion); + for (const DraggableRegion& region : regions) { + sk_region->op( + region.bounds.x(), + region.bounds.y(), + region.bounds.right(), + region.bounds.bottom(), + region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op); + } + return sk_region; +} + +void BrowserWindow::ScheduleUnresponsiveEvent(int ms) { + if (!window_unresponsive_closure_.IsCancelled()) + return; + + window_unresponsive_closure_.Reset( + base::Bind(&BrowserWindow::NotifyWindowUnresponsive, GetWeakPtr())); + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + window_unresponsive_closure_.callback(), + base::TimeDelta::FromMilliseconds(ms)); +} + +void BrowserWindow::NotifyWindowUnresponsive() { + window_unresponsive_closure_.Cancel(); + if (!window_->IsClosed() && window_->IsEnabled() && + !IsUnresponsiveEventSuppressed()) { + Emit("unresponsive"); + } +} + +// static +void BrowserWindow::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + prototype->SetClassName(mate::StringToV8(isolate, "BrowserWindow")); + mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) + .MakeDestroyable() + .SetMethod("close", &BrowserWindow::Close) + .SetMethod("focus", &BrowserWindow::Focus) + .SetMethod("blur", &BrowserWindow::Blur) + .SetMethod("isFocused", &BrowserWindow::IsFocused) + .SetMethod("show", &BrowserWindow::Show) + .SetMethod("showInactive", &BrowserWindow::ShowInactive) + .SetMethod("hide", &BrowserWindow::Hide) + .SetMethod("isVisible", &BrowserWindow::IsVisible) + .SetMethod("isEnabled", &BrowserWindow::IsEnabled) + .SetMethod("setEnabled", & BrowserWindow::SetEnabled) + .SetMethod("maximize", &BrowserWindow::Maximize) + .SetMethod("unmaximize", &BrowserWindow::Unmaximize) + .SetMethod("isMaximized", &BrowserWindow::IsMaximized) + .SetMethod("minimize", &BrowserWindow::Minimize) + .SetMethod("restore", &BrowserWindow::Restore) + .SetMethod("isMinimized", &BrowserWindow::IsMinimized) + .SetMethod("setFullScreen", &BrowserWindow::SetFullScreen) + .SetMethod("isFullScreen", &BrowserWindow::IsFullscreen) + .SetMethod("setAspectRatio", &BrowserWindow::SetAspectRatio) + .SetMethod("previewFile", &BrowserWindow::PreviewFile) + .SetMethod("closeFilePreview", &BrowserWindow::CloseFilePreview) +#if !defined(OS_WIN) + .SetMethod("setParentWindow", &BrowserWindow::SetParentWindow) +#endif + .SetMethod("getParentWindow", &BrowserWindow::GetParentWindow) + .SetMethod("getChildWindows", &BrowserWindow::GetChildWindows) + .SetMethod("getBrowserView", &BrowserWindow::GetBrowserView) + .SetMethod("setBrowserView", &BrowserWindow::SetBrowserView) + .SetMethod("isModal", &BrowserWindow::IsModal) + .SetMethod("getNativeWindowHandle", &BrowserWindow::GetNativeWindowHandle) + .SetMethod("getBounds", &BrowserWindow::GetBounds) + .SetMethod("setBounds", &BrowserWindow::SetBounds) + .SetMethod("getSize", &BrowserWindow::GetSize) + .SetMethod("setSize", &BrowserWindow::SetSize) + .SetMethod("getContentBounds", &BrowserWindow::GetContentBounds) + .SetMethod("setContentBounds", &BrowserWindow::SetContentBounds) + .SetMethod("getContentSize", &BrowserWindow::GetContentSize) + .SetMethod("setContentSize", &BrowserWindow::SetContentSize) + .SetMethod("setMinimumSize", &BrowserWindow::SetMinimumSize) + .SetMethod("getMinimumSize", &BrowserWindow::GetMinimumSize) + .SetMethod("setMaximumSize", &BrowserWindow::SetMaximumSize) + .SetMethod("getMaximumSize", &BrowserWindow::GetMaximumSize) + .SetMethod("setSheetOffset", &BrowserWindow::SetSheetOffset) + .SetMethod("setResizable", &BrowserWindow::SetResizable) + .SetMethod("isResizable", &BrowserWindow::IsResizable) + .SetMethod("setMovable", &BrowserWindow::SetMovable) + .SetMethod("isMovable", &BrowserWindow::IsMovable) + .SetMethod("setMinimizable", &BrowserWindow::SetMinimizable) + .SetMethod("isMinimizable", &BrowserWindow::IsMinimizable) + .SetMethod("setMaximizable", &BrowserWindow::SetMaximizable) + .SetMethod("isMaximizable", &BrowserWindow::IsMaximizable) + .SetMethod("setFullScreenable", &BrowserWindow::SetFullScreenable) + .SetMethod("isFullScreenable", &BrowserWindow::IsFullScreenable) + .SetMethod("setClosable", &BrowserWindow::SetClosable) + .SetMethod("isClosable", &BrowserWindow::IsClosable) + .SetMethod("setAlwaysOnTop", &BrowserWindow::SetAlwaysOnTop) + .SetMethod("isAlwaysOnTop", &BrowserWindow::IsAlwaysOnTop) + .SetMethod("center", &BrowserWindow::Center) + .SetMethod("setPosition", &BrowserWindow::SetPosition) + .SetMethod("getPosition", &BrowserWindow::GetPosition) + .SetMethod("setTitle", &BrowserWindow::SetTitle) + .SetMethod("getTitle", &BrowserWindow::GetTitle) + .SetMethod("flashFrame", &BrowserWindow::FlashFrame) + .SetMethod("setSkipTaskbar", &BrowserWindow::SetSkipTaskbar) + .SetMethod("setSimpleFullScreen", &BrowserWindow::SetSimpleFullScreen) + .SetMethod("isSimpleFullScreen", &BrowserWindow::IsSimpleFullScreen) + .SetMethod("setKiosk", &BrowserWindow::SetKiosk) + .SetMethod("isKiosk", &BrowserWindow::IsKiosk) + .SetMethod("setBackgroundColor", &BrowserWindow::SetBackgroundColor) + .SetMethod("setHasShadow", &BrowserWindow::SetHasShadow) + .SetMethod("hasShadow", &BrowserWindow::HasShadow) + .SetMethod("setOpacity", &BrowserWindow::SetOpacity) + .SetMethod("getOpacity", &BrowserWindow::GetOpacity) + .SetMethod("setRepresentedFilename", + &BrowserWindow::SetRepresentedFilename) + .SetMethod("getRepresentedFilename", + &BrowserWindow::GetRepresentedFilename) + .SetMethod("setDocumentEdited", &BrowserWindow::SetDocumentEdited) + .SetMethod("isDocumentEdited", &BrowserWindow::IsDocumentEdited) + .SetMethod("setIgnoreMouseEvents", &BrowserWindow::SetIgnoreMouseEvents) + .SetMethod("setContentProtection", &BrowserWindow::SetContentProtection) + .SetMethod("setFocusable", &BrowserWindow::SetFocusable) + .SetMethod("focusOnWebView", &BrowserWindow::FocusOnWebView) + .SetMethod("blurWebView", &BrowserWindow::BlurWebView) + .SetMethod("isWebViewFocused", &BrowserWindow::IsWebViewFocused) + .SetMethod("setProgressBar", &BrowserWindow::SetProgressBar) + .SetMethod("setOverlayIcon", &BrowserWindow::SetOverlayIcon) + .SetMethod("setThumbarButtons", &BrowserWindow::SetThumbarButtons) + .SetMethod("setMenu", &BrowserWindow::SetMenu) + .SetMethod("setAutoHideMenuBar", &BrowserWindow::SetAutoHideMenuBar) + .SetMethod("isMenuBarAutoHide", &BrowserWindow::IsMenuBarAutoHide) + .SetMethod("setMenuBarVisibility", &BrowserWindow::SetMenuBarVisibility) + .SetMethod("isMenuBarVisible", &BrowserWindow::IsMenuBarVisible) + .SetMethod("setVisibleOnAllWorkspaces", + &BrowserWindow::SetVisibleOnAllWorkspaces) + .SetMethod("isVisibleOnAllWorkspaces", + &BrowserWindow::IsVisibleOnAllWorkspaces) +#if defined(OS_MACOSX) + .SetMethod("setAutoHideCursor", &BrowserWindow::SetAutoHideCursor) + .SetMethod("mergeAllWindows", &BrowserWindow::MergeAllWindows) + .SetMethod("selectPreviousTab", &BrowserWindow::SelectPreviousTab) + .SetMethod("selectNextTab", &BrowserWindow::SelectNextTab) + .SetMethod("moveTabToNewWindow", &BrowserWindow::MoveTabToNewWindow) + .SetMethod("toggleTabBar", &BrowserWindow::ToggleTabBar) + .SetMethod("addTabbedWindow", &BrowserWindow::AddTabbedWindow) +#endif + .SetMethod("setVibrancy", &BrowserWindow::SetVibrancy) + .SetMethod("_setTouchBarItems", &BrowserWindow::SetTouchBar) + .SetMethod("_refreshTouchBarItem", &BrowserWindow::RefreshTouchBarItem) + .SetMethod("_setEscapeTouchBarItem", + &BrowserWindow::SetEscapeTouchBarItem) +#if defined(OS_WIN) + .SetMethod("hookWindowMessage", &BrowserWindow::HookWindowMessage) + .SetMethod("isWindowMessageHooked", + &BrowserWindow::IsWindowMessageHooked) + .SetMethod("unhookWindowMessage", &BrowserWindow::UnhookWindowMessage) + .SetMethod("unhookAllWindowMessages", + &BrowserWindow::UnhookAllWindowMessages) + .SetMethod("setThumbnailClip", &BrowserWindow::SetThumbnailClip) + .SetMethod("setThumbnailToolTip", &BrowserWindow::SetThumbnailToolTip) + .SetMethod("setAppDetails", &BrowserWindow::SetAppDetails) +#endif +#if defined(TOOLKIT_VIEWS) + .SetMethod("setIcon", &BrowserWindow::SetIcon) +#endif + .SetProperty("id", &BrowserWindow::ID) + .SetProperty("webContents", &BrowserWindow::WebContents); +} + +// static +v8::Local BrowserWindow::From(v8::Isolate* isolate, + NativeWindow* native_window) { + auto existing = TrackableObject::FromWrappedClass(isolate, native_window); + if (existing) + return existing->GetWrapper(); + else + return v8::Null(isolate); +} + +} // namespace api + +} // namespace atom + + +namespace { + +using atom::api::BrowserWindow; + +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { + v8::Isolate* isolate = context->GetIsolate(); + BrowserWindow::SetConstructor(isolate, base::Bind(&BrowserWindow::New)); + + mate::Dictionary browser_window( + isolate, BrowserWindow::GetConstructor(isolate)->GetFunction()); + browser_window.SetMethod( + "fromId", + &mate::TrackableObject::FromWeakMapID); + browser_window.SetMethod( + "getAllWindows", + &mate::TrackableObject::GetAll); + + mate::Dictionary dict(isolate, exports); + dict.Set("BrowserWindow", browser_window); +} + +} // namespace + +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_window, Initialize) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_browser_window.h similarity index 73% rename from atom/browser/api/atom_api_window.h rename to atom/browser/api/atom_api_browser_window.h index 657688f128b..82c2c017800 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_browser_window.h @@ -2,22 +2,23 @@ // 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_WINDOW_H_ -#define ATOM_BROWSER_API_ATOM_API_WINDOW_H_ +#ifndef ATOM_BROWSER_API_ATOM_API_BROWSER_WINDOW_H_ +#define ATOM_BROWSER_API_ATOM_API_BROWSER_WINDOW_H_ #include #include #include #include -#include "atom/browser/api/trackable_object.h" +#include "atom/browser/api/atom_api_web_contents.h" #include "atom/browser/native_window.h" #include "atom/browser/native_window_observer.h" #include "atom/common/api/atom_api_native_image.h" #include "atom/common/key_weak_map.h" -#include "native_mate/handle.h" +#include "base/cancelable_callback.h" +#include "base/memory/weak_ptr.h" +#include "content/public/browser/render_widget_host.h" #include "native_mate/persistent_dictionary.h" -#include "ui/gfx/image/image.h" class GURL; @@ -36,10 +37,11 @@ class NativeWindow; namespace api { -class WebContents; - -class Window : public mate::TrackableObject, - public NativeWindowObserver { +class BrowserWindow : public mate::TrackableObject, + public content::RenderWidgetHost::InputEventObserver, + public content::WebContentsObserver, + public ExtendedWebContentsObserver, + public NativeWindowObserver { public: static mate::WrappableBase* New(mate::Arguments* args); @@ -55,20 +57,38 @@ class Window : public mate::TrackableObject, int32_t ID() const; protected: - Window(v8::Isolate* isolate, v8::Local wrapper, - const mate::Dictionary& options); - ~Window() override; + BrowserWindow(v8::Isolate* isolate, + v8::Local wrapper, + const mate::Dictionary& options); + ~BrowserWindow() override; + + // content::RenderWidgetHost::InputEventObserver: + void OnInputEvent(const blink::WebInputEvent& event) override; + + // content::WebContentsObserver: + void RenderViewHostChanged(content::RenderViewHost* old_host, + content::RenderViewHost* new_host) override; + void RenderViewCreated(content::RenderViewHost* render_view_host) override; + void DidFirstVisuallyNonEmptyPaint() override; + void BeforeUnloadDialogCancelled() override; + void OnRendererUnresponsive(content::RenderWidgetHost*) override; + bool OnMessageReceived(const IPC::Message& message, + content::RenderFrameHost* rfh) override; + + // ExtendedWebContentsObserver: + void OnCloseContents() override; + void OnRendererResponsive() override; // NativeWindowObserver: void WillCloseWindow(bool* prevent_default) override; - void WillDestroyNativeObject() override; + void RequestPreferredWidth(int* width) override; + void OnCloseButtonClicked(bool* prevent_default) override; void OnWindowClosed() override; void OnWindowEndSession() override; void OnWindowBlur() override; void OnWindowFocus() override; void OnWindowShow() override; void OnWindowHide() override; - void OnReadyToShow() override; void OnWindowMaximize() override; void OnWindowUnmaximize() override; void OnWindowMinimize() override; @@ -78,7 +98,6 @@ class Window : public mate::TrackableObject, void OnWindowMoved() override; void OnWindowScrollTouchBegin() override; void OnWindowScrollTouchEnd() override; - void OnWindowScrollTouchEdge() override; void OnWindowSwipe(const std::string& direction) override; void OnWindowSheetBegin() override; void OnWindowSheetEnd() override; @@ -86,8 +105,6 @@ class Window : public mate::TrackableObject, void OnWindowLeaveFullScreen() override; void OnWindowEnterHtmlFullScreen() override; void OnWindowLeaveHtmlFullScreen() override; - void OnRendererUnresponsive() override; - void OnRendererResponsive() override; void OnExecuteWindowsCommand(const std::string& command_name) override; void OnTouchBarItemResult(const std::string& item_id, const base::DictionaryValue& details) override; @@ -97,11 +114,16 @@ class Window : public mate::TrackableObject, void OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) override; #endif + base::WeakPtr GetWeakPtr() { + return weak_factory_.GetWeakPtr(); + } + private: void Init(v8::Isolate* isolate, v8::Local wrapper, const mate::Dictionary& options, mate::Handle web_contents); + // APIs for NativeWindow. void Close(); void Focus(); @@ -112,6 +134,7 @@ class Window : public mate::TrackableObject, void Hide(); bool IsVisible(); bool IsEnabled(); + void SetEnabled(bool enable); void Maximize(); void Unmaximize(); bool IsMaximized(); @@ -221,7 +244,7 @@ class Window : public mate::TrackableObject, void MergeAllWindows(); void MoveTabToNewWindow(); void ToggleTabBar(); - void AddTabbedWindow(NativeWindow* window); + void AddTabbedWindow(NativeWindow* window, mate::Arguments* args); void SetVibrancy(mate::Arguments* args); void SetTouchBar(const std::vector& items); @@ -233,11 +256,34 @@ class Window : public mate::TrackableObject, // Remove this window from parent window's |child_windows_|. void RemoveFromParentChildWindows(); + // Called when the window needs to update its draggable region. + void UpdateDraggableRegions( + content::RenderFrameHost* rfh, + const std::vector& regions); + + // Convert draggable regions in raw format to SkRegion format. + std::unique_ptr DraggableRegionsToSkRegion( + const std::vector& regions); + + // Schedule a notification unresponsive event. + void ScheduleUnresponsiveEvent(int ms); + + // Dispatch unresponsive event to observers. + void NotifyWindowUnresponsive(); + #if defined(OS_WIN) typedef std::map MessageCallbackMap; MessageCallbackMap messages_callback_map_; #endif +#if defined(OS_MACOSX) + std::vector draggable_regions_; +#endif + + // Closure that would be called when window is unresponsive when closing, + // it should be cancelled when we can prove that the window is responsive. + base::CancelableClosure window_unresponsive_closure_; + v8::Global browser_view_; v8::Global web_contents_; v8::Global menu_; @@ -248,7 +294,9 @@ class Window : public mate::TrackableObject, std::unique_ptr window_; - DISALLOW_COPY_AND_ASSIGN(Window); + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(BrowserWindow); }; } // namespace api @@ -268,8 +316,8 @@ struct Converter { return true; } - atom::api::Window* window; - if (!Converter::FromV8(isolate, val, &window)) + atom::api::BrowserWindow* window; + if (!Converter::FromV8(isolate, val, &window)) return false; *out = window->window(); return true; @@ -278,4 +326,4 @@ struct Converter { } // namespace mate -#endif // ATOM_BROWSER_API_ATOM_API_WINDOW_H_ +#endif // ATOM_BROWSER_API_ATOM_API_BROWSER_WINDOW_H_ diff --git a/atom/browser/api/atom_api_browser_window_mac.mm b/atom/browser/api/atom_api_browser_window_mac.mm new file mode 100644 index 00000000000..4e63e652b38 --- /dev/null +++ b/atom/browser/api/atom_api_browser_window_mac.mm @@ -0,0 +1,120 @@ +// Copyright (c) 2018 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/api/atom_api_browser_window.h" + +#import + +#include "atom/browser/native_browser_view.h" +#include "atom/common/draggable_region.h" +#include "base/mac/scoped_nsobject.h" + +@interface NSView (WebContentsView) +- (void)setMouseDownCanMoveWindow:(BOOL)can_move; +@end + +@interface ControlRegionView : NSView +@end + +@implementation ControlRegionView + +- (BOOL)mouseDownCanMoveWindow { + return NO; +} + +- (NSView*)hitTest:(NSPoint)aPoint { + return nil; +} + +@end + +namespace atom { + +namespace api { + +namespace { + +// 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( + std::unique_ptr draggable, int width, int height) { + std::vector result; + 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()) { + result.push_back(gfx::SkIRectToRect(it.rect())); + } + return result; +} + +} // namespace + +void BrowserWindow::UpdateDraggableRegions( + content::RenderFrameHost* rfh, + const std::vector& regions) { + if (window_->has_frame()) + return; + + // All ControlRegionViews should be added as children of the WebContentsView, + // because WebContentsView will be removed and re-added when entering and + // leaving fullscreen mode. + NSView* webView = web_contents()->GetNativeView(); + NSInteger webViewWidth = NSWidth([webView bounds]); + NSInteger webViewHeight = NSHeight([webView bounds]); + + if ([webView respondsToSelector:@selector(setMouseDownCanMoveWindow:)]) { + [webView setMouseDownCanMoveWindow:YES]; + } + + // Remove all ControlRegionViews that are added last time. + // Note that [webView subviews] returns the view's mutable internal array and + // it should be copied to avoid mutating the original array while enumerating + // it. + base::scoped_nsobject subviews([[webView subviews] copy]); + for (NSView* subview in subviews.get()) + if ([subview isKindOfClass:[ControlRegionView class]]) + [subview removeFromSuperview]; + + // Draggable regions is implemented by having the whole web view draggable + // (mouseDownCanMoveWindow) and overlaying regions that are not draggable. + draggable_regions_ = regions; + std::vector system_drag_exclude_areas; + if (regions.empty()) { + system_drag_exclude_areas.push_back( + gfx::Rect(0, 0, webViewWidth, webViewHeight)); + } else { + CalculateNonDraggableRegions( + DraggableRegionsToSkRegion(regions), webViewWidth, webViewHeight); + } + + if (window_->browser_view()) + window_->browser_view()->UpdateDraggableRegions(system_drag_exclude_areas); + + // Create and add a ControlRegionView for each region that needs to be + // excluded from the dragging. + for (std::vector::const_iterator iter = + system_drag_exclude_areas.begin(); + iter != system_drag_exclude_areas.end(); + ++iter) { + base::scoped_nsobject controlRegion( + [[ControlRegionView alloc] initWithFrame:NSZeroRect]); + [controlRegion setFrame:NSMakeRect(iter->x(), + webViewHeight - iter->bottom(), + iter->width(), + iter->height())]; + [webView addSubview:controlRegion]; + } + + // AppKit will not update its cache of mouseDownCanMoveWindow unless something + // changes. Previously we tried adding an NSView and removing it, but for some + // reason it required reposting the mouse-down event, and didn't always work. + // Calling the below seems to be an effective solution. + [[webView window] setMovableByWindowBackground:NO]; + [[webView window] setMovableByWindowBackground:YES]; +} + +} // namespace api + +} // namespace atom diff --git a/atom/browser/api/atom_api_browser_window_views.cc b/atom/browser/api/atom_api_browser_window_views.cc new file mode 100644 index 00000000000..75043a83e30 --- /dev/null +++ b/atom/browser/api/atom_api_browser_window_views.cc @@ -0,0 +1,24 @@ +// Copyright (c) 2018 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/api/atom_api_browser_window.h" + +#include "atom/browser/native_window_views.h" + +namespace atom { + +namespace api { + +void BrowserWindow::UpdateDraggableRegions( + content::RenderFrameHost* rfh, + const std::vector& regions) { + if (window_->has_frame()) + return; + static_cast(window_.get())->UpdateDraggableRegions( + DraggableRegionsToSkRegion(regions)); +} + +} // namespace api + +} // namespace atom diff --git a/atom/browser/api/atom_api_content_tracing.cc b/atom/browser/api/atom_api_content_tracing.cc index 375196b2831..50b0c9799b8 100644 --- a/atom/browser/api/atom_api_content_tracing.cc +++ b/atom/browser/api/atom_api_content_tracing.cc @@ -73,4 +73,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_content_tracing, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_content_tracing, Initialize) diff --git a/atom/browser/api/atom_api_cookies.cc b/atom/browser/api/atom_api_cookies.cc index 9f58922fd0a..b0ecbea4fef 100644 --- a/atom/browser/api/atom_api_cookies.cc +++ b/atom/browser/api/atom_api_cookies.cc @@ -20,7 +20,6 @@ #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_getter.h" -using atom::AtomCookieDelegate; using content::BrowserThread; namespace mate { @@ -238,17 +237,15 @@ void SetCookieOnIO(scoped_refptr getter, } // namespace -Cookies::Cookies(v8::Isolate* isolate, - AtomBrowserContext* browser_context) - : request_context_getter_(browser_context->url_request_context_getter()), - cookie_delegate_(browser_context->cookie_delegate()) { +Cookies::Cookies(v8::Isolate* isolate, AtomBrowserContext* browser_context) + : browser_context_(browser_context), + request_context_getter_(browser_context->url_request_context_getter()) { Init(isolate); - cookie_delegate_->AddObserver(this); + cookie_change_subscription_ = browser_context->RegisterCookieChangeCallback( + base::Bind(&Cookies::OnCookieChanged, base::Unretained(this))); } -Cookies::~Cookies() { - cookie_delegate_->RemoveObserver(this); -} +Cookies::~Cookies() {} void Cookies::Get(const base::DictionaryValue& filter, const GetCallback& callback) { @@ -283,10 +280,8 @@ void Cookies::FlushStore(const base::Closure& callback) { base::Bind(FlushCookieStoreOnIOThread, getter, callback)); } -void Cookies::OnCookieChanged(const net::CanonicalCookie& cookie, - bool removed, - net::CookieStore::ChangeCause cause) { - Emit("changed", cookie, cause, removed); +void Cookies::OnCookieChanged(const CookieDetails* details) { + Emit("changed", *(details->cookie), details->cause, details->removed); } diff --git a/atom/browser/api/atom_api_cookies.h b/atom/browser/api/atom_api_cookies.h index d20dab8394c..b1fb9e0aaf7 100644 --- a/atom/browser/api/atom_api_cookies.h +++ b/atom/browser/api/atom_api_cookies.h @@ -8,7 +8,7 @@ #include #include "atom/browser/api/trackable_object.h" -#include "atom/browser/net/atom_cookie_delegate.h" +#include "atom/browser/net/cookie_details.h" #include "base/callback.h" #include "native_mate/handle.h" #include "net/cookies/canonical_cookie.h" @@ -27,8 +27,7 @@ class AtomBrowserContext; namespace api { -class Cookies : public mate::TrackableObject, - public AtomCookieDelegate::Observer { +class Cookies : public mate::TrackableObject { public: enum Error { SUCCESS, @@ -55,14 +54,16 @@ class Cookies : public mate::TrackableObject, void Set(const base::DictionaryValue& details, const SetCallback& callback); void FlushStore(const base::Closure& callback); - // AtomCookieDelegate::Observer: - void OnCookieChanged(const net::CanonicalCookie& cookie, - bool removed, - net::CookieStore::ChangeCause cause) override; + // AtomBrowserContext::RegisterCookieChangeCallback subscription: + void OnCookieChanged(const CookieDetails*); private: + // Store a reference to ensure this class gets destroyed before the context. + scoped_refptr browser_context_; + std::unique_ptr::Subscription> + cookie_change_subscription_; + net::URLRequestContextGetter* request_context_getter_; - scoped_refptr cookie_delegate_; DISALLOW_COPY_AND_ASSIGN(Cookies); }; diff --git a/atom/browser/api/atom_api_debugger.cc b/atom/browser/api/atom_api_debugger.cc index 59563bdd052..58c028e3d41 100644 --- a/atom/browser/api/atom_api_debugger.cc +++ b/atom/browser/api/atom_api_debugger.cc @@ -9,6 +9,7 @@ #include "atom/browser/atom_browser_main_parts.h" #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/value_converter.h" +#include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/memory/ptr_util.h" #include "content/public/browser/devtools_agent_host.h" @@ -48,20 +49,11 @@ void Debugger::DispatchProtocolMessage(DevToolsAgentHost* agent_host, v8::Locker locker(isolate()); v8::HandleScope handle_scope(isolate()); - v8::Local local_message = - v8::String::NewFromUtf8(isolate(), message.data()); - v8::MaybeLocal parsed_message = v8::JSON::Parse( - isolate()->GetCurrentContext(), local_message); - if (parsed_message.IsEmpty()) { + std::unique_ptr parsed_message = base::JSONReader::Read(message); + if (!parsed_message || !parsed_message->is_dict()) return; - } - - std::unique_ptr dict(new base::DictionaryValue()); - if (!mate::ConvertFromV8(isolate(), parsed_message.ToLocalChecked(), - dict.get())) { - return; - } - + base::DictionaryValue* dict = + static_cast(parsed_message.get()); int id; if (!dict->GetInteger("id", &id)) { std::string method; @@ -186,4 +178,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_debugger, Initialize); +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_debugger, Initialize); diff --git a/atom/browser/api/atom_api_desktop_capturer.cc b/atom/browser/api/atom_api_desktop_capturer.cc index 79feb7520b0..d27838ad22d 100644 --- a/atom/browser/api/atom_api_desktop_capturer.cc +++ b/atom/browser/api/atom_api_desktop_capturer.cc @@ -10,6 +10,7 @@ using base::PlatformThreadRef; #include "atom/common/native_mate_converters/gfx_converter.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/media/desktop_media_list.h" +#include "content/public/browser/desktop_capture.h" #include "native_mate/dictionary.h" #include "third_party/webrtc/modules/desktop_capture/desktop_capture_options.h" #include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h" @@ -50,17 +51,7 @@ void DesktopCapturer::StartHandling(bool capture_window, bool capture_screen, const gfx::Size& thumbnail_size) { webrtc::DesktopCaptureOptions options = - webrtc::DesktopCaptureOptions::CreateDefault(); - -#if defined(OS_WIN) - // On windows, desktop effects (e.g. Aero) will be disabled when the Desktop - // capture API is active by default. - // We keep the desktop effects in most times. Howerver, the screen still - // fickers when the API is capturing the window due to limitation of current - // implemetation. This is a known and wontFix issue in webrtc (see: - // http://code.google.com/p/webrtc/issues/detail?id=3373) - options.set_disable_effects(false); -#endif + content::CreateDesktopCaptureOptions(); std::unique_ptr screen_capturer( capture_screen ? webrtc::DesktopCapturer::CreateScreenCapturer(options) @@ -123,4 +114,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_desktop_capturer, Initialize); +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_desktop_capturer, Initialize); diff --git a/atom/browser/api/atom_api_dialog.cc b/atom/browser/api/atom_api_dialog.cc index efa8750eb9b..ff02c5fa009 100644 --- a/atom/browser/api/atom_api_dialog.cc +++ b/atom/browser/api/atom_api_dialog.cc @@ -6,7 +6,7 @@ #include #include -#include "atom/browser/api/atom_api_window.h" +#include "atom/browser/api/atom_api_browser_window.h" #include "atom/browser/native_window.h" #include "atom/browser/ui/certificate_trust.h" #include "atom/browser/ui/file_dialog.h" @@ -54,6 +54,9 @@ struct Converter { dict.Get("filters", &(out->filters)); dict.Get("properties", &(out->properties)); dict.Get("showsTagField", &(out->shows_tag_field)); + #if defined(MAS_BUILD) + dict.Get("securityScopedBookmarks", &(out->security_scoped_bookmarks)); + #endif return true; } }; @@ -137,4 +140,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_dialog, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_dialog, Initialize) diff --git a/atom/browser/api/atom_api_download_item.cc b/atom/browser/api/atom_api_download_item.cc index 3e5932cad0f..491cc18538b 100644 --- a/atom/browser/api/atom_api_download_item.cc +++ b/atom/browser/api/atom_api_download_item.cc @@ -236,4 +236,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_download_item, Initialize); +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_download_item, Initialize); diff --git a/atom/browser/api/atom_api_global_shortcut.cc b/atom/browser/api/atom_api_global_shortcut.cc index 039d708a7c1..a33ba7e45e5 100644 --- a/atom/browser/api/atom_api_global_shortcut.cc +++ b/atom/browser/api/atom_api_global_shortcut.cc @@ -98,4 +98,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_global_shortcut, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_global_shortcut, Initialize) diff --git a/atom/browser/api/atom_api_in_app_purchase.cc b/atom/browser/api/atom_api_in_app_purchase.cc new file mode 100644 index 00000000000..c30057fa070 --- /dev/null +++ b/atom/browser/api/atom_api_in_app_purchase.cc @@ -0,0 +1,115 @@ +// Copyright (c) 2017 Amaplex Software, 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_in_app_purchase.h" + +#include +#include +#include + +#include "atom/common/native_mate_converters/callback.h" +#include "native_mate/dictionary.h" + +#include "atom/common/node_includes.h" + +namespace mate { + +template <> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const in_app_purchase::Payment& payment) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.SetHidden("simple", true); + dict.Set("productIdentifier", payment.productIdentifier); + dict.Set("quantity", payment.quantity); + return dict.GetHandle(); + } +}; + +template <> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const in_app_purchase::Transaction& val) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.SetHidden("simple", true); + dict.Set("transactionIdentifier", val.transactionIdentifier); + dict.Set("transactionDate", val.transactionDate); + dict.Set("originalTransactionIdentifier", + val.originalTransactionIdentifier); + dict.Set("transactionState", val.transactionState); + dict.Set("errorCode", val.errorCode); + dict.Set("errorMessage", val.errorMessage); + dict.Set("payment", val.payment); + return dict.GetHandle(); + } +}; + +} // namespace mate + +namespace atom { + +namespace api { + +#if defined(OS_MACOSX) +// static +mate::Handle InAppPurchase::Create(v8::Isolate* isolate) { + return mate::CreateHandle(isolate, new InAppPurchase(isolate)); +} + +// static +void InAppPurchase::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + prototype->SetClassName(mate::StringToV8(isolate, "InAppPurchase")); + mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) + .SetMethod("canMakePayments", &in_app_purchase::CanMakePayments) + .SetMethod("getReceiptURL", &in_app_purchase::GetReceiptURL) + .SetMethod("purchaseProduct", &InAppPurchase::PurchaseProduct); +} + +InAppPurchase::InAppPurchase(v8::Isolate* isolate) { + Init(isolate); +} + +InAppPurchase::~InAppPurchase() { +} + +void InAppPurchase::PurchaseProduct(const std::string& product_id, + mate::Arguments* args) { + int quantity = 1; + in_app_purchase::InAppPurchaseCallback callback; + args->GetNext(&quantity); + args->GetNext(&callback); + in_app_purchase::PurchaseProduct(product_id, quantity, callback); +} + +void InAppPurchase::OnTransactionsUpdated( + const std::vector& transactions) { + Emit("transactions-updated", transactions); +} +#endif + +} // namespace api + +} // namespace atom + +namespace { + +using atom::api::InAppPurchase; + +void Initialize(v8::Local exports, + v8::Local unused, + v8::Local context, + void* priv) { +#if defined(OS_MACOSX) + v8::Isolate* isolate = context->GetIsolate(); + mate::Dictionary dict(isolate, exports); + dict.Set("inAppPurchase", InAppPurchase::Create(isolate)); + dict.Set("InAppPurchase", + InAppPurchase::GetConstructor(isolate)->GetFunction()); +#endif +} + +} // namespace + +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_in_app_purchase, Initialize) diff --git a/atom/browser/api/atom_api_in_app_purchase.h b/atom/browser/api/atom_api_in_app_purchase.h new file mode 100644 index 00000000000..3646c9b8e8f --- /dev/null +++ b/atom/browser/api/atom_api_in_app_purchase.h @@ -0,0 +1,46 @@ +// Copyright (c) 2017 Amaplex Software, 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_IN_APP_PURCHASE_H_ +#define ATOM_BROWSER_API_ATOM_API_IN_APP_PURCHASE_H_ + +#include +#include + +#include "atom/browser/api/event_emitter.h" +#include "atom/browser/mac/in_app_purchase.h" +#include "atom/browser/mac/in_app_purchase_observer.h" +#include "native_mate/handle.h" + +namespace atom { + +namespace api { + +class InAppPurchase: public mate::EventEmitter, + public in_app_purchase::TransactionObserver { + public: + static mate::Handle Create(v8::Isolate* isolate); + + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + + protected: + explicit InAppPurchase(v8::Isolate* isolate); + ~InAppPurchase() override; + + void PurchaseProduct(const std::string& product_id, mate::Arguments* args); + + // TransactionObserver: + void OnTransactionsUpdated( + const std::vector& transactions) override; + + private: + DISALLOW_COPY_AND_ASSIGN(InAppPurchase); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_ATOM_API_IN_APP_PURCHASE_H_ diff --git a/atom/browser/api/atom_api_menu.cc b/atom/browser/api/atom_api_menu.cc index da208b36591..14a38c87019 100644 --- a/atom/browser/api/atom_api_menu.cc +++ b/atom/browser/api/atom_api_menu.cc @@ -23,9 +23,13 @@ Menu::Menu(v8::Isolate* isolate, v8::Local wrapper) : model_(new AtomMenuModel(this)), parent_(nullptr) { InitWith(isolate, wrapper); + model_->AddObserver(this); } Menu::~Menu() { + if (model_) { + model_->RemoveObserver(this); + } } void Menu::AfterInit(v8::Isolate* isolate) { @@ -43,15 +47,21 @@ void Menu::AfterInit(v8::Isolate* isolate) { } bool Menu::IsCommandIdChecked(int command_id) const { - return is_checked_.Run(command_id); + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + return is_checked_.Run(GetWrapper(), command_id); } bool Menu::IsCommandIdEnabled(int command_id) const { - return is_enabled_.Run(command_id); + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + return is_enabled_.Run(GetWrapper(), command_id); } bool Menu::IsCommandIdVisible(int command_id) const { - return is_visible_.Run(command_id); + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + return is_visible_.Run(GetWrapper(), command_id); } bool Menu::GetAcceleratorForCommandIdWithParams( @@ -61,18 +71,23 @@ bool Menu::GetAcceleratorForCommandIdWithParams( v8::Locker locker(isolate()); v8::HandleScope handle_scope(isolate()); v8::Local val = get_accelerator_.Run( - command_id, use_default_accelerator); + GetWrapper(), command_id, use_default_accelerator); return mate::ConvertFromV8(isolate(), val, accelerator); } void Menu::ExecuteCommand(int command_id, int flags) { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); execute_command_.Run( + GetWrapper(), mate::internal::CreateEventFromFlags(isolate(), flags), command_id); } void Menu::MenuWillShow(ui::SimpleMenuModel* source) { - menu_will_show_.Run(); + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + menu_will_show_.Run(GetWrapper()); } void Menu::InsertItemAt( @@ -153,6 +168,14 @@ bool Menu::IsVisibleAt(int index) const { return model_->IsVisibleAt(index); } +void Menu::OnMenuWillClose() { + Emit("menu-will-close"); +} + +void Menu::OnMenuWillShow() { + Emit("menu-will-show"); +} + // static void Menu::BuildPrototype(v8::Isolate* isolate, v8::Local prototype) { @@ -205,4 +228,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_menu, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_menu, Initialize) diff --git a/atom/browser/api/atom_api_menu.h b/atom/browser/api/atom_api_menu.h index f2316fa1893..94cd6d5cc8a 100644 --- a/atom/browser/api/atom_api_menu.h +++ b/atom/browser/api/atom_api_menu.h @@ -8,7 +8,7 @@ #include #include -#include "atom/browser/api/atom_api_window.h" +#include "atom/browser/api/atom_api_browser_window.h" #include "atom/browser/api/trackable_object.h" #include "atom/browser/ui/atom_menu_model.h" #include "base/callback.h" @@ -18,7 +18,8 @@ namespace atom { namespace api { class Menu : public mate::TrackableObject, - public AtomMenuModel::Delegate { + public AtomMenuModel::Delegate, + public AtomMenuModel::Observer { public: static mate::WrappableBase* New(mate::Arguments* args); @@ -53,12 +54,18 @@ class Menu : public mate::TrackableObject, void ExecuteCommand(int command_id, int event_flags) override; void MenuWillShow(ui::SimpleMenuModel* source) override; - virtual void PopupAt(Window* window, int x, int y, int positioning_item) = 0; + virtual void PopupAt(BrowserWindow* window, + int x, int y, int positioning_item, + const base::Closure& callback) = 0; virtual void ClosePopupAt(int32_t window_id) = 0; std::unique_ptr model_; Menu* parent_; + // Observable: + void OnMenuWillClose() override; + void OnMenuWillShow() override; + private: void InsertItemAt(int index, int command_id, const base::string16& label); void InsertSeparatorAt(int index); @@ -87,12 +94,14 @@ class Menu : public mate::TrackableObject, bool IsVisibleAt(int index) const; // Stored delegate methods. - base::Callback is_checked_; - base::Callback is_enabled_; - base::Callback is_visible_; - base::Callback(int, bool)> get_accelerator_; - base::Callback, int)> execute_command_; - base::Callback menu_will_show_; + base::Callback, int)> is_checked_; + base::Callback, int)> is_enabled_; + base::Callback, int)> is_visible_; + base::Callback(v8::Local, int, bool)> + get_accelerator_; + base::Callback, v8::Local, int)> + execute_command_; + base::Callback)> menu_will_show_; DISALLOW_COPY_AND_ASSIGN(Menu); }; diff --git a/atom/browser/api/atom_api_menu_mac.h b/atom/browser/api/atom_api_menu_mac.h index ed897a7907a..252801ca072 100644 --- a/atom/browser/api/atom_api_menu_mac.h +++ b/atom/browser/api/atom_api_menu_mac.h @@ -22,18 +22,21 @@ class MenuMac : public Menu { protected: MenuMac(v8::Isolate* isolate, v8::Local wrapper); - void PopupAt(Window* window, int x, int y, int positioning_item) override; + void PopupAt(BrowserWindow* window, + int x, int y, int positioning_item, + const base::Closure& callback) override; void PopupOnUI(const base::WeakPtr& native_window, int32_t window_id, int x, int y, - int positioning_item); + int positioning_item, + base::Closure callback); void ClosePopupAt(int32_t window_id) override; private: friend class Menu; - static void SendActionToFirstResponder(const std::string& action); + void OnClosed(int32_t window_id, base::Closure callback); scoped_nsobject menu_controller_; diff --git a/atom/browser/api/atom_api_menu_mac.mm b/atom/browser/api/atom_api_menu_mac.mm index e9ed004938c..ed491730a11 100644 --- a/atom/browser/api/atom_api_menu_mac.mm +++ b/atom/browser/api/atom_api_menu_mac.mm @@ -27,14 +27,16 @@ MenuMac::MenuMac(v8::Isolate* isolate, v8::Local wrapper) weak_factory_(this) { } -void MenuMac::PopupAt(Window* window, int x, int y, int positioning_item) { +void MenuMac::PopupAt(BrowserWindow* window, + int x, int y, int positioning_item, + const base::Closure& callback) { NativeWindow* native_window = window->window(); if (!native_window) return; auto popup = base::Bind(&MenuMac::PopupOnUI, weak_factory_.GetWeakPtr(), native_window->GetWeakPtr(), window->ID(), x, y, - positioning_item); + positioning_item, callback); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, popup); } @@ -42,21 +44,19 @@ void MenuMac::PopupOnUI(const base::WeakPtr& native_window, int32_t window_id, int x, int y, - int positioning_item) { + int positioning_item, + base::Closure callback) { if (!native_window) return; - brightray::InspectableWebContents* web_contents = - native_window->inspectable_web_contents(); - if (!web_contents) - return; + NSWindow* nswindow = native_window->GetNativeWindow(); - auto close_callback = base::Bind(&MenuMac::ClosePopupAt, - weak_factory_.GetWeakPtr(), window_id); + auto close_callback = base::Bind( + &MenuMac::OnClosed, weak_factory_.GetWeakPtr(), window_id, callback); popup_controllers_[window_id] = base::scoped_nsobject( [[AtomMenuController alloc] initWithModel:model() useDefaultAccelerator:NO]); NSMenu* menu = [popup_controllers_[window_id] menu]; - NSView* view = web_contents->GetView()->GetNativeView(); + NSView* view = [nswindow contentView]; // Which menu item to show. NSMenuItem* item = nil; @@ -66,7 +66,6 @@ void MenuMac::PopupOnUI(const base::WeakPtr& native_window, // (-1, -1) means showing on mouse location. NSPoint position; if (x == -1 || y == -1) { - NSWindow* nswindow = native_window->GetNativeWindow(); position = [view convertPoint:[nswindow mouseLocationOutsideOfEventStream] fromView:nil]; } else { @@ -108,7 +107,23 @@ void MenuMac::PopupOnUI(const base::WeakPtr& native_window, } void MenuMac::ClosePopupAt(int32_t window_id) { + auto controller = popup_controllers_.find(window_id); + if (controller != popup_controllers_.end()) { + // Close the controller for the window. + [controller->second cancel]; + } else if (window_id == -1) { + // Or just close all opened controllers. + for (auto it = popup_controllers_.begin(); + it != popup_controllers_.end();) { + // The iterator is invalidated after the call. + [(it++)->second cancel]; + } + } +} + +void MenuMac::OnClosed(int32_t window_id, base::Closure callback) { popup_controllers_.erase(window_id); + callback.Run(); } // static diff --git a/atom/browser/api/atom_api_menu_views.cc b/atom/browser/api/atom_api_menu_views.cc index 7a53d5d5530..6e710838ac0 100644 --- a/atom/browser/api/atom_api_menu_views.cc +++ b/atom/browser/api/atom_api_menu_views.cc @@ -6,7 +6,8 @@ #include "atom/browser/native_window_views.h" #include "atom/browser/unresponsive_suppressor.h" -#include "content/public/browser/render_widget_host_view.h" +#include "brightray/browser/inspectable_web_contents.h" +#include "brightray/browser/inspectable_web_contents_view.h" #include "ui/display/screen.h" using views::MenuRunner; @@ -20,23 +21,20 @@ MenuViews::MenuViews(v8::Isolate* isolate, v8::Local wrapper) weak_factory_(this) { } -void MenuViews::PopupAt(Window* window, int x, int y, int positioning_item) { - NativeWindow* native_window = static_cast(window->window()); +void MenuViews::PopupAt(BrowserWindow* window, + int x, int y, int positioning_item, + const base::Closure& callback) { + auto* native_window = static_cast(window->window()); if (!native_window) return; - content::WebContents* web_contents = native_window->web_contents(); - if (!web_contents) - return; - content::RenderWidgetHostView* view = web_contents->GetRenderWidgetHostView(); - if (!view) - return; // (-1, -1) means showing on mouse location. gfx::Point location; if (x == -1 || y == -1) { location = display::Screen::GetScreen()->GetCursorScreenPoint(); } else { - gfx::Point origin = view->GetViewBounds().origin(); + views::View* view = native_window; // the instance is also its content view + gfx::Point origin = view->bounds().origin(); location = gfx::Point(origin.x() + x, origin.y() + y); } @@ -48,11 +46,11 @@ void MenuViews::PopupAt(Window* window, int x, int y, int positioning_item) { // Show the menu. int32_t window_id = window->ID(); auto close_callback = base::Bind( - &MenuViews::ClosePopupAt, weak_factory_.GetWeakPtr(), window_id); + &MenuViews::OnClosed, weak_factory_.GetWeakPtr(), window_id, callback); menu_runners_[window_id] = std::unique_ptr(new MenuRunner( model(), flags, close_callback)); menu_runners_[window_id]->RunMenuAt( - static_cast(window->window())->widget(), + native_window->widget(), NULL, gfx::Rect(location, gfx::Size()), views::MENU_ANCHOR_TOPLEFT, @@ -60,7 +58,22 @@ void MenuViews::PopupAt(Window* window, int x, int y, int positioning_item) { } void MenuViews::ClosePopupAt(int32_t window_id) { + auto runner = menu_runners_.find(window_id); + if (runner != menu_runners_.end()) { + // Close the runner for the window. + runner->second->Cancel(); + } else if (window_id == -1) { + // Or just close all opened runners. + for (auto it = menu_runners_.begin(); it != menu_runners_.end();) { + // The iterator is invalidated after the call. + (it++)->second->Cancel(); + } + } +} + +void MenuViews::OnClosed(int32_t window_id, base::Closure callback) { menu_runners_.erase(window_id); + callback.Run(); } // static diff --git a/atom/browser/api/atom_api_menu_views.h b/atom/browser/api/atom_api_menu_views.h index d0c33b3960e..ba82b6d336b 100644 --- a/atom/browser/api/atom_api_menu_views.h +++ b/atom/browser/api/atom_api_menu_views.h @@ -21,10 +21,14 @@ class MenuViews : public Menu { MenuViews(v8::Isolate* isolate, v8::Local wrapper); protected: - void PopupAt(Window* window, int x, int y, int positioning_item) override; + void PopupAt(BrowserWindow* window, + int x, int y, int positioning_item, + const base::Closure& callback) override; void ClosePopupAt(int32_t window_id) override; private: + void OnClosed(int32_t window_id, base::Closure callback); + // window ID -> open context menu std::map> menu_runners_; diff --git a/atom/browser/api/atom_api_net.cc b/atom/browser/api/atom_api_net.cc index 24008ed7aee..77393c92565 100644 --- a/atom/browser/api/atom_api_net.cc +++ b/atom/browser/api/atom_api_net.cc @@ -58,4 +58,4 @@ void Initialize(v8::Local exports, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_net, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_net, Initialize) diff --git a/atom/browser/api/atom_api_notification.cc b/atom/browser/api/atom_api_notification.cc index 9bb24df93a6..3eb99be4325 100644 --- a/atom/browser/api/atom_api_notification.cc +++ b/atom/browser/api/atom_api_notification.cc @@ -70,6 +70,7 @@ Notification::Notification(v8::Isolate* isolate, opts.Get("hasReply", &has_reply_); opts.Get("actions", &actions_); opts.Get("sound", &sound_); + opts.Get("closeButtonText", &close_button_text_); } } @@ -104,20 +105,24 @@ bool Notification::GetSilent() const { return silent_; } +bool Notification::GetHasReply() const { + return has_reply_; +} + base::string16 Notification::GetReplyPlaceholder() const { return reply_placeholder_; } -bool Notification::GetHasReply() const { - return has_reply_; +base::string16 Notification::GetSound() const { + return sound_; } std::vector Notification::GetActions() const { return actions_; } -base::string16 Notification::GetSound() const { - return sound_; +base::string16 Notification::GetCloseButtonText() const { + return close_button_text_; } // Setters @@ -137,12 +142,16 @@ void Notification::SetSilent(bool new_silent) { silent_ = new_silent; } +void Notification::SetHasReply(bool new_has_reply) { + has_reply_ = new_has_reply; +} + void Notification::SetReplyPlaceholder(const base::string16& new_placeholder) { reply_placeholder_ = new_placeholder; } -void Notification::SetHasReply(bool new_has_reply) { - has_reply_ = new_has_reply; +void Notification::SetSound(const base::string16& new_sound) { + sound_ = new_sound; } void Notification::SetActions( @@ -150,8 +159,8 @@ void Notification::SetActions( actions_ = actions; } -void Notification::SetSound(const base::string16& new_sound) { - sound_ = new_sound; +void Notification::SetCloseButtonText(const base::string16& text) { + close_button_text_ = text; } void Notification::NotificationAction(int index) { @@ -201,6 +210,7 @@ void Notification::Show() { options.reply_placeholder = reply_placeholder_; options.actions = actions_; options.sound = sound_; + options.close_button_text = close_button_text_; notification_->Show(options); } } @@ -222,15 +232,18 @@ void Notification::BuildPrototype(v8::Isolate* isolate, .SetProperty("subtitle", &Notification::GetSubtitle, &Notification::SetSubtitle) .SetProperty("body", &Notification::GetBody, &Notification::SetBody) - .SetProperty("silent", &Notification::GetSilent, &Notification::SetSilent) - .SetProperty("replyPlaceholder", &Notification::GetReplyPlaceholder, - &Notification::SetReplyPlaceholder) + .SetProperty("silent", &Notification::GetSilent, + &Notification::SetSilent) .SetProperty("hasReply", &Notification::GetHasReply, &Notification::SetHasReply) + .SetProperty("replyPlaceholder", &Notification::GetReplyPlaceholder, + &Notification::SetReplyPlaceholder) + .SetProperty("sound", &Notification::GetSound, + &Notification::SetSound) .SetProperty("actions", &Notification::GetActions, &Notification::SetActions) - .SetProperty("sound", &Notification::GetSound, - &Notification::SetSound); + .SetProperty("closeButtonText", &Notification::GetCloseButtonText, + &Notification::SetCloseButtonText); } } // namespace api @@ -257,4 +270,4 @@ void Initialize(v8::Local exports, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_common_notification, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_common_notification, Initialize) diff --git a/atom/browser/api/atom_api_notification.h b/atom/browser/api/atom_api_notification.h index 50197350bc5..91a5b72c983 100644 --- a/atom/browser/api/atom_api_notification.h +++ b/atom/browser/api/atom_api_notification.h @@ -52,20 +52,22 @@ class Notification : public mate::TrackableObject, base::string16 GetSubtitle() const; base::string16 GetBody() const; bool GetSilent() const; - base::string16 GetReplyPlaceholder() const; bool GetHasReply() const; - std::vector GetActions() const; + base::string16 GetReplyPlaceholder() const; base::string16 GetSound() const; + std::vector GetActions() const; + base::string16 GetCloseButtonText() const; // Prop Setters void SetTitle(const base::string16& new_title); void SetSubtitle(const base::string16& new_subtitle); void SetBody(const base::string16& new_body); void SetSilent(bool new_silent); - void SetReplyPlaceholder(const base::string16& new_reply_placeholder); void SetHasReply(bool new_has_reply); - void SetActions(const std::vector& actions); + void SetReplyPlaceholder(const base::string16& new_reply_placeholder); void SetSound(const base::string16& sound); + void SetActions(const std::vector& actions); + void SetCloseButtonText(const base::string16& text); private: base::string16 title_; @@ -75,10 +77,11 @@ class Notification : public mate::TrackableObject, base::string16 icon_path_; bool has_icon_ = false; bool silent_ = false; - base::string16 reply_placeholder_; bool has_reply_ = false; - std::vector actions_; + base::string16 reply_placeholder_; base::string16 sound_; + std::vector actions_; + base::string16 close_button_text_; brightray::NotificationPresenter* presenter_; diff --git a/atom/browser/api/atom_api_power_monitor.cc b/atom/browser/api/atom_api_power_monitor.cc index 429d3fc26e8..fce749790b5 100644 --- a/atom/browser/api/atom_api_power_monitor.cc +++ b/atom/browser/api/atom_api_power_monitor.cc @@ -16,6 +16,13 @@ namespace atom { namespace api { PowerMonitor::PowerMonitor(v8::Isolate* isolate) { +#if defined(OS_LINUX) + SetShutdownHandler(base::Bind(&PowerMonitor::ShouldShutdown, + base::Unretained(this))); +#elif defined(OS_MACOSX) + Browser::Get()->SetShutdownHandler(base::Bind(&PowerMonitor::ShouldShutdown, + base::Unretained(this))); +#endif base::PowerMonitor::Get()->AddObserver(this); Init(isolate); } @@ -24,6 +31,20 @@ PowerMonitor::~PowerMonitor() { base::PowerMonitor::Get()->RemoveObserver(this); } +bool PowerMonitor::ShouldShutdown() { + return !Emit("shutdown"); +} + +#if defined(OS_LINUX) +void PowerMonitor::BlockShutdown() { + PowerObserverLinux::BlockShutdown(); +} + +void PowerMonitor::UnblockShutdown() { + PowerObserverLinux::UnblockShutdown(); +} +#endif + void PowerMonitor::OnPowerStateChange(bool on_battery_power) { if (on_battery_power) Emit("on-battery"); @@ -55,6 +76,11 @@ v8::Local PowerMonitor::Create(v8::Isolate* isolate) { void PowerMonitor::BuildPrototype( v8::Isolate* isolate, v8::Local prototype) { prototype->SetClassName(mate::StringToV8(isolate, "PowerMonitor")); +#if defined(OS_LINUX) + mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) + .SetMethod("blockShutdown", &PowerMonitor::BlockShutdown) + .SetMethod("unblockShutdown", &PowerMonitor::UnblockShutdown); +#endif } } // namespace api @@ -68,10 +94,6 @@ using atom::api::PowerMonitor; void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { -#if defined(OS_MACOSX) - base::PowerMonitorDeviceSource::AllocateSystemIOPorts(); -#endif - v8::Isolate* isolate = context->GetIsolate(); mate::Dictionary dict(isolate, exports); dict.Set("powerMonitor", PowerMonitor::Create(isolate)); @@ -81,4 +103,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_power_monitor, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_power_monitor, Initialize) diff --git a/atom/browser/api/atom_api_power_monitor.h b/atom/browser/api/atom_api_power_monitor.h index 94717e4c908..90971556de8 100644 --- a/atom/browser/api/atom_api_power_monitor.h +++ b/atom/browser/api/atom_api_power_monitor.h @@ -6,8 +6,8 @@ #define ATOM_BROWSER_API_ATOM_API_POWER_MONITOR_H_ #include "atom/browser/api/trackable_object.h" +#include "atom/browser/lib/power_observer.h" #include "base/compiler_specific.h" -#include "base/power_monitor/power_observer.h" #include "native_mate/handle.h" namespace atom { @@ -15,7 +15,7 @@ namespace atom { namespace api { class PowerMonitor : public mate::TrackableObject, - public base::PowerObserver { + public PowerObserver { public: static v8::Local Create(v8::Isolate* isolate); @@ -26,6 +26,15 @@ class PowerMonitor : public mate::TrackableObject, explicit PowerMonitor(v8::Isolate* isolate); ~PowerMonitor() override; + // Called by native calles. + bool ShouldShutdown(); + +#if defined(OS_LINUX) + // Private JS APIs. + void BlockShutdown(); + void UnblockShutdown(); +#endif + // base::PowerObserver implementations: void OnPowerStateChange(bool on_battery_power) override; void OnSuspend() override; diff --git a/atom/browser/api/atom_api_power_save_blocker.cc b/atom/browser/api/atom_api_power_save_blocker.cc index f430e28c3ba..37aef91af9c 100644 --- a/atom/browser/api/atom_api_power_save_blocker.cc +++ b/atom/browser/api/atom_api_power_save_blocker.cc @@ -133,4 +133,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_power_save_blocker, Initialize); +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_power_save_blocker, Initialize); diff --git a/atom/browser/api/atom_api_protocol.cc b/atom/browser/api/atom_api_protocol.cc index 6ea1d8b7577..b904c88bfcc 100644 --- a/atom/browser/api/atom_api_protocol.cc +++ b/atom/browser/api/atom_api_protocol.cc @@ -253,4 +253,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_protocol, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_protocol, Initialize) diff --git a/atom/browser/api/atom_api_protocol.h b/atom/browser/api/atom_api_protocol.h index 40dc30600f9..dfc32be6bc5 100644 --- a/atom/browser/api/atom_api_protocol.h +++ b/atom/browser/api/atom_api_protocol.h @@ -78,10 +78,6 @@ class Protocol : public mate::TrackableObject { net::URLRequestJob* MaybeCreateJob( net::URLRequest* request, net::NetworkDelegate* network_delegate) const override { - if (!request->initiator().has_value()) { - // Don't intercept this request as it was created by `net.request`. - return nullptr; - } RequestJob* request_job = new RequestJob(request, network_delegate); request_job->SetHandlerInfo(isolate_, request_context_.get(), handler_); return request_job; diff --git a/atom/browser/api/atom_api_render_process_preferences.cc b/atom/browser/api/atom_api_render_process_preferences.cc index 5786d694cfd..d57a8e78a41 100644 --- a/atom/browser/api/atom_api_render_process_preferences.cc +++ b/atom/browser/api/atom_api_render_process_preferences.cc @@ -86,5 +86,5 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_render_process_preferences, +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_render_process_preferences, Initialize) diff --git a/atom/browser/api/atom_api_screen.cc b/atom/browser/api/atom_api_screen.cc index a87d6d3598f..2e94e05da35 100644 --- a/atom/browser/api/atom_api_screen.cc +++ b/atom/browser/api/atom_api_screen.cc @@ -144,4 +144,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_common_screen, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_common_screen, Initialize) diff --git a/atom/browser/api/atom_api_screen_mac.mm b/atom/browser/api/atom_api_screen_mac.mm index 0d22fad5b8b..056b775fdfc 100644 --- a/atom/browser/api/atom_api_screen_mac.mm +++ b/atom/browser/api/atom_api_screen_mac.mm @@ -9,6 +9,7 @@ namespace atom { namespace api { +//TODO(codebytere): deprecated; remove in 3.0 int Screen::getMenuBarHeight() { return [[NSApp mainMenu] menuBarHeight]; } diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index 0a124c44662..86d4e4bb227 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -17,6 +17,7 @@ #include "atom/browser/atom_permission_manager.h" #include "atom/browser/browser.h" #include "atom/browser/net/atom_cert_verifier.h" +#include "atom/browser/session_preferences.h" #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/content_converter.h" #include "atom/common/native_mate_converters/file_path_converter.h" @@ -32,6 +33,7 @@ #include "brightray/browser/media/media_device_id_salt.h" #include "brightray/browser/net/devtools_network_conditions.h" #include "brightray/browser/net/devtools_network_controller_handle.h" +#include "chrome/browser/browser_process.h" #include "chrome/common/pref_names.h" #include "components/prefs/pref_service.h" #include "content/public/browser/browser_thread.h" @@ -438,6 +440,18 @@ void DownloadIdCallback(content::DownloadManager* download_manager, std::vector()); } +void SetDevToolsNetworkEmulationClientIdInIO( + brightray::URLRequestContextGetter* url_request_context_getter, + const std::string& client_id) { + if (!url_request_context_getter) + return; + net::URLRequestContext* context = + url_request_context_getter->GetURLRequestContext(); + AtomNetworkDelegate* network_delegate = + static_cast(context->network_delegate()); + network_delegate->SetDevToolsNetworkEmulationClientId(client_id); +} + } // namespace Session::Session(v8::Isolate* isolate, AtomBrowserContext* browser_context) @@ -447,6 +461,8 @@ Session::Session(v8::Isolate* isolate, AtomBrowserContext* browser_context) content::BrowserContext::GetDownloadManager(browser_context)-> AddObserver(this); + new SessionPreferences(browser_context); + Init(isolate); AttachAsUserData(browser_context); } @@ -545,16 +561,24 @@ void Session::EnableNetworkEmulation(const mate::Dictionary& options) { browser_context_->network_controller_handle()->SetNetworkState( devtools_network_emulation_client_id_, std::move(conditions)); - browser_context_->network_delegate()->SetDevToolsNetworkEmulationClientId( - devtools_network_emulation_client_id_); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind( + &SetDevToolsNetworkEmulationClientIdInIO, + base::RetainedRef(browser_context_->url_request_context_getter()), + devtools_network_emulation_client_id_)); } void Session::DisableNetworkEmulation() { 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()); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind( + &SetDevToolsNetworkEmulationClientIdInIO, + base::RetainedRef(browser_context_->url_request_context_getter()), + std::string())); } void Session::SetCertVerifyProc(v8::Local val, @@ -620,7 +644,7 @@ void Session::SetUserAgent(const std::string& user_agent, mate::Arguments* args) { browser_context_->SetUserAgent(user_agent); - std::string accept_lang = l10n_util::GetApplicationLocale(""); + std::string accept_lang = g_browser_process->GetApplicationLocale(); args->GetNext(&accept_lang); scoped_refptr getter( @@ -680,6 +704,19 @@ void Session::CreateInterruptedDownload(const mate::Dictionary& options) { length, last_modified, etag, base::Time::FromDoubleT(start_time))); } +void Session::SetPreloads( + const std::vector& preloads) { + auto* prefs = SessionPreferences::FromBrowserContext(browser_context()); + DCHECK(prefs); + prefs->set_preloads(preloads); +} + +std::vector Session::GetPreloads() const { + auto* prefs = SessionPreferences::FromBrowserContext(browser_context()); + DCHECK(prefs); + return prefs->preloads(); +} + v8::Local Session::Cookies(v8::Isolate* isolate) { if (cookies_.IsEmpty()) { auto handle = Cookies::Create(isolate, browser_context()); @@ -766,6 +803,8 @@ void Session::BuildPrototype(v8::Isolate* isolate, .SetMethod("getBlobData", &Session::GetBlobData) .SetMethod("createInterruptedDownload", &Session::CreateInterruptedDownload) + .SetMethod("setPreloads", &Session::SetPreloads) + .SetMethod("getPreloads", &Session::GetPreloads) .SetProperty("cookies", &Session::Cookies) .SetProperty("protocol", &Session::Protocol) .SetProperty("webRequest", &Session::WebRequest); @@ -801,4 +840,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_session, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_session, Initialize) diff --git a/atom/browser/api/atom_api_session.h b/atom/browser/api/atom_api_session.h index 72f186e4fee..d56c2400d4d 100644 --- a/atom/browser/api/atom_api_session.h +++ b/atom/browser/api/atom_api_session.h @@ -6,6 +6,7 @@ #define ATOM_BROWSER_API_ATOM_API_SESSION_H_ #include +#include #include "atom/browser/api/trackable_object.h" #include "atom/browser/atom_blob_reader.h" @@ -81,6 +82,8 @@ class Session: public mate::TrackableObject, void GetBlobData(const std::string& uuid, const AtomBlobReader::CompletionCallback& callback); void CreateInterruptedDownload(const mate::Dictionary& options); + void SetPreloads(const std::vector& preloads); + std::vector GetPreloads() const; v8::Local Cookies(v8::Isolate* isolate); v8::Local Protocol(v8::Isolate* isolate); v8::Local WebRequest(v8::Isolate* isolate); diff --git a/atom/browser/api/atom_api_system_preferences.cc b/atom/browser/api/atom_api_system_preferences.cc index 74d6d03ce7c..2b18d0f7974 100644 --- a/atom/browser/api/atom_api_system_preferences.cc +++ b/atom/browser/api/atom_api_system_preferences.cc @@ -65,6 +65,7 @@ void SystemPreferences::BuildPrototype( &SystemPreferences::SubscribeLocalNotification) .SetMethod("unsubscribeLocalNotification", &SystemPreferences::UnsubscribeLocalNotification) + .SetMethod("registerDefaults", &SystemPreferences::RegisterDefaults) .SetMethod("getUserDefault", &SystemPreferences::GetUserDefault) .SetMethod("setUserDefault", &SystemPreferences::SetUserDefault) .SetMethod("removeUserDefault", &SystemPreferences::RemoveUserDefault) @@ -95,4 +96,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_system_preferences, Initialize); +NODE_BUILTIN_MODULE_CONTEXT_AWARE(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 index ea5daed94a5..4e432d7e51e 100644 --- a/atom/browser/api/atom_api_system_preferences.h +++ b/atom/browser/api/atom_api_system_preferences.h @@ -73,6 +73,7 @@ class SystemPreferences : public mate::EventEmitter void UnsubscribeLocalNotification(int request_id); v8::Local GetUserDefault(const std::string& name, const std::string& type); + void RegisterDefaults(mate::Arguments* args); void SetUserDefault(const std::string& name, const std::string& type, mate::Arguments* args); diff --git a/atom/browser/api/atom_api_system_preferences_mac.mm b/atom/browser/api/atom_api_system_preferences_mac.mm index 58e9b848e25..8f7ce7f144c 100644 --- a/atom/browser/api/atom_api_system_preferences_mac.mm +++ b/atom/browser/api/atom_api_system_preferences_mac.mm @@ -144,6 +144,28 @@ v8::Local SystemPreferences::GetUserDefault( } } +void SystemPreferences::RegisterDefaults(mate::Arguments* args) { + base::DictionaryValue value; + + if(!args->GetNext(&value)) { + args->ThrowError("Invalid userDefault data provided"); + } else { + @try { + NSDictionary* dict = DictionaryValueToNSDictionary(value); + for (id key in dict) { + id value = [dict objectForKey:key]; + if ([value isKindOfClass:[NSNull class]] || value == nil) { + args->ThrowError("Invalid userDefault data provided"); + return; + } + } + [[NSUserDefaults standardUserDefaults] registerDefaults:dict]; + } @catch (NSException* exception) { + args->ThrowError("Invalid userDefault data provided"); + } + } +} + void SystemPreferences::SetUserDefault(const std::string& name, const std::string& type, mate::Arguments* args) { diff --git a/atom/browser/api/atom_api_system_preferences_win.cc b/atom/browser/api/atom_api_system_preferences_win.cc index 43a2f2fc944..f9bbd294ee5 100644 --- a/atom/browser/api/atom_api_system_preferences_win.cc +++ b/atom/browser/api/atom_api_system_preferences_win.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. +#include + #include "atom/browser/api/atom_api_system_preferences.h" #include "atom/common/color_util.h" @@ -26,10 +28,10 @@ bool SystemPreferences::IsAeroGlassEnabled() { } std::string hexColorDWORDToRGBA(DWORD color) { + DWORD rgba = color << 8 | color >> 24; std::ostringstream stream; - stream << std::hex << color; - std::string hexColor = stream.str(); - return hexColor.substr(2) + hexColor.substr(0, 2); + stream << std::hex << std::setw(8) << std::setfill('0') << rgba; + return stream.str(); } std::string SystemPreferences::GetAccentColor() { diff --git a/atom/browser/api/atom_api_tray.cc b/atom/browser/api/atom_api_tray.cc index e40d7660eae..6a032e20279 100644 --- a/atom/browser/api/atom_api_tray.cc +++ b/atom/browser/api/atom_api_tray.cc @@ -40,8 +40,6 @@ struct Converter { } } - // Support old boolean parameter - // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings bool highlight; if (ConvertFromV8(isolate, val, &highlight)) { if (highlight) @@ -252,4 +250,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_tray, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_tray, Initialize) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index b4748d81630..6f5507c5539 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -7,9 +7,9 @@ #include #include +#include "atom/browser/api/atom_api_browser_window.h" #include "atom/browser/api/atom_api_debugger.h" #include "atom/browser/api/atom_api_session.h" -#include "atom/browser/api/atom_api_window.h" #include "atom/browser/atom_browser_client.h" #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" @@ -89,6 +89,11 @@ #include "ui/aura/window.h" #endif +#if defined(OS_LINUX) || defined(OS_WIN) +#include "content/public/common/renderer_preferences.h" +#include "ui/gfx/font_render_params.h" +#endif + #include "atom/common/node_includes.h" namespace { @@ -340,7 +345,7 @@ WebContents::WebContents(v8::Isolate* isolate, const mate::Dictionary& options) // Obtain the session. std::string partition; mate::Handle session; - if (options.Get("session", &session)) { + if (options.Get("session", &session) && !session.IsEmpty()) { } else if (options.Get("partition", &partition)) { session = Session::FromPartition(isolate, partition); } else { @@ -378,8 +383,8 @@ WebContents::WebContents(v8::Isolate* isolate, const mate::Dictionary& options) options.Get("transparent", &transparent); content::WebContents::CreateParams params(session->browser_context()); - auto* view = new OffScreenWebContentsView( - transparent, base::Bind(&WebContents::OnPaint, base::Unretained(this))); + auto* view = new OffScreenWebContentsView(transparent, + base::Bind(&WebContents::OnPaint, base::Unretained(this))); params.view = view; params.delegate_view = view; @@ -412,6 +417,19 @@ void WebContents::InitWithSessionAndOptions(v8::Isolate* isolate, managed_web_contents()->GetView()->SetDelegate(this); +#if defined(OS_LINUX) || defined(OS_WIN) + // Update font settings. + auto* prefs = web_contents->GetMutableRendererPrefs(); + CR_DEFINE_STATIC_LOCAL(const gfx::FontRenderParams, params, + (gfx::GetFontRenderParams(gfx::FontRenderParamsQuery(), nullptr))); + prefs->should_antialias_text = params.antialiasing; + prefs->use_subpixel_positioning = params.subpixel_positioning; + prefs->hinting = params.hinting; + prefs->use_autohinter = params.autohinter; + prefs->use_bitmaps = params.use_bitmaps; + prefs->subpixel_rendering = params.subpixel_rendering; +#endif + // Save the preferences in C++. new WebContentsPreferences(web_contents, options); @@ -446,6 +464,8 @@ void WebContents::InitWithSessionAndOptions(v8::Isolate* isolate, WebContents::~WebContents() { // The destroy() is called. if (managed_web_contents()) { + managed_web_contents()->GetView()->SetDelegate(nullptr); + // For webview we need to tell content module to do some cleanup work before // destroying it. if (type_ == WEB_VIEW) @@ -457,7 +477,8 @@ WebContents::~WebContents() { DestroyWebContents(false /* async */); } else { if (type_ == BROWSER_WINDOW && owner_window()) { - owner_window()->CloseContents(nullptr); + for (ExtendedWebContentsObserver& observer : observers_) + observer.OnCloseContents(); } else { DestroyWebContents(true /* async */); } @@ -481,12 +502,7 @@ bool WebContents::DidAddMessageToConsole(content::WebContents* source, const base::string16& message, int32_t line_no, const base::string16& source_id) { - if (type_ == OFF_SCREEN) { - return false; - } else { - Emit("console-message", level, message, line_no, source_id); - return true; - } + return Emit("console-message", level, message, line_no, source_id); } void WebContents::OnCreateWindow( @@ -570,9 +586,10 @@ void WebContents::MoveContents(content::WebContents* source, void WebContents::CloseContents(content::WebContents* source) { Emit("close"); - - if ((type_ == BROWSER_WINDOW || type_ == OFF_SCREEN) && owner_window()) - owner_window()->CloseContents(source); + if (managed_web_contents()) + managed_web_contents()->GetView()->SetDelegate(nullptr); + for (ExtendedWebContentsObserver& observer : observers_) + observer.OnCloseContents(); } void WebContents::ActivateContents(content::WebContents* source) { @@ -641,20 +658,20 @@ void WebContents::RendererUnresponsive( content::WebContents* source, const content::WebContentsUnresponsiveState& unresponsive_state) { Emit("unresponsive"); - if ((type_ == BROWSER_WINDOW || type_ == OFF_SCREEN) && owner_window()) - owner_window()->RendererUnresponsive(source); } void WebContents::RendererResponsive(content::WebContents* source) { Emit("responsive"); - if ((type_ == BROWSER_WINDOW || type_ == OFF_SCREEN) && owner_window()) - owner_window()->RendererResponsive(source); + for (ExtendedWebContentsObserver& observer : observers_) + observer.OnRendererResponsive(); } bool WebContents::HandleContextMenu(const content::ContextMenuParams& params) { if (params.custom_context.is_pepper_menu) { - Emit("pepper-context-menu", std::make_pair(params, web_contents())); - web_contents()->NotifyContextMenuClosed(params.custom_context); + Emit("pepper-context-menu", + std::make_pair(params, web_contents()), + base::Bind(&content::WebContents::NotifyContextMenuClosed, + base::Unretained(web_contents()), params.custom_context)); } else { Emit("context-menu", std::make_pair(params, web_contents())); } @@ -794,8 +811,7 @@ void WebContents::DidFinishLoad(content::RenderFrameHost* render_frame_host, void WebContents::DidFailLoad(content::RenderFrameHost* render_frame_host, const GURL& url, int error_code, - const base::string16& error_description, - bool was_ignored_by_handler) { + const base::string16& error_description) { bool is_main_frame = !render_frame_host->GetParent(); Emit("did-fail-load", error_code, error_description, url, is_main_frame); } @@ -895,10 +911,11 @@ void WebContents::DevToolsOpened() { managed_web_contents()->CallClientFunction( "DevToolsAPI.setInspectedTabId", &tab_id, nullptr, nullptr); - // Inherit owner window in devtools. - if (owner_window()) - handle->SetOwnerWindow(managed_web_contents()->GetDevToolsWebContents(), - owner_window()); + // Inherit owner window in devtools when it doesn't have one. + auto* devtools = managed_web_contents()->GetDevToolsWebContents(); + bool has_window = devtools->GetUserData(NativeWindowRelay::UserDataKey()); + if (owner_window() && !has_window) + handle->SetOwnerWindow(devtools, owner_window()); Emit("devtools-opened"); } @@ -965,7 +982,7 @@ bool WebContents::OnMessageReceived(const IPC::Message& message, // For webview only #1 will happen, for BrowserWindow both #1 and #3 may // happen. The #2 should never happen for webContents, because webview is // managed by GuestViewManager, and BrowserWindow's webContents is managed -// by api::Window. +// by api::BrowserWindow. // For #1, the destructor will do the cleanup work and we only need to make // sure "destroyed" event is emitted. For #3, the content::WebContents will // be destroyed on close, and WebContentsDestroyed would be called for it, so @@ -1178,7 +1195,8 @@ void WebContents::OpenDevTools(mate::Arguments* args) { std::string state; if (type_ == WEB_VIEW || !owner_window()) { state = "detach"; - } else if (args && args->Length() == 1) { + } + if (args && args->Length() == 1) { bool detach = false; mate::Dictionary options; if (args->GetNext(&options)) { @@ -1337,6 +1355,7 @@ void WebContents::Print(mate::Arguments* args) { std::vector WebContents::GetPrinterList() { std::vector printers; auto print_backend = printing::PrintBackend::CreateInstance(nullptr); + base::ThreadRestrictions::ScopedAllowIO allow_io; print_backend->EnumeratePrinters(&printers); return printers; } @@ -1647,10 +1666,10 @@ void WebContents::StartPainting() { return; #if defined(ENABLE_OSR) - auto* osr_rwhv = static_cast( - web_contents()->GetRenderWidgetHostView()); - if (osr_rwhv) - osr_rwhv->SetPainting(true); + const auto* wc_impl = static_cast(web_contents()); + auto* osr_wcv = static_cast(wc_impl->GetView()); + if (osr_wcv) + osr_wcv->SetPainting(true); #endif } @@ -1659,10 +1678,10 @@ void WebContents::StopPainting() { return; #if defined(ENABLE_OSR) - auto* osr_rwhv = static_cast( - web_contents()->GetRenderWidgetHostView()); - if (osr_rwhv) - osr_rwhv->SetPainting(false); + const auto* wc_impl = static_cast(web_contents()); + auto* osr_wcv = static_cast(wc_impl->GetView()); + if (osr_wcv) + osr_wcv->SetPainting(false); #endif } @@ -1671,9 +1690,10 @@ bool WebContents::IsPainting() const { return false; #if defined(ENABLE_OSR) - const auto* osr_rwhv = static_cast( - web_contents()->GetRenderWidgetHostView()); - return osr_rwhv && osr_rwhv->IsPainting(); + const auto* wc_impl = static_cast(web_contents()); + auto* osr_wcv = static_cast(wc_impl->GetView()); + + return osr_wcv && osr_wcv->IsPainting(); #else return false; #endif @@ -1684,10 +1704,11 @@ void WebContents::SetFrameRate(int frame_rate) { return; #if defined(ENABLE_OSR) - auto* osr_rwhv = static_cast( - web_contents()->GetRenderWidgetHostView()); - if (osr_rwhv) - osr_rwhv->SetFrameRate(frame_rate); + const auto* wc_impl = static_cast(web_contents()); + auto* osr_wcv = static_cast(wc_impl->GetView()); + + if (osr_wcv) + osr_wcv->SetFrameRate(frame_rate); #endif } @@ -1696,9 +1717,10 @@ int WebContents::GetFrameRate() const { return 0; #if defined(ENABLE_OSR) - const auto* osr_rwhv = static_cast( - web_contents()->GetRenderWidgetHostView()); - return osr_rwhv ? osr_rwhv->GetFrameRate() : 0; + const auto* wc_impl = static_cast(web_contents()); + auto* osr_wcv = static_cast(wc_impl->GetView()); + + return osr_wcv ? osr_wcv->GetFrameRate() : 0; #else return 0; #endif @@ -1770,7 +1792,7 @@ v8::Local WebContents::GetWebPreferences(v8::Isolate* isolate) { v8::Local WebContents::GetOwnerBrowserWindow() { if (owner_window()) - return Window::From(isolate(), owner_window()); + return BrowserWindow::From(isolate(), owner_window()); else return v8::Null(isolate()); } @@ -1808,6 +1830,11 @@ void WebContents::SetEmbedder(const WebContents* embedder) { } } +void WebContents::SetDevToolsWebContents(const WebContents* devtools) { + if (managed_web_contents()) + managed_web_contents()->SetDevToolsWebContents(devtools->web_contents()); +} + v8::Local WebContents::GetNativeView() const { gfx::NativeView ptr = web_contents()->GetNativeView(); auto buffer = node::Buffer::Copy( @@ -1929,6 +1956,7 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, .SetMethod("copyImageAt", &WebContents::CopyImageAt) .SetMethod("capturePage", &WebContents::CapturePage) .SetMethod("setEmbedder", &WebContents::SetEmbedder) + .SetMethod("setDevToolsWebContents", &WebContents::SetDevToolsWebContents) .SetMethod("getNativeView", &WebContents::GetNativeView) .SetMethod("setWebRTCIPHandlingPolicy", &WebContents::SetWebRTCIPHandlingPolicy) @@ -2006,4 +2034,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_web_contents, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_web_contents, Initialize) diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 2abdd2accc2..ce79b93e6e0 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -13,6 +13,7 @@ #include "atom/browser/api/trackable_object.h" #include "atom/browser/common_web_contents_delegate.h" #include "atom/browser/ui/autofill_popup.h" +#include "base/observer_list.h" #include "content/common/cursors/webcursor.h" #include "content/public/browser/keyboard_event_processing_result.h" #include "content/public/browser/web_contents.h" @@ -49,6 +50,15 @@ class WebViewGuestDelegate; namespace api { +// Certain events are only in WebContentsDelegate, provide our own Observer to +// dispatch those events. +class ExtendedWebContentsObserver { + public: + virtual void OnCloseContents() {} + virtual void OnRendererResponsive() {} +}; + +// Wrapper around the content::WebContents. class WebContents : public mate::TrackableObject, public CommonWebContentsDelegate, public content::WebContentsObserver { @@ -126,6 +136,7 @@ class WebContents : public mate::TrackableObject, void Print(mate::Arguments* args); std::vector GetPrinterList(); void SetEmbedder(const WebContents* embedder); + void SetDevToolsWebContents(const WebContents* devtools); v8::Local GetNativeView() const; // Print current page as PDF. @@ -230,6 +241,13 @@ class WebContents : public mate::TrackableObject, WebContentsZoomController* GetZoomController() { return zoom_controller_; } + void AddObserver(ExtendedWebContentsObserver* obs) { + observers_.AddObserver(obs); + } + void RemoveObserver(ExtendedWebContentsObserver* obs) { + observers_.RemoveObserver(obs); + } + protected: WebContents(v8::Isolate* isolate, content::WebContents* web_contents, @@ -325,8 +343,7 @@ class WebContents : public mate::TrackableObject, void DidFailLoad(content::RenderFrameHost* render_frame_host, const GURL& validated_url, int error_code, - const base::string16& error_description, - bool was_ignored_by_handler) override; + const base::string16& error_description) override; void DidStartLoading() override; void DidStopLoading() override; void DidGetResourceResponseStart( @@ -420,6 +437,9 @@ class WebContents : public mate::TrackableObject, // Whether to enable devtools. bool enable_devtools_; + // Observers of this WebContents. + base::ObserverList observers_; + 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 d8526e03ad8..ac84ac196b6 100644 --- a/atom/browser/api/atom_api_web_request.cc +++ b/atom/browser/api/atom_api_web_request.cc @@ -37,6 +37,26 @@ namespace atom { namespace api { +namespace { + +template +void CallNetworkDelegateMethod( + brightray::URLRequestContextGetter* url_request_context_getter, + Method method, + Event type, + URLPatterns patterns, + Listener listener) { + // Force creating network delegate. + net::URLRequestContext* context = + url_request_context_getter->GetURLRequestContext(); + // Then call the method. + AtomNetworkDelegate* network_delegate = + static_cast(context->network_delegate()); + (network_delegate->*method)(type, std::move(patterns), std::move(listener)); +} + +} // namespace + WebRequest::WebRequest(v8::Isolate* isolate, AtomBrowserContext* browser_context) : browser_context_(browser_context) { @@ -74,10 +94,15 @@ void WebRequest::SetListener(Method method, Event type, mate::Arguments* args) { return; } - auto delegate = browser_context_->network_delegate(); - BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::Bind(method, base::Unretained(delegate), type, - patterns, listener)); + brightray::URLRequestContextGetter* url_request_context_getter = + browser_context_->url_request_context_getter(); + if (!url_request_context_getter) + return; + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&CallNetworkDelegateMethod, + base::RetainedRef(url_request_context_getter), + method, type, std::move(patterns), std::move(listener))); } // static diff --git a/atom/browser/api/atom_api_web_view_manager.cc b/atom/browser/api/atom_api_web_view_manager.cc index d145fb15fb6..2a0cbd8a3eb 100644 --- a/atom/browser/api/atom_api_web_view_manager.cc +++ b/atom/browser/api/atom_api_web_view_manager.cc @@ -52,4 +52,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_web_view_manager, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_web_view_manager, Initialize) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc deleted file mode 100644 index bb7060adb76..00000000000 --- a/atom/browser/api/atom_api_window.cc +++ /dev/null @@ -1,1162 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "atom/browser/api/atom_api_window.h" -#include "atom/common/native_mate_converters/value_converter.h" - -#include "atom/browser/api/atom_api_browser_view.h" -#include "atom/browser/api/atom_api_menu.h" -#include "atom/browser/api/atom_api_web_contents.h" -#include "atom/browser/browser.h" -#include "atom/browser/native_window.h" -#include "atom/browser/web_contents_preferences.h" -#include "atom/common/native_mate_converters/callback.h" -#include "atom/common/native_mate_converters/file_path_converter.h" -#include "atom/common/native_mate_converters/gfx_converter.h" -#include "atom/common/native_mate_converters/gurl_converter.h" -#include "atom/common/native_mate_converters/image_converter.h" -#include "atom/common/native_mate_converters/string16_converter.h" -#include "atom/common/options_switches.h" -#include "base/command_line.h" -#include "base/threading/thread_task_runner_handle.h" -#include "content/public/browser/render_process_host.h" -#include "content/public/common/content_switches.h" -#include "native_mate/constructor.h" -#include "native_mate/dictionary.h" -#include "ui/gfx/geometry/rect.h" - -#if defined(TOOLKIT_VIEWS) -#include "atom/browser/native_window_views.h" -#endif - -#if defined(OS_WIN) -#include "atom/browser/ui/win/taskbar_host.h" -#include "ui/base/win/shell.h" -#endif - -#include "atom/common/node_includes.h" - -#if defined(OS_WIN) -namespace mate { - -template<> -struct Converter { - static bool FromV8(v8::Isolate* isolate, v8::Handle val, - atom::TaskbarHost::ThumbarButton* out) { - mate::Dictionary dict; - if (!ConvertFromV8(isolate, val, &dict)) - return false; - dict.Get("click", &(out->clicked_callback)); - dict.Get("tooltip", &(out->tooltip)); - dict.Get("flags", &out->flags); - return dict.Get("icon", &(out->icon)); - } -}; - -} // namespace mate -#endif - -namespace atom { - -namespace api { - -namespace { - -// 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); - if (buffer.IsEmpty()) - return v8::Null(isolate); - else - return buffer.ToLocalChecked(); -} - -} // namespace - - -Window::Window(v8::Isolate* isolate, v8::Local wrapper, - const mate::Dictionary& options) { - mate::Handle web_contents; - - // Use options.webPreferences in WebContents. - mate::Dictionary web_preferences = mate::Dictionary::CreateEmpty(isolate); - options.Get(options::kWebPreferences, &web_preferences); - - // Copy the backgroundColor to webContents. - v8::Local value; - if (options.Get(options::kBackgroundColor, &value)) - web_preferences.Set(options::kBackgroundColor, value); - - v8::Local transparent; - if (options.Get("transparent", &transparent)) - web_preferences.Set("transparent", transparent); - -#if defined(ENABLE_OSR) - // Offscreen windows are always created frameless. - bool offscreen; - if (web_preferences.Get("offscreen", &offscreen) && offscreen) { - auto window_options = const_cast(options); - window_options.Set(options::kFrame, false); - } -#endif - - if (options.Get("webContents", &web_contents)) { - // Set webPreferences from options if using an existing webContents. - // These preferences will be used when the webContent launches new - // render processes. - auto* existing_preferences = - WebContentsPreferences::FromWebContents(web_contents->web_contents()); - base::DictionaryValue web_preferences_dict; - if (mate::ConvertFromV8(isolate, web_preferences.GetHandle(), - &web_preferences_dict)) { - existing_preferences->web_preferences()->Clear(); - existing_preferences->Merge(web_preferences_dict); - } - - } else { - // Creates the WebContents used by BrowserWindow. - web_contents = WebContents::Create(isolate, web_preferences); - } - - Init(isolate, wrapper, options, web_contents); -} - -void Window::Init(v8::Isolate* isolate, - v8::Local wrapper, - const mate::Dictionary& options, - mate::Handle web_contents) { - web_contents_.Reset(isolate, web_contents.ToV8()); - api_web_contents_ = web_contents.get(); - - // Keep a copy of the options for later use. - mate::Dictionary(isolate, web_contents->GetWrapper()).Set( - "browserWindowOptions", options); - - // The parent window. - mate::Handle parent; - if (options.Get("parent", &parent)) - parent_window_.Reset(isolate, parent.ToV8()); - - // Creates BrowserWindow. - window_.reset(NativeWindow::Create( - web_contents->managed_web_contents(), - options, - parent.IsEmpty() ? nullptr : parent->window_.get())); - web_contents->SetOwnerWindow(window_.get()); - window_->set_is_offscreen_dummy(api_web_contents_->IsOffScreen()); - -#if defined(TOOLKIT_VIEWS) - // Sets the window icon. - mate::Handle icon; - if (options.Get(options::kIcon, &icon)) - SetIcon(icon); -#endif - - window_->InitFromOptions(options); - window_->AddObserver(this); - - InitWith(isolate, wrapper); - AttachAsUserData(window_.get()); - - // We can only append this window to parent window's child windows after this - // window's JS wrapper gets initialized. - if (!parent.IsEmpty()) - parent->child_windows_.Set(isolate, ID(), wrapper); -} - -Window::~Window() { - if (!window_->IsClosed()) - window_->CloseContents(nullptr); - - // Destroy the native window in next tick because the native code might be - // iterating all windows. - base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, window_.release()); -} - -void Window::WillCloseWindow(bool* prevent_default) { - *prevent_default = Emit("close"); -} - -void Window::WillDestroyNativeObject() { - // Close all child windows before closing current window. - v8::Locker locker(isolate()); - v8::HandleScope handle_scope(isolate()); - for (v8::Local value : child_windows_.Values(isolate())) { - mate::Handle child; - if (mate::ConvertFromV8(isolate(), value, &child)) - child->window_->CloseImmediately(); - } -} - -void Window::OnWindowClosed() { - api_web_contents_->DestroyWebContents(true /* async */); - - RemoveFromWeakMap(); - window_->RemoveObserver(this); - - // We can not call Destroy here because we need to call Emit first, but we - // also do not want any method to be used, so just mark as destroyed here. - MarkDestroyed(); - - Emit("closed"); - - RemoveFromParentChildWindows(); - - ResetBrowserView(); - - // Destroy the native class when window is closed. - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, GetDestroyClosure()); -} - -void Window::OnWindowEndSession() { - Emit("session-end"); -} - -void Window::OnWindowBlur() { - Emit("blur"); -} - -void Window::OnWindowFocus() { - Emit("focus"); -} - -void Window::OnWindowShow() { - Emit("show"); -} - -void Window::OnWindowHide() { - Emit("hide"); -} - -void Window::OnReadyToShow() { - Emit("ready-to-show"); -} - -void Window::OnWindowMaximize() { - Emit("maximize"); -} - -void Window::OnWindowUnmaximize() { - Emit("unmaximize"); -} - -void Window::OnWindowMinimize() { - Emit("minimize"); -} - -void Window::OnWindowRestore() { - Emit("restore"); -} - -void Window::OnWindowResize() { - Emit("resize"); -} - -void Window::OnWindowMove() { - Emit("move"); -} - -void Window::OnWindowMoved() { - Emit("moved"); -} - -void Window::OnWindowEnterFullScreen() { - Emit("enter-full-screen"); -} - -void Window::OnWindowLeaveFullScreen() { - Emit("leave-full-screen"); -} - -void Window::OnWindowScrollTouchBegin() { - Emit("scroll-touch-begin"); -} - -void Window::OnWindowScrollTouchEnd() { - Emit("scroll-touch-end"); -} - -void Window::OnWindowScrollTouchEdge() { - Emit("scroll-touch-edge"); -} - -void Window::OnWindowSwipe(const std::string& direction) { - Emit("swipe", direction); -} - -void Window::OnWindowSheetBegin() { - Emit("sheet-begin"); -} - -void Window::OnWindowSheetEnd() { - Emit("sheet-end"); -} - -void Window::OnWindowEnterHtmlFullScreen() { - Emit("enter-html-full-screen"); -} - -void Window::OnWindowLeaveHtmlFullScreen() { - Emit("leave-html-full-screen"); -} - -void Window::OnRendererUnresponsive() { - Emit("unresponsive"); -} - -void Window::OnRendererResponsive() { - Emit("responsive"); -} - -void Window::OnExecuteWindowsCommand(const std::string& command_name) { - Emit("app-command", command_name); -} - -void Window::OnTouchBarItemResult(const std::string& item_id, - const base::DictionaryValue& details) { - Emit("-touch-bar-interaction", item_id, details); -} - -void Window::OnNewWindowForTab() { - Emit("new-window-for-tab"); -} - -#if defined(OS_WIN) -void Window::OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) { - if (IsWindowMessageHooked(message)) { - messages_callback_map_[message].Run( - ToBuffer(isolate(), static_cast(&w_param), sizeof(WPARAM)), - ToBuffer(isolate(), static_cast(&l_param), sizeof(LPARAM))); - } -} -#endif - -// static -mate::WrappableBase* Window::New(mate::Arguments* args) { - if (!Browser::Get()->is_ready()) { - args->ThrowError("Cannot create BrowserWindow before app is ready"); - return nullptr; - } - - if (args->Length() > 1) { - args->ThrowError(); - return nullptr; - } - - mate::Dictionary options; - if (!(args->Length() == 1 && args->GetNext(&options))) { - options = mate::Dictionary::CreateEmpty(args->isolate()); - } - - return new Window(args->isolate(), args->GetThis(), options); -} - -void Window::Close() { - window_->Close(); -} - -void Window::Focus() { - window_->Focus(true); -} - -void Window::Blur() { - window_->Focus(false); -} - -bool Window::IsFocused() { - return window_->IsFocused(); -} - -void Window::Show() { - window_->Show(); -} - -void Window::ShowInactive() { - // This method doesn't make sense for modal window.. - if (IsModal()) - return; - - window_->ShowInactive(); -} - -void Window::Hide() { - window_->Hide(); -} - -bool Window::IsVisible() { - return window_->IsVisible(); -} - -bool Window::IsEnabled() { - return window_->IsEnabled(); -} - -void Window::Maximize() { - window_->Maximize(); -} - -void Window::Unmaximize() { - window_->Unmaximize(); -} - -bool Window::IsMaximized() { - return window_->IsMaximized(); -} - -void Window::Minimize() { - window_->Minimize(); -} - -void Window::Restore() { - window_->Restore(); -} - -bool Window::IsMinimized() { - return window_->IsMinimized(); -} - -void Window::SetFullScreen(bool fullscreen) { - window_->SetFullScreen(fullscreen); -} - -bool Window::IsFullscreen() { - return window_->IsFullscreen(); -} - -void Window::SetBounds(const gfx::Rect& bounds, mate::Arguments* args) { - bool animate = false; - args->GetNext(&animate); - window_->SetBounds(bounds, animate); -} - -gfx::Rect Window::GetBounds() { - return window_->GetBounds(); -} - -void Window::SetContentBounds(const gfx::Rect& bounds, mate::Arguments* args) { - bool animate = false; - args->GetNext(&animate); - window_->SetContentBounds(bounds, animate); -} - -gfx::Rect Window::GetContentBounds() { - return window_->GetContentBounds(); -} - -void Window::SetSize(int width, int height, mate::Arguments* args) { - bool animate = false; - args->GetNext(&animate); - window_->SetSize(gfx::Size(width, height), animate); -} - -std::vector Window::GetSize() { - std::vector result(2); - gfx::Size size = window_->GetSize(); - result[0] = size.width(); - result[1] = size.height(); - return result; -} - -void Window::SetContentSize(int width, int height, mate::Arguments* args) { - bool animate = false; - args->GetNext(&animate); - window_->SetContentSize(gfx::Size(width, height), animate); -} - -std::vector Window::GetContentSize() { - std::vector result(2); - gfx::Size size = window_->GetContentSize(); - result[0] = size.width(); - result[1] = size.height(); - return result; -} - -void Window::SetMinimumSize(int width, int height) { - window_->SetMinimumSize(gfx::Size(width, height)); -} - -std::vector Window::GetMinimumSize() { - std::vector result(2); - gfx::Size size = window_->GetMinimumSize(); - result[0] = size.width(); - result[1] = size.height(); - return result; -} - -void Window::SetMaximumSize(int width, int height) { - window_->SetMaximumSize(gfx::Size(width, height)); -} - -std::vector Window::GetMaximumSize() { - std::vector result(2); - gfx::Size size = window_->GetMaximumSize(); - result[0] = size.width(); - result[1] = size.height(); - 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); -} - -bool Window::IsResizable() { - return window_->IsResizable(); -} - -void Window::SetMovable(bool movable) { - window_->SetMovable(movable); -} - -bool Window::IsMovable() { - return window_->IsMovable(); -} - -void Window::SetMinimizable(bool minimizable) { - window_->SetMinimizable(minimizable); -} - -bool Window::IsMinimizable() { - return window_->IsMinimizable(); -} - -void Window::SetMaximizable(bool maximizable) { - window_->SetMaximizable(maximizable); -} - -bool Window::IsMaximizable() { - return window_->IsMaximizable(); -} - -void Window::SetFullScreenable(bool fullscreenable) { - window_->SetFullScreenable(fullscreenable); -} - -bool Window::IsFullScreenable() { - return window_->IsFullScreenable(); -} - -void Window::SetClosable(bool closable) { - window_->SetClosable(closable); -} - -bool Window::IsClosable() { - return window_->IsClosable(); -} - -void Window::SetAlwaysOnTop(bool top, mate::Arguments* args) { - std::string level = "floating"; - int relativeLevel = 0; - std::string error; - - args->GetNext(&level); - args->GetNext(&relativeLevel); - - window_->SetAlwaysOnTop(top, level, relativeLevel, &error); - - if (!error.empty()) { - args->ThrowError(error); - } -} - -bool Window::IsAlwaysOnTop() { - return window_->IsAlwaysOnTop(); -} - -void Window::Center() { - window_->Center(); -} - -void Window::SetPosition(int x, int y, mate::Arguments* args) { - bool animate = false; - args->GetNext(&animate); - window_->SetPosition(gfx::Point(x, y), animate); -} - -std::vector Window::GetPosition() { - std::vector result(2); - gfx::Point pos = window_->GetPosition(); - result[0] = pos.x(); - result[1] = pos.y(); - return result; -} - -void Window::SetTitle(const std::string& title) { - window_->SetTitle(title); -} - -std::string Window::GetTitle() { - return window_->GetTitle(); -} - -void Window::FlashFrame(bool flash) { - window_->FlashFrame(flash); -} - -void Window::SetSkipTaskbar(bool skip) { - window_->SetSkipTaskbar(skip); -} - -void Window::SetSimpleFullScreen(bool simple_fullscreen) { - window_->SetSimpleFullScreen(simple_fullscreen); -} - -bool Window::IsSimpleFullScreen() { - return window_->IsSimpleFullScreen(); -} - -void Window::SetKiosk(bool kiosk) { - window_->SetKiosk(kiosk); -} - -bool Window::IsKiosk() { - return window_->IsKiosk(); -} - -void Window::SetBackgroundColor(const std::string& color_name) { - window_->SetBackgroundColor(color_name); -} - -void Window::SetHasShadow(bool has_shadow) { - window_->SetHasShadow(has_shadow); -} - -bool Window::HasShadow() { - return window_->HasShadow(); -} - -void Window::SetOpacity(const double opacity) { - window_->SetOpacity(opacity); -} - -double Window::GetOpacity() { - return window_->GetOpacity(); -} - -void Window::FocusOnWebView() { - window_->FocusOnWebView(); -} - -void Window::BlurWebView() { - window_->BlurWebView(); -} - -bool Window::IsWebViewFocused() { - return window_->IsWebViewFocused(); -} - -void Window::SetRepresentedFilename(const std::string& filename) { - window_->SetRepresentedFilename(filename); -} - -std::string Window::GetRepresentedFilename() { - return window_->GetRepresentedFilename(); -} - -void Window::SetDocumentEdited(bool edited) { - window_->SetDocumentEdited(edited); -} - -bool Window::IsDocumentEdited() { - return window_->IsDocumentEdited(); -} - -void Window::SetIgnoreMouseEvents(bool ignore, mate::Arguments* args) { - mate::Dictionary options; - bool forward = false; - args->GetNext(&options) && options.Get("forward", &forward); - return window_->SetIgnoreMouseEvents(ignore, forward); -} - -void Window::SetContentProtection(bool enable) { - return window_->SetContentProtection(enable); -} - -void Window::SetFocusable(bool focusable) { - return window_->SetFocusable(focusable); -} - -void Window::SetProgressBar(double progress, mate::Arguments* args) { - mate::Dictionary options; - std::string mode; - NativeWindow::ProgressState state = NativeWindow::PROGRESS_NORMAL; - - args->GetNext(&options) && options.Get("mode", &mode); - - if (mode == "error") { - state = NativeWindow::PROGRESS_ERROR; - } else if (mode == "paused") { - state = NativeWindow::PROGRESS_PAUSED; - } else if (mode == "indeterminate") { - state = NativeWindow::PROGRESS_INDETERMINATE; - } else if (mode == "none") { - state = NativeWindow::PROGRESS_NONE; - } - - window_->SetProgressBar(progress, state); -} - -void Window::SetOverlayIcon(const gfx::Image& overlay, - const std::string& description) { - window_->SetOverlayIcon(overlay, description); -} - -bool Window::SetThumbarButtons(mate::Arguments* args) { -#if defined(OS_WIN) - std::vector buttons; - if (!args->GetNext(&buttons)) { - args->ThrowError(); - return false; - } - auto window = static_cast(window_.get()); - return window->taskbar_host().SetThumbarButtons( - window_->GetAcceleratedWidget(), buttons); -#else - return false; -#endif -} - -void Window::SetMenu(v8::Isolate* isolate, v8::Local value) { - mate::Handle menu; - if (value->IsObject() && - mate::V8ToString(value->ToObject()->GetConstructorName()) == "Menu" && - mate::ConvertFromV8(isolate, value, &menu)) { - menu_.Reset(isolate, menu.ToV8()); - window_->SetMenu(menu->model()); - } else if (value->IsNull()) { - menu_.Reset(); - window_->SetMenu(nullptr); - } else { - isolate->ThrowException(v8::Exception::TypeError( - mate::StringToV8(isolate, "Invalid Menu"))); - } -} - -void Window::SetAutoHideMenuBar(bool auto_hide) { - window_->SetAutoHideMenuBar(auto_hide); -} - -bool Window::IsMenuBarAutoHide() { - return window_->IsMenuBarAutoHide(); -} - -void Window::SetMenuBarVisibility(bool visible) { - window_->SetMenuBarVisibility(visible); -} - -bool Window::IsMenuBarVisible() { - return window_->IsMenuBarVisible(); -} - -#if defined(OS_WIN) -bool Window::HookWindowMessage(UINT message, - const MessageCallback& callback) { - messages_callback_map_[message] = callback; - return true; -} - -void Window::UnhookWindowMessage(UINT message) { - if (!ContainsKey(messages_callback_map_, message)) - return; - - messages_callback_map_.erase(message); -} - -bool Window::IsWindowMessageHooked(UINT message) { - return ContainsKey(messages_callback_map_, message); -} - -void Window::UnhookAllWindowMessages() { - messages_callback_map_.clear(); -} - -bool Window::SetThumbnailClip(const gfx::Rect& region) { - auto window = static_cast(window_.get()); - return window->taskbar_host().SetThumbnailClip( - window_->GetAcceleratedWidget(), region); -} - -bool Window::SetThumbnailToolTip(const std::string& tooltip) { - auto window = static_cast(window_.get()); - return window->taskbar_host().SetThumbnailToolTip( - window_->GetAcceleratedWidget(), tooltip); -} - -void Window::SetAppDetails(const mate::Dictionary& options) { - base::string16 app_id; - base::FilePath app_icon_path; - int app_icon_index = 0; - base::string16 relaunch_command; - base::string16 relaunch_display_name; - - options.Get("appId", &app_id); - options.Get("appIconPath", &app_icon_path); - options.Get("appIconIndex", &app_icon_index); - options.Get("relaunchCommand", &relaunch_command); - options.Get("relaunchDisplayName", &relaunch_display_name); - - ui::win::SetAppDetailsForWindow( - app_id, app_icon_path, app_icon_index, - relaunch_command, relaunch_display_name, - window_->GetAcceleratedWidget()); -} -#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); - window_->SetAspectRatio(aspect_ratio, extra_size); -} - -void Window::PreviewFile(const std::string& path, mate::Arguments* args) { - std::string display_name; - if (!args->GetNext(&display_name)) - display_name = path; - window_->PreviewFile(path, display_name); -} - -void Window::CloseFilePreview() { - window_->CloseFilePreview(); -} - -void Window::SetParentWindow(v8::Local value, - mate::Arguments* args) { - if (IsModal()) { - args->ThrowError("Can not be called for modal window"); - return; - } - - mate::Handle parent; - if (value->IsNull()) { - RemoveFromParentChildWindows(); - parent_window_.Reset(); - window_->SetParentWindow(nullptr); - } else if (mate::ConvertFromV8(isolate(), value, &parent)) { - parent_window_.Reset(isolate(), value); - window_->SetParentWindow(parent->window_.get()); - parent->child_windows_.Set(isolate(), ID(), GetWrapper()); - } else { - args->ThrowError("Must pass BrowserWindow instance or null"); - } -} - -v8::Local Window::GetParentWindow() const { - if (parent_window_.IsEmpty()) - return v8::Null(isolate()); - else - return v8::Local::New(isolate(), parent_window_); -} - -std::vector> Window::GetChildWindows() const { - return child_windows_.Values(isolate()); -} - -v8::Local Window::GetBrowserView() const { - if (browser_view_.IsEmpty()) { - return v8::Null(isolate()); - } - - return v8::Local::New(isolate(), browser_view_); -} - -void Window::SetBrowserView(v8::Local value) { - ResetBrowserView(); - - mate::Handle browser_view; - if (value->IsNull()) { - window_->SetBrowserView(nullptr); - } else if (mate::ConvertFromV8(isolate(), value, &browser_view)) { - window_->SetBrowserView(browser_view->view()); - browser_view->web_contents()->SetOwnerWindow(window_.get()); - browser_view_.Reset(isolate(), value); - } -} - -void Window::ResetBrowserView() { - if (browser_view_.IsEmpty()) { - return; - } - - mate::Handle browser_view; - if (mate::ConvertFromV8(isolate(), GetBrowserView(), &browser_view)) { - browser_view->web_contents()->SetOwnerWindow(nullptr); - } - - browser_view_.Reset(); -} - -bool Window::IsModal() const { - return window_->is_modal(); -} - -v8::Local Window::GetNativeWindowHandle() { - gfx::AcceleratedWidget handle = window_->GetAcceleratedWidget(); - return ToBuffer( - isolate(), static_cast(&handle), sizeof(gfx::AcceleratedWidget)); -} - -void Window::SetVisibleOnAllWorkspaces(bool visible) { - return window_->SetVisibleOnAllWorkspaces(visible); -} - -bool Window::IsVisibleOnAllWorkspaces() { - return window_->IsVisibleOnAllWorkspaces(); -} - -void Window::SetAutoHideCursor(bool auto_hide) { - window_->SetAutoHideCursor(auto_hide); -} - -void Window::SelectPreviousTab() { - window_->SelectPreviousTab(); -} - -void Window::SelectNextTab() { - window_->SelectNextTab(); -} - -void Window::MergeAllWindows() { - window_->MergeAllWindows(); -} - -void Window::MoveTabToNewWindow() { - window_->MoveTabToNewWindow(); -} - -void Window::ToggleTabBar() { - window_->ToggleTabBar(); -} - -void Window::AddTabbedWindow(NativeWindow* window) { - window_->AddTabbedWindow(window); -} - -void Window::SetVibrancy(mate::Arguments* args) { - std::string type; - - args->GetNext(&type); - window_->SetVibrancy(type); -} - -void Window::SetTouchBar(const std::vector& items) { - window_->SetTouchBar(items); -} - -void Window::RefreshTouchBarItem(const std::string& item_id) { - window_->RefreshTouchBarItem(item_id); -} - -void Window::SetEscapeTouchBarItem(const mate::PersistentDictionary& item) { - window_->SetEscapeTouchBarItem(item); -} - -int32_t Window::ID() const { - return weak_map_id(); -} - -v8::Local Window::WebContents(v8::Isolate* isolate) { - if (web_contents_.IsEmpty()) { - return v8::Null(isolate); - } - - return v8::Local::New(isolate, web_contents_); -} - -void Window::RemoveFromParentChildWindows() { - if (parent_window_.IsEmpty()) - return; - - mate::Handle parent; - if (!mate::ConvertFromV8(isolate(), GetParentWindow(), &parent)) - return; - - parent->child_windows_.Remove(ID()); -} - -// static -void Window::BuildPrototype(v8::Isolate* isolate, - v8::Local prototype) { - prototype->SetClassName(mate::StringToV8(isolate, "BrowserWindow")); - mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) - .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) - .SetMethod("hide", &Window::Hide) - .SetMethod("isVisible", &Window::IsVisible) - .SetMethod("isEnabled", &Window::IsEnabled) - .SetMethod("maximize", &Window::Maximize) - .SetMethod("unmaximize", &Window::Unmaximize) - .SetMethod("isMaximized", &Window::IsMaximized) - .SetMethod("minimize", &Window::Minimize) - .SetMethod("restore", &Window::Restore) - .SetMethod("isMinimized", &Window::IsMinimized) - .SetMethod("setFullScreen", &Window::SetFullScreen) - .SetMethod("isFullScreen", &Window::IsFullscreen) - .SetMethod("setAspectRatio", &Window::SetAspectRatio) - .SetMethod("previewFile", &Window::PreviewFile) - .SetMethod("closeFilePreview", &Window::CloseFilePreview) -#if !defined(OS_WIN) - .SetMethod("setParentWindow", &Window::SetParentWindow) -#endif - .SetMethod("getParentWindow", &Window::GetParentWindow) - .SetMethod("getChildWindows", &Window::GetChildWindows) - .SetMethod("getBrowserView", &Window::GetBrowserView) - .SetMethod("setBrowserView", &Window::SetBrowserView) - .SetMethod("isModal", &Window::IsModal) - .SetMethod("getNativeWindowHandle", &Window::GetNativeWindowHandle) - .SetMethod("getBounds", &Window::GetBounds) - .SetMethod("setBounds", &Window::SetBounds) - .SetMethod("getSize", &Window::GetSize) - .SetMethod("setSize", &Window::SetSize) - .SetMethod("getContentBounds", &Window::GetContentBounds) - .SetMethod("setContentBounds", &Window::SetContentBounds) - .SetMethod("getContentSize", &Window::GetContentSize) - .SetMethod("setContentSize", &Window::SetContentSize) - .SetMethod("setMinimumSize", &Window::SetMinimumSize) - .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) - .SetMethod("isMovable", &Window::IsMovable) - .SetMethod("setMinimizable", &Window::SetMinimizable) - .SetMethod("isMinimizable", &Window::IsMinimizable) - .SetMethod("setMaximizable", &Window::SetMaximizable) - .SetMethod("isMaximizable", &Window::IsMaximizable) - .SetMethod("setFullScreenable", &Window::SetFullScreenable) - .SetMethod("isFullScreenable", &Window::IsFullScreenable) - .SetMethod("setClosable", &Window::SetClosable) - .SetMethod("isClosable", &Window::IsClosable) - .SetMethod("setAlwaysOnTop", &Window::SetAlwaysOnTop) - .SetMethod("isAlwaysOnTop", &Window::IsAlwaysOnTop) - .SetMethod("center", &Window::Center) - .SetMethod("setPosition", &Window::SetPosition) - .SetMethod("getPosition", &Window::GetPosition) - .SetMethod("setTitle", &Window::SetTitle) - .SetMethod("getTitle", &Window::GetTitle) - .SetMethod("flashFrame", &Window::FlashFrame) - .SetMethod("setSkipTaskbar", &Window::SetSkipTaskbar) - .SetMethod("setSimpleFullScreen", &Window::SetSimpleFullScreen) - .SetMethod("isSimpleFullScreen", &Window::IsSimpleFullScreen) - .SetMethod("setKiosk", &Window::SetKiosk) - .SetMethod("isKiosk", &Window::IsKiosk) - .SetMethod("setBackgroundColor", &Window::SetBackgroundColor) - .SetMethod("setHasShadow", &Window::SetHasShadow) - .SetMethod("hasShadow", &Window::HasShadow) - .SetMethod("setOpacity", &Window::SetOpacity) - .SetMethod("getOpacity", &Window::GetOpacity) - .SetMethod("setRepresentedFilename", &Window::SetRepresentedFilename) - .SetMethod("getRepresentedFilename", &Window::GetRepresentedFilename) - .SetMethod("setDocumentEdited", &Window::SetDocumentEdited) - .SetMethod("isDocumentEdited", &Window::IsDocumentEdited) - .SetMethod("setIgnoreMouseEvents", &Window::SetIgnoreMouseEvents) - .SetMethod("setContentProtection", &Window::SetContentProtection) - .SetMethod("setFocusable", &Window::SetFocusable) - .SetMethod("focusOnWebView", &Window::FocusOnWebView) - .SetMethod("blurWebView", &Window::BlurWebView) - .SetMethod("isWebViewFocused", &Window::IsWebViewFocused) - .SetMethod("setProgressBar", &Window::SetProgressBar) - .SetMethod("setOverlayIcon", &Window::SetOverlayIcon) - .SetMethod("setThumbarButtons", &Window::SetThumbarButtons) - .SetMethod("setMenu", &Window::SetMenu) - .SetMethod("setAutoHideMenuBar", &Window::SetAutoHideMenuBar) - .SetMethod("isMenuBarAutoHide", &Window::IsMenuBarAutoHide) - .SetMethod("setMenuBarVisibility", &Window::SetMenuBarVisibility) - .SetMethod("isMenuBarVisible", &Window::IsMenuBarVisible) - .SetMethod("setVisibleOnAllWorkspaces", - &Window::SetVisibleOnAllWorkspaces) - .SetMethod("isVisibleOnAllWorkspaces", - &Window::IsVisibleOnAllWorkspaces) -#if defined(OS_MACOSX) - .SetMethod("setAutoHideCursor", &Window::SetAutoHideCursor) - .SetMethod("mergeAllWindows", &Window::MergeAllWindows) - .SetMethod("selectPreviousTab", &Window::SelectPreviousTab) - .SetMethod("selectNextTab", &Window::SelectNextTab) - .SetMethod("moveTabToNewWindow", &Window::MoveTabToNewWindow) - .SetMethod("toggleTabBar", &Window::ToggleTabBar) - .SetMethod("addTabbedWindow", &Window::AddTabbedWindow) -#endif - .SetMethod("setVibrancy", &Window::SetVibrancy) - .SetMethod("_setTouchBarItems", &Window::SetTouchBar) - .SetMethod("_refreshTouchBarItem", &Window::RefreshTouchBarItem) - .SetMethod("_setEscapeTouchBarItem", &Window::SetEscapeTouchBarItem) -#if defined(OS_WIN) - .SetMethod("hookWindowMessage", &Window::HookWindowMessage) - .SetMethod("isWindowMessageHooked", &Window::IsWindowMessageHooked) - .SetMethod("unhookWindowMessage", &Window::UnhookWindowMessage) - .SetMethod("unhookAllWindowMessages", &Window::UnhookAllWindowMessages) - .SetMethod("setThumbnailClip", &Window::SetThumbnailClip) - .SetMethod("setThumbnailToolTip", &Window::SetThumbnailToolTip) - .SetMethod("setAppDetails", &Window::SetAppDetails) -#endif -#if defined(TOOLKIT_VIEWS) - .SetMethod("setIcon", &Window::SetIcon) -#endif - .SetProperty("id", &Window::ID) - .SetProperty("webContents", &Window::WebContents); -} - -// static -v8::Local Window::From(v8::Isolate* isolate, - NativeWindow* native_window) { - auto existing = TrackableObject::FromWrappedClass(isolate, native_window); - if (existing) - return existing->GetWrapper(); - else - return v8::Null(isolate); -} - -} // namespace api - -} // namespace atom - - -namespace { - -using atom::api::Window; - -void Initialize(v8::Local exports, v8::Local unused, - v8::Local context, void* priv) { - v8::Isolate* isolate = context->GetIsolate(); - Window::SetConstructor(isolate, base::Bind(&Window::New)); - - mate::Dictionary browser_window( - isolate, Window::GetConstructor(isolate)->GetFunction()); - browser_window.SetMethod("fromId", - &mate::TrackableObject::FromWeakMapID); - browser_window.SetMethod("getAllWindows", - &mate::TrackableObject::GetAll); - - mate::Dictionary dict(isolate, exports); - dict.Set("BrowserWindow", browser_window); -} - -} // namespace - -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_window, Initialize) diff --git a/atom/browser/api/event_emitter.h b/atom/browser/api/event_emitter.h index f5a8025e860..8d9d2960df3 100644 --- a/atom/browser/api/event_emitter.h +++ b/atom/browser/api/event_emitter.h @@ -42,8 +42,10 @@ class EventEmitter : public Wrappable { // 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(); } + v8::Local GetWrapper() const { + return Wrappable::GetWrapper(); + } // this.emit(name, event, args...); template diff --git a/atom/browser/api/event_subscriber.h b/atom/browser/api/event_subscriber.h index 2f4f2396be4..8f09a61460a 100644 --- a/atom/browser/api/event_subscriber.h +++ b/atom/browser/api/event_subscriber.h @@ -73,6 +73,13 @@ class EventSubscriber : internal::EventSubscriberBase { content::BrowserThread::UI, FROM_HERE, base::Bind( [](EventSubscriber* subscriber) { + { + // It is possible that this function will execute in the UI + // thread before the outer function has returned and destroyed + // its auto_lock. We need to acquire the lock before deleting + // or risk a crash. + base::AutoLock auto_lock(subscriber->handler_lock_); + } delete subscriber; }, ptr)); diff --git a/atom/browser/atom_access_token_store.cc b/atom/browser/atom_access_token_store.cc index 86bfd115fbe..bf4e4ecdc87 100644 --- a/atom/browser/atom_access_token_store.cc +++ b/atom/browser/atom_access_token_store.cc @@ -54,7 +54,6 @@ class GeoURLRequestContextGetter : public net::URLRequestContextGetter { AtomAccessTokenStore::AtomAccessTokenStore() : request_context_getter_(new internal::GeoURLRequestContextGetter) { - device::GeolocationProvider::GetInstance()->UserDidOptIntoLocationServices(); } AtomAccessTokenStore::~AtomAccessTokenStore() { diff --git a/atom/browser/atom_browser_client.cc b/atom/browser/atom_browser_client.cc index 49ca11bfa58..69fd274f6e8 100644 --- a/atom/browser/atom_browser_client.cc +++ b/atom/browser/atom_browser_client.cc @@ -17,6 +17,7 @@ #include "atom/browser/atom_speech_recognition_manager_delegate.h" #include "atom/browser/child_web_contents_tracker.h" #include "atom/browser/native_window.h" +#include "atom/browser/session_preferences.h" #include "atom/browser/web_contents_permission_helper.h" #include "atom/browser/web_contents_preferences.h" #include "atom/browser/window_list.h" @@ -32,11 +33,13 @@ #include "chrome/browser/speech/tts_message_filter.h" #include "content/public/browser/browser_ppapi_host.h" #include "content/public/browser/client_certificate_delegate.h" +#include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/resource_dispatcher_host.h" #include "content/public/browser/site_instance.h" #include "content/public/browser/web_contents.h" +#include "content/public/common/content_paths.h" #include "content/public/common/content_switches.h" #include "content/public/common/resource_request_body.h" #include "content/public/common/url_constants.h" @@ -165,11 +168,14 @@ void AtomBrowserClient::RenderProcessWillLaunch( content::WebContents* web_contents = GetWebContentsFromProcessID(process_id); ProcessPreferences process_prefs; - process_prefs.sandbox = WebContentsPreferences::IsSandboxed(web_contents); - process_prefs.native_window_open - = WebContentsPreferences::UsesNativeWindowOpen(web_contents); - process_prefs.disable_popups - = WebContentsPreferences::DisablePopups(web_contents); + process_prefs.sandbox = + WebContentsPreferences::IsPreferenceEnabled("sandbox", web_contents); + process_prefs.native_window_open = + WebContentsPreferences::IsPreferenceEnabled("nativeWindowOpen", + web_contents); + process_prefs.disable_popups = + WebContentsPreferences::IsPreferenceEnabled("disablePopups", + web_contents); AddProcessPreferences(host->GetID(), process_prefs); // ensure the ProcessPreferences is removed later host->AddObserver(this); @@ -201,12 +207,8 @@ void AtomBrowserClient::OverrideWebkitPrefs( WebContentsPreferences::OverrideWebkitPrefs(web_contents, prefs); } -std::string AtomBrowserClient::GetApplicationLocale() { - return l10n_util::GetApplicationLocale(""); -} - void AtomBrowserClient::OverrideSiteInstanceForNavigation( - content::RenderFrameHost* render_frame_host, + content::RenderFrameHost* rfh, content::BrowserContext* browser_context, content::SiteInstance* current_instance, const GURL& url, @@ -216,32 +218,67 @@ void AtomBrowserClient::OverrideSiteInstanceForNavigation( return; } - if (!ShouldCreateNewSiteInstance(render_frame_host, browser_context, - current_instance, url)) + if (!ShouldCreateNewSiteInstance(rfh, browser_context, current_instance, url)) return; - scoped_refptr site_instance = - content::SiteInstance::CreateForURL(browser_context, url); + bool is_new_instance = true; + scoped_refptr site_instance; + + // Do we have an affinity site to manage ? + std::string affinity; + auto* web_contents = content::WebContents::FromRenderFrameHost(rfh); + auto* web_preferences = web_contents ? + WebContentsPreferences::FromWebContents(web_contents) : nullptr; + if (web_preferences && + web_preferences->web_preferences()->GetString("affinity", &affinity) && + !affinity.empty()) { + affinity = base::ToLowerASCII(affinity); + auto iter = site_per_affinities.find(affinity); + if (iter != site_per_affinities.end()) { + site_instance = iter->second; + is_new_instance = false; + } else { + // We must not provide the url. + // This site is "isolated" and must not be taken into account + // when Chromium looking at a candidate for an url. + site_instance = content::SiteInstance::Create( + browser_context); + site_per_affinities[affinity] = site_instance.get(); + } + } else { + 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))); + if (is_new_instance) { + // 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 web contents for the pending renderer process. - auto pending_process = (*new_instance)->GetProcess(); - pending_processes_[pending_process->GetID()] = - content::WebContents::FromRenderFrameHost(render_frame_host);; - // Clear the entry in map when process ends. - pending_process->AddObserver(this); + // Remember the original web contents for the pending renderer process. + auto pending_process = site_instance->GetProcess(); + pending_processes_[pending_process->GetID()] = web_contents; + } } void AtomBrowserClient::AppendExtraCommandLineSwitches( base::CommandLine* command_line, int process_id) { + // Make sure we're about to launch a known executable + { + base::FilePath child_path; + PathService::Get(content::CHILD_PROCESS_EXE, &child_path); + + base::ThreadRestrictions::ScopedAllowIO allow_io; + CHECK(base::MakeAbsoluteFilePath(command_line->GetProgram()) == child_path); + } + std::string process_type = command_line->GetSwitchValueASCII(::switches::kProcessType); if (process_type != ::switches::kRendererProcess) @@ -277,9 +314,12 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches( } content::WebContents* web_contents = GetWebContentsFromProcessID(process_id); - if (web_contents) + if (web_contents) { WebContentsPreferences::AppendExtraCommandLineSwitches( web_contents, command_line); + SessionPreferences::AppendExtraCommandLineSwitches( + web_contents->GetBrowserContext(), command_line); + } } void AtomBrowserClient::DidCreatePpapiPlugin( @@ -347,8 +387,7 @@ bool AtomBrowserClient::CanCreateWindow( bool user_gesture, bool opener_suppressed, bool* no_javascript_access) { - // FIXME: Ensure the DCHECK doesn't fail and then re-enable - // DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); int opener_render_process_id = opener->GetProcess()->GetID(); @@ -369,15 +408,11 @@ bool AtomBrowserClient::CanCreateWindow( } 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, - additional_features, - body, - opener)); + return delegate_->CanCreateWindow( + opener, opener_url, opener_top_level_frame_url, source_origin, + container_type, target_url, referrer, frame_name, disposition, features, + additional_features, body, user_gesture, opener_suppressed, + no_javascript_access); } return false; @@ -393,6 +428,19 @@ void AtomBrowserClient::GetAdditionalAllowedSchemesForFileSystem( additional_schemes->push_back(content::kChromeDevToolsScheme); } +void AtomBrowserClient::SiteInstanceDeleting( + content::SiteInstance* site_instance) { + // We are storing weak_ptr, is it fundamental to maintain the map up-to-date + // when an instance is destroyed. + for (auto iter = site_per_affinities.begin(); + iter != site_per_affinities.end(); ++iter) { + if (iter->second == site_instance) { + site_per_affinities.erase(iter); + break; + } + } +} + brightray::BrowserMainParts* AtomBrowserClient::OverrideCreateBrowserMainParts( const content::MainFunctionParams&) { v8::V8::Initialize(); // Init V8 before creating main parts. diff --git a/atom/browser/atom_browser_client.h b/atom/browser/atom_browser_client.h index f0d793407cc..54ade8170a7 100644 --- a/atom/browser/atom_browser_client.h +++ b/atom/browser/atom_browser_client.h @@ -53,7 +53,6 @@ class AtomBrowserClient : public brightray::BrowserClient, CreateSpeechRecognitionManagerDelegate() override; void OverrideWebkitPrefs(content::RenderViewHost* render_view_host, content::WebPreferences* prefs) override; - std::string GetApplicationLocale() override; void OverrideSiteInstanceForNavigation( content::RenderFrameHost* render_frame_host, content::BrowserContext* browser_context, @@ -99,6 +98,7 @@ class AtomBrowserClient : public brightray::BrowserClient, bool* no_javascript_access) override; void GetAdditionalAllowedSchemesForFileSystem( std::vector* schemes) override; + void SiteInstanceDeleting(content::SiteInstance* site_instance) override; // brightray::BrowserClient: brightray::BrowserMainParts* OverrideCreateBrowserMainParts( @@ -120,9 +120,9 @@ class AtomBrowserClient : public brightray::BrowserClient, content::SiteInstance* current_instance, const GURL& dest_url); struct ProcessPreferences { - bool sandbox; - bool native_window_open; - bool disable_popups; + bool sandbox = false; + bool native_window_open = false; + bool disable_popups = false; }; void AddProcessPreferences(int process_id, ProcessPreferences prefs); void RemoveProcessPreferences(int process_id); @@ -135,6 +135,10 @@ class AtomBrowserClient : public brightray::BrowserClient, std::map process_preferences_; std::map render_process_host_pids_; + + // list of site per affinity. weak_ptr to prevent instance locking + std::map site_per_affinities; + base::Lock process_preferences_lock_; std::unique_ptr diff --git a/atom/browser/atom_browser_context.cc b/atom/browser/atom_browser_context.cc index 63fb8289529..bd69d8bc78c 100644 --- a/atom/browser/atom_browser_context.cc +++ b/atom/browser/atom_browser_context.cc @@ -15,6 +15,7 @@ #include "atom/browser/net/atom_cert_verifier.h" #include "atom/browser/net/atom_network_delegate.h" #include "atom/browser/net/atom_url_request_job_factory.h" +#include "atom/browser/net/cookie_details.h" #include "atom/browser/net/http_protocol_handler.h" #include "atom/browser/web_view_manager.h" #include "atom/common/atom_version.h" @@ -70,9 +71,7 @@ std::string RemoveWhitespace(const std::string& str) { AtomBrowserContext::AtomBrowserContext(const std::string& partition, bool in_memory, const base::DictionaryValue& options) - : brightray::BrowserContext(partition, in_memory), - network_delegate_(new AtomNetworkDelegate), - cookie_delegate_(new AtomCookieDelegate) { + : brightray::BrowserContext(partition, in_memory) { // Construct user agent string. Browser* browser = Browser::Get(); std::string name = RemoveWhitespace(browser->GetName()); @@ -104,12 +103,15 @@ void AtomBrowserContext::SetUserAgent(const std::string& user_agent) { user_agent_ = user_agent; } -net::NetworkDelegate* AtomBrowserContext::CreateNetworkDelegate() { - return network_delegate_; +std::unique_ptr::Subscription> +AtomBrowserContext::RegisterCookieChangeCallback( + const base::Callback& cb) { + return cookie_change_sub_list_.Add(cb); } -net::CookieMonsterDelegate* AtomBrowserContext::CreateCookieDelegate() { - return cookie_delegate(); +std::unique_ptr +AtomBrowserContext::CreateNetworkDelegate() { + return base::MakeUnique(); } std::string AtomBrowserContext::GetUserAgent() { @@ -203,6 +205,14 @@ std::vector AtomBrowserContext::GetCookieableSchemes() { return default_schemes; } +void AtomBrowserContext::NotifyCookieChange( + const net::CanonicalCookie& cookie, + bool removed, + net::CookieStore::ChangeCause cause) { + CookieDetails cookie_details(&cookie, removed, cause); + cookie_change_sub_list_.Notify(&cookie_details); +} + void AtomBrowserContext::RegisterPrefs(PrefRegistrySimple* pref_registry) { pref_registry->RegisterFilePathPref(prefs::kSelectFileLastDirectory, base::FilePath()); diff --git a/atom/browser/atom_browser_context.h b/atom/browser/atom_browser_context.h index 7e73fa0395b..c892f2b6a22 100644 --- a/atom/browser/atom_browser_context.h +++ b/atom/browser/atom_browser_context.h @@ -8,9 +8,8 @@ #include #include -#include "atom/browser/net/atom_cookie_delegate.h" +#include "base/callback_list.h" #include "brightray/browser/browser_context.h" -#include "net/cookies/cookie_monster.h" namespace atom { @@ -19,6 +18,7 @@ class AtomDownloadManagerDelegate; class AtomNetworkDelegate; class AtomPermissionManager; class WebViewManager; +struct CookieDetails; class AtomBrowserContext : public brightray::BrowserContext { public: @@ -30,10 +30,13 @@ class AtomBrowserContext : public brightray::BrowserContext { const base::DictionaryValue& options = base::DictionaryValue()); void SetUserAgent(const std::string& user_agent); + // Register callbacks that needs to notified on any cookie store changes. + std::unique_ptr::Subscription> + RegisterCookieChangeCallback( + const base::Callback& cb); // brightray::URLRequestContextGetter::Delegate: - net::NetworkDelegate* CreateNetworkDelegate() override; - net::CookieMonsterDelegate* CreateCookieDelegate() override; + std::unique_ptr CreateNetworkDelegate() override; std::string GetUserAgent() override; std::unique_ptr CreateURLRequestJobFactory( content::ProtocolHandlerMap* protocol_handlers) override; @@ -42,6 +45,9 @@ class AtomBrowserContext : public brightray::BrowserContext { std::unique_ptr CreateCertVerifier( brightray::RequireCTDelegate* ct_delegate) override; std::vector GetCookieableSchemes() override; + void NotifyCookieChange(const net::CanonicalCookie& cookie, + bool removed, + net::CookieStore::ChangeCause cause) override; // content::BrowserContext: content::DownloadManagerDelegate* GetDownloadManagerDelegate() override; @@ -52,10 +58,6 @@ class AtomBrowserContext : public brightray::BrowserContext { void RegisterPrefs(PrefRegistrySimple* pref_registry) override; AtomBlobReader* GetBlobReader(); - AtomNetworkDelegate* network_delegate() const { return network_delegate_; } - AtomCookieDelegate* cookie_delegate() const { - return cookie_delegate_.get(); - } protected: AtomBrowserContext(const std::string& partition, bool in_memory, @@ -70,9 +72,7 @@ class AtomBrowserContext : public brightray::BrowserContext { std::string user_agent_; bool use_cache_; - // Managed by brightray::BrowserContext. - AtomNetworkDelegate* network_delegate_; - scoped_refptr cookie_delegate_; + base::CallbackList cookie_change_sub_list_; DISALLOW_COPY_AND_ASSIGN(AtomBrowserContext); }; diff --git a/atom/browser/atom_browser_main_parts.cc b/atom/browser/atom_browser_main_parts.cc index 89ab75c33cb..718d7d4f82b 100644 --- a/atom/browser/atom_browser_main_parts.cc +++ b/atom/browser/atom_browser_main_parts.cc @@ -24,6 +24,7 @@ #include "content/public/browser/child_process_security_policy.h" #include "device/geolocation/geolocation_delegate.h" #include "device/geolocation/geolocation_provider.h" +#include "ui/base/l10n/l10n_util.h" #include "v8/include/v8-debug.h" #if defined(USE_X11) @@ -38,7 +39,10 @@ namespace { // A provider of Geolocation services to override AccessTokenStore. class AtomGeolocationDelegate : public device::GeolocationDelegate { public: - AtomGeolocationDelegate() = default; + AtomGeolocationDelegate() { + device::GeolocationProvider::GetInstance() + ->UserDidOptIntoLocationServices(); + } scoped_refptr CreateAccessTokenStore() final { return new AtomAccessTokenStore(); @@ -131,13 +135,13 @@ void AtomBrowserMainParts::PostEarlyInitialization() { node_bindings_->Initialize(); // Create the global environment. - node::Environment* env = - node_bindings_->CreateEnvironment(js_env_->context()); + node::Environment* env = node_bindings_->CreateEnvironment( + js_env_->context(), js_env_->platform()); node_env_.reset(new NodeEnvironment(env)); // Enable support for v8 inspector node_debugger_.reset(new NodeDebugger(env)); - node_debugger_->Start(); + node_debugger_->Start(js_env_->platform()); // Add Electron extended APIs. atom_bindings_->BindTo(js_env_->isolate(), env->process_object()); @@ -149,6 +153,15 @@ void AtomBrowserMainParts::PostEarlyInitialization() { node_bindings_->set_uv_env(env); } +int AtomBrowserMainParts::PreCreateThreads() { + const int result = brightray::BrowserMainParts::PreCreateThreads(); + if (!result) { + fake_browser_process_->SetApplicationLocale( + brightray::BrowserClient::Get()->GetApplicationLocale()); + } + return result; +} + void AtomBrowserMainParts::PreMainMessageLoopRun() { js_env_->OnMessageLoopCreated(); @@ -185,6 +198,7 @@ void AtomBrowserMainParts::PreMainMessageLoopRun() { Browser::Get()->DidFinishLaunching(*empty_info); #endif + // Notify observers that main thread message loop was initialized. Browser::Get()->PreMainMessageLoopRun(); } diff --git a/atom/browser/atom_browser_main_parts.h b/atom/browser/atom_browser_main_parts.h index 2ba7d341f43..a4c3bbed561 100644 --- a/atom/browser/atom_browser_main_parts.h +++ b/atom/browser/atom_browser_main_parts.h @@ -49,6 +49,7 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts { // content::BrowserMainParts: void PreEarlyInitialization() override; void PostEarlyInitialization() override; + int PreCreateThreads() override; void PreMainMessageLoopRun() override; bool MainMessageLoopRun(int* result_code) override; void PostMainMessageLoopStart() override; diff --git a/atom/browser/atom_browser_main_parts_posix.cc b/atom/browser/atom_browser_main_parts_posix.cc index 8c96f91bfe6..d208ea8b744 100644 --- a/atom/browser/atom_browser_main_parts_posix.cc +++ b/atom/browser/atom_browser_main_parts_posix.cc @@ -119,9 +119,8 @@ void ShutdownDetector::ThreadMain() { int signal; size_t bytes_read = 0; - ssize_t ret; do { - ret = HANDLE_EINTR( + ssize_t ret = HANDLE_EINTR( read(shutdown_fd_, reinterpret_cast(&signal) + bytes_read, sizeof(signal) - bytes_read)); diff --git a/atom/browser/atom_download_manager_delegate.cc b/atom/browser/atom_download_manager_delegate.cc index de9c64ce8ad..6df090f9cf2 100644 --- a/atom/browser/atom_download_manager_delegate.cc +++ b/atom/browser/atom_download_manager_delegate.cc @@ -21,6 +21,32 @@ namespace atom { +namespace { + +// Generate default file path to save the download. +void CreateDownloadPath( + const GURL& url, + const std::string& content_disposition, + const std::string& suggested_filename, + const std::string& mime_type, + const base::FilePath& default_download_path, + const AtomDownloadManagerDelegate::CreateDownloadPathCallback& callback) { + DCHECK_CURRENTLY_ON(content::BrowserThread::FILE); + + auto generated_name = + net::GenerateFileName(url, content_disposition, std::string(), + suggested_filename, mime_type, "download"); + + if (!base::PathExists(default_download_path)) + base::CreateDirectory(default_download_path); + + base::FilePath path(default_download_path.Append(generated_name)); + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, + base::Bind(callback, path)); +} + +} // namespace + AtomDownloadManagerDelegate::AtomDownloadManagerDelegate( content::DownloadManager* manager) : download_manager_(manager), @@ -46,30 +72,6 @@ void AtomDownloadManagerDelegate::GetItemSavePath(content::DownloadItem* item, *path = download->GetSavePath(); } -void AtomDownloadManagerDelegate::CreateDownloadPath( - const GURL& url, - const std::string& content_disposition, - const std::string& suggested_filename, - const std::string& mime_type, - const base::FilePath& default_download_path, - const CreateDownloadPathCallback& callback) { - DCHECK_CURRENTLY_ON(content::BrowserThread::FILE); - - auto generated_name = net::GenerateFileName(url, - content_disposition, - std::string(), - suggested_filename, - mime_type, - "download"); - - if (!base::PathExists(default_download_path)) - base::CreateDirectory(default_download_path); - - base::FilePath path(default_download_path.Append(generated_name)); - content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, - base::Bind(callback, path)); -} - void AtomDownloadManagerDelegate::OnDownloadPathGenerated( uint32_t download_id, const content::DownloadTargetCallback& callback, @@ -164,14 +166,10 @@ bool AtomDownloadManagerDelegate::DetermineDownloadTarget( content::BrowserThread::PostTask( content::BrowserThread::FILE, FROM_HERE, - base::Bind(&AtomDownloadManagerDelegate::CreateDownloadPath, - weak_ptr_factory_.GetWeakPtr(), - download->GetURL(), + base::Bind(&CreateDownloadPath, download->GetURL(), download->GetContentDisposition(), - download->GetSuggestedFilename(), - download->GetMimeType(), - default_download_path, - download_path_callback)); + download->GetSuggestedFilename(), download->GetMimeType(), + default_download_path, download_path_callback)); return true; } diff --git a/atom/browser/atom_download_manager_delegate.h b/atom/browser/atom_download_manager_delegate.h index d2387212942..54928b26a3d 100644 --- a/atom/browser/atom_download_manager_delegate.h +++ b/atom/browser/atom_download_manager_delegate.h @@ -24,13 +24,6 @@ class AtomDownloadManagerDelegate : public content::DownloadManagerDelegate { explicit AtomDownloadManagerDelegate(content::DownloadManager* manager); virtual ~AtomDownloadManagerDelegate(); - // Generate default file path to save the download. - void CreateDownloadPath(const GURL& url, - const std::string& suggested_filename, - const std::string& content_disposition, - const std::string& mime_type, - const base::FilePath& path, - const CreateDownloadPathCallback& callback); void OnDownloadPathGenerated(uint32_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 c593b2bfba1..4168be35361 100644 --- a/atom/browser/atom_javascript_dialog_manager.cc +++ b/atom/browser/atom_javascript_dialog_manager.cc @@ -10,6 +10,7 @@ #include "atom/browser/api/atom_api_web_contents.h" #include "atom/browser/native_window.h" #include "atom/browser/ui/message_box.h" +#include "atom/browser/web_contents_preferences.h" #include "base/bind.h" #include "base/strings/utf_string_conversions.h" #include "ui/gfx/image/image_skia.h" @@ -18,6 +19,12 @@ using content::JavaScriptDialogType; namespace atom { +namespace { + +constexpr int kUserWantsNoMoreDialogs = -1; + +} // namespace + AtomJavaScriptDialogManager::AtomJavaScriptDialogManager( api::WebContents* api_web_contents) : api_web_contents_(api_web_contents) {} @@ -30,6 +37,11 @@ void AtomJavaScriptDialogManager::RunJavaScriptDialog( const base::string16& default_prompt_text, const DialogClosedCallback& callback, bool* did_suppress_message) { + const std::string origin = origin_url.GetOrigin().spec(); + if (origin_counts_[origin] == kUserWantsNoMoreDialogs) { + return callback.Run(false, base::string16()); + } + if (dialog_type != JavaScriptDialogType::JAVASCRIPT_DIALOG_TYPE_ALERT && dialog_type != JavaScriptDialogType::JAVASCRIPT_DIALOG_TYPE_CONFIRM) { callback.Run(false, base::string16()); @@ -41,12 +53,27 @@ void AtomJavaScriptDialogManager::RunJavaScriptDialog( buttons.push_back("Cancel"); } - atom::ShowMessageBox(NativeWindow::FromWebContents(web_contents), - atom::MessageBoxType::MESSAGE_BOX_TYPE_NONE, buttons, -1, - 0, atom::MessageBoxOptions::MESSAGE_BOX_NONE, "", - base::UTF16ToUTF8(message_text), "", "", false, - gfx::ImageSkia(), - base::Bind(&OnMessageBoxCallback, callback)); + origin_counts_[origin]++; + + std::string checkbox_string; + if (origin_counts_[origin] > 1 && + WebContentsPreferences::IsPreferenceEnabled("safeDialogs", + web_contents)) { + if (!WebContentsPreferences::GetString("safeDialogsMessage", + &checkbox_string, web_contents)) { + checkbox_string = "Prevent this app from creating additional dialogs"; + } + } + + auto* relay = NativeWindowRelay::FromWebContents(web_contents); + atom::ShowMessageBox( + relay ? relay->window.get() : nullptr, + atom::MessageBoxType::MESSAGE_BOX_TYPE_NONE, buttons, -1, 0, + atom::MessageBoxOptions::MESSAGE_BOX_NONE, "", + base::UTF16ToUTF8(message_text), "", checkbox_string, + false, gfx::ImageSkia(), + base::Bind(&AtomJavaScriptDialogManager::OnMessageBoxCallback, + base::Unretained(this), callback, origin)); } void AtomJavaScriptDialogManager::RunBeforeUnloadDialog( @@ -63,11 +90,13 @@ void AtomJavaScriptDialogManager::CancelDialogs( bool reset_state) { } -// static void AtomJavaScriptDialogManager::OnMessageBoxCallback( const DialogClosedCallback& callback, + const std::string& origin, int code, bool checkbox_checked) { + if (checkbox_checked) + origin_counts_[origin] = kUserWantsNoMoreDialogs; callback.Run(code == 0, base::string16()); } diff --git a/atom/browser/atom_javascript_dialog_manager.h b/atom/browser/atom_javascript_dialog_manager.h index d5e6c543361..0f5e55403be 100644 --- a/atom/browser/atom_javascript_dialog_manager.h +++ b/atom/browser/atom_javascript_dialog_manager.h @@ -5,6 +5,7 @@ #ifndef ATOM_BROWSER_ATOM_JAVASCRIPT_DIALOG_MANAGER_H_ #define ATOM_BROWSER_ATOM_JAVASCRIPT_DIALOG_MANAGER_H_ +#include #include #include "content/public/browser/javascript_dialog_manager.h" @@ -36,10 +37,13 @@ class AtomJavaScriptDialogManager : public content::JavaScriptDialogManager { bool reset_state) override; private: - static void OnMessageBoxCallback(const DialogClosedCallback& callback, - int code, - bool checkbox_checked); + void OnMessageBoxCallback(const DialogClosedCallback& callback, + const std::string& origin, + int code, + bool checkbox_checked); + api::WebContents* api_web_contents_; + std::map origin_counts_; }; } // namespace atom diff --git a/atom/browser/atom_permission_manager.cc b/atom/browser/atom_permission_manager.cc index e890618be73..88b31debee9 100644 --- a/atom/browser/atom_permission_manager.cc +++ b/atom/browser/atom_permission_manager.cc @@ -97,11 +97,28 @@ int AtomPermissionManager::RequestPermission( const GURL& requesting_origin, bool user_gesture, const StatusCallback& response_callback) { - return RequestPermissions( + return RequestPermissionWithDetails( + permission, + render_frame_host, + requesting_origin, + user_gesture, + nullptr, + response_callback); +} + +int AtomPermissionManager::RequestPermissionWithDetails( + content::PermissionType permission, + content::RenderFrameHost* render_frame_host, + const GURL& requesting_origin, + bool user_gesture, + const base::DictionaryValue* details, + const StatusCallback& response_callback) { + return RequestPermissionsWithDetails( std::vector(1, permission), render_frame_host, requesting_origin, user_gesture, + details, base::Bind(&PermissionRequestResponseCallbackWrapper, response_callback)); } @@ -111,6 +128,18 @@ int AtomPermissionManager::RequestPermissions( const GURL& requesting_origin, bool user_gesture, const StatusesCallback& response_callback) { + return RequestPermissionsWithDetails( + permissions, render_frame_host, requesting_origin, + user_gesture, nullptr, response_callback); +} + +int AtomPermissionManager::RequestPermissionsWithDetails( + const std::vector& permissions, + content::RenderFrameHost* render_frame_host, + const GURL& requesting_origin, + bool user_gesture, + const base::DictionaryValue* details, + const StatusesCallback& response_callback) { if (permissions.empty()) { response_callback.Run(std::vector()); return kNoPendingOperation; @@ -143,7 +172,12 @@ int AtomPermissionManager::RequestPermissions( const auto callback = base::Bind(&AtomPermissionManager::OnPermissionResponse, base::Unretained(this), request_id, i); - request_handler_.Run(web_contents, permission, callback); + if (details == nullptr) { + request_handler_.Run(web_contents, permission, callback, + base::DictionaryValue()); + } else { + request_handler_.Run(web_contents, permission, callback, *details); + } } return request_id; diff --git a/atom/browser/atom_permission_manager.h b/atom/browser/atom_permission_manager.h index b8a768a0794..dbeecc3da13 100644 --- a/atom/browser/atom_permission_manager.h +++ b/atom/browser/atom_permission_manager.h @@ -9,7 +9,8 @@ #include #include "base/callback.h" -#include "base/id_map.h" +#include "base/containers/id_map.h" +#include "base/values.h" #include "content/public/browser/permission_manager.h" namespace content { @@ -30,7 +31,8 @@ class AtomPermissionManager : public content::PermissionManager { using RequestHandler = base::Callback; + const StatusCallback&, + const base::DictionaryValue&)>; // Handler to dispatch permission requests in JS. void SetPermissionRequestHandler(const RequestHandler& handler); @@ -43,6 +45,13 @@ class AtomPermissionManager : public content::PermissionManager { bool user_gesture, const base::Callback& callback) override; + int RequestPermissionWithDetails( + content::PermissionType permission, + content::RenderFrameHost* render_frame_host, + const GURL& requesting_origin, + bool user_gesture, + const base::DictionaryValue* details, + const base::Callback& callback); int RequestPermissions( const std::vector& permissions, content::RenderFrameHost* render_frame_host, @@ -51,6 +60,14 @@ class AtomPermissionManager : public content::PermissionManager { const base::Callback&)>& callback) override; + int RequestPermissionsWithDetails( + const std::vector& permissions, + content::RenderFrameHost* render_frame_host, + const GURL& requesting_origin, + bool user_gesture, + const base::DictionaryValue* details, + const base::Callback&)>& callback); protected: void OnPermissionResponse(int request_id, @@ -76,7 +93,7 @@ class AtomPermissionManager : public content::PermissionManager { private: class PendingRequest; - using PendingRequestsMap = IDMap>; + using PendingRequestsMap = base::IDMap>; RequestHandler request_handler_; diff --git a/atom/browser/atom_resource_dispatcher_host_delegate.cc b/atom/browser/atom_resource_dispatcher_host_delegate.cc index 7cb8f718112..beca6c19354 100644 --- a/atom/browser/atom_resource_dispatcher_host_delegate.cc +++ b/atom/browser/atom_resource_dispatcher_host_delegate.cc @@ -61,7 +61,8 @@ void HandleExternalProtocolInUI( GURL escaped_url(net::EscapeExternalHandlerValue(url.spec())); auto callback = base::Bind(&OnOpenExternal, escaped_url); - permission_helper->RequestOpenExternalPermission(callback, has_user_gesture); + permission_helper->RequestOpenExternalPermission(callback, has_user_gesture, + url); } void OnPdfResourceIntercepted( @@ -74,7 +75,7 @@ void OnPdfResourceIntercepted( if (!web_contents) return; - if (!WebContentsPreferences::IsPluginsEnabled(web_contents)) { + if (!WebContentsPreferences::IsPreferenceEnabled("plugins", web_contents)) { auto browser_context = web_contents->GetBrowserContext(); auto download_manager = content::BrowserContext::GetDownloadManager(browser_context); diff --git a/atom/browser/atom_web_ui_controller_factory.cc b/atom/browser/atom_web_ui_controller_factory.cc index d113e656084..3262dc97409 100644 --- a/atom/browser/atom_web_ui_controller_factory.cc +++ b/atom/browser/atom_web_ui_controller_factory.cc @@ -52,7 +52,7 @@ AtomWebUIControllerFactory::CreateWebUIControllerForURL(content::WebUI* web_ui, if (url.host() == kPdfViewerUIHost) { base::StringPairs toplevel_params; base::SplitStringIntoKeyValuePairs(url.query(), '=', '&', &toplevel_params); - std::string stream_id, src; + std::string src; const net::UnescapeRule::Type unescape_rules = net::UnescapeRule::SPACES | net::UnescapeRule::PATH_SEPARATORS | diff --git a/atom/browser/auto_updater.cc b/atom/browser/auto_updater.cc index 8ada3ff6ab3..4dc1818cfb8 100644 --- a/atom/browser/auto_updater.cc +++ b/atom/browser/auto_updater.cc @@ -21,8 +21,7 @@ std::string AutoUpdater::GetFeedURL() { return ""; } -void AutoUpdater::SetFeedURL(const std::string& url, - const HeaderMap& requestHeaders) { +void AutoUpdater::SetFeedURL(mate::Arguments* args) { } void AutoUpdater::CheckForUpdates() { diff --git a/atom/browser/auto_updater.h b/atom/browser/auto_updater.h index 389e39d31eb..ae9178585a7 100644 --- a/atom/browser/auto_updater.h +++ b/atom/browser/auto_updater.h @@ -10,6 +10,7 @@ #include "base/macros.h" #include "build/build_config.h" +#include "native_mate/arguments.h" namespace base { class Time; @@ -53,8 +54,7 @@ class AutoUpdater { static void SetDelegate(Delegate* delegate); static std::string GetFeedURL(); - static void SetFeedURL(const std::string& url, - const HeaderMap& requestHeaders); + static void SetFeedURL(mate::Arguments* args); static void CheckForUpdates(); static void QuitAndInstall(); diff --git a/atom/browser/auto_updater_mac.mm b/atom/browser/auto_updater_mac.mm index e0317f42b03..8f1a6e43bcd 100644 --- a/atom/browser/auto_updater_mac.mm +++ b/atom/browser/auto_updater_mac.mm @@ -9,9 +9,13 @@ #import #import +#include "atom/browser/browser.h" +#include "atom/common/native_mate_converters/value_converter.h" #include "base/bind.h" #include "base/time/time.h" #include "base/strings/sys_string_conversions.h" +#include "native_mate/converter.h" +#include "native_mate/dictionary.h" namespace auto_updater { @@ -34,8 +38,29 @@ std::string AutoUpdater::GetFeedURL() { } // static -void AutoUpdater::SetFeedURL(const std::string& feed, - const HeaderMap& requestHeaders) { +void AutoUpdater::SetFeedURL(mate::Arguments* args) { + mate::Dictionary opts; + std::string feed; + HeaderMap requestHeaders; + std::string serverType = "default"; + if (args->GetNext(&opts)) { + if (!opts.Get("url", &feed)) { + args->ThrowError("Expected options object to contain a 'url' string property in setFeedUrl call"); + return; + } + opts.Get("headers", &requestHeaders); + opts.Get("serverType", &serverType); + if (serverType != "default" && serverType != "json") { + args->ThrowError("Expected serverType to be 'default' or 'json'"); + return; + } + } else if (args->GetNext(&feed)) { + args->GetNext(&requestHeaders); + } else { + args->ThrowError("Expected an options object with a 'url' property to be provided"); + return; + } + Delegate* delegate = GetDelegate(); if (!delegate) return; @@ -55,7 +80,13 @@ void AutoUpdater::SetFeedURL(const std::string& feed, // Initialize the SQRLUpdater. @try { - g_updater = [[SQRLUpdater alloc] initWithUpdateRequest:urlRequest]; + if (serverType == "json") { + NSString* nsAppVersion = base::SysUTF8ToNSString(atom::Browser::Get()->GetVersion()); + g_updater = [[SQRLUpdater alloc] initWithUpdateRequest:urlRequest forVersion:nsAppVersion]; + } else { + // default + g_updater = [[SQRLUpdater alloc] initWithUpdateRequest:urlRequest]; + } } @catch (NSException* error) { delegate->OnError(base::SysNSStringToUTF8(error.reason)); return; diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 8becca03593..f16ee501585 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -14,8 +14,10 @@ #include "base/message_loop/message_loop.h" #include "base/path_service.h" #include "base/run_loop.h" +#include "base/threading/thread_restrictions.h" #include "base/threading/thread_task_runner_handle.h" #include "brightray/browser/brightray_paths.h" +#include "brightray/common/application_info.h" namespace atom { @@ -95,31 +97,26 @@ void Browser::Shutdown() { } std::string Browser::GetVersion() const { - if (version_override_.empty()) { - std::string version = GetExecutableFileVersion(); - if (!version.empty()) - return version; - } - - return version_override_; + std::string ret = brightray::GetOverriddenApplicationVersion(); + if (ret.empty()) + ret = GetExecutableFileVersion(); + return ret; } void Browser::SetVersion(const std::string& version) { - version_override_ = version; + brightray::OverrideApplicationVersion(version); } std::string Browser::GetName() const { - if (name_override_.empty()) { - std::string name = GetExecutableFileProductName(); - if (!name.empty()) - return name; - } - - return name_override_; + std::string ret = name_override_; + if (ret.empty()) + ret = GetExecutableFileProductName(); + return ret; } void Browser::SetName(const std::string& name) { name_override_ = name; + brightray::OverrideApplicationName(name); } int Browser::GetBadgeCount() { @@ -151,6 +148,7 @@ void Browser::WillFinishLaunching() { void Browser::DidFinishLaunching(const base::DictionaryValue& launch_info) { // Make sure the userData directory is created. + base::ThreadRestrictions::ScopedAllowIO allow_io; base::FilePath user_data; if (PathService::Get(brightray::DIR_USER_DATA, &user_data)) base::CreateDirectoryAndGetError(user_data, nullptr); diff --git a/atom/browser/browser.h b/atom/browser/browser.h index b5e31160c8f..0970b26b776 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -105,6 +105,9 @@ class Browser : public WindowListObserver { LoginItemSettings GetLoginItemSettings(const LoginItemSettings& options); #if defined(OS_MACOSX) + // Set the handler which decides whether to shutdown. + void SetShutdownHandler(base::Callback handler); + // Hide the application. void Hide(); @@ -270,15 +273,10 @@ class Browser : public WindowListObserver { // The browser is being shutdown. bool is_shutdown_; - std::string version_override_; std::string name_override_; int badge_count_ = 0; -#if defined(OS_WIN) - base::string16 app_user_model_id_; -#endif - #if defined(OS_MACOSX) base::DictionaryValue about_panel_options_; #endif diff --git a/atom/browser/browser_linux.cc b/atom/browser/browser_linux.cc index 280d2defb6b..42cdcc043d7 100644 --- a/atom/browser/browser_linux.cc +++ b/atom/browser/browser_linux.cc @@ -31,10 +31,7 @@ bool LaunchXdgUtility(const std::vector& argv, int* exit_code) { if (devnull < 0) return false; base::LaunchOptions options; - - base::FileHandleMappingVector remap; - remap.push_back(std::make_pair(devnull, STDIN_FILENO)); - options.fds_to_remap = &remap; + options.fds_to_remap.push_back(std::make_pair(devnull, STDIN_FILENO)); base::Process process = base::LaunchProcess(argv, options); close(devnull); diff --git a/atom/browser/browser_mac.mm b/atom/browser/browser_mac.mm index d12de2c14c9..8f13626b9f4 100644 --- a/atom/browser/browser_mac.mm +++ b/atom/browser/browser_mac.mm @@ -17,10 +17,15 @@ #include "base/strings/sys_string_conversions.h" #include "brightray/common/application_info.h" #include "net/base/mac/url_conversions.h" +#include "ui/gfx/image/image.h" #include "url/gurl.h" namespace atom { +void Browser::SetShutdownHandler(base::Callback handler) { + [[AtomApplication sharedApplication] setShutdownHandler:std::move(handler)]; +} + void Browser::Focus() { [[AtomApplication sharedApplication] activateIgnoringOtherApps:YES]; } diff --git a/atom/browser/browser_win.cc b/atom/browser/browser_win.cc index ac0f713c889..f27e3dfa139 100644 --- a/atom/browser/browser_win.cc +++ b/atom/browser/browser_win.cc @@ -20,16 +20,16 @@ #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_restrictions.h" #include "base/win/registry.h" #include "base/win/win_util.h" #include "base/win/windows_version.h" +#include "brightray/common/application_info.h" namespace atom { namespace { -const wchar_t kAppUserModelIDFormat[] = L"electron.app.$1"; - BOOL CALLBACK WindowsEnumerationHandler(HWND hwnd, LPARAM param) { DWORD target_process_id = *reinterpret_cast(param); DWORD process_id = 0; @@ -118,8 +118,7 @@ void Browser::ClearRecentDocuments() { } void Browser::SetAppUserModelID(const base::string16& name) { - app_user_model_id_ = name; - SetCurrentProcessExplicitAppUserModelID(app_user_model_id_.c_str()); + brightray::SetAppUserModelID(name); } bool Browser::SetUserTasks(const std::vector& tasks) { @@ -153,15 +152,19 @@ bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol, // Main Registry Key HKEY root = HKEY_CURRENT_USER; - base::string16 keyPath = base::UTF8ToUTF16("Software\\Classes\\" + protocol); + base::string16 keyPath = L"Software\\Classes\\"; // Command Key - base::string16 cmdPath = keyPath + L"\\shell\\open\\command"; + base::string16 wprotocol = base::UTF8ToUTF16(protocol); + base::string16 shellPath = wprotocol + L"\\shell"; + base::string16 cmdPath = keyPath + shellPath + L"\\open\\command"; - base::win::RegKey key; + base::win::RegKey classesKey; 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 + + if (FAILED(classesKey.Open(root, keyPath.c_str(), KEY_ALL_ACCESS))) + // Classes key doesn't exist, that's concerning, but I guess + // we're not the default handler return true; if (FAILED(commandKey.Open(root, cmdPath.c_str(), KEY_ALL_ACCESS))) @@ -179,9 +182,25 @@ bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol, if (keyVal == exe) { // Let's kill the key - if (FAILED(key.DeleteKey(L"shell"))) + if (FAILED(classesKey.DeleteKey(shellPath.c_str()))) return false; + // Let's clean up after ourselves + base::win::RegKey protocolKey; + base::string16 protocolPath = keyPath + wprotocol; + + if (SUCCEEDED(protocolKey + .Open(root, protocolPath.c_str(), KEY_ALL_ACCESS))) { + protocolKey.DeleteValue(L"URL Protocol"); + + // Overwrite the default value to be empty, we can't delete it right away + protocolKey.WriteValue(L"", L""); + protocolKey.DeleteValue(L""); + } + + // If now empty, delete the whole key + classesKey.DeleteEmptyKey(wprotocol.c_str()); + return true; } else { return true; @@ -303,17 +322,13 @@ Browser::LoginItemSettings Browser::GetLoginItemSettings( } PCWSTR Browser::GetAppUserModelID() { - if (app_user_model_id_.empty()) { - SetAppUserModelID(base::ReplaceStringPlaceholders( - kAppUserModelIDFormat, base::UTF8ToUTF16(GetName()), nullptr)); - } - - return app_user_model_id_.c_str(); + return brightray::GetRawAppUserModelID(); } std::string Browser::GetExecutableFileVersion() const { base::FilePath path; if (PathService::Get(base::FILE_EXE, &path)) { + base::ThreadRestrictions::ScopedAllowIO allow_io; std::unique_ptr version_info( FileVersionInfo::CreateFileVersionInfo(path)); return base::UTF16ToUTF8(version_info->product_version()); @@ -323,14 +338,7 @@ std::string Browser::GetExecutableFileVersion() const { } std::string Browser::GetExecutableFileProductName() const { - base::FilePath path; - if (PathService::Get(base::FILE_EXE, &path)) { - std::unique_ptr version_info( - FileVersionInfo::CreateFileVersionInfo(path)); - return base::UTF16ToUTF8(version_info->product_name()); - } - - return ATOM_PRODUCT_NAME; + return brightray::GetApplicationName(); } } // namespace atom diff --git a/atom/browser/javascript_environment.cc b/atom/browser/javascript_environment.cc index b7e3aae20aa..631beb4ff61 100644 --- a/atom/browser/javascript_environment.cc +++ b/atom/browser/javascript_environment.cc @@ -8,12 +8,14 @@ #include "base/command_line.h" #include "base/message_loop/message_loop.h" +#include "base/task_scheduler/initialization_util.h" #include "base/threading/thread_task_runner_handle.h" #include "content/public/common/content_switches.h" #include "gin/array_buffer.h" #include "gin/v8_initializer.h" #include "atom/common/node_includes.h" +#include "vendor/node/src/tracing/trace_event.h" namespace atom { @@ -44,9 +46,17 @@ bool JavascriptEnvironment::Initialize() { if (!js_flags.empty()) v8::V8::SetFlagsFromString(js_flags.c_str(), js_flags.size()); + // The V8Platform of gin relies on Chromium's task schedule, which has not + // been started at this point, so we have to rely on Node's V8Platform. + platform_ = node::CreatePlatform( + base::RecommendedMaxNumberOfThreadsInPool(3, 8, 0.1, 0), nullptr); + v8::V8::InitializePlatform(platform_); + node::tracing::TraceEventHelper::SetTracingController( + new v8::TracingController()); gin::IsolateHolder::Initialize(gin::IsolateHolder::kNonStrictMode, gin::IsolateHolder::kStableV8Extras, - gin::ArrayBufferAllocator::SharedInstance()); + gin::ArrayBufferAllocator::SharedInstance(), + false); return true; } diff --git a/atom/browser/javascript_environment.h b/atom/browser/javascript_environment.h index 43a7295f902..69872e454aa 100644 --- a/atom/browser/javascript_environment.h +++ b/atom/browser/javascript_environment.h @@ -10,6 +10,7 @@ namespace node { class Environment; +class MultiIsolatePlatform; } namespace atom { @@ -22,6 +23,7 @@ class JavascriptEnvironment { void OnMessageLoopCreated(); void OnMessageLoopDestroying(); + node::MultiIsolatePlatform* platform() const { return platform_; } v8::Isolate* isolate() const { return isolate_; } v8::Local context() const { return v8::Local::New(isolate_, context_); @@ -30,6 +32,9 @@ class JavascriptEnvironment { private: bool Initialize(); + // Leaked on exit. + node::MultiIsolatePlatform* platform_; + bool initialized_; gin::IsolateHolder isolate_holder_; v8::Isolate* isolate_; diff --git a/atom/browser/lib/bluetooth_chooser.cc b/atom/browser/lib/bluetooth_chooser.cc index cf9a2444751..e41b5555af3 100644 --- a/atom/browser/lib/bluetooth_chooser.cc +++ b/atom/browser/lib/bluetooth_chooser.cc @@ -97,6 +97,17 @@ void BluetoothChooser::AddOrUpdateDevice(const std::string& device_id, int signal_strength_level) { DeviceInfo info = {device_id, device_name}; device_list_.push_back(info); + + // Emit a select-bluetooth-device handler to allow for user to listen for + // bluetooth device found. + bool prevent_default = api_web_contents_->Emit("select-bluetooth-device", + device_list_, base::Bind(&OnDeviceChosen, event_handler_)); + + // If emit not implimented select first device that matches the filters + // provided. + if (!prevent_default) { + event_handler_.Run(Event::SELECTED, device_id); + } } void BluetoothChooser::RemoveDevice(const std::string& device_id) { diff --git a/atom/browser/lib/power_observer.h b/atom/browser/lib/power_observer.h new file mode 100644 index 00000000000..0935b221e33 --- /dev/null +++ b/atom/browser/lib/power_observer.h @@ -0,0 +1,26 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_LIB_POWER_OBSERVER_H_ +#define ATOM_BROWSER_LIB_POWER_OBSERVER_H_ + +#include "base/macros.h" + +#if defined(OS_LINUX) +#include "atom/browser/lib/power_observer_linux.h" +#else +#include "base/power_monitor/power_observer.h" +#endif // defined(OS_LINUX) + +namespace atom { + +#if defined(OS_LINUX) +typedef PowerObserverLinux PowerObserver; +#else +typedef base::PowerObserver PowerObserver; +#endif // defined(OS_LINUX) + +} // namespace atom + +#endif // ATOM_BROWSER_LIB_POWER_OBSERVER_H_ diff --git a/atom/browser/lib/power_observer_linux.cc b/atom/browser/lib/power_observer_linux.cc new file mode 100644 index 00000000000..27da2b91762 --- /dev/null +++ b/atom/browser/lib/power_observer_linux.cc @@ -0,0 +1,165 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. +#include "atom/browser/lib/power_observer_linux.h" + +#include +#include +#include + +#include "base/bind.h" +#include "device/bluetooth/dbus/dbus_thread_manager_linux.h" + +namespace { + +const char kLogindServiceName[] = "org.freedesktop.login1"; +const char kLogindObjectPath[] = "/org/freedesktop/login1"; +const char kLogindManagerInterface[] = "org.freedesktop.login1.Manager"; + +std::string get_executable_basename() { + char buf[4096]; + size_t buf_size = sizeof(buf); + std::string rv("electron"); + if (!uv_exepath(buf, &buf_size)) { + rv = strrchr(static_cast(buf), '/') + 1; + } + return std::move(rv); +} + +} // namespace + +namespace atom { + +PowerObserverLinux::PowerObserverLinux() + : lock_owner_name_(get_executable_basename()), weak_ptr_factory_(this) { + auto dbus_thread_manager = bluez::DBusThreadManagerLinux::Get(); + if (dbus_thread_manager) { + bus_ = dbus_thread_manager->GetSystemBus(); + if (bus_) { + logind_ = bus_->GetObjectProxy(kLogindServiceName, + dbus::ObjectPath(kLogindObjectPath)); + logind_->WaitForServiceToBeAvailable( + base::Bind(&PowerObserverLinux::OnLoginServiceAvailable, + weak_ptr_factory_.GetWeakPtr())); + } else { + LOG(WARNING) << "Failed to get system bus connection"; + } + } else { + LOG(WARNING) << "DBusThreadManagerLinux instance isn't available"; + } +} + +void PowerObserverLinux::OnLoginServiceAvailable(bool service_available) { + if (!service_available) { + LOG(WARNING) << kLogindServiceName << " not available"; + return; + } + // Connect to PrepareForShutdown/PrepareForSleep signals + logind_->ConnectToSignal(kLogindManagerInterface, "PrepareForShutdown", + base::Bind(&PowerObserverLinux::OnPrepareForShutdown, + weak_ptr_factory_.GetWeakPtr()), + base::Bind(&PowerObserverLinux::OnSignalConnected, + weak_ptr_factory_.GetWeakPtr())); + logind_->ConnectToSignal(kLogindManagerInterface, "PrepareForSleep", + base::Bind(&PowerObserverLinux::OnPrepareForSleep, + weak_ptr_factory_.GetWeakPtr()), + base::Bind(&PowerObserverLinux::OnSignalConnected, + weak_ptr_factory_.GetWeakPtr())); + // Take sleep inhibit lock + BlockSleep(); +} + +void PowerObserverLinux::BlockSleep() { + dbus::MethodCall sleep_inhibit_call(kLogindManagerInterface, "Inhibit"); + dbus::MessageWriter inhibit_writer(&sleep_inhibit_call); + inhibit_writer.AppendString("sleep"); // what + // Use the executable name as the lock owner, which will list rebrands of the + // electron executable as separate entities. + inhibit_writer.AppendString(lock_owner_name_); // who + inhibit_writer.AppendString("Application cleanup before suspend"); // why + inhibit_writer.AppendString("delay"); // mode + logind_->CallMethod(&sleep_inhibit_call, + dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, + base::Bind(&PowerObserverLinux::OnInhibitResponse, + weak_ptr_factory_.GetWeakPtr(), &sleep_lock_)); +} + +void PowerObserverLinux::UnblockSleep() { + sleep_lock_.reset(); +} + +void PowerObserverLinux::BlockShutdown() { + if (shutdown_lock_.is_valid()) { + LOG(WARNING) << "Trying to subscribe to shutdown multiple times"; + return; + } + dbus::MethodCall shutdown_inhibit_call(kLogindManagerInterface, "Inhibit"); + dbus::MessageWriter inhibit_writer(&shutdown_inhibit_call); + inhibit_writer.AppendString("shutdown"); // what + inhibit_writer.AppendString(lock_owner_name_); // who + inhibit_writer.AppendString("Ensure a clean shutdown"); // why + inhibit_writer.AppendString("delay"); // mode + logind_->CallMethod( + &shutdown_inhibit_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, + base::Bind(&PowerObserverLinux::OnInhibitResponse, + weak_ptr_factory_.GetWeakPtr(), &shutdown_lock_)); +} + +void PowerObserverLinux::UnblockShutdown() { + if (!shutdown_lock_.is_valid()) { + LOG(WARNING) + << "Trying to unsubscribe to shutdown without being subscribed"; + return; + } + shutdown_lock_.reset(); +} + +void PowerObserverLinux::SetShutdownHandler(base::Callback handler) { + should_shutdown_ = std::move(handler); +} + +void PowerObserverLinux::OnInhibitResponse(base::ScopedFD* scoped_fd, + dbus::Response* response) { + dbus::MessageReader reader(response); + reader.PopFileDescriptor(scoped_fd); +} + +void PowerObserverLinux::OnPrepareForSleep(dbus::Signal* signal) { + dbus::MessageReader reader(signal); + bool suspending; + if (!reader.PopBool(&suspending)) { + LOG(ERROR) << "Invalid signal: " << signal->ToString(); + return; + } + if (suspending) { + OnSuspend(); + UnblockSleep(); + } else { + BlockSleep(); + OnResume(); + } +} + +void PowerObserverLinux::OnPrepareForShutdown(dbus::Signal* signal) { + dbus::MessageReader reader(signal); + bool shutting_down; + if (!reader.PopBool(&shutting_down)) { + LOG(ERROR) << "Invalid signal: " << signal->ToString(); + return; + } + if (shutting_down) { + if (!should_shutdown_ || should_shutdown_.Run()) { + // The user didn't try to prevent shutdown. Release the lock and allow the + // shutdown to continue normally. + shutdown_lock_.reset(); + } + } +} + +void PowerObserverLinux::OnSignalConnected(const std::string& interface, + const std::string& signal, + bool success) { + LOG_IF(WARNING, !success) << "Failed to connect to " << signal; +} + +} // namespace atom diff --git a/atom/browser/lib/power_observer_linux.h b/atom/browser/lib/power_observer_linux.h new file mode 100644 index 00000000000..c2cb1d80150 --- /dev/null +++ b/atom/browser/lib/power_observer_linux.h @@ -0,0 +1,54 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_LIB_POWER_OBSERVER_LINUX_H_ +#define ATOM_BROWSER_LIB_POWER_OBSERVER_LINUX_H_ + +#include + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/power_monitor/power_observer.h" +#include "dbus/bus.h" +#include "dbus/message.h" +#include "dbus/object_proxy.h" + +namespace atom { + +class PowerObserverLinux : public base::PowerObserver { + public: + PowerObserverLinux(); + + protected: + void BlockSleep(); + void UnblockSleep(); + void BlockShutdown(); + void UnblockShutdown(); + + void SetShutdownHandler(base::Callback should_shutdown); + + private: + void OnLoginServiceAvailable(bool available); + void OnInhibitResponse(base::ScopedFD* scoped_fd, dbus::Response* response); + void OnPrepareForSleep(dbus::Signal* signal); + void OnPrepareForShutdown(dbus::Signal* signal); + void OnSignalConnected(const std::string& interface, + const std::string& signal, + bool success); + + base::Callback should_shutdown_; + + scoped_refptr bus_; + scoped_refptr logind_; + std::string lock_owner_name_; + base::ScopedFD sleep_lock_; + base::ScopedFD shutdown_lock_; + base::WeakPtrFactory weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(PowerObserverLinux); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_LIB_POWER_OBSERVER_LINUX_H_ diff --git a/atom/browser/mac/atom_application.h b/atom/browser/mac/atom_application.h index 45bb011cd77..243667b4603 100644 --- a/atom/browser/mac/atom_application.h +++ b/atom/browser/mac/atom_application.h @@ -2,8 +2,9 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#import "base/mac/scoped_sending_event.h" -#import "base/mac/scoped_nsobject.h" +#include "base/callback.h" +#include "base/mac/scoped_sending_event.h" +#include "base/mac/scoped_nsobject.h" @interface AtomApplication : NSApplication currentActivity_; NSCondition* handoffLock_; BOOL updateReceived_; + base::Callback shouldShutdown_; } + (AtomApplication*)sharedApplication; +- (void)setShutdownHandler:(base::Callback)handler; + // CrAppProtocol: - (BOOL)isHandlingSendEvent; diff --git a/atom/browser/mac/atom_application.mm b/atom/browser/mac/atom_application.mm index f934bfeacdd..a7ea338857f 100644 --- a/atom/browser/mac/atom_application.mm +++ b/atom/browser/mac/atom_application.mm @@ -4,6 +4,7 @@ #import "atom/browser/mac/atom_application.h" +#import "atom/browser/mac/atom_application_delegate.h" #include "atom/browser/mac/dict_util.h" #include "atom/browser/browser.h" #include "base/auto_reset.h" @@ -27,6 +28,20 @@ inline void dispatch_sync_main(dispatch_block_t block) { return (AtomApplication*)[super sharedApplication]; } +- (void)terminate:(id)sender { + if (shouldShutdown_ && !shouldShutdown_.Run()) + return; // User will call Quit later. + + // We simply try to close the browser, which in turn will try to close the + // windows. Termination can proceed if all windows are closed or window close + // can be cancelled which will abort termination. + atom::Browser::Get()->Quit(); +} + +- (void)setShutdownHandler:(base::Callback)handler { + shouldShutdown_ = std::move(handler); +} + - (BOOL)isHandlingSendEvent { return handlingSendEvent_; } diff --git a/atom/browser/mac/atom_application_delegate.mm b/atom/browser/mac/atom_application_delegate.mm index 9a5133368ef..e210ad2b6a7 100644 --- a/atom/browser/mac/atom_application_delegate.mm +++ b/atom/browser/mac/atom_application_delegate.mm @@ -87,17 +87,6 @@ static base::mac::ScopedObjCClassSwizzler* g_swizzle_imk_input_session; return atom::Browser::Get()->OpenFile(filename_str) ? YES : NO; } -- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender { - atom::Browser* browser = atom::Browser::Get(); - if (browser->is_quiting()) { - return NSTerminateNow; - } else { - // System started termination. - atom::Browser::Get()->Quit(); - return NSTerminateCancel; - } -} - - (BOOL)applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag { atom::Browser* browser = atom::Browser::Get(); diff --git a/atom/browser/mac/dict_util.mm b/atom/browser/mac/dict_util.mm index 32141cdeb57..5761e9a4369 100644 --- a/atom/browser/mac/dict_util.mm +++ b/atom/browser/mac/dict_util.mm @@ -87,18 +87,17 @@ std::unique_ptr NSDictionaryToDictionaryValue( id value = [dict objectForKey:key]; if ([value isKindOfClass:[NSString class]]) { - result->SetStringWithoutPathExpansion( - str_key, base::SysNSStringToUTF8(value)); + result->SetKey(str_key, base::Value(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]); + result->SetKey(str_key, base::Value([value boolValue])); else if (strcmp(objc_type, @encode(double)) == 0 || strcmp(objc_type, @encode(float)) == 0) - result->SetDoubleWithoutPathExpansion(str_key, [value doubleValue]); + result->SetKey(str_key, base::Value([value doubleValue])); else - result->SetIntegerWithoutPathExpansion(str_key, [value intValue]); + result->SetKey(str_key, base::Value([value intValue])); } else if ([value isKindOfClass:[NSArray class]]) { std::unique_ptr sub_arr = NSArrayToListValue(value); if (sub_arr) @@ -115,9 +114,8 @@ std::unique_ptr NSDictionaryToDictionaryValue( result->SetWithoutPathExpansion(str_key, base::MakeUnique()); } else { - result->SetStringWithoutPathExpansion( - str_key, - base::SysNSStringToUTF8([value description])); + result->SetKey(str_key, + base::Value(base::SysNSStringToUTF8([value description]))); } } diff --git a/atom/browser/mac/in_app_purchase.h b/atom/browser/mac/in_app_purchase.h new file mode 100644 index 00000000000..d014744d55c --- /dev/null +++ b/atom/browser/mac/in_app_purchase.h @@ -0,0 +1,30 @@ +// Copyright (c) 2017 Amaplex Software, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_MAC_IN_APP_PURCHASE_H_ +#define ATOM_BROWSER_MAC_IN_APP_PURCHASE_H_ + +#include + +#include "base/callback.h" + +namespace in_app_purchase { + +// --------------------------- Typedefs --------------------------- + +typedef base::Callback InAppPurchaseCallback; + +// --------------------------- Functions --------------------------- + +bool CanMakePayments(void); + +std::string GetReceiptURL(void); + +void PurchaseProduct(const std::string& productID, + int quantity, + const InAppPurchaseCallback& callback); + +} // namespace in_app_purchase + +#endif // ATOM_BROWSER_MAC_IN_APP_PURCHASE_H_ diff --git a/atom/browser/mac/in_app_purchase.mm b/atom/browser/mac/in_app_purchase.mm new file mode 100644 index 00000000000..f9f98332da1 --- /dev/null +++ b/atom/browser/mac/in_app_purchase.mm @@ -0,0 +1,157 @@ +// Copyright (c) 2017 Amaplex Software, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/mac/in_app_purchase.h" + +#include "base/bind.h" +#include "base/strings/sys_string_conversions.h" +#include "content/public/browser/browser_thread.h" + +#import +#import + +// ============================================================================ +// InAppPurchase +// ============================================================================ + +// --------------------------------- Interface -------------------------------- + +@interface InAppPurchase : NSObject { + @private + in_app_purchase::InAppPurchaseCallback callback_; + NSInteger quantity_; +} + +- (id)initWithCallback:(const in_app_purchase::InAppPurchaseCallback&)callback + quantity:(NSInteger)quantity; + +- (void)purchaseProduct:(NSString*)productID; + +@end + +// ------------------------------- Implementation ----------------------------- + +@implementation InAppPurchase + +/** + * Init with a callback. + * + * @param callback - The callback that will be called when the payment is added + * to the queue. + */ +- (id)initWithCallback:(const in_app_purchase::InAppPurchaseCallback&)callback + quantity:(NSInteger)quantity { + if ((self = [super init])) { + callback_ = callback; + quantity_ = quantity; + } + + return self; +} + +/** + * Start the in-app purchase process. + * + * @param productID - The id of the product to purchase (the id of + * com.example.app.product1 is product1). + */ +- (void)purchaseProduct:(NSString*)productID { + // Retrieve the product information. (The products request retrieves, + // information about valid products along with a list of the invalid product + // identifiers, and then calls its delegate to process the result). + SKProductsRequest* productsRequest; + productsRequest = [[SKProductsRequest alloc] + initWithProductIdentifiers:[NSSet setWithObject:productID]]; + + productsRequest.delegate = self; + [productsRequest start]; +} + +/** + * Process product informations and start the payment. + * + * @param request - The product request. + * @param response - The informations about the list of products. + */ +- (void)productsRequest:(SKProductsRequest*)request + didReceiveResponse:(SKProductsResponse*)response { + // Release request object. + [request release]; + + // Get the first product. + NSArray* products = response.products; + SKProduct* product = [products count] == 1 ? [products firstObject] : nil; + + // Return if the product is not found or invalid. + if (product == nil) { + [self runCallback:false]; + return; + } + + // Start the payment process. + [self checkout:product]; +} + +/** + * Submit a payment request to the App Store. + * + * @param product - The product to purchase. + */ +- (void)checkout:(SKProduct*)product { + // Add the payment to the transaction queue. (The observer will be called + // when the transaction is finished). + SKMutablePayment* payment = [SKMutablePayment paymentWithProduct:product]; + payment.quantity = quantity_; + + [[SKPaymentQueue defaultQueue] addPayment:payment]; + + // Notify that the payment has been added to the queue with success. + [self runCallback:true]; +} + +/** + * Submit a payment request to the App Store. + * + * @param product - The product to purchase. + */ +- (void)runCallback:(bool)isProductValid { + if (callback_) { + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, + base::Bind(callback_, isProductValid)); + } + // Release this delegate. + [self release]; +} + +@end + +// ============================================================================ +// C++ in_app_purchase +// ============================================================================ + +namespace in_app_purchase { + +bool CanMakePayments() { + return [SKPaymentQueue canMakePayments]; +} + +std::string GetReceiptURL() { + NSURL* receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; + if (receiptURL != nil) { + return [[receiptURL absoluteString] UTF8String]; + } else { + return ""; + } +} + +void PurchaseProduct(const std::string& productID, + int quantity, + const InAppPurchaseCallback& callback) { + auto* iap = + [[InAppPurchase alloc] initWithCallback:callback quantity:quantity]; + + [iap purchaseProduct:base::SysUTF8ToNSString(productID)]; +} + +} // namespace in_app_purchase diff --git a/atom/browser/mac/in_app_purchase_observer.h b/atom/browser/mac/in_app_purchase_observer.h new file mode 100644 index 00000000000..d2d0be2b0ae --- /dev/null +++ b/atom/browser/mac/in_app_purchase_observer.h @@ -0,0 +1,59 @@ +// Copyright (c) 2017 Amaplex Software, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_MAC_IN_APP_PURCHASE_OBSERVER_H_ +#define ATOM_BROWSER_MAC_IN_APP_PURCHASE_OBSERVER_H_ + +#include +#include + +#include "base/callback.h" +#include "base/memory/weak_ptr.h" + +#if defined(__OBJC__) +@class InAppTransactionObserver; +#else // __OBJC__ +class InAppTransactionObserver; +#endif // __OBJC__ + +namespace in_app_purchase { + +// --------------------------- Structures --------------------------- + +struct Payment { + std::string productIdentifier = ""; + int quantity = 1; +}; + +struct Transaction { + std::string transactionIdentifier = ""; + std::string transactionDate = ""; + std::string originalTransactionIdentifier = ""; + int errorCode = 0; + std::string errorMessage = ""; + std::string transactionState = ""; + Payment payment; +}; + +// --------------------------- Classes --------------------------- + +class TransactionObserver { + public: + TransactionObserver(); + virtual ~TransactionObserver(); + + virtual void OnTransactionsUpdated( + const std::vector& transactions) = 0; + + private: + InAppTransactionObserver* obeserver_; + + base::WeakPtrFactory weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(TransactionObserver); +}; + +} // namespace in_app_purchase + +#endif // ATOM_BROWSER_MAC_IN_APP_PURCHASE_OBSERVER_H_ diff --git a/atom/browser/mac/in_app_purchase_observer.mm b/atom/browser/mac/in_app_purchase_observer.mm new file mode 100644 index 00000000000..ef21228ad6e --- /dev/null +++ b/atom/browser/mac/in_app_purchase_observer.mm @@ -0,0 +1,188 @@ +// Copyright (c) 2017 Amaplex Software, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/mac/in_app_purchase_observer.h" + +#include "base/bind.h" +#include "base/strings/sys_string_conversions.h" +#include "content/public/browser/browser_thread.h" + +#import +#import + +// ============================================================================ +// InAppTransactionObserver +// ============================================================================ + +namespace { + +using InAppTransactionCallback = + base::RepeatingCallback< + void(const std::vector&)>; + +} // namespace + +@interface InAppTransactionObserver : NSObject { + @private + InAppTransactionCallback callback_; +} + +- (id)initWithCallback:(const InAppTransactionCallback&)callback; + +@end + +@implementation InAppTransactionObserver + +/** + * Init with a callback. + * + * @param callback - The callback that will be called for each transaction + * update. + */ +- (id)initWithCallback:(const InAppTransactionCallback&)callback { + if ((self = [super init])) { + callback_ = callback; + + [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; + } + + return self; +} + +/** + * Cleanup. + */ +- (void)dealloc { + [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; + [super dealloc]; +} + +/** + * Run the callback in the browser thread. + * + * @param transaction - The transaction to pass to the callback. + */ +- (void)runCallback:(NSArray*)transactions { + // Convert the transaction. + std::vector converted; + converted.reserve([transactions count]); + for (SKPaymentTransaction* transaction in transactions) { + converted.push_back([self skPaymentTransactionToStruct:transaction]); + } + + // Send the callback to the browser thread. + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, base::Bind(callback_, converted)); +} + +/** + * Convert an NSDate to ISO String. + * + * @param date - The date to convert. + */ +- (NSString*)dateToISOString:(NSDate*)date { + NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init]; + NSLocale* enUSPOSIXLocale = + [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + [dateFormatter setLocale:enUSPOSIXLocale]; + [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZZZ"]; + + return [dateFormatter stringFromDate:date]; +} + +/** + * Convert a SKPayment object to a Payment structure. + * + * @param payment - The SKPayment object to convert. + */ +- (in_app_purchase::Payment)skPaymentToStruct:(SKPayment*)payment { + in_app_purchase::Payment paymentStruct; + + if (payment.productIdentifier != nil) { + paymentStruct.productIdentifier = [payment.productIdentifier UTF8String]; + } + + if (payment.quantity >= 1) { + paymentStruct.quantity = (int)payment.quantity; + } + + return paymentStruct; +} + +/** + * Convert a SKPaymentTransaction object to a Transaction structure. + * + * @param transaction - The SKPaymentTransaction object to convert. + */ +- (in_app_purchase::Transaction)skPaymentTransactionToStruct: + (SKPaymentTransaction*)transaction { + in_app_purchase::Transaction transactionStruct; + + if (transaction.transactionIdentifier != nil) { + transactionStruct.transactionIdentifier = + [transaction.transactionIdentifier UTF8String]; + } + + if (transaction.transactionDate != nil) { + transactionStruct.transactionDate = + [[self dateToISOString:transaction.transactionDate] UTF8String]; + } + + if (transaction.originalTransaction != nil) { + transactionStruct.originalTransactionIdentifier = + [transaction.originalTransaction.transactionIdentifier UTF8String]; + } + + if (transaction.error != nil) { + transactionStruct.errorCode = (int)transaction.error.code; + transactionStruct.errorMessage = + [[transaction.error localizedDescription] UTF8String]; + } + + if (transaction.transactionState < 5) { + transactionStruct.transactionState = [[@[ + @"purchasing", @"purchased", @"failed", @"restored", @"deferred" + ] objectAtIndex:transaction.transactionState] UTF8String]; + } + + if (transaction.payment != nil) { + transactionStruct.payment = [self skPaymentToStruct:transaction.payment]; + } + + return transactionStruct; +} + +#pragma mark - +#pragma mark SKPaymentTransactionObserver methods + +/** + * Executed when a transaction is updated. + * + * @param queue - The payment queue. + * @param transactions - The list of transactions updated. + */ +- (void)paymentQueue:(SKPaymentQueue*)queue + updatedTransactions:(NSArray*)transactions { + [self runCallback:transactions]; +} + +@end + +// ============================================================================ +// C++ in_app_purchase +// ============================================================================ + +namespace in_app_purchase { + +TransactionObserver::TransactionObserver() : weak_ptr_factory_(this) { + obeserver_ = [[InAppTransactionObserver alloc] + initWithCallback:base::Bind(&TransactionObserver::OnTransactionsUpdated, + weak_ptr_factory_.GetWeakPtr())]; +} + +TransactionObserver::~TransactionObserver() { + [obeserver_ release]; +} + +} // namespace in_app_purchase diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 1b4111eabce..ee8ba1dc87d 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -8,44 +8,12 @@ #include #include -#include "atom/browser/atom_browser_context.h" -#include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/browser.h" -#include "atom/browser/unresponsive_suppressor.h" #include "atom/browser/window_list.h" -#include "atom/common/api/api_messages.h" -#include "atom/common/native_mate_converters/file_path_converter.h" +#include "atom/common/color_util.h" #include "atom/common/options_switches.h" -#include "base/files/file_util.h" -#include "base/json/json_writer.h" -#include "base/message_loop/message_loop.h" -#include "base/strings/utf_string_conversions.h" -#include "base/threading/thread_task_runner_handle.h" #include "brightray/browser/inspectable_web_contents.h" -#include "brightray/browser/inspectable_web_contents_view.h" -#include "components/prefs/pref_service.h" -#include "content/browser/renderer_host/render_widget_host_impl.h" -#include "content/public/browser/navigation_entry.h" -#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" #include "native_mate/dictionary.h" -#include "third_party/skia/include/core/SkRegion.h" -#include "ui/gfx/codec/png_codec.h" -#include "ui/gfx/geometry/point.h" -#include "ui/gfx/geometry/rect.h" -#include "ui/gfx/geometry/size.h" -#include "ui/gfx/geometry/size_conversions.h" -#include "ui/gl/gpu_switching_manager.h" - -#if defined(OS_LINUX) || defined(OS_WIN) -#include "content/public/common/renderer_preferences.h" -#include "ui/gfx/font_render_params.h" -#endif DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::NativeWindowRelay); @@ -55,8 +23,7 @@ NativeWindow::NativeWindow( brightray::InspectableWebContents* inspectable_web_contents, const mate::Dictionary& options, NativeWindow* parent) - : content::WebContentsObserver(inspectable_web_contents->GetWebContents()), - has_frame_(true), + : has_frame_(true), transparent_(false), enable_larger_than_screen_(false), is_closed_(false), @@ -66,7 +33,7 @@ NativeWindow::NativeWindow( parent_(parent), is_modal_(false), is_osr_dummy_(false), - inspectable_web_contents_(inspectable_web_contents), + browser_view_(nullptr), weak_factory_(this) { options.Get(options::kFrame, &has_frame_); options.Get(options::kTransparent, &transparent_); @@ -75,24 +42,6 @@ NativeWindow::NativeWindow( if (parent) options.Get("modal", &is_modal_); -#if defined(OS_LINUX) || defined(OS_WIN) - auto* prefs = web_contents()->GetMutableRendererPrefs(); - - // Update font settings. - CR_DEFINE_STATIC_LOCAL(const gfx::FontRenderParams, params, - (gfx::GetFontRenderParams(gfx::FontRenderParamsQuery(), nullptr))); - prefs->should_antialias_text = params.antialiasing; - prefs->use_subpixel_positioning = params.subpixel_positioning; - prefs->hinting = params.hinting; - prefs->use_autohinter = params.autohinter; - prefs->use_bitmaps = params.use_bitmaps; - prefs->subpixel_rendering = params.subpixel_rendering; -#endif - - // Tell the content module to initialize renderer widget with transparent - // mode. - ui::GpuSwitchingManager::SetTransparent(transparent_); - WindowList::AddWindow(this); } @@ -102,16 +51,6 @@ NativeWindow::~NativeWindow() { NotifyWindowClosed(); } -// static -NativeWindow* NativeWindow::FromWebContents( - content::WebContents* web_contents) { - for (const auto& window : WindowList::GetWindows()) { - if (window->web_contents() == web_contents) - return window; - } - return nullptr; -} - void NativeWindow::InitFromOptions(const mate::Dictionary& options) { // Setup window from options. int x = -1, y = -1; @@ -198,10 +137,10 @@ void NativeWindow::InitFromOptions(const mate::Dictionary& options) { } std::string color; if (options.Get(options::kBackgroundColor, &color)) { - SetBackgroundColor(color); + SetBackgroundColor(ParseHexColor(color)); } else if (!transparent()) { // For normal window, use white as default background. - SetBackgroundColor("#FFFF"); + SetBackgroundColor(SK_ColorWHITE); } std::string title(Browser::Get()->GetName()); options.Get(options::kTitle, &title); @@ -362,7 +301,8 @@ void NativeWindow::MoveTabToNewWindow() { void NativeWindow::ToggleTabBar() { } -void NativeWindow::AddTabbedWindow(NativeWindow* window) { +bool NativeWindow::AddTabbedWindow(NativeWindow* window) { + return true; // for non-Mac platforms } void NativeWindow::SetVibrancy(const std::string& filename) { @@ -379,19 +319,6 @@ void NativeWindow::SetEscapeTouchBarItem( const mate::PersistentDictionary& item) { } -void NativeWindow::FocusOnWebView() { - web_contents()->GetRenderViewHost()->GetWidget()->Focus(); -} - -void NativeWindow::BlurWebView() { - web_contents()->GetRenderViewHost()->GetWidget()->Blur(); -} - -bool NativeWindow::IsWebViewFocused() { - auto host_view = web_contents()->GetRenderViewHost()->GetWidget()->GetView(); - return host_view && host_view->HasFocus(); -} - void NativeWindow::SetAutoHideMenuBar(bool auto_hide) { } @@ -427,7 +354,13 @@ void NativeWindow::PreviewFile(const std::string& path, void NativeWindow::CloseFilePreview() { } -void NativeWindow::RequestToClosePage() { +void NativeWindow::NotifyWindowRequestPreferredWith(int* width) { + for (NativeWindowObserver& observer : observers_) + observer.RequestPreferredWidth(width); +} + +void NativeWindow::NotifyWindowCloseButtonClicked() { + // First ask the observers whether we want to close. bool prevent_default = false; for (NativeWindowObserver& observer : observers_) observer.WillCloseWindow(&prevent_default); @@ -436,60 +369,13 @@ void NativeWindow::RequestToClosePage() { return; } - // Assume the window is not responding if it doesn't cancel the close and is - // not closed in 5s, in this way we can quickly show the unresponsive - // dialog when the window is busy executing some script withouth waiting for - // the unresponsive timeout. - if (window_unresposive_closure_.IsCancelled()) - ScheduleUnresponsiveEvent(5000); - - if (!web_contents()) - // Already closed by renderer - return; - - if (web_contents()->NeedToFireBeforeUnload()) - web_contents()->DispatchBeforeUnload(); - else - web_contents()->Close(); -} - -void NativeWindow::CloseContents(content::WebContents* source) { - if (!inspectable_web_contents_) - return; - - inspectable_web_contents_->GetView()->SetDelegate(nullptr); - inspectable_web_contents_ = nullptr; - Observe(nullptr); - + // Then ask the observers how should we close the window. for (NativeWindowObserver& observer : observers_) - observer.WillDestroyNativeObject(); + observer.OnCloseButtonClicked(&prevent_default); + if (prevent_default) + return; - // When the web contents is gone, close the window immediately, but the - // memory will not be freed until you call delete. - // In this way, it would be safe to manage windows via smart pointers. If you - // want to free memory when the window is closed, you can do deleting by - // overriding the OnWindowClosed method in the observer. CloseImmediately(); - - // Do not sent "unresponsive" event after window is closed. - window_unresposive_closure_.Cancel(); -} - -void NativeWindow::RendererUnresponsive(content::WebContents* source) { - // Schedule the unresponsive shortly later, since we may receive the - // responsive event soon. This could happen after the whole application had - // blocked for a while. - // Also notice that when closing this event would be ignored because we have - // 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); -} - -void NativeWindow::RendererResponsive(content::WebContents* source) { - window_unresposive_closure_.Cancel(); - for (NativeWindowObserver& observer : observers_) - observer.OnRendererResponsive(); } void NativeWindow::NotifyWindowClosed() { @@ -578,11 +464,6 @@ void NativeWindow::NotifyWindowScrollTouchEnd() { observer.OnWindowScrollTouchEnd(); } -void NativeWindow::NotifyWindowScrollTouchEdge() { - for (NativeWindowObserver& observer : observers_) - observer.OnWindowScrollTouchEdge(); -} - void NativeWindow::NotifyWindowSwipe(const std::string& direction) { for (NativeWindowObserver& observer : observers_) observer.OnWindowSwipe(direction); @@ -639,99 +520,4 @@ void NativeWindow::NotifyWindowMessage( } #endif -std::unique_ptr NativeWindow::DraggableRegionsToSkRegion( - const std::vector& regions) { - std::unique_ptr sk_region(new SkRegion); - for (const DraggableRegion& region : regions) { - sk_region->op( - region.bounds.x(), - region.bounds.y(), - region.bounds.right(), - region.bounds.bottom(), - region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op); - } - return sk_region; -} - -void NativeWindow::RenderViewCreated( - content::RenderViewHost* render_view_host) { - if (!transparent_) - return; - - content::RenderWidgetHostImpl* impl = content::RenderWidgetHostImpl::FromID( - render_view_host->GetProcess()->GetID(), - render_view_host->GetRoutingID()); - if (impl) - impl->SetBackgroundOpaque(false); -} - -void NativeWindow::BeforeUnloadDialogCancelled() { - WindowList::WindowCloseCancelled(this); - - // Cancel unresponsive event when window close is cancelled. - window_unresposive_closure_.Cancel(); -} - -void NativeWindow::DidFirstVisuallyNonEmptyPaint() { - if (IsVisible()) - return; - - // When there is a non-empty first paint, resize the RenderWidget to force - // Chromium to draw. - const auto view = web_contents()->GetRenderWidgetHostView(); - view->Show(); - view->SetSize(GetContentSize()); - - // Emit the ReadyToShow event in next tick in case of pending drawing work. - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, - base::Bind(&NativeWindow::NotifyReadyToShow, GetWeakPtr())); -} - -bool NativeWindow::OnMessageReceived(const IPC::Message& message) { - bool handled = true; - IPC_BEGIN_MESSAGE_MAP(NativeWindow, message) - IPC_MESSAGE_HANDLER(AtomViewHostMsg_UpdateDraggableRegions, - UpdateDraggableRegions) - IPC_MESSAGE_UNHANDLED(handled = false) - IPC_END_MESSAGE_MAP() - - return handled; -} - -void NativeWindow::UpdateDraggableRegions( - const std::vector& regions) { - // Draggable region is not supported for non-frameless window. - if (has_frame_) - return; - draggable_region_ = DraggableRegionsToSkRegion(regions); -} - -void NativeWindow::ScheduleUnresponsiveEvent(int ms) { - if (!window_unresposive_closure_.IsCancelled()) - return; - - window_unresposive_closure_.Reset( - base::Bind(&NativeWindow::NotifyWindowUnresponsive, - weak_factory_.GetWeakPtr())); - base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( - FROM_HERE, - window_unresposive_closure_.callback(), - base::TimeDelta::FromMilliseconds(ms)); -} - -void NativeWindow::NotifyWindowUnresponsive() { - window_unresposive_closure_.Cancel(); - - if (!is_closed_ && !IsUnresponsiveEventSuppressed() && IsEnabled()) { - for (NativeWindowObserver& observer : observers_) - observer.OnRendererUnresponsive(); - } -} - -void NativeWindow::NotifyReadyToShow() { - for (NativeWindowObserver& observer : observers_) - observer.OnReadyToShow(); -} - } // namespace atom diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 1e02a37fc07..219463ac156 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -11,21 +11,13 @@ #include #include "atom/browser/native_window_observer.h" -#include "atom/browser/ui/accelerator_util.h" #include "atom/browser/ui/atom_menu_model.h" -#include "base/cancelable_callback.h" #include "base/memory/weak_ptr.h" #include "base/observer_list.h" #include "base/supports_user_data.h" -#include "content/public/browser/readback_types.h" -#include "content/public/browser/render_frame_host.h" -#include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_user_data.h" #include "extensions/browser/app_window/size_constraints.h" #include "native_mate/persistent_dictionary.h" -#include "ui/gfx/geometry/rect_f.h" -#include "ui/gfx/image/image.h" -#include "ui/gfx/image/image_skia.h" class SkRegion; @@ -38,8 +30,10 @@ struct NativeWebKeyboardEvent; } namespace gfx { +class Image; class Point; class Rect; +class RectF; class Size; } @@ -53,8 +47,7 @@ class NativeBrowserView; struct DraggableRegion; -class NativeWindow : public base::SupportsUserData, - public content::WebContentsObserver { +class NativeWindow : public base::SupportsUserData { public: ~NativeWindow() override; @@ -65,9 +58,6 @@ class NativeWindow : public base::SupportsUserData, const mate::Dictionary& options, NativeWindow* parent = nullptr); - // Find a window from its WebContents - static NativeWindow* FromWebContents(content::WebContents* web_contents); - void InitFromOptions(const mate::Dictionary& options); virtual void Close() = 0; @@ -80,6 +70,7 @@ class NativeWindow : public base::SupportsUserData, virtual void Hide() = 0; virtual bool IsVisible() = 0; virtual bool IsEnabled() = 0; + virtual void SetEnabled(bool enable) = 0; virtual void Maximize() = 0; virtual void Unmaximize() = 0; virtual bool IsMaximized() = 0; @@ -138,7 +129,7 @@ class NativeWindow : public base::SupportsUserData, virtual bool IsSimpleFullScreen() = 0; virtual void SetKiosk(bool kiosk) = 0; virtual bool IsKiosk() = 0; - virtual void SetBackgroundColor(const std::string& color_name) = 0; + virtual void SetBackgroundColor(SkColor color) = 0; virtual void SetHasShadow(bool has_shadow) = 0; virtual bool HasShadow() = 0; virtual void SetOpacity(const double opacity) = 0; @@ -192,12 +183,7 @@ class NativeWindow : public base::SupportsUserData, virtual void MergeAllWindows(); virtual void MoveTabToNewWindow(); virtual void ToggleTabBar(); - virtual void AddTabbedWindow(NativeWindow* window); - - // Webview APIs. - virtual void FocusOnWebView(); - virtual void BlurWebView(); - virtual bool IsWebViewFocused(); + virtual bool AddTabbedWindow(NativeWindow* window); // Toggle the menu bar. virtual void SetAutoHideMenuBar(bool auto_hide); @@ -215,17 +201,17 @@ class NativeWindow : public base::SupportsUserData, const std::string& display_name); virtual void CloseFilePreview(); + // Converts between content bounds and window bounds. + virtual gfx::Rect ContentBoundsToWindowBounds( + const gfx::Rect& bounds) const = 0; + virtual gfx::Rect WindowBoundsToContentBounds( + const gfx::Rect& bounds) const = 0; + base::WeakPtr GetWeakPtr() { return weak_factory_.GetWeakPtr(); } - // Requests the WebContents to close, can be cancelled by the page. - virtual void RequestToClosePage(); - // Methods called by the WebContents. - virtual void CloseContents(content::WebContents* source); - virtual void RendererUnresponsive(content::WebContents* source); - virtual void RendererResponsive(content::WebContents* source); virtual void HandleKeyboardEvent( content::WebContents*, const content::NativeWebKeyboardEvent& event) {} @@ -239,6 +225,8 @@ class NativeWindow : public base::SupportsUserData, // Public API used by platform-dependent delegates and observers to send UI // related notifications. + void NotifyWindowRequestPreferredWith(int* width); + void NotifyWindowCloseButtonClicked(); void NotifyWindowClosed(); void NotifyWindowEndSession(); void NotifyWindowBlur(); @@ -254,7 +242,6 @@ class NativeWindow : public base::SupportsUserData, void NotifyWindowMoved(); void NotifyWindowScrollTouchBegin(); void NotifyWindowScrollTouchEnd(); - void NotifyWindowScrollTouchEdge(); void NotifyWindowSwipe(const std::string& direction); void NotifyWindowSheetBegin(); void NotifyWindowSheetEnd(); @@ -278,20 +265,16 @@ class NativeWindow : public base::SupportsUserData, observers_.RemoveObserver(obs); } - brightray::InspectableWebContents* inspectable_web_contents() const { - return inspectable_web_contents_; - } - 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_; } void set_is_offscreen_dummy(bool is_dummy) { is_osr_dummy_ = is_dummy; } bool is_offscreen_dummy() const { return is_osr_dummy_; } + NativeBrowserView* browser_view() const { return browser_view_; } NativeWindow* parent() const { return parent_; } bool is_modal() const { return is_modal_; } @@ -300,47 +283,17 @@ class NativeWindow : public base::SupportsUserData, const mate::Dictionary& options, NativeWindow* parent); - // Convert draggable regions in raw format to SkRegion format. Caller is - // responsible for deleting the returned SkRegion instance. - std::unique_ptr DraggableRegionsToSkRegion( - const std::vector& regions); - - // Converts between content bounds and window bounds. - virtual gfx::Rect ContentBoundsToWindowBounds( - const gfx::Rect& bounds) const = 0; - virtual gfx::Rect WindowBoundsToContentBounds( - const gfx::Rect& bounds) const = 0; - - // Called when the window needs to update its draggable region. - virtual void UpdateDraggableRegions( - const std::vector& regions); - - // content::WebContentsObserver: - void RenderViewCreated(content::RenderViewHost* render_view_host) override; - void BeforeUnloadDialogCancelled() override; - void DidFirstVisuallyNonEmptyPaint() override; - bool OnMessageReceived(const IPC::Message& message) override; + void set_browser_view(NativeBrowserView* browser_view) { + browser_view_ = browser_view; + } private: - // Schedule a notification unresponsive event. - void ScheduleUnresponsiveEvent(int ms); - - // Dispatch unresponsive event to observers. - void NotifyWindowUnresponsive(); - - // Dispatch ReadyToShow event to observers. - void NotifyReadyToShow(); - // Whether window has standard frame. bool has_frame_; // Whether window is transparent. bool transparent_; - // For custom drag, the whole window is non-draggable and the draggable region - // has to been explicitly provided. - std::unique_ptr draggable_region_; // used in custom drag. - // Minimum and maximum size, stored as content size. extensions::SizeConstraints size_constraints_; @@ -350,10 +303,6 @@ class NativeWindow : public base::SupportsUserData, // The windows has been closed. bool is_closed_; - // Closure that would be called when window is unresponsive when closing, - // 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 macOS. double sheet_offset_x_; @@ -373,8 +322,8 @@ class NativeWindow : public base::SupportsUserData, // Is this a dummy window for an offscreen WebContents. bool is_osr_dummy_; - // The page this window is viewing. - brightray::InspectableWebContents* inspectable_web_contents_; + // The browser view layer. + NativeBrowserView* browser_view_; // Observers of this window. base::ObserverList observers_; @@ -391,6 +340,10 @@ class NativeWindowRelay : explicit NativeWindowRelay(base::WeakPtr window) : key(UserDataKey()), window(window) {} + static void* UserDataKey() { + return content::WebContentsUserData::UserDataKey(); + } + void* key; base::WeakPtr window; diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 24d9a0aab87..b91a2a36c4b 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -12,7 +12,6 @@ #include "atom/browser/native_window.h" #include "base/mac/scoped_nsobject.h" -#include "content/public/browser/render_widget_host.h" @class AtomNSWindow; @class AtomNSWindowDelegate; @@ -20,8 +19,7 @@ namespace atom { -class NativeWindowMac : public NativeWindow, - public content::RenderWidgetHost::InputEventObserver { +class NativeWindowMac : public NativeWindow { public: NativeWindowMac(brightray::InspectableWebContents* inspectable_web_contents, const mate::Dictionary& options, @@ -38,6 +36,7 @@ class NativeWindowMac : public NativeWindow, void Hide() override; bool IsVisible() override; bool IsEnabled() override; + void SetEnabled(bool enable) override; void Maximize() override; void Unmaximize() override; bool IsMaximized() override; @@ -80,7 +79,7 @@ class NativeWindowMac : public NativeWindow, bool IsSimpleFullScreen() override; void SetKiosk(bool kiosk) override; bool IsKiosk() override; - void SetBackgroundColor(const std::string& color_name) override; + void SetBackgroundColor(SkColor color) override; void SetHasShadow(bool has_shadow) override; bool HasShadow() override; void SetOpacity(const double opacity) override; @@ -110,7 +109,7 @@ class NativeWindowMac : public NativeWindow, void MergeAllWindows() override; void MoveTabToNewWindow() override; void ToggleTabBar() override; - void AddTabbedWindow(NativeWindow* window) override; + bool AddTabbedWindow(NativeWindow* window) override; void SetVibrancy(const std::string& type) override; void SetTouchBar( @@ -118,17 +117,8 @@ class NativeWindowMac : public NativeWindow, void RefreshTouchBarItem(const std::string& item_id) override; void SetEscapeTouchBarItem(const mate::PersistentDictionary& item) override; - // content::RenderWidgetHost::InputEventObserver: - void OnInputEvent(const blink::WebInputEvent& event) override; - - // content::WebContentsObserver: - void RenderViewHostChanged(content::RenderViewHost* old_host, - content::RenderViewHost* new_host) override; - - // Refresh the DraggableRegion views. - void UpdateDraggableRegionViews() { - UpdateDraggableRegionViews(draggable_regions_); - } + gfx::Rect ContentBoundsToWindowBounds(const gfx::Rect& bounds) const; + gfx::Rect WindowBoundsToContentBounds(const gfx::Rect& bounds) const; // Set the attribute of NSWindow while work around a bug of zoom button. void SetStyleMask(bool on, NSUInteger flag); @@ -143,36 +133,14 @@ class NativeWindowMac : public NativeWindow, TitleBarStyle title_bar_style() const { return title_bar_style_; } bool zoom_to_page_width() const { return zoom_to_page_width_; } - bool fullscreen_window_title() const { return fullscreen_window_title_; } - bool simple_fullscreen() const { return always_simple_fullscreen_; } - protected: - // Return a vector of non-draggable regions that fill a window of size - // |width| by |height|, but leave gaps where the window should be draggable. - std::vector CalculateNonDraggableRegions( - const std::vector& regions, int width, int height); - private: - // NativeWindow: - gfx::Rect ContentBoundsToWindowBounds(const gfx::Rect& bounds) const; - gfx::Rect WindowBoundsToContentBounds(const gfx::Rect& bounds) const; - void UpdateDraggableRegions( - const std::vector& regions) override; - void InternalSetParentWindow(NativeWindow* parent, bool attach); void ShowWindowButton(NSWindowButton button); - void InstallView(); - void UninstallView(); - - // Install the drag view, which will cover the whole window and decides - // whether we can drag. - void UpdateDraggableRegionViews(const std::vector& regions); - - void RegisterInputEventObserver(content::RenderViewHost* host); - void UnregisterInputEventObserver(content::RenderViewHost* host); + void InstallView(NSView* view); base::scoped_nsobject window_; base::scoped_nsobject window_delegate_; @@ -183,10 +151,6 @@ class NativeWindowMac : public NativeWindow, // The view that will fill the whole frameless window. base::scoped_nsobject content_view_; - NativeBrowserView* browser_view_; - - std::vector draggable_regions_; - bool is_kiosk_; bool was_fullscreen_; @@ -211,6 +175,9 @@ class NativeWindowMac : public NativeWindow, NSRect original_frame_; NSUInteger simple_fullscreen_mask_; + base::scoped_nsobject background_color_before_vibrancy_; + bool transparency_before_vibrancy_; + // The presentation options before entering simple fullscreen mode. NSApplicationPresentationOptions simple_fullscreen_options_; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 59301fc7a98..ba91651a029 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -12,8 +12,6 @@ #include "atom/browser/native_browser_view_mac.h" #include "atom/browser/ui/cocoa/atom_touch_bar.h" #include "atom/browser/window_list.h" -#include "atom/common/color_util.h" -#include "atom/common/draggable_region.h" #include "atom/common/options_switches.h" #include "base/mac/mac_util.h" #include "base/mac/scoped_cftyperef.h" @@ -22,12 +20,8 @@ #include "brightray/browser/inspectable_web_contents_view.h" #include "brightray/browser/mac/event_dispatching_window.h" #include "content/public/browser/browser_accessibility_state.h" -#include "content/public/browser/render_view_host.h" -#include "content/public/browser/render_widget_host_view.h" -#include "content/public/browser/web_contents.h" #include "native_mate/dictionary.h" #include "skia/ext/skia_utils_mac.h" -#include "third_party/skia/include/core/SkRegion.h" #include "ui/gfx/skia_util.h" namespace { @@ -216,16 +210,14 @@ bool ScopedDisableResize::disable_resize_ = false; if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) return frame; - content::WebContents* web_contents = shell_->web_contents(); - if (!web_contents) - return frame; - - CGFloat page_width = static_cast( - web_contents->GetPreferredSize().width()); - NSRect window_frame = [window frame]; + // Get preferred width from observers. Usually the page width. + int preferred_width = 0; + shell_->NotifyWindowRequestPreferredWith(&preferred_width); // Never shrink from the current size on zoom. - CGFloat zoomed_width = std::max(page_width, NSWidth(window_frame)); + NSRect window_frame = [window frame]; + CGFloat zoomed_width = std::max(static_cast(preferred_width), + NSWidth(window_frame)); // |frame| determines our maximum extents. We need to set the origin of the // frame -- and only move it left if necessary. @@ -241,30 +233,10 @@ bool ScopedDisableResize::disable_resize_ = false; } - (void)windowDidBecomeMain:(NSNotification*)notification { - content::WebContents* web_contents = shell_->web_contents(); - if (!web_contents) - return; - - web_contents->RestoreFocus(); - - content::RenderWidgetHostView* rwhv = web_contents->GetRenderWidgetHostView(); - if (rwhv) - rwhv->SetActive(true); - shell_->NotifyWindowFocus(); } - (void)windowDidResignMain:(NSNotification*)notification { - content::WebContents* web_contents = shell_->web_contents(); - if (!web_contents) - return; - - web_contents->StoreFocus(); - - content::RenderWidgetHostView* rwhv = web_contents->GetRenderWidgetHostView(); - if (rwhv) - rwhv->SetActive(false); - shell_->NotifyWindowBlur(); } @@ -294,7 +266,6 @@ bool ScopedDisableResize::disable_resize_ = false; } - (void)windowDidResize:(NSNotification*)notification { - shell_->UpdateDraggableRegionViews(); shell_->NotifyWindowResize(); } @@ -413,10 +384,7 @@ bool ScopedDisableResize::disable_resize_ = false; } - (BOOL)windowShouldClose:(id)window { - // When user tries to close the window by clicking the close button, we do - // not close the window immediately, instead we try to close the web page - // first, and when the web page is closed the window will also be closed. - shell_->RequestToClosePage(); + shell_->NotifyWindowCloseButtonClicked(); return NO; } @@ -732,7 +700,7 @@ enum { [super performClose:sender]; } -- (void)toggleFullScreen:(id)sender { +- (void)toggleFullScreenMode:(id)sender { if (shell_->simple_fullscreen()) shell_->SetSimpleFullScreen(!shell_->IsSimpleFullScreen()); else @@ -748,25 +716,6 @@ enum { @end -@interface ControlRegionView : NSView -@end - -@implementation ControlRegionView - -- (BOOL)mouseDownCanMoveWindow { - return NO; -} - -- (NSView*)hitTest:(NSPoint)aPoint { - return nil; -} - -@end - -@interface NSView (WebContentsView) -- (void)setMouseDownCanMoveWindow:(BOOL)can_move; -@end - @interface AtomProgressBar : NSProgressIndicator @end @@ -811,8 +760,7 @@ struct Converter { return false; if (title_bar_style == "hidden") { *out = atom::NativeWindowMac::HIDDEN; - } else if (title_bar_style == "hidden-inset" || // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings - title_bar_style == "hiddenInset") { + } else if (title_bar_style == "hiddenInset") { *out = atom::NativeWindowMac::HIDDEN_INSET; } else if (title_bar_style == "customButtonsOnHover") { *out = atom::NativeWindowMac::CUSTOM_BUTTONS_ON_HOVER; @@ -832,7 +780,6 @@ NativeWindowMac::NativeWindowMac( const mate::Dictionary& options, NativeWindow* parent) : NativeWindow(web_contents, options, parent), - browser_view_(nullptr), is_kiosk_(false), was_fullscreen_(false), zoom_to_page_width_(false), @@ -1004,7 +951,7 @@ NativeWindowMac::NativeWindowMac( options.Get(options::kDisableAutoHideCursor, &disableAutoHideCursor); [window_ setDisableAutoHideCursor:disableAutoHideCursor]; - NSView* view = inspectable_web_contents()->GetView()->GetNativeView(); + NSView* view = web_contents->GetView()->GetNativeView(); [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; // Use an NSEvent monitor to listen for the wheel event. @@ -1015,9 +962,6 @@ NativeWindowMac::NativeWindowMac( if ([[event window] windowNumber] != [window_ windowNumber]) return event; - if (!web_contents) - return event; - if (!began && (([event phase] == NSEventPhaseMayBegin) || ([event phase] == NSEventPhaseBegan))) { this->NotifyWindowScrollTouchBegin(); @@ -1030,7 +974,7 @@ NativeWindowMac::NativeWindowMac( return event; }]; - InstallView(); + InstallView(web_contents->GetView()->GetNativeView()); std::string type; if (options.Get(options::kVibrancyType, &type)) { @@ -1040,14 +984,10 @@ NativeWindowMac::NativeWindowMac( // Set maximizable state last to ensure zoom button does not get reset // by calls to other APIs. SetMaximizable(maximizable); - - RegisterInputEventObserver( - web_contents->GetWebContents()->GetRenderViewHost()); } NativeWindowMac::~NativeWindowMac() { [NSEvent removeMonitor:wheel_event_monitor_]; - Observe(nullptr); } void NativeWindowMac::Close() { @@ -1135,6 +1075,17 @@ bool NativeWindowMac::IsEnabled() { return [window_ attachedSheet] == nil; } +void NativeWindowMac::SetEnabled(bool enable) { + if (enable) { + [window_ beginSheet: window_ completionHandler:^(NSModalResponse returnCode) { + NSLog(@"modal enabled"); + return; + }]; + } else { + [window_ endSheet: [window_ attachedSheet]]; + } +} + void NativeWindowMac::Maximize() { if (IsMaximized()) return; @@ -1178,7 +1129,7 @@ void NativeWindowMac::SetFullScreen(bool fullscreen) { if (fullscreen == IsFullscreen()) return; - [window_ toggleFullScreen:nil]; + [window_ toggleFullScreenMode:nil]; } bool NativeWindowMac::IsFullscreen() const { @@ -1492,15 +1443,10 @@ bool NativeWindowMac::IsKiosk() { return is_kiosk_; } -void NativeWindowMac::SetBackgroundColor(const std::string& color_name) { - SkColor color = ParseHexColor(color_name); +void NativeWindowMac::SetBackgroundColor(SkColor color) { 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) { @@ -1544,20 +1490,19 @@ void NativeWindowMac::SetContentProtection(bool enable) { : NSWindowSharingReadOnly]; } -void NativeWindowMac::SetBrowserView(NativeBrowserView* browser_view) { - if (browser_view_) { - [browser_view_->GetInspectableWebContentsView()->GetNativeView() +void NativeWindowMac::SetBrowserView(NativeBrowserView* view) { + if (browser_view()) { + [browser_view()->GetInspectableWebContentsView()->GetNativeView() removeFromSuperview]; - browser_view_ = nullptr; + set_browser_view(nullptr); } - if (!browser_view) { + if (!view) { return; } - browser_view_ = browser_view; - auto* native_view = - browser_view->GetInspectableWebContentsView()->GetNativeView(); + set_browser_view(view); + auto* native_view = view->GetInspectableWebContentsView()->GetNativeView(); [[window_ contentView] addSubview:native_view positioned:NSWindowAbove relativeTo:nil]; @@ -1569,7 +1514,7 @@ void NativeWindowMac::SetParentWindow(NativeWindow* parent) { } gfx::NativeView NativeWindowMac::GetNativeView() const { - return inspectable_web_contents()->GetView()->GetNativeView(); + return [window_ contentView]; } gfx::NativeWindow NativeWindowMac::GetNativeWindow() const { @@ -1577,7 +1522,7 @@ gfx::NativeWindow NativeWindowMac::GetNativeWindow() const { } gfx::AcceleratedWidget NativeWindowMac::GetAcceleratedWidget() const { - return inspectable_web_contents()->GetView()->GetNativeView(); + return [window_ contentView]; } void NativeWindowMac::SetProgressBar(double progress, const NativeWindow::ProgressState state) { @@ -1665,10 +1610,14 @@ void NativeWindowMac::ToggleTabBar() { } } -void NativeWindowMac::AddTabbedWindow(NativeWindow* window) { - if ([window_ respondsToSelector:@selector(addTabbedWindow:ordered:)]) { - [window_ addTabbedWindow:window->GetNativeWindow() ordered:NSWindowAbove]; +bool NativeWindowMac::AddTabbedWindow(NativeWindow* window) { + if (window_.get() == window->GetNativeWindow()) { + return false; + } else { + if ([window_ respondsToSelector:@selector(addTabbedWindow:ordered:)]) + [window_ addTabbedWindow:window->GetNativeWindow() ordered:NSWindowAbove]; } + return true; } void NativeWindowMac::SetVibrancy(const std::string& type) { @@ -1677,6 +1626,10 @@ void NativeWindowMac::SetVibrancy(const std::string& type) { NSView* vibrant_view = [window_ vibrantView]; if (type.empty()) { + if (background_color_before_vibrancy_) { + [window_ setBackgroundColor:background_color_before_vibrancy_]; + [window_ setTitlebarAppearsTransparent:transparency_before_vibrancy_]; + } if (vibrant_view == nil) return; [vibrant_view removeFromSuperview]; @@ -1685,6 +1638,12 @@ void NativeWindowMac::SetVibrancy(const std::string& type) { return; } + background_color_before_vibrancy_.reset([window_ backgroundColor]); + transparency_before_vibrancy_ = [window_ titlebarAppearsTransparent]; + + [window_ setTitlebarAppearsTransparent:YES]; + [window_ setBackgroundColor:[NSColor clearColor]]; + NSVisualEffectView* effect_view = (NSVisualEffectView*)vibrant_view; if (effect_view == nil) { effect_view = [[[NSVisualEffectView alloc] @@ -1751,42 +1710,6 @@ void NativeWindowMac::SetEscapeTouchBarItem(const mate::PersistentDictionary& it [window_ setEscapeTouchBarItem:item]; } -void NativeWindowMac::OnInputEvent(const blink::WebInputEvent& event) { - switch (event.GetType()) { - case blink::WebInputEvent::kGestureScrollBegin: - case blink::WebInputEvent::kGestureScrollUpdate: - case blink::WebInputEvent::kGestureScrollEnd: - this->NotifyWindowScrollTouchEdge(); - break; - default: - break; - } -} - -void NativeWindowMac::RenderViewHostChanged( - content::RenderViewHost* old_host, - content::RenderViewHost* new_host) { - UnregisterInputEventObserver(old_host); - RegisterInputEventObserver(new_host); -} - -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 { - 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()) { - result.push_back(gfx::SkIRectToRect(it.rect())); - } - } - return result; -} - gfx::Rect NativeWindowMac::ContentBoundsToWindowBounds( const gfx::Rect& bounds) const { if (has_frame()) { @@ -1813,13 +1736,6 @@ gfx::Rect NativeWindowMac::WindowBoundsToContentBounds( } } -void NativeWindowMac::UpdateDraggableRegions( - const std::vector& regions) { - NativeWindow::UpdateDraggableRegions(regions); - draggable_regions_ = regions; - UpdateDraggableRegionViews(regions); -} - void NativeWindowMac::InternalSetParentWindow(NativeWindow* parent, bool attach) { if (is_modal()) return; @@ -1845,14 +1761,13 @@ void NativeWindowMac::ShowWindowButton(NSWindowButton button) { [view.superview addSubview:view positioned:NSWindowAbove relativeTo:nil]; } -void NativeWindowMac::InstallView() { +void NativeWindowMac::InstallView(NSView* view) { // Make sure the bottom corner is rounded for non-modal windows: http://crbug.com/396264. // But do not enable it on OS X 10.9 for transparent window, otherwise a // semi-transparent frame would show. if (!(transparent() && base::mac::IsOS10_9()) && !is_modal()) [[window_ contentView] setWantsLayer:YES]; - NSView* view = inspectable_web_contents()->GetView()->GetNativeView(); if (has_frame()) { [view setFrame:[[window_ contentView] bounds]]; [[window_ contentView] addSubview:view]; @@ -1900,68 +1815,6 @@ void NativeWindowMac::InstallView() { } } -void NativeWindowMac::UninstallView() { - NSView* view = inspectable_web_contents()->GetView()->GetNativeView(); - [view removeFromSuperview]; -} - -void NativeWindowMac::UpdateDraggableRegionViews( - const std::vector& regions) { - if (has_frame()) - return; - - // All ControlRegionViews should be added as children of the WebContentsView, - // because WebContentsView will be removed and re-added when entering and - // leaving fullscreen mode. - NSView* webView = web_contents()->GetNativeView(); - NSInteger webViewWidth = NSWidth([webView bounds]); - NSInteger webViewHeight = NSHeight([webView bounds]); - - if ([webView respondsToSelector:@selector(setMouseDownCanMoveWindow:)]) { - [webView setMouseDownCanMoveWindow:YES]; - } - - // Remove all ControlRegionViews that are added last time. - // Note that [webView subviews] returns the view's mutable internal array and - // it should be copied to avoid mutating the original array while enumerating - // it. - base::scoped_nsobject subviews([[webView subviews] copy]); - for (NSView* subview in subviews.get()) - if ([subview isKindOfClass:[ControlRegionView class]]) - [subview removeFromSuperview]; - - // Draggable regions is implemented by having the whole web view draggable - // (mouseDownCanMoveWindow) and overlaying regions that are not draggable. - std::vector system_drag_exclude_areas = - CalculateNonDraggableRegions(regions, webViewWidth, webViewHeight); - - if (browser_view_) { - browser_view_->UpdateDraggableRegions(system_drag_exclude_areas); - } - - // Create and add a ControlRegionView for each region that needs to be - // excluded from the dragging. - for (std::vector::const_iterator iter = - system_drag_exclude_areas.begin(); - iter != system_drag_exclude_areas.end(); - ++iter) { - base::scoped_nsobject controlRegion( - [[ControlRegionView alloc] initWithFrame:NSZeroRect]); - [controlRegion setFrame:NSMakeRect(iter->x(), - webViewHeight - iter->bottom(), - iter->width(), - iter->height())]; - [webView addSubview:controlRegion]; - } - - // AppKit will not update its cache of mouseDownCanMoveWindow unless something - // changes. Previously we tried adding an NSView and removing it, but for some - // reason it required reposting the mouse-down event, and didn't always work. - // Calling the below seems to be an effective solution. - [window_ setMovableByWindowBackground:NO]; - [window_ setMovableByWindowBackground:YES]; -} - void NativeWindowMac::SetStyleMask(bool on, NSUInteger flag) { // Changing the styleMask of a frameless windows causes it to change size so // we explicitly disable resizing while setting it. @@ -1988,18 +1841,6 @@ void NativeWindowMac::SetCollectionBehavior(bool on, NSUInteger flag) { SetMaximizable(was_maximizable); } -void NativeWindowMac::RegisterInputEventObserver( - content::RenderViewHost* host) { - if (host) - host->GetWidget()->AddInputEventObserver(this); -} - -void NativeWindowMac::UnregisterInputEventObserver( - content::RenderViewHost* host) { - if (host) - host->GetWidget()->RemoveInputEventObserver(this); -} - // static NativeWindow* NativeWindow::Create( brightray::InspectableWebContents* inspectable_web_contents, diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index 9aad030aa32..4dfd3521091 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -34,8 +34,11 @@ class NativeWindowObserver { // Called when the window is gonna closed. virtual void WillCloseWindow(bool* prevent_default) {} - // Called before the native window object is going to be destroyed. - virtual void WillDestroyNativeObject() {} + // Called when the window wants to know the preferred width. + virtual void RequestPreferredWidth(int* width) {} + + // Called when closed button is clicked. + virtual void OnCloseButtonClicked(bool* prevent_default) {} // Called when the window is closed. virtual void OnWindowClosed() {} @@ -55,9 +58,6 @@ class NativeWindowObserver { // Called when window is hidden. virtual void OnWindowHide() {} - // Called when window is ready to show. - virtual void OnReadyToShow() {} - // Called when window state changed. virtual void OnWindowMaximize() {} virtual void OnWindowUnmaximize() {} @@ -68,7 +68,6 @@ class NativeWindowObserver { virtual void OnWindowMoved() {} virtual void OnWindowScrollTouchBegin() {} virtual void OnWindowScrollTouchEnd() {} - virtual void OnWindowScrollTouchEdge() {} virtual void OnWindowSwipe(const std::string& direction) {} virtual void OnWindowSheetBegin() {} virtual void OnWindowSheetEnd() {} @@ -85,12 +84,6 @@ class NativeWindowObserver { virtual void OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) {} #endif - // Called when renderer is hung. - virtual void OnRendererUnresponsive() {} - - // Called when renderer recovers. - virtual void OnRendererResponsive() {} - // Called on Windows when App Commands arrive (WM_APPCOMMAND) virtual void OnExecuteWindowsCommand(const std::string& command_name) {} }; diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 357afe35ca3..46f4a5c1b74 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -16,7 +16,6 @@ #include "atom/browser/web_contents_preferences.h" #include "atom/browser/web_view_manager.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" @@ -124,7 +123,8 @@ class NativeWindowClientView : public views::ClientView { virtual ~NativeWindowClientView() {} bool CanClose() override { - static_cast(contents_view())->RequestToClosePage(); + static_cast(contents_view())-> + NotifyWindowCloseButtonClicked(); return false; } @@ -140,8 +140,8 @@ NativeWindowViews::NativeWindowViews( NativeWindow* parent) : NativeWindow(web_contents, options, parent), window_(new views::Widget), - web_view_(inspectable_web_contents()->GetView()->GetView()), - browser_view_(nullptr), + web_view_(web_contents->GetView()->GetView()), + focused_view_(web_contents->GetView()->GetWebView()), menu_bar_autohide_(false), menu_bar_visible_(false), menu_bar_alt_pressed_(false), @@ -302,9 +302,6 @@ NativeWindowViews::NativeWindowViews( } LONG ex_style = ::GetWindowLong(GetAcceleratedWidget(), GWL_EXSTYLE); - // Window without thick frame has to have WS_EX_COMPOSITED style. - if (!thick_frame_) - ex_style |= WS_EX_COMPOSITED; if (window_type == "toolbar") ex_style |= WS_EX_TOOLWINDOW; ::SetWindowLong(GetAcceleratedWidget(), GWL_EXSTYLE, ex_style); @@ -794,9 +791,8 @@ bool NativeWindowViews::IsKiosk() { return IsFullscreen(); } -void NativeWindowViews::SetBackgroundColor(const std::string& color_name) { +void NativeWindowViews::SetBackgroundColor(SkColor background_color) { // web views' background color. - SkColor background_color = ParseHexColor(color_name); SetBackground(views::CreateSolidBackground(background_color)); #if defined(OS_WIN) @@ -952,22 +948,21 @@ void NativeWindowViews::SetMenu(AtomMenuModel* menu_model) { Layout(); } -void NativeWindowViews::SetBrowserView(NativeBrowserView* browser_view) { - if (browser_view_) { +void NativeWindowViews::SetBrowserView(NativeBrowserView* view) { + if (browser_view()) { web_view_->RemoveChildView( - browser_view_->GetInspectableWebContentsView()->GetView()); - browser_view_ = nullptr; + browser_view()->GetInspectableWebContentsView()->GetView()); + set_browser_view(nullptr); } - if (!browser_view) { + if (!view) { return; } // Add as child of the main web view to avoid (0, 0) origin from overlapping // with menu bar. - browser_view_ = browser_view; - web_view_->AddChildView( - browser_view->GetInspectableWebContentsView()->GetView()); + set_browser_view(view); + web_view_->AddChildView(view->GetInspectableWebContentsView()->GetView()); } void NativeWindowViews::SetParentWindow(NativeWindow* parent) { @@ -1075,6 +1070,60 @@ gfx::AcceleratedWidget NativeWindowViews::GetAcceleratedWidget() const { return GetNativeWindow()->GetHost()->GetAcceleratedWidget(); } +gfx::Rect NativeWindowViews::ContentBoundsToWindowBounds( + const gfx::Rect& bounds) const { + if (!has_frame()) + return bounds; + + gfx::Rect window_bounds(bounds); +#if defined(OS_WIN) + HWND hwnd = GetAcceleratedWidget(); + gfx::Rect dpi_bounds = display::win::ScreenWin::DIPToScreenRect(hwnd, bounds); + window_bounds = display::win::ScreenWin::ScreenToDIPRect( + hwnd, + window_->non_client_view()->GetWindowBoundsForClientBounds(dpi_bounds)); +#endif + + if (menu_bar_ && menu_bar_visible_) { + window_bounds.set_y(window_bounds.y() - kMenuBarHeight); + window_bounds.set_height(window_bounds.height() + kMenuBarHeight); + } + return window_bounds; +} + +gfx::Rect NativeWindowViews::WindowBoundsToContentBounds( + const gfx::Rect& bounds) const { + if (!has_frame()) + return bounds; + + gfx::Rect content_bounds(bounds); +#if defined(OS_WIN) + HWND hwnd = GetAcceleratedWidget(); + content_bounds.set_size( + display::win::ScreenWin::DIPToScreenSize(hwnd, content_bounds.size())); + RECT rect; + SetRectEmpty(&rect); + DWORD style = ::GetWindowLong(hwnd, GWL_STYLE); + DWORD ex_style = ::GetWindowLong(hwnd, GWL_EXSTYLE); + AdjustWindowRectEx(&rect, style, FALSE, ex_style); + content_bounds.set_width(content_bounds.width() - (rect.right - rect.left)); + content_bounds.set_height(content_bounds.height() - (rect.bottom - rect.top)); + content_bounds.set_size( + display::win::ScreenWin::ScreenToDIPSize(hwnd, content_bounds.size())); +#endif + + if (menu_bar_ && menu_bar_visible_) { + content_bounds.set_y(content_bounds.y() + kMenuBarHeight); + content_bounds.set_height(content_bounds.height() - kMenuBarHeight); + } + return content_bounds; +} + +void NativeWindowViews::UpdateDraggableRegions( + std::unique_ptr region) { + draggable_region_ = std::move(region); +} + #if defined(OS_WIN) void NativeWindowViews::SetIcon(HICON window_icon, HICON app_icon) { // We are responsible for storing the images. @@ -1135,10 +1184,6 @@ void NativeWindowViews::OnWidgetActivationChanged( &NativeWindow::NotifyWindowBlur, GetWeakPtr())); - if (active && inspectable_web_contents() && - !inspectable_web_contents()->IsDevToolsViewShowing()) - web_contents()->Focus(); - // Hide menu bar when window is blured. if (!active && menu_bar_autohide_ && menu_bar_visible_) SetMenuBarVisibility(false); @@ -1153,9 +1198,9 @@ void NativeWindowViews::OnWidgetBoundsChanged( // handle minimized windows on Windows. const auto new_bounds = GetBounds(); if (widget_size_ != new_bounds.size()) { - if (browser_view_) { - const auto flags = static_cast(browser_view_) - ->GetAutoResizeFlags(); + if (browser_view()) { + const auto flags = static_cast(browser_view())-> + GetAutoResizeFlags(); int width_delta = 0; int height_delta = 0; if (flags & kAutoResizeWidth) { @@ -1165,7 +1210,7 @@ void NativeWindowViews::OnWidgetBoundsChanged( height_delta = new_bounds.height() - widget_size_.height(); } - auto* view = browser_view_->GetInspectableWebContentsView()->GetView(); + auto* view = browser_view()->GetInspectableWebContentsView()->GetView(); auto new_view_size = view->size(); new_view_size.set_width(new_view_size.width() + width_delta); new_view_size.set_height(new_view_size.height() + height_delta); @@ -1191,7 +1236,7 @@ void NativeWindowViews::DeleteDelegate() { } views::View* NativeWindowViews::GetInitiallyFocusedView() { - return inspectable_web_contents()->GetView()->GetWebView(); + return focused_view_; } bool NativeWindowViews::CanResize() const { @@ -1273,55 +1318,6 @@ void NativeWindowViews::OnWidgetMove() { NotifyWindowMove(); } -gfx::Rect NativeWindowViews::ContentBoundsToWindowBounds( - const gfx::Rect& bounds) const { - if (!has_frame()) - return bounds; - - gfx::Rect window_bounds(bounds); -#if defined(OS_WIN) - HWND hwnd = GetAcceleratedWidget(); - gfx::Rect dpi_bounds = display::win::ScreenWin::DIPToScreenRect(hwnd, bounds); - window_bounds = display::win::ScreenWin::ScreenToDIPRect( - hwnd, - window_->non_client_view()->GetWindowBoundsForClientBounds(dpi_bounds)); -#endif - - if (menu_bar_ && menu_bar_visible_) { - window_bounds.set_y(window_bounds.y() - kMenuBarHeight); - window_bounds.set_height(window_bounds.height() + kMenuBarHeight); - } - return window_bounds; -} - -gfx::Rect NativeWindowViews::WindowBoundsToContentBounds( - const gfx::Rect& bounds) const { - if (!has_frame()) - return bounds; - - gfx::Rect content_bounds(bounds); -#if defined(OS_WIN) - HWND hwnd = GetAcceleratedWidget(); - content_bounds.set_size( - display::win::ScreenWin::DIPToScreenSize(hwnd, content_bounds.size())); - RECT rect; - SetRectEmpty(&rect); - DWORD style = ::GetWindowLong(hwnd, GWL_STYLE); - DWORD ex_style = ::GetWindowLong(hwnd, GWL_EXSTYLE); - AdjustWindowRectEx(&rect, style, FALSE, ex_style); - content_bounds.set_width(content_bounds.width() - (rect.right - rect.left)); - content_bounds.set_height(content_bounds.height() - (rect.bottom - rect.top)); - content_bounds.set_size( - display::win::ScreenWin::ScreenToDIPSize(hwnd, content_bounds.size())); -#endif - - if (menu_bar_ && menu_bar_visible_) { - content_bounds.set_y(content_bounds.y() + kMenuBarHeight); - content_bounds.set_height(content_bounds.height() - kMenuBarHeight); - } - return content_bounds; -} - void NativeWindowViews::HandleKeyboardEvent( content::WebContents*, const content::NativeWebKeyboardEvent& event) { @@ -1369,22 +1365,26 @@ void NativeWindowViews::ShowAutofillPopup( const gfx::RectF& bounds, const std::vector& values, const std::vector& labels) { - const auto* web_preferences = - WebContentsPreferences::FromWebContents(web_contents)->web_preferences(); - bool is_offsceen = false; - web_preferences->GetBoolean("offscreen", &is_offsceen); - int guest_instance_id = 0; - web_preferences->GetInteger(options::kGuestInstanceID, &guest_instance_id); - bool is_embedder_offscreen = false; - if (guest_instance_id) { - auto manager = WebViewManager::GetWebViewManager(web_contents); - if (manager) { - auto embedder = manager->GetEmbedder(guest_instance_id); - if (embedder) { - is_embedder_offscreen = WebContentsPreferences::IsPreferenceEnabled( - "offscreen", embedder); + + auto* web_contents_preferences = + WebContentsPreferences::FromWebContents(web_contents); + if (web_contents_preferences) { + const auto* web_preferences = web_contents_preferences->web_preferences(); + + web_preferences->GetBoolean("offscreen", &is_offsceen); + int guest_instance_id = 0; + web_preferences->GetInteger(options::kGuestInstanceID, &guest_instance_id); + + if (guest_instance_id) { + auto manager = WebViewManager::GetWebViewManager(web_contents); + if (manager) { + auto embedder = manager->GetEmbedder(guest_instance_id); + if (embedder) { + is_embedder_offscreen = WebContentsPreferences::IsPreferenceEnabled( + "offscreen", embedder); + } } } } diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index f08603d1234..da2a712d420 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -101,7 +101,7 @@ class NativeWindowViews : public NativeWindow, bool IsSimpleFullScreen() override; void SetKiosk(bool kiosk) override; bool IsKiosk() override; - void SetBackgroundColor(const std::string& color_name) override; + void SetBackgroundColor(SkColor color) override; void SetHasShadow(bool has_shadow) override; bool HasShadow() override; void SetOpacity(const double opacity) override; @@ -126,15 +126,21 @@ class NativeWindowViews : public NativeWindow, gfx::AcceleratedWidget GetAcceleratedWidget() const override; + gfx::Rect ContentBoundsToWindowBounds(const gfx::Rect& bounds) const override; + gfx::Rect WindowBoundsToContentBounds(const gfx::Rect& bounds) const override; + + void UpdateDraggableRegions(std::unique_ptr region); + #if defined(OS_WIN) void SetIcon(HICON small_icon, HICON app_icon); #elif defined(USE_X11) void SetIcon(const gfx::ImageSkia& icon); #endif - void SetEnabled(bool enable); + void SetEnabled(bool enable) override; views::Widget* widget() const { return window_.get(); } + SkRegion* draggable_region() const { return draggable_region_.get(); } #if defined(OS_WIN) TaskbarHost& taskbar_host() { return taskbar_host_; } @@ -183,8 +189,6 @@ class NativeWindowViews : public NativeWindow, #endif // NativeWindow: - gfx::Rect ContentBoundsToWindowBounds(const gfx::Rect& bounds) const override; - gfx::Rect WindowBoundsToContentBounds(const gfx::Rect& bounds) const override; void HandleKeyboardEvent( content::WebContents*, const content::NativeWebKeyboardEvent& event) override; @@ -210,8 +214,7 @@ class NativeWindowViews : public NativeWindow, std::unique_ptr window_; views::View* web_view_; // Managed by inspectable_web_contents_. - - NativeBrowserView* browser_view_; + views::View* focused_view_; // The view should be focused by default. std::unique_ptr autofill_popup_; @@ -289,6 +292,10 @@ class NativeWindowViews : public NativeWindow, // Map from accelerator to menu item's command id. accelerator_util::AcceleratorTable accelerator_table_; + // For custom drag, the whole window is non-draggable and the draggable region + // has to been explicitly provided. + std::unique_ptr draggable_region_; // used in custom drag. + // How many times the Disable has been called. int disable_count_; diff --git a/atom/browser/net/asar/url_request_asar_job.cc b/atom/browser/net/asar/url_request_asar_job.cc index ad02ed620d6..a39e84ce5b8 100644 --- a/atom/browser/net/asar/url_request_asar_job.cc +++ b/atom/browser/net/asar/url_request_asar_job.cc @@ -101,24 +101,20 @@ void URLRequestAsarJob::InitializeFileJob( } void URLRequestAsarJob::Start() { - if (type_ == TYPE_ASAR) { - int flags = base::File::FLAG_OPEN | - base::File::FLAG_READ | - base::File::FLAG_ASYNC; - int rv = stream_->Open(archive_->path(), flags, - base::Bind(&URLRequestAsarJob::DidOpen, - weak_ptr_factory_.GetWeakPtr())); - if (rv != net::ERR_IO_PENDING) - DidOpen(rv); - } else if (type_ == TYPE_FILE) { + if (type_ == TYPE_ASAR || type_ == TYPE_FILE) { auto* meta_info = new FileMetaInfo(); + if (type_ == TYPE_ASAR) { + meta_info->file_path = archive_->path(); + meta_info->file_exists = true; + meta_info->is_directory = false; + meta_info->file_size = file_info_.size; + } file_task_runner_->PostTaskAndReply( FROM_HERE, - base::Bind(&URLRequestAsarJob::FetchMetaInfo, file_path_, + base::Bind(&URLRequestAsarJob::FetchMetaInfo, file_path_, type_, base::Unretained(meta_info)), base::Bind(&URLRequestAsarJob::DidFetchMetaInfo, - weak_ptr_factory_.GetWeakPtr(), - base::Owned(meta_info))); + weak_ptr_factory_.GetWeakPtr(), base::Owned(meta_info))); } else { base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, @@ -194,15 +190,11 @@ std::unique_ptr URLRequestAsarJob::SetUpSourceStream() { } bool URLRequestAsarJob::GetMimeType(std::string* mime_type) const { - if (type_ == TYPE_ASAR) { - return net::GetMimeTypeFromFile(file_path_, mime_type); - } else { - if (meta_info_.mime_type_result) { - *mime_type = meta_info_.mime_type; - return true; - } - return false; + if (meta_info_.mime_type_result) { + *mime_type = meta_info_.mime_type; + return true; } + return false; } void URLRequestAsarJob::SetExtraRequestHeaders( @@ -238,12 +230,16 @@ void URLRequestAsarJob::GetResponseInfo(net::HttpResponseInfo* info) { } void URLRequestAsarJob::FetchMetaInfo(const base::FilePath& file_path, + JobType type, FileMetaInfo* meta_info) { - base::File::Info file_info; - meta_info->file_exists = base::GetFileInfo(file_path, &file_info); - if (meta_info->file_exists) { - meta_info->file_size = file_info.size; - meta_info->is_directory = file_info.is_directory; + if (type == TYPE_FILE) { + base::File::Info file_info; + meta_info->file_exists = base::GetFileInfo(file_path, &file_info); + if (meta_info->file_exists) { + meta_info->file_path = file_path; + meta_info->file_size = file_info.size; + meta_info->is_directory = file_info.is_directory; + } } // On Windows GetMimeTypeFromFile() goes to the registry. Thus it should be // done in WorkerPool. @@ -261,9 +257,9 @@ void URLRequestAsarJob::DidFetchMetaInfo(const FileMetaInfo* meta_info) { int flags = base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_ASYNC; - int rv = stream_->Open(file_path_, flags, - base::Bind(&URLRequestAsarJob::DidOpen, - weak_ptr_factory_.GetWeakPtr())); + int rv = stream_->Open( + meta_info_.file_path, flags, + base::Bind(&URLRequestAsarJob::DidOpen, weak_ptr_factory_.GetWeakPtr())); if (rv != net::ERR_IO_PENDING) DidOpen(rv); } diff --git a/atom/browser/net/asar/url_request_asar_job.h b/atom/browser/net/asar/url_request_asar_job.h index 149faed6c5b..d7c974fa488 100644 --- a/atom/browser/net/asar/url_request_asar_job.h +++ b/atom/browser/net/asar/url_request_asar_job.h @@ -63,6 +63,13 @@ class URLRequestAsarJob : public net::URLRequestJob { void GetResponseInfo(net::HttpResponseInfo* info) override; private: + // The type of this job. + enum JobType { + TYPE_ERROR, + TYPE_ASAR, + TYPE_FILE, + }; + // Meta information about the file. It's used as a member in the // URLRequestFileJob and also passed between threads because disk access is // necessary to obtain it. @@ -80,10 +87,13 @@ class URLRequestAsarJob : public net::URLRequestJob { bool file_exists; // Flag showing whether the file name actually refers to a directory. bool is_directory; + // Path to the file. + base::FilePath file_path; }; // Fetches file info on a background thread. static void FetchMetaInfo(const base::FilePath& file_path, + JobType type, FileMetaInfo* meta_info); // Callback after fetching file info on a background thread. @@ -99,12 +109,6 @@ class URLRequestAsarJob : public net::URLRequestJob { // Callback after data is asynchronously read from the file into |buf|. void DidRead(scoped_refptr buf, int result); - // The type of this job. - enum JobType { - TYPE_ERROR, - TYPE_ASAR, - TYPE_FILE, - }; JobType type_; std::shared_ptr archive_; diff --git a/atom/browser/net/atom_cert_verifier.cc b/atom/browser/net/atom_cert_verifier.cc index eccfe614e39..189dd4878aa 100644 --- a/atom/browser/net/atom_cert_verifier.cc +++ b/atom/browser/net/atom_cert_verifier.cc @@ -94,25 +94,27 @@ class CertVerifierRequest : public AtomCertVerifier::Request { request->default_result = net::ErrorToString(error); request->error_code = error; request->certificate = params_.certificate(); + auto response_callback = base::Bind(&CertVerifierRequest::OnResponseInUI, + weak_ptr_factory_.GetWeakPtr()); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&CertVerifierRequest::OnVerifyRequestInUI, - weak_ptr_factory_.GetWeakPtr(), - cert_verifier_->verify_proc(), - base::Passed(&request))); + cert_verifier_->verify_proc(), base::Passed(&request), + response_callback)); } - void OnVerifyRequestInUI(const AtomCertVerifier::VerifyProc& verify_proc, - std::unique_ptr request) { - verify_proc.Run(*(request.get()), - base::Bind(&CertVerifierRequest::OnResponseInUI, - weak_ptr_factory_.GetWeakPtr())); + static void OnVerifyRequestInUI( + const AtomCertVerifier::VerifyProc& verify_proc, + std::unique_ptr request, + const base::Callback& response_callback) { + verify_proc.Run(*(request.get()), response_callback); } - void OnResponseInUI(int result) { - BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::Bind(&CertVerifierRequest::NotifyResponseInIO, - weak_ptr_factory_.GetWeakPtr(), result)); + static void OnResponseInUI(base::WeakPtr self, + int result) { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&CertVerifierRequest::NotifyResponseInIO, self, result)); } void NotifyResponseInIO(int result) { diff --git a/atom/browser/net/atom_cookie_delegate.cc b/atom/browser/net/atom_cookie_delegate.cc deleted file mode 100644 index b94f396d981..00000000000 --- a/atom/browser/net/atom_cookie_delegate.cc +++ /dev/null @@ -1,44 +0,0 @@ -// 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/net/atom_cookie_delegate.h" - -#include "content/public/browser/browser_thread.h" - -namespace atom { - -AtomCookieDelegate::AtomCookieDelegate() { -} - -AtomCookieDelegate::~AtomCookieDelegate() { -} - -void AtomCookieDelegate::AddObserver(Observer* observer) { - observers_.AddObserver(observer); -} - -void AtomCookieDelegate::RemoveObserver(Observer* observer) { - observers_.RemoveObserver(observer); -} - -void AtomCookieDelegate::NotifyObservers( - const net::CanonicalCookie& cookie, - bool removed, - net::CookieStore::ChangeCause cause) { - for (Observer& observer : observers_) - observer.OnCookieChanged(cookie, removed, cause); -} - -void AtomCookieDelegate::OnCookieChanged( - const net::CanonicalCookie& cookie, - bool removed, - net::CookieStore::ChangeCause cause) { - content::BrowserThread::PostTask( - content::BrowserThread::UI, - FROM_HERE, - base::Bind(&AtomCookieDelegate::NotifyObservers, - this, cookie, removed, cause)); -} - -} // namespace atom diff --git a/atom/browser/net/atom_cookie_delegate.h b/atom/browser/net/atom_cookie_delegate.h deleted file mode 100644 index 8c58aa6ada0..00000000000 --- a/atom/browser/net/atom_cookie_delegate.h +++ /dev/null @@ -1,48 +0,0 @@ -// 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_NET_ATOM_COOKIE_DELEGATE_H_ -#define ATOM_BROWSER_NET_ATOM_COOKIE_DELEGATE_H_ - -#include "base/observer_list.h" -#include "net/cookies/cookie_monster.h" - -namespace atom { - -class AtomCookieDelegate : public net::CookieMonsterDelegate { - public: - AtomCookieDelegate(); - ~AtomCookieDelegate() override; - - class Observer { - public: - virtual void OnCookieChanged(const net::CanonicalCookie& cookie, - bool removed, - net::CookieStore::ChangeCause cause) {} - protected: - virtual ~Observer() {} - }; - - void AddObserver(Observer* observer); - void RemoveObserver(Observer* observer); - - // net::CookieMonsterDelegate: - void OnCookieChanged(const net::CanonicalCookie& cookie, - bool removed, - net::CookieStore::ChangeCause cause) override; - - - private: - base::ObserverList observers_; - - void NotifyObservers(const net::CanonicalCookie& cookie, - bool removed, - net::CookieStore::ChangeCause cause); - - DISALLOW_COPY_AND_ASSIGN(AtomCookieDelegate); -}; - -} // namespace atom - -#endif // ATOM_BROWSER_NET_ATOM_COOKIE_DELEGATE_H_ diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index 5a76d966d6f..680459a14c8 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -41,20 +41,38 @@ const char* ResourceTypeToString(content::ResourceType type) { } } +int32_t GetWebContentsID(int process_id, int frame_id) { + auto* webContents = content::WebContents::FromRenderFrameHost( + content::RenderFrameHost::FromID(process_id, frame_id)); + return atom::api::WebContents::GetIDFromWrappedClass(webContents); +} + namespace { using ResponseHeadersContainer = std::pair*, const std::string&>; void RunSimpleListener(const AtomNetworkDelegate::SimpleListener& listener, - std::unique_ptr details) { + std::unique_ptr details, + int render_process_id, + int render_frame_id) { + int32_t id = GetWebContentsID(render_process_id, render_frame_id); + // id must be greater than zero + if (id) + details->SetInteger("webContentsId", id); return listener.Run(*(details.get())); } void RunResponseListener( const AtomNetworkDelegate::ResponseListener& listener, std::unique_ptr details, + int render_process_id, + int render_frame_id, const AtomNetworkDelegate::ResponseCallback& callback) { + int32_t id = GetWebContentsID(render_process_id, render_frame_id); + // id must be greater than zero + if (id) + details->SetInteger("webContentsId", id); return listener.Run(*(details.get()), callback); } @@ -78,16 +96,6 @@ void ToDictionary(base::DictionaryValue* details, net::URLRequest* request) { details->SetDouble("timestamp", base::Time::Now().ToDoubleT() * 1000); const auto* info = content::ResourceRequestInfo::ForRequest(request); if (info) { - int process_id = info->GetChildID(); - int frame_id = info->GetRenderFrameID(); - auto* webContents = content::WebContents::FromRenderFrameHost( - content::RenderFrameHost::FromID(process_id, frame_id)); - int webContentsId = atom::api::WebContents::GetIDFromWrappedClass( - webContents); - - // webContentsId must be greater than zero - if (webContentsId) - details->SetInteger("webContentsId", webContentsId); details->SetString("resourceType", ResourceTypeToString(info->GetResourceType())); } else { @@ -100,7 +108,7 @@ void ToDictionary(base::DictionaryValue* details, std::unique_ptr dict(new base::DictionaryValue); net::HttpRequestHeaders::Iterator it(headers); while (it.GetNext()) - dict->SetStringWithoutPathExpansion(it.name(), it.value()); + dict->SetKey(it.name(), base::Value(it.value())); details->Set("requestHeaders", std::move(dict)); } @@ -219,27 +227,26 @@ AtomNetworkDelegate::~AtomNetworkDelegate() { void AtomNetworkDelegate::SetSimpleListenerInIO( SimpleEvent type, - const URLPatterns& patterns, - const SimpleListener& callback) { + URLPatterns patterns, + SimpleListener callback) { if (callback.is_null()) simple_listeners_.erase(type); else - simple_listeners_[type] = { patterns, callback }; + simple_listeners_[type] = { std::move(patterns), std::move(callback) }; } void AtomNetworkDelegate::SetResponseListenerInIO( ResponseEvent type, - const URLPatterns& patterns, - const ResponseListener& callback) { + URLPatterns patterns, + ResponseListener callback) { if (callback.is_null()) response_listeners_.erase(type); else - response_listeners_[type] = { patterns, callback }; + response_listeners_[type] = { std::move(patterns), std::move(callback) }; } void AtomNetworkDelegate::SetDevToolsNetworkEmulationClientId( const std::string& client_id) { - base::AutoLock auto_lock(lock_); client_id_ = client_id; } @@ -258,16 +265,10 @@ int AtomNetworkDelegate::OnBeforeStartTransaction( 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()) + if (!client_id_.empty()) headers->SetHeader( DevToolsNetworkTransaction::kDevToolsEmulateNetworkConditionsClientId, - client_id); + client_id_); if (!base::ContainsKey(response_listeners_, kOnBeforeSendHeaders)) return brightray::NetworkDelegate::OnBeforeStartTransaction( request, callback, headers); @@ -382,6 +383,10 @@ int AtomNetworkDelegate::HandleResponseEvent( std::unique_ptr details(new base::DictionaryValue); FillDetailsObject(details.get(), request, args...); + int render_process_id, render_frame_id; + content::ResourceRequestInfo::GetRenderFrameForRequest( + request, &render_process_id, &render_frame_id); + // The |request| could be destroyed before the |callback| is called. callbacks_[request->identifier()] = callback; @@ -391,7 +396,7 @@ int AtomNetworkDelegate::HandleResponseEvent( BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(RunResponseListener, info.listener, base::Passed(&details), - response)); + render_process_id, render_frame_id, response)); return net::ERR_IO_PENDING; } @@ -405,9 +410,14 @@ void AtomNetworkDelegate::HandleSimpleEvent( std::unique_ptr details(new base::DictionaryValue); FillDetailsObject(details.get(), request, args...); + int render_process_id, render_frame_id; + content::ResourceRequestInfo::GetRenderFrameForRequest( + request, &render_process_id, &render_frame_id); + BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, - base::Bind(RunSimpleListener, info.listener, base::Passed(&details))); + base::Bind(RunSimpleListener, info.listener, base::Passed(&details), + render_process_id, render_frame_id)); } template diff --git a/atom/browser/net/atom_network_delegate.h b/atom/browser/net/atom_network_delegate.h index 7a50d6076f2..ad37a926d47 100644 --- a/atom/browser/net/atom_network_delegate.h +++ b/atom/browser/net/atom_network_delegate.h @@ -62,11 +62,11 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { ~AtomNetworkDelegate() override; void SetSimpleListenerInIO(SimpleEvent type, - const URLPatterns& patterns, - const SimpleListener& callback); + URLPatterns patterns, + SimpleListener callback); void SetResponseListenerInIO(ResponseEvent type, - const URLPatterns& patterns, - const ResponseListener& callback); + URLPatterns patterns, + ResponseListener callback); void SetDevToolsNetworkEmulationClientId(const std::string& client_id); @@ -118,8 +118,6 @@ 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_; diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index e75aba91373..1f0bdae4c08 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -7,6 +7,7 @@ #include #include "atom/browser/api/atom_api_url_request.h" #include "atom/browser/atom_browser_context.h" +#include "atom/browser/net/atom_url_request_job_factory.h" #include "base/callback.h" #include "content/public/browser/browser_thread.h" #include "net/base/elements_upload_data_stream.h" @@ -121,6 +122,9 @@ void AtomURLRequest::DoInitialize( request_->set_method(method); // Do not send cookies from the cookie store. DoSetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES); + // Set a flag to stop custom protocol from intercepting this request. + request_->SetUserData(DisableProtocolInterceptFlagKey(), + base::WrapUnique(new base::SupportsUserData::Data())); } void AtomURLRequest::DoTerminate() { diff --git a/atom/browser/net/atom_url_request_job_factory.cc b/atom/browser/net/atom_url_request_job_factory.cc index 79dd80b5866..20680adf7da 100644 --- a/atom/browser/net/atom_url_request_job_factory.cc +++ b/atom/browser/net/atom_url_request_job_factory.cc @@ -15,8 +15,18 @@ using content::BrowserThread; namespace atom { +namespace { + +int disable_protocol_intercept_flag_key = 0; + +} // namespace + typedef net::URLRequestJobFactory::ProtocolHandler ProtocolHandler; +const void* DisableProtocolInterceptFlagKey() { + return &disable_protocol_intercept_flag_key; +} + AtomURLRequestJobFactory::AtomURLRequestJobFactory() {} AtomURLRequestJobFactory::~AtomURLRequestJobFactory() { @@ -93,6 +103,8 @@ net::URLRequestJob* AtomURLRequestJobFactory::MaybeCreateJobWithProtocolHandler( auto it = protocol_handler_map_.find(scheme); if (it == protocol_handler_map_.end()) return nullptr; + if (request->GetUserData(DisableProtocolInterceptFlagKey())) + return nullptr; return it->second->MaybeCreateJob(request, network_delegate); } diff --git a/atom/browser/net/atom_url_request_job_factory.h b/atom/browser/net/atom_url_request_job_factory.h index 56f979c2ff6..a560839b68e 100644 --- a/atom/browser/net/atom_url_request_job_factory.h +++ b/atom/browser/net/atom_url_request_job_factory.h @@ -16,6 +16,8 @@ namespace atom { +const void* DisableProtocolInterceptFlagKey(); + class AtomURLRequestJobFactory : public net::URLRequestJobFactory { public: AtomURLRequestJobFactory(); diff --git a/atom/browser/net/cookie_details.h b/atom/browser/net/cookie_details.h new file mode 100644 index 00000000000..5103836dcb6 --- /dev/null +++ b/atom/browser/net/cookie_details.h @@ -0,0 +1,27 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_NET_COOKIE_DETAILS_H_ +#define ATOM_BROWSER_NET_COOKIE_DETAILS_H_ + +#include "base/macros.h" +#include "net/cookies/cookie_store.h" + +namespace atom { + +struct CookieDetails { + public: + CookieDetails(const net::CanonicalCookie* cookie_copy, + bool is_removed, + net::CookieStore::ChangeCause cause) + : cookie(cookie_copy), removed(is_removed), cause(cause) {} + + const net::CanonicalCookie* cookie; + bool removed; + net::CookieStore::ChangeCause cause; +}; + +} // namespace atom + +#endif // ATOM_BROWSER_NET_COOKIE_DETAILS_H_ diff --git a/atom/browser/node_debugger.cc b/atom/browser/node_debugger.cc index 1f43f47fb1c..191531d5bf3 100644 --- a/atom/browser/node_debugger.cc +++ b/atom/browser/node_debugger.cc @@ -14,15 +14,13 @@ namespace atom { NodeDebugger::NodeDebugger(node::Environment* env) - : env_(env), platform_(nullptr) { + : env_(env) { } NodeDebugger::~NodeDebugger() { - if (platform_) - FreePlatform(platform_); } -void NodeDebugger::Start() { +void NodeDebugger::Start(node::MultiIsolatePlatform* platform) { auto inspector = env_->inspector_agent(); if (inspector == nullptr) return; @@ -36,23 +34,16 @@ void NodeDebugger::Start() { #endif } - if (options.inspector_enabled()) { - // Use custom platform since the gin platform does not work correctly - // with node's inspector agent. We use the default thread pool size - // specified by node.cc - platform_ = node::CreatePlatform( - /* thread_pool_size */ 4, env_->event_loop(), - /* tracing_controller */ nullptr); - - // Set process._debugWaitConnect if --inspect-brk was specified to stop - // the debugger on the first line - if (options.wait_for_connect()) { - mate::Dictionary process(env_->isolate(), env_->process_object()); - process.Set("_breakFirstLine", true); - } - - inspector->Start(platform_, nullptr, options); + // Set process._debugWaitConnect if --inspect-brk was specified to stop + // the debugger on the first line + if (options.wait_for_connect()) { + mate::Dictionary process(env_->isolate(), env_->process_object()); + process.Set("_breakFirstLine", true); } + + inspector->Start(static_cast(platform), nullptr, + options); + DCHECK(env_->inspector_agent()->IsStarted()); } } // namespace atom diff --git a/atom/browser/node_debugger.h b/atom/browser/node_debugger.h index 609a4dda3c5..f62eeadac55 100644 --- a/atom/browser/node_debugger.h +++ b/atom/browser/node_debugger.h @@ -9,7 +9,7 @@ namespace node { class Environment; -class NodePlatform; +class MultiIsolatePlatform; } namespace atom { @@ -20,11 +20,10 @@ class NodeDebugger { explicit NodeDebugger(node::Environment* env); ~NodeDebugger(); - void Start(); + void Start(node::MultiIsolatePlatform* platform); private: node::Environment* env_; - node::NodePlatform* platform_; DISALLOW_COPY_AND_ASSIGN(NodeDebugger); }; diff --git a/atom/browser/osr/osr_render_widget_host_view.cc b/atom/browser/osr/osr_render_widget_host_view.cc index 295b3f886db..328671f09df 100644 --- a/atom/browser/osr/osr_render_widget_host_view.cc +++ b/atom/browser/osr/osr_render_widget_host_view.cc @@ -14,9 +14,9 @@ #include "base/memory/ptr_util.h" #include "base/single_thread_task_runner.h" #include "base/time/time.h" -#include "cc/output/copy_output_request.h" -#include "cc/scheduler/delay_based_time_source.h" +#include "components/viz/common/frame_sinks/delay_based_time_source.h" #include "components/viz/common/gl_helper.h" +#include "components/viz/common/quads/copy_output_request.h" #include "content/browser/renderer_host/compositor_resize_lock.h" #include "content/browser/renderer_host/render_widget_host_delegate.h" #include "content/browser/renderer_host/render_widget_host_impl.h" @@ -124,14 +124,14 @@ class AtomCopyFrameGenerator { } void GenerateCopyFrame(const gfx::Rect& damage_rect) { - if (!view_->render_widget_host()) + if (!view_->render_widget_host() || !view_->IsPainting()) return; - std::unique_ptr request = - cc::CopyOutputRequest::CreateBitmapRequest(base::Bind( - &AtomCopyFrameGenerator::CopyFromCompositingSurfaceHasResult, - weak_ptr_factory_.GetWeakPtr(), - damage_rect)); + std::unique_ptr request = + viz::CopyOutputRequest::CreateBitmapRequest(base::Bind( + &AtomCopyFrameGenerator::CopyFromCompositingSurfaceHasResult, + weak_ptr_factory_.GetWeakPtr(), + damage_rect)); request->set_area(gfx::Rect(view_->GetPhysicalBackingSize())); view_->GetRootLayer()->RequestCopyOfOutput(std::move(request)); @@ -145,7 +145,7 @@ class AtomCopyFrameGenerator { private: void CopyFromCompositingSurfaceHasResult( const gfx::Rect& damage_rect, - std::unique_ptr result) { + std::unique_ptr result) { if (result->IsEmpty() || result->size().IsEmpty() || !view_->render_widget_host()) { OnCopyFrameCaptureFailure(damage_rect); @@ -194,7 +194,7 @@ class AtomCopyFrameGenerator { void OnCopyFrameCaptureSuccess( const gfx::Rect& damage_rect, - std::shared_ptr bitmap) { + const std::shared_ptr& bitmap) { base::AutoLock lock(onPaintLock_); view_->OnPaint(damage_rect, *bitmap); } @@ -214,12 +214,12 @@ class AtomCopyFrameGenerator { DISALLOW_COPY_AND_ASSIGN(AtomCopyFrameGenerator); }; -class AtomBeginFrameTimer : public cc::DelayBasedTimeSourceClient { +class AtomBeginFrameTimer : public viz::DelayBasedTimeSourceClient { public: AtomBeginFrameTimer(int frame_rate_threshold_us, const base::Closure& callback) : callback_(callback) { - time_source_.reset(new cc::DelayBasedTimeSource( + time_source_.reset(new viz::DelayBasedTimeSource( content::BrowserThread::GetTaskRunnerForThread( content::BrowserThread::UI).get())); time_source_->SetTimebaseAndInterval( @@ -248,13 +248,15 @@ class AtomBeginFrameTimer : public cc::DelayBasedTimeSourceClient { } const base::Closure callback_; - std::unique_ptr time_source_; + std::unique_ptr time_source_; DISALLOW_COPY_AND_ASSIGN(AtomBeginFrameTimer); }; OffScreenRenderWidgetHostView::OffScreenRenderWidgetHostView( bool transparent, + bool painting, + int frame_rate, const OnPaintCallback& callback, content::RenderWidgetHost* host, OffScreenRenderWidgetHostView* parent_host_view, @@ -268,17 +270,18 @@ OffScreenRenderWidgetHostView::OffScreenRenderWidgetHostView( transparent_(transparent), callback_(callback), parent_callback_(nullptr), - frame_rate_(60), + frame_rate_(frame_rate), frame_rate_threshold_us_(0), last_time_(base::Time::Now()), scale_factor_(kDefaultScaleFactor), size_(native_window->GetSize()), - painting_(true), + painting_(painting), is_showing_(!render_widget_host_->is_hidden()), is_destroyed_(false), popup_position_(gfx::Rect()), hold_resize_(false), pending_resize_(false), + paint_callback_running_(false), renderer_compositor_frame_sink_(nullptr), background_color_(SkColor()), weak_ptr_factory_(this) { @@ -300,10 +303,14 @@ OffScreenRenderWidgetHostView::OffScreenRenderWidgetHostView( ui::ContextFactoryPrivate* context_factory_private = factory->GetContextFactoryPrivate(); compositor_.reset( - new ui::Compositor(context_factory_private->AllocateFrameSinkId(), - content::GetContextFactory(), context_factory_private, - base::ThreadTaskRunnerHandle::Get(), false)); - compositor_->SetAcceleratedWidget(native_window_->GetAcceleratedWidget()); + new ui::Compositor( + context_factory_private->AllocateFrameSinkId(), + content::GetContextFactory(), + context_factory_private, + base::ThreadTaskRunnerHandle::Get(), + false /* enable_surface_synchronization */, + false /* enable_pixel_canvas */)); + compositor_->SetAcceleratedWidget(gfx::kNullAcceleratedWidget); compositor_->SetRootLayer(root_layer_.get()); #endif GetCompositor()->SetDelegate(this); @@ -370,11 +377,11 @@ void OffScreenRenderWidgetHostView::SendBeginFrame( base::TimeTicks deadline = display_time - estimated_browser_composite_time; - const cc::BeginFrameArgs& begin_frame_args = - cc::BeginFrameArgs::Create(BEGINFRAME_FROM_HERE, - begin_frame_source_.source_id(), - begin_frame_number_, frame_time, deadline, - vsync_period, cc::BeginFrameArgs::NORMAL); + const viz::BeginFrameArgs& begin_frame_args = + viz::BeginFrameArgs::Create(BEGINFRAME_FROM_HERE, + begin_frame_source_.source_id(), + begin_frame_number_, frame_time, deadline, + vsync_period, viz::BeginFrameArgs::NORMAL); DCHECK(begin_frame_args.IsValid()); begin_frame_number_++; @@ -527,7 +534,7 @@ void OffScreenRenderWidgetHostView::UnlockMouse() { } void OffScreenRenderWidgetHostView::DidCreateNewRendererCompositorFrameSink( - cc::mojom::CompositorFrameSinkClient* renderer_compositor_frame_sink) { + viz::mojom::CompositorFrameSinkClient* renderer_compositor_frame_sink) { renderer_compositor_frame_sink_ = renderer_compositor_frame_sink; if (GetDelegatedFrameHost()) { GetDelegatedFrameHost()->DidCreateNewRendererCompositorFrameSink( @@ -737,6 +744,8 @@ content::RenderWidgetHostViewBase* return new OffScreenRenderWidgetHostView( transparent_, + true, + embedder_host_view->GetFrameRate(), callback_, render_widget_host, embedder_host_view, @@ -928,7 +937,7 @@ bool OffScreenRenderWidgetHostView::IsAutoResizeEnabled() const { void OffScreenRenderWidgetHostView::SetNeedsBeginFrames( bool needs_begin_frames) { - SetupFrameRate(false); + SetupFrameRate(true); begin_frame_timer_->SetActive(needs_begin_frames); @@ -999,7 +1008,9 @@ void OffScreenRenderWidgetHostView::OnPaint( } damage.Intersect(GetViewBounds()); + paint_callback_running_ = true; callback_.Run(damage, bitmap); + paint_callback_running_ = false; for (size_t i = 0; i < damages.size(); i++) { CopyBitmapTo(bitmap, originals[i], damages[i]); @@ -1147,7 +1158,7 @@ void OffScreenRenderWidgetHostView::SetPainting(bool painting) { painting_ = painting; if (software_output_device_) { - software_output_device_->SetActive(painting_, true); + software_output_device_->SetActive(painting_, !paint_callback_running_); } } @@ -1164,16 +1175,16 @@ void OffScreenRenderWidgetHostView::SetFrameRate(int frame_rate) { } else { if (frame_rate <= 0) frame_rate = 1; - if (frame_rate > 60) - frame_rate = 60; + if (frame_rate > 240) + frame_rate = 240; frame_rate_ = frame_rate; } + SetupFrameRate(true); + for (auto guest_host_view : guest_host_views_) guest_host_view->SetFrameRate(frame_rate); - - SetupFrameRate(true); } int OffScreenRenderWidgetHostView::GetFrameRate() const { @@ -1201,7 +1212,7 @@ void OffScreenRenderWidgetHostView::SetupFrameRate(bool force) { frame_rate_threshold_us_ = 1000000 / frame_rate_; - GetCompositor()->vsync_manager()->SetAuthoritativeVSyncInterval( + GetCompositor()->SetAuthoritativeVSyncInterval( base::TimeDelta::FromMicroseconds(frame_rate_threshold_us_)); if (copy_frame_generator_.get()) { diff --git a/atom/browser/osr/osr_render_widget_host_view.h b/atom/browser/osr/osr_render_widget_host_view.h index ed4b160440e..f8867fbae30 100644 --- a/atom/browser/osr/osr_render_widget_host_view.h +++ b/atom/browser/osr/osr_render_widget_host_view.h @@ -21,7 +21,8 @@ #include "base/threading/thread.h" #include "base/time/time.h" #include "cc/output/compositor_frame.h" -#include "cc/scheduler/begin_frame_source.h" +#include "components/viz/common/frame_sinks/begin_frame_args.h" +#include "components/viz/common/frame_sinks/begin_frame_source.h" #include "content/browser/frame_host/render_widget_host_view_guest.h" #include "content/browser/renderer_host/compositor_resize_lock.h" #include "content/browser/renderer_host/delegated_frame_host.h" @@ -74,6 +75,8 @@ class OffScreenRenderWidgetHostView public OffscreenViewProxyObserver { public: OffScreenRenderWidgetHostView(bool transparent, + bool painting, + int frame_rate, const OnPaintCallback& callback, content::RenderWidgetHost* render_widget_host, OffScreenRenderWidgetHostView* parent_host_view, @@ -116,7 +119,7 @@ class OffScreenRenderWidgetHostView // content::RenderWidgetHostViewBase: void DidCreateNewRendererCompositorFrameSink( - cc::mojom::CompositorFrameSinkClient* renderer_compositor_frame_sink) + viz::mojom::CompositorFrameSinkClient* renderer_compositor_frame_sink) override; void SubmitCompositorFrame(const viz::LocalSurfaceId& local_surface_id, cc::CompositorFrame frame) override; @@ -315,6 +318,8 @@ class OffScreenRenderWidgetHostView bool hold_resize_; bool pending_resize_; + bool paint_callback_running_; + std::unique_ptr root_layer_; std::unique_ptr compositor_; std::unique_ptr delegated_frame_host_; @@ -323,8 +328,8 @@ class OffScreenRenderWidgetHostView std::unique_ptr begin_frame_timer_; // Provides |source_id| for BeginFrameArgs that we create. - cc::StubBeginFrameSource begin_frame_source_; - uint64_t begin_frame_number_ = cc::BeginFrameArgs::kStartingFrameNumber; + viz::StubBeginFrameSource begin_frame_source_; + uint64_t begin_frame_number_ = viz::BeginFrameArgs::kStartingFrameNumber; #if defined(OS_MACOSX) CALayer* background_layer_; @@ -338,7 +343,7 @@ class OffScreenRenderWidgetHostView std::string selected_text_; #endif - cc::mojom::CompositorFrameSinkClient* renderer_compositor_frame_sink_; + viz::mojom::CompositorFrameSinkClient* renderer_compositor_frame_sink_; SkColor background_color_; diff --git a/atom/browser/osr/osr_web_contents_view.cc b/atom/browser/osr/osr_web_contents_view.cc index 0e10abf6a03..4cbc6a5c42f 100644 --- a/atom/browser/osr/osr_web_contents_view.cc +++ b/atom/browser/osr/osr_web_contents_view.cc @@ -15,6 +15,8 @@ namespace atom { OffScreenWebContentsView::OffScreenWebContentsView( bool transparent, const OnPaintCallback& callback) : transparent_(transparent), + painting_(true), + frame_rate_(60), callback_(callback), web_contents_(nullptr) { #if defined(OS_MACOSX) @@ -103,6 +105,8 @@ content::RenderWidgetHostViewBase* auto relay = NativeWindowRelay::FromWebContents(web_contents_); return new OffScreenRenderWidgetHostView( transparent_, + painting_, + GetFrameRate(), callback_, render_widget_host, nullptr, @@ -125,6 +129,8 @@ content::RenderWidgetHostViewBase* return new OffScreenRenderWidgetHostView( transparent_, + true, + view->GetFrameRate(), callback_, render_widget_host, view, @@ -202,6 +208,42 @@ void OffScreenWebContentsView::UpdateDragCursor( blink::WebDragOperation operation) { } +void OffScreenWebContentsView::SetPainting(bool painting) { + auto* view = GetView(); + if (view != nullptr) { + view->SetPainting(painting); + } else { + painting_ = painting; + } +} + +bool OffScreenWebContentsView::IsPainting() const { + auto* view = GetView(); + if (view != nullptr) { + return view->IsPainting(); + } else { + return painting_; + } +} + +void OffScreenWebContentsView::SetFrameRate(int frame_rate) { + auto* view = GetView(); + if (view != nullptr) { + view->SetFrameRate(frame_rate); + } else { + frame_rate_ = frame_rate; + } +} + +int OffScreenWebContentsView::GetFrameRate() const { + auto* view = GetView(); + if (view != nullptr) { + return view->GetFrameRate(); + } else { + return frame_rate_; + } +} + OffScreenRenderWidgetHostView* OffScreenWebContentsView::GetView() const { if (web_contents_) { return static_cast( diff --git a/atom/browser/osr/osr_web_contents_view.h b/atom/browser/osr/osr_web_contents_view.h index ffb3b38619c..788e55bf316 100644 --- a/atom/browser/osr/osr_web_contents_view.h +++ b/atom/browser/osr/osr_web_contents_view.h @@ -69,6 +69,11 @@ class OffScreenWebContentsView : public content::WebContentsView, content::RenderWidgetHostImpl* source_rwh) override; void UpdateDragCursor(blink::WebDragOperation operation) override; + void SetPainting(bool painting); + bool IsPainting() const; + void SetFrameRate(int frame_rate); + int GetFrameRate() const; + private: #if defined(OS_MACOSX) void PlatformCreate(); @@ -78,6 +83,8 @@ class OffScreenWebContentsView : public content::WebContentsView, OffScreenRenderWidgetHostView* GetView() const; const bool transparent_; + bool painting_; + int frame_rate_; OnPaintCallback callback_; // Weak refs. diff --git a/atom/browser/relauncher.cc b/atom/browser/relauncher.cc index 9610d80693b..0f3f254b65f 100644 --- a/atom/browser/relauncher.cc +++ b/atom/browser/relauncher.cc @@ -87,15 +87,12 @@ bool RelaunchAppWithHelper(const base::FilePath& helper, 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; + options.fds_to_remap.push_back( + std::make_pair(pipe_write_fd.get(), internal::kRelauncherSyncFD)); base::Process process = base::LaunchProcess(relaunch_argv, options); #elif defined(OS_WIN) base::Process process = base::LaunchProcess( @@ -140,11 +137,7 @@ bool RelaunchAppWithHelper(const base::FilePath& helper, } 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"; diff --git a/atom/browser/relauncher_linux.cc b/atom/browser/relauncher_linux.cc index 6d60b072f6d..b5e84bce12d 100644 --- a/atom/browser/relauncher_linux.cc +++ b/atom/browser/relauncher_linux.cc @@ -60,14 +60,13 @@ int LaunchProgram(const StringVector& relauncher_args, // Redirect the stdout of child process to /dev/null, otherwise after // relaunch the child process will raise exception when writing to stdout. base::ScopedFD devnull(HANDLE_EINTR(open("/dev/null", O_WRONLY))); - base::FileHandleMappingVector no_stdout; - no_stdout.push_back(std::make_pair(devnull.get(), STDERR_FILENO)); - no_stdout.push_back(std::make_pair(devnull.get(), STDOUT_FILENO)); base::LaunchOptions options; options.allow_new_privs = true; options.new_process_group = true; // detach - options.fds_to_remap = &no_stdout; + options.fds_to_remap.push_back(std::make_pair(devnull.get(), STDERR_FILENO)); + options.fds_to_remap.push_back(std::make_pair(devnull.get(), STDOUT_FILENO)); + base::Process process = base::LaunchProcess(argv, options); return process.IsValid() ? 0 : 1; } diff --git a/atom/browser/relauncher_mac.cc b/atom/browser/relauncher_mac.cc index 5aee5d2d928..74c135874be 100644 --- a/atom/browser/relauncher_mac.cc +++ b/atom/browser/relauncher_mac.cc @@ -82,13 +82,12 @@ int LaunchProgram(const StringVector& relauncher_args, // Redirect the stdout of child process to /dev/null, otherwise after // relaunch the child process will raise exception when writing to stdout. base::ScopedFD devnull(HANDLE_EINTR(open("/dev/null", O_WRONLY))); - base::FileHandleMappingVector no_stdout; - no_stdout.push_back(std::make_pair(devnull.get(), STDERR_FILENO)); - no_stdout.push_back(std::make_pair(devnull.get(), STDOUT_FILENO)); base::LaunchOptions options; options.new_process_group = true; // detach - options.fds_to_remap = &no_stdout; + options.fds_to_remap.push_back(std::make_pair(devnull.get(), STDERR_FILENO)); + options.fds_to_remap.push_back(std::make_pair(devnull.get(), STDOUT_FILENO)); + base::Process process = base::LaunchProcess(argv, options); return process.IsValid() ? 0 : 1; } diff --git a/atom/browser/relauncher_win.cc b/atom/browser/relauncher_win.cc index 1aebb515ecf..3198b7ad73e 100644 --- a/atom/browser/relauncher_win.cc +++ b/atom/browser/relauncher_win.cc @@ -87,7 +87,7 @@ StringType AddQuoteForArg(const StringType& arg) { } // namespace StringType GetWaitEventName(base::ProcessId pid) { - return base::StringPrintf(L"%s-%d", kWaitEventName, static_cast(pid)); + return base::StringPrintf(L"%ls-%d", kWaitEventName, static_cast(pid)); } StringType ArgvToCommandLineString(const StringVector& argv) { diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index 3313eba36bb..e73d055de09 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile electron.icns CFBundleVersion - 1.8.2 + 0.0.0 CFBundleShortVersionString - 1.8.2 + 0.0.0 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/atom/browser/resources/mac/electron.icns b/atom/browser/resources/mac/electron.icns index dac213ed9d8..b4617c22123 100644 Binary files a/atom/browser/resources/mac/electron.icns and b/atom/browser/resources/mac/electron.icns differ diff --git a/atom/browser/resources/win/atom.ico b/atom/browser/resources/win/atom.ico index 004176004f7..c080e4b377a 100644 Binary files a/atom/browser/resources/win/atom.ico and b/atom/browser/resources/win/atom.ico differ diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index c349f99d712..c5f5dcd23eb 100644 --- a/atom/browser/resources/win/atom.rc +++ b/atom/browser/resources/win/atom.rc @@ -56,8 +56,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,8,2,2 - PRODUCTVERSION 1,8,2,2 + FILEVERSION 0,0,0,0 + PRODUCTVERSION 0,0,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "1.8.2" + VALUE "FileVersion", "0.0.0" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "1.8.2" + VALUE "ProductVersion", "0.0.0" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/browser/session_preferences.cc b/atom/browser/session_preferences.cc new file mode 100644 index 00000000000..0731036d66f --- /dev/null +++ b/atom/browser/session_preferences.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/session_preferences.h" + +#include "atom/common/options_switches.h" +#include "base/command_line.h" +#include "base/memory/ptr_util.h" + +namespace atom { + +namespace { + +#if defined(OS_WIN) +const base::FilePath::CharType kPathDelimiter = FILE_PATH_LITERAL(';'); +#else +const base::FilePath::CharType kPathDelimiter = FILE_PATH_LITERAL(':'); +#endif + +} // namespace + +// static +int SessionPreferences::kLocatorKey = 0; + +SessionPreferences::SessionPreferences(content::BrowserContext* context) { + context->SetUserData(&kLocatorKey, base::WrapUnique(this)); +} + +SessionPreferences::~SessionPreferences() { +} + +// static +SessionPreferences* SessionPreferences::FromBrowserContext( + content::BrowserContext* context) { + return static_cast(context->GetUserData(&kLocatorKey)); +} + +// static +void SessionPreferences::AppendExtraCommandLineSwitches( + content::BrowserContext* context, base::CommandLine* command_line) { + SessionPreferences* self = FromBrowserContext(context); + if (!self) + return; + + base::FilePath::StringType preloads; + for (const auto& preload : self->preloads()) { + if (!base::FilePath(preload).IsAbsolute()) { + LOG(ERROR) << "preload script must have absolute path: " << preload; + continue; + } + if (preloads.empty()) + preloads = preload; + else + preloads += kPathDelimiter + preload; + } + if (!preloads.empty()) + command_line->AppendSwitchNative(switches::kPreloadScripts, preloads); +} + +} // namespace atom diff --git a/atom/browser/session_preferences.h b/atom/browser/session_preferences.h new file mode 100644 index 00000000000..ab0ba470bbf --- /dev/null +++ b/atom/browser/session_preferences.h @@ -0,0 +1,46 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_SESSION_PREFERENCES_H_ +#define ATOM_BROWSER_SESSION_PREFERENCES_H_ + +#include + +#include "base/files/file_path.h" +#include "base/supports_user_data.h" +#include "content/public/browser/browser_context.h" + +namespace base { +class CommandLine; +} + +namespace atom { + +class SessionPreferences : public base::SupportsUserData::Data { + public: + static SessionPreferences* FromBrowserContext( + content::BrowserContext* context); + static void AppendExtraCommandLineSwitches( + content::BrowserContext* context, base::CommandLine* command_line); + + explicit SessionPreferences(content::BrowserContext* context); + ~SessionPreferences() override; + + void set_preloads(const std::vector& preloads) { + preloads_ = preloads; + } + const std::vector& preloads() const { + return preloads_; + } + + private: + // The user data key. + static int kLocatorKey; + + std::vector preloads_; +}; + +} // namespace atom + +#endif // ATOM_BROWSER_SESSION_PREFERENCES_H_ diff --git a/atom/browser/ui/atom_menu_model.cc b/atom/browser/ui/atom_menu_model.cc index 1ec6b3c09f5..835e8d3dc45 100644 --- a/atom/browser/ui/atom_menu_model.cc +++ b/atom/browser/ui/atom_menu_model.cc @@ -42,8 +42,16 @@ bool AtomMenuModel::GetAcceleratorAtWithParams( void AtomMenuModel::MenuWillClose() { ui::SimpleMenuModel::MenuWillClose(); - for (Observer& observer : observers_) - observer.MenuWillClose(); + for (Observer& observer : observers_) { + observer.OnMenuWillClose(); + } +} + +void AtomMenuModel::MenuWillShow() { + ui::SimpleMenuModel::MenuWillShow(); + for (Observer& observer : observers_) { + observer.OnMenuWillShow(); + } } AtomMenuModel* AtomMenuModel::GetSubmenuModelAt(int index) { diff --git a/atom/browser/ui/atom_menu_model.h b/atom/browser/ui/atom_menu_model.h index 7bf734b74c3..f20cf2586dd 100644 --- a/atom/browser/ui/atom_menu_model.h +++ b/atom/browser/ui/atom_menu_model.h @@ -36,8 +36,11 @@ class AtomMenuModel : public ui::SimpleMenuModel { public: virtual ~Observer() {} + // Notifies the menu will open. + virtual void OnMenuWillShow() {} + // Notifies the menu has been closed. - virtual void MenuWillClose() {} + virtual void OnMenuWillClose() {} }; explicit AtomMenuModel(Delegate* delegate); @@ -54,6 +57,7 @@ class AtomMenuModel : public ui::SimpleMenuModel { // ui::SimpleMenuModel: void MenuWillClose() override; + void MenuWillShow() override; using SimpleMenuModel::GetSubmenuModelAt; AtomMenuModel* GetSubmenuModelAt(int index); diff --git a/atom/browser/ui/cocoa/atom_menu_controller.mm b/atom/browser/ui/cocoa/atom_menu_controller.mm index acb6abb4b75..a117a1f4fa4 100644 --- a/atom/browser/ui/cocoa/atom_menu_controller.mm +++ b/atom/browser/ui/cocoa/atom_menu_controller.mm @@ -9,10 +9,10 @@ #include "base/logging.h" #include "base/strings/sys_string_conversions.h" #include "base/strings/utf_string_conversions.h" +#include "content/public/browser/browser_thread.h" #include "ui/base/accelerators/accelerator.h" #include "ui/base/accelerators/platform_accelerator_cocoa.h" #include "ui/base/l10n/l10n_util_mac.h" -#include "content/public/browser/browser_thread.h" #include "ui/events/cocoa/cocoa_event_utils.h" #include "ui/gfx/image/image.h" @@ -44,7 +44,10 @@ Role kRolesMap[] = { { @selector(performClose:), "close" }, { @selector(performZoom:), "zoom" }, { @selector(terminate:), "quit" }, - { @selector(toggleFullScreen:), "togglefullscreen" }, + // โ†“ is intentionally not `toggleFullScreen`. The macOS full screen menu item behaves weird. + // If we use `toggleFullScreen`, then the menu item will use the default label, and not take + // the one provided. + { @selector(toggleFullScreenMode:), "togglefullscreen" }, { @selector(toggleTabBar:), "toggletabbar" }, { @selector(selectNextTab:), "selectnexttab" }, { @selector(selectPreviousTab:), "selectprevioustab" }, @@ -118,8 +121,9 @@ static base::scoped_nsobject recentDocumentsMenuSwap_; - (void)cancel { if (isMenuOpen_) { [menu_ cancelTracking]; - model_->MenuWillClose(); isMenuOpen_ = NO; + model_->MenuWillClose(); + closeCallback.Run(); } } @@ -177,6 +181,8 @@ static base::scoped_nsobject recentDocumentsMenuSwap_; // Repopulate with items from the submenu to be replaced [self moveMenuItems:recentDocumentsMenuSwap_ to:recentDocumentsMenu]; + // Update the submenu's title + [recentDocumentsMenu setTitle:[recentDocumentsMenuSwap_ title]]; // Replace submenu [item setSubmenu:recentDocumentsMenu]; @@ -332,11 +338,11 @@ static base::scoped_nsobject recentDocumentsMenuSwap_; if (isMenuOpen_) { isMenuOpen_ = NO; model_->MenuWillClose(); - - // Post async task so that itemSelected runs before the close callback - // deletes the controller from the map which deallocates it - if (!closeCallback.is_null()) + // Post async task so that itemSelected runs before the close callback + // deletes the controller from the map which deallocates it + if (!closeCallback.is_null()) { BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, closeCallback); + } } } diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 927c8d1572b..d5c9a0f8110 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -135,6 +135,8 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; [self updateSegmentedControl:(NSCustomTouchBarItem*)item withSettings:settings]; } else if (item_type == "scrubber") { [self updateScrubber:(NSCustomTouchBarItem*)item withSettings:settings]; + } else if (item_type == "group") { + [self updateGroup:(NSGroupTouchBarItem*)item withSettings:settings]; } } @@ -169,18 +171,31 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; auto identifier = [self identifierFromID:item_id type:item_type]; if (!identifier) return; - std::vector popover_ids; - settings.Get("_popover", &popover_ids); - for (auto& popover_id : popover_ids) { - auto popoverIdentifier = [self identifierFromID:popover_id type:"popover"]; - if (!popoverIdentifier) continue; + std::vector parents; + settings.Get("_parents", &parents); + for (auto& parent : parents) { + std::string parent_type; + std::string parent_id; + if (!parent.Get("type", &parent_type) || !parent.Get("id", &parent_id)) + continue; + auto parentIdentifier = [self identifierFromID:parent_id type:parent_type]; + if (!parentIdentifier) continue; - NSPopoverTouchBarItem* popoverItem = - [touchBar itemForIdentifier:popoverIdentifier]; - [self refreshTouchBarItem:popoverItem.popoverTouchBar - id:identifier - withType:item_type - withSettings:settings]; + if (parent_type == "popover") { + NSPopoverTouchBarItem* popoverItem = + [touchBar itemForIdentifier:parentIdentifier]; + [self refreshTouchBarItem:popoverItem.popoverTouchBar + id:identifier + withType:item_type + withSettings:settings]; + } else if (parent_type == "group") { + NSGroupTouchBarItem* groupItem = + [touchBar itemForIdentifier:parentIdentifier]; + [self refreshTouchBarItem:groupItem.groupTouchBar + id:identifier + withType:item_type + withSettings:settings]; + } } [self refreshTouchBarItem:touchBar @@ -478,6 +493,17 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; items:generatedItems]; } +- (void)updateGroup:(NSGroupTouchBarItem*)item + withSettings:(const mate::PersistentDictionary&)settings { + + mate::PersistentDictionary child; + if (!settings.Get("child", &child)) return; + std::vector items; + if (!child.Get("ordereredItems", &items)) return; + + item.groupTouchBar = [self touchBarFromItemIdentifiers:[self identifiersFromSettings:items]]; +} + - (NSTouchBarItem*)makeSegmentedControlForID:(NSString*)id withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); diff --git a/atom/browser/ui/file_dialog.h b/atom/browser/ui/file_dialog.h index 80b96162bac..7363c0afdea 100644 --- a/atom/browser/ui/file_dialog.h +++ b/atom/browser/ui/file_dialog.h @@ -33,11 +33,25 @@ enum FileDialogProperty { FILE_DIALOG_TREAT_PACKAGE_APP_AS_DIRECTORY = 1 << 7, }; -typedef base::Callback& paths)> OpenDialogCallback; +#if defined(MAS_BUILD) + typedef base::Callback& paths, + const std::vector& bookmarkData)> OpenDialogCallback; -typedef base::Callback SaveDialogCallback; + typedef base::Callback SaveDialogCallback; +#else + typedef base::Callback& paths)> OpenDialogCallback; + + typedef base::Callback SaveDialogCallback; +#endif struct DialogSettings { atom::NativeWindow* parent_window = nullptr; @@ -50,6 +64,7 @@ struct DialogSettings { int properties = 0; bool shows_tag_field = true; bool force_detached = false; + bool security_scoped_bookmarks = false; }; bool ShowOpenDialog(const DialogSettings& settings, diff --git a/atom/browser/ui/file_dialog_gtk.cc b/atom/browser/ui/file_dialog_gtk.cc index 44a9017ab74..918e02f2d9f 100644 --- a/atom/browser/ui/file_dialog_gtk.cc +++ b/atom/browser/ui/file_dialog_gtk.cc @@ -4,6 +4,8 @@ #include "atom/browser/ui/file_dialog.h" +#include // _() macro + #include "atom/browser/native_window_views.h" #include "atom/browser/unresponsive_suppressor.h" #include "base/callback.h" @@ -23,9 +25,8 @@ gboolean FileFilterCaseInsensitive(const GtkFileFilterInfo* file_info, // Makes .* file extension matches all file types. if (*file_extension == ".*") return true; - return base::EndsWith( - file_info->filename, - *file_extension, base::CompareCase::INSENSITIVE_ASCII); + return base::EndsWith(file_info->filename, *file_extension, + base::CompareCase::INSENSITIVE_ASCII); } // Deletes |data| when gtk_file_filter_add_custom() is done with it. @@ -35,26 +36,21 @@ void OnFileFilterDataDestroyed(std::string* file_extension) { class FileChooserDialog { public: - FileChooserDialog(GtkFileChooserAction action, - const DialogSettings& settings) + FileChooserDialog(GtkFileChooserAction action, const DialogSettings& settings) : parent_(static_cast(settings.parent_window)), filters_(settings.filters) { - const char* confirm_text = GTK_STOCK_OK; + const char* confirm_text = _("_OK"); if (!settings.button_label.empty()) confirm_text = settings.button_label.c_str(); else if (action == GTK_FILE_CHOOSER_ACTION_SAVE) - confirm_text = GTK_STOCK_SAVE; + confirm_text = _("_Save"); else if (action == GTK_FILE_CHOOSER_ACTION_OPEN) - confirm_text = GTK_STOCK_OPEN; + confirm_text = _("_Open"); dialog_ = gtk_file_chooser_dialog_new( - settings.title.c_str(), - NULL, - action, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - confirm_text, GTK_RESPONSE_ACCEPT, - NULL); + settings.title.c_str(), NULL, action, _("_Cancel"), GTK_RESPONSE_CANCEL, + confirm_text, GTK_RESPONSE_ACCEPT, NULL); if (parent_) { parent_->SetEnabled(false); libgtkui::SetGtkTransientForAura(dialog_, parent_->GetNativeWindow()); @@ -69,8 +65,8 @@ class FileChooserDialog { if (!settings.default_path.empty()) { if (base::DirectoryExists(settings.default_path)) { - gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog_), - settings.default_path.value().c_str()); + gtk_file_chooser_set_current_folder( + GTK_FILE_CHOOSER(dialog_), settings.default_path.value().c_str()); } else { if (settings.default_path.IsAbsolute()) { gtk_file_chooser_set_current_folder( @@ -78,7 +74,8 @@ class FileChooserDialog { settings.default_path.DirName().value().c_str()); } - gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog_), + gtk_file_chooser_set_current_name( + GTK_FILE_CHOOSER(dialog_), settings.default_path.BaseName().value().c_str()); } } @@ -103,8 +100,8 @@ class FileChooserDialog { void RunAsynchronous() { g_signal_connect(dialog_, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); - g_signal_connect(dialog_, "response", - G_CALLBACK(OnFileDialogResponseThunk), this); + g_signal_connect(dialog_, "response", G_CALLBACK(OnFileDialogResponseThunk), + this); gtk_widget_show_all(dialog_); // We need to call gtk_window_present after making the widgets visible to @@ -132,11 +129,11 @@ class FileChooserDialog { std::vector GetFileNames() const { std::vector paths; - GSList* filenames = gtk_file_chooser_get_filenames( - GTK_FILE_CHOOSER(dialog_)); + GSList* filenames = + gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog_)); for (GSList* iter = filenames; iter != NULL; iter = g_slist_next(iter)) { - base::FilePath path = AddExtensionForFilename( - static_cast(iter->data)); + base::FilePath path = + AddExtensionForFilename(static_cast(iter->data)); g_free(iter->data); paths.push_back(path); } @@ -190,8 +187,7 @@ void FileChooserDialog::AddFilters(const Filters& filters) { std::unique_ptr file_extension( new std::string("." + filter.second[j])); gtk_file_filter_add_custom( - gtk_filter, - GTK_FILE_FILTER_FILENAME, + gtk_filter, GTK_FILE_FILTER_FILENAME, reinterpret_cast(FileFilterCaseInsensitive), file_extension.release(), reinterpret_cast(OnFileFilterDataDestroyed)); @@ -227,7 +223,6 @@ base::FilePath FileChooserDialog::AddExtensionForFilename( return path.ReplaceExtension(extensions[0]); } - } // namespace bool ShowOpenDialog(const DialogSettings& settings, @@ -258,8 +253,7 @@ void ShowOpenDialog(const DialogSettings& settings, open_dialog->RunOpenAsynchronous(callback); } -bool ShowSaveDialog(const DialogSettings& settings, - base::FilePath* path) { +bool ShowSaveDialog(const DialogSettings& settings, base::FilePath* path) { FileChooserDialog save_dialog(GTK_FILE_CHOOSER_ACTION_SAVE, settings); gtk_widget_show_all(save_dialog.dialog()); int response = gtk_dialog_run(GTK_DIALOG(save_dialog.dialog())); @@ -273,8 +267,8 @@ bool ShowSaveDialog(const DialogSettings& settings, void ShowSaveDialog(const DialogSettings& settings, const SaveDialogCallback& callback) { - FileChooserDialog* save_dialog = new FileChooserDialog( - GTK_FILE_CHOOSER_ACTION_SAVE, settings); + FileChooserDialog* save_dialog = + new FileChooserDialog(GTK_FILE_CHOOSER_ACTION_SAVE, settings); save_dialog->RunSaveAsynchronous(callback); } diff --git a/atom/browser/ui/file_dialog_mac.mm b/atom/browser/ui/file_dialog_mac.mm index 276f782ce0d..b26d472db13 100644 --- a/atom/browser/ui/file_dialog_mac.mm +++ b/atom/browser/ui/file_dialog_mac.mm @@ -14,10 +14,42 @@ #include "base/mac/scoped_cftyperef.h" #include "base/strings/sys_string_conversions.h" +@interface PopUpButtonHandler : NSObject +@property (nonatomic, strong) NSSavePanel *savePanel; +@property (nonatomic, strong) NSArray *fileTypes; +- (instancetype)initWithPanel:(NSSavePanel *)panel andTypes:(NSArray *)types; +- (void)selectFormat:(id)sender; +@end + +@implementation PopUpButtonHandler +- (instancetype)initWithPanel:(NSSavePanel *)panel andTypes:(NSArray *)types { + self = [super init]; + if (self) { + _savePanel = panel; + _fileTypes = types; + } + return self; +} + +- (void)selectFormat:(id)sender { + NSPopUpButton *button = (NSPopUpButton *)sender; + NSInteger selectedItemIndex = [button indexOfSelectedItem]; + NSString *nameFieldString = [[self savePanel] nameFieldStringValue]; + NSString *trimmedNameFieldString = [nameFieldString stringByDeletingPathExtension]; + NSString *extension = [[self fileTypes] objectAtIndex: selectedItemIndex]; + + NSString *nameFieldStringWithExt = [NSString stringWithFormat:@"%@.%@", trimmedNameFieldString, extension]; + [[self savePanel] setNameFieldStringValue:nameFieldStringWithExt]; + [[self savePanel] setAllowedFileTypes:@[extension]]; +} +@end + namespace file_dialog { namespace { +static PopUpButtonHandler *popUpButtonHandler; + void SetAllowedFileTypes(NSSavePanel* dialog, const Filters& filters) { NSMutableSet* file_type_set = [NSMutableSet set]; for (size_t i = 0; i < filters.size(); ++i) { @@ -25,6 +57,7 @@ void SetAllowedFileTypes(NSSavePanel* dialog, const Filters& filters) { for (size_t j = 0; j < filter.second.size(); ++j) { // If we meet a '*' file extension, we allow all the file types and no // need to set the specified file types. + if (filter.second[j] == "*") { [dialog setAllowsOtherFileTypes:YES]; return; @@ -41,6 +74,29 @@ void SetAllowedFileTypes(NSSavePanel* dialog, const Filters& filters) { file_types = [file_type_set allObjects]; [dialog setAllowedFileTypes:file_types]; + + if (!popUpButtonHandler) + popUpButtonHandler = [[PopUpButtonHandler alloc] initWithPanel:dialog andTypes:file_types]; + + // add file format picker + NSView *accessoryView = [[NSView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 200, 32.0)]; + NSTextField *label = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 60, 22)]; + + [label setEditable:NO]; + [label setStringValue:@"Format:"]; + [label setBordered:NO]; + [label setBezeled:NO]; + [label setDrawsBackground:NO]; + + NSPopUpButton *popupButton = [[NSPopUpButton alloc] initWithFrame:NSMakeRect(50.0, 2, 140, 22.0) pullsDown:NO]; + [popupButton addItemsWithTitles:file_types]; + [popupButton setTarget:popUpButtonHandler]; + [popupButton setAction:@selector(selectFormat:)]; + + [accessoryView addSubview:label]; + [accessoryView addSubview:popupButton]; + + [dialog setAccessoryView:accessoryView]; } void SetupDialog(NSSavePanel* dialog, @@ -129,11 +185,44 @@ int RunModalDialog(NSSavePanel* dialog, const DialogSettings& settings) { return chosen; } -void ReadDialogPaths(NSOpenPanel* dialog, std::vector* paths) { +// Create bookmark data and serialise it into a base64 string. +std::string GetBookmarkDataFromNSURL(NSURL* url) { + // Create the file if it doesn't exist (necessary for NSSavePanel options). + NSFileManager *defaultManager = [NSFileManager defaultManager]; + if (![defaultManager fileExistsAtPath: [url path]]) { + [defaultManager createFileAtPath: [url path] contents: nil attributes: nil]; + } + + NSError *error = nil; + NSData *bookmarkData = [url bookmarkDataWithOptions: NSURLBookmarkCreationWithSecurityScope + includingResourceValuesForKeys: nil + relativeToURL: nil + error: &error]; + if (error != nil) { + // Send back an empty string if there was an error. + return ""; + } else { + // Encode NSData in base64 then convert to NSString. + NSString *base64data = [[NSString alloc] initWithData: [bookmarkData base64EncodedDataWithOptions: 0] + encoding: NSUTF8StringEncoding]; + return base::SysNSStringToUTF8(base64data); + } +} + +void ReadDialogPathsWithBookmarks(NSOpenPanel* dialog, + std::vector* paths, + std::vector* bookmarks) { NSArray* urls = [dialog URLs]; for (NSURL* url in urls) - if ([url isFileURL]) + if ([url isFileURL]) { paths->push_back(base::FilePath(base::SysNSStringToUTF8([url path]))); + bookmarks->push_back(GetBookmarkDataFromNSURL(url)); + } +} + +void ReadDialogPaths(NSOpenPanel* dialog, std::vector* paths) { + std::vector ignored_bookmarks; + ReadDialogPathsWithBookmarks(dialog, paths, &ignored_bookmarks); } } // namespace @@ -154,6 +243,33 @@ bool ShowOpenDialog(const DialogSettings& settings, return true; } +void OpenDialogCompletion(int chosen, NSOpenPanel* dialog, + const DialogSettings& settings, + const OpenDialogCallback& callback) { + if (chosen == NSFileHandlingPanelCancelButton) { + #if defined(MAS_BUILD) + callback.Run(false, std::vector(), + std::vector()); + #else + callback.Run(false, std::vector()); + #endif + } else { + std::vector paths; + #if defined(MAS_BUILD) + std::vector bookmarks; + if (settings.security_scoped_bookmarks) { + ReadDialogPathsWithBookmarks(dialog, &paths, &bookmarks); + } else { + ReadDialogPaths(dialog, &paths); + } + callback.Run(true, paths, bookmarks); + #else + ReadDialogPaths(dialog, &paths); + callback.Run(true, paths); + #endif + } +} + void ShowOpenDialog(const DialogSettings& settings, const OpenDialogCallback& c) { NSOpenPanel* dialog = [NSOpenPanel openPanel]; @@ -168,24 +284,12 @@ void ShowOpenDialog(const DialogSettings& settings, if (!settings.parent_window || !settings.parent_window->GetNativeWindow() || settings.force_detached) { int chosen = [dialog runModal]; - if (chosen == NSFileHandlingPanelCancelButton) { - callback.Run(false, std::vector()); - } else { - std::vector paths; - ReadDialogPaths(dialog, &paths); - callback.Run(true, paths); - } + OpenDialogCompletion(chosen, dialog, settings, callback); } else { NSWindow* window = settings.parent_window->GetNativeWindow(); [dialog beginSheetModalForWindow:window completionHandler:^(NSInteger chosen) { - if (chosen == NSFileHandlingPanelCancelButton) { - callback.Run(false, std::vector()); - } else { - std::vector paths; - ReadDialogPaths(dialog, &paths); - callback.Run(true, paths); - } + OpenDialogCompletion(chosen, dialog, settings, callback); }]; } } @@ -205,6 +309,29 @@ bool ShowSaveDialog(const DialogSettings& settings, return true; } +void SaveDialogCompletion(int chosen, NSSavePanel* dialog, + const DialogSettings& settings, + const SaveDialogCallback& callback) { + if (chosen == NSFileHandlingPanelCancelButton) { + #if defined(MAS_BUILD) + callback.Run(false, base::FilePath(), ""); + #else + callback.Run(false, base::FilePath()); + #endif + } else { + std::string path = base::SysNSStringToUTF8([[dialog URL] path]); + #if defined(MAS_BUILD) + std::string bookmark; + if (settings.security_scoped_bookmarks) { + bookmark = GetBookmarkDataFromNSURL([dialog URL]); + } + callback.Run(true, base::FilePath(path), bookmark); + #else + callback.Run(true, base::FilePath(path)); + #endif + } +} + void ShowSaveDialog(const DialogSettings& settings, const SaveDialogCallback& c) { NSSavePanel* dialog = [NSSavePanel savePanel]; @@ -217,22 +344,12 @@ void ShowSaveDialog(const DialogSettings& settings, if (!settings.parent_window || !settings.parent_window->GetNativeWindow() || settings.force_detached) { int chosen = [dialog runModal]; - if (chosen == NSFileHandlingPanelCancelButton) { - callback.Run(false, base::FilePath()); - } else { - std::string path = base::SysNSStringToUTF8([[dialog URL] path]); - callback.Run(true, base::FilePath(path)); - } + SaveDialogCompletion(chosen, dialog, settings, callback); } else { NSWindow* window = settings.parent_window->GetNativeWindow(); [dialog beginSheetModalForWindow:window completionHandler:^(NSInteger chosen) { - if (chosen == NSFileHandlingPanelCancelButton) { - callback.Run(false, base::FilePath()); - } else { - std::string path = base::SysNSStringToUTF8([[dialog URL] path]); - callback.Run(true, base::FilePath(path)); - } + SaveDialogCompletion(chosen, dialog, settings, callback); }]; } } diff --git a/atom/browser/ui/message_box_gtk.cc b/atom/browser/ui/message_box_gtk.cc index 8c6c30f90f8..76fd5eb89b4 100644 --- a/atom/browser/ui/message_box_gtk.cc +++ b/atom/browser/ui/message_box_gtk.cc @@ -4,6 +4,8 @@ #include "atom/browser/ui/message_box.h" +#include + #include "atom/browser/browser.h" #include "atom/browser/native_window_observer.h" #include "atom/browser/native_window_views.h" @@ -16,11 +18,11 @@ #include "chrome/browser/ui/libgtkui/skia_utils_gtk.h" #include "ui/views/widget/desktop_aura/x11_desktop_handler.h" -#define ANSI_FOREGROUND_RED "\x1b[31m" +#define ANSI_FOREGROUND_RED "\x1b[31m" #define ANSI_FOREGROUND_BLACK "\x1b[30m" -#define ANSI_TEXT_BOLD "\x1b[1m" -#define ANSI_BACKGROUND_GRAY "\x1b[47m" -#define ANSI_RESET "\x1b[0m" +#define ANSI_TEXT_BOLD "\x1b[1m" +#define ANSI_BACKGROUND_GRAY "\x1b[47m" +#define ANSI_RESET "\x1b[0m" namespace atom { @@ -37,40 +39,23 @@ class GtkMessageBox : public NativeWindowObserver { const std::string& message, const std::string& detail, const std::string& checkbox_label, - bool checkbox_checked, - const gfx::ImageSkia& icon) + bool checkbox_checked) : cancel_id_(cancel_id), checkbox_checked_(false), parent_(static_cast(parent_window)) { // Create dialog. - dialog_ = gtk_message_dialog_new( - nullptr, // parent - static_cast(0), // no flags - GetMessageType(type), // type - GTK_BUTTONS_NONE, // no buttons - "%s", message.c_str()); + dialog_ = + gtk_message_dialog_new(nullptr, // parent + static_cast(0), // no flags + GetMessageType(type), // type + GTK_BUTTONS_NONE, // no buttons + "%s", message.c_str()); if (!detail.empty()) - gtk_message_dialog_format_secondary_text( - GTK_MESSAGE_DIALOG(dialog_), "%s", detail.c_str()); + gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog_), + "%s", detail.c_str()); if (!title.empty()) gtk_window_set_title(GTK_WINDOW(dialog_), title.c_str()); - // Set dialog's icon. - if (!icon.isNull()) { - GdkPixbuf* pixbuf = libgtkui::GdkPixbufFromSkBitmap(*icon.bitmap()); - 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); - } - if (!checkbox_label.empty()) { GtkWidget* message_area = gtk_message_dialog_get_message_area(GTK_MESSAGE_DIALOG(dialog_)); @@ -124,17 +109,16 @@ class GtkMessageBox : public NativeWindowObserver { } const char* TranslateToStock(int id, const std::string& text) { - std::string lower = base::ToLowerASCII(text); + const std::string lower = base::ToLowerASCII(text); if (lower == "cancel") - return GTK_STOCK_CANCEL; - else if (lower == "no") - return GTK_STOCK_NO; - else if (lower == "ok") - return GTK_STOCK_OK; - else if (lower == "yes") - return GTK_STOCK_YES; - else - return text.c_str(); + return _("_Cancel"); + if (lower == "no") + return _("_No"); + if (lower == "ok") + return _("_OK"); + if (lower == "yes") + return _("_Yes"); + return text.c_str(); } void Show() { @@ -158,8 +142,8 @@ class GtkMessageBox : public NativeWindowObserver { callback_ = callback; g_signal_connect(dialog_, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), nullptr); - g_signal_connect(dialog_, "response", - G_CALLBACK(OnResponseDialogThunk), this); + g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseDialogThunk), + this); Show(); } @@ -211,9 +195,9 @@ int ShowMessageBox(NativeWindow* parent, const std::string& title, const std::string& message, const std::string& detail, - const gfx::ImageSkia& icon) { + const gfx::ImageSkia& /*icon*/) { return GtkMessageBox(parent, type, buttons, default_id, cancel_id, title, - message, detail, "", false, icon) + message, detail, "", false) .RunSynchronous(); } @@ -228,10 +212,10 @@ void ShowMessageBox(NativeWindow* parent, const std::string& detail, const std::string& checkbox_label, bool checkbox_checked, - const gfx::ImageSkia& icon, + const gfx::ImageSkia& /*icon*/, const MessageBoxCallback& callback) { (new GtkMessageBox(parent, type, buttons, default_id, cancel_id, title, - message, detail, checkbox_label, checkbox_checked, icon)) + message, detail, checkbox_label, checkbox_checked)) ->RunAsynchronous(callback); } @@ -239,15 +223,11 @@ void ShowErrorBox(const base::string16& title, const base::string16& content) { if (Browser::Get()->is_ready()) { GtkMessageBox(nullptr, MESSAGE_BOX_TYPE_ERROR, {"OK"}, -1, 0, "Error", base::UTF16ToUTF8(title).c_str(), - base::UTF16ToUTF8(content).c_str(), "", false, - gfx::ImageSkia()) + base::UTF16ToUTF8(content).c_str(), "", false) .RunSynchronous(); } else { - fprintf(stderr, - ANSI_TEXT_BOLD ANSI_BACKGROUND_GRAY - ANSI_FOREGROUND_RED "%s\n" - ANSI_FOREGROUND_BLACK "%s" - ANSI_RESET "\n", + fprintf(stderr, ANSI_TEXT_BOLD ANSI_BACKGROUND_GRAY ANSI_FOREGROUND_RED + "%s\n" ANSI_FOREGROUND_BLACK "%s" ANSI_RESET "\n", base::UTF16ToUTF8(title).c_str(), base::UTF16ToUTF8(content).c_str()); } diff --git a/atom/browser/ui/message_box_mac.mm b/atom/browser/ui/message_box_mac.mm index 8f495f3bf7a..17c66043a27 100644 --- a/atom/browser/ui/message_box_mac.mm +++ b/atom/browser/ui/message_box_mac.mm @@ -188,7 +188,7 @@ void ShowMessageBox(NativeWindow* parent_window, if (!parent_window || !parent_window->GetNativeWindow() || parent_window->is_offscreen_dummy()) { int ret = [[alert autorelease] runModal]; - callback.Run(ret, false); + callback.Run(ret, alert.suppressionButton.state == NSOnState); } else { ModalDelegate* delegate = [[ModalDelegate alloc] initWithCallback:callback andAlert:alert diff --git a/atom/browser/ui/tray_icon_cocoa.h b/atom/browser/ui/tray_icon_cocoa.h index 7680b6f30f8..d0b124acab2 100644 --- a/atom/browser/ui/tray_icon_cocoa.h +++ b/atom/browser/ui/tray_icon_cocoa.h @@ -35,7 +35,7 @@ class TrayIconCocoa : public TrayIcon, protected: // AtomMenuModel::Observer: - void MenuWillClose() override; + void OnMenuWillClose() override; private: // Atom custom view for NSStatusItem. diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index 9c5e100a2a0..a358f676a8c 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -33,6 +33,7 @@ const CGFloat kVerticalTitleMargin = 2; base::scoped_nsobject title_; base::scoped_nsobject attributedTitle_; base::scoped_nsobject statusItem_; + base::scoped_nsobject trackingArea_; } @end // @interface StatusItemView @@ -61,12 +62,12 @@ const CGFloat kVerticalTitleMargin = 2; [self updateDimensions]; // Add NSTrackingArea for listening to mouseEnter, mouseExit, and mouseMove events - auto trackingArea = [[[NSTrackingArea alloc] + trackingArea_.reset([[NSTrackingArea alloc] initWithRect:[self bounds] options:NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways owner:self - userInfo:nil] autorelease]; - [self addTrackingArea:trackingArea]; + userInfo:nil]); + [self addTrackingArea:trackingArea_]; } return self; } @@ -78,6 +79,11 @@ const CGFloat kVerticalTitleMargin = 2; } - (void)removeItem { + // Turn off tracking events to prevent crash + if (trackingArea_) { + [self removeTrackingArea:trackingArea_]; + trackingArea_.reset(); + } [[NSStatusBar systemStatusBar] removeStatusItem:statusItem_]; statusItem_.reset(); } @@ -455,7 +461,7 @@ gfx::Rect TrayIconCocoa::GetBounds() { return bounds; } -void TrayIconCocoa::MenuWillClose() { +void TrayIconCocoa::OnMenuWillClose() { [status_item_view_ setNeedsDisplay:YES]; } diff --git a/atom/browser/ui/views/menu_bar.cc b/atom/browser/ui/views/menu_bar.cc index 329dec29562..bf9801a1457 100644 --- a/atom/browser/ui/views/menu_bar.cc +++ b/atom/browser/ui/views/menu_bar.cc @@ -24,48 +24,66 @@ namespace atom { namespace { +#if defined(USE_X11) + +SkColor GdkRgbaToSkColor(const GdkRGBA& rgba) { + return SkColorSetARGB(rgba.alpha * 255, rgba.red * 255, rgba.green * 255, + rgba.blue * 255); +} + +SkColor GetStyleContextFgColor(GtkStyleContext* style_context, + GtkStateFlags state) { + GdkRGBA rgba; + gtk_style_context_get_color(style_context, state, &rgba); + return GdkRgbaToSkColor(rgba); +} + +SkColor GetStyleContextBgColor(GtkStyleContext* style_context, + GtkStateFlags state) { + GdkRGBA rgba; + gtk_style_context_get_background_color(style_context, state, &rgba); + return GdkRgbaToSkColor(rgba); +} + +void GetMenuBarColor(SkColor* enabled, + SkColor* disabled, + SkColor* highlight, + SkColor* hover, + SkColor* background) { + GtkWidget* menu_bar = gtk_menu_bar_new(); + GtkStyleContext* sc = gtk_widget_get_style_context(menu_bar); + *enabled = GetStyleContextFgColor(sc, GTK_STATE_FLAG_NORMAL); + *disabled = GetStyleContextFgColor(sc, GTK_STATE_FLAG_INSENSITIVE); + *highlight = GetStyleContextFgColor(sc, GTK_STATE_FLAG_SELECTED); + *hover = GetStyleContextFgColor(sc, GTK_STATE_FLAG_PRELIGHT); + *background = GetStyleContextBgColor(sc, GTK_STATE_FLAG_NORMAL); + gtk_widget_destroy(GTK_WIDGET(menu_bar)); +} + +#endif // USE_X11 + const char kViewClassName[] = "ElectronMenuBar"; // Default color of the menu bar. const SkColor kDefaultColor = SkColorSetARGB(255, 233, 233, 233); -#if defined(USE_X11) -void GetMenuBarColor(SkColor* enabled, SkColor* disabled, SkColor* highlight, - SkColor* hover, SkColor* background) { - GtkWidget* menu_bar = gtk_menu_bar_new(); - - GtkStyle* style = gtk_rc_get_style(menu_bar); - *enabled = libgtkui::GdkColorToSkColor(style->fg[GTK_STATE_NORMAL]); - *disabled = libgtkui::GdkColorToSkColor(style->fg[GTK_STATE_INSENSITIVE]); - *highlight = libgtkui::GdkColorToSkColor(style->fg[GTK_STATE_SELECTED]); - *hover = libgtkui::GdkColorToSkColor(style->fg[GTK_STATE_PRELIGHT]); - *background = libgtkui::GdkColorToSkColor(style->bg[GTK_STATE_NORMAL]); - - gtk_widget_destroy(menu_bar); -} -#endif - } // namespace MenuBar::MenuBar(NativeWindow* window) - : background_color_(kDefaultColor), - menu_model_(NULL), - window_(window) { + : background_color_(kDefaultColor), menu_model_(NULL), window_(window) { UpdateMenuBarColor(); SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal)); } -MenuBar::~MenuBar() { -} +MenuBar::~MenuBar() {} void MenuBar::SetMenu(AtomMenuModel* model) { menu_model_ = model; RemoveAllChildViews(true); for (int i = 0; i < model->GetItemCount(); ++i) { - SubmenuButton* button = new SubmenuButton(model->GetLabelAt(i), - this, - background_color_); + SubmenuButton* button = + new SubmenuButton(model->GetLabelAt(i), this, background_color_); button->set_tag(i); #if defined(USE_X11) diff --git a/atom/browser/ui/views/submenu_button.cc b/atom/browser/ui/views/submenu_button.cc index 3c3cff3c654..5ae2aae1ce8 100644 --- a/atom/browser/ui/views/submenu_button.cc +++ b/atom/browser/ui/views/submenu_button.cc @@ -60,7 +60,7 @@ std::unique_ptr SubmenuButton::CreateInkDropRipple() std::unique_ptr SubmenuButton::CreateInkDrop() { std::unique_ptr ink_drop = - CustomButton::CreateDefaultInkDropImpl(); + views::Button::CreateDefaultInkDropImpl(); ink_drop->SetShowHighlightOnHover(false); return std::move(ink_drop); } diff --git a/atom/browser/ui/webui/pdf_viewer_handler.cc b/atom/browser/ui/webui/pdf_viewer_handler.cc index cc51d2d92df..e4ba5be3bab 100644 --- a/atom/browser/ui/webui/pdf_viewer_handler.cc +++ b/atom/browser/ui/webui/pdf_viewer_handler.cc @@ -8,6 +8,7 @@ #include "base/bind.h" #include "base/memory/ptr_util.h" #include "base/values.h" +#include "chrome/browser/browser_process.h" #include "content/public/browser/stream_handle.h" #include "content/public/browser/stream_info.h" #include "content/public/browser/web_contents.h" @@ -193,7 +194,7 @@ void PdfViewerHandler::GetStrings(const base::ListValue* args) { SET_STRING("tooltipZoomOut", "Zoom out"); #undef SET_STRING - webui::SetLoadTimeDataDefaults(l10n_util::GetApplicationLocale(""), + webui::SetLoadTimeDataDefaults(g_browser_process->GetApplicationLocale(), result.get()); ResolveJavascriptCallback(*callback_id, *result); } diff --git a/atom/browser/ui/webui/pdf_viewer_ui.cc b/atom/browser/ui/webui/pdf_viewer_ui.cc index 44013b25566..be154aeddfb 100644 --- a/atom/browser/ui/webui/pdf_viewer_ui.cc +++ b/atom/browser/ui/webui/pdf_viewer_ui.cc @@ -9,9 +9,9 @@ #include "atom/browser/atom_browser_context.h" #include "atom/browser/loader/layered_resource_handler.h" #include "atom/browser/ui/webui/pdf_viewer_handler.h" +#include "atom/common/api/api_messages.h" #include "atom/common/atom_constants.h" #include "base/sequenced_task_runner_helpers.h" -#include "components/pdf/common/pdf_messages.h" #include "content/browser/loader/resource_dispatcher_host_impl.h" #include "content/browser/loader/resource_request_info_impl.h" #include "content/browser/loader/stream_resource_handler.h" @@ -78,11 +78,13 @@ class BundledDataSource : public content::URLDataSource { } std::string GetMimeType(const std::string& path) const override { - std::string filename = PathWithoutParams(path); + base::FilePath::StringType ext = + base::FilePath::FromUTF8Unsafe(PathWithoutParams(path)).Extension(); std::string mime_type; - net::GetMimeTypeFromFile( - base::FilePath::FromUTF8Unsafe(filename), &mime_type); - return mime_type; + if (!ext.empty() && + net::GetWellKnownMimeTypeFromExtension(ext.substr(1), &mime_type)) + return mime_type; + return "text/html"; } bool ShouldAddContentSecurityPolicy() const override { return false; } @@ -215,7 +217,7 @@ bool PdfViewerUI::OnMessageReceived( content::RenderFrameHost* render_frame_host) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(PdfViewerUI, message) - IPC_MESSAGE_HANDLER(PDFHostMsg_PDFSaveURLAs, OnSaveURLAs) + IPC_MESSAGE_HANDLER(AtomFrameHostMsg_PDFSaveURLAs, OnSaveURLAs) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; diff --git a/atom/browser/ui/win/atom_desktop_native_widget_aura.cc b/atom/browser/ui/win/atom_desktop_native_widget_aura.cc index e0cd68608a3..f9f0d1e8610 100644 --- a/atom/browser/ui/win/atom_desktop_native_widget_aura.cc +++ b/atom/browser/ui/win/atom_desktop_native_widget_aura.cc @@ -3,12 +3,16 @@ // found in the LICENSE file. #include "atom/browser/ui/win/atom_desktop_native_widget_aura.h" +#include "ui/views/corewm/tooltip_controller.h" +#include "ui/wm/public/tooltip_client.h" namespace atom { AtomDesktopNativeWidgetAura::AtomDesktopNativeWidgetAura( views::internal::NativeWidgetDelegate* delegate) : views::DesktopNativeWidgetAura(delegate) { + // This is to enable the override of OnWindowActivated + wm::SetActivationChangeObserver(GetNativeWindow(), this); } void AtomDesktopNativeWidgetAura::Activate() { @@ -19,4 +23,23 @@ void AtomDesktopNativeWidgetAura::Activate() { views::DesktopNativeWidgetAura::Activate(); } +void AtomDesktopNativeWidgetAura::OnWindowActivated( + wm::ActivationChangeObserver::ActivationReason reason, + aura::Window* gained_active, + aura::Window* lost_active) { + views::DesktopNativeWidgetAura::OnWindowActivated( + reason, gained_active, lost_active); + if (lost_active != nullptr) { + auto* tooltip_controller = static_cast( + wm::GetTooltipClient(lost_active->GetRootWindow())); + + // This will cause the tooltip to be hidden when a window is deactivated, + // as it should be. + // TODO(brenca): Remove this fix when the chromium issue is fixed. + // crbug.com/724538 + if (tooltip_controller != nullptr) + tooltip_controller->OnCancelMode(nullptr); + } +} + } // namespace atom diff --git a/atom/browser/ui/win/atom_desktop_native_widget_aura.h b/atom/browser/ui/win/atom_desktop_native_widget_aura.h index b5a6c0933d5..8aca097d9f7 100644 --- a/atom/browser/ui/win/atom_desktop_native_widget_aura.h +++ b/atom/browser/ui/win/atom_desktop_native_widget_aura.h @@ -19,6 +19,10 @@ class AtomDesktopNativeWidgetAura : public views::DesktopNativeWidgetAura { void Activate() override; private: + void OnWindowActivated( + wm::ActivationChangeObserver::ActivationReason reason, + aura::Window* gained_active, + aura::Window* lost_active) override; DISALLOW_COPY_AND_ASSIGN(AtomDesktopNativeWidgetAura); }; diff --git a/atom/browser/ui/x/x_window_utils.cc b/atom/browser/ui/x/x_window_utils.cc index 8f5e0777082..275c7858924 100644 --- a/atom/browser/ui/x/x_window_utils.cc +++ b/atom/browser/ui/x/x_window_utils.cc @@ -8,6 +8,7 @@ #include "base/environment.h" #include "base/strings/string_util.h" +#include "base/threading/thread_restrictions.h" #include "dbus/bus.h" #include "dbus/message.h" #include "dbus/object_proxy.h" @@ -51,6 +52,7 @@ void SetWindowType(::Window xwindow, const std::string& type) { } bool ShouldUseGlobalMenuBar() { + base::ThreadRestrictions::ScopedAllowIO allow_io; std::unique_ptr env(base::Environment::Create()); if (env->HasVar("ELECTRON_FORCE_WINDOW_MENU_BAR")) return false; diff --git a/atom/browser/web_contents_permission_helper.cc b/atom/browser/web_contents_permission_helper.cc index ec9e4ad6e94..2d85009c79a 100644 --- a/atom/browser/web_contents_permission_helper.cc +++ b/atom/browser/web_contents_permission_helper.cc @@ -7,6 +7,7 @@ #include #include "atom/browser/atom_permission_manager.h" +#include "atom/common/native_mate_converters/gurl_converter.h" #include "brightray/browser/media/media_stream_devices_controller.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/render_process_host.h" @@ -54,13 +55,14 @@ WebContentsPermissionHelper::~WebContentsPermissionHelper() { void WebContentsPermissionHelper::RequestPermission( content::PermissionType permission, const base::Callback& callback, - bool user_gesture) { + bool user_gesture, + const base::DictionaryValue* details) { auto rfh = web_contents_->GetMainFrame(); auto permission_manager = static_cast( web_contents_->GetBrowserContext()->GetPermissionManager()); auto origin = web_contents_->GetLastCommittedURL(); - permission_manager->RequestPermission( - permission, rfh, origin, false, + permission_manager->RequestPermissionWithDetails( + permission, rfh, origin, false, details, base::Bind(&OnPermissionResponse, callback)); } @@ -94,10 +96,13 @@ void WebContentsPermissionHelper::RequestPointerLockPermission( void WebContentsPermissionHelper::RequestOpenExternalPermission( const base::Callback& callback, - bool user_gesture) { + bool user_gesture, + const GURL& url) { + base::DictionaryValue details; + details.SetString("externalURL", url.spec()); RequestPermission( static_cast(PermissionType::OPEN_EXTERNAL), - callback, user_gesture); + callback, user_gesture, &details); } } // namespace atom diff --git a/atom/browser/web_contents_permission_helper.h b/atom/browser/web_contents_permission_helper.h index 89da64b7583..02879c68e36 100644 --- a/atom/browser/web_contents_permission_helper.h +++ b/atom/browser/web_contents_permission_helper.h @@ -33,7 +33,8 @@ class WebContentsPermissionHelper void RequestPointerLockPermission(bool user_gesture); void RequestOpenExternalPermission( const base::Callback& callback, - bool user_gesture); + bool user_gesture, + const GURL& url); private: explicit WebContentsPermissionHelper(content::WebContents* web_contents); @@ -42,7 +43,8 @@ class WebContentsPermissionHelper void RequestPermission( content::PermissionType permission, const base::Callback& callback, - bool user_gesture = false); + bool user_gesture = false, + const base::DictionaryValue* details = nullptr); content::WebContents* web_contents_; diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index 81ce0c1e064..41650874219 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -112,7 +112,8 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( // If the `sandbox` option was passed to the BrowserWindow's webPreferences, // pass `--enable-sandbox` to the renderer so it won't have any node.js // integration. - if (IsSandboxed(web_contents)) { + bool sandbox = false; + if (web_preferences.GetBoolean("sandbox", &sandbox) && sandbox) { command_line->AppendSwitch(switches::kEnableSandbox); } else if (!command_line->HasSwitch(switches::kEnableSandbox)) { command_line->AppendSwitch(::switches::kNoSandbox); @@ -136,6 +137,17 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( LOG(ERROR) << "preload url must be file:// protocol."; } + // Custom args for renderer process + base::Value* customArgs; + if ((web_preferences.Get(options::kCustomArgs, &customArgs)) + && (customArgs->is_list())) { + for (const base::Value& customArg : customArgs->GetList()) { + if (customArg.is_string()) { + command_line->AppendArg(customArg.GetString()); + } + } + } + // Run Electron APIs and preload script in isolated world bool isolated; if (web_preferences.GetBoolean(options::kContextIsolation, &isolated) && @@ -197,11 +209,14 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( if (manager) { auto embedder = manager->GetEmbedder(guest_instance_id); if (embedder) { - auto window = NativeWindow::FromWebContents(embedder); - if (window) { - const bool visible = window->IsVisible() && !window->IsMinimized(); - if (!visible) { - command_line->AppendSwitch(switches::kHiddenPage); + auto* relay = NativeWindowRelay::FromWebContents(web_contents); + if (relay) { + auto* window = relay->window.get(); + if (window) { + const bool visible = window->IsVisible() && !window->IsMinimized(); + if (!visible) { + command_line->AppendSwitch(switches::kHiddenPage); + } } } } @@ -226,25 +241,6 @@ bool WebContentsPreferences::IsPreferenceEnabled( return bool_value; } -bool WebContentsPreferences::IsSandboxed(content::WebContents* web_contents) { - return IsPreferenceEnabled("sandbox", web_contents); -} - -bool WebContentsPreferences::UsesNativeWindowOpen( - content::WebContents* web_contents) { - return IsPreferenceEnabled("nativeWindowOpen", web_contents); -} - -bool WebContentsPreferences::IsPluginsEnabled( - content::WebContents* web_contents) { - return IsPreferenceEnabled("plugins", web_contents); -} - -bool WebContentsPreferences::DisablePopups( - content::WebContents* web_contents) { - return IsPreferenceEnabled("disablePopups", web_contents); -} - // static void WebContentsPreferences::OverrideWebkitPrefs( content::WebContents* web_contents, content::WebPreferences* prefs) { @@ -308,4 +304,13 @@ bool WebContentsPreferences::GetInteger(const std::string& attributeName, return false; } +bool WebContentsPreferences::GetString(const std::string& attribute_name, + std::string* string_value, + content::WebContents* web_contents) { + WebContentsPreferences* self = FromWebContents(web_contents); + if (!self) + return false; + return self->web_preferences()->GetString(attribute_name, string_value); +} + } // namespace atom diff --git a/atom/browser/web_contents_preferences.h b/atom/browser/web_contents_preferences.h index 366aa1d9520..5cc6bd97b23 100644 --- a/atom/browser/web_contents_preferences.h +++ b/atom/browser/web_contents_preferences.h @@ -39,10 +39,10 @@ class WebContentsPreferences static bool IsPreferenceEnabled(const std::string& attribute_name, content::WebContents* web_contents); - static bool IsSandboxed(content::WebContents* web_contents); - static bool UsesNativeWindowOpen(content::WebContents* web_contents); - static bool DisablePopups(content::WebContents* web_contents); - static bool IsPluginsEnabled(content::WebContents* web_contents); + + static bool GetString(const std::string& attribute_name, + std::string* string_value, + content::WebContents* web_contents); // Modify the WebPreferences according to |web_contents|'s preferences. static void OverrideWebkitPrefs( @@ -61,14 +61,14 @@ class WebContentsPreferences private: friend class content::WebContentsUserData; + // Get preferences value as integer possibly coercing it from a string + bool GetInteger(const std::string& attributeName, int* intValue); + static std::vector instances_; content::WebContents* web_contents_; base::DictionaryValue web_preferences_; - // Get preferences value as integer possibly coercing it from a string - bool GetInteger(const std::string& attributeName, int* intValue); - DISALLOW_COPY_AND_ASSIGN(WebContentsPreferences); }; diff --git a/atom/browser/web_dialog_helper.cc b/atom/browser/web_dialog_helper.cc index a20d962c0a8..878b4176a9d 100644 --- a/atom/browser/web_dialog_helper.cc +++ b/atom/browser/web_dialog_helper.cc @@ -53,7 +53,13 @@ class FileSelectHelper : public base::RefCounted, ~FileSelectHelper() override {} - void OnOpenDialogDone(bool result, const std::vector& paths) { +#if defined(MAS_BUILD) + void OnOpenDialogDone(bool result, const std::vector& paths, + const std::vector& bookmarks) +#else + void OnOpenDialogDone(bool result, const std::vector& paths) +#endif + { std::vector file_info; if (result) { for (auto& path : paths) { @@ -73,7 +79,13 @@ class FileSelectHelper : public base::RefCounted, OnFilesSelected(file_info); } - void OnSaveDialogDone(bool result, const base::FilePath& path) { +#if defined(MAS_BUILD) + void OnSaveDialogDone(bool result, const base::FilePath& path, + const std::string& bookmark) +#else + void OnSaveDialogDone(bool result, const base::FilePath& path) +#endif + { std::vector file_info; if (result) { content::FileChooserFileInfo info; @@ -223,12 +235,8 @@ void WebDialogHelper::RunFileChooser( NOTREACHED(); } - AtomBrowserContext* browser_context = static_cast( - window_->web_contents()->GetBrowserContext()); - if (!browser_context) { - browser_context = static_cast( - render_frame_host->GetProcess()->GetBrowserContext()); - } + auto* browser_context = static_cast( + render_frame_host->GetProcess()->GetBrowserContext()); settings.default_path = browser_context->prefs()->GetFilePath( prefs::kSelectFileLastDirectory).Append(params.default_file_name); settings.properties = flags; diff --git a/atom/browser/window_list.cc b/atom/browser/window_list.cc index 2ab0b24cf50..baef11f6561 100644 --- a/atom/browser/window_list.cc +++ b/atom/browser/window_list.cc @@ -93,7 +93,7 @@ void WindowList::CloseAllWindows() { void WindowList::DestroyAllWindows() { WindowVector windows = GetInstance()->windows_; for (const auto& window : windows) - window->CloseContents(nullptr); // e.g. Destroy() + window->CloseImmediately(); // e.g. Destroy() } WindowList::WindowList() { diff --git a/atom/common/api/api_messages.h b/atom/common/api/api_messages.h index cb8f1909033..2ff7c4f07c1 100644 --- a/atom/common/api/api_messages.h +++ b/atom/common/api/api_messages.h @@ -8,9 +8,11 @@ #include "base/strings/string16.h" #include "base/values.h" #include "content/public/common/common_param_traits.h" +#include "content/public/common/referrer.h" #include "ipc/ipc_message_macros.h" #include "ui/gfx/geometry/rect_f.h" #include "ui/gfx/ipc/gfx_param_traits.h" +#include "url/gurl.h" // The message starter should be declared in ipc/ipc_message_start.h. Since // we don't want to patch Chromium, we just pretend to be Content Shell. @@ -49,7 +51,7 @@ IPC_MESSAGE_ROUTED1(AtomAutofillFrameMsg_AcceptSuggestion, base::string16 /* suggestion */) // Sent by the renderer when the draggable regions are updated. -IPC_MESSAGE_ROUTED1(AtomViewHostMsg_UpdateDraggableRegions, +IPC_MESSAGE_ROUTED1(AtomFrameHostMsg_UpdateDraggableRegions, std::vector /* regions */) // Update renderer process preferences. @@ -62,3 +64,8 @@ IPC_SYNC_MESSAGE_ROUTED1_1(AtomViewHostMsg_SetTemporaryZoomLevel, // Sent by renderer to get the zoom level. IPC_SYNC_MESSAGE_ROUTED0_1(AtomViewHostMsg_GetZoomLevel, double /* result */) + +// Brings up SaveAs... dialog to save specified URL. +IPC_MESSAGE_ROUTED2(AtomFrameHostMsg_PDFSaveURLAs, + GURL /* url */, + content::Referrer /* referrer */) diff --git a/atom/common/api/atom_api_asar.cc b/atom/common/api/atom_api_asar.cc index ef27850370f..c9c355e4235 100644 --- a/atom/common/api/atom_api_asar.cc +++ b/atom/common/api/atom_api_asar.cc @@ -163,4 +163,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_common_asar, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_common_asar, Initialize) diff --git a/atom/common/api/atom_api_clipboard.cc b/atom/common/api/atom_api_clipboard.cc index e8e804e9286..47c85650a84 100644 --- a/atom/common/api/atom_api_clipboard.cc +++ b/atom/common/api/atom_api_clipboard.cc @@ -149,19 +149,19 @@ void Clipboard::WriteText(const base::string16& text, mate::Arguments* args) { writer.WriteText(text); } -base::string16 Clipboard::ReadRtf(mate::Arguments* args) { +base::string16 Clipboard::ReadRTF(mate::Arguments* args) { std::string data; ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); clipboard->ReadRTF(GetClipboardType(args), &data); return base::UTF8ToUTF16(data); } -void Clipboard::WriteRtf(const std::string& text, mate::Arguments* args) { +void Clipboard::WriteRTF(const std::string& text, mate::Arguments* args) { ui::ScopedClipboardWriter writer(GetClipboardType(args)); writer.WriteRTF(text); } -base::string16 Clipboard::ReadHtml(mate::Arguments* args) { +base::string16 Clipboard::ReadHTML(mate::Arguments* args) { base::string16 data; base::string16 html; std::string url; @@ -173,7 +173,7 @@ base::string16 Clipboard::ReadHtml(mate::Arguments* args) { return data; } -void Clipboard::WriteHtml(const base::string16& html, mate::Arguments* args) { +void Clipboard::WriteHTML(const base::string16& html, mate::Arguments* args) { ui::ScopedClipboardWriter writer(GetClipboardType(args)); writer.WriteHTML(html, std::string()); } @@ -238,10 +238,10 @@ void Initialize(v8::Local exports, v8::Local unused, dict.SetMethod("write", &atom::api::Clipboard::Write); dict.SetMethod("readText", &atom::api::Clipboard::ReadText); dict.SetMethod("writeText", &atom::api::Clipboard::WriteText); - dict.SetMethod("readRTF", &atom::api::Clipboard::ReadRtf); - dict.SetMethod("writeRTF", &atom::api::Clipboard::WriteRtf); - dict.SetMethod("readHTML", &atom::api::Clipboard::ReadHtml); - dict.SetMethod("writeHTML", &atom::api::Clipboard::WriteHtml); + dict.SetMethod("readRTF", &atom::api::Clipboard::ReadRTF); + dict.SetMethod("writeRTF", &atom::api::Clipboard::WriteRTF); + dict.SetMethod("readHTML", &atom::api::Clipboard::ReadHTML); + dict.SetMethod("writeHTML", &atom::api::Clipboard::WriteHTML); dict.SetMethod("readBookmark", &atom::api::Clipboard::ReadBookmark); dict.SetMethod("writeBookmark", &atom::api::Clipboard::WriteBookmark); dict.SetMethod("readImage", &atom::api::Clipboard::ReadImage); @@ -251,14 +251,8 @@ void Initialize(v8::Local exports, v8::Local unused, dict.SetMethod("readBuffer", &atom::api::Clipboard::ReadBuffer); dict.SetMethod("writeBuffer", &atom::api::Clipboard::WriteBuffer); dict.SetMethod("clear", &atom::api::Clipboard::Clear); - - // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings - dict.SetMethod("readRtf", &atom::api::Clipboard::ReadRtf); - dict.SetMethod("writeRtf", &atom::api::Clipboard::WriteRtf); - dict.SetMethod("readHtml", &atom::api::Clipboard::ReadHtml); - dict.SetMethod("writeHtml", &atom::api::Clipboard::WriteHtml); } } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_common_clipboard, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_common_clipboard, Initialize) diff --git a/atom/common/api/atom_api_clipboard.h b/atom/common/api/atom_api_clipboard.h index 92bc7b04a45..cdc81049ac6 100644 --- a/atom/common/api/atom_api_clipboard.h +++ b/atom/common/api/atom_api_clipboard.h @@ -30,11 +30,11 @@ class Clipboard { static base::string16 ReadText(mate::Arguments* args); static void WriteText(const base::string16& text, mate::Arguments* args); - static base::string16 ReadRtf(mate::Arguments* args); - static void WriteRtf(const std::string& text, mate::Arguments* args); + static base::string16 ReadRTF(mate::Arguments* args); + static void WriteRTF(const std::string& text, mate::Arguments* args); - static base::string16 ReadHtml(mate::Arguments* args); - static void WriteHtml(const base::string16& html, mate::Arguments* args); + static base::string16 ReadHTML(mate::Arguments* args); + static void WriteHTML(const base::string16& html, mate::Arguments* args); static v8::Local ReadBookmark(mate::Arguments* args); static void WriteBookmark(const base::string16& title, diff --git a/atom/common/api/atom_api_crash_reporter.cc b/atom/common/api/atom_api_crash_reporter.cc index f435cdf030b..78ef525f911 100644 --- a/atom/common/api/atom_api_crash_reporter.cc +++ b/atom/common/api/atom_api_crash_reporter.cc @@ -31,15 +31,6 @@ struct Converter { namespace { -// TODO(2.0) Remove -void SetExtraParameter(const std::string& key, mate::Arguments* args) { - std::string value; - if (args->GetNext(&value)) - CrashReporter::GetInstance()->AddExtraParameter(key, value); - else - CrashReporter::GetInstance()->RemoveExtraParameter(key); -} - void AddExtraParameter(const std::string& key, const std::string& value) { CrashReporter::GetInstance()->AddExtraParameter(key, value); } @@ -57,7 +48,6 @@ void Initialize(v8::Local exports, v8::Local unused, mate::Dictionary dict(context->GetIsolate(), exports); auto reporter = base::Unretained(CrashReporter::GetInstance()); dict.SetMethod("start", base::Bind(&CrashReporter::Start, reporter)); - dict.SetMethod("setExtraParameter", &SetExtraParameter); dict.SetMethod("addExtraParameter", &AddExtraParameter); dict.SetMethod("removeExtraParameter", &RemoveExtraParameter); dict.SetMethod("getParameters", &GetParameters); @@ -71,4 +61,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_common_crash_reporter, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_common_crash_reporter, Initialize) diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index 3939a0afe56..ed736dabd00 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -15,6 +15,7 @@ #include "base/files/file_util.h" #include "base/strings/pattern.h" #include "base/strings/string_util.h" +#include "base/threading/thread_restrictions.h" #include "native_mate/object_template_builder.h" #include "net/base/data_url.h" #include "third_party/skia/include/core/SkBitmap.h" @@ -130,8 +131,11 @@ bool AddImageSkiaRep(gfx::ImageSkia* image, const base::FilePath& path, double scale_factor) { std::string file_contents; - if (!asar::ReadFileToString(path, &file_contents)) - return false; + { + base::ThreadRestrictions::ScopedAllowIO allow_io; + if (!asar::ReadFileToString(path, &file_contents)) + return false; + } const unsigned char* data = reinterpret_cast(file_contents.data()); @@ -452,7 +456,7 @@ void NativeImage::AddRepresentation(const mate::Dictionary& options) { // Re-initialize image when first representationย is added to an empty image if (skia_rep_added && IsEmpty()) { gfx::Image image(image_skia); - image_.SwapRepresentations(&image); + image_ = std::move(image); } } @@ -579,10 +583,7 @@ void NativeImage::BuildPrototype( .SetMethod("resize", &NativeImage::Resize) .SetMethod("crop", &NativeImage::Crop) .SetMethod("getAspectRatio", &NativeImage::GetAspectRatio) - .SetMethod("addRepresentation", &NativeImage::AddRepresentation) - // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings - .SetMethod("toPng", &NativeImage::ToPNG) - .SetMethod("toJpeg", &NativeImage::ToJPEG); + .SetMethod("addRepresentation", &NativeImage::AddRepresentation); } } // namespace api @@ -635,4 +636,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_common_native_image, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_common_native_image, Initialize) diff --git a/atom/common/api/atom_api_shell.cc b/atom/common/api/atom_api_shell.cc index 39e6aea7baf..231d2ea9cbe 100644 --- a/atom/common/api/atom_api_shell.cc +++ b/atom/common/api/atom_api_shell.cc @@ -151,4 +151,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_common_shell, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_common_shell, Initialize) diff --git a/atom/common/api/atom_api_v8_util.cc b/atom/common/api/atom_api_v8_util.cc index a01ca8f84d9..f7edfd28cb3 100644 --- a/atom/common/api/atom_api_v8_util.cc +++ b/atom/common/api/atom_api_v8_util.cc @@ -123,4 +123,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_common_v8_util, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_common_v8_util, Initialize) diff --git a/atom/common/api/atom_bindings.cc b/atom/common/api/atom_bindings.cc index c2b158b0ce2..a41f2eebddd 100644 --- a/atom/common/api/atom_bindings.cc +++ b/atom/common/api/atom_bindings.cc @@ -71,9 +71,6 @@ void AtomBindings::BindTo(v8::Isolate* isolate, // TODO(kevinsawicki): Make read-only in 2.0 to match node versions.Set(ATOM_PROJECT_NAME, ATOM_VERSION_STRING); versions.Set("chrome", CHROME_VERSION_STRING); - - // TODO(kevinsawicki): Remove in 2.0 - versions.Set("atom-shell", ATOM_VERSION_STRING); } } diff --git a/atom/common/asar/archive.cc b/atom/common/asar/archive.cc index c8d85f904d9..84f9cd8dd5b 100644 --- a/atom/common/asar/archive.cc +++ b/atom/common/asar/archive.cc @@ -14,6 +14,8 @@ #include "base/logging.h" #include "base/pickle.h" #include "base/strings/string_number_conversions.h" +#include "base/task_scheduler/post_task.h" +#include "base/threading/thread_restrictions.h" #include "base/values.h" #if defined(OS_WIN) @@ -115,17 +117,17 @@ bool FillFileInfoWithNode(Archive::FileInfo* info, } // namespace Archive::Archive(const base::FilePath& path) - : path_(path), - file_(path_, base::File::FLAG_OPEN | base::File::FLAG_READ), + : path_(path), file_(base::File::FILE_OK), header_size_(0) { + base::ThreadRestrictions::ScopedAllowIO allow_io; + file_.Initialize(path_, base::File::FLAG_OPEN | base::File::FLAG_READ); #if defined(OS_WIN) - fd_(_open_osfhandle( - reinterpret_cast(file_.GetPlatformFile()), 0)), + fd_ = + _open_osfhandle(reinterpret_cast(file_.GetPlatformFile()), 0); #elif defined(OS_POSIX) - fd_(file_.GetPlatformFile()), + fd_ = file_.GetPlatformFile(); #else - fd_(-1), + fd_ = -1; #endif - header_size_(0) { } Archive::~Archive() { @@ -136,6 +138,8 @@ Archive::~Archive() { file_.TakePlatformFile(); } #endif + base::ThreadRestrictions::ScopedAllowIO allow_io; + file_.Close(); } bool Archive::Init() { @@ -151,7 +155,10 @@ bool Archive::Init() { int len; buf.resize(8); - len = file_.ReadAtCurrentPos(buf.data(), buf.size()); + { + base::ThreadRestrictions::ScopedAllowIO allow_io; + len = file_.ReadAtCurrentPos(buf.data(), buf.size()); + } if (len != static_cast(buf.size())) { PLOG(ERROR) << "Failed to read header size from " << path_.value(); return false; @@ -165,7 +172,10 @@ bool Archive::Init() { } buf.resize(size); - len = file_.ReadAtCurrentPos(buf.data(), buf.size()); + { + base::ThreadRestrictions::ScopedAllowIO allow_io; + len = file_.ReadAtCurrentPos(buf.data(), buf.size()); + } if (len != static_cast(buf.size())) { PLOG(ERROR) << "Failed to read header from " << path_.value(); return false; diff --git a/atom/common/atom_command_line.cc b/atom/common/atom_command_line.cc index 08880ffe4a3..54420b533a6 100644 --- a/atom/common/atom_command_line.cc +++ b/atom/common/atom_command_line.cc @@ -10,31 +10,22 @@ namespace atom { // static -std::vector AtomCommandLine::argv_; - -#if defined(OS_WIN) -// static -std::vector AtomCommandLine::wargv_; -#endif +base::CommandLine::StringVector AtomCommandLine::argv_; // static -void AtomCommandLine::Init(int argc, const char* const* argv) { +void AtomCommandLine::Init(int argc, base::CommandLine::CharType** argv) { + DCHECK(argv_.empty()); + + // NOTE: uv_setup_args does nothing on Windows, so we don't need to call it. + // Otherwise we'd have to convert the arguments from UTF16. +#if !defined(OS_WIN) // Hack around with the argv pointer. Used for process.title = "blah" - char** new_argv = uv_setup_args(argc, const_cast(argv)); - for (int i = 0; i < argc; ++i) { - argv_.push_back(new_argv[i]); - } -} - -#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]); - } -} + argv = uv_setup_args(argc, argv); #endif + argv_.assign(argv, argv + argc); +} + #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 a834ce92566..57dab57e78c 100644 --- a/atom/common/atom_command_line.h +++ b/atom/common/atom_command_line.h @@ -8,6 +8,7 @@ #include #include +#include "base/command_line.h" #include "base/macros.h" #include "build/build_config.h" @@ -16,13 +17,9 @@ namespace atom { // Singleton to remember the original "argc" and "argv". class AtomCommandLine { public: - static void Init(int argc, const char* const* argv); - static std::vector argv() { return argv_; } + static const base::CommandLine::StringVector& argv() { return argv_; } -#if defined(OS_WIN) - static void InitW(int argc, const wchar_t* const* argv); - static std::vector wargv() { return wargv_; } -#endif + static void Init(int argc, base::CommandLine::CharType** argv); #if defined(OS_LINUX) // On Linux the command line has to be read from base::CommandLine since @@ -31,11 +28,7 @@ class AtomCommandLine { #endif private: - static std::vector argv_; - -#if defined(OS_WIN) - static std::vector wargv_; -#endif + static base::CommandLine::StringVector argv_; DISALLOW_IMPLICIT_CONSTRUCTORS(AtomCommandLine); }; diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index a279283cf09..45ebe50d288 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -5,24 +5,27 @@ #ifndef ATOM_COMMON_ATOM_VERSION_H_ #define ATOM_COMMON_ATOM_VERSION_H_ -#define ATOM_MAJOR_VERSION 1 -#define ATOM_MINOR_VERSION 8 -#define ATOM_PATCH_VERSION 2 -#define ATOM_PRE_RELEASE_VERSION -beta.2 - -#ifndef ATOM_PRE_RELEASE_VERSION -# define ATOM_PRE_RELEASE_VERSION "" -#endif +#define ATOM_MAJOR_VERSION 0 +#define ATOM_MINOR_VERSION 0 +#define ATOM_PATCH_VERSION 0 +#define ATOM_PRE_RELEASE_VERSION -dev #ifndef ATOM_STRINGIFY #define ATOM_STRINGIFY(n) ATOM_STRINGIFY_HELPER(n) #define ATOM_STRINGIFY_HELPER(n) #n #endif -# define ATOM_VERSION_STRING ATOM_STRINGIFY(ATOM_MAJOR_VERSION) "." \ +#ifndef ATOM_PRE_RELEASE_VERSION + #define ATOM_VERSION_STRING ATOM_STRINGIFY(ATOM_MAJOR_VERSION) "." \ + ATOM_STRINGIFY(ATOM_MINOR_VERSION) "." \ + ATOM_STRINGIFY(ATOM_PATCH_VERSION) +#else + #define ATOM_VERSION_STRING ATOM_STRINGIFY(ATOM_MAJOR_VERSION) "." \ ATOM_STRINGIFY(ATOM_MINOR_VERSION) "." \ ATOM_STRINGIFY(ATOM_PATCH_VERSION) \ ATOM_STRINGIFY(ATOM_PRE_RELEASE_VERSION) +#endif + #define ATOM_VERSION "v" ATOM_VERSION_STRING diff --git a/atom/common/chrome_version.h b/atom/common/chrome_version.h index ac24a95f87f..d0ebc901bb3 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 "61.0.3163.100" +#define CHROME_VERSION_STRING "62.0.3202.94" #define CHROME_VERSION "v" CHROME_VERSION_STRING #endif // ATOM_COMMON_CHROME_VERSION_H_ diff --git a/atom/common/common_message_generator.h b/atom/common/common_message_generator.h index 8b41a17f29b..fac3d548c09 100644 --- a/atom/common/common_message_generator.h +++ b/atom/common/common_message_generator.h @@ -8,6 +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" #include "chrome/common/chrome_utility_printing_messages.h" -#include "components/pdf/common/pdf_messages.h" diff --git a/atom/common/crash_reporter/crash_reporter.cc b/atom/common/crash_reporter/crash_reporter.cc index 88930f0d3ce..97476623e88 100644 --- a/atom/common/crash_reporter/crash_reporter.cc +++ b/atom/common/crash_reporter/crash_reporter.cc @@ -11,6 +11,7 @@ #include "base/files/file_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" +#include "base/threading/thread_restrictions.h" #include "content/public/common/content_switches.h" namespace crash_reporter { @@ -53,6 +54,7 @@ bool CrashReporter::GetUploadToServer() { std::vector CrashReporter::GetUploadedReports(const base::FilePath& crashes_dir) { + base::ThreadRestrictions::ScopedAllowIO allow_io; std::string file_content; std::vector result; base::FilePath uploads_path = diff --git a/atom/common/crash_reporter/crash_reporter_linux.cc b/atom/common/crash_reporter/crash_reporter_linux.cc index d14c15de195..881780d8589 100644 --- a/atom/common/crash_reporter/crash_reporter_linux.cc +++ b/atom/common/crash_reporter/crash_reporter_linux.cc @@ -17,6 +17,7 @@ #include "base/logging.h" #include "base/memory/singleton.h" #include "base/process/memory.h" +#include "base/threading/thread_restrictions.h" #include "vendor/breakpad/src/client/linux/handler/exception_handler.h" #include "vendor/breakpad/src/common/linux/linux_libc_support.h" @@ -90,8 +91,10 @@ bool CrashReporterLinux::GetUploadToServer() { } void CrashReporterLinux::EnableCrashDumping(const base::FilePath& crashes_dir) { - base::CreateDirectory(crashes_dir); - + { + base::ThreadRestrictions::ScopedAllowIO allow_io; + base::CreateDirectory(crashes_dir); + } std::string log_file = crashes_dir.Append("uploads.log").value(); strncpy(g_crash_log_path, log_file.c_str(), sizeof(g_crash_log_path)); diff --git a/atom/common/crash_reporter/crash_reporter_mac.mm b/atom/common/crash_reporter/crash_reporter_mac.mm index 3f82d2466fb..55f387756a0 100644 --- a/atom/common/crash_reporter/crash_reporter_mac.mm +++ b/atom/common/crash_reporter/crash_reporter_mac.mm @@ -13,6 +13,7 @@ #include "base/strings/string_piece.h" #include "base/strings/stringprintf.h" #include "base/strings/sys_string_conversions.h" +#include "base/threading/thread_restrictions.h" #include "vendor/crashpad/client/crashpad_client.h" #include "vendor/crashpad/client/crashpad_info.h" #include "vendor/crashpad/client/settings.h" @@ -139,8 +140,11 @@ std::vector CrashReporterMac::GetUploadedReports(const base::FilePath& crashes_dir) { std::vector uploaded_reports; - if (!base::PathExists(crashes_dir)) { - return uploaded_reports; + { + base::ThreadRestrictions::ScopedAllowIO allow_io; + if (!base::PathExists(crashes_dir)) { + return uploaded_reports; + } } // Load crashpad database. std::unique_ptr database = diff --git a/atom/common/linux/application_info.cc b/atom/common/linux/application_info.cc index 053bd4bb863..cff228ba490 100644 --- a/atom/common/linux/application_info.cc +++ b/atom/common/linux/application_info.cc @@ -2,18 +2,74 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. +#include "brightray/common/application_info.h" + +#include +#include + +#include #include #include "atom/common/atom_version.h" +#include "base/environment.h" +#include "base/logging.h" +#include "chrome/browser/ui/libgtkui/gtk_util.h" + +namespace { + +GDesktopAppInfo* get_desktop_app_info() { + std::unique_ptr env(base::Environment::Create()); + const std::string desktop_id = libgtkui::GetDesktopName(env.get()); + return desktop_id.empty() ? nullptr + : g_desktop_app_info_new(desktop_id.c_str()); +} + +} // namespace namespace brightray { std::string GetApplicationName() { - return ATOM_PRODUCT_NAME; + // attempt #1: the string set in app.setName() + std::string ret = GetOverriddenApplicationName(); + + // attempt #2: the 'Name' entry from .desktop file's [Desktop] section + if (ret.empty()) { + GDesktopAppInfo* info = get_desktop_app_info(); + if (info != nullptr) { + char* str = g_desktop_app_info_get_string(info, "Name"); + g_clear_object(&info); + if (str != nullptr) + ret = str; + g_clear_pointer(&str, g_free); + } + } + + // attempt #3: Electron's name + if (ret.empty()) { + ret = ATOM_PRODUCT_NAME; + } + + return ret; } std::string GetApplicationVersion() { - return ATOM_VERSION_STRING; + std::string ret; + + // ensure ATOM_PRODUCT_NAME and ATOM_PRODUCT_STRING match up + if (GetApplicationName() == ATOM_PRODUCT_NAME) + ret = ATOM_VERSION_STRING; + + // try to use the string set in app.setVersion() + if (ret.empty()) + ret = GetOverriddenApplicationVersion(); + + // no known version number; return some safe fallback + if (ret.empty()) { + LOG(WARNING) << "No version found. Was app.setVersion() called?"; + ret = "0.0"; + } + + return ret; } } // namespace brightray diff --git a/atom/common/native_mate_converters/content_converter.cc b/atom/common/native_mate_converters/content_converter.cc index c064df43b66..01682bb7706 100644 --- a/atom/common/native_mate_converters/content_converter.cc +++ b/atom/common/native_mate_converters/content_converter.cc @@ -221,16 +221,16 @@ Converter>::ToV8( post_data_dict->Set("bytes", std::move(bytes)); } else if (type == ResourceRequestBody::Element::TYPE_FILE) { post_data_dict->SetString("type", "file"); - post_data_dict->SetStringWithoutPathExpansion( - "filePath", element.path().AsUTF8Unsafe()); + post_data_dict->SetKey("filePath", + base::Value(element.path().AsUTF8Unsafe())); post_data_dict->SetInteger("offset", static_cast(element.offset())); post_data_dict->SetInteger("length", static_cast(element.length())); post_data_dict->SetDouble( "modificationTime", element.expected_modification_time().ToDoubleT()); } else if (type == ResourceRequestBody::Element::TYPE_FILE_FILESYSTEM) { post_data_dict->SetString("type", "fileSystem"); - post_data_dict->SetStringWithoutPathExpansion( - "fileSystemURL", element.filesystem_url().spec()); + post_data_dict->SetKey("fileSystemURL", + base::Value(element.filesystem_url().spec())); post_data_dict->SetInteger("offset", static_cast(element.offset())); post_data_dict->SetInteger("length", static_cast(element.length())); post_data_dict->SetDouble( diff --git a/atom/common/native_mate_converters/net_converter.cc b/atom/common/native_mate_converters/net_converter.cc index 05c20ea6be2..618c089ae9a 100644 --- a/atom/common/native_mate_converters/net_converter.cc +++ b/atom/common/native_mate_converters/net_converter.cc @@ -203,7 +203,7 @@ void FillRequestDetails(base::DictionaryValue* details, details->SetString("method", request->method()); std::string url; if (!request->url_chain().empty()) url = request->url().spec(); - details->SetStringWithoutPathExpansion("url", url); + details->SetKey("url", base::Value(url)); details->SetString("referrer", request->referrer()); std::unique_ptr list(new base::ListValue); GetUploadData(list.get(), request); @@ -239,7 +239,7 @@ void GetUploadData(base::ListValue* upload_data_list, const net::UploadFileElementReader* file_reader = reader->AsFileReader(); auto file_path = file_reader->path().AsUTF8Unsafe(); - upload_data_dict->SetStringWithoutPathExpansion("file", file_path); + upload_data_dict->SetKey("file", base::Value(file_path)); } else { const storage::UploadBlobElementReader* blob_reader = static_cast(reader.get()); diff --git a/atom/common/native_mate_converters/v8_value_converter.cc b/atom/common/native_mate_converters/v8_value_converter.cc index 72c59582794..b4935b5e3ff 100644 --- a/atom/common/native_mate_converters/v8_value_converter.cc +++ b/atom/common/native_mate_converters/v8_value_converter.cc @@ -320,8 +320,12 @@ base::Value* V8ValueConverter::FromV8ValueImpl( if (val->IsInt32()) return new base::Value(val->ToInt32()->Value()); - if (val->IsNumber()) - return new base::Value(val->ToNumber()->Value()); + if (val->IsNumber()) { + double val_as_double = val->ToNumber()->Value(); + if (!std::isfinite(val_as_double)) + return nullptr; + return new base::Value(val_as_double); + } if (val->IsString()) { v8::String::Utf8Value utf8(val->ToString()); diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index 03e96ae8a4f..15c65185684 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -4,6 +4,7 @@ #include "atom/common/node_bindings.h" +#include #include #include @@ -17,6 +18,7 @@ #include "base/files/file_path.h" #include "base/path_service.h" #include "base/run_loop.h" +#include "base/strings/utf_string_conversions.h" #include "base/threading/thread_task_runner_handle.h" #include "base/trace_event/trace_event.h" #include "content/public/browser/browser_thread.h" @@ -25,44 +27,71 @@ #include "atom/common/node_includes.h" -// Force all builtin modules to be referenced so they can actually run their -// DSO constructors, see http://git.io/DRIqCg. -#define REFERENCE_MODULE(name) \ - extern "C" void _register_ ## name(void); \ - void (*fp_register_ ## name)(void) = _register_ ## name -// Electron's builtin modules. -REFERENCE_MODULE(atom_browser_app); -REFERENCE_MODULE(atom_browser_auto_updater); -REFERENCE_MODULE(atom_browser_browser_view); -REFERENCE_MODULE(atom_browser_content_tracing); -REFERENCE_MODULE(atom_browser_debugger); -REFERENCE_MODULE(atom_browser_desktop_capturer); -REFERENCE_MODULE(atom_browser_dialog); -REFERENCE_MODULE(atom_browser_download_item); -REFERENCE_MODULE(atom_browser_global_shortcut); -REFERENCE_MODULE(atom_browser_menu); -REFERENCE_MODULE(atom_browser_net); -REFERENCE_MODULE(atom_browser_power_monitor); -REFERENCE_MODULE(atom_browser_power_save_blocker); -REFERENCE_MODULE(atom_browser_protocol); -REFERENCE_MODULE(atom_browser_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); -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_native_image); -REFERENCE_MODULE(atom_common_notification); -REFERENCE_MODULE(atom_common_screen); -REFERENCE_MODULE(atom_common_shell); -REFERENCE_MODULE(atom_common_v8_util); -REFERENCE_MODULE(atom_renderer_ipc); -REFERENCE_MODULE(atom_renderer_web_frame); -#undef REFERENCE_MODULE +#define ELECTRON_BUILTIN_MODULES(V) \ + V(atom_browser_app) \ + V(atom_browser_auto_updater) \ + V(atom_browser_browser_view) \ + V(atom_browser_content_tracing) \ + V(atom_browser_debugger) \ + V(atom_browser_desktop_capturer) \ + V(atom_browser_dialog) \ + V(atom_browser_download_item) \ + V(atom_browser_global_shortcut) \ + V(atom_browser_in_app_purchase) \ + V(atom_browser_menu) \ + V(atom_browser_net) \ + V(atom_browser_power_monitor) \ + V(atom_browser_power_save_blocker) \ + V(atom_browser_protocol) \ + V(atom_browser_render_process_preferences) \ + V(atom_browser_session) \ + V(atom_browser_system_preferences) \ + V(atom_browser_tray) \ + V(atom_browser_web_contents) \ + V(atom_browser_web_view_manager) \ + V(atom_browser_window) \ + V(atom_common_asar) \ + V(atom_common_clipboard) \ + V(atom_common_crash_reporter) \ + V(atom_common_native_image) \ + V(atom_common_notification) \ + V(atom_common_screen) \ + V(atom_common_shell) \ + V(atom_common_v8_util) \ + V(atom_renderer_ipc) \ + V(atom_renderer_web_frame) + +// This is used to load built-in modules. Instead of using +// __attribute__((constructor)), we call the _register_ +// function for each built-in modules explicitly. This is only +// forward declaration. The definitions are in each module's +// implementation when calling the NODE_BUILTIN_MODULE_CONTEXT_AWARE. +#define V(modname) void _register_##modname(); +ELECTRON_BUILTIN_MODULES(V) +#undef V + +namespace { + +void stop_and_close_uv_loop(uv_loop_t* loop) { + // Close any active handles + uv_stop(loop); + uv_walk(loop, [](uv_handle_t* handle, void*){ + if (!uv_is_closing(handle)) { + uv_close(handle, nullptr); + } + }, nullptr); + + // Run the loop to let it finish all the closing handles + // NB: after uv_stop(), uv_run(UV_RUN_DEFAULT) returns 0 when that's done + for (;;) + if (!uv_run(loop, UV_RUN_DEFAULT)) + break; + + DCHECK(!uv_loop_alive(loop)); + uv_loop_close(loop); +} + +} // namespace namespace atom { @@ -100,10 +129,15 @@ base::FilePath GetResourcesPath(bool is_browser) { NodeBindings::NodeBindings(BrowserEnvironment browser_env) : browser_env_(browser_env), - uv_loop_(browser_env == WORKER ? uv_loop_new() : uv_default_loop()), embed_closed_(false), uv_env_(nullptr), weak_factory_(this) { + if (browser_env == WORKER) { + uv_loop_init(&worker_loop_); + uv_loop_ = &worker_loop_; + } else { + uv_loop_ = uv_default_loop(); + } } NodeBindings::~NodeBindings() { @@ -119,9 +153,15 @@ NodeBindings::~NodeBindings() { uv_sem_destroy(&embed_sem_); uv_close(reinterpret_cast(&dummy_uv_handle_), nullptr); - // Destroy loop. - if (uv_loop_ != uv_default_loop()) - uv_loop_delete(uv_loop_); + // Clean up worker loop + if (uv_loop_ == &worker_loop_) + stop_and_close_uv_loop(uv_loop_); +} + +void NodeBindings::RegisterBuiltinModules() { +#define V(modname) _register_##modname(); + ELECTRON_BUILTIN_MODULES(V) +#undef V } void NodeBindings::Initialize() { @@ -135,6 +175,9 @@ void NodeBindings::Initialize() { AtomCommandLine::InitializeFromCommandLine(); #endif + // Explicitly register electron's builtin modules. + RegisterBuiltinModules(); + // Init node. // (we assume node::Init would not modify the parameters under embedded mode). node::Init(nullptr, nullptr, nullptr, nullptr); @@ -149,8 +192,16 @@ void NodeBindings::Initialize() { } node::Environment* NodeBindings::CreateEnvironment( - v8::Handle context) { + v8::Handle context, + node::MultiIsolatePlatform* platform) { +#if defined(OS_WIN) + auto& atom_args = AtomCommandLine::argv(); + std::vector args(atom_args.size()); + std::transform(atom_args.cbegin(), atom_args.cend(), args.begin(), + [](auto& a) { return base::WideToUTF8(a); }); +#else auto args = AtomCommandLine::argv(); +#endif // Feed node the path to initialization script. base::FilePath::StringType process_type; @@ -170,13 +221,12 @@ node::Environment* NodeBindings::CreateEnvironment( resources_path.Append(FILE_PATH_LITERAL("electron.asar")) .Append(process_type) .Append(FILE_PATH_LITERAL("init.js")); - std::string script_path_str = script_path.AsUTF8Unsafe(); - args.insert(args.begin() + 1, script_path_str.c_str()); + args.insert(args.begin() + 1, script_path.AsUTF8Unsafe()); std::unique_ptr c_argv = StringVectorToArgArray(args); node::Environment* env = node::CreateEnvironment( - new node::IsolateData(context->GetIsolate(), uv_loop_), context, - args.size(), c_argv.get(), 0, nullptr); + node::CreateIsolateData(context->GetIsolate(), uv_loop_, platform), + context, args.size(), c_argv.get(), 0, nullptr); if (browser_env_ == BROWSER) { // SetAutorunMicrotasks is no longer called in node::CreateEnvironment diff --git a/atom/common/node_bindings.h b/atom/common/node_bindings.h index 5047a9afb23..8f0fdbe5761 100644 --- a/atom/common/node_bindings.h +++ b/atom/common/node_bindings.h @@ -17,6 +17,7 @@ class MessageLoop; namespace node { class Environment; +class MultiIsolatePlatform; } namespace atom { @@ -30,6 +31,7 @@ class NodeBindings { }; static NodeBindings* Create(BrowserEnvironment browser_env); + static void RegisterBuiltinModules(); virtual ~NodeBindings(); @@ -37,7 +39,9 @@ class NodeBindings { void Initialize(); // Create the environment and load node.js. - node::Environment* CreateEnvironment(v8::Handle context); + node::Environment* CreateEnvironment( + v8::Handle context, + node::MultiIsolatePlatform* platform = nullptr); // Load node.js in the environment. void LoadEnvironment(node::Environment* env); @@ -85,6 +89,9 @@ class NodeBindings { // Whether the libuv loop has ended. bool embed_closed_; + // Loop used when constructed in WORKER mode + uv_loop_t worker_loop_; + // Dummy handle to make uv's loop not quit. uv_async_t dummy_uv_handle_; diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index 78235426efe..eed6e7bc092 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -139,6 +139,8 @@ const char kNodeIntegrationInWorker[] = "nodeIntegrationInWorker"; // Enable the web view tag. const char kWebviewTag[] = "webviewTag"; +const char kCustomArgs[] = "additionalArguments"; + } // namespace options namespace switches { @@ -180,6 +182,7 @@ const char kAppPath[] = "app-path"; const char kBackgroundColor[] = "background-color"; const char kPreloadScript[] = "preload"; const char kPreloadURL[] = "preload-url"; +const char kPreloadScripts[] = "preload-scripts"; const char kNodeIntegration[] = "node-integration"; const char kContextIsolation[] = "context-isolation"; const char kGuestInstanceID[] = "guest-instance-id"; diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index 401feae48a0..525301971c8 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -68,6 +68,7 @@ extern const char kBlinkFeatures[]; extern const char kDisableBlinkFeatures[]; extern const char kNodeIntegrationInWorker[]; extern const char kWebviewTag[]; +extern const char kCustomArgs[]; } // namespace options @@ -91,6 +92,7 @@ extern const char kAppPath[]; extern const char kBackgroundColor[]; extern const char kPreloadScript[]; extern const char kPreloadURL[]; +extern const char kPreloadScripts[]; extern const char kNodeIntegration[]; extern const char kContextIsolation[]; extern const char kGuestInstanceID[]; diff --git a/atom/renderer/api/atom_api_renderer_ipc.cc b/atom/renderer/api/atom_api_renderer_ipc.cc index 7bee1411ba1..2bb35992ff9 100644 --- a/atom/renderer/api/atom_api_renderer_ipc.cc +++ b/atom/renderer/api/atom_api_renderer_ipc.cc @@ -76,4 +76,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace atom -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_renderer_ipc, atom::api::Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_renderer_ipc, atom::api::Initialize) diff --git a/atom/renderer/api/atom_api_spell_check_client.cc b/atom/renderer/api/atom_api_spell_check_client.cc index 36561da467e..cc7716e7563 100644 --- a/atom/renderer/api/atom_api_spell_check_client.cc +++ b/atom/renderer/api/atom_api_spell_check_client.cc @@ -9,6 +9,7 @@ #include "atom/common/native_mate_converters/string16_converter.h" #include "base/logging.h" +#include "chrome/renderer/spellchecker/spellcheck_worditerator.h" #include "native_mate/converter.h" #include "native_mate/dictionary.h" #include "third_party/icu/source/common/unicode/uscript.h" @@ -41,7 +42,10 @@ SpellCheckClient::SpellCheckClient(const std::string& language, v8::Isolate* isolate, v8::Local provider) : isolate_(isolate), + context_(isolate, isolate->GetCurrentContext()), provider_(isolate, provider) { + DCHECK(!context_.IsEmpty()); + character_attributes_.SetDefaultLanguage(language); // Persistent the method. @@ -49,7 +53,9 @@ SpellCheckClient::SpellCheckClient(const std::string& language, dict.Get("spellCheck", &spell_check_); } -SpellCheckClient::~SpellCheckClient() {} +SpellCheckClient::~SpellCheckClient() { + context_.Reset(); +} void SpellCheckClient::CheckSpelling( const blink::WebString& text, @@ -78,45 +84,57 @@ void SpellCheckClient::RequestCheckingOfText( completionCallback->DidFinishCheckingText(results); } -void SpellCheckClient::ShowSpellingUI(bool show) { -} +void SpellCheckClient::ShowSpellingUI(bool show) {} bool SpellCheckClient::IsShowingSpellingUI() { return false; } void SpellCheckClient::UpdateSpellingUIWithMisspelledWord( - const blink::WebString& word) { -} + const blink::WebString& word) {} void SpellCheckClient::SpellCheckText( const base::string16& text, bool stop_at_first_result, std::vector* results) { - if (text.length() == 0 || spell_check_.IsEmpty()) + if (text.empty() || spell_check_.IsEmpty()) return; + if (!text_iterator_.IsInitialized() && + !text_iterator_.Initialize(&character_attributes_, true)) { + // We failed to initialize text_iterator_, return as spelled correctly. + VLOG(1) << "Failed to initialize SpellcheckWordIterator"; + return; + } + + if (!contraction_iterator_.IsInitialized() && + !contraction_iterator_.Initialize(&character_attributes_, false)) { + // We failed to initialize the word iterator, return as spelled correctly. + VLOG(1) << "Failed to initialize contraction_iterator_"; + return; + } + + text_iterator_.SetText(text.c_str(), text.size()); + + SpellCheckScope scope(*this); base::string16 word; int word_start; int word_length; - if (!text_iterator_.IsInitialized() && - !text_iterator_.Initialize(&character_attributes_, true)) { - // We failed to initialize text_iterator_, return as spelled correctly. - VLOG(1) << "Failed to initialize SpellcheckWordIterator"; - return; - } + for (auto status = + text_iterator_.GetNextWord(&word, &word_start, &word_length); + status != SpellcheckWordIterator::IS_END_OF_TEXT; + status = text_iterator_.GetNextWord(&word, &word_start, &word_length)) { + if (status == SpellcheckWordIterator::IS_SKIPPABLE) + continue; - base::string16 in_word(text); - text_iterator_.SetText(in_word.c_str(), in_word.size()); - while (text_iterator_.GetNextWord(&word, &word_start, &word_length)) { // Found a word (or a contraction) that the spellchecker can check the // spelling of. - if (SpellCheckWord(word)) + if (SpellCheckWord(scope, word)) continue; // If the given word is a concatenated word of two or more valid words // (e.g. "hello:hello"), we should treat it as a valid word. - if (IsValidContraction(word)) + if (IsValidContraction(scope, word)) continue; blink::WebTextCheckingResult result; @@ -129,16 +147,16 @@ void SpellCheckClient::SpellCheckText( } } -bool SpellCheckClient::SpellCheckWord(const base::string16& word_to_check) { - if (spell_check_.IsEmpty()) - return true; +bool SpellCheckClient::SpellCheckWord( + const SpellCheckScope& scope, + const base::string16& word_to_check) const { + DCHECK(!scope.spell_check_.IsEmpty()); - v8::HandleScope handle_scope(isolate_); v8::Local word = mate::ConvertToV8(isolate_, word_to_check); - v8::Local result = spell_check_.NewHandle()->Call( - provider_.NewHandle(), 1, &word); + v8::Local result = + scope.spell_check_->Call(scope.provider_, 1, &word); - if (result->IsBoolean()) + if (!result.IsEmpty() && result->IsBoolean()) return result->BooleanValue(); else return true; @@ -148,13 +166,9 @@ bool SpellCheckClient::SpellCheckWord(const base::string16& word_to_check) { // This function is a fall-back when the SpellcheckWordIterator class // returns a concatenated word which is not in the selected dictionary // (e.g. "in'n'out") but each word is valid. -bool SpellCheckClient::IsValidContraction(const base::string16& contraction) { - if (!contraction_iterator_.IsInitialized() && - !contraction_iterator_.Initialize(&character_attributes_, false)) { - // We failed to initialize the word iterator, return as spelled correctly. - VLOG(1) << "Failed to initialize contraction_iterator_"; - return true; - } +bool SpellCheckClient::IsValidContraction(const SpellCheckScope& scope, + const base::string16& contraction) { + DCHECK(contraction_iterator_.IsInitialized()); contraction_iterator_.SetText(contraction.c_str(), contraction.length()); @@ -162,13 +176,28 @@ bool SpellCheckClient::IsValidContraction(const base::string16& contraction) { int word_start; int word_length; - while (contraction_iterator_.GetNextWord(&word, &word_start, &word_length)) { - if (!SpellCheckWord(word)) + for (auto status = + contraction_iterator_.GetNextWord(&word, &word_start, &word_length); + status != SpellcheckWordIterator::IS_END_OF_TEXT; + status = contraction_iterator_.GetNextWord(&word, &word_start, + &word_length)) { + if (status == SpellcheckWordIterator::IS_SKIPPABLE) + continue; + + if (!SpellCheckWord(scope, word)) return false; } return true; } +SpellCheckClient::SpellCheckScope::SpellCheckScope( + const SpellCheckClient& client) + : handle_scope_(client.isolate_), + context_scope_( + v8::Local::New(client.isolate_, client.context_)), + provider_(client.provider_.NewHandle()), + spell_check_(client.spell_check_.NewHandle()) {} + } // namespace api } // namespace atom diff --git a/atom/renderer/api/atom_api_spell_check_client.h b/atom/renderer/api/atom_api_spell_check_client.h index ce0533a5b19..acdb2164437 100644 --- a/atom/renderer/api/atom_api_spell_check_client.h +++ b/atom/renderer/api/atom_api_spell_check_client.h @@ -50,22 +50,28 @@ class SpellCheckClient : public blink::WebSpellCheckPanelHostClient, void UpdateSpellingUIWithMisspelledWord( const blink::WebString& word) override; + struct SpellCheckScope { + v8::HandleScope handle_scope_; + v8::Context::Scope context_scope_; + v8::Local provider_; + v8::Local spell_check_; + + explicit SpellCheckScope(const SpellCheckClient& client); + }; + // Check the spelling of text. void SpellCheckText(const base::string16& text, bool stop_at_first_result, std::vector* results); // Call JavaScript to check spelling a word. - bool SpellCheckWord(const base::string16& word_to_check); - - // Find a possible correctly spelled word for a misspelled word. Computes an - // empty string if input misspelled word is too long, there is ambiguity, or - // the correct spelling cannot be determined. - base::string16 GetAutoCorrectionWord(const base::string16& word); + bool SpellCheckWord(const SpellCheckScope& scope, + const base::string16& word_to_check) const; // Returns whether or not the given word is a contraction of valid words // (e.g. "word:word"). - bool IsValidContraction(const base::string16& word); + bool IsValidContraction(const SpellCheckScope& scope, + const base::string16& word); // Represents character attributes used for filtering out characters which // are not supported by this SpellCheck object. @@ -79,9 +85,8 @@ class SpellCheckClient : public blink::WebSpellCheckPanelHostClient, SpellcheckWordIterator text_iterator_; SpellcheckWordIterator contraction_iterator_; - bool auto_spell_correct_turned_on_; - v8::Isolate* isolate_; + v8::Persistent context_; mate::ScopedPersistent provider_; mate::ScopedPersistent spell_check_; diff --git a/atom/renderer/api/atom_api_web_frame.cc b/atom/renderer/api/atom_api_web_frame.cc index fbda47b2976..56cde1e56fb 100644 --- a/atom/renderer/api/atom_api_web_frame.cc +++ b/atom/renderer/api/atom_api_web_frame.cc @@ -13,12 +13,15 @@ #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_frame_visitor.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/platform/WebCache.h" #include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/public/web/WebElement.h" #include "third_party/WebKit/public/web/WebFrameWidget.h" +#include "third_party/WebKit/public/web/WebImeTextSpan.h" #include "third_party/WebKit/public/web/WebInputMethodController.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" #include "third_party/WebKit/public/web/WebScriptExecutionCallback.h" @@ -28,6 +31,32 @@ #include "atom/common/node_includes.h" +namespace mate { + +template <> +struct Converter { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + blink::WebLocalFrame::ScriptExecutionType* out) { + std::string execution_type; + if (!ConvertFromV8(isolate, val, &execution_type)) + return false; + if (execution_type == "asynchronous") { + *out = blink::WebLocalFrame::kAsynchronous; + } else if (execution_type == + "asynchronousBlockingOnload") { + *out = blink::WebLocalFrame::kAsynchronousBlockingOnload; + } else if (execution_type == "synchronous") { + *out = blink::WebLocalFrame::kSynchronous; + } else { + return false; + } + return true; + } +}; + +} // namespace mate + namespace atom { namespace api { @@ -58,6 +87,30 @@ class ScriptExecutionCallback : public blink::WebScriptExecutionCallback { DISALLOW_COPY_AND_ASSIGN(ScriptExecutionCallback); }; +class FrameSpellChecker : public content::RenderFrameVisitor { + public: + explicit FrameSpellChecker(SpellCheckClient* spell_check_client, + content::RenderFrame* main_frame) + : spell_check_client_(spell_check_client), main_frame_(main_frame) {} + ~FrameSpellChecker() override { + spell_check_client_ = nullptr; + main_frame_ = nullptr; + } + bool Visit(content::RenderFrame* render_frame) override { + auto view = render_frame->GetRenderView(); + if (view->GetMainRenderFrame() == main_frame_ || + (render_frame->IsMainFrame() && render_frame == main_frame_)) { + render_frame->GetWebFrame()->SetTextCheckClient(spell_check_client_); + } + return true; + } + + private: + SpellCheckClient* spell_check_client_; + content::RenderFrame* main_frame_; + DISALLOW_COPY_AND_ASSIGN(FrameSpellChecker); +}; + } // namespace WebFrame::WebFrame(v8::Isolate* isolate) @@ -65,6 +118,11 @@ WebFrame::WebFrame(v8::Isolate* isolate) Init(isolate); } +WebFrame::WebFrame(v8::Isolate* isolate, blink::WebLocalFrame* blink_frame) + : web_frame_(blink_frame) { + Init(isolate); +} + WebFrame::~WebFrame() { } @@ -139,10 +197,15 @@ void WebFrame::SetSpellCheckProvider(mate::Arguments* args, return; } - spell_check_client_.reset(new SpellCheckClient( + std::unique_ptr client(new SpellCheckClient( language, auto_spell_correct_turned_on, args->isolate(), provider)); + // Set spellchecker for all live frames in the same process or + // in the sandbox mode for all live sub frames to this WebFrame. + FrameSpellChecker spell_checker( + client.get(), content::RenderFrame::FromWebFrame(web_frame_)); + content::RenderFrame::ForEach(&spell_checker); + spell_check_client_.swap(client); web_frame_->SetSpellCheckPanelHostClient(spell_check_client_.get()); - web_frame_->SetTextCheckClient(spell_check_client_.get()); } void WebFrame::RegisterURLSchemeAsSecure(const std::string& scheme) { @@ -159,6 +222,10 @@ void WebFrame::RegisterURLSchemeAsBypassingCSP(const std::string& scheme) { void WebFrame::RegisterURLSchemeAsPrivileged(const std::string& scheme, mate::Arguments* args) { + // TODO(deepak1556): blink::SchemeRegistry methods should be called + // before any renderer threads are created. Fixing this would break + // current api. Change it with 2.0. + // Read optional flags bool secure = true; bool bypassCSP = true; @@ -203,7 +270,7 @@ void WebFrame::InsertText(const std::string& text) { web_frame_->FrameWidget() ->GetActiveWebInputMethodController() ->CommitText(blink::WebString::FromUTF8(text), - blink::WebVector(), + blink::WebVector(), blink::WebRange(), 0); } @@ -226,6 +293,69 @@ void WebFrame::ExecuteJavaScript(const base::string16& code, callback.release()); } +void WebFrame::ExecuteJavaScriptInIsolatedWorld( + int world_id, + const std::vector& scripts, + mate::Arguments* args) { + std::vector sources; + + for (const auto& script : scripts) { + base::string16 code; + base::string16 url; + int start_line = 1; + script.Get("url", &url); + script.Get("startLine", &start_line); + + if (!script.Get("code", &code)) { + args->ThrowError("Invalid 'code'"); + return; + } + + sources.emplace_back(blink::WebScriptSource( + blink::WebString::FromUTF16(code), + blink::WebURL(GURL(url)), start_line)); + } + + bool has_user_gesture = false; + args->GetNext(&has_user_gesture); + + blink::WebLocalFrame::ScriptExecutionType scriptExecutionType = + blink::WebLocalFrame::kSynchronous; + args->GetNext(&scriptExecutionType); + + ScriptExecutionCallback::CompletionCallback completion_callback; + args->GetNext(&completion_callback); + std::unique_ptr callback( + new ScriptExecutionCallback(completion_callback)); + + web_frame_->RequestExecuteScriptInIsolatedWorld( + world_id, &sources.front(), sources.size(), has_user_gesture, + scriptExecutionType, callback.release()); +} + +void WebFrame::SetIsolatedWorldSecurityOrigin( + int world_id, + const std::string& origin_url) { + web_frame_->SetIsolatedWorldSecurityOrigin( + world_id, + blink::WebSecurityOrigin::CreateFromString( + blink::WebString::FromUTF8(origin_url))); +} + +void WebFrame::SetIsolatedWorldContentSecurityPolicy( + int world_id, + const std::string& security_policy) { + web_frame_->SetIsolatedWorldContentSecurityPolicy( + world_id, blink::WebString::FromUTF8(security_policy)); +} + +void WebFrame::SetIsolatedWorldHumanReadableName( + int world_id, + const std::string& name) { + web_frame_->SetIsolatedWorldHumanReadableName( + world_id, blink::WebString::FromUTF8(name)); +} + // static mate::Handle WebFrame::Create(v8::Isolate* isolate) { return mate::CreateHandle(isolate, new WebFrame(isolate)); @@ -245,6 +375,79 @@ void WebFrame::ClearCache(v8::Isolate* isolate) { base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL); } +v8::Local WebFrame::Opener() const { + blink::WebFrame* frame = web_frame_->Opener(); + if (frame && frame->IsWebLocalFrame()) + return mate::CreateHandle(isolate(), + new WebFrame(isolate(), + frame->ToWebLocalFrame())).ToV8(); + else + return v8::Null(isolate()); +} + +v8::Local WebFrame::Parent() const { + blink::WebFrame* frame = web_frame_->Parent(); + if (frame && frame->IsWebLocalFrame()) + return mate::CreateHandle(isolate(), + new WebFrame(isolate(), + frame->ToWebLocalFrame())).ToV8(); + else + return v8::Null(isolate()); +} + +v8::Local WebFrame::Top() const { + blink::WebFrame* frame = web_frame_->Top(); + if (frame && frame->IsWebLocalFrame()) + return mate::CreateHandle(isolate(), + new WebFrame(isolate(), + frame->ToWebLocalFrame())).ToV8(); + else + return v8::Null(isolate()); +} + +v8::Local WebFrame::FirstChild() const { + blink::WebFrame* frame = web_frame_->FirstChild(); + if (frame && frame->IsWebLocalFrame()) + return mate::CreateHandle(isolate(), + new WebFrame(isolate(), + frame->ToWebLocalFrame())).ToV8(); + else + return v8::Null(isolate()); +} + +v8::Local WebFrame::NextSibling() const { + blink::WebFrame* frame = web_frame_->NextSibling(); + if (frame && frame->IsWebLocalFrame()) + return mate::CreateHandle(isolate(), + new WebFrame(isolate(), + frame->ToWebLocalFrame())).ToV8(); + else + return v8::Null(isolate()); +} + +v8::Local WebFrame::GetFrameForSelector( + const std::string& selector) const { + blink::WebElement element = web_frame_->GetDocument().QuerySelector( + blink::WebString::FromUTF8(selector)); + blink::WebLocalFrame* element_frame = + blink::WebLocalFrame::FromFrameOwnerElement(element); + if (element_frame) + return mate::CreateHandle(isolate(), + new WebFrame(isolate(), element_frame)).ToV8(); + else + return v8::Null(isolate()); +} + +v8::Local WebFrame::FindFrameByName(const std::string& name) const { + blink::WebLocalFrame* local_frame = web_frame_->FindFrameByName( + blink::WebString::FromUTF8(name))->ToWebLocalFrame(); + if (local_frame) + return mate::CreateHandle(isolate(), + new WebFrame(isolate(), local_frame)).ToV8(); + else + return v8::Null(isolate()); +} + // static void WebFrame::BuildPrototype( v8::Isolate* isolate, v8::Local prototype) { @@ -275,10 +478,23 @@ void WebFrame::BuildPrototype( .SetMethod("insertText", &WebFrame::InsertText) .SetMethod("insertCSS", &WebFrame::InsertCSS) .SetMethod("executeJavaScript", &WebFrame::ExecuteJavaScript) + .SetMethod("executeJavaScriptInIsolatedWorld", + &WebFrame::ExecuteJavaScriptInIsolatedWorld) + .SetMethod("setIsolatedWorldSecurityOrigin", + &WebFrame::SetIsolatedWorldSecurityOrigin) + .SetMethod("setIsolatedWorldContentSecurityPolicy", + &WebFrame::SetIsolatedWorldContentSecurityPolicy) + .SetMethod("setIsolatedWorldHumanReadableName", + &WebFrame::SetIsolatedWorldHumanReadableName) .SetMethod("getResourceUsage", &WebFrame::GetResourceUsage) .SetMethod("clearCache", &WebFrame::ClearCache) - // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings - .SetMethod("setZoomLevelLimits", &WebFrame::SetVisualZoomLevelLimits); + .SetMethod("getFrameForSelector", &WebFrame::GetFrameForSelector) + .SetMethod("findFrameByName", &WebFrame::FindFrameByName) + .SetProperty("opener", &WebFrame::Opener) + .SetProperty("parent", &WebFrame::Parent) + .SetProperty("top", &WebFrame::Top) + .SetProperty("firstChild", &WebFrame::FirstChild) + .SetProperty("nextSibling", &WebFrame::NextSibling); } } // namespace api @@ -299,4 +515,4 @@ void Initialize(v8::Local exports, v8::Local unused, } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_renderer_web_frame, Initialize) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_renderer_web_frame, Initialize) diff --git a/atom/renderer/api/atom_api_web_frame.h b/atom/renderer/api/atom_api_web_frame.h index 6c6d0b19f98..fd6a2c3e585 100644 --- a/atom/renderer/api/atom_api_web_frame.h +++ b/atom/renderer/api/atom_api_web_frame.h @@ -7,6 +7,7 @@ #include #include +#include #include "atom/renderer/guest_view_container.h" #include "native_mate/handle.h" @@ -18,6 +19,7 @@ class WebLocalFrame; } namespace mate { +class Dictionary; class Arguments; } @@ -36,6 +38,7 @@ class WebFrame : public mate::Wrappable { private: explicit WebFrame(v8::Isolate* isolate); + explicit WebFrame(v8::Isolate* isolate, blink::WebLocalFrame* blink_frame); ~WebFrame() override; void SetName(const std::string& name); @@ -71,13 +74,35 @@ class WebFrame : public mate::Wrappable { void InsertText(const std::string& text); void InsertCSS(const std::string& css); - // Excecuting scripts. + // Executing scripts. void ExecuteJavaScript(const base::string16& code, mate::Arguments* args); + void ExecuteJavaScriptInIsolatedWorld( + int world_id, + const std::vector& scripts, + mate::Arguments* args); + + // Isolated world related methods + void SetIsolatedWorldSecurityOrigin(int world_id, + const std::string& origin_url); + void SetIsolatedWorldContentSecurityPolicy( + int world_id, + const std::string& security_policy); + void SetIsolatedWorldHumanReadableName(int world_id, + const std::string& name); // Resource related methods blink::WebCache::ResourceTypeStats GetResourceUsage(v8::Isolate* isolate); void ClearCache(v8::Isolate* isolate); + // Frame navigation + v8::Local Opener() const; + v8::Local Parent() const; + v8::Local Top() const; + v8::Local FirstChild() const; + v8::Local NextSibling() const; + v8::Local GetFrameForSelector(const std::string& selector) const; + v8::Local FindFrameByName(const std::string& name) const; + std::unique_ptr spell_check_client_; blink::WebLocalFrame* web_frame_; diff --git a/atom/renderer/atom_render_frame_observer.cc b/atom/renderer/atom_render_frame_observer.cc index d64588a17c8..1971a26c1c5 100644 --- a/atom/renderer/atom_render_frame_observer.cc +++ b/atom/renderer/atom_render_frame_observer.cc @@ -52,7 +52,7 @@ void AtomRenderFrameObserver::DraggableRegionsChanged() { region.draggable = webregion.draggable; regions.push_back(region); } - Send(new AtomViewHostMsg_UpdateDraggableRegions(routing_id(), regions)); + Send(new AtomFrameHostMsg_UpdateDraggableRegions(routing_id(), regions)); } void AtomRenderFrameObserver::WillReleaseScriptContext( diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 82ba9d02435..937e422a0d2 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -25,6 +25,7 @@ #include "atom/common/node_includes.h" #include "atom_natives.h" // NOLINT: This file is generated with js2c +#include "vendor/node/src/tracing/trace_event.h" namespace atom { @@ -95,6 +96,11 @@ void AtomRendererClient::DidCreateScriptContext( node_bindings_->PrepareMessageLoop(); } + // Setup node tracing controller. + if (!node::tracing::TraceEventHelper::GetTracingController()) + node::tracing::TraceEventHelper::SetTracingController( + new v8::TracingController()); + // Setup node environment for each window. node::Environment* env = node_bindings_->CreateEnvironment(context); diff --git a/atom/renderer/atom_sandboxed_renderer_client.cc b/atom/renderer/atom_sandboxed_renderer_client.cc index 07d20bda8b0..33f9c6940b6 100644 --- a/atom/renderer/atom_sandboxed_renderer_client.cc +++ b/atom/renderer/atom_sandboxed_renderer_client.cc @@ -11,6 +11,7 @@ #include "atom/common/native_mate_converters/string16_converter.h" #include "atom/common/native_mate_converters/v8_value_converter.h" #include "atom/common/native_mate_converters/value_converter.h" +#include "atom/common/node_bindings.h" #include "atom/common/options_switches.h" #include "atom/renderer/api/atom_api_renderer_ipc.h" #include "atom/renderer/atom_render_view_observer.h" @@ -136,6 +137,8 @@ class AtomSandboxedRenderViewObserver : public AtomRenderViewObserver { AtomSandboxedRendererClient::AtomSandboxedRendererClient() { + // Explicitly register electron's builtin modules. + NodeBindings::RegisterBuiltinModules(); } AtomSandboxedRendererClient::~AtomSandboxedRendererClient() { diff --git a/atom/renderer/renderer_client_base.cc b/atom/renderer/renderer_client_base.cc index f28099931f3..89af0932d10 100644 --- a/atom/renderer/renderer_client_base.cc +++ b/atom/renderer/renderer_client_base.cc @@ -113,6 +113,11 @@ void RendererClientBase::RenderThreadStarted() { blink::SchemeRegistry::RegisterURLSchemeAsSecure( WTF::String::FromUTF8(scheme.data(), scheme.length())); + // Allow file scheme to handle service worker by default. + // FIXME(zcbenz): Can this be moved elsewhere? + blink::WebSecurityPolicy::RegisterURLSchemeAsAllowingServiceWorkers("file"); + blink::SchemeRegistry::RegisterURLSchemeAsSupportingFetchAPI("file"); + preferences_manager_.reset(new PreferencesManager); #if defined(OS_WIN) @@ -145,10 +150,6 @@ void RendererClientBase::RenderFrameCreated( new ContentSettingsObserver(render_frame); new printing::PrintWebViewHelper(render_frame); - // Allow file scheme to handle service worker by default. - // FIXME(zcbenz): Can this be moved elsewhere? - blink::WebSecurityPolicy::RegisterURLSchemeAsAllowingServiceWorkers("file"); - // This is required for widevine plugin detection provided during runtime. blink::ResetPluginCache(); @@ -166,10 +167,8 @@ void RendererClientBase::RenderViewCreated(content::RenderView* render_view) { 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); + SkColor color = name.empty() ? SK_ColorTRANSPARENT : ParseHexColor(name); web_frame_widget->SetBaseBackgroundColor(color); } } diff --git a/brightray/brightray.gyp b/brightray/brightray.gyp index 065fe818a64..ebd043845f4 100644 --- a/brightray/brightray.gyp +++ b/brightray/brightray.gyp @@ -21,8 +21,10 @@ '<(libchromiumcontent_src_dir)/skia/config', '<(libchromiumcontent_src_dir)/third_party/boringssl/src/include', '<(libchromiumcontent_src_dir)/third_party/skia/include/core', + '<(libchromiumcontent_src_dir)/third_party/skia/include/gpu', '<(libchromiumcontent_src_dir)/third_party/mojo/src', '<(libchromiumcontent_src_dir)/third_party/WebKit', + '<(libchromiumcontent_src_dir)/third_party/khronos', '<(libchromiumcontent_dir)/gen', ], 'direct_dependent_settings': { @@ -33,6 +35,7 @@ '<(libchromiumcontent_src_dir)/skia/config', '<(libchromiumcontent_src_dir)/third_party/boringssl/src/include', '<(libchromiumcontent_src_dir)/third_party/skia/include/core', + '<(libchromiumcontent_src_dir)/third_party/skia/include/gpu', '<(libchromiumcontent_src_dir)/third_party/skia/include/config', '<(libchromiumcontent_src_dir)/third_party/icu/source/common', '<(libchromiumcontent_src_dir)/third_party/mojo/src', @@ -69,6 +72,7 @@ ], 'libraries': [ '-lpthread', + '-latomic', '::DestructorAtExit + g_io_thread_application_locale = LAZY_INSTANCE_INITIALIZER; + +std::string g_application_locale; + +void SetApplicationLocaleOnIOThread(const std::string& locale) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + g_io_thread_application_locale.Get() = locale; +} + } // namespace +// static +void BrowserClient::SetApplicationLocale(const std::string& locale) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + if (!BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::BindOnce(&SetApplicationLocaleOnIOThread, locale))) { + g_io_thread_application_locale.Get() = locale; + } + g_application_locale = locale; +} + BrowserClient* BrowserClient::Get() { return g_browser_client; } @@ -71,6 +97,11 @@ void BrowserClient::GetAdditionalAllowedSchemesForFileSystem( additional_schemes->push_back(content::kChromeUIScheme); } +void BrowserClient::GetAdditionalWebUISchemes( + std::vector* additional_schemes) { + additional_schemes->push_back(content::kChromeDevToolsScheme); +} + net::NetLog* BrowserClient::GetNetLog() { return &net_log_; } @@ -88,4 +119,10 @@ content::DevToolsManagerDelegate* BrowserClient::GetDevToolsManagerDelegate() { return new DevToolsManagerDelegate; } +std::string BrowserClient::GetApplicationLocale() { + if (BrowserThread::CurrentlyOn(BrowserThread::IO)) + return g_io_thread_application_locale.Get(); + return g_application_locale; +} + } // namespace brightray diff --git a/brightray/browser/browser_client.h b/brightray/browser/browser_client.h index cc162242f75..88b132c42c5 100644 --- a/brightray/browser/browser_client.h +++ b/brightray/browser/browser_client.h @@ -20,6 +20,7 @@ class PlatformNotificationService; class BrowserClient : public content::ContentBrowserClient { public: static BrowserClient* Get(); + static void SetApplicationLocale(const std::string& locale); BrowserClient(); ~BrowserClient(); @@ -44,9 +45,12 @@ class BrowserClient : public content::ContentBrowserClient { override; void GetAdditionalAllowedSchemesForFileSystem( std::vector* additional_schemes) override; + void GetAdditionalWebUISchemes( + std::vector* additional_schemes) override; net::NetLog* GetNetLog() override; base::FilePath GetDefaultDownloadDirectory() override; content::DevToolsManagerDelegate* GetDevToolsManagerDelegate() override; + std::string GetApplicationLocale() override; protected: // Subclasses should override this to provide their own BrowserMainParts diff --git a/brightray/browser/browser_context.cc b/brightray/browser/browser_context.cc index 0dbb7174796..43dc5142ed2 100644 --- a/brightray/browser/browser_context.cc +++ b/brightray/browser/browser_context.cc @@ -7,6 +7,7 @@ #include "base/files/file_path.h" #include "base/path_service.h" #include "base/strings/string_util.h" +#include "base/threading/thread_restrictions.h" #include "brightray/browser/brightray_paths.h" #include "brightray/browser/browser_client.h" #include "brightray/browser/inspectable_web_contents_impl.h" @@ -100,10 +101,12 @@ BrowserContext::~BrowserContext() { void BrowserContext::InitPrefs() { auto prefs_path = GetPath().Append(FILE_PATH_LITERAL("Preferences")); + base::ThreadRestrictions::ScopedAllowIO allow_io; PrefServiceFactory prefs_factory; - prefs_factory.SetUserPrefsFile(prefs_path, - JsonPrefStore::GetTaskRunnerForFile( - prefs_path, BrowserThread::GetBlockingPool()).get()); + scoped_refptr pref_store = + base::MakeRefCounted(prefs_path); + pref_store->ReadPrefs(); // Synchronous. + prefs_factory.set_user_prefs(pref_store); auto registry = make_scoped_refptr(new PrefRegistrySimple); RegisterInternalPrefs(registry.get()); @@ -140,8 +143,8 @@ net::URLRequestContextGetter* BrowserContext::CreateRequestContext( return url_request_getter_.get(); } -net::NetworkDelegate* BrowserContext::CreateNetworkDelegate() { - return new NetworkDelegate; +std::unique_ptr BrowserContext::CreateNetworkDelegate() { + return base::MakeUnique(); } std::string BrowserContext::GetMediaDeviceIDSalt() { diff --git a/brightray/browser/browser_context.h b/brightray/browser/browser_context.h index b78a8f36581..6d0348db253 100644 --- a/brightray/browser/browser_context.h +++ b/brightray/browser/browser_context.h @@ -90,7 +90,7 @@ class BrowserContext : public base::RefCounted, virtual void RegisterPrefs(PrefRegistrySimple* pref_registry) {} // URLRequestContextGetter::Delegate: - net::NetworkDelegate* CreateNetworkDelegate() override; + std::unique_ptr CreateNetworkDelegate() override; base::FilePath GetPath() const override; diff --git a/brightray/browser/browser_main_parts.cc b/brightray/browser/browser_main_parts.cc index 18a9df24b10..9313a9fa92b 100644 --- a/brightray/browser/browser_main_parts.cc +++ b/brightray/browser/browser_main_parts.cc @@ -4,20 +4,26 @@ #include "brightray/browser/browser_main_parts.h" -#if defined(OSX_POSIX) +#if defined(OS_POSIX) #include #endif #include #include +#if defined(OS_LINUX) +#include // for g_setenv() +#endif + #include "base/command_line.h" #include "base/feature_list.h" #include "base/message_loop/message_loop.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" +#include "brightray/browser/browser_client.h" #include "brightray/browser/browser_context.h" #include "brightray/browser/devtools_manager_delegate.h" +#include "brightray/browser/media/media_capture_devices_dispatcher.h" #include "brightray/browser/web_ui_controller_factory.h" #include "brightray/common/application_info.h" #include "brightray/common/main_delegate.h" @@ -27,6 +33,8 @@ #include "net/proxy/proxy_resolver_v8.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/material_design/material_design_controller.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/base/ui_base_switches.h" #if defined(USE_AURA) #include "ui/display/display.h" @@ -97,7 +105,7 @@ void OverrideLinuxAppDataPath() { } int BrowserX11ErrorHandler(Display* d, XErrorEvent* error) { - if (!g_in_x11_io_error_handler) { + if (!g_in_x11_io_error_handler && base::ThreadTaskRunnerHandle::IsSet()) { base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::Bind(&ui::LogErrorEventDescription, d, *error)); } @@ -218,10 +226,29 @@ void BrowserMainParts::ToolkitInitialized() { } void BrowserMainParts::PreMainMessageLoopStart() { -#if defined(OS_MACOSX) - l10n_util::OverrideLocaleWithCocoaLocale(); + // Initialize ui::ResourceBundle. + ui::ResourceBundle::InitSharedInstanceWithLocale( + "", nullptr, ui::ResourceBundle::DO_NOT_LOAD_COMMON_RESOURCES); + auto cmd_line = base::CommandLine::ForCurrentProcess(); + if (cmd_line->HasSwitch(switches::kLang)) { + const std::string locale = cmd_line->GetSwitchValueASCII(switches::kLang); + const base::FilePath locale_file_path = + ui::ResourceBundle::GetSharedInstance().GetLocaleFilePath(locale, true); + if (!locale_file_path.empty()) { + custom_locale_ = locale; +#if defined(OS_LINUX) + /* When built with USE_GLIB, libcc's GetApplicationLocaleInternal() uses + * glib's g_get_language_names(), which keys off of getenv("LC_ALL") */ + g_setenv("LC_ALL", custom_locale_.c_str(), TRUE); #endif - InitializeResourceBundle(""); + } + } + +#if defined(OS_MACOSX) + if (custom_locale_.empty()) + l10n_util::OverrideLocaleWithCocoaLocale(); +#endif + LoadResourceBundle(custom_locale_); #if defined(OS_MACOSX) InitializeMainNib(); #endif @@ -268,9 +295,19 @@ int BrowserMainParts::PreCreateThreads() { #endif #endif + // Force MediaCaptureDevicesDispatcher to be created on UI thread. + MediaCaptureDevicesDispatcher::GetInstance(); + if (!views::LayoutProvider::Get()) layout_provider_.reset(new views::LayoutProvider()); + // Initialize the app locale. + BrowserClient::SetApplicationLocale( + l10n_util::GetApplicationLocale(custom_locale_)); + + // Manage global state of net and other IO thread related. + io_thread_ = base::MakeUnique(); + return 0; } @@ -279,6 +316,8 @@ void BrowserMainParts::PostDestroyThreads() { device::BluetoothAdapterFactory::Shutdown(); bluez::DBusBluezManagerWrapperLinux::Shutdown(); #endif + + io_thread_.reset(); } } // namespace brightray diff --git a/brightray/browser/browser_main_parts.h b/brightray/browser/browser_main_parts.h index 45c69f15fb0..f69682ce516 100644 --- a/brightray/browser/browser_main_parts.h +++ b/brightray/browser/browser_main_parts.h @@ -6,11 +6,13 @@ #define BRIGHTRAY_BROWSER_BROWSER_MAIN_PARTS_H_ #include +#include #include "base/compiler_specific.h" #include "base/macros.h" #include "base/path_service.h" #include "brightray/browser/brightray_paths.h" +#include "brightray/browser/io_thread.h" #include "content/public/browser/browser_main_parts.h" #include "ui/views/layout/layout_provider.h" @@ -50,6 +52,8 @@ class BrowserMainParts : public content::BrowserMainParts { void OverrideAppLogsPath(); #endif + std::unique_ptr io_thread_; + #if defined(TOOLKIT_VIEWS) std::unique_ptr views_delegate_; #endif @@ -59,6 +63,7 @@ class BrowserMainParts : public content::BrowserMainParts { #endif std::unique_ptr layout_provider_; + std::string custom_locale_; DISALLOW_COPY_AND_ASSIGN(BrowserMainParts); }; diff --git a/brightray/browser/devtools_manager_delegate.cc b/brightray/browser/devtools_manager_delegate.cc index f6ba60f9f58..daec9b12e8e 100644 --- a/brightray/browser/devtools_manager_delegate.cc +++ b/brightray/browser/devtools_manager_delegate.cc @@ -90,9 +90,7 @@ void DevToolsManagerDelegate::StartHttpHandler() { CreateSocketFactory(), std::string(), base::FilePath(), - base::FilePath(), - std::string(), - GetBrightrayUserAgent()); + base::FilePath()); } DevToolsManagerDelegate::DevToolsManagerDelegate() diff --git a/brightray/browser/inspectable_web_contents.h b/brightray/browser/inspectable_web_contents.h index 28c00f6d72d..7b0a010b9e2 100644 --- a/brightray/browser/inspectable_web_contents.h +++ b/brightray/browser/inspectable_web_contents.h @@ -37,6 +37,7 @@ class InspectableWebContents { virtual void SetDelegate(InspectableWebContentsDelegate* delegate) = 0; virtual InspectableWebContentsDelegate* GetDelegate() const = 0; + virtual void SetDevToolsWebContents(content::WebContents* devtools) = 0; virtual void SetDockState(const std::string& state) = 0; virtual void ShowDevTools() = 0; virtual void CloseDevTools() = 0; diff --git a/brightray/browser/inspectable_web_contents_impl.cc b/brightray/browser/inspectable_web_contents_impl.cc index 4d0826990c8..bb51298d957 100644 --- a/brightray/browser/inspectable_web_contents_impl.cc +++ b/brightray/browser/inspectable_web_contents_impl.cc @@ -242,13 +242,14 @@ InspectableWebContentsImpl::InspectableWebContentsImpl( InspectableWebContentsImpl::~InspectableWebContentsImpl() { // Unsubscribe from devtools and Clean up resources. - if (devtools_web_contents_) { - devtools_web_contents_->SetDelegate(nullptr); + if (GetDevToolsWebContents()) { + if (managed_devtools_web_contents_) + managed_devtools_web_contents_->SetDelegate(nullptr); // Calling this also unsubscribes the observer, so WebContentsDestroyed // won't be called again. WebContentsDestroyed(); } - // Let destructor destroy devtools_web_contents_. + // Let destructor destroy managed_devtools_web_contents_. } InspectableWebContentsView* InspectableWebContentsImpl::GetView() const { @@ -261,7 +262,10 @@ content::WebContents* InspectableWebContentsImpl::GetWebContents() const { content::WebContents* InspectableWebContentsImpl::GetDevToolsWebContents() const { - return devtools_web_contents_.get(); + if (external_devtools_web_contents_) + return external_devtools_web_contents_; + else + return managed_devtools_web_contents_.get(); } void InspectableWebContentsImpl::InspectElement(int x, int y) { @@ -288,43 +292,56 @@ void InspectableWebContentsImpl::SetDockState(const std::string& state) { } } +void InspectableWebContentsImpl::SetDevToolsWebContents( + content::WebContents* devtools) { + if (!managed_devtools_web_contents_) + external_devtools_web_contents_ = devtools; +} + void InspectableWebContentsImpl::ShowDevTools() { + if (embedder_message_dispatcher_) { + if (managed_devtools_web_contents_) + view_->ShowDevTools(); + return; + } + // Show devtools only after it has done loading, this is to make sure the // SetIsDocked is called *BEFORE* ShowDevTools. - if (!devtools_web_contents_) { - embedder_message_dispatcher_.reset( - DevToolsEmbedderMessageDispatcher::CreateForDevToolsFrontend(this)); + embedder_message_dispatcher_.reset( + DevToolsEmbedderMessageDispatcher::CreateForDevToolsFrontend(this)); - content::WebContents::CreateParams create_params( - web_contents_->GetBrowserContext()); - devtools_web_contents_.reset(content::WebContents::Create(create_params)); - - Observe(devtools_web_contents_.get()); - devtools_web_contents_->SetDelegate(this); - - AttachTo(content::DevToolsAgentHost::GetOrCreateFor(web_contents_.get())); - - devtools_web_contents_->GetController().LoadURL( - GetDevToolsURL(can_dock_), - content::Referrer(), - ui::PAGE_TRANSITION_AUTO_TOPLEVEL, - std::string()); - } else { - view_->ShowDevTools(); + if (!external_devtools_web_contents_) { // no external devtools + managed_devtools_web_contents_.reset( + content::WebContents::Create( + content::WebContents::CreateParams( + web_contents_->GetBrowserContext()))); + managed_devtools_web_contents_->SetDelegate(this); } + + Observe(GetDevToolsWebContents()); + AttachTo(content::DevToolsAgentHost::GetOrCreateFor(web_contents_.get())); + + GetDevToolsWebContents()->GetController().LoadURL( + GetDevToolsURL(can_dock_), + content::Referrer(), + ui::PAGE_TRANSITION_AUTO_TOPLEVEL, + std::string()); } void InspectableWebContentsImpl::CloseDevTools() { - if (devtools_web_contents_) { + if (GetDevToolsWebContents()) { frontend_loaded_ = false; - view_->CloseDevTools(); - devtools_web_contents_.reset(); + if (managed_devtools_web_contents_) { + view_->CloseDevTools(); + managed_devtools_web_contents_.reset(); + } + embedder_message_dispatcher_.reset(); web_contents_->Focus(); } } bool InspectableWebContentsImpl::IsDevToolsViewShowing() { - return devtools_web_contents_ && view_->IsDevToolsViewShowing(); + return managed_devtools_web_contents_ && view_->IsDevToolsViewShowing(); } void InspectableWebContentsImpl::AttachTo( @@ -347,7 +364,7 @@ void InspectableWebContentsImpl::CallClientFunction( const base::Value* arg1, const base::Value* arg2, const base::Value* arg3) { - if (!devtools_web_contents_) + if (!GetDevToolsWebContents()) return; std::string javascript = function_name + "("; @@ -365,7 +382,7 @@ void InspectableWebContentsImpl::CallClientFunction( } } javascript.append(");"); - devtools_web_contents_->GetMainFrame()->ExecuteJavaScript( + GetDevToolsWebContents()->GetMainFrame()->ExecuteJavaScript( base::UTF8ToUTF16(javascript)); } @@ -400,7 +417,8 @@ void InspectableWebContentsImpl::CloseWindow() { void InspectableWebContentsImpl::LoadCompleted() { frontend_loaded_ = true; - view_->ShowDevTools(); + if (managed_devtools_web_contents_) + view_->ShowDevTools(); // If the devtools can dock, "SetIsDocked" will be called by devtools itself. if (!can_dock_) { @@ -415,7 +433,7 @@ void InspectableWebContentsImpl::LoadCompleted() { } base::string16 javascript = base::UTF8ToUTF16( "Components.dockController.setDockSide(\"" + dock_state_ + "\");"); - devtools_web_contents_->GetMainFrame()->ExecuteJavaScript(javascript); + GetDevToolsWebContents()->GetMainFrame()->ExecuteJavaScript(javascript); } if (view_->GetDelegate()) @@ -428,15 +446,17 @@ void InspectableWebContentsImpl::SetInspectedPageBounds(const gfx::Rect& rect) { return; contents_resizing_strategy_.CopyFrom(strategy); - view_->SetContentsResizingStrategy(contents_resizing_strategy_); + if (managed_devtools_web_contents_) + view_->SetContentsResizingStrategy(contents_resizing_strategy_); } void InspectableWebContentsImpl::InspectElementCompleted() { } void InspectableWebContentsImpl::InspectedURLChanged(const std::string& url) { - view_->SetTitle(base::UTF8ToUTF16(base::StringPrintf(kTitleFormat, - url.c_str()))); + if (managed_devtools_web_contents_) + view_->SetTitle(base::UTF8ToUTF16(base::StringPrintf(kTitleFormat, + url.c_str()))); } void InspectableWebContentsImpl::LoadNetworkResource( @@ -452,8 +472,8 @@ void InspectableWebContentsImpl::LoadNetworkResource( return; } - auto browser_context = - static_cast(devtools_web_contents_->GetBrowserContext()); + auto* browser_context = static_cast( + GetDevToolsWebContents()->GetBrowserContext()); net::URLFetcher* fetcher = (net::URLFetcher::Create(gurl, net::URLFetcher::GET, this)).release(); @@ -468,7 +488,8 @@ void InspectableWebContentsImpl::LoadNetworkResource( void InspectableWebContentsImpl::SetIsDocked(const DispatchCallback& callback, bool docked) { - view_->SetIsDocked(docked); + if (managed_devtools_web_contents_) + view_->SetIsDocked(docked); if (!callback.is_null()) callback.Run(nullptr); } @@ -587,7 +608,7 @@ void InspectableWebContentsImpl::GetPreferences( void InspectableWebContentsImpl::SetPreference(const std::string& name, const std::string& value) { DictionaryPrefUpdate update(pref_service_, kDevToolsPreferences); - update.Get()->SetStringWithoutPathExpansion(name, value); + update.Get()->SetKey(name, base::Value(value)); } void InspectableWebContentsImpl::RemovePreference(const std::string& name) { @@ -640,7 +661,7 @@ void InspectableWebContentsImpl::DispatchProtocolMessage( if (message.length() < kMaxMessageChunkSize) { base::string16 javascript = base::UTF8ToUTF16( "DevToolsAPI.dispatchMessage(" + message + ");"); - devtools_web_contents_->GetMainFrame()->ExecuteJavaScript(javascript); + GetDevToolsWebContents()->GetMainFrame()->ExecuteJavaScript(javascript); return; } @@ -664,13 +685,15 @@ void InspectableWebContentsImpl::RenderFrameHostChanged( frontend_host_.reset(content::DevToolsFrontendHost::Create( new_host, base::Bind(&InspectableWebContentsImpl::HandleMessageFromDevToolsFrontend, - base::Unretained(this)))); + weak_factory_.GetWeakPtr()))); } void InspectableWebContentsImpl::WebContentsDestroyed() { frontend_loaded_ = false; + external_devtools_web_contents_ = nullptr; Observe(nullptr); Detach(); + embedder_message_dispatcher_.reset(); for (const auto& pair : pending_requests_) delete pair.first; @@ -758,7 +781,7 @@ void InspectableWebContentsImpl::ReadyToCommitNavigation( content::NavigationHandle* navigation_handle) { if (navigation_handle->IsInMainFrame()) { if (navigation_handle->GetRenderFrameHost() == - devtools_web_contents_->GetMainFrame() && + GetDevToolsWebContents()->GetMainFrame() && frontend_host_) { return; } diff --git a/brightray/browser/inspectable_web_contents_impl.h b/brightray/browser/inspectable_web_contents_impl.h index c2f359bcd29..6c1b34d8635 100644 --- a/brightray/browser/inspectable_web_contents_impl.h +++ b/brightray/browser/inspectable_web_contents_impl.h @@ -48,6 +48,7 @@ class InspectableWebContentsImpl : void SetDelegate(InspectableWebContentsDelegate* delegate) override; InspectableWebContentsDelegate* GetDelegate() const override; + void SetDevToolsWebContents(content::WebContents* devtools) override; void SetDockState(const std::string& state) override; void ShowDevTools() override; void CloseDevTools() override; @@ -192,7 +193,13 @@ class InspectableWebContentsImpl : PrefService* pref_service_; // weak reference. std::unique_ptr web_contents_; - std::unique_ptr devtools_web_contents_; + + // The default devtools created by this class when we don't have an external + // one assigned by SetDevToolsWebContents. + std::unique_ptr managed_devtools_web_contents_; + // The external devtools assigned by SetDevToolsWebContents. + content::WebContents* external_devtools_web_contents_ = nullptr; + std::unique_ptr view_; using ExtensionsAPIs = std::map; diff --git a/brightray/browser/io_thread.cc b/brightray/browser/io_thread.cc new file mode 100644 index 00000000000..f20fb0bf9eb --- /dev/null +++ b/brightray/browser/io_thread.cc @@ -0,0 +1,48 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "brightray/browser/io_thread.h" + +#include "content/public/browser/browser_thread.h" +#include "net/proxy/proxy_service.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_builder.h" + +#if defined(USE_NSS_CERTS) +#include "net/cert_net/nss_ocsp.h" +#endif + +using content::BrowserThread; + +namespace brightray { + +IOThread::IOThread() { + BrowserThread::SetIOThreadDelegate(this); +} + +IOThread::~IOThread() { + BrowserThread::SetIOThreadDelegate(nullptr); +} + +void IOThread::Init() { + net::URLRequestContextBuilder builder; + builder.set_proxy_service(net::ProxyService::CreateDirect()); + builder.DisableHttpCache(); + url_request_context_ = builder.Build(); + +#if defined(USE_NSS_CERTS) + net::SetMessageLoopForNSSHttpIO(); + net::SetURLRequestContextForNSSHttpIO(url_request_context_.get()); +#endif +} + +void IOThread::CleanUp() { +#if defined(USE_NSS_CERTS) + net::ShutdownNSSHttpIO(); + net::SetURLRequestContextForNSSHttpIO(nullptr); +#endif + url_request_context_.reset(); +} + +} // namespace brightray diff --git a/brightray/browser/io_thread.h b/brightray/browser/io_thread.h new file mode 100644 index 00000000000..c04f09fa8a9 --- /dev/null +++ b/brightray/browser/io_thread.h @@ -0,0 +1,37 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef BRIGHTRAY_BROWSER_IO_THREAD_H_ +#define BRIGHTRAY_BROWSER_IO_THREAD_H_ + +#include + +#include "base/macros.h" +#include "content/public/browser/browser_thread_delegate.h" + +namespace net { +class URLRequestContext; +} + +namespace brightray { + +class IOThread : public content::BrowserThreadDelegate { + public: + IOThread(); + ~IOThread() override; + + protected: + // BrowserThreadDelegate Implementation, runs on the IO thread. + void Init() override; + void CleanUp() override; + + private: + std::unique_ptr url_request_context_; + + DISALLOW_COPY_AND_ASSIGN(IOThread); +}; + +} // namespace brightray + +#endif // BRIGHTRAY_BROWSER_IO_THREAD_H_ diff --git a/brightray/browser/linux/libnotify_notification.cc b/brightray/browser/linux/libnotify_notification.cc index db59e451388..143a5fdea3e 100644 --- a/brightray/browser/linux/libnotify_notification.cc +++ b/brightray/browser/linux/libnotify_notification.cc @@ -4,9 +4,12 @@ #include "brightray/browser/linux/libnotify_notification.h" +#include +#include #include #include "base/files/file_enumerator.h" +#include "base/logging.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "brightray/browser/notification_delegate.h" @@ -20,31 +23,26 @@ namespace { LibNotifyLoader libnotify_loader_; +const std::set& GetServerCapabilities() { + static std::set caps; + if (caps.empty()) { + auto capabilities = libnotify_loader_.notify_get_server_caps(); + for (auto l=capabilities; l != nullptr; l=l->next) + caps.insert(static_cast(l->data)); + g_list_free_full(capabilities, g_free); + } + return caps; +} + bool HasCapability(const std::string& capability) { - bool result = false; - GList* capabilities = libnotify_loader_.notify_get_server_caps(); - - if (g_list_find_custom(capabilities, capability.c_str(), - (GCompareFunc)g_strcmp0) != NULL) - result = true; - - g_list_free_full(capabilities, g_free); - - return result; + return GetServerCapabilities().count(capability) != 0; } bool NotifierSupportsActions() { if (getenv("ELECTRON_USE_UBUNTU_NOTIFIER")) return false; - static bool notify_has_result = false; - static bool notify_result = false; - - if (notify_has_result) - return notify_result; - - notify_result = HasCapability("actions"); - return notify_result; + return HasCapability("actions"); } void log_and_clear_error(GError* error, const char* context) { @@ -63,10 +61,12 @@ bool LibnotifyNotification::Initialize() { !libnotify_loader_.Load("libnotify.so.5") && !libnotify_loader_.Load("libnotify.so.1") && !libnotify_loader_.Load("libnotify.so")) { + LOG(WARNING) << "Unable to find libnotify; notifications disabled"; return false; } if (!libnotify_loader_.notify_is_initted() && !libnotify_loader_.notify_init(GetApplicationName().c_str())) { + LOG(WARNING) << "Unable to initialize libnotify; notifications disabled"; return false; } return true; diff --git a/brightray/browser/mac/cocoa_notification.h b/brightray/browser/mac/cocoa_notification.h index dcc430099fe..1cdd7e33b42 100644 --- a/brightray/browser/mac/cocoa_notification.h +++ b/brightray/browser/mac/cocoa_notification.h @@ -7,6 +7,7 @@ #import +#include #include #include @@ -27,13 +28,17 @@ class CocoaNotification : public Notification { void NotificationDisplayed(); void NotificationReplied(const std::string& reply); - void NotificationButtonClicked(); + void NotificationActivated(); + void NotificationActivated(NSUserNotificationAction* action); NSUserNotification* notification() const { return notification_; } private: + void LogAction(const char* action); + base::scoped_nsobject notification_; - int action_index_; + std::map additional_action_indices_; + unsigned action_index_; DISALLOW_COPY_AND_ASSIGN(CocoaNotification); }; diff --git a/brightray/browser/mac/cocoa_notification.mm b/brightray/browser/mac/cocoa_notification.mm index 5e51974b3e4..5201baa363d 100644 --- a/brightray/browser/mac/cocoa_notification.mm +++ b/brightray/browser/mac/cocoa_notification.mm @@ -28,14 +28,19 @@ CocoaNotification::~CocoaNotification() { void CocoaNotification::Show(const NotificationOptions& options) { notification_.reset([[NSUserNotification alloc] init]); + + NSString* identifier = [NSString stringWithFormat:@"ElectronNotification%d", g_identifier_++]; + [notification_ setTitle:base::SysUTF16ToNSString(options.title)]; [notification_ setSubtitle:base::SysUTF16ToNSString(options.subtitle)]; [notification_ setInformativeText:base::SysUTF16ToNSString(options.msg)]; - [notification_ setIdentifier:[NSString stringWithFormat:@"%s%d", "ElectronNotification", g_identifier_]]; - g_identifier_++; + [notification_ setIdentifier:identifier]; - if ([notification_ respondsToSelector:@selector(setContentImage:)] && - !options.icon.drawsNothing()) { + if (getenv("ELECTRON_DEBUG_NOTIFICATIONS")) { + LOG(INFO) << "Notification created (" << [identifier UTF8String] << ")"; + } + + if (!options.icon.drawsNothing()) { NSImage* image = skia::SkBitmapToNSImageWithColorSpace( options.icon, base::mac::GetGenericRGBColorSpace()); [notification_ setContentImage:image]; @@ -43,29 +48,50 @@ void CocoaNotification::Show(const NotificationOptions& options) { if (options.silent) { [notification_ setSoundName:nil]; - } else if (options.sound != nil) { - [notification_ setSoundName:base::SysUTF16ToNSString(options.sound)]; - } else { + } else if (options.sound.empty()) { [notification_ setSoundName:NSUserNotificationDefaultSoundName]; + } else { + [notification_ setSoundName:base::SysUTF16ToNSString(options.sound)]; } [notification_ setHasActionButton:false]; int i = 0; + action_index_ = UINT_MAX; + NSMutableArray* additionalActions = [[[NSMutableArray alloc] init] autorelease]; for (const auto& action : options.actions) { if (action.type == base::ASCIIToUTF16("button")) { - [notification_ setHasActionButton:true]; - [notification_ setActionButtonTitle:base::SysUTF16ToNSString(action.text)]; - action_index_ = i; + if (action_index_ == UINT_MAX) { + // First button observed is the displayed action + [notification_ setHasActionButton:true]; + [notification_ setActionButtonTitle:base::SysUTF16ToNSString(action.text)]; + action_index_ = i; + } else { + // All of the rest are appended to the list of additional actions + NSString* actionIdentifier = [NSString stringWithFormat:@"%@Action%d", identifier, i]; + NSUserNotificationAction* notificationAction = + [NSUserNotificationAction actionWithIdentifier:actionIdentifier + title:base::SysUTF16ToNSString(action.text)]; + [additionalActions addObject:notificationAction]; + additional_action_indices_.insert(std::make_pair(base::SysNSStringToUTF8(actionIdentifier), i)); + } } i++; } + if ([additionalActions count] > 0 && + [notification_ respondsToSelector:@selector(setAdditionalActions:)]) { + [notification_ setAdditionalActions:additionalActions]; // Requires macOS 10.10 + } if (options.has_reply) { [notification_ setResponsePlaceholder:base::SysUTF16ToNSString(options.reply_placeholder)]; [notification_ setHasReplyButton:true]; } + if (!options.close_button_text.empty()) { + [notification_ setOtherButtonTitle:base::SysUTF16ToNSString(options.close_button_text)]; + } + [NSUserNotificationCenter.defaultUserNotificationCenter deliverNotification:notification_]; } @@ -74,22 +100,55 @@ void CocoaNotification::Dismiss() { if (notification_) [NSUserNotificationCenter.defaultUserNotificationCenter removeDeliveredNotification:notification_]; + NotificationDismissed(); + + this->LogAction("dismissed"); } void CocoaNotification::NotificationDisplayed() { if (delegate()) delegate()->NotificationDisplayed(); + + this->LogAction("displayed"); } void CocoaNotification::NotificationReplied(const std::string& reply) { if (delegate()) delegate()->NotificationReplied(reply); + + this->LogAction("replied to"); } -void CocoaNotification::NotificationButtonClicked() { +void CocoaNotification::NotificationActivated() { if (delegate()) delegate()->NotificationAction(action_index_); + + this->LogAction("button clicked"); +} + +void CocoaNotification::NotificationActivated(NSUserNotificationAction* action) { + if (delegate()) { + unsigned index = action_index_; + std::string identifier = base::SysNSStringToUTF8(action.identifier); + for (const auto& it : additional_action_indices_) { + if (it.first == identifier) { + index = it.second; + break; + } + } + + delegate()->NotificationAction(index); + } + + this->LogAction("button clicked"); +} + +void CocoaNotification::LogAction(const char* action) { + if (getenv("ELECTRON_DEBUG_NOTIFICATIONS")) { + NSString* identifier = [notification_ valueForKey:@"identifier"]; + LOG(INFO) << "Notification " << action << " (" << [identifier UTF8String] << ")"; + } } } // namespace brightray diff --git a/brightray/browser/mac/notification_center_delegate.mm b/brightray/browser/mac/notification_center_delegate.mm index d8a8c901834..1cb3dc9a33d 100644 --- a/brightray/browser/mac/notification_center_delegate.mm +++ b/brightray/browser/mac/notification_center_delegate.mm @@ -28,13 +28,21 @@ - (void)userNotificationCenter:(NSUserNotificationCenter*)center didActivateNotification:(NSUserNotification *)notif { auto notification = presenter_->GetNotification(notif); + + if (getenv("ELECTRON_DEBUG_NOTIFICATIONS")) { + LOG(INFO) << "Notification activated (" << [notif.identifier UTF8String] << ")"; + } + if (notification) { - if (notif.activationType == NSUserNotificationActivationTypeReplied) { - notification->NotificationReplied([notif.response.string UTF8String]); + // Ref: https://developer.apple.com/documentation/foundation/nsusernotificationactivationtype?language=objc + if (notif.activationType == NSUserNotificationActivationTypeContentsClicked) { + notification->NotificationClicked(); } else if (notif.activationType == NSUserNotificationActivationTypeActionButtonClicked) { - notification->NotificationButtonClicked(); - } else if (notif.activationType == NSUserNotificationActivationTypeContentsClicked) { - notification->NotificationClicked(); + notification->NotificationActivated(); + } else if (notif.activationType == NSUserNotificationActivationTypeReplied) { + notification->NotificationReplied([notif.response.string UTF8String]); + } else if (notif.activationType == NSUserNotificationActivationTypeAdditionalActionClicked) { + notification->NotificationActivated([notif additionalActivationAction]); } } } diff --git a/brightray/browser/mac/notification_presenter_mac.mm b/brightray/browser/mac/notification_presenter_mac.mm index b8903057346..436584ac655 100644 --- a/brightray/browser/mac/notification_presenter_mac.mm +++ b/brightray/browser/mac/notification_presenter_mac.mm @@ -22,6 +22,11 @@ CocoaNotification* NotificationPresenterMac::GetNotification( isEqual:ns_notification.identifier]) return native_notification; } + + if (getenv("ELECTRON_DEBUG_NOTIFICATIONS")) { + LOG(INFO) << "Could not find notification for " << [ns_notification.identifier UTF8String]; + } + return nullptr; } diff --git a/brightray/browser/media/media_capture_devices_dispatcher.cc b/brightray/browser/media/media_capture_devices_dispatcher.cc index aafb37594b6..8d122890b77 100644 --- a/brightray/browser/media/media_capture_devices_dispatcher.cc +++ b/brightray/browser/media/media_capture_devices_dispatcher.cc @@ -44,8 +44,7 @@ MediaCaptureDevicesDispatcher::MediaCaptureDevicesDispatcher() : is_device_enumeration_disabled_(false) { // MediaCaptureDevicesDispatcher is a singleton. It should be created on // UI thread. - // FIXME: Ensure the DCHECK doesn't fail and then re-enable - // DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); } MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() {} diff --git a/brightray/browser/net/devtools_network_protocol_handler.cc b/brightray/browser/net/devtools_network_protocol_handler.cc index 715b2c540ec..caf738435fd 100644 --- a/brightray/browser/net/devtools_network_protocol_handler.cc +++ b/brightray/browser/net/devtools_network_protocol_handler.cc @@ -72,10 +72,10 @@ std::unique_ptr CreateFailureResponse(int id, const std::string& param) { auto response = base::MakeUnique(); auto error_object = base::MakeUnique(); - response->Set(kError, std::move(error_object)); error_object->SetInteger(params::kErrorCode, kErrorInvalidParams); error_object->SetString(params::kErrorMessage, base::StringPrintf("Missing or Invalid '%s' parameter", param.c_str())); + response->Set(kError, std::move(error_object)); return response; } diff --git a/brightray/browser/net/devtools_network_transaction.h b/brightray/browser/net/devtools_network_transaction.h index d038006a6fe..d7317e47ac4 100644 --- a/brightray/browser/net/devtools_network_transaction.h +++ b/brightray/browser/net/devtools_network_transaction.h @@ -12,6 +12,7 @@ #include "net/base/completion_callback.h" #include "net/base/load_states.h" #include "net/base/request_priority.h" +#include "net/http/http_raw_request_headers.h" #include "net/http/http_transaction.h" namespace brightray { @@ -66,6 +67,10 @@ class DevToolsNetworkTransaction : public net::HttpTransaction { int ResumeNetworkStart() override; void GetConnectionAttempts(net::ConnectionAttempts* out) const override; + // FIXME(torycl) Implement these methods properly + void SetRequestHeadersCallback(net::RequestHeadersCallback callback) {} + void SetResponseHeadersCallback(net::ResponseHeadersCallback callback) {} + private: void Fail(); bool CheckFailed(); diff --git a/brightray/browser/notification.h b/brightray/browser/notification.h index 69efcff386f..1b81dda5a18 100644 --- a/brightray/browser/notification.h +++ b/brightray/browser/notification.h @@ -28,13 +28,14 @@ struct NotificationOptions { base::string16 subtitle; base::string16 msg; std::string tag; + bool silent; GURL icon_url; SkBitmap icon; - bool silent; bool has_reply; base::string16 reply_placeholder; base::string16 sound; std::vector actions; + base::string16 close_button_text; }; class Notification { diff --git a/brightray/browser/url_request_context_getter.cc b/brightray/browser/url_request_context_getter.cc index 3003fbc7d23..6d9f8a2dab6 100644 --- a/brightray/browser/url_request_context_getter.cc +++ b/brightray/browser/url_request_context_getter.cc @@ -12,6 +12,7 @@ #include "base/strings/string_util.h" #include "base/threading/sequenced_worker_pool.h" #include "base/threading/worker_pool.h" +#include "brightray/browser/browser_client.h" #include "brightray/browser/net/devtools_network_controller_handle.h" #include "brightray/browser/net/devtools_network_transaction_factory.h" #include "brightray/browser/net/require_ct_delegate.h" @@ -28,7 +29,6 @@ #include "net/cert/ct_log_verifier.h" #include "net/cert/ct_policy_enforcer.h" #include "net/cert/multi_log_ct_verifier.h" -#include "net/cookies/cookie_monster.h" #include "net/dns/mapped_host_resolver.h" #include "net/http/http_auth_filter.h" #include "net/http/http_auth_handler_factory.h" @@ -53,13 +53,8 @@ #include "net/url_request/url_request_intercepting_job_factory.h" #include "net/url_request/url_request_job_factory_impl.h" #include "storage/browser/quota/special_storage_policy.h" -#include "ui/base/l10n/l10n_util.h" #include "url/url_constants.h" -#if defined(USE_NSS_CERTS) -#include "net/cert_net/nss_ocsp.h" -#endif - using content::BrowserThread; namespace brightray { @@ -104,8 +99,7 @@ URLRequestContextGetter::Delegate::CreateHttpCacheBackendFactory( net::DISK_CACHE, net::CACHE_BACKEND_DEFAULT, cache_path, - max_size, - BrowserThread::GetTaskRunnerForThread(BrowserThread::CACHE)); + max_size); } std::unique_ptr @@ -142,7 +136,7 @@ URLRequestContextGetter::URLRequestContextGetter( protocol_interceptors_(std::move(protocol_interceptors)), job_factory_(nullptr) { // Must first be created on the UI thread. - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK_CURRENTLY_ON(BrowserThread::UI); if (protocol_handlers) std::swap(protocol_handlers_, *protocol_handlers); @@ -158,9 +152,21 @@ URLRequestContextGetter::URLRequestContextGetter( } URLRequestContextGetter::~URLRequestContextGetter() { -#if defined(USE_NSS_CERTS) - net::SetURLRequestContextForNSSHttpIO(NULL); -#endif +} + +void URLRequestContextGetter::OnCookieChanged( + const net::CanonicalCookie& cookie, + net::CookieStore::ChangeCause cause) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + + if (!delegate_) + return; + + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::BindOnce( + &Delegate::NotifyCookieChange, base::Unretained(delegate_), cookie, + !(cause == net::CookieStore::ChangeCause::INSERTED), cause)); } net::HostResolver* URLRequestContextGetter::host_resolver() { @@ -168,47 +174,47 @@ net::HostResolver* URLRequestContextGetter::host_resolver() { } net::URLRequestContext* URLRequestContextGetter::GetURLRequestContext() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK_CURRENTLY_ON(BrowserThread::IO); if (!url_request_context_.get()) { ct_delegate_.reset(new RequireCTDelegate); auto& command_line = *base::CommandLine::ForCurrentProcess(); url_request_context_.reset(new net::URLRequestContext); -#if defined(USE_NSS_CERTS) - net::SetURLRequestContextForNSSHttpIO(url_request_context_.get()); -#endif - // --log-net-log if (net_log_) { net_log_->StartLogging(); url_request_context_->set_net_log(net_log_); } - network_delegate_.reset(delegate_->CreateNetworkDelegate()); - url_request_context_->set_network_delegate(network_delegate_.get()); - storage_.reset( new net::URLRequestContextStorage(url_request_context_.get())); + storage_->set_network_delegate(delegate_->CreateNetworkDelegate()); + auto cookie_path = in_memory_ ? base::FilePath() : base_path_.Append(FILE_PATH_LITERAL("Cookies")); auto cookie_config = content::CookieStoreConfig( cookie_path, content::CookieStoreConfig::EPHEMERAL_SESSION_COOKIES, - nullptr, - delegate_->CreateCookieDelegate()); + nullptr); cookie_config.cookieable_schemes = delegate_->GetCookieableSchemes(); std::unique_ptr cookie_store = content::CreateCookieStore(cookie_config); storage_->set_cookie_store(std::move(cookie_store)); + // Cookie store will outlive notifier by order of declaration + // in the header. + cookie_change_sub_ = + url_request_context_->cookie_store()->AddCallbackForAllChanges( + base::Bind(&URLRequestContextGetter::OnCookieChanged, this)); + storage_->set_channel_id_service(base::MakeUnique( new net::DefaultChannelIDStore(nullptr))); - std::string accept_lang = l10n_util::GetApplicationLocale(""); - storage_->set_http_user_agent_settings(base::WrapUnique( - new net::StaticHttpUserAgentSettings( - net::HttpUtil::GenerateAcceptLanguageHeader(accept_lang), + storage_->set_http_user_agent_settings( + base::WrapUnique(new net::StaticHttpUserAgentSettings( + net::HttpUtil::GenerateAcceptLanguageHeader( + BrowserClient::Get()->GetApplicationLocale()), user_agent_))); std::unique_ptr host_resolver( diff --git a/brightray/browser/url_request_context_getter.h b/brightray/browser/url_request_context_getter.h index 25c77560701..51b0ed53226 100644 --- a/brightray/browser/url_request_context_getter.h +++ b/brightray/browser/url_request_context_getter.h @@ -11,10 +11,11 @@ #include "base/files/file_path.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/content_browser_client.h" -#include "net/cookies/cookie_monster.h" +#include "net/cookies/cookie_store.h" #include "net/http/http_cache.h" #include "net/http/transport_security_state.h" #include "net/http/url_security_manager.h" +#include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_getter.h" namespace base { @@ -44,8 +45,7 @@ class URLRequestContextGetter : public net::URLRequestContextGetter { Delegate() {} virtual ~Delegate() {} - virtual net::NetworkDelegate* CreateNetworkDelegate() { return nullptr; } - virtual net::CookieMonsterDelegate* CreateCookieDelegate() { + virtual std::unique_ptr CreateNetworkDelegate() { return nullptr; } virtual std::string GetUserAgent(); @@ -57,6 +57,9 @@ class URLRequestContextGetter : public net::URLRequestContextGetter { RequireCTDelegate* ct_delegate); virtual net::SSLConfigService* CreateSSLConfigService(); virtual std::vector GetCookieableSchemes(); + virtual void NotifyCookieChange(const net::CanonicalCookie& cookie, + bool removed, + net::CookieStore::ChangeCause cause) {} }; URLRequestContextGetter( @@ -70,6 +73,10 @@ class URLRequestContextGetter : public net::URLRequestContextGetter { content::URLRequestInterceptorScopedVector protocol_interceptors); virtual ~URLRequestContextGetter(); + // net::CookieStore::CookieChangedCallback implementation. + void OnCookieChanged(const net::CanonicalCookie& cookie, + net::CookieStore::ChangeCause cause); + // net::URLRequestContextGetter: net::URLRequestContext* GetURLRequestContext() override; scoped_refptr GetNetworkTaskRunner() @@ -91,12 +98,13 @@ class URLRequestContextGetter : public net::URLRequestContextGetter { std::unique_ptr ct_delegate_; std::unique_ptr proxy_config_service_; - std::unique_ptr network_delegate_; std::unique_ptr storage_; std::unique_ptr url_request_context_; std::unique_ptr host_mapping_rules_; std::unique_ptr http_auth_preferences_; std::unique_ptr http_network_session_; + std::unique_ptr + cookie_change_sub_; content::ProtocolHandlerMap protocol_handlers_; content::URLRequestInterceptorScopedVector protocol_interceptors_; diff --git a/brightray/browser/win/notification_presenter_win.cc b/brightray/browser/win/notification_presenter_win.cc index 35f9799b8da..a0a8f37cd91 100644 --- a/brightray/browser/win/notification_presenter_win.cc +++ b/brightray/browser/win/notification_presenter_win.cc @@ -9,9 +9,11 @@ #include #include +#include "base/environment.h" #include "base/files/file_util.h" #include "base/md5.h" #include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_restrictions.h" #include "base/time/time.h" #include "base/win/windows_version.h" #include "brightray/browser/win/notification_presenter_win7.h" @@ -26,6 +28,10 @@ namespace brightray { namespace { +bool IsDebuggingNotifications() { + return base::Environment::Create()->HasVar("ELECTRON_DEBUG_NOTIFICATIONS"); +} + bool SaveIconToPath(const SkBitmap& bitmap, const base::FilePath& path) { std::vector png_data; if (!gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &png_data)) @@ -49,6 +55,10 @@ NotificationPresenter* NotificationPresenter::Create() { new NotificationPresenterWin); if (!presenter->Init()) return nullptr; + + if (IsDebuggingNotifications()) + LOG(INFO) << "Successfully created Windows notifications presenter"; + return presenter.release(); } @@ -59,6 +69,7 @@ NotificationPresenterWin::~NotificationPresenterWin() { } bool NotificationPresenterWin::Init() { + base::ThreadRestrictions::ScopedAllowIO allow_io; return temp_dir_.CreateUniqueTempDir(); } @@ -73,6 +84,7 @@ base::string16 NotificationPresenterWin::SaveIconToFilesystem( filename = std::to_string(now.ToInternalValue()) + ".png"; } + base::ThreadRestrictions::ScopedAllowIO allow_io; base::FilePath path = temp_dir_.GetPath().Append(base::UTF8ToUTF16(filename)); if (base::PathExists(path)) return path.value(); diff --git a/brightray/browser/win/win32_desktop_notifications/desktop_notification_controller.cc b/brightray/browser/win/win32_desktop_notifications/desktop_notification_controller.cc index 8dea03f1fc6..358074c602b 100644 --- a/brightray/browser/win/win32_desktop_notifications/desktop_notification_controller.cc +++ b/brightray/browser/win/win32_desktop_notifications/desktop_notification_controller.cc @@ -174,7 +174,7 @@ void DesktopNotificationController::AnimateAll() { if (SystemParametersInfo(SPI_GETWORKAREA, 0, &work_area, 0)) { ScreenMetrics metrics; POINT origin = { work_area.right, - work_area.bottom - metrics.Y(toast_margin_) }; + work_area.bottom - metrics.Y(toast_margin_) }; auto hdwp = BeginDeferWindowPos(static_cast(instances_.size())); @@ -231,7 +231,7 @@ void DesktopNotificationController::AnimateAll() { // Set new toast positions if (!instances_.empty()) { ScreenMetrics metrics; - auto margin = metrics.Y(toast_margin_); + auto margin = metrics.Y(toast_margin_); int target_pos = 0; for (auto&& inst : instances_) { @@ -305,7 +305,7 @@ void DesktopNotificationController::CreateToast(NotificationLink&& data) { auto toast = Toast::Get(item.hwnd); toast_pos = toast->GetVerticalPosition() + toast->GetHeight() + - scr.Y(toast_margin_); + scr.Y(toast_margin_); } instances_.push_back({ hwnd, move(data) }); diff --git a/brightray/browser/win/win32_desktop_notifications/desktop_notification_controller.h b/brightray/browser/win/win32_desktop_notifications/desktop_notification_controller.h index 643a61f5331..1e76e1e1ad2 100644 --- a/brightray/browser/win/win32_desktop_notifications/desktop_notification_controller.h +++ b/brightray/browser/win/win32_desktop_notifications/desktop_notification_controller.h @@ -36,8 +36,7 @@ class DesktopNotificationController { TimerID_Animate = 1 }; - template - static constexpr T toast_margin_ = 20; + static constexpr int toast_margin_ = 20; // Wrapper around `NotificationData` which makes sure that // the `controller` member is cleared when the controller object diff --git a/brightray/browser/win/windows_toast_notification.cc b/brightray/browser/win/windows_toast_notification.cc index 13a8f002616..8fef7f0d36e 100644 --- a/brightray/browser/win/windows_toast_notification.cc +++ b/brightray/browser/win/windows_toast_notification.cc @@ -11,6 +11,7 @@ #include #include +#include "base/environment.h" #include "base/strings/utf_string_conversions.h" #include "brightray/browser/notification_delegate.h" #include "brightray/browser/win/notification_presenter_win.h" @@ -30,15 +31,8 @@ namespace brightray { namespace { -bool GetAppUserModelId(ScopedHString* app_id) { - PWSTR current_app_id; - if (SUCCEEDED(GetCurrentProcessExplicitAppUserModelID(¤t_app_id))) { - app_id->Reset(current_app_id); - CoTaskMemFree(current_app_id); - } else { - app_id->Reset(base::UTF8ToUTF16(GetApplicationName())); - } - return app_id->success(); +bool IsDebuggingNotifications() { + return base::Environment::Create()->HasVar("ELECTRON_DEBUG_NOTIFICATIONS"); } } // namespace @@ -65,7 +59,7 @@ bool WindowsToastNotification::Initialize() { return false; ScopedHString app_id; - if (!GetAppUserModelId(&app_id)) + if (!GetAppUserModelID(&app_id)) return false; return SUCCEEDED( @@ -129,11 +123,14 @@ void WindowsToastNotification::Show(const NotificationOptions& options) { return; } + if (IsDebuggingNotifications()) LOG(INFO) << "Notification created"; + if (delegate()) delegate()->NotificationDisplayed(); } void WindowsToastNotification::Dismiss() { + if (IsDebuggingNotifications()) LOG(INFO) << "Hiding notification"; toast_notifier_->Hide(toast_notification_.Get()); } @@ -164,16 +161,28 @@ bool WindowsToastNotification::GetToastXml( ? ABI::Windows::UI::Notifications::ToastTemplateType_ToastText02 : ABI::Windows::UI::Notifications:: ToastTemplateType_ToastImageAndText02; - if (FAILED(toastManager->GetTemplateContent(template_type, toast_xml))) + if (FAILED(toastManager->GetTemplateContent(template_type, toast_xml))) { + if (IsDebuggingNotifications()) + LOG(INFO) << "Fetching XML template failed"; return false; - if (!SetXmlText(*toast_xml, title, msg)) + } + + if (!SetXmlText(*toast_xml, title, msg)) { + if (IsDebuggingNotifications()) + LOG(INFO) << "Setting text fields on template failed"; return false; + } } // Configure the toast's notification sound if (silent) { - if (FAILED(SetXmlAudioSilent(*toast_xml))) + if (FAILED(SetXmlAudioSilent(*toast_xml))) { + if (IsDebuggingNotifications()) { + LOG(INFO) << "Setting \"silent\" option on notification failed"; + } + return false; + } } // Configure the toast's image @@ -398,6 +407,8 @@ IFACEMETHODIMP ToastEventHandler::Invoke( content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, base::Bind(&Notification::NotificationClicked, notification_)); + if (IsDebuggingNotifications()) LOG(INFO) << "Notification clicked"; + return S_OK; } @@ -407,6 +418,8 @@ IFACEMETHODIMP ToastEventHandler::Invoke( content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, base::Bind(&Notification::NotificationDismissed, notification_)); + if (IsDebuggingNotifications()) LOG(INFO) << "Notification dismissed"; + return S_OK; } @@ -416,6 +429,8 @@ IFACEMETHODIMP ToastEventHandler::Invoke( content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, base::Bind(&Notification::NotificationFailed, notification_)); + if (IsDebuggingNotifications()) LOG(INFO) << "Notification failed"; + return S_OK; } diff --git a/brightray/browser/zoom_level_delegate.cc b/brightray/browser/zoom_level_delegate.cc index 2cf4fad700b..694223d742d 100644 --- a/brightray/browser/zoom_level_delegate.cc +++ b/brightray/browser/zoom_level_delegate.cc @@ -95,7 +95,7 @@ void ZoomLevelDelegate::OnZoomLevelChanged( if (modification_is_removal) host_zoom_dictionary->RemoveWithoutPathExpansion(change.host, nullptr); else - host_zoom_dictionary->SetDoubleWithoutPathExpansion(change.host, level); + host_zoom_dictionary->SetKey(change.host, base::Value(level)); } void ZoomLevelDelegate::ExtractPerHostZoomLevels( diff --git a/brightray/common/application_info.cc b/brightray/common/application_info.cc new file mode 100644 index 00000000000..1d3da35d723 --- /dev/null +++ b/brightray/common/application_info.cc @@ -0,0 +1,26 @@ +#include "brightray/common/application_info.h" + +namespace brightray { + +namespace { + +std::string g_overridden_application_name; +std::string g_overridden_application_version; + +} + +void OverrideApplicationName(const std::string& name) { + g_overridden_application_name = name; +} +std::string GetOverriddenApplicationName() { + return g_overridden_application_name; +} + +void OverrideApplicationVersion(const std::string& version) { + g_overridden_application_version = version; +} +std::string GetOverriddenApplicationVersion() { + return g_overridden_application_version; +} + +} // namespace brightray diff --git a/brightray/common/application_info.h b/brightray/common/application_info.h index ffff6ff0ab0..a4918cc7c3d 100644 --- a/brightray/common/application_info.h +++ b/brightray/common/application_info.h @@ -1,13 +1,29 @@ #ifndef BRIGHTRAY_COMMON_APPLICATION_INFO_H_ #define BRIGHTRAY_COMMON_APPLICATION_INFO_H_ +#if defined(OS_WIN) +#include "brightray/browser/win/scoped_hstring.h" +#endif + #include namespace brightray { +void OverrideApplicationName(const std::string& name); +std::string GetOverriddenApplicationName(); + +void OverrideApplicationVersion(const std::string& version); +std::string GetOverriddenApplicationVersion(); + std::string GetApplicationName(); std::string GetApplicationVersion(); -} +#if defined(OS_WIN) +PCWSTR GetRawAppUserModelID(); +bool GetAppUserModelID(ScopedHString* app_id); +void SetAppUserModelID(const base::string16& name); +#endif + +} // namespace brightray #endif // BRIGHTRAY_COMMON_APPLICATION_INFO_H_ diff --git a/brightray/common/application_info_win.cc b/brightray/common/application_info_win.cc index aa0ed280387..08b203a2329 100644 --- a/brightray/common/application_info_win.cc +++ b/brightray/common/application_info_win.cc @@ -1,12 +1,27 @@ #include "brightray/common/application_info.h" +#include // windows.h must be included first + +#include + #include #include "base/file_version_info.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" +#include "brightray/browser/win/scoped_hstring.h" namespace brightray { +namespace { + +base::string16 g_app_user_model_id; + +} + +const wchar_t kAppUserModelIDFormat[] = L"electron.app.$1"; + std::string GetApplicationName() { auto module = GetModuleHandle(nullptr); std::unique_ptr info( @@ -21,4 +36,34 @@ std::string GetApplicationVersion() { return base::UTF16ToUTF8(info->product_version()); } +void SetAppUserModelID(const base::string16& name) { + g_app_user_model_id = name; + SetCurrentProcessExplicitAppUserModelID(g_app_user_model_id.c_str()); +} + +PCWSTR GetRawAppUserModelID() { + if (g_app_user_model_id.empty()) { + PWSTR current_app_id; + if (SUCCEEDED(GetCurrentProcessExplicitAppUserModelID(¤t_app_id))) { + g_app_user_model_id = current_app_id; + } else { + std::string name = GetOverriddenApplicationName(); + if (name.empty()) { + name = GetApplicationName(); + } + base::string16 generated_app_id = base::ReplaceStringPlaceholders( + kAppUserModelIDFormat, base::UTF8ToUTF16(name), nullptr); + SetAppUserModelID(generated_app_id); + } + CoTaskMemFree(current_app_id); + } + + return g_app_user_model_id.c_str(); +} + +bool GetAppUserModelID(ScopedHString* app_id) { + app_id->Reset(GetRawAppUserModelID()); + return app_id->success(); +} + } // namespace brightray diff --git a/brightray/common/main_delegate.cc b/brightray/common/main_delegate.cc index 423794d8027..fd449365521 100644 --- a/brightray/common/main_delegate.cc +++ b/brightray/common/main_delegate.cc @@ -42,17 +42,22 @@ bool SubprocessNeedsResourceBundle(const std::string& process_type) { } // namespace -void InitializeResourceBundle(const std::string& locale) { - // Load locales. - ui::ResourceBundle::InitSharedInstanceWithLocale( - locale, nullptr, ui::ResourceBundle::DO_NOT_LOAD_COMMON_RESOURCES); +void LoadResourceBundle(const std::string& locale) { + const bool initialized = ui::ResourceBundle::HasSharedInstance(); + if (initialized) + ui::ResourceBundle::CleanupSharedInstance(); - // Load other resource files. + ui::ResourceBundle::InitSharedInstanceWithLocale( + locale, nullptr, ui::ResourceBundle::DO_NOT_LOAD_COMMON_RESOURCES); + + ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); + bundle.ReloadLocaleResources(locale); + +// Load other resource files. #if defined(OS_MACOSX) LoadCommonResources(); #else base::FilePath pak_dir; - ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); PathService::Get(base::DIR_MODULE, &pak_dir); bundle.AddDataPackFromPath( pak_dir.Append(FILE_PATH_LITERAL("content_shell.pak")), @@ -104,7 +109,7 @@ void MainDelegate::PreSandboxStartup() { // browser process as a command line flag. if (SubprocessNeedsResourceBundle(process_type)) { std::string locale = cmd.GetSwitchValueASCII(switches::kLang); - InitializeResourceBundle(locale); + LoadResourceBundle(locale); } } diff --git a/brightray/common/main_delegate.h b/brightray/common/main_delegate.h index 4139a20521b..c5a294caab5 100644 --- a/brightray/common/main_delegate.h +++ b/brightray/common/main_delegate.h @@ -24,7 +24,7 @@ namespace brightray { class BrowserClient; class ContentClient; -void InitializeResourceBundle(const std::string& locale); +void LoadResourceBundle(const std::string& locale); void LoadCommonResources(); class MainDelegate : public content::ContentMainDelegate { diff --git a/brightray/filename_rules.gypi b/brightray/filename_rules.gypi index e1ee46dddee..ffdd120b8b2 100644 --- a/brightray/filename_rules.gypi +++ b/brightray/filename_rules.gypi @@ -62,7 +62,7 @@ ['exclude', '_gtk(_browsertest|_unittest)?\\.(h|cc)$'], ['exclude', '(^|/)gtk/'], ['exclude', '(^|/)gtk_[^/]*\\.(h|cc)$'], - ['exclude', '(^|/)libgtk2ui/'], + ['exclude', '(^|/)libgtkui/'], ['exclude', '(^|/)x/'], ], }], diff --git a/brightray/filenames.gypi b/brightray/filenames.gypi index 0c6efa3a7f0..6a7d91f135c 100644 --- a/brightray/filenames.gypi +++ b/brightray/filenames.gypi @@ -29,6 +29,8 @@ 'browser/inspectable_web_contents_view.h', 'browser/inspectable_web_contents_view_mac.h', 'browser/inspectable_web_contents_view_mac.mm', + 'browser/io_thread.cc', + 'browser/io_thread.h', 'browser/mac/bry_inspectable_web_contents_view.h', 'browser/mac/bry_inspectable_web_contents_view.mm', 'browser/mac/cocoa_notification.h', @@ -110,6 +112,7 @@ 'browser/zoom_level_delegate.cc', 'browser/zoom_level_delegate.h', 'common/application_info.h', + 'common/application_info.cc', 'common/application_info_mac.mm', 'common/application_info_win.cc', 'common/content_client.cc', diff --git a/chromium_src/chrome/browser/browser_process.cc b/chromium_src/chrome/browser/browser_process.cc index d37478396ef..a9a2a2f3c71 100644 --- a/chromium_src/chrome/browser/browser_process.cc +++ b/chromium_src/chrome/browser/browser_process.cc @@ -20,8 +20,12 @@ BrowserProcess::~BrowserProcess() { g_browser_process = NULL; } +void BrowserProcess::SetApplicationLocale(const std::string& locale) { + locale_ = locale; +} + std::string BrowserProcess::GetApplicationLocale() { - return l10n_util::GetApplicationLocale(""); + return locale_; } IconManager* BrowserProcess::GetIconManager() { diff --git a/chromium_src/chrome/browser/browser_process.h b/chromium_src/chrome/browser/browser_process.h index 1c1156f4524..73a4b637791 100644 --- a/chromium_src/chrome/browser/browser_process.h +++ b/chromium_src/chrome/browser/browser_process.h @@ -28,6 +28,7 @@ class BrowserProcess { BrowserProcess(); ~BrowserProcess(); + void SetApplicationLocale(const std::string& locale); std::string GetApplicationLocale(); IconManager* GetIconManager(); @@ -36,6 +37,7 @@ class BrowserProcess { private: std::unique_ptr print_job_manager_; std::unique_ptr icon_manager_; + std::string locale_; DISALLOW_COPY_AND_ASSIGN(BrowserProcess); }; diff --git a/chromium_src/chrome/browser/icon_loader.cc b/chromium_src/chrome/browser/icon_loader.cc index a0cca6379d4..91d80e78727 100644 --- a/chromium_src/chrome/browser/icon_loader.cc +++ b/chromium_src/chrome/browser/icon_loader.cc @@ -2,8 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include + #include "chrome/browser/icon_loader.h" #include "base/bind.h" +#include "base/task_scheduler/post_task.h" +#include "base/task_scheduler/task_traits.h" #include "base/threading/thread_task_runner_handle.h" #include "content/public/browser/browser_thread.h" @@ -18,10 +22,10 @@ IconLoader* IconLoader::Create(const base::FilePath& file_path, void IconLoader::Start() { target_task_runner_ = base::ThreadTaskRunnerHandle::Get(); - BrowserThread::PostTaskAndReply( - BrowserThread::FILE, FROM_HERE, - base::Bind(&IconLoader::ReadGroup, base::Unretained(this)), - base::Bind(&IconLoader::OnReadGroup, base::Unretained(this))); + + base::PostTaskWithTraits( + FROM_HERE, traits(), + base::BindOnce(&IconLoader::ReadGroup, base::Unretained(this))); } IconLoader::IconLoader(const base::FilePath& file_path, @@ -33,10 +37,6 @@ IconLoader::~IconLoader() {} void IconLoader::ReadGroup() { group_ = GroupForFilepath(file_path_); -} - -void IconLoader::OnReadGroup() { - BrowserThread::PostTask( - ReadIconThreadID(), FROM_HERE, - base::Bind(&IconLoader::ReadIcon, base::Unretained(this))); + GetReadIconTaskRunner()->PostTask( + FROM_HERE, base::BindOnce(&IconLoader::ReadIcon, base::Unretained(this))); } diff --git a/chromium_src/chrome/browser/icon_loader.h b/chromium_src/chrome/browser/icon_loader.h index 50d9ef45410..fc91909d86d 100644 --- a/chromium_src/chrome/browser/icon_loader.h +++ b/chromium_src/chrome/browser/icon_loader.h @@ -12,6 +12,7 @@ #include "base/files/file_path.h" #include "base/macros.h" #include "base/single_thread_task_runner.h" +#include "base/task_scheduler/task_traits.h" #include "build/build_config.h" #include "content/public/browser/browser_thread.h" #include "ui/gfx/image/image.h" @@ -66,13 +67,19 @@ class IconLoader { // Given a file path, get the group for the given file. static IconGroup GroupForFilepath(const base::FilePath& file_path); - // The thread ReadIcon() should be called on. - static content::BrowserThread::ID ReadIconThreadID(); + // The TaskRunner that ReadIcon() must be called on. + static scoped_refptr GetReadIconTaskRunner(); void ReadGroup(); - void OnReadGroup(); void ReadIcon(); + // The traits of the tasks posted by this class. These operations may block, + // because they are fetching icons from the disk, yet the result will be seen + // by the user so they should be prioritized accordingly. + static constexpr base::TaskTraits traits() { + return {base::MayBlock(), base::TaskPriority::USER_VISIBLE}; + } + // The task runner object of the thread in which we notify the delegate. scoped_refptr target_task_runner_; @@ -82,8 +89,6 @@ class IconLoader { IconSize icon_size_; - std::unique_ptr image_; - IconLoadedCallback callback_; DISALLOW_COPY_AND_ASSIGN(IconLoader); diff --git a/chromium_src/chrome/browser/icon_loader_auralinux.cc b/chromium_src/chrome/browser/icon_loader_auralinux.cc index 449d05771d7..484f6888c17 100644 --- a/chromium_src/chrome/browser/icon_loader_auralinux.cc +++ b/chromium_src/chrome/browser/icon_loader_auralinux.cc @@ -5,6 +5,7 @@ #include "chrome/browser/icon_loader.h" #include "base/bind.h" +#include "base/memory/ptr_util.h" #include "base/message_loop/message_loop.h" #include "base/nix/mime_util_xdg.h" #include "ui/views/linux_ui/linux_ui.h" @@ -16,10 +17,11 @@ IconLoader::IconGroup IconLoader::GroupForFilepath( } // static -content::BrowserThread::ID IconLoader::ReadIconThreadID() { +scoped_refptr IconLoader::GetReadIconTaskRunner() { // ReadIcon() calls into views::LinuxUI and GTK2 code, so it must be on the UI // thread. - return content::BrowserThread::UI; + return content::BrowserThread::GetTaskRunnerForThread( + content::BrowserThread::UI); } void IconLoader::ReadIcon() { @@ -38,14 +40,17 @@ void IconLoader::ReadIcon() { NOTREACHED(); } + std::unique_ptr image; + views::LinuxUI* ui = views::LinuxUI::instance(); if (ui) { - gfx::Image image = ui->GetIconForContentType(group_, size_pixels); - if (!image.IsEmpty()) - image_.reset(new gfx::Image(image)); + image = base::MakeUnique( + ui->GetIconForContentType(group_, size_pixels)); + if (image->IsEmpty()) + image = nullptr; } target_task_runner_->PostTask( - FROM_HERE, base::Bind(callback_, base::Passed(&image_), group_)); + FROM_HERE, base::BindOnce(callback_, base::Passed(&image), group_)); delete this; } diff --git a/chromium_src/chrome/browser/icon_loader_mac.mm b/chromium_src/chrome/browser/icon_loader_mac.mm index dd94e9fe7ea..f10b47d27a6 100644 --- a/chromium_src/chrome/browser/icon_loader_mac.mm +++ b/chromium_src/chrome/browser/icon_loader_mac.mm @@ -8,8 +8,10 @@ #include "base/bind.h" #include "base/files/file_path.h" +#include "base/memory/ptr_util.h" #include "base/message_loop/message_loop.h" #include "base/strings/sys_string_conversions.h" +#include "base/task_scheduler/post_task.h" #include "base/threading/thread.h" #include "ui/gfx/image/image_skia.h" #include "ui/gfx/image/image_skia_util_mac.h" @@ -21,8 +23,9 @@ IconLoader::IconGroup IconLoader::GroupForFilepath( } // static -content::BrowserThread::ID IconLoader::ReadIconThreadID() { - return content::BrowserThread::FILE; +scoped_refptr IconLoader::GetReadIconTaskRunner() { + // NSWorkspace is thread-safe. + return base::CreateTaskRunnerWithTraits(traits()); } void IconLoader::ReadIcon() { @@ -30,9 +33,11 @@ void IconLoader::ReadIcon() { NSWorkspace* workspace = [NSWorkspace sharedWorkspace]; NSImage* icon = [workspace iconForFileType:group]; + std::unique_ptr image; + if (icon_size_ == ALL) { // The NSImage already has all sizes. - image_.reset(new gfx::Image([icon retain])); + image = base::MakeUnique([icon retain]); } else { NSSize size = NSZeroSize; switch (icon_size_) { @@ -48,11 +53,11 @@ void IconLoader::ReadIcon() { gfx::ImageSkia image_skia(gfx::ImageSkiaFromResizedNSImage(icon, size)); if (!image_skia.isNull()) { image_skia.MakeThreadSafe(); - image_.reset(new gfx::Image(image_skia)); + image = base::MakeUnique(image_skia); } } target_task_runner_->PostTask( - FROM_HERE, base::Bind(callback_, base::Passed(&image_), group_)); + FROM_HERE, base::Bind(callback_, base::Passed(&image), group_)); delete this; } diff --git a/chromium_src/chrome/browser/icon_loader_win.cc b/chromium_src/chrome/browser/icon_loader_win.cc index 279c819cc49..1d0912bd518 100644 --- a/chromium_src/chrome/browser/icon_loader_win.cc +++ b/chromium_src/chrome/browser/icon_loader_win.cc @@ -8,7 +8,9 @@ #include #include "base/bind.h" +#include "base/memory/ptr_util.h" #include "base/message_loop/message_loop.h" +#include "base/task_scheduler/post_task.h" #include "base/threading/thread.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/display/win/dpi.h" @@ -29,8 +31,10 @@ IconLoader::IconGroup IconLoader::GroupForFilepath( } // static -content::BrowserThread::ID IconLoader::ReadIconThreadID() { - return content::BrowserThread::FILE; +scoped_refptr IconLoader::GetReadIconTaskRunner() { + // Technically speaking, only a thread with COM is needed, not one that has + // a COM STA. However, this is what is available for now. + return base::CreateCOMSTATaskRunnerWithTraits(traits()); } void IconLoader::ReadIcon() { @@ -49,7 +53,7 @@ void IconLoader::ReadIcon() { NOTREACHED(); } - image_.reset(); + std::unique_ptr image; SHFILEINFO file_info = { 0 }; if (SHGetFileInfo(group_.c_str(), FILE_ATTRIBUTE_NORMAL, &file_info, @@ -61,12 +65,12 @@ void IconLoader::ReadIcon() { gfx::ImageSkia image_skia(gfx::ImageSkiaRep(*bitmap, display::win::GetDPIScale())); image_skia.MakeThreadSafe(); - image_.reset(new gfx::Image(image_skia)); + image = base::MakeUnique(image_skia); DestroyIcon(file_info.hIcon); } } target_task_runner_->PostTask( - FROM_HERE, base::Bind(callback_, base::Passed(&image_), group_)); + FROM_HERE, base::Bind(callback_, base::Passed(&image), group_)); delete this; } 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 a35e80cea23..4774544cf2c 100644 --- a/chromium_src/chrome/browser/printing/pdf_to_emf_converter.cc +++ b/chromium_src/chrome/browser/printing/pdf_to_emf_converter.cc @@ -19,7 +19,6 @@ #include "base/macros.h" #include "base/memory/ptr_util.h" #include "base/threading/thread_task_runner_handle.h" -#include "chrome/common/chrome_utility_messages.h" #include "chrome/common/chrome_utility_printing_messages.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/child_process_data.h" diff --git a/chromium_src/chrome/browser/printing/print_job.cc b/chromium_src/chrome/browser/printing/print_job.cc index 557dbe35d94..ccc6ac9bed1 100644 --- a/chromium_src/chrome/browser/printing/print_job.cc +++ b/chromium_src/chrome/browser/printing/print_job.cc @@ -459,7 +459,7 @@ void PrintJob::HoldUntilStopIsCalled() { } void PrintJob::Quit() { - base::MessageLoop::current()->QuitWhenIdle(); + base::RunLoop::QuitCurrentWhenIdleDeprecated(); } // Takes settings_ ownership and will be deleted in the receiving thread. 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 a148f35b3fc..e3dcbe063ed 100644 --- a/chromium_src/chrome/browser/printing/print_view_manager_base.cc +++ b/chromium_src/chrome/browser/printing/print_view_manager_base.cc @@ -143,7 +143,7 @@ void PrintViewManagerBase::OnDidPrintPage( } std::unique_ptr metafile( - new PdfMetafileSkia(PDF_SKIA_DOCUMENT_TYPE)); + new PdfMetafileSkia(SkiaDocumentType::PDF)); if (metafile_must_be_valid) { if (!metafile->InitFromData(shared_buf.memory(), params.data_size)) { NOTREACHED() << "Invalid metafile header"; @@ -324,7 +324,7 @@ void PrintViewManagerBase::ShouldQuitFromInnerMessageLoop() { inside_inner_message_loop_) { // We are in a message loop created by RenderAllMissingPagesNow. Quit from // it. - base::MessageLoop::current()->QuitWhenIdle(); + base::RunLoop::QuitCurrentWhenIdleDeprecated(); inside_inner_message_loop_ = false; } } @@ -433,9 +433,10 @@ 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::QuitWhenIdle); + base::RunLoop run_loop; + quit_timer.Start(FROM_HERE, + TimeDelta::FromMilliseconds(kPrinterSettingsTimeout), + run_loop.QuitWhenIdleClosure()); inside_inner_message_loop_ = true; diff --git a/chromium_src/chrome/browser/process_singleton_posix.cc b/chromium_src/chrome/browser/process_singleton_posix.cc index 46e0a6a5d14..50c6a1877f1 100644 --- a/chromium_src/chrome/browser/process_singleton_posix.cc +++ b/chromium_src/chrome/browser/process_singleton_posix.cc @@ -77,7 +77,6 @@ #include "base/rand_util.h" #include "base/sequenced_task_runner_helpers.h" #include "base/single_thread_task_runner.h" -#include "base/single_thread_task_runner.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" @@ -85,6 +84,7 @@ #include "base/strings/sys_string_conversions.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/platform_thread.h" +#include "base/threading/thread_restrictions.h" #include "base/threading/thread_task_runner_handle.h" #include "base/time/time.h" #include "base/timer/timer.h" @@ -722,6 +722,7 @@ ProcessSingleton::ProcessSingleton( : notification_callback_(notification_callback), current_pid_(base::GetCurrentProcId()) { // The user_data_dir may have not been created yet. + base::ThreadRestrictions::ScopedAllowIO allow_io; base::CreateDirectoryAndGetError(user_data_dir, nullptr); socket_path_ = user_data_dir.Append(kSingletonSocketFilename); @@ -734,6 +735,10 @@ ProcessSingleton::ProcessSingleton( ProcessSingleton::~ProcessSingleton() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // Manually free resources with IO explicitly allowed. + base::ThreadRestrictions::ScopedAllowIO allow_io; + watcher_ = nullptr; + ignore_result(socket_dir_.Delete()); } ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() { @@ -960,6 +965,7 @@ void ProcessSingleton::DisablePromptForTesting() { } bool ProcessSingleton::Create() { + base::ThreadRestrictions::ScopedAllowIO allow_io; int sock; sockaddr_un addr; diff --git a/chromium_src/chrome/browser/process_singleton_win.cc b/chromium_src/chrome/browser/process_singleton_win.cc index 121f6375dac..6629a6ff3b1 100644 --- a/chromium_src/chrome/browser/process_singleton_win.cc +++ b/chromium_src/chrome/browser/process_singleton_win.cc @@ -189,8 +189,7 @@ ProcessSingleton::ProcessSingleton( is_virtualized_(false), lock_file_(INVALID_HANDLE_VALUE), user_data_dir_(user_data_dir), - should_kill_remote_process_callback_( - base::Bind(&TerminateAppWithError)) { + should_kill_remote_process_callback_(base::Bind(&TerminateAppWithError)) { // The user_data_dir may have not been created yet. base::CreateDirectoryAndGetError(user_data_dir, nullptr); } diff --git a/chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.cc b/chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.cc index 62427e99546..eb54a39014f 100644 --- a/chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.cc +++ b/chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.cc @@ -9,6 +9,7 @@ #include "content/public/browser/render_process_host.h" #include "content/public/common/webplugininfo.h" #include "content/public/browser/plugin_service.h" +#include "media/media_features.h" using content::PluginService; using content::WebPluginInfo; @@ -24,17 +25,17 @@ WidevineCdmMessageFilter::WidevineCdmMessageFilter( bool WidevineCdmMessageFilter::OnMessageReceived(const IPC::Message& message) { IPC_BEGIN_MESSAGE_MAP(WidevineCdmMessageFilter, message) -#if BUILDFLAG(ENABLE_PEPPER_CDMS) +#if BUILDFLAG(ENABLE_LIBRARY_CDMS) IPC_MESSAGE_HANDLER( ChromeViewHostMsg_IsInternalPluginAvailableForMimeType, OnIsInternalPluginAvailableForMimeType) -#endif +#endif // BUILDFLAG(ENABLE_LIBRARY_CDMS) IPC_MESSAGE_UNHANDLED(return false) IPC_END_MESSAGE_MAP() return true; } -#if BUILDFLAG(ENABLE_PEPPER_CDMS) +#if BUILDFLAG(ENABLE_LIBRARY_CDMS) void WidevineCdmMessageFilter::OnIsInternalPluginAvailableForMimeType( const std::string& mime_type, bool* is_available, @@ -60,7 +61,7 @@ void WidevineCdmMessageFilter::OnIsInternalPluginAvailableForMimeType( *is_available = false; } -#endif // BUILDFLAG(ENABLE_PEPPER_CDMS) +#endif // BUILDFLAG(ENABLE_LIBRARY_CDMS) void WidevineCdmMessageFilter::OnDestruct() const { BrowserThread::DeleteOnUIThread::Destruct(this); diff --git a/chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.h b/chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.h index 6cd77fe57c5..d1e5c2d38db 100644 --- a/chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.h +++ b/chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.h @@ -7,6 +7,7 @@ #include "chrome/common/widevine_cdm_messages.h" #include "content/public/browser/browser_message_filter.h" +#include "media/media_features.h" namespace content { class BrowserContext; @@ -25,7 +26,7 @@ class WidevineCdmMessageFilter : public content::BrowserMessageFilter { virtual ~WidevineCdmMessageFilter(); - #if BUILDFLAG(ENABLE_PEPPER_CDMS) +#if BUILDFLAG(ENABLE_LIBRARY_CDMS) // Returns whether any internal plugin supporting |mime_type| is registered // and enabled. Does not determine whether the plugin can actually be // instantiated (e.g. whether it has all its dependencies). @@ -38,7 +39,7 @@ class WidevineCdmMessageFilter : public content::BrowserMessageFilter { bool* is_available, std::vector* additional_param_names, std::vector* additional_param_values); -#endif +#endif // BUILDFLAG(ENABLE_LIBRARY_CDMS) int render_process_id_; content::BrowserContext* browser_context_; diff --git a/chromium_src/chrome/common/chrome_paths.cc b/chromium_src/chrome/common/chrome_paths.cc index f1f4e57ba01..5acab714251 100644 --- a/chromium_src/chrome/common/chrome_paths.cc +++ b/chromium_src/chrome/common/chrome_paths.cc @@ -16,6 +16,7 @@ #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_paths_internal.h" #include "chrome/common/widevine_cdm_constants.h" +#include "media/media_features.h" #include "third_party/widevine/cdm/stub/widevine_cdm_version.h" #include "third_party/widevine/cdm/widevine_cdm_common.h" @@ -360,7 +361,7 @@ bool PathProvider(int key, base::FilePath* result) { #endif cur = cur.Append(FILE_PATH_LITERAL("pnacl")); break; -#if defined(WIDEVINE_CDM_AVAILABLE) && BUILDFLAG(ENABLE_PEPPER_CDMS) +#if defined(WIDEVINE_CDM_AVAILABLE) && BUILDFLAG(ENABLE_LIBRARY_CDMS) #if defined(WIDEVINE_CDM_IS_COMPONENT) case chrome::DIR_COMPONENT_WIDEVINE_CDM: if (!PathService::Get(chrome::DIR_USER_DATA, &cur)) @@ -376,7 +377,7 @@ bool PathProvider(int key, base::FilePath* result) { return false; cur = cur.AppendASCII(kWidevineCdmAdapterFileName); break; -#endif // defined(WIDEVINE_CDM_AVAILABLE) && BUILDFLAG(ENABLE_PEPPER_CDMS) +#endif // defined(WIDEVINE_CDM_AVAILABLE) && BUILDFLAG(ENABLE_LIBRARY_CDMS) case chrome::FILE_RESOURCES_PACK: #if defined(OS_MACOSX) && !defined(OS_IOS) if (base::mac::AmIBundled()) { diff --git a/chromium_src/chrome/common/chrome_utility_messages.h b/chromium_src/chrome/common/chrome_utility_messages.h deleted file mode 100644 index 012c9f67509..00000000000 --- a/chromium_src/chrome/common/chrome_utility_messages.h +++ /dev/null @@ -1,163 +0,0 @@ -// 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. - -// Multiply-included message file, so no include guard. - -#if defined(OS_WIN) -#include -#endif // defined(OS_WIN) - -#include -#include -#include - -#include "base/files/file_path.h" -#include "base/strings/string16.h" -#include "base/values.h" -#include "ipc/ipc_message_macros.h" -#include "ipc/ipc_platform_file.h" -#include "third_party/skia/include/core/SkBitmap.h" -#include "ui/gfx/ipc/gfx_param_traits.h" - -// Singly-included section for typedefs. -#ifndef CHROME_COMMON_CHROME_UTILITY_MESSAGES_H_ -#define CHROME_COMMON_CHROME_UTILITY_MESSAGES_H_ - -#if defined(OS_WIN) -// A vector of filters, each being a tuple containing a display string (i.e. -// "Text Files") and a filter pattern (i.e. "*.txt"). -typedef std::vector> - GetOpenFileNameFilter; -#endif // OS_WIN - -#endif // CHROME_COMMON_CHROME_UTILITY_MESSAGES_H_ - -#define IPC_MESSAGE_START ChromeUtilityMsgStart - - -#if defined(OS_WIN) -IPC_STRUCT_BEGIN(ChromeUtilityMsg_GetSaveFileName_Params) - IPC_STRUCT_MEMBER(HWND, owner) - IPC_STRUCT_MEMBER(DWORD, flags) - IPC_STRUCT_MEMBER(GetOpenFileNameFilter, filters) - IPC_STRUCT_MEMBER(int, one_based_filter_index) - IPC_STRUCT_MEMBER(base::FilePath, suggested_filename) - IPC_STRUCT_MEMBER(base::FilePath, initial_directory) - IPC_STRUCT_MEMBER(base::string16, default_extension) -IPC_STRUCT_END() -#endif // OS_WIN - -//------------------------------------------------------------------------------ -// Utility process messages: -// These are messages from the browser to the utility process. - -// Tell the utility process to parse a JSON string into a Value object. -IPC_MESSAGE_CONTROL1(ChromeUtilityMsg_ParseJSON, - std::string /* JSON to parse */) - -// Tell the utility process to decode the given image data. -IPC_MESSAGE_CONTROL2(ChromeUtilityMsg_DecodeImage, - std::vector /* encoded image contents */, - bool /* shrink image if needed for IPC msg limit */) - -// Tell the utility process to decode the given JPEG image data with a robust -// libjpeg codec. -IPC_MESSAGE_CONTROL1(ChromeUtilityMsg_RobustJPEGDecodeImage, - std::vector) // encoded image contents - -// Tell the utility process to patch the given |input_file| using |patch_file| -// and place the output in |output_file|. The patch should use the bsdiff -// algorithm (Courgette's version). -IPC_MESSAGE_CONTROL3(ChromeUtilityMsg_PatchFileBsdiff, - base::FilePath /* input_file */, - base::FilePath /* patch_file */, - base::FilePath /* output_file */) - -// Tell the utility process to patch the given |input_file| using |patch_file| -// and place the output in |output_file|. The patch should use the Courgette -// algorithm. -IPC_MESSAGE_CONTROL3(ChromeUtilityMsg_PatchFileCourgette, - base::FilePath /* input_file */, - base::FilePath /* patch_file */, - base::FilePath /* output_file */) - - -#if defined(OS_WIN) -// Invokes ui::base::win::OpenFileViaShell from the utility process. -IPC_MESSAGE_CONTROL1(ChromeUtilityMsg_OpenFileViaShell, - base::FilePath /* full_path */) - -// Invokes ui::base::win::OpenFolderViaShell from the utility process. -IPC_MESSAGE_CONTROL1(ChromeUtilityMsg_OpenFolderViaShell, - base::FilePath /* full_path */) - -// Instructs the utility process to invoke GetOpenFileName. |owner| is the -// parent of the modal dialog, |flags| are OFN_* flags. |filter| constrains the -// user's file choices. |initial_directory| and |filename| select the directory -// to be displayed and the file to be initially selected. -// -// Either ChromeUtilityHostMsg_GetOpenFileName_Failed or -// ChromeUtilityHostMsg_GetOpenFileName_Result will be returned when the -// operation completes whether due to error or user action. -IPC_MESSAGE_CONTROL5(ChromeUtilityMsg_GetOpenFileName, - HWND /* owner */, - DWORD /* flags */, - GetOpenFileNameFilter /* filter */, - base::FilePath /* initial_directory */, - base::FilePath /* filename */) -IPC_MESSAGE_CONTROL1(ChromeUtilityMsg_GetSaveFileName, - ChromeUtilityMsg_GetSaveFileName_Params /* params */) -#endif // defined(OS_WIN) - - -//------------------------------------------------------------------------------ -// Utility process host messages: -// These are messages from the utility process to the browser. - -// Reply when the utility process successfully parsed a JSON string. -// -// WARNING: The result can be of any Value subclass type, but we can't easily -// pass indeterminate value types by const object reference with our IPC macros, -// so we put the result Value into a ListValue. Handlers should examine the -// first (and only) element of the ListValue for the actual result. -IPC_MESSAGE_CONTROL1(ChromeUtilityHostMsg_ParseJSON_Succeeded, - base::ListValue) - -// Reply when the utility process failed in parsing a JSON string. -IPC_MESSAGE_CONTROL1(ChromeUtilityHostMsg_ParseJSON_Failed, - std::string /* error message, if any*/) - -// Reply when the utility process has failed while unpacking and parsing a -// web resource. |error_message| is a user-readable explanation of what -// went wrong. -IPC_MESSAGE_CONTROL1(ChromeUtilityHostMsg_UnpackWebResource_Failed, - std::string /* error_message, if any */) - -// Reply when the utility process has succeeded in decoding the image. -IPC_MESSAGE_CONTROL1(ChromeUtilityHostMsg_DecodeImage_Succeeded, - SkBitmap) // decoded image - -// Reply when an error occurred decoding the image. -IPC_MESSAGE_CONTROL0(ChromeUtilityHostMsg_DecodeImage_Failed) - -// Reply when a file has been patched. -IPC_MESSAGE_CONTROL1(ChromeUtilityHostMsg_PatchFile_Finished, int /* result */) - - -// Reply when the utility process has started. -IPC_MESSAGE_CONTROL0(ChromeUtilityHostMsg_ProcessStarted) - - -#if defined(OS_WIN) -IPC_MESSAGE_CONTROL0(ChromeUtilityHostMsg_GetOpenFileName_Failed) -IPC_MESSAGE_CONTROL2(ChromeUtilityHostMsg_GetOpenFileName_Result, - base::FilePath /* directory */, - std::vector /* filenames */) -IPC_MESSAGE_CONTROL0(ChromeUtilityHostMsg_GetSaveFileName_Failed) -IPC_MESSAGE_CONTROL2(ChromeUtilityHostMsg_GetSaveFileName_Result, - base::FilePath /* path */, - int /* one_based_filter_index */) -IPC_MESSAGE_CONTROL1(ChromeUtilityHostMsg_BuildDirectWriteFontCache, - base::FilePath /* cache file path */) -#endif // defined(OS_WIN) diff --git a/chromium_src/chrome/common/widevine_cdm_messages.h b/chromium_src/chrome/common/widevine_cdm_messages.h index df5558f67a5..cb732a47ded 100644 --- a/chromium_src/chrome/common/widevine_cdm_messages.h +++ b/chromium_src/chrome/common/widevine_cdm_messages.h @@ -7,13 +7,13 @@ #include #include "ipc/ipc_message_macros.h" -#include "ppapi/features/features.h" +#include "media/media_features.h" #define IPC_MESSAGE_START ChromeMsgStart // Renderer -> Browser messages. -#if BUILDFLAG(ENABLE_PEPPER_CDMS) +#if BUILDFLAG(ENABLE_LIBRARY_CDMS) // Returns whether any internal plugin supporting |mime_type| is registered and // enabled. Does not determine whether the plugin can actually be instantiated // (e.g. whether it has all its dependencies). @@ -26,6 +26,6 @@ IPC_SYNC_MESSAGE_CONTROL1_3( bool /* is_available */, std::vector /* additional_param_names */, std::vector /* additional_param_values */) -#endif +#endif // BUILDFLAG(ENABLE_LIBRARY_CDMS) // Browser -> Renderer messages. diff --git a/chromium_src/chrome/renderer/media/chrome_key_systems.cc b/chromium_src/chrome/renderer/media/chrome_key_systems.cc index 1fc394d24c4..6c93d676284 100644 --- a/chromium_src/chrome/renderer/media/chrome_key_systems.cc +++ b/chromium_src/chrome/renderer/media/chrome_key_systems.cc @@ -18,6 +18,7 @@ #include "content/public/renderer/render_thread.h" #include "media/base/eme_constants.h" #include "media/base/key_system_properties.h" +#include "media/media_features.h" // #include "widevine_cdm_version.h" // In SHARED_INTERMEDIATE_DIR. #include "third_party/widevine/cdm/stub/widevine_cdm_version.h" @@ -32,7 +33,7 @@ using media::KeySystemProperties; using media::SupportedCodecs; -#if BUILDFLAG(ENABLE_PEPPER_CDMS) +#if BUILDFLAG(ENABLE_LIBRARY_CDMS) static const char kExternalClearKeyPepperType[] = "application/x-ppapi-clearkey-cdm"; @@ -264,15 +265,15 @@ static void AddPepperBasedWidevine( #endif // defined(OS_CHROMEOS) } #endif // defined(WIDEVINE_CDM_AVAILABLE) -#endif // BUILDFLAG(ENABLE_PEPPER_CDMS) +#endif // BUILDFLAG(ENABLE_LIBRARY_CDMS) void AddChromeKeySystems( std::vector>* key_systems_properties) { -#if BUILDFLAG(ENABLE_PEPPER_CDMS) +#if BUILDFLAG(ENABLE_LIBRARY_CDMS) AddExternalClearKey(key_systems_properties); #if defined(WIDEVINE_CDM_AVAILABLE) AddPepperBasedWidevine(key_systems_properties); #endif // defined(WIDEVINE_CDM_AVAILABLE) -#endif // BUILDFLAG(ENABLE_PEPPER_CDMS) +#endif // BUILDFLAG(ENABLE_LIBRARY_CDMS) } 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 744b263ae2d..0074dc55ff6 100644 --- a/chromium_src/chrome/renderer/printing/print_web_view_helper.cc +++ b/chromium_src/chrome/renderer/printing/print_web_view_helper.cc @@ -1254,7 +1254,7 @@ bool PrintWebViewHelper::PrintPreviewContext::CreatePreviewDocument( return false; } - metafile_.reset(new PdfMetafileSkia(PDF_SKIA_DOCUMENT_TYPE)); + metafile_.reset(new PdfMetafileSkia(SkiaDocumentType::PDF)); CHECK(metafile_->Init()); current_page_index_ = 0; 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 be8b3bdd31b..e7f421e8611 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 @@ -33,7 +33,7 @@ bool PrintWebViewHelper::RenderPreviewPage( 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(PDF_SKIA_DOCUMENT_TYPE)); + draft_metafile.reset(new PdfMetafileSkia(SkiaDocumentType::PDF)); initial_render_metafile = draft_metafile.get(); } @@ -50,7 +50,7 @@ bool PrintWebViewHelper::RenderPreviewPage( DCHECK(!draft_metafile.get()); draft_metafile = print_preview_context_.metafile()->GetMetafileForCurrentPage( - PDF_SKIA_DOCUMENT_TYPE); + SkiaDocumentType::PDF); } return PreviewPageRendered(page_number, draft_metafile.get()); @@ -58,7 +58,7 @@ bool PrintWebViewHelper::RenderPreviewPage( bool PrintWebViewHelper::PrintPagesNative(blink::WebLocalFrame* frame, int page_count) { - PdfMetafileSkia metafile(PDF_SKIA_DOCUMENT_TYPE); + PdfMetafileSkia metafile(SkiaDocumentType::PDF); if (!metafile.Init()) return false; 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 08ba2060924..25ab5a5f257 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 @@ -22,7 +22,7 @@ using blink::WebLocalFrame; void PrintWebViewHelper::PrintPageInternal( const PrintMsg_PrintPage_Params& params, WebLocalFrame* frame) { - PdfMetafileSkia metafile(PDF_SKIA_DOCUMENT_TYPE); + PdfMetafileSkia metafile(SkiaDocumentType::PDF); CHECK(metafile.Init()); int page_number = params.page_number; @@ -60,7 +60,7 @@ bool PrintWebViewHelper::RenderPreviewPage( is_print_ready_metafile_sent_; if (render_to_draft) { - draft_metafile.reset(new PdfMetafileSkia(PDF_SKIA_DOCUMENT_TYPE)); + draft_metafile.reset(new PdfMetafileSkia(SkiaDocumentType::PDF)); CHECK(draft_metafile->Init()); initial_render_metafile = draft_metafile.get(); } @@ -80,7 +80,7 @@ bool PrintWebViewHelper::RenderPreviewPage( DCHECK(!draft_metafile.get()); draft_metafile = print_preview_context_.metafile()->GetMetafileForCurrentPage( - PDF_SKIA_DOCUMENT_TYPE); + SkiaDocumentType::PDF); } } return PreviewPageRendered(page_number, draft_metafile.get()); 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 5d069659cab..118bf92e812 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 @@ -30,7 +30,7 @@ bool PrintWebViewHelper::RenderPreviewPage( 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(PDF_SKIA_DOCUMENT_TYPE)); + draft_metafile.reset(new PdfMetafileSkia(SkiaDocumentType::PDF)); initial_render_metafile = draft_metafile.get(); } @@ -49,14 +49,14 @@ bool PrintWebViewHelper::RenderPreviewPage( DCHECK(!draft_metafile.get()); draft_metafile = print_preview_context_.metafile()->GetMetafileForCurrentPage( - PDF_SKIA_DOCUMENT_TYPE); + SkiaDocumentType::PDF); } return PreviewPageRendered(page_number, draft_metafile.get()); } bool PrintWebViewHelper::PrintPagesNative(blink::WebLocalFrame* frame, int page_count) { - PdfMetafileSkia metafile(PDF_SKIA_DOCUMENT_TYPE); + PdfMetafileSkia metafile(SkiaDocumentType::PDF); if (!metafile.Init()) return false; diff --git a/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.cc b/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.cc index 46465f4dfd4..af2a12c3ec2 100644 --- a/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.cc +++ b/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.cc @@ -7,10 +7,13 @@ #include "chrome/renderer/spellchecker/spellcheck_worditerator.h" #include +#include #include +#include #include "base/i18n/break_iterator.h" #include "base/logging.h" +#include "base/macros.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "third_party/icu/source/common/unicode/normlzr.h" @@ -21,11 +24,9 @@ // SpellcheckCharAttribute implementation: SpellcheckCharAttribute::SpellcheckCharAttribute() - : script_code_(USCRIPT_LATIN) { -} + : script_code_(USCRIPT_LATIN) {} -SpellcheckCharAttribute::~SpellcheckCharAttribute() { -} +SpellcheckCharAttribute::~SpellcheckCharAttribute() {} void SpellcheckCharAttribute::SetDefaultLanguage(const std::string& language) { CreateRuleSets(language); @@ -33,8 +34,8 @@ void SpellcheckCharAttribute::SetDefaultLanguage(const std::string& language) { base::string16 SpellcheckCharAttribute::GetRuleSet( bool allow_contraction) const { - return allow_contraction ? - ruleset_allow_contraction_ : ruleset_disallow_contraction_; + return allow_contraction ? ruleset_allow_contraction_ + : ruleset_disallow_contraction_; } void SpellcheckCharAttribute::CreateRuleSets(const std::string& language) { @@ -160,8 +161,13 @@ void SpellcheckCharAttribute::CreateRuleSets(const std::string& language) { // Treat numbers as word characters except for Arabic and Hebrew. const char* aletter_extra = " [0123456789]"; - if (script_code_ == USCRIPT_HEBREW || script_code_ == USCRIPT_ARABIC) + if (script_code_ == USCRIPT_HEBREW) aletter_extra = ""; + else if (script_code_ == USCRIPT_ARABIC) + // When "script=Arabic", it does not include tatweel, which is + // "script=Common" so add it back. Otherwise, it creates unwanted + // word breaks. + aletter_extra = " [\\u0640]"; const char kMidLetterExtra[] = ""; // For Hebrew, treat single/double quoation marks as MidLetter. @@ -178,19 +184,11 @@ void SpellcheckCharAttribute::CreateRuleSets(const std::string& language) { const char kDisallowContraction[] = ""; ruleset_allow_contraction_ = base::ASCIIToUTF16( - base::StringPrintf(kRuleTemplate, - aletter, - aletter_extra, - midletter_extra, - aletter_plus, - kAllowContraction)); + base::StringPrintf(kRuleTemplate, aletter, aletter_extra, midletter_extra, + aletter_plus, kAllowContraction)); ruleset_disallow_contraction_ = base::ASCIIToUTF16( - base::StringPrintf(kRuleTemplate, - aletter, - aletter_extra, - midletter_extra, - aletter_plus, - kDisallowContraction)); + base::StringPrintf(kRuleTemplate, aletter, aletter_extra, midletter_extra, + aletter_plus, kDisallowContraction)); } bool SpellcheckCharAttribute::OutputChar(UChar c, @@ -214,12 +212,11 @@ bool SpellcheckCharAttribute::OutputChar(UChar c, bool SpellcheckCharAttribute::OutputArabic(UChar c, base::string16* output) const { - // Discard characters not from Arabic alphabets. We also discard vowel marks - // of Arabic (Damma, Fatha, Kasra, etc.) to prevent our Arabic dictionary from - // marking an Arabic word including vowel marks as misspelled. (We need to - // check these vowel marks manually and filter them out since their script - // codes are USCRIPT_ARABIC.) - if (0x0621 <= c && c <= 0x064D) + // Include non-Arabic characters (which should trigger a spelling error) + // and Arabic characters excluding vowel marks and class "Lm". + // We filter the latter because, while they are "letters", they are + // optional and so don't affect the correctness of the rest of the word. + if (!(0x0600 <= c && c <= 0x06FF) || (u_isalpha(c) && c != 0x0640)) output->push_back(c); return true; } @@ -281,8 +278,8 @@ bool SpellcheckCharAttribute::OutputHebrew(UChar c, // USCRIPT_HEBREW.) // Pass through ASCII single/double quotation marks and Hebrew Geresh and // Gershayim. - if ((0x05D0 <= c && c <= 0x05EA) || c == 0x22 || c == 0x27 || - c == 0x05F4 || c == 0x05F3) + if ((0x05D0 <= c && c <= 0x05EA) || c == 0x22 || c == 0x27 || c == 0x05F4 || + c == 0x05F3) output->push_back(c); return true; } @@ -301,10 +298,7 @@ bool SpellcheckCharAttribute::OutputDefault(UChar c, // SpellcheckWordIterator implementation: SpellcheckWordIterator::SpellcheckWordIterator() - : text_(NULL), - attribute_(NULL), - iterator_() { -} + : text_(nullptr), attribute_(nullptr), iterator_() {} SpellcheckWordIterator::~SpellcheckWordIterator() { Reset(); @@ -357,9 +351,10 @@ bool SpellcheckWordIterator::SetText(const base::char16* text, size_t length) { return true; } -bool SpellcheckWordIterator::GetNextWord(base::string16* word_string, - int* word_start, - int* word_length) { +SpellcheckWordIterator::WordIteratorStatus SpellcheckWordIterator::GetNextWord( + base::string16* word_string, + int* word_start, + int* word_length) { DCHECK(!!text_); word_string->clear(); @@ -367,28 +362,41 @@ bool SpellcheckWordIterator::GetNextWord(base::string16* word_string, *word_length = 0; if (!text_) { - return false; + return IS_END_OF_TEXT; } - // Find a word that can be checked for spelling. Our rule sets filter out - // invalid words (e.g. numbers and characters not supported by the - // spellchecker language) so this ubrk_getRuleStatus() call returns - // UBRK_WORD_NONE when this iterator finds an invalid word. So, we skip such - // words until we can find a valid word or reach the end of the input string. + // Find a word that can be checked for spelling or a character that can be + // skipped over. Rather than moving past a skippable character this returns + // IS_SKIPPABLE and defers handling the character to the calling function. while (iterator_->Advance()) { const size_t start = iterator_->prev(); const size_t length = iterator_->pos() - start; - if (iterator_->IsWord()) { - if (Normalize(start, length, word_string)) { + switch (iterator_->GetWordBreakStatus()) { + case base::i18n::BreakIterator::IS_WORD_BREAK: { + if (Normalize(start, length, word_string)) { + *word_start = start; + *word_length = length; + return IS_WORD; + } + break; + } + case base::i18n::BreakIterator::IS_SKIPPABLE_WORD: { + *word_string = iterator_->GetString(); *word_start = start; *word_length = length; - return true; + return IS_SKIPPABLE; + } + // |iterator_| is RULE_BASED so the break status should never be + // IS_LINE_OR_CHAR_BREAK. + case base::i18n::BreakIterator::IS_LINE_OR_CHAR_BREAK: { + NOTREACHED(); + break; } } } // There aren't any more words in the given text. - return false; + return IS_END_OF_TEXT; } void SpellcheckWordIterator::Reset() { diff --git a/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.h b/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.h index 7e07d29273a..966137a324a 100644 --- a/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.h +++ b/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.h @@ -9,6 +9,8 @@ #ifndef CHROME_RENDERER_SPELLCHECKER_SPELLCHECK_WORDITERATOR_H_ #define CHROME_RENDERER_SPELLCHECKER_SPELLCHECK_WORDITERATOR_H_ +#include + #include #include @@ -19,8 +21,8 @@ namespace base { namespace i18n { class BreakIterator; -} // namespace i18n -} // namespace base +} // namespace i18n +} // namespace base // A class which encapsulates language-specific operations used by // SpellcheckWordIterator. When we set the spellchecker language, this class @@ -112,6 +114,17 @@ class SpellcheckCharAttribute { // class SpellcheckWordIterator { public: + enum WordIteratorStatus { + // The end of a sequence of text that the iterator recognizes as characters + // that can form a word. + IS_WORD, + // Non-word characters that the iterator can skip past, such as punctuation, + // whitespace, and characters from another character set. + IS_SKIPPABLE, + // The end of the text that the iterator is going over. + IS_END_OF_TEXT + }; + SpellcheckWordIterator(); ~SpellcheckWordIterator(); @@ -130,19 +143,30 @@ class SpellcheckWordIterator { // without calling Initialize(). bool SetText(const base::char16* text, size_t length); - // Retrieves a word (or a contraction), stores its copy to 'word_string', and - // stores the position and the length for input word to 'word_start'. Since - // this function normalizes the output word, the length of 'word_string' may - // be different from the 'word_length'. Therefore, when we call functions that - // changes the input text, such as string16::replace(), we need to use - // 'word_start' and 'word_length' as listed in the following snippet. + // Advances |iterator_| through |text_| and gets the current status of the + // word iterator within |text|: // - // while(iterator.GetNextWord(&word, &offset, &length)) - // text.replace(offset, length, word); + // - Returns IS_WORD if the iterator just found the end of a sequence of word + // characters and it was able to normalize the sequence. This stores the + // normalized string into |word_string| and stores the position and length + // into |word_start| and |word_length| respectively. Keep in mind that + // since this function normalizes the output word, the length of + // |word_string| may be different from the |word_length|. Therefore, when + // we call functions that change the input text, such as + // string16::replace(), we need to use |word_start| and |word_length| as + // listed in the following snippet: // - bool GetNextWord(base::string16* word_string, - int* word_start, - int* word_length); + // while(iterator.GetNextWord(&word, &offset, &length)) + // text.replace(offset, length, word); + // + // - Returns IS_SKIPPABLE if the iterator just found a character that the + // iterator can skip past such as punctuation, whitespace, and characters + // from another character set. This stores the character, position, and + // length into |word_string|, |word_start|, and |word_length| respectively. + // + // - Returns IS_END_OF_TEXT if the iterator has reached the end of |text_|. + SpellcheckWordIterator::WordIteratorStatus + GetNextWord(base::string16* word_string, int* word_start, int* word_length); // Releases all the resources attached to this object. void Reset(); diff --git a/chromium_src/chrome/renderer/tts_dispatcher.cc b/chromium_src/chrome/renderer/tts_dispatcher.cc index aea07712da8..7240ed71d33 100644 --- a/chromium_src/chrome/renderer/tts_dispatcher.cc +++ b/chromium_src/chrome/renderer/tts_dispatcher.cc @@ -8,7 +8,6 @@ #include "chrome/common/tts_messages.h" #include "chrome/common/tts_utterance_request.h" #include "content/public/renderer/render_thread.h" -#include "third_party/WebKit/public/platform/WebCString.h" #include "third_party/WebKit/public/platform/WebSpeechSynthesisUtterance.h" #include "third_party/WebKit/public/platform/WebSpeechSynthesisVoice.h" #include "third_party/WebKit/public/platform/WebString.h" diff --git a/chromium_src/components/pdf/common/pdf_messages.h b/chromium_src/components/pdf/common/pdf_messages.h deleted file mode 100644 index c6325be4e1b..00000000000 --- a/chromium_src/components/pdf/common/pdf_messages.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// Multiply-included file, no traditional include guard. -#include - -#include "content/public/common/common_param_traits_macros.h" -#include "content/public/common/referrer.h" -#include "ipc/ipc_message_macros.h" -#include "url/gurl.h" -#include "url/ipc/url_param_traits.h" - -#define IPC_MESSAGE_START PDFMsgStart - -// Brings up SaveAs... dialog to save specified URL. -IPC_MESSAGE_ROUTED2(PDFHostMsg_PDFSaveURLAs, - GURL /* url */, - content::Referrer /* referrer */) diff --git a/chromium_src/components/pdf/renderer/pepper_pdf_host.cc b/chromium_src/components/pdf/renderer/pepper_pdf_host.cc index ba3c87e70cc..96385148c4f 100644 --- a/chromium_src/components/pdf/renderer/pepper_pdf_host.cc +++ b/chromium_src/components/pdf/renderer/pepper_pdf_host.cc @@ -4,8 +4,8 @@ #include "components/pdf/renderer/pepper_pdf_host.h" +#include "atom/common/api/api_messages.h" #include "base/memory/ptr_util.h" -#include "components/pdf/common/pdf_messages.h" #include "content/public/common/referrer.h" #include "content/public/renderer/pepper_plugin_instance.h" #include "content/public/renderer/render_frame.h" @@ -77,8 +77,8 @@ int32_t PepperPDFHost::OnHostMsgSaveAs( referrer.url = url; referrer.policy = blink::kWebReferrerPolicyDefault; referrer = content::Referrer::SanitizeForRequest(url, referrer); - render_frame->Send( - new PDFHostMsg_PDFSaveURLAs(render_frame->GetRoutingID(), url, referrer)); + render_frame->Send(new AtomFrameHostMsg_PDFSaveURLAs( + render_frame->GetRoutingID(), url, referrer)); return PP_OK; } diff --git a/common.gypi b/common.gypi index 233e2104771..f4189345646 100644 --- a/common.gypi +++ b/common.gypi @@ -13,6 +13,8 @@ 'component%': 'static_library', 'debug_http2': 'false', 'debug_nghttp2': 'false', + # XXX(alexeykuzmin): Must match the clang version we use. See `clang -v`. + 'llvm_version': '6.0', 'python': 'python', 'openssl_fips': '', 'openssl_no_asm': 1, @@ -180,7 +182,9 @@ '-Wl,--no-whole-archive', ], }, { - 'libraries': [ '<@(libchromiumcontent_v8_libraries)' ], + 'libraries': [ + '<@(libchromiumcontent_v8_libraries)', + ], }], ], }], @@ -241,6 +245,25 @@ }], # OS=="win" ], }], + ['OS=="linux" and _toolset=="target" and _target_name in ["dump_syms", "node"]', { + 'conditions': [ + ['libchromiumcontent_component==0', { + 'libraries': [ + '<(libchromiumcontent_dir)/libc++.a', + ], + 'ldflags': [ + '-lpthread', + ], + }, { + 'libraries': [ + '<(libchromiumcontent_dir)/libc++.so', + ], + 'ldflags': [ + '-Wl,-rpath=\$$ORIGIN', + ], + }], + ], + }] ], 'msvs_cygwin_shell': 0, # Strangely setting it to 1 would make building under cygwin fail. 'msvs_disabled_warnings': [ diff --git a/default_app/icon.png b/default_app/icon.png index ac3a6547d9e..bc527dda8ae 100644 Binary files a/default_app/icon.png and b/default_app/icon.png differ diff --git a/docs-translations/README.md b/docs-translations/README.md index b7d945f0f2d..3b7676af53d 100644 --- a/docs-translations/README.md +++ b/docs-translations/README.md @@ -4,7 +4,7 @@ This directory once contained unstructured translations of Electron's documentation, but has been deprecated in favor of a new translation process using [Crowdin], a GitHub-friendly platform for collaborative translation. -For more details, visit the [electron/electron-i18n] repo. +For more details, visit the [electron/i18n] repo. ## Contributing @@ -15,19 +15,19 @@ If you're interested in helping translate Electron's docs, visit If you miss having access to Electron's raw markdown files in your preferred language, don't fret! You can still get raw docs, they're just in a -different place now. See [electron/electron-i18n/tree/master/content] +different place now. See [electron/i18n/tree/master/content] To more easily view and browse offline docs in your language, clone the repo and use [vmd], an Electron-based GitHub-styled markdown viewer: ```sh npm i -g vmd -git clone https://github.com/electron/electron-i18n +git clone https://github.com/electron/i18n vmd electron-i18n/content/zh-CN ``` [crowdin.com/project/electron]: https://crowdin.com/project/electron [Crowdin]: https://crowdin.com/project/electron -[electron/electron-i18n]: https://github.com/electron/electron-i18n#readme -[electron/electron-i18n/tree/master/content]: https://github.com/electron/electron-i18n/tree/master/content +[electron/i18n]: https://github.com/electron/i18n#readme +[electron/i18n/tree/master/content]: https://github.com/electron/i18n/tree/master/content [vmd]: http://ghub.io/vmd diff --git a/docs/README.md b/docs/README.md index 92bccccbd8f..13149314cfb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -14,34 +14,89 @@ an issue: * [Electron FAQ](faq.md) -## Guides +## Guides and Tutorials + +* [Setting up the Development Environment](tutorial/development-environment.md) + * [Setting up macOS](tutorial/development-environment.md#setting-up-macos) + * [Setting up Windows](tutorial/development-environment.md#setting-up-windows) + * [Setting up Linux](tutorial/development-environment.md#setting-up-linux) + * [Choosing an Editor](tutorial/development-environment.md#a-good-editor) +* [Creating your First App](tutorial/first-app.md) + * [Installing Electron](tutorial/first-app.md#installing-electron) + * [Electron Development in a Nutshell](tutorial/first-app.md#electron-development-in-a-nutshell) + * [Running Your App](tutorial/first-app.md#running-your-app) +* [Boilerplates and CLIs](tutorial/boilerplates-and-clis.md) + * [Boilerplate vs CLI](tutorial/boilerplates-and-clis.md#boilerplate-vs-cli) + * [electron-forge](tutorial/boilerplates-and-clis.md#electron-forge) + * [electron-builder](tutorial/boilerplates-and-clis.md#electron-builder) + * [electron-react-boilerplate](tutorial/boilerplates-and-clis.md#electron-react-boilerplate) + * [Other Tools and Boilerplates](tutorial/boilerplates-and-clis.md#other-tools-and-boilerplates) +* [Application Architecture](tutorial/application-architecture.md) + * [Main and Renderer Processes](tutorial/application-architecture.md#main-and-renderer-processes) + * [Using Electron's APIs](tutorial/application-architecture.md#using-electron-apis) + * [Using Node.js APIs](tutorial/application-architecture.md#using-node.js-apis) + * [Using Native Node.js Modules](tutorial/using-native-node-modules.md) + * [Inter-Process Communication](tutorial/application-architecture.md#) +* Adding Features to Your App + * [Notifications](tutorial/notifications.md) + * [Recent Documents](tutorial/desktop-environment-integration.md#recent-documents-windows-mac-os) + * [Application Progress](tutorial/progress-bar.md) + * [Custom Dock Menu](tutorial/desktop-environment-integration.md#custom-dock-menu-mac-os) + * [Custom Windows Taskbar](tutorial/windows-taskbar.md) + * [Custom Linux Desktop Actions](tutorial/linux-desktop-actions.md) + * [Keyboard Shortcuts](tutorial/keyboard-shortcuts.md) + * [Offline/Online Detection](tutorial/online-offline-events.md) + * [Represented File for macOS BrowserWindows](tutorial/represented-file.md) + * [Native File Drag & Drop](tutorial/native-file-drag-drop.md) +* [Application Accessibility](tutorial/accessibility.md) + * [Spectron](tutorial/accessibility.md#spectron) + * [Devtron](tutorial/accessibility.md#devtron) + * [Enabling Accessibility](tutorial/accessibility.md#enabling-accessibility) +* [Application Testing and Debugging](tutorial/application-debugging.md) + * [Debugging the Main Process](tutorial/debugging-main-process.md) + * [Using Selenium and WebDriver](tutorial/using-selenium-and-webdriver.md) + * [Testing on Headless CI Systems (Travis, Jenkins)](tutorial/testing-on-headless-ci.md) + * [DevTools Extension](tutorial/devtools-extension.md) +* [Application Distribution](tutorial/application-distribution.md) + * [Supported Platforms](tutorial/supported-platforms.md) + * [Mac App Store](tutorial/mac-app-store-submission-guide.md) + * [Windows Store](tutorial/windows-store-guide.md) + * [Snapcraft](tutorial/snapcraft.md) +* [Application Security](tutorial/security.md) + * [Reporting Security Issues](tutorial/security.md#reporting-security-issues) + * [Chromium Security Issues and Upgrades](tutorial/security.md#chromium-security-issues-and-upgrades) + * [Electron Security Warnings](tutorial/security.md#electron-security-warnings) + * [Security Checklist](tutorial/security.md#checklist-security-recommendations) +* [Application Updates](tutorial/updates.md) + * [Deploying an Update Server](tutorial/updates.md#deploying-an-update-server) + * [Implementing Updates in Your App](tutorial/updates.md#implementing-updates-in-your-app) + * [Applying Updates](tutorial/updates.md#applying-updates) + +## Detailed Tutorials + +These individual tutorials expand on topics discussed in the guide above. + +* [In Detail: Installing Electron](tutorial/installation.md) + * [Global versus Local Installation](tutorial/installation.md#global-versus-local-installation) + * [Proxies](tutorial/installation.md#proxies) + * [Custom Mirrors and Caches](tutorial/installation.md#custom-mirrors-and-caches) + * [Troubleshooting](tutorial/installation.md#troubleshooting) +* [In Detail: Electron's Versioning Scheme](tutorial/electron-versioning.md) + * [semver](tutorial/electron-versioning.md#semver) + * [Stabilization Branches](tutorial/electron-versioning.md#stabilization-branches) + * [Beta Releases and Bug Fixes](tutorial/electron-versioning.md#beta-releases-and-bug-fixes) +* [In Detail: Packaging App Source Code with asar](tutorial/application-packaging.md) + * [Generating asar Archives](tutorial/application-packaging.md#generating-asar-archives) + * [Using asar Archives](tutorial/application-packaging.md#using-asar-archives) + * [Limitations](tutorial/application-packaging.md#limitations-of-the-node-api) + * [Adding Unpacked Files to asar Archives](tutorial/application-packaging.md#adding-unpacked-files-to-asar-archives) +* [In Detail: Using Pepper Flash Plugin](tutorial/using-pepper-flash-plugin.md) +* [In Detail: Using Widevine CDM Plugin](tutorial/using-widevine-cdm-plugin.md) +* [Offscreen Rendering](tutorial/offscreen-rendering.md) + +--- * [Glossary of Terms](glossary.md) -* [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) -* [Using Selenium and WebDriver](tutorial/using-selenium-and-webdriver.md) -* [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) -* [Offscreen Rendering](tutorial/offscreen-rendering.md) -* [Keyboard Shortcuts](tutorial/keyboard-shortcuts.md) -* [Updating Applications](tutorial/updates.md) - -## Tutorials - -* [Quick Start](tutorial/quick-start.md) -* [Desktop Environment Integration](tutorial/desktop-environment-integration.md) -* [Online/Offline Event Detection](tutorial/online-offline-events.md) -* [REPL](tutorial/repl.md) -* [Native Notifications](tutorial/notifications.md) ## API References @@ -65,6 +120,7 @@ an issue: * [contentTracing](api/content-tracing.md) * [dialog](api/dialog.md) * [globalShortcut](api/global-shortcut.md) +* [inAppPurchase](api/in-app-purchase.md) * [ipcMain](api/ipc-main.md) * [Menu](api/menu.md) * [MenuItem](api/menu-item.md) @@ -96,6 +152,7 @@ an issue: * [Coding Style](development/coding-style.md) * [Using clang-format on C++ Code](development/clang-format.md) +* [Testing](development/testing.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) @@ -106,6 +163,9 @@ an issue: * [Debug Instructions (Windows)](development/debug-instructions-windows.md) * [Setting Up Symbol Server in debugger](development/setting-up-symbol-server.md) * [Documentation Styleguide](styleguide.md) -* [Upgrading Chrome](development/upgrading-chrome.md) +* [Contributing to Electron](../CONTRIBUTING.md) +* [Issues](development/issues.md) +* [Pull Requests](development/pull-requests.md) +* [Upgrading Chromium](development/upgrading-chromium.md) * [Chromium Development](development/chromium-development.md) * [V8 Development](development/v8-development.md) diff --git a/docs/api/app.md b/docs/api/app.md index 00cf2eb9523..926d1dde8ff 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -194,7 +194,7 @@ Returns: [`NSUserActivity.activityType`][activity-type]. * `userInfo` Object - Contains app-specific state stored by the activity. -Emitted when [Handoff][handoff] is about to be resumed on another device. If you need to update the state to be transferred, you should call `event.preventDefault()` immediatelly, construct a new `userInfo` dictionary and call `app.updateCurrentActiviy()` in a timely manner. Otherwise the operation will fail and `continue-activity-error` will be called. +Emitted when [Handoff][handoff] is about to be resumed on another device. If you need to update the state to be transferred, you should call `event.preventDefault()` immediately, construct a new `userInfo` dictionary and call `app.updateCurrentActiviy()` in a timely manner. Otherwise the operation will fail and `continue-activity-error` will be called. ### Event: 'new-window-for-tab' _macOS_ @@ -844,11 +844,6 @@ disables that behaviour. This method can only be called before app is ready. -### `app.getAppMemoryInfo()` _Deprecated_ - -Returns [`ProcessMetric[]`](structures/process-metric.md): Array of `ProcessMetric` objects that correspond to memory and cpu usage statistics of all the processes associated with the app. -**Note:** This method is deprecated, use `app.getAppMetrics()` instead. - ### `app.getAppMetrics()` Returns [`ProcessMetric[]`](structures/process-metric.md): Array of `ProcessMetric` objects that correspond to memory and cpu usage statistics of all the processes associated with the app. @@ -970,6 +965,23 @@ details. Disabled by default. Set the about panel options. This will override the values defined in the app's `.plist` file. See the [Apple docs][about-panel-options] for more details. +### `app.startAccessingSecurityScopedResource(bookmarkData)` _macOS (mas)_ + +* `bookmarkData` String - The base64 encoded security scoped bookmark data returned by the `dialog.showOpenDialog` or `dialog.showSaveDialog` methods. + +Returns `Function` - This function **must** be called once you have finished accessing the security scoped file. If you do not remember to stop accessing the bookmark, [kernel resources will be leaked](https://developer.apple.com/reference/foundation/nsurl/1417051-startaccessingsecurityscopedreso?language=objc) and your app will lose its ability to reach outside the sandbox completely, until your app is restarted. + +```js +// Start accessing the file. +const stopAccessingSecurityScopedResource = app.startAccessingSecurityScopedResource(data) +// You can now access the file outside of the sandbox ๐ŸŽ‰ + +// Remember to stop accessing the file once you've finished with it. +stopAccessingSecurityScopedResource() +``` + +Start accessing a security scoped resource. With this method electron applications that are packaged for the Mac App Store may reach outside their sandbox to access files chosen by the user. See [Apple's documentation](https://developer.apple.com/library/content/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW16) for a description of how this system works. + ### `app.commandLine.appendSwitch(switch[, value])` * `switch` String - A command-line switch @@ -1078,7 +1090,7 @@ Sets the application's [dock menu][dock-menu]. 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 +[tasks]:https://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 diff --git a/docs/api/auto-updater.md b/docs/api/auto-updater.md index 9631ad75926..66bcf19dbe0 100644 --- a/docs/api/auto-updater.md +++ b/docs/api/auto-updater.md @@ -88,10 +88,13 @@ On Windows only `releaseName` is available. The `autoUpdater` object has the following methods: -### `autoUpdater.setFeedURL(url[, requestHeaders])` +### `autoUpdater.setFeedURL(options)` -* `url` String -* `requestHeaders` Object (optional) _macOS_ - HTTP request headers. +* `options` Object + * `url` String + * `headers` Object (optional) _macOS_ - HTTP request headers. + * `serverType` String (optional) _macOS_ - Either `json` or `default`, see the [Squirrel.Mac][squirrel-mac] + README for more information. Sets the `url` and initialize the auto updater. @@ -109,9 +112,13 @@ using this API. Restarts the app and installs the update after it has been downloaded. It should only be called after `update-downloaded` has been emitted. -**Note:** `autoUpdater.quitAndInstall()` will close all application windows -first and only emit `before-quit` event on `app` after that. This is different -from the normal quit event sequence. +Under the hood calling `autoUpdater.quitAndInstall()` will close all application +windows first, and automatically call `app.quit()` after all windows have been +closed. + +**Note:** If the application is quit without calling this API after the +`update-downloaded` event has been emitted, the application will still be +replaced by the updated one on the next run. [squirrel-mac]: https://github.com/Squirrel/Squirrel.Mac [server-support]: https://github.com/Squirrel/Squirrel.Mac#server-support diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index ef90dc01330..378fe78bae0 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -133,7 +133,7 @@ state is `hidden` in order to minimize power consumption. Process: [Main](../glossary.md#main-process) `BrowserWindow` is an -[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter). +[EventEmitter](https://nodejs.org/api/events.html#events_class_events_eventemitter). It creates a new `BrowserWindow` with native properties as set by the `options`. @@ -220,7 +220,6 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. * `hidden` - Results in a hidden title bar and a full size content window, yet the title bar still has the standard window controls ("traffic lights") in the top left. - * `hidden-inset` - Deprecated, use `hiddenInset` instead. * `hiddenInset` - Results in a hidden title bar with an alternative look where the traffic light buttons are slightly more inset from the window edge. * `customButtonsOnHover` Boolean (optional) - Draw custom close, minimize, @@ -229,7 +228,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. buttons prevent issues with mouse events that occur with the standard window toolbar buttons. **Note:** This option is currently experimental. * `fullscreenWindowTitle` Boolean (optional) - Shows the title in the - tile bar in full screen mode on macOS for all `titleBarStyle` options. + title bar in full screen mode on macOS for all `titleBarStyle` options. Default is `false`. * `thickFrame` Boolean (optional) - Use `WS_THICKFRAME` style for frameless windows on Windows, which adds standard window frame. Setting it to `false` will remove @@ -280,6 +279,13 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. same `partition`. If there is no `persist:` prefix, the page will use an in-memory session. By assigning the same `partition`, multiple pages can share the same session. Default is the default session. + * `affinity` String (optional) - When specified, web pages with the same + `affinity` will run in the same renderer process. Note that due to reusing + the renderer process, certain `webPreferences` options will also be shared + between the web pages even when you specified different values for them, + including but not limited to `preload`, `sandbox` and `nodeIntegration`. + So it is suggested to use exact same `webPreferences` for web pages with + the same `affinity`. * `zoomFactor` Number (optional) - The default zoom factor of the page, `3.0` represents `300%`. Default is `1.0`. * `javascript` Boolean (optional) - Enables JavaScript support. Default is `true`. @@ -343,7 +349,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. Console tab. **Note:** This option is currently experimental and may change or be removed in future Electron releases. * `nativeWindowOpen` Boolean (optional) - Whether to use native - `window.open()`. Defaults to `false`. **Note:** This option is currently + `window.open()`. Defaults to `false`. **Note:** This option is currently experimental. * `webviewTag` Boolean (optional) - Whether to enable the [`` tag](webview-tag.md). Defaults to the value of the `nodeIntegration` option. **Note:** The @@ -353,6 +359,15 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. script. You can use the `will-attach-webview` event on [webContents](web-contents.md) to strip away the `preload` script and to validate or alter the ``'s initial settings. + * `additionArguments` String[] (optional) - A list of strings that will be appended + to `process.argv` in the renderer process of this app. Useful for passing small + bits of data down to renderer process preload scripts. + * `safeDialogs` Boolean (optional) - Whether to enable browser style + consecutive dialog protection. Default is `false`. + * `safeDialogsMessage` String (optional) - The message to display when + consecutive dialog protection is triggered. If not defined the default + message would be used, note that currently the default message is in + English and not localized. When setting minimum or maximum window size with `minWidth`/`maxWidth`/ `minHeight`/`maxHeight`, it only constrains the users. It won't prevent you from @@ -572,7 +587,7 @@ Returns `BrowserWindow[]` - An array of all opened browser windows. #### `BrowserWindow.getFocusedWindow()` -Returns `BrowserWindow` - The window that is focused in this application, otherwise returns `null`. +Returns `BrowserWindow | null` - The window that is focused in this application, otherwise returns `null`. #### `BrowserWindow.fromWebContents(webContents)` @@ -847,6 +862,12 @@ the supplied bounds. Returns [`Rectangle`](structures/rectangle.md) +#### `win.setEnabled(enable)` + +* `enable` Boolean + +Disable or enable the window. + #### `win.setSize(width, height[, animate])` * `width` Integer @@ -1159,6 +1180,14 @@ win.loadURL('http://localhost:8000/post', { }) ``` +#### `win.loadFile(filePath)` + +* `filePath` String + +Same as `webContents.loadFile`, `filePath` should be a path to an HTML +file relative to the root of your application. See the `webContents` docs +for more information. + #### `win.reload()` Same as `webContents.reload`. @@ -1459,6 +1488,6 @@ removed in future Electron releases. [blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/runtime_enabled_features.json5?l=70 [page-visibility-api]: https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API [quick-look]: https://en.wikipedia.org/wiki/Quick_Look -[vibrancy-docs]: https://developer.apple.com/reference/appkit/nsvisualeffectview?language=objc +[vibrancy-docs]: https://developer.apple.com/documentation/appkit/nsvisualeffectview?preferredLanguage=objc [window-levels]: https://developer.apple.com/reference/appkit/nswindow/1664726-window_levels [chrome-content-scripts]: https://developer.chrome.com/extensions/content_scripts#execution-environment diff --git a/docs/api/chrome-command-line-switches.md b/docs/api/chrome-command-line-switches.md index f3e189fdf43..09c24f6c195 100644 --- a/docs/api/chrome-command-line-switches.md +++ b/docs/api/chrome-command-line-switches.md @@ -28,6 +28,10 @@ Disables the disk cache for HTTP requests. Disable HTTP/2 and SPDY/3.1 protocols. +## --lang + +Set a custom locale. + ## --inspect=`port` and --inspect-brk=`port` Debug-related flags, see the [Debugging the Main Process][debugging-main-process] guide for details. diff --git a/docs/api/client-request.md b/docs/api/client-request.md index f020493694d..5cd9562b654 100644 --- a/docs/api/client-request.md +++ b/docs/api/client-request.md @@ -32,7 +32,7 @@ the hostname and the port number 'hostname:port'. * `redirect` String (optional) - The redirect mode for this request. Should be one of `follow`, `error` or `manual`. Defaults to `follow`. When mode is `error`, any redirection will be aborted. When mode is `manual` the redirection will be -deferred until [`request.followRedirect`](#requestfollowRedirect) is invoked. Listen for the [`redirect`](#event-redirect) event in +deferred until [`request.followRedirect`](#requestfollowredirect) is invoked. Listen for the [`redirect`](#event-redirect) event in this mode to get more details about the redirect request. `options` properties such as `protocol`, `host`, `hostname`, `port` and `path` @@ -137,7 +137,7 @@ Returns: * `responseHeaders` Object Emitted when there is redirection and the mode is `manual`. Calling -[`request.followRedirect`](#requestfollowRedirect) will continue with the redirection. +[`request.followRedirect`](#requestfollowredirect) will continue with the redirection. ### Instance Properties diff --git a/docs/api/crash-reporter.md b/docs/api/crash-reporter.md index cfa133fbc3d..3d26dfa1182 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -24,6 +24,10 @@ following projects: * [socorro](https://github.com/mozilla/socorro) * [mini-breakpad-server](https://github.com/electron/mini-breakpad-server) +Or use a 3rd party hosted solution: + +* [Backtrace I/O](https://backtrace.io/electron/) + Crash reports are saved locally in an application-specific temp directory folder. For a `productName` of `YourName`, crash reports will be stored in a folder named `YourName Crashes` inside the temp directory. You can customize this temp @@ -62,7 +66,7 @@ This will start the process that will monitor and send the crash reports. Replac and `crashesDirectory` with appropriate values. **Note:** If you need send additional/updated `extra` parameters after your -first call `start` you can call `setExtraParameter` on macOS or call `start` +first call `start` you can call `addExtraParameter` on macOS or call `start` again with the new/updated `extra` parameters on Linux and Windows. ```js diff --git a/docs/api/dialog.md b/docs/api/dialog.md index cf4e9e17f06..a69cba8b969 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -50,8 +50,10 @@ The `dialog` module has the following methods: as a directory instead of a file. * `message` String (optional) _macOS_ - Message to display above input boxes. + * `securityScopedBookmarks` Boolean (optional) _masOS_ _mas_ - Create [security scoped bookmarks](https://developer.apple.com/library/content/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW16) when packaged for the Mac App Store. * `callback` Function (optional) * `filePaths` String[] - An array of file paths chosen by the user + * `bookmarks` String[] _macOS_ _mas_ - An array matching the `filePaths` array of base64 encoded strings which contains security scoped bookmark data. `securityScopedBookmarks` must be enabled for this to be populated. Returns `String[]`, an array of file paths chosen by the user, if the callback is provided it returns `undefined`. @@ -99,8 +101,10 @@ shown. displayed in front of the filename text field. * `showsTagField` Boolean (optional) _macOS_ - Show the tags input box, defaults to `true`. + * `securityScopedBookmarks` Boolean (optional) _masOS_ _mas_ - Create a [security scoped bookmark](https://developer.apple.com/library/content/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW16) when packaged for the Mac App Store. If this option is enabled and the file doesn't already exist a blank file will be created at the chosen path. * `callback` Function (optional) * `filename` String + * `bookmark` String _macOS_ _mas_ - Base64 encoded string which contains the security scoped bookmark data for the saved file. `securityScopedBookmarks` must be enabled for this to be present. Returns `String`, the path of the file chosen by the user, if a callback is provided it returns `undefined`. diff --git a/docs/api/download-item.md b/docs/api/download-item.md index 9a9cfc6a4a9..f5265d26a5d 100644 --- a/docs/api/download-item.md +++ b/docs/api/download-item.md @@ -44,7 +44,7 @@ win.webContents.session.on('will-download', (event, item, webContents) => { Returns: * `event` Event -* `state` String +* `state` String - Can be `progressing` or `interrupted`. Emitted when the download has been updated and is not done. @@ -58,7 +58,7 @@ The `state` can be one of following: Returns: * `event` Event -* `state` String +* `state` String - Can be `completed`, `cancelled` or `interrupted`. Emitted when the download is in a terminal state. This includes a completed download, a cancelled download (via `downloadItem.cancel()`), and interrupted diff --git a/docs/api/in-app-purchase.md b/docs/api/in-app-purchase.md new file mode 100644 index 00000000000..0757bfd5cba --- /dev/null +++ b/docs/api/in-app-purchase.md @@ -0,0 +1,39 @@ +# inAppPurchase + +> In-app purchases on Mac App Store. + +Process: [Main](../glossary.md#main-process) + +## Events + +The `inAppPurchase` module emits the following events: + +### Event: 'transactions-updated' + +Emitted when one or more transactions have been updated. + +Returns: + +* `event` Event +* `transactions` Transaction[] - Array of [`Transaction`](structures/transaction) objects. + +## Methods + +The `inAppPurchase` module has the following methods: + +### `inAppPurchase.purchaseProduct(productID, quantity, callback)` + +* `productID` String - The id of the product to purchase. (the id of `com.example.app.product1` is `product1`). +* `quantity` Integer (optional) - The number of items the user wants to purchase. +* `callback` Function (optional) - The callback called when the payment is added to the PaymentQueue. +* `isProductValid` Boolean - Determine if the product is valid and added to the payment queue. + +You should listen for the `transactions-updated` event as soon as possible and certainly before you call `purchaseProduct`. + +### `inAppPurchase.canMakePayments()` + +Returns `true` if the user can make a payment and `false` otherwise. + +### `inAppPurchase.getReceiptURL()` + +Returns `String`, the path to the receipt. diff --git a/docs/api/menu-item.md b/docs/api/menu-item.md index a44a76e4bfb..f687e43dbc9 100644 --- a/docs/api/menu-item.md +++ b/docs/api/menu-item.md @@ -54,19 +54,19 @@ The `role` property can have following values: * `cut` * `copy` * `paste` -* `pasteandmatchstyle` -* `selectall` +* `pasteAndMatchStyle` +* `selectAll` * `delete` * `minimize` - Minimize current window. * `close` - Close current window. * `quit`- Quit the application. * `reload` - Reload the current window. -* `forcereload` - Reload the current window ignoring the cache. -* `toggledevtools` - Toggle developer tools in the current window. -* `togglefullscreen`- Toggle full screen mode on the current window. -* `resetzoom` - Reset the focused page's zoom level to the original size. -* `zoomin` - Zoom in the focused page by 10%. -* `zoomout` - Zoom out the focused page by 10%. +* `forceReload` - Reload the current window ignoring the cache. +* `toggleDevTools` - Toggle developer tools in the current window. +* `toggleFullScreen`- Toggle full screen mode on the current window. +* `resetZoom` - Reset the focused page's zoom level to the original size. +* `zoomIn` - Zoom in the focused page by 10%. +* `zoomOut` - Zoom out the focused page by 10%. * `editMenu` - Whole default "Edit" menu (Undo, Copy, etc.). * `windowMenu` - Whole default "Window" menu (Minimize, Close, etc.). @@ -74,25 +74,26 @@ The following additional roles are available on _macOS_: * `about` - Map to the `orderFrontStandardAboutPanel` action. * `hide` - Map to the `hide` action. -* `hideothers` - Map to the `hideOtherApplications` action. +* `hideOthers` - Map to the `hideOtherApplications` action. * `unhide` - Map to the `unhideAllApplications` action. -* `startspeaking` - Map to the `startSpeaking` action. -* `stopspeaking` - Map to the `stopSpeaking` action. +* `startSpeaking` - Map to the `startSpeaking` action. +* `stopSpeaking` - Map to the `stopSpeaking` action. * `front` - Map to the `arrangeInFront` action. * `zoom` - Map to the `performZoom` action. -* `toggletabbar` - Map to the `toggleTabBar` action. -* `selectnexttab` - Map to the `selectNextTab` action. -* `selectprevioustab` - Map to the `selectPreviousTab` action. -* `mergeallwindows` - Map to the `mergeAllWindows` action. -* `movetabtonewwindow` - Map to the `moveTabToNewWindow` action. +* `toggleTabBar` - Map to the `toggleTabBar` action. +* `selectNextTab` - Map to the `selectNextTab` action. +* `selectPreviousTab` - Map to the `selectPreviousTab` action. +* `mergeAllWindows` - Map to the `mergeAllWindows` action. +* `moveTabToNewWindow` - Map to the `moveTabToNewWindow` action. * `window` - The submenu is a "Window" menu. * `help` - The submenu is a "Help" menu. * `services` - The submenu is a "Services" menu. -* `recentdocuments` - The submenu is an "Open Recent" menu. -* `clearrecentdocuments` - Map to the `clearRecentDocuments` action. +* `recentDocuments` - The submenu is an "Open Recent" menu. +* `clearRecentDocuments` - Map to the `clearRecentDocuments` action. When specifying a `role` on macOS, `label` and `accelerator` are the only options that will affect the menu item. All other options will be ignored. +Lowercase `role`, e.g. `toggledevtools`, is still supported. ### Instance Properties diff --git a/docs/api/menu.md b/docs/api/menu.md index cc4922e6e4d..4c118879610 100644 --- a/docs/api/menu.md +++ b/docs/api/menu.md @@ -59,10 +59,10 @@ will become properties of the constructed menu items. The `menu` object has the following instance methods: -#### `menu.popup([browserWindow, options])` +#### `menu.popup(options)` -* `browserWindow` [BrowserWindow](browser-window.md) (optional) - Default is the focused window. -* `options` Object (optional) +* `options` Object + * `window` [BrowserWindow](browser-window.md) (optional) - Default is the focused window. * `x` Number (optional) - Default is the current mouse cursor position. Must be declared if `y` is declared. * `y` Number (optional) - Default is the current mouse cursor position. @@ -70,6 +70,7 @@ The `menu` object has the following instance methods: * `positioningItem` Number (optional) _macOS_ - The index of the menu item to be positioned under the mouse cursor at the specified coordinates. Default is -1. + * `callback` Function (optional) - Called when menu is closed. Pops up this menu as a context menu in the [`BrowserWindow`](browser-window.md). @@ -98,6 +99,29 @@ Returns `MenuItem` the item with the specified `id` Inserts the `menuItem` to the `pos` position of the menu. +### Instance Events + +Objects created with `new Menu` emit the following events: + +**Note:** Some events are only available on specific operating systems and are +labeled as such. + +#### Event: 'menu-will-show' + +Returns: + +* `event` Event + +Emitted when `menu.popup()` is called. + +#### Event: 'menu-will-close' + +Returns: + +* `event` Event + +Emitted when a popup is closed either manually or with `menu.closePopup()`. + ### Instance Properties `menu` objects also have the following properties: @@ -109,6 +133,11 @@ A `MenuItem[]` array containing the menu's items. Each `Menu` consists of multiple [`MenuItem`](menu-item.md)s and each `MenuItem` can have a submenu. +### Instance Events + +Objects created with `new Menu` or returned by `Menu.buildFromTemplate` emit +the following events: + ## Examples The `Menu` class is only available in the main process, but you can also use it @@ -230,7 +259,7 @@ menu.append(new MenuItem({label: 'MenuItem2', type: 'checkbox', checked: true})) window.addEventListener('contextmenu', (e) => { e.preventDefault() - menu.popup(remote.getCurrentWindow()) + menu.popup({window: remote.getCurrentWindow()}) }, false) ``` diff --git a/docs/api/notification.md b/docs/api/notification.md index 9d8f15853ed..571fc88d7ed 100644 --- a/docs/api/notification.md +++ b/docs/api/notification.md @@ -15,7 +15,7 @@ If you want to show Notifications from a renderer process you should use the [HT Process: [Main](../glossary.md#main-process) `Notification` is an -[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter). +[EventEmitter](https://nodejs.org/api/events.html#events_class_events_eventemitter). It creates a new `Notification` with native properties as set by the `options`. @@ -39,7 +39,7 @@ Returns `Boolean` - Whether or not desktop notifications are supported on the cu * `replyPlaceholder` String (optional) _macOS_ - The placeholder to write in the inline reply input field. * `sound` String (optional) _macOS_ - The name of the sound file to play when the notification is shown. * `actions` [NotificationAction[]](structures/notification-action.md) (optional) _macOS_ - Actions to add to the notification. Please read the available actions and limitations in the `NotificationAction` documentation. - + * `closeButtonText` String (optional) _macOS_ - A custom title for the close button of an alert. An empty string will cause the default localized text to be used. ### Instance Events diff --git a/docs/api/power-monitor.md b/docs/api/power-monitor.md index 0c012c64ac8..5d600da48a9 100644 --- a/docs/api/power-monitor.md +++ b/docs/api/power-monitor.md @@ -39,3 +39,10 @@ Emitted when the system changes to AC power. ### Event: 'on-battery' _Windows_ Emitted when system changes to battery power. + +### Event: 'shutdown' _Linux_ _macOS_ + +Emitted when the system is about to reboot or shut down. If the event handler +invokes `e.preventDefault()`, Electron will attempt to delay system shutdown in +order for the app to exit cleanly. If `e.preventDefault()` is called, the app +should exit as soon as possible by calling something like `app.quit()`. diff --git a/docs/api/remote.md b/docs/api/remote.md index 4c721e68229..716332428e0 100644 --- a/docs/api/remote.md +++ b/docs/api/remote.md @@ -119,7 +119,7 @@ event is emitted. To avoid this problem, ensure you clean up any references to renderer callbacks passed to the main process. This involves cleaning up event handlers, or -ensuring the main process is explicitly told to deference callbacks that came +ensuring the main process is explicitly told to dereference callbacks that came from a renderer process that is exiting. ## Accessing built-in modules in the main process @@ -195,5 +195,5 @@ process. The `process` object in the main process. This is the same as `remote.getGlobal('process')` but is cached. -[rmi]: http://en.wikipedia.org/wiki/Java_remote_method_invocation +[rmi]: https://en.wikipedia.org/wiki/Java_remote_method_invocation [enumerable-properties]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties diff --git a/docs/api/sandbox-option.md b/docs/api/sandbox-option.md index b67b9783dfb..a4355376dcf 100644 --- a/docs/api/sandbox-option.md +++ b/docs/api/sandbox-option.md @@ -189,7 +189,7 @@ feature. We are still not aware of the security implications of exposing some electron renderer APIs to the preload script, but here are some things to consider before rendering untrusted content: -- A preload script can accidentaly leak privileged APIs to untrusted code. +- A preload script can accidentally leak privileged APIs to untrusted code. - Some bug in V8 engine may allow malicious code to access the renderer preload APIs, effectively granting full access to the system through the `remote` module. diff --git a/docs/api/session.md b/docs/api/session.md index f75b6399f10..d20969cbba2 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -187,7 +187,7 @@ The `proxyBypassRules` is a comma separated list of rules described below: Examples: "127.0.1", "[0:0::1]", "[::1]", "http://[::1]:99" -* `IP_LITERAL "/" PREFIX_LENGHT_IN_BITS` +* `IP_LITERAL "/" PREFIX_LENGTH_IN_BITS` Match any URL that is to an IP literal that falls between the given range. IP range is specified using CIDR notation. @@ -195,7 +195,7 @@ The `proxyBypassRules` is a comma separated list of rules described below: Examples: "192.168.1.1/16", "fefe:13::abc/33". -* `` +* `` Match local addresses. The meaning of `` is whether the host matches one of: "127.0.0.1", "::1", "localhost". @@ -293,6 +293,8 @@ win.webContents.session.setCertificateVerifyProc((request, callback) => { 'pointerLock', 'fullscreen', 'openExternal'. * `callback` Function * `permissionGranted` Boolean - Allow or deny the permission. + * `details` Object - Some properties are only available on certain permission types. + * `externalURL` String - The url of the `openExternal` request. Sets the handler which can be used to respond to permission requests for the `session`. Calling `callback(true)` will allow the permission and `callback(false)` will reject it. @@ -317,7 +319,7 @@ Clears the host resolver cache. #### `ses.allowNTLMCredentialsForDomains(domains)` -* `domains` String - A comma-seperated list of servers for which +* `domains` String - A comma-separated list of servers for which integrated authentication is enabled. Dynamically sets whether to always send credentials for HTTP NTLM or Negotiate @@ -356,8 +358,6 @@ Returns `String` - The user agent for this session. * `callback` Function * `result` Buffer - Blob data. -Returns `Blob` - The blob data associated with the `identifier`. - #### `ses.createInterruptedDownload(options)` * `options` Object @@ -384,6 +384,18 @@ the initial state will be `interrupted`. The download will start only when the Clears the sessionโ€™s HTTP authentication cache. +#### `ses.setPreloads(preloads)` + +* `preloads` String[] - An array of absolute path to preload scripts + +Adds scripts that will be executed on ALL web contents that are associated with +this session just before normal `preload` scripts run. + +#### `ses.getPreloads()` + +Returns `String[]` an array of paths to preload scripts that have been +registered. + ### Instance Properties The following properties are available on instances of `Session`: diff --git a/docs/api/shell.md b/docs/api/shell.md index 5462da9e29e..45d18add854 100644 --- a/docs/api/shell.md +++ b/docs/api/shell.md @@ -36,7 +36,7 @@ Open the given file in the desktop's default manner. ### `shell.openExternal(url[, options, callback])` -* `url` String - max 2081 characters on windows, or the function returns false. +* `url` String - Max 2081 characters on windows, or the function returns false. * `options` Object (optional) _macOS_ * `activate` Boolean - `true` to bring the opened application to the foreground. The default is `true`. diff --git a/docs/api/structures/crash-report.md b/docs/api/structures/crash-report.md index b26d4adeca7..5248cba8043 100644 --- a/docs/api/structures/crash-report.md +++ b/docs/api/structures/crash-report.md @@ -1,4 +1,4 @@ # CrashReport Object -* `date` String -* `ID` Integer \ No newline at end of file +* `date` Date +* `id` String diff --git a/docs/api/structures/notification-action.md b/docs/api/structures/notification-action.md index a2cf4e078a9..9817502a815 100644 --- a/docs/api/structures/notification-action.md +++ b/docs/api/structures/notification-action.md @@ -7,7 +7,7 @@ | Action Type | Platform Support | Usage of `text` | Default `text` | Limitations | |-------------|------------------|-----------------|----------------|-------------| -| `button` | macOS | Used as the label for the button | "Show" | Maximum of one button, if multiple are provided only the last is used. This action is also incompatible with `hasReply` and will be ignored if `hasReply` is `true`. | +| `button` | macOS | Used as the label for the button | "Show" (or a localized string by system default if first of such `button`, otherwise empty) | Only the first one is used. If multiple are provided, those beyond the first will be listed as additional actions (displayed when mouse active over the action button). Any such action also is incompatible with `hasReply` and will be ignored if `hasReply` is `true`. | ### Button support on macOS @@ -15,6 +15,6 @@ In order for extra notification buttons to work on macOS your app must meet the following criteria. * App is signed -* App has it's `NSUserNotificationAlertStyle` set to `alert` in the `info.plist`. +* App has it's `NSUserNotificationAlertStyle` set to `alert` in the `Info.plist`. If either of these requirements are not met the button simply won't appear. diff --git a/docs/api/structures/transaction.md b/docs/api/structures/transaction.md new file mode 100644 index 00000000000..78ee4e8ad0c --- /dev/null +++ b/docs/api/structures/transaction.md @@ -0,0 +1,11 @@ +# Transaction Object + +* `transactionIdentifier` String +* `transactionDate` String +* `originalTransactionIdentifier` String +* `transactionState` String - The transaction sate (`"purchasing"`, `"purchased"`, `"failed"`, `"restored"`, or `"deferred"`) +* `errorCode` Integer +* `errorMessage` String +* `payment` Object + * `productIdentifier` String + * `quantity` Integer diff --git a/docs/api/structures/web-source.md b/docs/api/structures/web-source.md new file mode 100644 index 00000000000..240cb265acd --- /dev/null +++ b/docs/api/structures/web-source.md @@ -0,0 +1,5 @@ +# WebSource Object + +* `code` String +* `url` String (optional) +* `startLine` Integer (optional) - Default is 1. \ No newline at end of file diff --git a/docs/api/system-preferences.md b/docs/api/system-preferences.md index 1fe53eed360..5f6f004a3c1 100644 --- a/docs/api/system-preferences.md +++ b/docs/api/system-preferences.md @@ -106,6 +106,12 @@ This is necessary for events such as `NSUserDefaultsDidChangeNotification`. Same as `unsubscribeNotification`, but removes the subscriber from `NSNotificationCenter`. +### `systemPreferences.registerDefaults(defaults)` _macOS_ + +* `defaults` Object - a dictionary of (`key: value`) user defaults + +Add the specified defaults to your application's `NSUserDefaults`. + ### `systemPreferences.getUserDefault(key, type)` _macOS_ * `key` String diff --git a/docs/api/touch-bar.md b/docs/api/touch-bar.md index 4145210461c..5dba25c76fb 100644 --- a/docs/api/touch-bar.md +++ b/docs/api/touch-bar.md @@ -8,7 +8,7 @@ Process: [Main](../tutorial/quick-start.md#main-process) * `options` Object * `items` ([TouchBarButton](touch-bar-button.md) | [TouchBarColorPicker](touch-bar-color-picker.md) | [TouchBarGroup](touch-bar-group.md) | [TouchBarLabel](touch-bar-label.md) | [TouchBarPopover](touch-bar-popover.md) | [TouchBarScrubber](touch-bar-scrubber.md) | [TouchBarSegmentedControl](touch-bar-segmented-control.md) | [TouchBarSlider](touch-bar-slider.md) | [TouchBarSpacer](touch-bar-spacer.md))[] - * `escapeItem` ([TouchBarButton](touch-bar-button.md) | [TouchBarColorPicker](touch-bar-color-picker.md) | [TouchBarGroup](touch-bar-group.md) | [TouchBarLabel](touch-bar-label.md) | [TouchBarPopover](touch-bar-popover.md) | [TouchBarScrubber](touch-bar-scrubber.md) | [TouchBarSegmentedControl](touch-bar-segmented-control.md) | [TouchBarSlider](touch-bar-slider.md) | [TouchBarSpacer](touch-bar-spacer.md)) (optional) + * `escapeItem` ([TouchBarButton](touch-bar-button.md) | [TouchBarColorPicker](touch-bar-color-picker.md) | [TouchBarGroup](touch-bar-group.md) | [TouchBarLabel](touch-bar-label.md) | [TouchBarPopover](touch-bar-popover.md) | [TouchBarScrubber](touch-bar-scrubber.md) | [TouchBarSegmentedControl](touch-bar-segmented-control.md) | [TouchBarSlider](touch-bar-slider.md) | [TouchBarSpacer](touch-bar-spacer.md) | null) (optional) Creates a new touch bar with the specified items. Use `BrowserWindow.setTouchBar` to add the `TouchBar` to a window. @@ -26,7 +26,7 @@ The following properties are available on instances of `TouchBar`: #### `touchBar.escapeItem` -The `TouchBarButton` that will replace the "esc" button on the touch bar when set. +A `TouchBarItem` that will replace the "esc" button on the touch bar when set. Setting to `null` restores the default "esc" button. Changing this value immediately updates the escape item in the touch bar. diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index b2121e12395..6aaa2bdd9b0 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -291,7 +291,7 @@ Calling `event.preventDefault` will prevent the page `keydown`/`keyup` events and the menu shortcuts. To only prevent the menu shortcuts, use -[`setIgnoreMenuShortcuts`](#contentssetignoremenushortcuts): +[`setIgnoreMenuShortcuts`](#contentssetignoremenushortcutsignore-experimental): ```javascript const {BrowserWindow} = require('electron') @@ -625,6 +625,28 @@ const options = {extraHeaders: 'pragma: no-cache\n'} webContents.loadURL('https://github.com', options) ``` +#### `contents.loadFile(filePath)` + +* `filePath` String + +Loads the given file in the window, `filePath` should be a path to +an HTML file relative to the root of your application. For instance +an app structure like this: + +```sh +| root +| - package.json +| - src +| - main.js +| - index.html +``` + +Would require code like this + +```js +win.loadFile('src/index.html') +``` + #### `contents.downloadURL(url)` * `url` String @@ -809,7 +831,8 @@ Sends a request to get current zoom factor, the `callback` will be called with Changes the zoom level to the specified level. The original size is 0 and each increment above or below represents zooming 20% larger or smaller to default -limits of 300% and 50% of original size, respectively. +limits of 300% and 50% of original size, respectively. The formula for this is +`scale := 1.2 ^ level`. #### `contents.getZoomLevel(callback)` @@ -819,14 +842,6 @@ limits of 300% and 50% of original size, respectively. Sends a request to get current zoom level, the `callback` will be called with `callback(zoomLevel)`. -#### `contents.setZoomLevelLimits(minimumLevel, maximumLevel)` - -* `minimumLevel` Number -* `maximumLevel` Number - -**Deprecated:** Call `setVisualZoomLevelLimits` instead to set the visual zoom -level limits. This method will be removed in Electron 2.0. - #### `contents.setVisualZoomLevelLimits(minimumLevel, maximumLevel)` * `minimumLevel` Number @@ -1074,6 +1089,68 @@ win.webContents.on('devtools-opened', () => { Removes the specified path from DevTools workspace. +#### `contents.setDevToolsWebContents(devToolsWebContents)` + +* `devToolsWebContents` WebContents + +Uses the `devToolsWebContents` as the target `WebContents` to show devtools. + +The `devToolsWebContents` must not have done any navigation, and it should not +be used for other purposes after the call. + +By default Electron manages the devtools by creating an internal `WebContents` +with native view, which developers have very limited control of. With the +`setDevToolsWebContents` method, developers can use any `WebContents` to show +the devtools in it, including `BrowserWindow`, `BrowserView` and `` +tag. + +Note that closing the devtools does not destroy the `devToolsWebContents`, it +is caller's responsibility to destroy `devToolsWebContents`. + +An example of showing devtools in a `` tag: + +```html + + + + + + + + + + +``` + +An example of showing devtools in a `BrowserWindow`: + +```js +const {app, BrowserWindow} = require('electron') + +let win = null +let devtools = null + +app.once('ready', () => { + win = new BrowserWindow() + devtools = new BrowserWindow() + win.loadURL('https://github.com') + win.webContents.setDevToolsWebContents(devtools.webContents) + win.webContents.openDevTools({mode: 'detach'}) +}) +``` + #### `contents.openDevTools([options])` * `options` Object (optional) @@ -1083,6 +1160,9 @@ Removes the specified path from DevTools workspace. Opens the devtools. +When `contents` is a `` tag, the `mode` would be `detach` by default, +explicitly passing an empty `mode` can force using last used dock state. + #### `contents.closeDevTools()` Closes the devtools. @@ -1288,7 +1368,7 @@ Set the size of the page. This is only supported for `` guest contents. * `options` Object * `normal` Object (optional) - Normal size of the page. This can be used in - combination with the [`disableguestresize`](web-view-tag.md#disableguestresize) + combination with the [`disableguestresize`](webview-tag.md#disableguestresize) attribute to manually resize the webview guest contents. * `width` Integer * `height` Integer diff --git a/docs/api/web-frame.md b/docs/api/web-frame.md index 7c938ac792d..cc7ab360600 100644 --- a/docs/api/web-frame.md +++ b/docs/api/web-frame.md @@ -4,6 +4,10 @@ Process: [Renderer](../glossary.md#renderer-process) +`webFrame` export of the electron module is an instance of the `WebFrame` +class representing the top frame of the current `BrowserWindow`. Sub-frames can +be retrieved by certain properties and methods (e.g. `webFrame.firstChild`). + An example of zooming current page to 200%. ```javascript @@ -14,7 +18,7 @@ webFrame.setZoomFactor(2) ## Methods -The `webFrame` module has the following methods: +The `WebFrame` class has the following instance methods: ### `webFrame.setZoomFactor(factor)` @@ -39,14 +43,6 @@ limits of 300% and 50% of original size, respectively. Returns `Number` - The current zoom level. -### `webFrame.setZoomLevelLimits(minimumLevel, maximumLevel)` - -* `minimumLevel` Number -* `maximumLevel` Number - -**Deprecated:** Call `setVisualZoomLevelLimits` instead to set the visual zoom -level limits. This method will be removed in Electron 2.0. - ### `webFrame.setVisualZoomLevelLimits(minimumLevel, maximumLevel)` * `minimumLevel` Number @@ -145,6 +141,37 @@ 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.executeJavaScriptInIsolatedWorld(worldId, scripts[, userGesture, callback])` + +* `worldId` Integer +* `scripts` [WebSource[]](structures/web-source.md) +* `userGesture` Boolean (optional) - Default is `false`. +* `callback` Function (optional) - Called after script has been executed. + * `result` Any + +Work like `executeJavaScript` but evaluates `scripts` in isolated context. + +### `webFrame.setIsolatedWorldContentSecurityPolicy(worldId, csp)` + +* `worldId` Integer +* `csp` String + +Set the content security policy of the isolated world. + +### `webFrame.setIsolatedWorldHumanReadableName(worldId, name)` + +* `worldId` Integer +* `name` String + +Set the name of the isolated world. Useful in devtools. + +### `webFrame.setIsolatedWorldSecurityOrigin(worldId, securityOrigin)` + +* `worldId` Integer +* `securityOrigin` String + +Set the security origin of the isolated world. + ### `webFrame.getResourceUsage()` Returns `Object`: @@ -191,3 +218,49 @@ 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 + +### `webFrame.getFrameForSelector(selector)` + +* `selector` String - CSS selector for a frame element. + +Returns `WebFrame` - The frame element in `webFrame's` document selected by +`selector`, `null` would be returned if `selector` does not select a frame or +if the frame is not in the current renderer process. + +### `webFrame.findFrameByName(name)` + +* `name` String + +Returns `WebFrame` - A child of `webFrame` with the supplied `name`, `null` +would be returned if there's no such frame or if the frame is not in the current +renderer process. + +## Properties + +### `webFrame.top` + +A `WebFrame` representing top frame in frame hierarchy to which `webFrame` +belongs, the property would be `null` if top frame is not in the current +renderer process. + +### `webFrame.opener` + +A `WebFrame` representing the frame which opened `webFrame`, the property would +be `null` if there's no opener or opener is not in the current renderer process. + +### `webFrame.parent` + +A `WebFrame` representing parent frame of `webFrame`, the property would be +`null` if `webFrame` is top or parent is not in the current renderer process. + +### `webFrame.firstChild` + +A `WebFrame` representing the first child frame of `webFrame`, the property +would be `null` if `webFrame` has no children or if first child is not in the +current renderer process. + +### `webFrame.nextSibling` + +A `WebFrame` representing next sibling frame, the property would be `null` if +`webFrame` is the last frame its parent or if the next sibling is not in the +current renderer process. diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md index c0ae25d8dcb..2d8e142db06 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -528,7 +528,7 @@ can be obtained by subscribing to [`found-in-page`](webview-tag.md#event-found-i ### `.stopFindInPage(action)` * `action` String - Specifies the action to take place when ending - [`.findInPage`](webview-tag.md#webviewtagfindinpage) request. + [`.findInPage`](#webviewfindinpagetext-options) request. * `clearSelection` - Clear the selection. * `keepSelection` - Translate the selection into a normal selection. * `activateSelection` - Focus and click the selection node. @@ -579,7 +579,7 @@ Send an asynchronous message to renderer process via `channel`, you can also send arbitrary arguments. The renderer process can handle the message by listening to the `channel` event with the [`ipcRenderer`](ipc-renderer.md) module. -See [webContents.send](web-contents.md#webcontentssendchannel-args) for +See [webContents.send](web-contents.md#contentssendchannel-arg1-arg2-) for examples. ### `.sendInputEvent(event)` @@ -588,7 +588,7 @@ examples. Sends an input `event` to the page. -See [webContents.sendInputEvent](web-contents.md#webcontentssendinputeventevent) +See [webContents.sendInputEvent](web-contents.md#contentssendinputeventevent) for detailed description of `event` object. ### `.setZoomFactor(factor)` @@ -752,7 +752,7 @@ Returns: * `finalUpdate` Boolean Fired when a result is available for -[`webview.findInPage`](webview-tag.md#webviewtagfindinpage) request. +[`webview.findInPage`](#webviewfindinpagetext-options) request. ```javascript const webview = document.querySelector('webview') diff --git a/docs/development/build-instructions-linux.md b/docs/development/build-instructions-linux.md index 796698499d2..442b3ded9d7 100644 --- a/docs/development/build-instructions-linux.md +++ b/docs/development/build-instructions-linux.md @@ -8,7 +8,7 @@ Follow the guidelines below for building Electron on Linux. * Python 2.7.x. Some distributions like CentOS 6.x still use Python 2.6.x so you may need to check your Python version with `python -V`. * Node.js. There are various ways to install Node. You can download - source code from [nodejs.org](http://nodejs.org) and compile it. + source code from [nodejs.org](https://nodejs.org) and compile it. Doing so permits installing Node on your own home directory as a standard user. Or try repositories such as [NodeSource](https://nodesource.com/blog/nodejs-v012-iojs-and-the-nodesource-linux-repositories). * [clang](https://clang.llvm.org/get_started.html) 3.4 or later. @@ -17,7 +17,7 @@ Follow the guidelines below for building Electron on Linux. On Ubuntu, install the following libraries: ```sh -$ sudo apt-get install build-essential clang libdbus-1-dev libgtk2.0-dev \ +$ sudo apt-get install build-essential clang libdbus-1-dev libgtk-3-dev \ libnotify-dev libgnome-keyring-dev libgconf2-dev \ libasound2-dev libcap-dev libcups2-dev libxtst-dev \ libxss1 libnss3-dev gcc-multilib g++-multilib curl \ @@ -27,7 +27,7 @@ $ sudo apt-get install build-essential clang libdbus-1-dev libgtk2.0-dev \ On RHEL / CentOS, install the following libraries: ```sh -$ sudo yum install clang dbus-devel gtk2-devel libnotify-devel \ +$ sudo yum install clang dbus-devel gtk3-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 @@ -36,7 +36,7 @@ $ sudo yum install clang dbus-devel gtk2-devel libnotify-devel \ On Fedora, install the following libraries: ```sh -$ sudo dnf install clang dbus-devel gtk2-devel libnotify-devel \ +$ sudo dnf install clang dbus-devel gtk3-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 @@ -155,12 +155,12 @@ information may help you. ### Building `libchromiumcontent` locally -To avoid using the prebuilt binaries of `libchromiumcontent`, you can build `libchromiumcontent` locally. To do so, follow these steps: +To avoid using the prebuilt binaries of `libchromiumcontent`, you can build `libchromiumcontent` locally. To do so, follow these steps: 1. Install [depot_tools](https://chromium.googlesource.com/chromium/src/+/master/docs/linux_build_instructions.md#Install) 2. Install [additional build dependencies](https://chromium.googlesource.com/chromium/src/+/master/docs/linux_build_instructions.md#Install-additional-build-dependencies) 3. Fetch the git submodules: - + ```sh $ git submodule update --init --recursive ``` @@ -179,11 +179,11 @@ $ ./script/build.py -c R ### Using system `clang` instead of downloaded `clang` binaries -By default Electron is built with prebuilt +By default Electron is built with prebuilt [`clang`](https://clang.llvm.org/get_started.html) binaries provided by the -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 +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`: @@ -208,8 +208,8 @@ $ ./script/build.py -c R ### Environment variables -Apart from `CC` and `CXX`, you can also set following environment variables to -custom the building configurations: +Apart from `CC` and `CXX`, you can also set the following environment variables to +customise the build configuration: * `CPPFLAGS` * `CPPFLAGS_host` @@ -226,4 +226,4 @@ custom the building configurations: * `LDFLAGS` The environment variables have to be set when executing the `bootstrap.py` -script, it won't work in the `build.py` script. \ No newline at end of file +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 04c96bc3f61..42dcc626fa5 100644 --- a/docs/development/build-instructions-osx.md +++ b/docs/development/build-instructions-osx.md @@ -6,7 +6,7 @@ Follow the guidelines below for building Electron on macOS. * macOS >= 10.11.6 * [Xcode](https://developer.apple.com/technologies/tools/) >= 8.2.1 -* [node.js](http://nodejs.org) (external) +* [node.js](https://nodejs.org) (external) If you are using the Python downloaded by Homebrew, you also need to install the following Python modules: diff --git a/docs/development/build-instructions-windows.md b/docs/development/build-instructions-windows.md index 66e710a89ae..8d7d0c7eaf2 100644 --- a/docs/development/build-instructions-windows.md +++ b/docs/development/build-instructions-windows.md @@ -8,7 +8,7 @@ Follow the guidelines below for building Electron on Windows. * Visual Studio 2015 Update 3 - [download VS 2015 Community Edition for free](https://www.visualstudio.com/vs/older-downloads/) * [Python 2.7](http://www.python.org/download/releases/2.7/) -* [Node.js](http://nodejs.org/download/) +* [Node.js](https://nodejs.org/download/) * [Git](http://git-scm.com) * [Debugging Tools for Windows](https://msdn.microsoft.com/en-us/library/windows/hardware/ff551063.aspx) if you plan on creating a full distribution since `symstore.exe` is used for @@ -143,7 +143,7 @@ Try reinstalling 32bit Node.js. ### Error: ENOENT, stat 'C:\Users\USERNAME\AppData\Roaming\npm' -Simply making that directory [should fix the problem](http://stackoverflow.com/a/25095327/102704): +Simply making that directory [should fix the problem](https://stackoverflow.com/a/25095327/102704): ```powershell $ mkdir ~\AppData\Roaming\npm diff --git a/docs/development/coding-style.md b/docs/development/coding-style.md index 503496ba3fd..cf11a46e450 100644 --- a/docs/development/coding-style.md +++ b/docs/development/coding-style.md @@ -5,10 +5,27 @@ 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`. +## General Code + +* End files with a newline. +* Place requires in the following order: + * Built in Node Modules (such as `path`) + * Built in Electron Modules (such as `ipc`, `app`) + * Local Modules (using relative paths) +* Place class properties in the following order: + * Class methods and properties (methods starting with a `@`) + * Instance methods and properties +* Avoid platform-dependent code: + * Use `path.join()` to concatenate filenames. + * Use `os.tmpdir()` rather than `/tmp` when you need to reference the + temporary directory. +* Using a plain `return` when returning explicitly at the end of a function. + * Not `return null`, `return undefined`, `null`, or `undefined` + ## C++ and Python For C++ and Python, we follow Chromium's [Coding -Style](http://www.chromium.org/developers/coding-style). You can use +Style](https://www.chromium.org/developers/coding-style). You can use [clang-format](clang-format.md) to format the C++ code automatically. There is also a script `script/cpplint.py` to check whether all files conform. @@ -21,9 +38,16 @@ document. The document mentions some special types, scoped types (that automatically release their memory when going out of scope), logging mechanisms etc. +## Documentation + +* Write [remark](https://github.com/remarkjs/remark) markdown style + +You can run `npm run lint-docs` to ensure that your documentation changes are +formatted correctly. + ## JavaScript -* Write [standard](http://npm.im/standard) JavaScript style. +* Write [standard](https://npm.im/standard) JavaScript style. * File names should be concatenated with `-` instead of `_`, e.g. `file-name.js` rather than `file_name.js`, because in [github/atom](https://github.com/github/atom) module names are usually in diff --git a/docs/development/debugging-instructions-macos-xcode.md b/docs/development/debugging-instructions-macos-xcode.md new file mode 100644 index 00000000000..e136ee9253c --- /dev/null +++ b/docs/development/debugging-instructions-macos-xcode.md @@ -0,0 +1,52 @@ +## Debugging with XCode + +### Build Debug Electron with Release libchromiumcontent +You can create a debug build of electron by following [build instructions for macOS](build-instructions-osx.md). +The bootstrap process will download Release version of libchromiumcontent by default, +so you will not be able to step through the chromium source. + +### Build Debug Electron with Debug libchromiumcontent +If you want to debug and step through libchromiumcontent, you will have to run the +bootsrap script with the `--build_debug_libcc` argument. + +```sh +$ cd electron +$ ./script/bootstrap.py -v --build_debug_libcc +``` +This can take a significant amount of time depending on build machine as it has to +build all of the libchromium source. + +Once, the lib is built, create a symlink to the built directory under download + +`ln -s vendor/libchromiumcontent/dist/main/shared_library vendor/download/libchromiumcontent/shared_library` + +Electron debug builds will use this shared library to link against. + +```sh +$ ./script/build.py -c D --libcc +``` +This will build debug electron with debug version of libchromiumcontent. + +### Generate xcode project for debugging sources (cannot build code from xcode) +Run the update script with the --xcode argument. +```sh +$ ./script/update.py --xcode +``` +This will generate the electron.ninjs.xcworkspace. You will have to open this workspace +to set breakpoints and inspect. + +### Debugging and breakpoints + +Launch electron app after build. +You can now open the xcode workspace created above and attach to the electron process +through the Debug > Attach To Process > Electron debug menu. [Note: If you want to debug +the renderer process, you need to attach to the Electron Helper as well.] + +You can now set breakpoints in any of the indexed files. However, you will not be able +to set breakpoints directly in the chromium source. +To set break points in the chromium source, you can choose Debug > Breakpoints > Create +Symbolic Breakpoint and set any function name as the symbol. This will set the breakpoint +for all functions with that name, from all the classes if there are more than one. +You can also do this step of setting break points prior to attaching the debugger, +however, actual breakpoints for symbolic breakpoint functions may not show up until the +debugger is attached to the app. \ No newline at end of file diff --git a/docs/development/debugging-instructions-macos.md b/docs/development/debugging-instructions-macos.md index 13eedc610a1..b6999c58976 100644 --- a/docs/development/debugging-instructions-macos.md +++ b/docs/development/debugging-instructions-macos.md @@ -5,6 +5,8 @@ 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 lldb, and the Electron source code, it is fairly easy to enable step-through debugging with breakpoints inside Electron's source code. +You can also use [XCode for debugging](debugging-instructions-macos-xcode.md) if +you prefer a graphical interface. ## Requirements diff --git a/docs/development/issues.md b/docs/development/issues.md new file mode 100644 index 00000000000..4bb4fe357d5 --- /dev/null +++ b/docs/development/issues.md @@ -0,0 +1,109 @@ +# Issues In Electron + +# Issues + +* [How to Contribute in Issues](#how-to-contribute-in-issues) +* [Asking for General Help](#asking-for-general-help) +* [Submitting a Bug Report](#submitting-a-bug-report) +* [Triaging a Bug Report](#triaging-a-bug-report) +* [Resolving a Bug Report](#resolving-a-bug-report) + +## How to Contribute in Issues + +For any issue, there are fundamentally three ways an individual can +contribute: + +1. By opening the issue for discussion: If you believe that you have found + a new bug in Electron, you should report it by creating a new issue in + the `electron/electron` issue tracker. +2. By helping to triage the issue: You can do this either by providing + assistive details (a reproducible test case that demonstrates a bug) or by + providing suggestions to address the issue. +3. By helping to resolve the issue: This can be done by demonstrating + that the issue is not a bug or is fixed; but more often, by opening + a pull request that changes the source in `electron/electron` in a + concrete and reviewable manner. + +## Asking for General Help + +Because the level of activity in the `electron/electron` repository is +so high, questions or requests for general help using Electron should +be directed at the [community slack channel](https://atomio.slack.com) +or the [forum](https://discuss.atom.io/c/electron). + +## Submitting a Bug Report + +When opening a new issue in the `electron/electron` issue tracker, users +will be presented with a template that should be filled in. + +```markdown + + +* Electron version: +* Operating system: + +### Expected behavior + + + +### Actual behavior + + + +### How to reproduce + + +``` + +If you believe that you have found a bug in Electron, please fill out this +form to the best of your ability. + +The two most important pieces of information needed to evaluate the report are +a description of the bug and a simple test case to recreate it. It easier to fix +a bug if it can be reproduced. + +See [How to create a Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve). + +## Triaging a Bug Report + +It's common for open issues to involve discussion. Some contributors may +have differing opinions, including whether the behavior is a bug or feature. +This discussion is part of the process and should be kept focused, helpful, +and professional. + +Terse responses that provide neither additional context nor supporting detail +are not helpful or professional. To many, such responses are annoying and +unfriendly. + +Contributors are encouraged to solve issues collaboratively and help one +another make progress. If encounter an issue that you feel is invalid, or +which contains incorrect information, explain *why* you feel that way with +additional supporting context, and be willing to be convinced that you may +be wrong. By doing so, we can often reach the correct outcome faster. + +## Resolving a Bug Report + +Most issues are resolved by opening a pull request. The process for opening and +reviewing a pull request is similar to that of opening and triaging issues, but +carries with it a necessary review and approval workflow that ensures that the +proposed changes meet the minimal quality and functional guidelines of the +Electron project. diff --git a/docs/development/pull-requests.md b/docs/development/pull-requests.md new file mode 100644 index 00000000000..83302575fa4 --- /dev/null +++ b/docs/development/pull-requests.md @@ -0,0 +1,235 @@ +# Pull Requests + +* [Dependencies](#dependencies) +* [Setting up your local environment](#setting-up-your-local-environment) + * [Step 1: Fork](#step-1-fork) + * [Step 2: Build](#step-2-build) + * [Step 3: Branch](#step-3-branch) +* [Making Changes](#making-changes) + * [Step 4: Code](#step-4-code) + * [Step 5: Commit](#step-5-commit) + * [Commit message guidelines](#commit-message-guidelines) + * [Step 6: Rebase](#step-6-rebase) + * [Step 7: Test](#step-7-test) + * [Step 8: Push](#step-8-push) + * [Step 9: Opening the Pull Request](#step-8-opening-the-pull-request) + * [Step 10: Discuss and Update](#step-9-discuss-and-update) + * [Approval and Request Changes Workflow](#approval-and-request-changes-workflow) + * [Step 11: Landing](#step-10-landing) + * [Continuous Integration Testing](#continuous-integration-testing) + +## Setting up your local environment + +### Step 1: Fork + +Fork the project [on GitHub](https://github.com/electron/electron) and clone your fork +locally. + +```sh +$ git clone git@github.com:username/electron.git +$ cd electron +$ git remote add upstream https://github.com/electron/electron.git +$ git fetch upstream +``` + +### Step 2: Build + +Build steps and dependencies differ slightly depending on your operating system. +See these detailed guides on building Electron locally: +* [Building on MacOS](https://electronjs.org/docs/development/build-instructions-osx) +* [Building on Linux](https://electronjs.org/docs/development/build-instructions-linux) +* [Building on Windows](https://electronjs.org/docs/development/build-instructions-windows) + +Once you've built the project locally, you're ready to start making changes! + +### Step 3: Branch + +To keep your development environment organized, create local branches to +hold your work. These should be branched directly off of the `master` branch. + +```sh +$ git checkout -b my-branch -t upstream/master +``` + +## Making Changes + +### Step 4: Code + +Most pull requests opened against the `electron/electron` repository include +changes to either the C/C++ code in the `atom/` or `brightray/` folders, +the JavaScript code in the `lib/` folder, the documentation in `docs/api/` +or tests in the `spec/` folder. + +Please be sure to run `npm run lint` from time to time on any code changes +to ensure that they follow the project's code style. + +See [coding style](https://electronjs.org/docs/development/coding-style) for +more information about best practice when modifying code in different parts of +the project. + +### Step 5: Commit + +It is recommended to keep your changes grouped logically within individual +commits. Many contributors find it easier to review changes that are split +across multiple commits. There is no limit to the number of commits in a +pull request. + +```sh +$ git add my/changed/files +$ git commit +``` + +Note that multiple commits often get squashed when they are landed. + +#### Commit message guidelines + +A good commit message should describe what changed and why. + +1. The first line should: + - contain a short description of the change (preferably 50 characters or less, + and no more than 72 characters) + - be entirely in lowercase with the exception of proper nouns, acronyms, and + the words that refer to code, like function/variable names + + Examples: + - `updated osx build documentation for new sdk` + - `fixed typos in atom_api_menu.h` + + +2. Keep the second line blank. +3. Wrap all other lines at 72 columns. + +See [this article](https://chris.beams.io/posts/git-commit/) for more examples +of how to write good git commit messages. + +### Step 6: Rebase + +Once you have committed your changes, it is a good idea to use `git rebase` +(not `git merge`) to synchronize your work with the main repository. + +```sh +$ git fetch upstream +$ git rebase upstream/master +``` + +This ensures that your working branch has the latest changes from `electron/electron` +master. + +### Step 7: Test + +Bug fixes and features should always come with tests. A +[testing guide](https://electronjs.org/docs/development/testing) has been +provided to make the process easier. Looking at other tests to see how they +should be structured can also help. + +Before submitting your changes in a pull request, always run the full +test suite. To run the tests: + +```sh +$ npm run test +``` + +Make sure the linter does not report any issues and that all tests pass. +Please do not submit patches that fail either check. + +If you are updating tests and just want to run a single spec to check it: + +```sh +$ npm run test -match=menu +``` + +The above would only run spec modules matching `menu`, which is useful for +anyone who's working on tests that would otherwise be at the very end of +the testing cycle. + +### Step 8: Push + +Once your commits are ready to go -- with passing tests and linting -- +begin the process of opening a pull request by pushing your working branch +to your fork on GitHub. + +```sh +$ git push origin my-branch +``` + +### Step 9: Opening the Pull Request + +From within GitHub, opening a new pull request will present you with a template +that should be filled out: + +```markdown + +``` + +### Step 10: Discuss and update + +You will probably get feedback or requests for changes to your pull request. +This is a big part of the submission process so don't be discouraged! Some +contributors may sign off on the pull request right away. Others may have +detailed comments or feedback. This is a necessary part of the process +in order to evaluate whether the changes are correct and necessary. + +To make changes to an existing pull request, make the changes to your local +branch, add a new commit with those changes, and push those to your fork. +GitHub will automatically update the pull request. + +```sh +$ git add my/changed/files +$ git commit +$ git push origin my-branch +``` + +There are a number of more advanced mechanisms for managing commits using +`git rebase` that can be used, but are beyond the scope of this guide. + +Feel free to post a comment in the pull request to ping reviewers if you are +awaiting an answer on something. If you encounter words or acronyms that +seem unfamiliar, refer to this +[glossary](https://sites.google.com/a/chromium.org/dev/glossary). + +#### Approval and Request Changes Workflow + +All pull requests require approval from a [Code Owner](https://github.com/orgs/electron/teams/code-owners) of the area you +modified in order to land. Whenever a maintainer reviews a pull request they +may request changes. These may be small, such as fixing a typo, or may involve +substantive changes. Such requests are intended to be helpful, but at times +may come across as abrupt or unhelpful, especially if they do not include +concrete suggestions on *how* to change them. + +Try not to be discouraged. If you feel that a review is unfair, say so or seek +the input of another project contributor. Often such comments are the result of +a reviewer having taken insufficient time to review and are not ill-intended. +Such difficulties can often be resolved with a bit of patience. That said, +reviewers should be expected to provide helpful feeback. + +### Step 11: Landing + +In order to land, a pull request needs to be reviewed and approved by +at least one Electron Code Owner and pass CI. After that, if there are no +objections from other contributors, the pull request can be merged. + +Congratulations and thanks for your contribution! + +### Continuous Integration Testing + +Every pull request is tested on the Continuous Integration (CI) system to +confirm that it works on Electron's supported platforms. + +Ideally, the pull request will pass ("be green") on all of CI's platforms. +This means that all tests pass and there are no linting errors. However, +it is not uncommon for the CI infrastructure itself to fail on specific +platforms or for so-called "flaky" tests to fail ("be red"). Each CI +failure must be manually inspected to determine the cause. + +CI starts automatically when you open a pull request, but only +[Releasers](https://github.com/orgs/electron/teams/releasers/members) +can restart a CI run. If you believe CI is giving a false negative, +ask a Releaser to restart the tests. + diff --git a/docs/development/setting-up-symbol-server.md b/docs/development/setting-up-symbol-server.md index 7f55e2ace8d..0f5030cbf51 100644 --- a/docs/development/setting-up-symbol-server.md +++ b/docs/development/setting-up-symbol-server.md @@ -5,7 +5,7 @@ about the functions contained in executables and dynamic libraries and provide you with information to get clean call stacks. A Symbol Server allows the debugger to load the correct symbols, binaries and sources automatically without forcing users to download large debugging files. The server functions like -[Microsoft's symbol server](http://support.microsoft.com/kb/311503) so the +[Microsoft's symbol server](https://support.microsoft.com/kb/311503) so the documentation there can be useful. Note that because released Electron builds are heavily optimized, debugging is @@ -38,7 +38,7 @@ or by typing the `.sympath` command. If you would like to get symbols from Microsoft's symbol server as well, you should list that first: ```powershell -SRV*c:\code\symbols\*http://msdl.microsoft.com/download/symbols;SRV*c:\code\symbols\*https://electron-symbols.githubapp.com +SRV*c:\code\symbols\*https://msdl.microsoft.com/download/symbols;SRV*c:\code\symbols\*https://electron-symbols.githubapp.com ``` ## Using the symbol server in Visual Studio diff --git a/docs/development/source-code-directory-structure.md b/docs/development/source-code-directory-structure.md index 53c0e2b37c0..b29a818f9ec 100644 --- a/docs/development/source-code-directory-structure.md +++ b/docs/development/source-code-directory-structure.md @@ -4,12 +4,12 @@ The source code of Electron is separated into a few parts, mostly following Chromium on the separation conventions. You may need to become familiar with [Chromium's multi-process -architecture](http://dev.chromium.org/developers/design-documents/multi-process-architecture) +architecture](https://dev.chromium.org/developers/design-documents/multi-process-architecture) to understand the source code better. ## Structure of Source Code -```sh +```diff Electron โ”œโ”€โ”€ atom/ - C++ source code. | โ”œโ”€โ”€ app/ - System entry code. @@ -30,6 +30,7 @@ Electron | loop into Chromium's message loop. | โ””โ”€โ”€ api/ - The implementation of common APIs, and foundations of | Electron's built-in modules. +โ”œโ”€โ”€ brightray/ - Thin shim over libcc that makes it easier to use. โ”œโ”€โ”€ chromium_src/ - Source code copied from Chromium. See below. โ”œโ”€โ”€ default_app/ - The default page to show when Electron is started without | providing an app. @@ -49,15 +50,15 @@ Electron ## `/chromium_src` -The files in `/chromium_src` tend to be pieces of Chromium that aren't part of -the content layer. For example to implement Pepper API, we need some wiring -similar to what official Chrome does. We could have built the relevant -sources as a part of [libcc](../glossary.md#libchromiumcontent) but most -often we don't require all the features (some tend to be proprietary, -analytics stuff) so we just took parts of the code. These could have easily -been patches in libcc, but at the time when these were written the goal of -libcc was to maintain very minimal patches and chromium_src changes tend to be -big ones. Also, note that these patches can never be upstreamed unlike other +The files in `/chromium_src` tend to be pieces of Chromium that aren't part of +the content layer. For example to implement Pepper API, we need some wiring +similar to what official Chrome does. We could have built the relevant +sources as a part of [libcc](../glossary.md#libchromiumcontent) but most +often we don't require all the features (some tend to be proprietary, +analytics stuff) so we just took parts of the code. These could have easily +been patches in libcc, but at the time when these were written the goal of +libcc was to maintain very minimal patches and chromium_src changes tend to be +big ones. Also, note that these patches can never be upstreamed unlike other libcc patches we maintain now. ## Structure of Other Directories diff --git a/docs/development/testing.md b/docs/development/testing.md new file mode 100644 index 00000000000..b67734b5c53 --- /dev/null +++ b/docs/development/testing.md @@ -0,0 +1,44 @@ +# Testing + +We aim to keep the code coverage of Electron high. We ask that all pull +request not only pass all existing tests, but ideally also add new tests +to cover changed code and new scenarios. Ensuring that we capture as +many code paths and use cases of Electron as possible ensures that we +all ship apps with fewer bugs. + +This repository comes with linting rules for both JavaScript and C++ โ€“ +as well as unit and integration tests. To learn more about Electron's +coding style, please see the [coding-style(coding-style.md) document. + +## Linting +To ensure that your JavaScript is in compliance with the Electron coding +style, run `npm run lint-js`, which will run `standard` against both +Electron itself as well as the unit tests. If you are using an editor +with a plugin/addon system, you might want to use one of the many +[StandardJS addons][standard-addons] to be informed of coding style +violations before you ever commit them. + +To run `standard` with parameters, run `npm run lint-js --` followed by +arguments you want passed to `standard`. + +To ensure that your C++ is in compliance with the Electron coding style, +run `npm run lint-cpp`, which runs a `cpplint` script. We recommend that +you use `clang-format` and prepared [a short tutorial](clang-format.md). + +There is not a lot of Python in this repository, but it too is governed +by coding style rules. `npm run lint-py` will check all Python, using +`pylint` to do so. + +## Unit Tests + +To run all unit tests, run `npm run test`. The unit tests are an Electron +app (surprise!) that can be found in the `spec` folder. Note that it has +its own `package.json` and that its dependencies are therefore not defined +in the top-level `package.json`. + +To run only a selected number of tests, run `npm run test -match=NAME`, +replacing the `NAME` with the file name of the test suite you would like +to run. As an example: If you want to run only IPC suites, you would run +`npm run test -match=ipc`. + +[standard-addons]: https://standardjs.com/#are-there-text-editor-plugins diff --git a/docs/development/upgrading-chromium.md b/docs/development/upgrading-chromium.md index 3f450a85d86..61c97bbc5c9 100644 --- a/docs/development/upgrading-chromium.md +++ b/docs/development/upgrading-chromium.md @@ -12,10 +12,11 @@ This is an overview of the steps needed to upgrade Chromium in Electron. ## Upgrade `libcc` to a new Chromium version 1. Get the code and initialize the project: - - ```sh + ```sh $ git clone git@github.com:electron/libchromiumcontent.git $ cd libchromiumcontent - $ ./script/bootstrap -v``` + $ ./script/bootstrap -v + ``` 2. Update the Chromium snapshot - Choose a version number from [OmahaProxy](https://omahaproxy.appspot.com/) and update the `VERSION` file with it @@ -36,7 +37,7 @@ This is an overview of the steps needed to upgrade Chromium in Electron. - If some patches are no longer compatible with the Chromium code, fix compilation errors. 6. When the build succeeds, create a `dist` for Electron - - `$ ./script/create-dist --no_zip` + - `$ ./script/create-dist --no_zip` - It will create a `dist/main` folder in the libcc repo's root. You will need this to build Electron. 7. (Optional) Update script contents if there are errors resulting from files @@ -47,18 +48,18 @@ This is an overview of the steps needed to upgrade Chromium in Electron. ## Update Electron's code 1. Get the code: - - ```sh - $ git clone git@github.com:electron/electron.git - $ cd electron - ``` + ```sh + $ git clone git@github.com:electron/electron.git + $ cd electron + ``` 2. If you have libcc built on your machine in its own repo, tell Electron to use it: - - ```sh - $ ./script/bootstrap.py -v \ - --libcc_source_path /src \ - --libcc_shared_library_path /shared_library \ - --libcc_static_library_path /static_library - ``` + ```sh + $ ./script/bootstrap.py -v \ + --libcc_source_path /src \ + --libcc_shared_library_path /shared_library \ + --libcc_static_library_path /static_library + ``` 3. If you haven't yet built libcc but it's already supposed to be upgraded to a new Chromium, bootstrap Electron as usual `$ ./script/bootstrap.py -v` @@ -106,7 +107,7 @@ Follow all the steps above to fix Electron code on all supported platforms. If there are any compilation errors related to the Crashpad, it probably means you need to update the fork to a newer revision. See -[Upgrading Crashpad](https://github.com/electron/electron/tree/master/docs/development/upgrading-crashpad.md) +[Upgrading Crashpad](upgrading-crashpad.md) for instructions on how to do that. @@ -115,7 +116,7 @@ for instructions on how to do that. Upgrade `vendor/node` to the Node release that corresponds to the v8 version used in the new Chromium release. See the v8 versions in Node on -See [Upgrading Node](https://github.com/electron/electron/tree/master/docs/development/upgrading-node.md) +See [Upgrading Node](upgrading-node.md) for instructions on this. ## Verify ffmpeg support diff --git a/docs/development/upgrading-crashpad.md b/docs/development/upgrading-crashpad.md index 0eb31a8f596..1fd65d5f22a 100644 --- a/docs/development/upgrading-crashpad.md +++ b/docs/development/upgrading-crashpad.md @@ -2,7 +2,7 @@ 1. Get the version of crashpad that we're going to use. - `libcc/src/third_party/crashpad/README.chromium` will have a line `Revision:` with a checksum - - We need to check out the correponding branch. + - We need to check out the corresponding branch. - Clone Google's crashpad (https://chromium.googlesource.com/crashpad/crashpad) - `git clone https://chromium.googlesource.com/crashpad/crashpad` - Check out the branch with the revision checksum: @@ -16,10 +16,10 @@ 2. Make a checklist of the Electron patches that need to be applied with `git log --oneline` - - Or view http://github.com/electron/crashpad/commits/previous-branch-name + - Or view https://github.com/electron/crashpad/commits/previous-branch-name 3. For each patch: - - In `electron-crashpad-vA.B.C.D`, cherry-pick the patch's checksum + - In `electron-crashpad-vA.B.C.D`, cherry-pick the patch's checksum - `git cherry-pick ` - Resolve any conflicts - Make sure it builds then add, commit, and push work to electron's crashpad fork @@ -37,6 +37,3 @@ 6. Push changes to submodule reference - (From electron root) `git add vendor/crashpad` - `git push origin upgrade-to-chromium-62` - - - diff --git a/docs/development/upgrading-node.md b/docs/development/upgrading-node.md index 108e36a5245..821a89391df 100644 --- a/docs/development/upgrading-node.md +++ b/docs/development/upgrading-node.md @@ -3,8 +3,8 @@ ## Discussion One upgrade issue is building all of Electron with a single copy -of V8 to ensure compatability. This is important because -upstream Node and [libchromiumcontent](upgrading-chrome.md) +of V8 to ensure compatibility. This is important because +upstream Node and [libchromiumcontent](upgrading-chromium.md) both use their own versions of V8. Upgrading Node is much easier than upgrading libchromiumcontent, @@ -38,9 +38,10 @@ So in short, the primary steps are: ## Updating Electron's Node [fork](https://github.com/electron/node) -1. Create a branch in https://github.com/electron/node: `electron-node-vX.X.X` +1. Ensure that `master` on `electron/node` has updated release tags from `nodejs/node` +2. Create a branch in https://github.com/electron/node: `electron-node-vX.X.X` where the base that you're branching from is the tag for the desired update - `vX.X.X` Must use a version of node compatible with our current version of chromium -2. Re-apply our commits from the previous version of node we were using (`vY.Y.Y`) to `v.X.X.X` +3. Re-apply our commits from the previous version of node we were using (`vY.Y.Y`) to `v.X.X.X` - Check release tag and select the range of commits we need to re-apply - Cherry-pick commit range: 1. Checkout both `vY.Y.Y` & `v.X.X.X` @@ -82,7 +83,7 @@ We need to generate a patch file from each patch applied to V8. Manually edit the `.patch` file to match upstream V8's directory: - If a diff section has no instances of `deps/V8`, remove it altogether. - We donโ€™t want those patches because weโ€™re only patching V8. - - Replace instances of `a/deps/v8`/filename.ext` with `a/filename.ext` + - Replace instances of `a/deps/v8/filename.ext` with `a/filename.ext` - This is needed because upstream Node keeps its V8 files in a subdirectory - Ensure that local status is clean: `git status` to make sure there are no unstaged changes. - Confirm that the patch applies cleanly with @@ -96,7 +97,7 @@ We need to generate a patch file from each patch applied to V8. - `mv test.patch patches/v8/xxx-patch_name.patch` - Add the patched code to the index _without_ committing: - `cd src/v8 && git add . && cd ../..` - - We don't want to commit the changes (they're kept in the patchfiles) + - We don't want to commit the changes (they're kept in the patchfiles) but need them locally so that they don't show up in subsequent diffs while we iterate through more patches - Add the patch file to the index: @@ -105,20 +106,20 @@ We need to generate a patch file from each patch applied to V8. - `git commit patches/v8/` 8. Update `patches/v8/README.md` with references to all new patches that have been added so that the next person will know which need to be removed. 9. Update Electron's submodule references: - - ```sh - cd electron/vendor/node - electron/vendor/node$ git fetch - electron/vendor/node$ git checkout electron-node-vA.B.C - electron/vendor/node$ cd ../libchromiumcontent - electron/vendor/libchromiumcontent$ git fetch - electron/vendor/libchromiumcontent$ git checkout upgrade-to-chromium-X - electron/vendor/libchromiumcontent$ cd ../.. - electron$ git add vendor - electron$ git commit -m "update submodule referefences for node and libc" - electron$ git pso upgrade-to-chromium-62 - electron$ script/bootstrap.py -d - electron$ script/build.py -c -D - ``` + ```sh + $ cd electron/vendor/node + electron/vendor/node$ git fetch + electron/vendor/node$ git checkout electron-node-vA.B.C + electron/vendor/node$ cd ../libchromiumcontent + electron/vendor/libchromiumcontent$ git fetch + electron/vendor/libchromiumcontent$ git checkout upgrade-to-chromium-X + electron/vendor/libchromiumcontent$ cd ../.. + electron$ git add vendor + electron$ git commit -m "update submodule referefences for node and libc" + electron$ git pso upgrade-to-chromium-62 + electron$ script/bootstrap.py -d + electron$ script/build.py -c -D + ``` ## Notes @@ -143,16 +144,3 @@ We need to generate a patch file from each patch applied to V8. - Building node: - Thereโ€™s a chance we need to change our build configuration to match the build flags that node wants in `node/common.gypi` - - - - - - - - - - - - - diff --git a/docs/images/versioning-sketch-0.png b/docs/images/versioning-sketch-0.png new file mode 100644 index 00000000000..2150b0c1c32 Binary files /dev/null and b/docs/images/versioning-sketch-0.png differ diff --git a/docs/images/versioning-sketch-1.png b/docs/images/versioning-sketch-1.png new file mode 100644 index 00000000000..4ba26954087 Binary files /dev/null and b/docs/images/versioning-sketch-1.png differ diff --git a/docs/images/versioning-sketch-2.png b/docs/images/versioning-sketch-2.png new file mode 100755 index 00000000000..81e71bf7684 Binary files /dev/null and b/docs/images/versioning-sketch-2.png differ diff --git a/docs/images/versioning-sketch-3.png b/docs/images/versioning-sketch-3.png new file mode 100644 index 00000000000..6c2ebc4b2cf Binary files /dev/null and b/docs/images/versioning-sketch-3.png differ diff --git a/docs/images/versioning-sketch-4.png b/docs/images/versioning-sketch-4.png new file mode 100644 index 00000000000..16236e95510 Binary files /dev/null and b/docs/images/versioning-sketch-4.png differ diff --git a/docs/images/versioning-sketch-5.png b/docs/images/versioning-sketch-5.png new file mode 100644 index 00000000000..9af531c750b Binary files /dev/null and b/docs/images/versioning-sketch-5.png differ diff --git a/docs/images/versioning-sketch-6.png b/docs/images/versioning-sketch-6.png new file mode 100644 index 00000000000..4ede4fd5ab8 Binary files /dev/null and b/docs/images/versioning-sketch-6.png differ diff --git a/docs/images/versioning-sketch-7.png b/docs/images/versioning-sketch-7.png new file mode 100644 index 00000000000..47c49291d95 Binary files /dev/null and b/docs/images/versioning-sketch-7.png differ diff --git a/docs/styleguide.md b/docs/styleguide.md index 9d6148ce241..caeaa09736e 100644 --- a/docs/styleguide.md +++ b/docs/styleguide.md @@ -230,4 +230,4 @@ a module or a class. ## Documentation Translations -See [electron/electron-i18n](https://github.com/electron/electron-i18n#readme) +See [electron/i18n](https://github.com/electron/i18n#readme) diff --git a/docs/tutorial/about.md b/docs/tutorial/about.md index 01d4b0750c4..2dd74ffaaad 100644 --- a/docs/tutorial/about.md +++ b/docs/tutorial/about.md @@ -27,7 +27,11 @@ In Electron, Node.js and Chromium share a single V8 instanceโ€”usually the versi ### Versioning -Due to the hard dependency on Node.js and Chromium, Electron is in a tricky versioning position and [does not follow `semver`](http://semver.org). You should therefore always reference a specific version of Electron. [Read more about Electron's versioning](https://electronjs.org/docs/tutorial/electron-versioning) or see the [versions currently in use](https://electronjs.org/#electron-versions). +As of version 2.0 Electron [follows `semver`](https://semver.org). +For most applications, and using any recent version of npm, +running `$ npm install electron` will do the right thing. + +The version update process is detailed explicitly in our [Versioning Doc](electron-versioning.md). ### LTS @@ -50,8 +54,8 @@ Below are milestones in Electron's history. | :calendar: | :tada: | | --- | --- | | **April 2013**| [Atom Shell is started](https://github.com/electron/electron/commit/6ef8875b1e93787fa9759f602e7880f28e8e6b45).| -| **May 2014** | [Atom Shell is open sourced](http://blog.atom.io/2014/05/06/atom-is-now-open-source.html). | +| **May 2014** | [Atom Shell is open sourced](https://blog.atom.io/2014/05/06/atom-is-now-open-source.html). | | **April 2015** | [Atom Shell is re-named Electron](https://github.com/electron/electron/pull/1389). | | **May 2016** | [Electron releases `v1.0.0`](https://electronjs.org/blog/electron-1-0).| -| **May 2016** | [Electron apps compatible with Mac App Store](https://electronjs.org/docs/tutorial/mac-app-store-submission-guide).| -| **August 2016** | [Windows Store support for Electron apps](https://electronjs.org/docs/tutorial/windows-store-guide).| +| **May 2016** | [Electron apps compatible with Mac App Store](mac-app-store-submission-guide.md).| +| **August 2016** | [Windows Store support for Electron apps](windows-store-guide.md).| diff --git a/docs/tutorial/accessibility.md b/docs/tutorial/accessibility.md index b41ba713abc..64510fdedb8 100644 --- a/docs/tutorial/accessibility.md +++ b/docs/tutorial/accessibility.md @@ -1,16 +1,24 @@ # Accessibility -Making accessible applications is important and we're happy to introduce new functionality to [Devtron](https://electron.atom.io/devtron) and [Spectron](https://electron.atom.io/spectron) that gives developers the opportunity to make their apps better for everyone. +Making accessible applications is important and we're happy to introduce new +functionality to [Devtron][devtron] and [Spectron][spectron] that gives +developers the opportunity to make their apps better for everyone. --- -Accessibility concerns in Electron applications are similar to those of websites because they're both ultimately HTML. With Electron apps, however, you can't use the online resources for accessibility audits because your app doesn't have a URL to point the auditor to. +Accessibility concerns in Electron applications are similar to those of +websites because they're both ultimately HTML. With Electron apps, however, +you can't use the online resources for accessibility audits because your app +doesn't have a URL to point the auditor to. -These new features bring those auditing tools to your Electron app. You can choose to add audits to your tests with Spectron or use them within DevTools with Devtron. Read on for a summary of the tools or checkout our [accessibility documentation](https://electronjs.org/docs/tutorial/accessibility) for more information. +These new features bring those auditing tools to your Electron app. You can +choose to add audits to your tests with Spectron or use them within DevTools +with Devtron. Read on for a summary of the tools. ## Spectron -In the testing framework Spectron, you can now audit each window and `` tag in your application. For example: +In the testing framework Spectron, you can now audit each window and `` +tag in your application. For example: ```javascript app.client.auditAccessibility().then(function (audit) { @@ -20,31 +28,43 @@ app.client.auditAccessibility().then(function (audit) { }) ``` -You can read more about this feature in [Spectron's documentation](https://github.com/electron/spectron#accessibility-testing). +You can read more about this feature in [Spectron's documentation][spectron-a11y]. ## Devtron -In Devtron, there is a new accessibility tab which will allow you to audit a page in your app, sort and filter the results. +In Devtron, there is a new accessibility tab which will allow you to audit a +page in your app, sort and filter the results. -![devtron screenshot](https://cloud.githubusercontent.com/assets/1305617/17156618/9f9bcd72-533f-11e6-880d-389115f40a2a.png) +![devtron screenshot][devtron-screenshot] -Both of these tools are using the [Accessibility Developer Tools](https://github.com/GoogleChrome/accessibility-developer-tools) library built by Google for Chrome. You can learn more about the accessibility audit rules this library uses on that [repository's wiki](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules). +Both of these tools are using the [Accessibility Developer Tools][a11y-devtools] +library built by Google for Chrome. You can learn more about the accessibility +audit rules this library uses on that [repository's wiki][a11y-devtools-wiki]. -If you know of other great accessibility tools for Electron, add them to the [accessibility documentation](https://electronjs.org/docs/tutorial/accessibility) with a pull request. +If you know of other great accessibility tools for Electron, add them to the +accessibility documentation with a pull request. ## Enabling Accessibility -Electron applications keep accessibility disabled by default for performance reasons but there are multiple ways to enable it. +Electron applications keep accessibility disabled by default for performance +reasons but there are multiple ways to enable it. ### Inside Application -By using [`app.setAccessibilitySupportEnabled(enabled)`](https://electron.atom.io/docs/api/app.md#appsetaccessibilitysupportenabledenabled-macos-windows), you can expose accessibility switch to users in the application preferences. User's system assistive utilities have priority over this setting and will override it. +By using [`app.setAccessibilitySupportEnabled(enabled)`][setAccessibilitySupportEnabled], +you can expose accessibility switch to users in the application preferences. +User's system assistive utilities have priority over this setting and will +override it. ### Assistive Technology -Electron application will enable accessibility automatically when it detects assistive technology (Windows) or VoiceOver (macOS). See Chrome's [accessibility documentation](https://www.chromium.org/developers/design-documents/accessibility#TOC-How-Chrome-detects-the-presence-of-Assistive-Technology) for more details. +Electron application will enable accessibility automatically when it detects +assistive technology (Windows) or VoiceOver (macOS). See Chrome's +[accessibility documentation][a11y-docs] for more details. -On macOS, third-party assistive technology can switch accessibility inside Electron applications by setting the attribute `AXManualAccessibility` programmatically: +On macOS, third-party assistive technology can switch accessibility inside +Electron applications by setting the attribute `AXManualAccessibility` +programmatically: ```objc CFStringRef kAXManualAccessibility = CFSTR("AXManualAccessibility"); @@ -54,9 +74,18 @@ CFStringRef kAXManualAccessibility = CFSTR("AXManualAccessibility"); AXUIElementRef appRef = AXUIElementCreateApplication(app.processIdentifier); if (appRef == nil) return; - + CFBooleanRef value = enable ? kCFBooleanTrue : kCFBooleanFalse; AXUIElementSetAttributeValue(appRef, kAXManualAccessibility, value); CFRelease(appRef); } ``` + +[devtron]: https://electronjs.org/devtron +[devtron-screenshot]: https://cloud.githubusercontent.com/assets/1305617/17156618/9f9bcd72-533f-11e6-880d-389115f40a2a.png +[spectron]: https://electronjs.org/spectron +[spectron-a11y]: https://github.com/electron/spectron#accessibility-testing +[a11y-docs]: https://www.chromium.org/developers/design-documents/accessibility#TOC-How-Chrome-detects-the-presence-of-Assistive-Technology +[a11y-devtools]: https://github.com/GoogleChrome/accessibility-developer-tools +[a11y-devtools-wiki]: https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules +[setAccessibilitySupportEnabled]: ../api/app.md#appsetaccessibilitysupportenabledenabled-macos-windows diff --git a/docs/tutorial/application-architecture.md b/docs/tutorial/application-architecture.md new file mode 100644 index 00000000000..12d11be009a --- /dev/null +++ b/docs/tutorial/application-architecture.md @@ -0,0 +1,142 @@ +# Electron Application Architecture + +Before we can dive into Electron's APIs, we need to discuss the two process +types available in Electron. They are fundamentally different and important to +understand. + +## Main and Renderer Processes + +In Electron, the process that runs `package.json`'s `main` script is called +__the main process__. The script that runs in the main process can display a +GUI by creating web pages. An Electron app always has one main process, but +never more. + +Since Electron uses Chromium for displaying web pages, Chromium's +multi-process architecture is also used. Each web page in Electron runs in +its own process, which is called __the renderer process__. + +In normal browsers, web pages usually run in a sandboxed environment and are not +allowed access to native resources. Electron users, however, have the power to +use Node.js APIs in web pages allowing lower level operating system +interactions. + +### Differences Between Main Process and Renderer Process + +The main process creates web pages by creating `BrowserWindow` instances. Each +`BrowserWindow` instance runs the web page in its own renderer process. When a +`BrowserWindow` instance is destroyed, the corresponding renderer process +is also terminated. + +The main process manages all web pages and their corresponding renderer +processes. Each renderer process is isolated and only cares about the web page +running in it. + +In web pages, calling native GUI related APIs is not allowed because managing +native GUI resources in web pages is very dangerous and it is easy to leak +resources. If you want to perform GUI operations in a web page, the renderer +process of the web page must communicate with the main process to request that +the main process perform those operations. + +> #### Aside: Communication Between Processes +> In Electron, we have several ways to communicate between the main process +and renderer processes. Like [`ipcRenderer`](../api/ipc-renderer.md) and +[`ipcMain`](../api/ipc-main.md) modules for sending messages, and the +[remote](../api/remote.md) module for RPC style communication. There is also +an FAQ entry on [how to share data between web pages][share-data]. + +## Using Electron APIs + +Electron offers a number of APIs that support the development of a desktop +application in both the main process and the renderer process. In both +processes, you'd access Electron's APIs by requiring its included module: + +```javascript +const electron = require('electron') +``` + +All Electron APIs are assigned a process type. Many of them can only be +used from the main process, some of them only from a renderer process, +some from both. The documentation for the individual API will clearly +state which process they can be used from. + +A window in Electron is for instance created using the `BrowserWindow` +class. It is only available in the main process. + +```javascript +// This will work in the main process, but be `undefined` in a +// renderer process: +const { BrowserWindow } = require('electron') + +const win = new BrowserWindow() +``` + +Since communication between the processes is possible, a renderer process +can call upon the main process to perform tasks. Electron comes with a +module called `remote` that exposes APIs usually only available on the +main process. In order to create a `BrowserWindow` from a renderer process, +we'd use the remote as a middle-man: + +```javascript +// This will work in a renderer process, but be `undefined` in the +// main process: +const { remote } = require('electron') +const { BrowserWindow } = remote + +const win = new BrowserWindow() +``` + +## Using Node.js APIs + +Electron exposes full access to Node.js both in the main and the renderer +process. This has two important implications: + +1) All APIs available in Node.js are available in Electron. Calling the +following code from an Electron app works: + +```javascript +const fs = require('fs') + +const root = fs.readdirSync('/') + +// This will print all files at the root-level of the disk, +// either '/' or 'C:\'. +console.log(root) +``` + +As you might already be able to guess, this has important security implications +if you ever attempt to load remote content. You can find more information and +guidance on loading remote content in our [security documentation][security]. + +2) You can use Node.js modules in your application. Pick your favorite npm +module. npm offers currently the world's biggest repository of open-source +code โ€“ย the ability to use well-maintained and tested code that used to be +reserved for server applications is one of the key features of Electron. + +As an example, to use the official AWS SDK in your application, you'd first +install it as a dependency: + +```sh +npm install --save aws-sdk +``` + +Then, in your Electron app, simply require and use the module as if you were +building a Node.js application: + +```javascript +// A ready-to-use S3 Client +const S3 = require('aws-sdk/clients/s3') +``` + +There is one important caveat: Native Node.js modules (that is, modules that +require compilation of native code before they can be used) will need to be +compiled to be used with Electron. + +The vast majority of Node.js modules are _not_ native. Only 400 out of the +~650.000 modules are native. However, if you do need native modules, please +consult [this guide on how to recompile them for Electron][native-node] (it's +easy). + +[node-docs]: https://nodejs.org/en/docs/ +[security]: ./security.md +[native-node]: ./using-native-node-modules.md +[share-data]: ../faq.md#how-to-share-data-between-web-pages diff --git a/docs/tutorial/application-debugging.md b/docs/tutorial/application-debugging.md new file mode 100644 index 00000000000..25eefe0e5fe --- /dev/null +++ b/docs/tutorial/application-debugging.md @@ -0,0 +1,38 @@ +# Application Debugging + +Whenever your Electron application is not behaving the way you wanted it to, +an array of debugging tools might help you find coding errors, performance +bottlenecks, or optimization opportunities. + +## Renderer Process + +The most comprehensive tool to debug individual renderer processes is the +Chromium Developer Toolset. It is available for all renderer processes, +including instances of `BrowserWindow`, `BrowserView`, and `WebView`. You +can open them programmatically by calling the `openDevTools()` API on the +`webContents` of the instance: + +```javascript +const { BrowserWindow } = require('electron') + +let win = new BrowserWindow() +win.webContents.openDevTools() +``` + +Google offers [excellent documentation for their developer tools][devtools]. +We recommend that you make yourself familiar with them - they are usually one +of the most powerful utilities in any Electron Developer's tool belt. + +## Main Process + +Debugging the main process is a bit trickier, since you cannot simply open +developer tools for them. The Chromium Developer Tools can [be used +to debug Electron's main process][node-inspect] thanks to a closer collaboration +between Google / Chrome and Node.js, but you might encounter oddities like +`require` not being present in the console. + +For more information, see the [Debugging the Main Process documentation][main-debug]. + +[node-inspect]: https://nodejs.org/en/docs/inspector/ +[devtools]: https://developer.chrome.com/devtools +[main-debug]: ./debugging-main-process.md diff --git a/docs/tutorial/application-packaging.md b/docs/tutorial/application-packaging.md index e01ecc3d9a8..79b95352226 100644 --- a/docs/tutorial/application-packaging.md +++ b/docs/tutorial/application-packaging.md @@ -5,7 +5,12 @@ path names on Windows, slightly speed up `require` and conceal your source code from cursory inspection, you can choose to package your app into an [asar][asar] archive with little changes to your source code. -## Generating `asar` Archive +Most users will get this feature for free, since it's supported out of the box +by [`electron-packager`][electron-packager], [`electron-forge`][electron-forge], +and [`electron-builder`][electron-builder]. If you are not using any of these +tools, read on. + +## Generating `asar` Archives An [asar][asar] archive is a simple tar-like format that concatenates files into a single file. Electron can read arbitrary files from it without unpacking @@ -71,8 +76,9 @@ 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') -let win = new BrowserWindow({width: 800, height: 600}) +const { BrowserWindow } = require('electron') +const win = new BrowserWindow() + win.loadURL('file:///path/to/example.asar/static/index.html') ``` @@ -164,22 +170,26 @@ and `command`s are executed under shell. There is no reliable way to determine whether a command uses a file in asar archive, and even if we do, we can not be sure whether we can replace the path in command without side effects. -## Adding Unpacked Files in `asar` Archive +## Adding Unpacked Files to `asar` Archives -As stated above, some Node APIs will unpack the file to filesystem when -calling, apart from the performance issues, it could also lead to false alerts -of virus scanners. +As stated above, some Node APIs will unpack the file to the filesystem when +called. Apart from the performance issues, various anti-virus scanners might +be triggered by this behavior. -To work around this, you can unpack some files creating archives by using the -`--unpack` option, an example of excluding shared libraries of native modules -is: +As a workaround, you can leave various files unpacked using the `--unpack` option. +In the following example, shared libraries of native Node.js modules will not be +packed: ```sh $ asar pack app app.asar --unpack *.node ``` -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. +After running the command, you will notice that a folder named `app.asar.unpacked` +was created together with the `app.asar` file. It contains the unpacked files +and should be shipped together with the `app.asar` archive. [asar]: https://github.com/electron/asar +[electron-packager]: https://github.com/electron-userland/electron-packager +[electron-forge]: https://github.com/electron-userland/electron-forge +[electron-builder]: https://github.com/electron-userland/electron-builder + diff --git a/docs/tutorial/boilerplates-and-clis.md b/docs/tutorial/boilerplates-and-clis.md new file mode 100644 index 00000000000..3a24c36607e --- /dev/null +++ b/docs/tutorial/boilerplates-and-clis.md @@ -0,0 +1,66 @@ +# Boilerplates and CLIs + +Electron development is un-opinionated - there is no "one true way" to develop, +build, package, or release an Electron application. Additional features for +Electron, both for build- and run-time, can usually be found on +[npm](https://www.npmjs.com/search?q=electron) in individual packages, allowing developers to build both +the app and build pipeline they need. + +That level of modularity and extendability ensures that all developers working +with Electron, both big and small in team-size, are never restricted in what +they can or cannot do at any time during their development lifecycle. However, +for many developers, one of the community-driven boilerplates or command line +tools might make it dramatically easier to compile, package, and release an +app. + +## Boilerplate vs CLI + +A boilerplate is simply a starting point - a canvas, so to speak - from which +you build your application. They usually come in the form of a repository you +can clone and customize to your heart's content. + +A command line tool on the other hand continues to support you throughout the +development and release. They are more helpful and supportive but enforce +guidelines on how your code should be structured and built. *Especially for +beginners, using a command line tool is likely to be helpful*. + +## electron-forge + +A "complete tool for building modern Electron applications". Electron Forge +unifies the existing (and well maintained) build tools for Electron development +into a simple, easy to use package so that anyone can jump right in to Electron +development. + +Forge comes with [ready-to-use templates](https://electronforge.io/templates) for popular +frameworks like React, Vue, or Angular. It uses the same core modules used by the +greater Electron community (like [`electron-packager`](https://github.com/electron-userland/electron-packager)) โ€“ย  +changes made by Electron maintainers (like Slack) benefit Forge's users, too. + +You can find more information and documentation on [electronforge.io](https://electronforge.io/). + +## electron-builder + +A "complete solution to package and build a ready-for-distribution Electron app" +that focuses on an integrated experience. [`electron-builder`](https://github.com/electron-userland/electron-builder) adds one +single dependency focused on simplicity and manages all further requirements +internally. + +`electron-builder` replaces features and modules used by the Electron +maintainers (such as the auto-updater) with custom ones. They are generally +tighter integrated but will have less in common with popular Electron apps +like Atom, Visual Studio Code, or Slack. + +You can find more information and documentation in [the repository](https://github.com/electron-userland/electron-builder). + +## electron-react-boilerplate + +If you don't want any tools but simply a solid boilerplate to build from, +CT Lin's [`electron-react-boilerplate`](https://github.com/chentsulin/electron-react-boilerplate) might be worth +a look. It's quite popular in the community and uses `electron-builder` +internally. + +## Other Tools and Boilerplates + +The ["Awesome Electron" list](https://github.com/sindresorhus/awesome-electron#boilerplates) contains more tools and boilerplates +to choose from. If you find the length of the list intimidating, don't +forget that adding tools as you go along is a valid approach, too. diff --git a/docs/tutorial/desktop-environment-integration.md b/docs/tutorial/desktop-environment-integration.md index 96e56362b2d..1eb883b4630 100644 --- a/docs/tutorial/desktop-environment-integration.md +++ b/docs/tutorial/desktop-environment-integration.md @@ -10,344 +10,26 @@ environments with Electron APIs. ## Notifications -See [Notifications](notifications.md) +See the [Notifications documentation](notifications.md). -## Recent documents (Windows & macOS) +## Recent Documents -Windows and macOS provide easy access to a list of recent documents opened by -the application via JumpList or dock menu, respectively. +See [Recent Documents documentation](recent-documents.md). -__JumpList:__ +## Progress Bar -![JumpList Recent Files](https://cloud.githubusercontent.com/assets/2289/23446924/11a27b98-fdfc-11e6-8485-cc3b1e86b80a.png) +See the [Progress Bar documentation](progress-bar.md). -__Application dock menu:__ +## Unity Launcher - +See the [Unity Launcher documentation][unity-launcher]. -To add a file to recent documents, you can use the -[app.addRecentDocument][addrecentdocument] API: +## Represented File for macOS Window -```javascript -const {app} = require('electron') -app.addRecentDocument('/Users/USERNAME/Desktop/work.type') -``` - -And you can use [app.clearRecentDocuments][clearrecentdocuments] API to empty -the recent documents list: - -```javascript -const {app} = require('electron') -app.clearRecentDocuments() -``` - -### Windows Notes - -In order to be able to use this feature on Windows, your application has to be -registered as a handler of the file type of the document, otherwise the file -won't appear in JumpList even after you have added it. You can find everything -on registering your application in [Application Registration][app-registration]. - -When a user clicks a file from the JumpList, a new instance of your application -will be started with the path of the file added as a command line argument. - -### macOS Notes - -When a file is requested from the recent documents menu, the `open-file` event -of `app` module will be emitted for it. - -## Custom Dock Menu (macOS) - -macOS enables developers to specify a custom menu for the dock, which usually -contains some shortcuts for commonly used features of your application: - -__Dock menu of Terminal.app:__ - - - -To set your custom dock menu, you can use the `app.dock.setMenu` API, which is -only available on macOS: - -```javascript -const {app, Menu} = require('electron') - -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...'} -]) -app.dock.setMenu(dockMenu) -``` - -## User Tasks (Windows) - -On Windows you can specify custom actions in the `Tasks` category of JumpList, -as quoted from MSDN: - -> Applications define tasks based on both the program's features and the key -> things a user is expected to do with them. Tasks should be context-free, in -> that the application does not need to be running for them to work. They -> should also be the statistically most common actions that a normal user would -> perform in an application, such as compose an email message or open the -> calendar in a mail program, create a new document in a word processor, launch -> an application in a certain mode, or launch one of its subcommands. An -> application should not clutter the menu with advanced features that standard -> users won't need or one-time actions such as registration. Do not use tasks -> for promotional items such as upgrades or special offers. -> -> It is strongly recommended that the task list be static. It should remain the -> same regardless of the state or status of the application. While it is -> possible to vary the list dynamically, you should consider that this could -> confuse the user who does not expect that portion of the destination list to -> change. - -__Tasks of Internet Explorer:__ - -![IE](http://i.msdn.microsoft.com/dynimg/IC420539.png) - -Unlike the dock menu in macOS which is a real menu, user tasks in Windows work -like application shortcuts such that when user clicks a task, a program will be -executed with specified arguments. - -To set user tasks for your application, you can use -[app.setUserTasks][setusertaskstasks] API: - -```javascript -const {app} = require('electron') -app.setUserTasks([ - { - program: process.execPath, - arguments: '--new-window', - iconPath: process.execPath, - iconIndex: 0, - title: 'New Window', - description: 'Create a new window' - } -]) -``` - -To clean your tasks list, just call `app.setUserTasks` with an empty array: - -```javascript -const {app} = require('electron') -app.setUserTasks([]) -``` - -The user tasks will still show even after your application closes, so the icon -and program path specified for a task should exist until your application is -uninstalled. - -## Thumbnail Toolbars - -On Windows you can add a thumbnail toolbar with specified buttons in a taskbar -layout of an application window. It provides users a way to access to a -particular window's command without restoring or activating the window. - -From MSDN, it's illustrated: - -> This toolbar is simply the familiar standard toolbar common control. It has a -> maximum of seven buttons. Each button's ID, image, tooltip, and state are defined -> in a structure, which is then passed to the taskbar. The application can show, -> enable, disable, or hide buttons from the thumbnail toolbar as required by its -> current state. -> -> For example, Windows Media Player might offer standard media transport controls -> such as play, pause, mute, and stop. - -__Thumbnail toolbar of Windows Media Player:__ - -![player](https://i-msdn.sec.s-msft.com/dynimg/IC420540.png) - -You can use [BrowserWindow.setThumbarButtons][setthumbarbuttons] to set -thumbnail toolbar in your application: - -```javascript -const {BrowserWindow} = require('electron') -const path = require('path') - -let win = new BrowserWindow({ - width: 800, - height: 600 -}) - -win.setThumbarButtons([ - { - tooltip: 'button1', - icon: path.join(__dirname, 'button1.png'), - click () { console.log('button1 clicked') } - }, - { - tooltip: 'button2', - icon: path.join(__dirname, 'button2.png'), - flags: ['enabled', 'dismissonclick'], - click () { console.log('button2 clicked.') } - } -]) -``` - -To clean thumbnail toolbar buttons, just call `BrowserWindow.setThumbarButtons` -with an empty array: - -```javascript -const {BrowserWindow} = require('electron') -let win = new BrowserWindow() -win.setThumbarButtons([]) -``` - -## Unity Launcher Shortcuts (Linux) - -In Unity, you can add custom entries to its launcher via modifying the -`.desktop` file, see [Adding Shortcuts to a Launcher][unity-launcher]. - -__Launcher shortcuts of Audacious:__ - -![audacious](https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles?action=AttachFile&do=get&target=shortcuts.png) - -## Progress Bar in Taskbar (Windows, macOS, 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 macOS 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. - -__Progress bar in taskbar button:__ - -![Taskbar Progress Bar](https://cloud.githubusercontent.com/assets/639601/5081682/16691fda-6f0e-11e4-9676-49b6418f1264.png) - -To set the progress bar for a Window, you can use the -[BrowserWindow.setProgressBar][setprogressbar] API: - -```javascript -const {BrowserWindow} = require('electron') -let win = new BrowserWindow() -win.setProgressBar(0.5) -``` - -## Icon Overlays in Taskbar (Windows) - -On Windows a taskbar button can use a small overlay to display application -status, as quoted from MSDN: - -> Icon overlays serve as a contextual notification of status, and are intended -> to negate the need for a separate notification area status icon to communicate -> that information to the user. For instance, the new mail status in Microsoft -> Outlook, currently shown in the notification area, can now be indicated -> through an overlay on the taskbar button. Again, you must decide during your -> development cycle which method is best for your application. Overlay icons are -> intended to supply important, long-standing status or notifications such as -> network status, messenger status, or new mail. The user should not be -> presented with constantly changing overlays or animations. - -__Overlay on taskbar button:__ - -![Overlay on taskbar button](https://i-msdn.sec.s-msft.com/dynimg/IC420441.png) - -To set the overlay icon for a window, you can use the -[BrowserWindow.setOverlayIcon][setoverlayicon] API: - -```javascript -const {BrowserWindow} = require('electron') -let win = new BrowserWindow() -win.setOverlayIcon('path/to/overlay.png', 'Description for overlay') -``` - -## Flash Frame (Windows) - -On Windows you can highlight the taskbar button to get the user's attention. -This is similar to bouncing the dock icon on macOS. -From the MSDN reference documentation: - -> Typically, a window is flashed to inform the user that the window requires -> attention but that it does not currently have the keyboard focus. - -To flash the BrowserWindow taskbar button, you can use the -[BrowserWindow.flashFrame][flashframe] API: - -```javascript -const {BrowserWindow} = require('electron') -let win = new BrowserWindow() -win.once('focus', () => win.flashFrame(false)) -win.flashFrame(true) -``` - -Don't forget to call the `flashFrame` method with `false` to turn off the flash. In -the above example, it is called when the window comes into focus, but you might -use a timeout or some other event to disable it. - -## Represented File of Window (macOS) - -On macOS a window can set its represented file, so the file's icon can show in -the title bar and when users Command-Click or Control-Click on the title a path -popup will show. - -You can also set the edited state of a window so that the file icon can indicate -whether the document in this window has been modified. - -__Represented file popup menu:__ - - - -To set the represented file of window, you can use the -[BrowserWindow.setRepresentedFilename][setrepresentedfilename] and -[BrowserWindow.setDocumentEdited][setdocumentedited] APIs: - -```javascript -const {BrowserWindow} = require('electron') -let win = new BrowserWindow() -win.setRepresentedFilename('/etc/passwd') -win.setDocumentEdited(true) -``` +See the [Represented File documentation](represented-file.md). ## Dragging files out of the window -For certain kinds of apps that manipulate on files, it is important to be able -to drag files from Electron to other apps. To implement this feature in your -app, you need to call `webContents.startDrag(item)` API on `ondragstart` event. +See the [Native File Drag & Drop documentation](native-file-drag-drop.md). -In web page: - -```html -item - -``` - -In the main process: - -```javascript -const {ipcMain} = require('electron') -ipcMain.on('ondragstart', (event, filePath) => { - event.sender.startDrag({ - file: filePath, - icon: '/path/to/icon.png' - }) -}) -``` - -[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/ -[flashframe]: ../api/browser-window.md#winflashframeflag diff --git a/docs/tutorial/development-environment.md b/docs/tutorial/development-environment.md new file mode 100644 index 00000000000..7f8f5b30bfd --- /dev/null +++ b/docs/tutorial/development-environment.md @@ -0,0 +1,117 @@ +# Developer Environment + +Electron development is essentially Node.js development. To turn your operating +system into an environment capable of building desktop apps with Electron, +you will merely need Node.js, npm, a code editor of your choice, and a +rudimentary understanding of your operating system's command line client. + +## Setting up macOS + +> Electron supports Mac OS X 10.9 (and all versions named macOS) and up. Apple +does not allow running macOS in virtual machines unless the host computer is +already an Apple computer, so if you find yourself in need of a Mac, consider +using a cloud service that rents access to Macs (like [MacInCloud][macincloud] +or [xcloud](https://xcloud.me)). + +First, install a recent version of Node.js. We recommend that you install +either the latest `LTS` or `Current` version available. Visit +[the Node.js download page][node-download] and select the `macOS Installer`. +While Homebrew is an offered option, but we recommend against it - many tools +will be incompatible with the way Homebrew installs Node.js. + +Once downloaded, execute the installer and let the installation wizard guide +you through the installation. + +Once installed, confirm that everything works as expected. Find the macOS +`Terminal` application in your `/Applications/Utilities` folder (or by +simply search for the word `Terminal` in Spotlight). Open up `Terminal` +or another command line client of your choice and confirm that both `node` +and `npm` are available: + +```sh +# This command should print the version of Node.js +node -v + +# This command should print the version of npm +npm -v +``` + +If both commands printed a version number, you are all set! Before you get +started, you might want to install a [code editor](#a-good-editor) suited +for JavaScript development. + +## Setting up Windows + +> Electron supports Windows 7 and later versions โ€“ย attempting to develop Electron +applications on earlier versions of Windows will not work. Microsoft provides +free [virtual machine images with Windows 10][windows-vm] for developers. + +First, install a recent version of Node.js. We recommend that you install +either the latest `LTS` or `Current` version available. Visit +[the Node.js download page][node-download] and select the `Windows Installer`. +Once downloaded, execute the installer and let the installation wizard guide +you through the installation. + +On the screen that allows you to configure the installation, make sure to +select the `Node.js runtime`, `npm package manager`, and `Add to PATH` +options. + +Once installed, confirm that everything works as expected. Find the Windows +PowerShell by simply opening the Start Menu and typing `PowerShell`. Open +up `PowerShell` or another command line client of your choice and confirm that +both `node` and `npm` are available: + +```powershell +# This command should print the version of Node.js +node -v + +# This command should print the version of npm +npm -v +``` + +If both commands printed a version number, you are all set! Before you get +started, you might want to install a [code editor](#a-good-editor) suited +for JavaScript development. + +## Setting up Linux + +> Generally speaking, Electron supports Ubuntu 12.04, Fedora 21, Debian 8 +and later. + +First, install a recent version of Node.js. Depending on your Linux +distribution, the installation steps might differ. Assuming that you normally +install software using a package manager like `apt` or `pacman`, use the +official [Node.js guidance on installing on Linux][node-package]. + +You're running Linux, so you likely already know how to operate a command line +client. Open up your favorite client and confirm that both `node` and `npm` +are available globally: + +```sh +# This command should print the version of Node.js +node -v + +# This command should print the version of npm +npm -v +``` + +If both commands printed a version number, you are all set! Before you get +started, you might want to install a [code editor](#a-good-editor) suited +for JavaScript development. + +## A Good Editor + +We might suggest two free popular editors built in Electron: +GitHub's [Atom][atom] and Microsoft's [Visual Studio Code][code]. Both of +them have excellent JavaScript support. + +If you are one of the many developers with a strong preference, know that +virtually all code editors and IDEs these days support JavaScript. + +[macincloud]: https://www.macincloud.com/ +[xcloud]: https://xcloud.me +[node-download]: https://nodejs.org/en/download/ +[node-package]: https://nodejs.org/en/download/package-manager/ +[atom]: https://atom.io/ +[code]: https://code.visualstudio.com/ +[windows-vm]: https://developer.microsoft.com/en-us/windows/downloads/virtual-machines \ No newline at end of file diff --git a/docs/tutorial/devtools-extension.md b/docs/tutorial/devtools-extension.md index 3d08df40027..d96d2728605 100644 --- a/docs/tutorial/devtools-extension.md +++ b/docs/tutorial/devtools-extension.md @@ -50,7 +50,7 @@ Following Devtools Extensions are tested and guaranteed to work in Electron: * [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) -* [Cerebral Debugger](http://www.cerebraljs.com/documentation/the_debugger) +* [Cerebral Debugger](https://cerebraljs.com/docs/introduction/debugger.html) * [Redux DevTools Extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd) * [MobX Developer Tools](https://chrome.google.com/webstore/detail/mobx-developer-tools/pfgnfdagidkfgccljigdamigbcnndkod) diff --git a/docs/tutorial/electron-versioning.md b/docs/tutorial/electron-versioning.md index de6a4b0259d..ecc62150131 100644 --- a/docs/tutorial/electron-versioning.md +++ b/docs/tutorial/electron-versioning.md @@ -1,120 +1,150 @@ # Electron Versioning -## Overview of Semantic Versioning +> A detailed look at our versioning policy and implementation. -If you've been using Node and npm for a while, you are probably aware of [Semantic Versioning], or SemVer for short. It's a convention for specifying version numbers for software that helps communicate intentions to the users of your software. - -Semantic versions are always made up of three numbers: +As of version 2.0.0, Electron follows [semver](#semver). The following command will install the most recent stable build of Electron: ```sh -major.minor.patch +npm install --save-dev electron ``` -Semantic version numbers are bumped (incremented) using the following rules: - -* **Major** is for changes that break backwards compatibility. -* **Minor** is for new features that don't break backwards compatibility. -* **Patch** is for bug fixes and other minor changes. - -A simple mnemonic for remembering this scheme is as follows: +To update an existing project to use the latest stable version: ```sh -breaking.feature.fix +npm install --save-dev electron@latest ``` -## Before Version 2 +## Version 1.x -Before version 2 of Electron we didn't follow SemVer, instead the following was used: +Electron versions *< 2.0* did not conform to the [semver](http://semver.org) spec: major versions corresponded to end-user API changes, minor versions corresponded to Chromium major releases, and patch versions corresponded to new features and bug fixes. While convenient for developers merging features, it creates problems for developers of client-facing applications. The QA testing cycles of major apps like Slack, Stride, Teams, Skype, VS Code, Atom, and Desktop can be lengthy and stability is a highly desired outcome. There is a high risk in adopting new features while trying to absorb bug fixes. -- **Major**: Breaking changes to Electron's API -- **Minor**: Major Chrome, minor node or "significant" Electron changes -- **Patch**: New features and bug fixes +Here is an example of the 1.x strategy: -This system had a number of drawbacks, such as: +![](../images/versioning-sketch-0.png) -- New bugs could be introduced into a new patch version because patch versions added features -- It didn't follow SemVer so it could confuse consumers -- It wasn't clear what the differences between stable and beta builds were -- The lack of a formalized stabilization process and release schedule lead to sporadic releases and betas that could last several months +An app developed with `1.8.1` cannot take the `1.8.3` bug fix without either absorbing the `1.8.2` feature, or by backporting the fix and maintaining a new release line. -## Version 2 and Beyond +## Version 2.0 and Beyond -From version 2.0.0, Electron will attempt to adhere to SemVer and follow a -release schedule and stabilization process similar to that of Chromium. +There are several major changes from our 1.x strategy outlined below. Each change is intended to satisfy the needs and priorities of developers/maintainers and app developers. -### Version Change Rules +1. Strict use of semver +2. Introduction of semver-compliant `-beta` tags +3. Introduction of [conventional commit messages](https://conventionalcommits.org/) +4. Clearly defined stabilization branches +5. The `master` branch is versionless; only stabilization branches contain version information -Here are the general rules that apply when releasing new versions: +We will cover in detail how git branching works, how npm tagging works, what developers should expect to see, and how one can backport changes. -| Type of change | Version increase -|---|--- -| Chromium version update | Major -| Node *major* version update | Major -| Electron breaking API change | Major -| Any other changes deemed "risky" | Major -| Node *minor* version update | Minor -| Electron non-breaking API change | Minor -| Electron bug fix | Patch +# semver -When you install an npm module with the `--save` or `--save-dev` flags, it -will be prefixed with a caret `^` in package.json: +From 2.0 onward, Electron will follow semver. -```json -{ - "devDependencies": { - "electron": "^2.0.0" - } -} +Below is a table explicitly mapping types of changes to their corresponding category of semver (e.g. Major, Minor, Patch). + +| Major Version Increments | Minor Version Increments | Patch Version Increments | +| ------------------------------- | ---------------------------------- | ----------------------------- | +| Electron breaking API changes | Electron non-breaking API changes | Electron bug fixes | +| Node.js major version updates | Node.js minor version updates | Node.js patch version updates | +| Chromium version updates | | fix-related chromium patches | + + +Note that most chromium updates will be considered breaking. Fixes that can be backported will likely be cherry-picked as patches. + +# Stabilization Branches + +Stabilization branches are branches that run parallel to master, taking in only cherry-picked commits that are related to security or stability. These branches are never merged back to master. + +![](../images/versioning-sketch-1.png) + +Stabilization branches are always either **major** or **minor** version lines, and named against the following template `$MAJOR-$MINOR-x` e.g. `2-0-x`. + +We allow for multiple stabilization branches to exist simultaneously, and intend to support at least two in parallel at all times, backporting security fixes as necessary. +![](../images/versioning-sketch-2.png) + +Older lines will not be supported by GitHub, but other groups can take ownership and backport stability and security fixes on their own. We discourage this, but recognize that it makes life easier for many app developers. + +# Beta Releases and Bug Fixes + +Developers want to know which releases are _safe_ to use. Even seemingly innocent features can introduce regressions in complex applications. At the same time, locking to a fixed version is dangerous because youโ€™re ignoring security patches and bug fixes that may have come out since your version. Our goal is to allow the following standard semver ranges in `package.json` : + +* Use `~2.0.0` to admit only stability or security related fixes to your `2.0.0` release. +* Use `^2.0.0` to admit non-breaking _reasonably stable_ feature work as well as security and bug fixes. + +Whatโ€™s important about the second point is that apps using `^` should still be able to expect a reasonable level of stability. To accomplish this, semver allows for a _pre-release identifier_ to indicate a particular version is not yet _safe_ or _stable_. + +Whatever you choose, you will periodically have to bump the version in your `package.json` as breaking changes are a fact of Chromium life. + +The process is as follows: + +1. All new major and minor releases lines begin with a `-beta.N` tag for `N >= 1`. At that point, the feature set is **locked**. That release line admits no further features, and focuses only on security and stability. +e.g. `2.0.0-beta.1`. +2. Bug fixes, regression fixes, and security patches can be admitted. Upon doing so, a new beta is released incrementing `N`. +e.g. `2.0.0-beta.2` +3. If a particular beta release is _generally regarded_ as stable, it will be re-released as a stable build, changing only the version information. +e.g. `2.0.0`. +4. If future bug fixes or security patches need to be made once a release is stable, they are applied and the _patch_ version is incremented accordingly +e.g. `2.0.1`. + +For each major and minor bump, you should expect to see something like the following: + +```text +2.0.0-beta.1 +2.0.0-beta.2 +2.0.0-beta.3 +2.0.0 +2.0.1 +2.0.2 ``` -The [caret semver range](https://docs.npmjs.com/misc/semver#caret-ranges-123-025-004) -allows minor- and patch-level changes to be installed, i.e. non-breaking -features and bug fixes. +An example lifecycle in pictures: -Alternatively, a more conservative approach is to use the -[tilde semver range](https://docs.npmjs.com/misc/semver#tilde-ranges-123-12-1) -`~`, which will only allow patch-level upgrades, i.e. bug fixes. +* A new release branch is created that includes the latest set of features. It is published as `2.0.0-beta.1`. +![](../images/versioning-sketch-3.png) +* A bug fix comes into master that can be backported to the release branch. The patch is applied, and a new beta is published as `2.0.0-beta.2`. +![](../images/versioning-sketch-4.png) +* The beta is considered _generally stable_ and it is published again as a non-beta under `2.0.0`. +![](../images/versioning-sketch-5.png) +* Later, a zero-day exploit is revealed and a fix is applied to master. We backport the fix to the `2-0-x` line and release `2.0.1`. +![](../images/versioning-sketch-6.png) +A few examples of how various semver ranges will pick up new releases: -### The Release Schedule +![](../images/versioning-sketch-7.png) -**Note: The schedule outlined here is _aspirational_. We are not yet cutting -releases at a weekly cadence, but we hope to get there eventually.** +# Missing Features: Alphas, and Nightly +Our strategy has a few tradeoffs, which for now we feel are appropriate. Most importantly that new features in master may take a while before reaching a stable release line. If you want to try a new feature immediately, you will have to build Electron yourself. - +As a future consideration, we may introduce one or both of the following: -Here are some important points to call out: +* nightly builds off of master; these would allow folks to test new features quickly and give feedback +* alpha releases that have looser stability constraints to betas; for example it would be allowable to admit new features while a stability channel is in _alpha_ -- A new release is performed approximately weekly. -- Minor versions are branched off of master for stabilization. -- The stabilization period is approximately weekly. -- Important bug fixes are cherry-picked to stabilization branches after landing - in master. -- Features are not cherry picked; a minor version should only get *more stable* - with its patch versions. -- There is little difference in the release schedule between a major and minor - release, other than the risk/effort it may take for third parties to adopt -- Chromium updates will be performed as fast as the team can manage. In an ideal - world this would happen every 6 weeks to align with - [Chromium's release schedule][Chromium release]. -- Excluding exceptional circumstances, only the previous stable build will - get backported bug fixes. +# Feature Flags +Feature flags are a common practice in Chromium, and are well-established in the web-development ecosystem. In the context of Electron, a feature flag or **soft branch** must have the following properties: -### The Beta Process +* it is enabled/disabled either at runtime, or build-time; we do not support the concept of a request-scoped feature flag +* it completely segments new and old code paths; refactoring old code to support a new feature _violates_ the feature-flag contract +* feature flags are eventually removed after the soft-branch is merged -Electron relies on its consumers getting involved in stabilization. The short -target stabilization period and rapid release cadence was designed for shipping -security and bug fixes out fast and to encourage the automation of testing. +We reconcile flagged code with our versioning strategy as follows: -You can install the beta by specifying the `beta` dist tag when installing via -npm: +1. we do not consider iterating on feature-flagged code in a stability branch; even judicious use of feature flags is not without risk +2. you may break API contracts in feature-flagged code without bumping the major version. Flagged code does not adhere to semver -```sh -npm install electron@beta -``` +# Semantic Commits -[Semantic Versioning]: http://semver.org -[pre-release identifier]: http://semver.org/#spec-item-9 -[npm dist tag]: https://docs.npmjs.com/cli/dist-tag -[normal version]: http://semver.org/#spec-item-2 -[Chromium release]: https://www.chromium.org/developers/calendar \ No newline at end of file +We seek to increase clarity at all levels of the update and releases process. Starting with `2.0.0` we will require pull requests adhere to the [Conventional Commits](https://conventionalcommits.org/) spec, which can be summarized as follows: + +* Commits that would result in a semver **major** bump must start with `BREAKING CHANGE:`. +* Commits that would result in a semver **minor** bump must start with `feat:`. +* Commits that would result in a semver **patch** bump must start with `fix:`. + +* We allow squashing of commits, provided that the squashed message adheres the the above message format. +* It is acceptable for some commits in a pull request to not include a semantic prefix, as long as a later commit in the same pull request contains a meaningful encompassing semantic message. + +# Versionless `master` + +- The `master` branch will always contain `0.0.0-dev` in its `package.json` +- Release branches are never merged back to master +- Release branches _do_ contain the correct version in their `package.json` diff --git a/docs/tutorial/first-app.md b/docs/tutorial/first-app.md new file mode 100644 index 00000000000..4e44b09a2c0 --- /dev/null +++ b/docs/tutorial/first-app.md @@ -0,0 +1,235 @@ +# Writing Your First Electron App + +Electron enables you to create desktop applications with pure JavaScript by +providing a runtime with rich native (operating system) APIs. You could see it +as a variant of the Node.js runtime that is focused on desktop applications +instead of web servers. + +This doesn't mean Electron is a JavaScript binding to graphical user interface +(GUI) libraries. Instead, Electron uses web pages as its GUI, so you could also +see it as a minimal Chromium browser, controlled by JavaScript. + +**Note**: This example is also available as a repository you can +[download and run immediately](#trying-this-example). + +As far as development is concerned, an Electron application is essentially a +Node.js application. The starting point is a `package.json` that is identical +to that of a Node.js module. A most basic Electron app would have the following +folder structure: + +```text +your-app/ +โ”œโ”€โ”€ package.json +โ”œโ”€โ”€ main.js +โ””โ”€โ”€ index.html +``` + +Create a new empty folder for your new Electron application. Open up your +command line client and run `npm init` from that very folder. + +```sh +npm init +``` + +npm will guide you through creating a basic `package.json` file. The script +specified by the `main` field is the startup script of your app, which will +run the main process. An example of your `package.json` might look like this: + +```json +{ + "name": "your-app", + "version": "0.1.0", + "main": "main.js" +} +``` + +__Note__: If the `main` field is not present in `package.json`, Electron will +attempt to load an `index.js` (just like Node.js itself). If this was actually +a simple Node application, you would add a `start` script that instructs `node` +to execute the current package: + +```json +{ + "name": "your-app", + "version": "0.1.0", + "main": "main.js", + "scripts": { + "start": "node ." + } +} +``` + +Turning this Node application into an Electron application is quite simple - we +merely replace the `node` runtime with the `electron` runtime. + +```json +{ + "name": "your-app", + "version": "0.1.0", + "main": "main.js", + "scripts": { + "start": "electron ." + } +} +``` + +## Installing Electron + +At this point, you'll need to install `electron` itself. The recommended way +of doing so is to install it as a development dependency in your app, which +allows you to work on multiple apps with different Electron versions. To do so, +run the following command from your app's directory: + +```sh +npm install --save-dev electron +``` + +Other means for installing Electron exist. Please consult the +[installation guide](installation.md) to learn about use with proxies, mirrors, +and custom caches. + +## Electron Development in a Nutshell + +Electron apps are developed in JavaScript using the same principals and methods +found in Node.js development. All APIs and features found in Electron are +accessible through the `electron` module, which can be required like any other +Node.js module: + +```javascript +const electron = require('electron') +``` + +The `electron` module exposes features in namespaces. As examples, the lifecycle +of the application is managed through `electron.app`, windows can be created +using the `electron.BrowserWindow` class. A simple `main.js` file might just wait +for the application to be ready and open a window: + +```javascript +const {app, BrowserWindow} = require('electron') + +function createWindow () { + // Create the browser window. + win = new BrowserWindow({width: 800, height: 600}) + + // and load the index.html of the app. + win.loadURL(url.format({ + pathname: path.join(__dirname, 'index.html'), + protocol: 'file:', + slashes: true + })) +} + +app.on('ready', createWindow) +``` + +The `main.js` should create windows and handle all the system events your +application might encounter. A more complete version of the above example +might open developer tools, handle the window being closed, or re-create +windows on macOS if the user clicks on the app's icon in the dock. + +```javascript +const {app, BrowserWindow} = require('electron') +const path = require('path') +const url = require('url') + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let win + +function createWindow () { + // Create the browser window. + win = new BrowserWindow({width: 800, height: 600}) + + // and load the index.html of the app. + win.loadURL(url.format({ + pathname: path.join(__dirname, 'index.html'), + protocol: 'file:', + slashes: true + })) + + // Open the DevTools. + win.webContents.openDevTools() + + // Emitted when the window is closed. + win.on('closed', () => { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + win = null + }) +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.on('ready', createWindow) + +// Quit when all windows are closed. +app.on('window-all-closed', () => { + // On macOS it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') { + app.quit() + } +}) + +app.on('activate', () => { + // On macOS it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (win === null) { + createWindow() + } +}) + +// In this file you can include the rest of your app's specific main process +// code. You can also put them in separate files and require them here. +``` + +Finally the `index.html` is the web page you want to show: + +```html + + + + + Hello World! + + +

Hello World!

+ We are using node , + Chrome , + and Electron . + + +``` + +## Running Your App + +Once you've created your initial `main.js`, `index.html`, and `package.json` +files, you can try your app by running `npm start` from your application's +directory. + +## Trying this Example + +Clone and run the code in this tutorial by using the +[`electron/electron-quick-start`][quick-start] repository. + +**Note**: Running this requires [Git](https://git-scm.com). + +```sh +# Clone the repository +$ git clone https://github.com/electron/electron-quick-start +# Go into the repository +$ cd electron-quick-start +# Install dependencies +$ npm install +# Run the app +$ npm start +``` + +For a list of boilerplates and tools to kick-start your development process, +see the [Boilerplates and CLIs documentation][boilerplates]. + +[share-data]: ../faq.md#how-to-share-data-between-web-pages +[quick-start]: https://github.com/electron/electron-quick-start +[boilerplates]: ./boilerplates-and-clis.md diff --git a/docs/tutorial/installation.md b/docs/tutorial/installation.md index 22688be458c..5d70c61db49 100644 --- a/docs/tutorial/installation.md +++ b/docs/tutorial/installation.md @@ -1,8 +1,6 @@ # Installation -> Tips for installing Electron - -To install prebuilt Electron binaries, use [`npm`](https://docs.npmjs.com/). +To install prebuilt Electron binaries, use [`npm`][npm]. The preferred method is to install Electron as a development dependency in your app: @@ -10,9 +8,8 @@ app: npm install electron --save-dev ``` -See the -[Electron versioning doc](https://electronjs.org/docs/tutorial/electron-versioning) -for info on how to manage Electron versions in your apps. +See the [Electron versioning doc][versioning] for info on how to +manage Electron versions in your apps. ## Global Installation @@ -32,7 +29,7 @@ If you want to change the architecture that is downloaded (e.g., `ia32` on an npm install --arch=ia32 electron ``` -In addition to changing the architecture, you can also specify the platform +In addition to changing the architecture, you can also specify the platform (e.g., `win32`, `linux`, etc.) using the `--platform` flag: ```shell @@ -41,37 +38,104 @@ npm install --platform=win32 electron ## Proxies -If you need to use an HTTP proxy you can [set these environment variables](https://github.com/request/request/tree/f0c4ec061141051988d1216c24936ad2e7d5c45d#controlling-proxy-behaviour-using-environment-variables). +If you need to use an HTTP proxy you can [set these environment variables][proxy-env]. + +## Custom Mirrors and Caches +During installation, the `electron` module will call out to +[`electron-download`][electron-download] to download prebuilt binaries of +Electron for your platform. It will do so by contacting GitHub's +release download page (`https://github.com/electron/electron/releases/tag/v$VERSION`, +where `$VERSION` is the exact version of Electron). + +If you are unable to access GitHub or you need to provide a custom build, you +can do so by either providing a mirror or an existing cache directory. + +#### Mirror +You can use environment variables to override the base URL, the path at which to +look for Electron binaries, and the binary filename. The url used by `electron-download` +is composed as follows: + +```txt +url = ELECTRON_MIRROR + ELECTRON_CUSTOM_DIR + '/' + ELECTRON_CUSTOM_FILENAME +``` + +For instance, to use the China mirror: + +```txt +ELECTRON_MIRROR="https://npm.taobao.org/mirrors/electron/" +``` + +#### Cache +Alternatively, you can override the local cache. `electron-download` will cache +downloaded binaries in a local directory to not stress your network. You can use +that cache folder to provide custom builds of Electron or to avoid making contact +with the network at all. + +* Linux: `$XDG_CACHE_HOME` or `~/.cache/electron/` +* MacOS: `~/Library/Caches/electron/` +* Windows: `$LOCALAPPDATA/electron/Cache` or `~/AppData/Local/electron/Cache/` + +On environments that have been using older versions of Electron, you might find the +cache also in `~/.electron`. + +You can also override the local cache location by providing a `ELECTRON_CACHE` +environment variable. + +The cache contains the version's official zip file as well as a checksum, stored as +a text file. A typical cache might look like this: + +```sh +โ”œโ”€โ”€ electron-v1.7.9-darwin-x64.zip +โ”œโ”€โ”€ electron-v1.8.1-darwin-x64.zip +โ”œโ”€โ”€ electron-v1.8.2-beta.1-darwin-x64.zip +โ”œโ”€โ”€ electron-v1.8.2-beta.2-darwin-x64.zip +โ”œโ”€โ”€ electron-v1.8.2-beta.3-darwin-x64.zip +โ”œโ”€โ”€ SHASUMS256.txt-1.7.9 +โ”œโ”€โ”€ SHASUMS256.txt-1.8.1 +โ”œโ”€โ”€ SHASUMS256.txt-1.8.2-beta.1 +โ”œโ”€โ”€ SHASUMS256.txt-1.8.2-beta.2 +โ”œโ”€โ”€ SHASUMS256.txt-1.8.2-beta.3 +``` ## Troubleshooting -When running `npm install electron`, some users occasionally encounter +When running `npm install electron`, some users occasionally encounter installation errors. -In almost all cases, these errors are the result of network problems and not -actual issues with the `electron` npm package. Errors like `ELIFECYCLE`, -`EAI_AGAIN`, `ECONNRESET`, and `ETIMEDOUT` are all indications of such -network problems. The best resolution is to try switching networks, or +In almost all cases, these errors are the result of network problems and not +actual issues with the `electron` npm package. Errors like `ELIFECYCLE`, +`EAI_AGAIN`, `ECONNRESET`, and `ETIMEDOUT` are all indications of such +network problems. The best resolution is to try switching networks, or just wait a bit and try installing again. -You can also attempt to download Electron directly from -[electron/electron/releases](https://github.com/electron/electron/releases) +You can also attempt to download Electron directly from +[electron/electron/releases][releases] if installing via `npm` is failing. -If installation fails with an `EACCESS` error you may need to -[fix your npm permissions](https://docs.npmjs.com/getting-started/fixing-npm-permissions). +If installation fails with an `EACCESS` error you may need to +[fix your npm permissions][npm-permissions]. -If the above error persists, the [unsafe-perm](https://docs.npmjs.com/misc/config#unsafe-perm) flag may need to be set to true: +If the above error persists, the [unsafe-perm][unsafe-perm] flag may need to be +set to true: ```sh sudo npm install electron --unsafe-perm=true ``` -On slower networks, it may be advisable to use the `--verbose` flag in order to show download progress: +On slower networks, it may be advisable to use the `--verbose` flag in order to +show download progress: ```sh npm install --verbose electron ``` If you need to force a re-download of the asset and the SHASUM file set the -`force_no_cache` enviroment variable to `true`. \ No newline at end of file +`force_no_cache` environment variable to `true`. + +[npm]: https://docs.npmjs.com +[versioning]: ./electron-versioning.md +[releases]: https://github.com/electron/electron/releases +[proxy-env]: https://github.com/request/request/tree/f0c4ec061141051988d1216c24936ad2e7d5c45d#controlling-proxy-behaviour-using-environment-variables +[electron-download]: https://github.com/electron-userland/electron-download +[npm-permissions]: https://docs.npmjs.com/getting-started/fixing-npm-permissions +[unsafe-perm]: https://docs.npmjs.com/misc/config#unsafe-perm \ No newline at end of file diff --git a/docs/tutorial/keyboard-shortcuts.md b/docs/tutorial/keyboard-shortcuts.md index 2828f221bf1..586c780bd50 100644 --- a/docs/tutorial/keyboard-shortcuts.md +++ b/docs/tutorial/keyboard-shortcuts.md @@ -52,7 +52,7 @@ window.addEventListener('keyup', doSomething, true) Note the third parameter `true` which means the listener will always receive key presses before other listeners so they can't have `stopPropagation()` called on them. -The [`before-input-event`](web-contents.md#event-before-input-event) event +The [`before-input-event`](../api/web-contents.md#event-before-input-event) event is emitted before dispatching `keydown` and `keyup` events in the page. It can be used to catch and handle custom shortcuts that are not visible in the menu. diff --git a/docs/tutorial/linux-desktop-actions.md b/docs/tutorial/linux-desktop-actions.md new file mode 100644 index 00000000000..3713ac69ba0 --- /dev/null +++ b/docs/tutorial/linux-desktop-actions.md @@ -0,0 +1,41 @@ +# Custom Linux Desktop Launcher Actions + +On many Linux environments, you can add custom entries to its launcher +by modifying the `.desktop` file. For Canonical's Unity documentation, +see [Adding Shortcuts to a Launcher][unity-launcher]. For details on a +more generic implementation, see the [freedesktop.org Specification][spec]. + +__Launcher shortcuts of Audacious:__ + +![audacious][audacious-launcher] + +Generally speaking, shortcuts are added by providing a `Name` and `Exec` +property for each entry in the shortcuts menu. Unity will execute the +`Exec` field once clicked by the user. The format is as follows: + +```text +Actions=PlayPause;Next;Previous + +[Desktop Action PlayPause] +Name=Play-Pause +Exec=audacious -t +OnlyShowIn=Unity; + +[Desktop Action Next] +Name=Next +Exec=audacious -f +OnlyShowIn=Unity; + +[Desktop Action Previous] +Name=Previous +Exec=audacious -r +OnlyShowIn=Unity; +``` + +Unity's preferred way of telling your application what to do is to use +parameters. You can find these in your app in the global variable +`process.argv`. + +[unity-launcher]: https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles#Adding_shortcuts_to_a_launcher +[audacious-launcher]: https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles?action=AttachFile&do=get&target=shortcuts.png +[spec]: https://specifications.freedesktop.org/desktop-entry-spec/1.1/ar01s11.html \ No newline at end of file diff --git a/docs/tutorial/mac-app-store-submission-guide.md b/docs/tutorial/mac-app-store-submission-guide.md index 3b77207e475..e8812a85b57 100644 --- a/docs/tutorial/mac-app-store-submission-guide.md +++ b/docs/tutorial/mac-app-store-submission-guide.md @@ -231,7 +231,7 @@ more details. ## Known issues -### `shell.openItem(filePath)` +### `shell.openItem(filePath)` This will fail when the app is signed for distribution in the Mac App Store. Subscribe to [#9005](https://github.com/electron/electron/issues/9005) for updates. @@ -248,29 +248,29 @@ submit a copy of U.S. Encryption Registration (ERN) approval. Electron uses following cryptographic algorithms: -* AES - [NIST SP 800-38A](http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf), [NIST SP 800-38D](http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf), [RFC 3394](http://www.ietf.org/rfc/rfc3394.txt) -* HMAC - [FIPS 198-1](http://csrc.nist.gov/publications/fips/fips198-1/FIPS-198-1_final.pdf) +* AES - [NIST SP 800-38A](https://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf), [NIST SP 800-38D](https://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf), [RFC 3394](https://www.ietf.org/rfc/rfc3394.txt) +* HMAC - [FIPS 198-1](https://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) +* HKDF - [NIST SP 800-56C](https://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) +* SHA - [FIPS 180-4](https://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) +* DES - [FIPS 46-3](https://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) +* DSA - [ANSI X9.30](https://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) +* MD2 - [RFC 1319](https://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) +* MDC2 - [ISO/IEC 10118-2](https://wiki.openssl.org/index.php/Manual:Mdc2(3)) * 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) +* RIPEMD - [ISO/IEC 10118-3](https://webstore.ansi.org/RecordDetail.aspx?sku=ISO%2FIEC%2010118-3:2004) On how to get the ERN approval, you can reference the article: [How to legally submit an app to Appleโ€™s App Store when it uses encryption (or how to obtain an diff --git a/docs/tutorial/macos-dock.md b/docs/tutorial/macos-dock.md new file mode 100644 index 00000000000..8f861cf0024 --- /dev/null +++ b/docs/tutorial/macos-dock.md @@ -0,0 +1,41 @@ +# MacOS Dock + +Electron has APIs to configure the app's icon in the macOS Dock. A macOS-only +API exists to create a [a custom dock menu](#custom-dock-menu-mac-os), but +Electron also uses the app's dock icon to implement cross-platform features +like [recent documents][recent-documents] and +[application progress][progress-bar]. + +The custom dock is commonly used to add shortcuts to tasks the user wouldn't +want to open the whole app window for. + +__Dock menu of Terminal.app:__ + +![Dock Menu][dock-menu-image] + +To set your custom dock menu, you can use the `app.dock.setMenu` API, which is +only available on macOS: + +```javascript +const { app, Menu } = require('electron') + +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...' } +]) + +app.dock.setMenu(dockMenu) +``` + +[dock-menu-image]: https://cloud.githubusercontent.com/assets/639601/5069962/6032658a-6e9c-11e4-9953-aa84006bdfff.png +[recent-documents]: ./recent-documents.md +[progress-bar]: ./progress-bar.md diff --git a/docs/tutorial/native-file-drag-drop.md b/docs/tutorial/native-file-drag-drop.md new file mode 100644 index 00000000000..99d25418260 --- /dev/null +++ b/docs/tutorial/native-file-drag-drop.md @@ -0,0 +1,37 @@ +# Native File Drag & Drop + +Certain kinds of applications that manipulate files might want to support +the operating system's native file drag & drop feature. Dragging files into +web content is common and supported by many websites. Electron additionally +supports dragging files and content out from web content into the operating +system's world. + +To implement this feature in your app, you need to call `webContents.startDrag(item)` +API in response to the `ondragstart` event. + +In your renderer process, handle the `ondragstart` event and forward the +information to your main process. + +```html +item + +``` + +Then, in the main process, augment the event with a path to the file that is +being dragged and an icon. + +```javascript +const { ipcMain } = require('electron') + +ipcMain.on('ondragstart', (event, filePath) => { + event.sender.startDrag({ + file: filePath, + icon: '/path/to/icon.png' + }) +}) +``` diff --git a/docs/tutorial/offscreen-rendering.md b/docs/tutorial/offscreen-rendering.md index 4c3024bdc68..a213968ea3e 100644 --- a/docs/tutorial/offscreen-rendering.md +++ b/docs/tutorial/offscreen-rendering.md @@ -14,7 +14,7 @@ performance loss. **Note:** An offscreen window is always created as a [Frameless Window](../api/frameless-window.md). -## Two modes of rendering +## Rendering Modes ### GPU accelerated @@ -35,17 +35,19 @@ To enable this mode GPU acceleration has to be disabled by calling the ## Usage ``` javascript -const {app, BrowserWindow} = require('electron') +const { app, BrowserWindow } = require('electron') app.disableHardwareAcceleration() let win + app.once('ready', () => { win = new BrowserWindow({ webPreferences: { offscreen: true } }) + win.loadURL('http://github.com') win.webContents.on('paint', (event, dirty, image) => { // updateBitmap(dirty, image.getBitmap()) diff --git a/docs/tutorial/planned-breaking-changes.md b/docs/tutorial/planned-breaking-changes.md index 7dfc6084f5c..b5401bfc2e4 100644 --- a/docs/tutorial/planned-breaking-changes.md +++ b/docs/tutorial/planned-breaking-changes.md @@ -1,6 +1,6 @@ # Planned Breaking API Changes -The following list includes the APIs that will be removed in Electron 2.0. +The following list includes the APIs that will be removed in Electron 3.0. There is no timetable for when this release will occur but deprecation warnings will be added at least 90 days beforehand. @@ -25,16 +25,6 @@ let optionsB = {webPreferences: {enableBlinkFeatures: ''}} let windowB = new BrowserWindow(optionsB) ``` - -```js -// Deprecated -let optionsA = {titleBarStyle: 'hidden-inset'} -let windowA = new BrowserWindow(optionsA) -// Replace with -let optionsB = {titleBarStyle: 'hiddenInset'} -let windowB = new BrowserWindow(optionsB) -``` - ## `clipboard` ```js @@ -76,28 +66,9 @@ crashReporter.start({ }) ``` -## `menu` - -```js -// Deprecated -menu.popup(browserWindow, 100, 200, 2) -// Replace with -menu.popup(browserWindow, {x: 100, y: 200, positioningItem: 2}) -``` - ## `nativeImage` ```js -// Deprecated -nativeImage.toPng() -// Replace with -nativeImage.toPNG() - -// Deprecated -nativeImage.toJpeg() -// Replace with -nativeImage.toJPEG() - // Deprecated nativeImage.createFromBuffer(buffer, 1.0) // Replace with @@ -106,19 +77,15 @@ nativeImage.createFromBuffer(buffer, { }) ``` -## `process` +## `screen` ```js // Deprecated -process.versions['atom-shell'] +screen.getMenuBarHeight() // Replace with -process.versions.electron +screen.getPrimaryDisplay().workArea ``` -* `process.versions.electron` and `process.version.chrome` will be made - read-only properties for consistency with the other `process.versions` - properties set by Node. - ## `session` ```js @@ -155,21 +122,9 @@ webContents.openDevTools({detach: true}) webContents.openDevTools({mode: 'detach'}) ``` -```js -// Deprecated -webContents.setZoomLevelLimits(1, 2) -// Replace with -webContents.setVisualZoomLevelLimits(1, 2) -``` - ## `webFrame` ```js -// Deprecated -webFrame.setZoomLevelLimits(1, 2) -// Replace with -webFrame.setVisualZoomLevelLimits(1, 2) - // Deprecated webFrame.registerURLSchemeAsSecure('app') // Replace with @@ -181,15 +136,6 @@ webFrame.registerURLSchemeAsPrivileged('app', {secure: true}) protocol.registerStandardSchemes(['app'], {secure: true}) ``` -## `` - -```js -// Deprecated -webview.setZoomLevelLimits(1, 2) -// Replace with -webview.setVisualZoomLevelLimits(1, 2) -``` - ## Node Headers URL This is the URL specified as `disturl` in a `.npmrc` file or as the `--dist-url` @@ -199,26 +145,8 @@ Deprecated: https://atom.io/download/atom-shell Replace with: https://atom.io/download/electron -## Duplicate ARM Assets - -Each Electron release includes two identical ARM builds with slightly different -filenames, like `electron-v1.7.3-linux-arm.zip` and -`electron-v1.7.3-linux-armv7l.zip`. The asset with the `v7l` prefix was added -to clarify to users which ARM version it supports, and to disambiguate it from -future armv6l and arm64 assets that may be produced. - -The file _without the prefix_ is still being published to avoid breaking any -setups that may be consuming it. Starting at 2.0, the un-prefixed file will -no longer be published. - -For details, see -[6986](https://github.com/electron/electron/pull/6986) -and -[7189](https://github.com/electron/electron/pull/7189). - - ## `FIXME` comments -The `FIXME` string is used in code comments to denote things that should be -fixed for the 2.0 release. See +The `FIXME` string is used in code comments to denote things that should be +fixed for the 3.0 release. See https://github.com/electron/electron/search?q=fixme diff --git a/docs/tutorial/progress-bar.md b/docs/tutorial/progress-bar.md new file mode 100644 index 00000000000..6922b3464fb --- /dev/null +++ b/docs/tutorial/progress-bar.md @@ -0,0 +1,35 @@ +# Progress Bar in Taskbar (Windows, macOS, 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 macOS 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. + +__Progress bar in taskbar button:__ + +![Taskbar Progress Bar][taskbar-progress-image] + +All three cases are covered by the same API - the `setProgressBar()` method +available on instances of `BrowserWindows`. Call it with a number between `0` +and `1` to indicate your progress. If you have a long-running task that's +currently at 63% towards completion, you'd call it with `setProgressBar(0.63)`. + +Generally speaking, setting the parameter to a value below zero (like `-1`) +will remove the progress bar while setting it to a value higher than one +(like `2`) will switch the progress bar to intermediate mode. + +See the [API documentation for more options and modes][setprogressbar]. + +```javascript +const { BrowserWindow } = require('electron') +const win = new BrowserWindow() + +win.setProgressBar(0.5) +``` + +[taskbar-progress-image]: https://cloud.githubusercontent.com/assets/639601/5081682/16691fda-6f0e-11e4-9676-49b6418f1264.png +[setprogressbar]: ../api/browser-window.md#winsetprogressbarprogress diff --git a/docs/tutorial/quick-start.md b/docs/tutorial/quick-start.md index aa8a5408e20..5630068d871 100644 --- a/docs/tutorial/quick-start.md +++ b/docs/tutorial/quick-start.md @@ -5,246 +5,18 @@ providing a runtime with rich native (operating system) APIs. You could see it as a variant of the Node.js runtime that is focused on desktop applications instead of web servers. -This doesn't mean Electron is a JavaScript binding to graphical user interface -(GUI) libraries. Instead, Electron uses web pages as its GUI, so you could also -see it as a minimal Chromium browser, controlled by JavaScript. +The old "Quick Start" document that used to live here has been split up into +two documents: -### Main Process +* To check out how a simple Electron app is built, see +[Writing Your First Electron App][first-app] +* To check out the process architecture, see +[Main and Renderer Processes][processes]. -In Electron, the process that runs `package.json`'s `main` script is called -__the main process__. The script that runs in the main process can display a GUI -by creating web pages. +If you just came here to learn about Electron, check out the +[official guides][readme]. -### Renderer Process +[first-app]: ./first-app.md +[processes]: ./application-architecture.md#main-and-renderer-processes +[readme]: ../README.md -Since Electron uses Chromium for displaying web pages, Chromium's -multi-process architecture is also used. Each web page in Electron runs in -its own process, which is called __the renderer process__. - -In normal browsers, web pages usually run in a sandboxed environment and are not -allowed access to native resources. Electron users, however, have the power to -use Node.js APIs in web pages allowing lower level operating system -interactions. - -### Differences Between Main Process and Renderer Process - -The main process creates web pages by creating `BrowserWindow` instances. Each -`BrowserWindow` instance runs the web page in its own renderer process. When a -`BrowserWindow` instance is destroyed, the corresponding renderer process -is also terminated. - -The main process manages all web pages and their corresponding renderer -processes. Each renderer process is isolated and only cares about the web page -running in it. - -In web pages, calling native GUI related APIs is not allowed because managing -native GUI resources in web pages is very dangerous and it is easy to leak -resources. If you want to perform GUI operations in a web page, the renderer -process of the web page must communicate with the main process to request that -the main process perform those operations. - -In Electron, we have several ways to communicate between the main process and -renderer processes. Like [`ipcRenderer`](../api/ipc-renderer.md) and -[`ipcMain`](../api/ipc-main.md) modules for sending messages, and the -[remote](../api/remote.md) module for RPC style communication. There is also -an FAQ entry on [how to share data between web pages][share-data]. - -## Write your First Electron App - -Generally, an Electron app is structured like this: - -```text -your-app/ -โ”œโ”€โ”€ package.json -โ”œโ”€โ”€ main.js -โ””โ”€โ”€ index.html -``` - -The format of `package.json` is exactly the same as that of Node's modules, and -the script specified by the `main` field is the startup script of your app, -which will run the main process. An example of your `package.json` might look -like this: - -```json -{ - "name" : "your-app", - "version" : "0.1.0", - "main" : "main.js" -} -``` - -__Note__: If the `main` field is not present in `package.json`, Electron will -attempt to load an `index.js`. - -The `main.js` should create windows and handle system events, a typical -example being: - -```javascript -const {app, BrowserWindow} = require('electron') -const path = require('path') -const url = require('url') - -// Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the JavaScript object is garbage collected. -let win - -function createWindow () { - // Create the browser window. - win = new BrowserWindow({width: 800, height: 600}) - - // and load the index.html of the app. - win.loadURL(url.format({ - pathname: path.join(__dirname, 'index.html'), - protocol: 'file:', - slashes: true - })) - - // Open the DevTools. - win.webContents.openDevTools() - - // Emitted when the window is closed. - win.on('closed', () => { - // Dereference the window object, usually you would store windows - // in an array if your app supports multi windows, this is the time - // when you should delete the corresponding element. - win = null - }) -} - -// This method will be called when Electron has finished -// initialization and is ready to create browser windows. -// Some APIs can only be used after this event occurs. -app.on('ready', createWindow) - -// Quit when all windows are closed. -app.on('window-all-closed', () => { - // On macOS it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - if (process.platform !== 'darwin') { - app.quit() - } -}) - -app.on('activate', () => { - // On macOS it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (win === null) { - createWindow() - } -}) - -// In this file you can include the rest of your app's specific main process -// code. You can also put them in separate files and require them here. -``` - -Finally the `index.html` is the web page you want to show: - -```html - - - - - Hello World! - - -

Hello World!

- We are using node , - Chrome , - and Electron . - - -``` - -## Run your app - -Once you've created your initial `main.js`, `index.html`, and `package.json` files, -you'll probably want to try running your app locally to test it and make sure it's -working as expected. - -### `electron` - -[`electron`](https://github.com/electron-userland/electron-prebuilt) is -an `npm` module that contains pre-compiled versions of Electron. - -If you've installed it globally with `npm`, then you will only need to run the -following in your app's source directory: - -```sh -electron . -``` - -If you've installed it locally, then run: - -#### macOS / Linux - -```sh -$ ./node_modules/.bin/electron . -``` - -#### Windows - -```sh -$ .\node_modules\.bin\electron . -``` - -#### Node v8.2.0 and later - -```sh -$ npx electron . -``` - -### Manually Downloaded Electron Binary - -If you downloaded Electron manually, you can also use the included -binary to execute your app directly. - -#### macOS - -```sh -$ ./Electron.app/Contents/MacOS/Electron your-app/ -``` - -#### Linux - -```sh -$ ./electron/electron your-app/ -``` - -#### Windows - -```sh -$ .\electron\electron.exe your-app\ -``` - -`Electron.app` here is part of the Electron's release package, you can download -it from [here](https://github.com/electron/electron/releases). - -### Run as a distribution - -After you're done writing your app, you can create a distribution by -following the [Application Distribution](./application-distribution.md) guide -and then executing the packaged app. - -### Try this Example - -Clone and run the code in this tutorial by using the [`electron/electron-quick-start`](https://github.com/electron/electron-quick-start) -repository. - -**Note**: Running this requires [Git](https://git-scm.com) and [Node.js](https://nodejs.org/en/download/) (which includes [npm](https://npmjs.org)) on your system. - -```sh -# Clone the repository -$ git clone https://github.com/electron/electron-quick-start -# Go into the repository -$ cd electron-quick-start -# Install dependencies -$ npm install -# Run the app -$ npm start -``` - -For more example apps, see the -[list of boilerplates](https://electronjs.org/community#boilerplates) -created by the awesome electron community. - -[share-data]: ../faq.md#how-to-share-data-between-web-pages diff --git a/docs/tutorial/recent-documents.md b/docs/tutorial/recent-documents.md new file mode 100644 index 00000000000..5d77ff08a33 --- /dev/null +++ b/docs/tutorial/recent-documents.md @@ -0,0 +1,48 @@ +# Recent Documents (Windows & macOS) + +Windows and macOS provide easy access to a list of recent documents opened by +the application via JumpList or dock menu, respectively. + +__JumpList:__ + +![JumpList Recent Files][jumplist-image] + +__Application dock menu:__ + +![macOS Dock Menu][dock-menu-image] + +To add a file to recent documents, you can use the +[app.addRecentDocument][addrecentdocument] API: + +```javascript +const { app } = require('electron') +app.addRecentDocument('/Users/USERNAME/Desktop/work.type') +``` + +And you can use [app.clearRecentDocuments][clearrecentdocuments] API to empty +the recent documents list: + +```javascript +const { app } = require('electron') +app.clearRecentDocuments() +``` + +## Windows Notes + +In order to be able to use this feature on Windows, your application has to be +registered as a handler of the file type of the document, otherwise the file +won't appear in JumpList even after you have added it. You can find everything +on registering your application in [Application Registration][app-registration]. + +When a user clicks a file from the JumpList, a new instance of your application +will be started with the path of the file added as a command line argument. + +## macOS Notes + +When a file is requested from the recent documents menu, the `open-file` event +of `app` module will be emitted for it. + +[jumplist-image]: https://cloud.githubusercontent.com/assets/2289/23446924/11a27b98-fdfc-11e6-8485-cc3b1e86b80a.png +[dock-menu-image]: https://cloud.githubusercontent.com/assets/639601/5069610/2aa80758-6e97-11e4-8cfb-c1a414a10774.png +[addrecentdocument]: ../api/app.md#appaddrecentdocumentpath-macos-windows +[clearrecentdocuments]: ../api/app.md#appclearrecentdocuments-macos-windows diff --git a/docs/tutorial/represented-file.md b/docs/tutorial/represented-file.md new file mode 100644 index 00000000000..b8867d62858 --- /dev/null +++ b/docs/tutorial/represented-file.md @@ -0,0 +1,28 @@ +# Represented File for macOS BrowserWindows + +On macOS a window can set its represented file, so the file's icon can show in +the title bar and when users Command-Click or Control-Click on the title a path +popup will show. + +You can also set the edited state of a window so that the file icon can indicate +whether the document in this window has been modified. + +__Represented file popup menu:__ + +![Represented File][represented-image] + +To set the represented file of window, you can use the +[BrowserWindow.setRepresentedFilename][setrepresentedfilename] and +[BrowserWindow.setDocumentEdited][setdocumentedited] APIs: + +```javascript +const { BrowserWindow } = require('electron') + +const win = new BrowserWindow() +win.setRepresentedFilename('/etc/passwd') +win.setDocumentEdited(true) +``` + +[represented-image]: https://cloud.githubusercontent.com/assets/639601/5082061/670a949a-6f14-11e4-987a-9aaa04b23c1d.png +[setrepresentedfilename]: ../api/browser-window.md#winsetrepresentedfilenamefilename-macos +[setdocumentedited]: ../api/browser-window.md#winsetdocumenteditededited-macos diff --git a/docs/tutorial/security.md b/docs/tutorial/security.md index 5b9fe287472..f9d6f105d10 100644 --- a/docs/tutorial/security.md +++ b/docs/tutorial/security.md @@ -1,17 +1,17 @@ # Security, Native Capabilities, and Your Responsibility -As web developers, we usually enjoy the strong security net of the browser - the -risks associated with the code we write are relatively small. Our websites are -granted limited powers in a sandbox, and we trust that our users enjoy a browser -built by a large team of engineers that is able to quickly respond to newly -discovered security threats. +As web developers, we usually enjoy the strong security net of the browser - +the risks associated with the code we write are relatively small. Our websites +are granted limited powers in a sandbox, and we trust that our users enjoy a +browser built by a large team of engineers that is able to quickly respond to +newly discovered security threats. When working with Electron, it is important to understand that Electron is not a web browser. It allows you to build feature-rich desktop applications with familiar web technologies, but your code wields much greater power. JavaScript can access the filesystem, user shell, and more. This allows you to build -high quality native applications, but the inherent security risks scale with the -additional powers granted to your code. +high quality native applications, but the inherent security risks scale with +the additional powers granted to your code. With that in mind, be aware that displaying arbitrary content from untrusted sources poses a severe security risk that Electron is not intended to handle. @@ -34,8 +34,8 @@ contributions available today, Electron will often not be on the very latest version of Chromium, lagging behind by either days or weeks. We feel that our current system of updating the Chromium component strikes an -appropriate balance between the resources we have available and the needs of the -majority of applications built on top of the framework. We definitely are +appropriate balance between the resources we have available and the needs of +the majority of applications built on top of the framework. We definitely are interested in hearing more about specific use cases from the people that build things on top of Electron. Pull requests and contributions supporting this effort are always very welcome. @@ -44,41 +44,500 @@ effort are always very welcome. A security issue exists whenever you receive code from a remote destination and execute it locally. As an example, consider a remote website being displayed -inside a browser window. If an attacker somehow manages to change said content -(either by attacking the source directly, or by sitting between your app and -the actual destination), they will be able to execute native code on the user's -machine. +inside a [`BrowserWindow`][browser-window]. If an attacker somehow manages to +change said content (either by attacking the source directly, or by sitting +between your app and the actual destination), they will be able to execute +native code on the user's machine. > :warning: Under no circumstances should you load and execute remote code with -Node integration enabled. Instead, use only local files (packaged together with -your application) to execute Node code. To display remote content, use the -`webview` tag and make sure to disable the `nodeIntegration`. +Node.js integration enabled. Instead, use only local files (packaged together +with your application) to execute Node.js code. To display remote content, use +the [`webview`][web-view] tag and make sure to disable the `nodeIntegration`. -#### Checklist +## Electron Security Warnings -This is not bulletproof, but at the least, you should attempt the following: +From Electron 2.0 on, developers will see warnings and recommendations printed +to the developer console. They only show up when the binary's name is Electron, +indicating that a developer is currently looking at the console. -* Only display secure (https) content -* Disable the Node integration in all renderers that display remote content - (setting `nodeIntegration` to `false` in `webPreferences`) -* Enable context isolation in all renderers that display remote content - (setting `contextIsolation` to `true` in `webPreferences`) -* Use `ses.setPermissionRequestHandler()` in all sessions that load remote content -* Do not disable `webSecurity`. Disabling it will disable the same-origin policy. -* Define a [`Content-Security-Policy`](http://www.html5rocks.com/en/tutorials/security/content-security-policy/) -, and use restrictive rules (i.e. `script-src 'self'`) -* [Override and disable `eval`](https://github.com/nylas/N1/blob/0abc5d5defcdb057120d726b271933425b75b415/static/index.js#L6-L8) -, which allows strings to be executed as code. -* Do not set `allowRunningInsecureContent` to true. -* Do not enable `experimentalFeatures` or `experimentalCanvasFeatures` unless - you know what you're doing. -* Do not use `blinkFeatures` unless you know what you're doing. -* WebViews: Do not add the `nodeintegration` attribute. -* WebViews: Do not use `disablewebsecurity` -* WebViews: Do not use `allowpopups` -* WebViews: Do not use `insertCSS` or `executeJavaScript` with remote CSS/JS. -* WebViews: Verify the options and params of all `` tags before they - get attached using the `will-attach-webview` event: +You can force-enable or force-disable these warnings by setting +`ELECTRON_ENABLE_SECURITY_WARNINGS` or `ELECTRON_DISABLE_SECURITY_WARNINGS` on +either `process.env` or the `window` object. + +## Checklist: Security Recommendations + +This is not bulletproof, but at the least, you should follow these steps to +improve the security of your application. + +1. [Only load secure content](#only-load-secure-content) +2. [Disable the Node.js integration in all renderers that display remote content](#disable-node.js-integration-for-remote-content) +3. [Enable context isolation in all renderers that display remote content](#enable-context-isolation-for-remote-content) +4. [Use `ses.setPermissionRequestHandler()` in all sessions that load remote content](#handle-session-permission-requests-from-remote-content) +5. [Do not disable `webSecurity`](#do-not-disable-websecurity) +6. [Define a `Content-Security-Policy`](#define-a-content-security-policy) and use restrictive rules (i.e. `script-src 'self'`) +7. [Override and disable `eval`](#override-and-disable-eval), which allows strings to be executed as code. +8. [Do not set `allowRunningInsecureContent` to `true`](#do-not-set-allowRunningInsecureContent-to-true) +9. [Do not enable experimental features](#do-not-enable-experimental-features) +10. [Do not use `blinkFeatures`](#do-not-use-blinkfeatures) +11. [WebViews: Do not use `allowpopups`](#do-not-use-allowpopups) +12. [WebViews: Verify the options and params of all `` tags](#verify-webview-options-before-creation) + + +## 1) Only Load Secure Content + +Any resources not included with your application should be loaded using a +secure protocol like `HTTPS`. In other words, do not use insecure protocols +like `HTTP`. Similarly, we recommend the use of `WSS` over `WS`, `FTPS` over +`FTP`, and so on. + +### Why? + +`HTTPS` has three main benefits: + +1) It authenticates the remote server, ensuring your app connects to the correct + host instead of an impersonator. +2) It ensures data integrity, asserting that the data was not modified while in + transit between your application and the host. +3) It encrypts the traffic between your user and the destination host, making it + more difficult to eavesdrop on the information sent between your app and + the host. + +### How? + +```js +// Bad +browserWindow.loadURL('http://my-website.com') + +// Good +browserWindow.loadURL('https://my-website.com') +``` + +```html + + + + + + + +``` + + +## 2) Disable Node.js Integration for Remote Content + +It is paramount that you disable Node.js integration in any renderer +([`BrowserWindow`][browser-window], [`BrowserView`][browser-view], or +[`WebView`][web-view]) that loads remote content. The goal is to limit the +powers you grant to remote content, thus making it dramatically more difficult +for an attacker to harm your users should they gain the ability to execute +JavaScript on your website. + +After this, you can grant additional permissions for specific hosts. For example, +if you are opening a BrowserWindow pointed at `https://my-website.com/", you can +give that website exactly the abilities it needs, but no more. + +### Why? + +A cross-site-scripting (XSS) attack is more dangerous if an attacker can jump +out of the renderer process and execute code on the user's computer. +Cross-site-scripting attacks are fairly common - and while an issue, their +power is usually limited to messing with the website that they are executed on. +Disabling Node.js integration helps prevent an XSS from being escalated into a +so-called "Remote Code Execution" (RCE) attack. + +### How? + +```js +// Bad +const mainWindow = new BrowserWindow() +mainWindow.loadURL('https://my-website.com') +``` + +```js +// Good +const mainWindow = new BrowserWindow({ + webPreferences: { + nodeIntegration: false, + preload: './preload.js' + } +}) + +mainWindow.loadURL('https://my-website.com') +``` + +```html + + + + + +``` + +When disabling Node.js integration, you can still expose APIs to your website that +do consume Node.js modules or features. Preload scripts continue to have access +to `require` and other Node.js features, allowing developers to expose a custom +API to remotely loaded content. + +In the following example preload script, the later loaded website will have +access to a `window.readConfig()` method, but no Node.js features. + +```js +const { readFileSync } = require('fs') + +window.readConfig = function () { + const data = readFileSync('./config.json') + return data +} +``` + + +## 3) Enable Context Isolation for Remote Content + +Context isolation is an Electron feature that allows developers to run code +in preload scripts and in Electron APIs in a dedicated JavaScript context. In +practice, that means that global objects like `Array.prototype.push` or +`JSON.parse` cannot be modified by scripts running in the renderer process. + +Electron uses the same technology as Chromium's [Content Scripts](https://developer.chrome.com/extensions/content_scripts#execution-environment) +to enable this behavior. + +### Why? + +Context isolation allows each the scripts on running in the renderer to make +changes to its JavaScript environment without worrying about conflicting with +the scripts in the Electron API or the preload script. + +While still an experimental Electron feature, context isolation adds an +additional layer of security. It creates a new JavaScript world for Electron +APIs and preload scripts. + +At the same time, preload scripts still have access to the `document` and +`window` objects. In other words, you're getting a decent return on a likely +very small investment. + +### How? + +```js +// Main process +const mainWindow = new BrowserWindow({ + webPreferences: { + contextIsolation: true, + preload: 'preload.js' + } +}) +``` + +```js +// Preload script + +// Set a variable in the page before it loads +webFrame.executeJavaScript('window.foo = "foo";') + +// The loaded page will not be able to access this, it is only available +// in this context +window.bar = 'bar' + +document.addEventListener('DOMContentLoaded', () => { + // Will log out 'undefined' since window.foo is only available in the main + // context + console.log(window.foo) + + // Will log out 'bar' since window.bar is available in this context + console.log(window.bar) +}) +``` + + +## 4) Handle Session Permission Requests From Remote Content + +You may have seen permission requests while using Chrome: They pop up whenever +the website attempts to use a feature that the user has to manually approve ( +like notifications). + +The API is based on the [Chromium permissions API](https://developer.chrome.com/extensions/permissions) +and implements the same types of permissions. + +### Why? + +By default, Electron will automatically approve all permission requests unless +the developer has manually configured a custom handler. While a solid default, +security-conscious developers might want to assume the very opposite. + +### How? + +```js +const { session } = require('electron') + +session + .fromPartition('some-partition') + .setPermissionRequestHandler((webContents, permission, callback) => { + const url = webContents.getURL() + + if (permission === 'notifications') { + // Approves the permissions request + callback(true) + } + + if (!url.startsWith('https://my-website.com')) { + // Denies the permissions request + return callback(false) + } + }) +``` + + +## 5) Do Not Disable WebSecurity + +_Recommendation is Electron's default_ + +You may have already guessed that disabling the `webSecurity` property on a +renderer process ([`BrowserWindow`][browser-window], +[`BrowserView`][browser-view], or [`WebView`][web-view]) disables crucial +security features. + +Do not disable `webSecurity` in production applications. + +### Why? + +Disabling `webSecurity` will disable the same-origin policy and set +`allowRunningInsecureContent` property to `true`. In other words, it allows +the execution of insecure code from different domains. + +### How? +```js +// Bad +const mainWindow = new BrowserWindow({ + webPreferences: { + webSecurity: false + } +}) +``` + +```js +// Good +const mainWindow = new BrowserWindow() +``` + +```html + + + + + +``` + + +## 6) Define a Content Security Policy + +A Content Security Policy (CSP) is an additional layer of protection against +cross-site-scripting attacks and data injection attacks. We recommend that they +be enabled by any website you load inside Electron. + +### Why? + +CSP allows the server serving content to restrict and control the resources +Electron can load for that given web page. `https://your-page.com` should +be allowed to load scripts from the origins you defined while scripts from +`https://evil.attacker.com` should not be allowed to run. Defining a CSP is an +easy way to improve your applications security. + +### How? + +Electron respects [the `Content-Security-Policy` HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy) +and the respective `` tag. + +The following CSP will allow Electron to execute scripts from the current +website and from `apis.mydomain.com`. + +```txt +// Bad +Content-Security-Policy: '*' + +// Good +Content-Security-Policy: script-src 'self' https://apis.mydomain.com +``` + + +## 7) Override and Disable `eval` + +`eval()` is a core JavaScript method that allows the execution of JavaScript +from a string. Disabling it disables your app's ability to evaluate JavaScript +that is not known in advance. + +### Why? + +The `eval()` method has precisely one mission: To evaluate a series of +characters as JavaScript and execute it. It is a required method whenever you +need to evaluate code that is not known ahead of time. While legitimate use +cases exist, just like any other code generators, `eval()` is difficult to +harden. + +Generally speaking, it is easier to completely disable `eval()` than to make +it bulletproof. Thus, if you do not need it, it is a good idea to disable it. + +### How? + +```js +// ESLint will warn about any use of eval(), even this one +// eslint-disable-next-line +window.eval = global.eval = function () { + throw new Error(`Sorry, this app does not support window.eval().`) +} +``` + + +## 8) Do Not Set `allowRunningInsecureContent` to `true` + +_Recommendation is Electron's default_ + +By default, Electron will now allow websites loaded over `HTTPS` to load and +execute scripts, CSS, or plugins from insecure sources (`HTTP`). Setting the +property `allowRunningInsecureContent` to `true` disables that protection. + +Loading the initial HTML of a website over `HTTPS` and attempting to load +subsequent resources via `HTTP` is also known as "mixed content". + +### Why? + +Simply put, loading content over `HTTPS` assures the authenticity and integrity +of the loaded resources while encrypting the traffic itself. See the section on +[only displaying secure content](#only-display-secure-content) for more details. + +### How? + +```js +// Bad +const mainWindow = new BrowserWindow({ + webPreferences: { + allowRunningInsecureContent: true + } +}) +``` + +```js +// Good +const mainWindow = new BrowserWindow({}) +``` + + +## 9) Do Not Enable Experimental Features + +_Recommendation is Electron's default_ + +Advanced users of Electron can enable experimental Chromium features using the +`experimentalFeatures` and `experimentalCanvasFeatures` properties. + +### Why? + +Experimental features are, as the name suggests, experimental and have not been +enabled for all Chromium users. Furthermore, their impact on Electron as a whole +has likely not been tested. + +Legitimate use cases exist, but unless you know what you are doing, you should +not enable this property. + +### How? + +```js +// Bad +const mainWindow = new BrowserWindow({ + webPreferences: { + experimentalFeatures: true + } +}) +``` + +```js +// Good +const mainWindow = new BrowserWindow({}) +``` + + +## 10) Do Not Use `blinkFeatures` + +_Recommendation is Electron's default_ + +Blink is the name of the rendering engine behind Chromium. As with +`experimentalFeatures`, the `blinkFeatures` property allows developers to +enable features that have been disabled by default. + +### Why? + +Generally speaking, there are likely good reasons if a feature was not enabled +by default. Legitimate use cases for enabling specific features exist. As a +developer, you should know exactly why you need to enable a feature, what the +ramifications are, and how it impacts the security of your application. Under +no circumstances should you enable features speculatively. + +### How? +```js +// Bad +const mainWindow = new BrowserWindow({ + webPreferences: { + blinkFeatures: ['ExecCommandInJavaScript'] + } +}) +``` + +```js +// Good +const mainWindow = new BrowserWindow() +``` + + +## 11) Do Not Use `allowpopups` + +_Recommendation is Electron's default_ + +If you are using [`WebViews`][web-view], you might need the pages and scripts +loaded in your `` tag to open new windows. The `allowpopups` attribute +enables them to create new [`BrowserWindows`][browser-window] using the +`window.open()` method. `WebViews` are otherwise not allowed to create new +windows. + +### Why? + +If you do not need popups, you are better off not allowing the creation of +new [`BrowserWindows`][browser-window] by default. This follows the principle +of minimally required access: Don't let a website create new popups unless +you know it needs that feature. + +### How? + +```html + + + + + +``` + + +## 12) Verify WebView Options Before Creation + +A WebView created in a renderer process that does not have Node.js integration +enabled will not be able to enable integration itself. However, a WebView will +always create an independent renderer process with its own `webPreferences`. + +It is a good idea to control the creation of new [`WebViews`][web-view] from +the main process and to verify that their webPreferences do not disable +security features. + +### Why? + +Since WebViews live in the DOM, they can be created by a script running on your +website even if Node.js integration is otherwise disabled. + +Electron enables developers to disable various security features that control +a renderer process. In most cases, developers do not need to disable any of +those features - and you should therefore not allow different configurations +for newly created [``][web-view] tags. + +### How? + +Before a [``][web-view] tag is attached, Electron will fire the +`will-attach-webview` event on the hosting `webContents`. Use the event to +prevent the creation of WebViews with possibly insecure options. ```js app.on('web-contents-created', (event, contents) => { @@ -87,7 +546,7 @@ app.on('web-contents-created', (event, contents) => { delete webPreferences.preload delete webPreferences.preloadURL - // Disable node integration + // Disable Node.js integration webPreferences.nodeIntegration = false // Verify URL being loaded @@ -100,3 +559,7 @@ app.on('web-contents-created', (event, contents) => { Again, this list merely minimizes the risk, it does not remove it. If your goal is to display a website, a browser will be a more secure option. + +[browser-window]: ../api/browser-window.md +[browser-view]: ../api/browser-view.md +[web-view]: ../api/web-view.md diff --git a/docs/tutorial/snapcraft.md b/docs/tutorial/snapcraft.md new file mode 100644 index 00000000000..b7b430bdd69 --- /dev/null +++ b/docs/tutorial/snapcraft.md @@ -0,0 +1,184 @@ +# Snapcraft Guide (Ubuntu Software Center & More) + +This guide provides information on how to package your Electron application +for any Snapcraft environment, including the Ubuntu Software Center. + +## Background and Requirements + +Together with the broader Linux community, Canonical aims to fix many of the +common software installation problems with the [`snapcraft`](https://snapcraft.io/) +project. Snaps are containerized software packages that include required +dependencies, auto-update, and work on all major Linux distributions without +system modification. + +There are three ways to create a `.snap` file: + +1) Using [`electron-forge`][electron-forge] or + [`electron-builder`][electron-builder], both tools that come with `snap` + support out of the box. This is the easiest option. +2) Using `electron-installer-snap`, which takes `electron-packager`'s output. +3) Using an already created `.deb` package. + +In all cases, you will need to have the `snapcraft` tool installed. We +recommend building on Ubuntu 16.04 (or the current LTS). + +```sh +snap install snapcraft --classic +``` + +While it _is possible_ to install `snapcraft` on macOS using Homebrew, it +is not able to build `snap` packages and is focused on managing packages +in the store. + +## Using `electron-installer-snap` + +The module works like [`electron-winstaller`][electron-winstaller] and similar +modules in that its scope is limited to building snap packages. You can install +it with: + +```sh +npm install --save-dev electron-installer-snap +``` + +### Step 1: Package Your Electron Application + +Package the application using [electron-packager][electron-packager] (or a +similar tool). Make sure to remove `node_modules` that you don't need in your +final application, since any module you don't actually need will just increase +your application's size. + +The output should look roughly like this: + +```text +. +โ””โ”€โ”€ dist + โ””โ”€โ”€ app-linux-x64 + โ”œโ”€โ”€ LICENSE + โ”œโ”€โ”€ LICENSES.chromium.html + โ”œโ”€โ”€ content_shell.pak + โ”œโ”€โ”€ app + โ”œโ”€โ”€ icudtl.dat + โ”œโ”€โ”€ libgcrypt.so.11 + โ”œโ”€โ”€ libnode.so + โ”œโ”€โ”€ locales + โ”œโ”€โ”€ natives_blob.bin + โ”œโ”€โ”€ resources + โ”œโ”€โ”€ snapshot_blob.bin + โ””โ”€โ”€ version +``` + +### Step 2: Running `electron-installer-snap` + +From a terminal that has `snapcraft` in its `PATH`, run `electron-installer-snap` +with the only required parameter `--src`, which is the location of your packaged +Electron application created in the first step. + +```sh +npx electron-installer-snap --src=out/myappname-linux-x64 +``` + +If you have an existing build pipeline, you can use `electron-installer-snap` +programmatically. For more information, see the [Snapcraft API docs][snapcraft-syntax]. + +```js +const snap = require('electron-installer-snap') + +snap(options) + .then(snapPath => console.log(`Created snap at ${snapPath}!`)) +``` + +## Using an Existing Debian Package + +Snapcraft is capable of taking an existing `.deb` file and turning it into +a `.snap` file. The creation of a snap is configured using a `snapcraft.yaml` +file that describes the sources, dependencies, description, and other core +building blocks. + +### Step 1: Create a Debian Package + +If you do not already have a `.deb` package, using `electron-installer-snap` +might be an easier path to create snap packages. However, multiple solutions +for creating Debian packages exist, including [`electron-forge`][electron-forge], +[`electron-builder`][electron-builder] or +[`electron-installer-debian`][electron-installer-debian]. + +### Step 2: Create a snapcraft.yaml + +For more information on the available configuration options, see the +[documentation on the snapcraft syntax][snapcraft-syntax]. +Let's look at an example: + +```yaml +name: myApp +version: '2.0.0' +summary: A little description for the app. +description: | + You know what? This app is amazing! It does all the things + for you. Some say it keeps you young, maybe even happy. + +grade: stable +confinement: classic + +parts: + slack: + plugin: dump + source: my-deb.deb + source-type: deb + after: + - desktop-gtk3 + stage-packages: + - libasound2 + - libgconf2-4 + - libnotify4 + - libnspr4 + - libnss3 + - libpcre3 + - libpulse0 + - libxss1 + - libxtst6 + electron-launch: + plugin: dump + source: files/ + prepare: | + chmod +x bin/electron-launch + +apps: + myApp: + command: bin/electron-launch $SNAP/usr/lib/myApp/myApp + desktop: usr/share/applications/myApp.desktop + # Correct the TMPDIR path for Chromium Framework/Electron to ensure + # libappindicator has readable resources. + environment: + TMPDIR: $XDG_RUNTIME_DIR +``` + +As you can see, the `snapcraft.yaml` instructs the system to launch a file +called `electron-launch`. In this example, it simply passes information on +to the app's binary: + +```sh +#!/bin/sh + +exec "$@" --executed-from="$(pwd)" --pid=$$ > /dev/null 2>&1 & +``` + +Alternatively, if you're building your `snap` with `strict` confinement, you +can use the `desktop-launch` command: + +```yaml +apps: + myApp: + # Correct the TMPDIR path for Chromium Framework/Electron to ensure + # libappindicator has readable resources. + command: env TMPDIR=$XDG_RUNTIME_DIR PATH=/usr/local/bin:${PATH} ${SNAP}/bin/desktop-launch $SNAP/myApp/desktop + desktop: usr/share/applications/desktop.desktop +``` + +[snapcraft.io]: https://snapcraft.io/ +[snapcraft-store]: https://snapcraft.io/store/ +[snapcraft-syntax]: https://docs.snapcraft.io/build-snaps/syntax +[electron-packager]: https://github.com/electron-userland/electron-packager +[electron-forge]: https://github.com/electron-userland/electron-forge +[electron-builder]: https://github.com/electron-userland/electron-builder +[electron-installer-debian]: https://github.com/unindented/electron-installer-debian +[electron-winstaller]: https://github.com/electron/windows-installer diff --git a/docs/tutorial/supported-platforms.md b/docs/tutorial/supported-platforms.md index 7474cec466c..5cf59f33b82 100644 --- a/docs/tutorial/supported-platforms.md +++ b/docs/tutorial/supported-platforms.md @@ -18,9 +18,13 @@ Please note, the `ARM` version of Windows is not supported for now. ### Linux The prebuilt `ia32` (`i686`) and `x64` (`amd64`) binaries of Electron are built on -Ubuntu 12.04, the `arm` binary is built against ARM v7 with hard-float ABI and +Ubuntu 12.04, the `armv7l` binary is built against ARM v7 with hard-float ABI and NEON for Debian Wheezy. +[Until the release of Electron 2.0][arm-breaking-change], Electron will also +continue to release the `armv7l` binary with a simple `arm` suffix. Both binaries +are identical. + Whether the prebuilt binary can run on a distribution depends on whether the distribution includes the libraries that Electron is linked to on the building platform, so only Ubuntu 12.04 is guaranteed to work, but following platforms @@ -29,3 +33,5 @@ are also verified to be able to run the prebuilt binaries of Electron: * Ubuntu 12.04 and later * Fedora 21 * Debian 8 + +[arm-breaking-change]: https://github.com/electron/electron/blob/master/docs/tutorial/planned-breaking-changes.md#duplicate-arm-assets diff --git a/docs/tutorial/updates.md b/docs/tutorial/updates.md index 623d25b19ea..6deb8c3804e 100644 --- a/docs/tutorial/updates.md +++ b/docs/tutorial/updates.md @@ -1,52 +1,54 @@ # Updating Applications -There are several ways to update an Electron application. The easiest and -officially supported one is taking advantage of the built-in -[Squirrel](https://github.com/Squirrel) framework and +There are several ways to update an Electron application. The easiest and +officially supported one is taking advantage of the built-in +[Squirrel](https://github.com/Squirrel) framework and Electron's [autoUpdater](../api/auto-updater.md) module. -## Deploying an update server +## Deploying an Update Server -To get started, you first need to deploy a server that the +To get started, you first need to deploy a server that the [autoUpdater](../api/auto-updater.md) module will download new updates from. Depending on your needs, you can choose from one of these: -- [Hazel](https://github.com/zeit/hazel) โ€“ Simple update server for open-source -apps. Pulls from -[GitHub Releases](https://help.github.com/articles/creating-releases/) -and can be deployed for free on [Now](https://zeit.co/now). -- [Nuts](https://github.com/GitbookIO/nuts) โ€“ Also uses -[GitHub Releases](https://help.github.com/articles/creating-releases/), -but caches app updates on disk and supports private repositories. -- [electron-release-server](https://github.com/ArekSredzki/electron-release-server) โ€“ -Provides a dashboard for handling releases -- [Nucleus](https://github.com/atlassian/nucleus) - A complete update server for Electron apps maintained by Atlassian. Supports multiple applications and channels; uses a static file store to minify server cost. +- [Hazel][hazel] โ€“ Update server for private or open-source apps which can be +deployed for free on [Now][now]. It pulls from [GitHub Releases][gh-releases] +and leverages the power of GitHub's CDN. +- [Nuts][nuts] โ€“ Also uses [GitHub Releases][gh-releases], but caches app +updates on disk and supports private repositories. +- [electron-release-server][electron-release-server] โ€“ Provides a dashboard for +handling releases and does not require releases to originate on GitHub. +- [Nucleus][nucleus] โ€“ A complete update server for Electron apps maintained by +Atlassian. Supports multiple applications and channels; uses a static file store +to minify server cost. -If your app is packaged with [electron-builder][electron-builder-lib] you can use the -[electron-updater] module, which does not require a server and allows for updates -from S3, GitHub or any other static file host. +If your app is packaged with [`electron-builder`][electron-builder-lib] you can use the +[electron-updater] module, which does not require a server and allows for updates +from S3, GitHub or any other static file host. This sidesteps Electron's built-in +update mechanism, meaning that the rest of this documentation will not apply to +`electron-builder`'s updater. -## Implementing updates in your app +## Implementing Updates in Your App -Once you've deployed your update server, continue with importing the required -modules in your code. The following code might vary for different server -software, but it works like described when using +Once you've deployed your update server, continue with importing the required +modules in your code. The following code might vary for different server +software, but it works like described when using [Hazel](https://github.com/zeit/hazel). -**Important:** Please ensure that the code below will only be executed in -your packaged app, and not in development. You can use -[electron-is-dev](https://github.com/sindresorhus/electron-is-dev) to check for +**Important:** Please ensure that the code below will only be executed in +your packaged app, and not in development. You can use +[electron-is-dev](https://github.com/sindresorhus/electron-is-dev) to check for the environment. -```js -const {app, autoUpdater, dialog} = require('electron') +```javascript +const { app, autoUpdater, dialog } = require('electron') ``` -Next, construct the URL of the update server and tell +Next, construct the URL of the update server and tell [autoUpdater](../api/auto-updater.md) about it: -```js +```javascript const server = 'https://your-deployment-url.com' const feed = `${server}/update/${process.platform}/${app.getVersion()}` @@ -55,25 +57,25 @@ autoUpdater.setFeedURL(feed) As the final step, check for updates. The example below will check every minute: -```js +```javascript setInterval(() => { autoUpdater.checkForUpdates() }, 60000) ``` -Once your application is [packaged](../tutorial/application-distribution.md), -it will receive an update for each new -[GitHub Release](https://help.github.com/articles/creating-releases/) that you +Once your application is [packaged](../tutorial/application-distribution.md), +it will receive an update for each new +[GitHub Release](https://help.github.com/articles/creating-releases/) that you publish. -## Applying updates +## Applying Updates -Now that you've configured the basic update mechanism for your application, you +Now that you've configured the basic update mechanism for your application, you need to ensure that the user will get notified when there's an update. This -can be achieved using the autoUpdater API +can be achieved using the autoUpdater API [events](../api/auto-updater.md#events): -```js +```javascript autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => { const dialogOpts = { type: 'info', @@ -89,11 +91,11 @@ autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => { }) ``` -Also make sure that errors are +Also make sure that errors are [being handled](../api/auto-updater.md#event-error). Here's an example for logging them to `stderr`: -```js +```javascript autoUpdater.on('error', message => { console.error('There was a problem updating the application') console.error(message) @@ -102,3 +104,9 @@ autoUpdater.on('error', message => { [electron-builder-lib]: https://github.com/electron-userland/electron-builder [electron-updater]: https://www.electron.build/auto-update +[now]: https://zeit.co/now +[hazel]: https://github.com/zeit/hazel +[nuts]: https://github.com/GitbookIO/nuts +[gh-releases]: https://help.github.com/articles/creating-releases/ +[electron-release-server]: https://github.com/ArekSredzki/electron-release-server +[nucleus]: https://github.com/atlassian/nucleus diff --git a/docs/tutorial/using-pepper-flash-plugin.md b/docs/tutorial/using-pepper-flash-plugin.md index 839f00b520f..42ead0d6949 100644 --- a/docs/tutorial/using-pepper-flash-plugin.md +++ b/docs/tutorial/using-pepper-flash-plugin.md @@ -63,7 +63,7 @@ the plugins yourself, its path can be received by calling Add `plugins` attribute to `` tag. ```html - + ``` ## Troubleshooting diff --git a/docs/tutorial/using-selenium-and-webdriver.md b/docs/tutorial/using-selenium-and-webdriver.md index eb989c0ff1c..f6fdd539a3c 100644 --- a/docs/tutorial/using-selenium-and-webdriver.md +++ b/docs/tutorial/using-selenium-and-webdriver.md @@ -169,4 +169,4 @@ your app's folder. This eliminates the need to copy-paste your app into Electron's resource directory. [chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ -[spectron]: https://electron.atom.io/spectron +[spectron]: https://electronjs.org/spectron diff --git a/docs/tutorial/windows-store-guide.md b/docs/tutorial/windows-store-guide.md index 436549638c1..5068d85d034 100644 --- a/docs/tutorial/windows-store-guide.md +++ b/docs/tutorial/windows-store-guide.md @@ -151,7 +151,7 @@ You will receive two files: `DesktopAppConverter.zip` and `BaseImage-14316.wim`. Once installation succeeded, you can move on to compiling your Electron app. [windows-sdk]: https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk -[app-converter]: https://www.microsoft.com/en-us/download/details.aspx?id=51691 +[app-converter]: https://docs.microsoft.com/en-us/windows/uwp/porting/desktop-to-uwp-run-desktop-app-converter [add-appxpackage]: https://technet.microsoft.com/en-us/library/hh856048.aspx [electron-packager]: https://github.com/electron-userland/electron-packager [electron-windows-store]: https://github.com/catalystcode/electron-windows-store diff --git a/docs/tutorial/windows-taskbar.md b/docs/tutorial/windows-taskbar.md new file mode 100644 index 00000000000..1e539830eb4 --- /dev/null +++ b/docs/tutorial/windows-taskbar.md @@ -0,0 +1,185 @@ +# Windows Taskbar + +Electron has APIs to configure the app's icon in the Windows taskbar. Supported +are the [creation of a `JumpList`](#jumplist), +[custom thumbnails and toolbars](#thumbnail-toolbars), +[icon overlays](#icon-overlays-in-taskbar-windows), and the so-called +["Flash Frame" effect](#flash-frame), but +Electron also uses the app's dock icon to implement cross-platform features +like [recent documents][recent-documents] and +[application progress][progress-bar]. + +## JumpList + +Windows allows apps to define a custom context menu that shows up when users +right-click the app's icon in the task bar. That context menu is called +`JumpList`. You specify custom actions in the `Tasks` category of JumpList, +as quoted from MSDN: + +> Applications define tasks based on both the program's features and the key +> things a user is expected to do with them. Tasks should be context-free, in +> that the application does not need to be running for them to work. They +> should also be the statistically most common actions that a normal user would +> perform in an application, such as compose an email message or open the +> calendar in a mail program, create a new document in a word processor, launch +> an application in a certain mode, or launch one of its subcommands. An +> application should not clutter the menu with advanced features that standard +> users won't need or one-time actions such as registration. Do not use tasks +> for promotional items such as upgrades or special offers. +> +> It is strongly recommended that the task list be static. It should remain the +> same regardless of the state or status of the application. While it is +> possible to vary the list dynamically, you should consider that this could +> confuse the user who does not expect that portion of the destination list to +> change. + +__Tasks of Internet Explorer:__ + +![IE](http://i.msdn.microsoft.com/dynimg/IC420539.png) + +Unlike the dock menu in macOS which is a real menu, user tasks in Windows work +like application shortcuts such that when user clicks a task, a program will be +executed with specified arguments. + +To set user tasks for your application, you can use +[app.setUserTasks][setusertaskstasks] API: + +```javascript +const { app } = require('electron') +app.setUserTasks([ + { + program: process.execPath, + arguments: '--new-window', + iconPath: process.execPath, + iconIndex: 0, + title: 'New Window', + description: 'Create a new window' + } +]) +``` + +To clean your tasks list, just call `app.setUserTasks` with an empty array: + +```javascript +const { app } = require('electron') +app.setUserTasks([]) +``` + +The user tasks will still show even after your application closes, so the icon +and program path specified for a task should exist until your application is +uninstalled. + + +## Thumbnail Toolbars + +On Windows you can add a thumbnail toolbar with specified buttons in a taskbar +layout of an application window. It provides users a way to access to a +particular window's command without restoring or activating the window. + +From MSDN, it's illustrated: + +> This toolbar is simply the familiar standard toolbar common control. It has a +> maximum of seven buttons. Each button's ID, image, tooltip, and state are defined +> in a structure, which is then passed to the taskbar. The application can show, +> enable, disable, or hide buttons from the thumbnail toolbar as required by its +> current state. +> +> For example, Windows Media Player might offer standard media transport controls +> such as play, pause, mute, and stop. + +__Thumbnail toolbar of Windows Media Player:__ + +![player](https://i-msdn.sec.s-msft.com/dynimg/IC420540.png) + +You can use [BrowserWindow.setThumbarButtons][setthumbarbuttons] to set +thumbnail toolbar in your application: + +```javascript +const { BrowserWindow } = require('electron') +const path = require('path') + +const win = new BrowserWindow() + +win.setThumbarButtons([ + { + tooltip: 'button1', + icon: path.join(__dirname, 'button1.png'), + click () { console.log('button1 clicked') } + }, { + tooltip: 'button2', + icon: path.join(__dirname, 'button2.png'), + flags: ['enabled', 'dismissonclick'], + click () { console.log('button2 clicked.') } + } +]) +``` + +To clean thumbnail toolbar buttons, just call `BrowserWindow.setThumbarButtons` +with an empty array: + +```javascript +const { BrowserWindow } = require('electron') + +const win = new BrowserWindow() +win.setThumbarButtons([]) +``` + + +## Icon Overlays in Taskbar + +On Windows a taskbar button can use a small overlay to display application +status, as quoted from MSDN: + +> Icon overlays serve as a contextual notification of status, and are intended +> to negate the need for a separate notification area status icon to communicate +> that information to the user. For instance, the new mail status in Microsoft +> Outlook, currently shown in the notification area, can now be indicated +> through an overlay on the taskbar button. Again, you must decide during your +> development cycle which method is best for your application. Overlay icons are +> intended to supply important, long-standing status or notifications such as +> network status, messenger status, or new mail. The user should not be +> presented with constantly changing overlays or animations. + +__Overlay on taskbar button:__ + +![Overlay on taskbar button](https://i-msdn.sec.s-msft.com/dynimg/IC420441.png) + +To set the overlay icon for a window, you can use the +[BrowserWindow.setOverlayIcon][setoverlayicon] API: + +```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() +win.setOverlayIcon('path/to/overlay.png', 'Description for overlay') +``` + + +## Flash Frame + +On Windows you can highlight the taskbar button to get the user's attention. +This is similar to bouncing the dock icon on macOS. +From the MSDN reference documentation: + +> Typically, a window is flashed to inform the user that the window requires +> attention but that it does not currently have the keyboard focus. + +To flash the BrowserWindow taskbar button, you can use the +[BrowserWindow.flashFrame][flashframe] API: + +```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() +win.once('focus', () => win.flashFrame(false)) +win.flashFrame(true) +``` + +Don't forget to call the `flashFrame` method with `false` to turn off the flash. In +the above example, it is called when the window comes into focus, but you might +use a timeout or some other event to disable it. + +[setthumbarbuttons]: ../api/browser-window.md#winsetthumbarbuttonsbuttons-windows +[setusertaskstasks]: ../api/app.md#appsetusertaskstasks-windows +[setoverlayicon]: ../api/browser-window.md#winsetoverlayiconoverlay-description-windows +[flashframe]: ../api/browser-window.md#winflashframeflag +[recent-documents]: ./recent-documents.md +[progress-bar]: ./progress-bar.md diff --git a/electron.gyp b/electron.gyp index 4f3554d88a9..e0391b0d8b6 100644 --- a/electron.gyp +++ b/electron.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '1.8.2-beta.2', + 'version%': '0.0.0-dev', 'js2c_input_dir': '<(SHARED_INTERMEDIATE_DIR)/js2c', }, 'includes': [ @@ -28,6 +28,11 @@ 'ENABLE_OSR', ], }], # enable_osr==1 + ['enable_run_as_node', { + 'defines': [ + 'ENABLE_RUN_AS_NODE', + ], + }], # enable_run_as_node ], }, 'targets': [ @@ -591,6 +596,7 @@ '$(SDKROOT)/System/Library/Frameworks/Security.framework', '$(SDKROOT)/System/Library/Frameworks/SecurityInterface.framework', '$(SDKROOT)/System/Library/Frameworks/ServiceManagement.framework', + '$(SDKROOT)/System/Library/Frameworks/StoreKit.framework', ], }, 'mac_bundle': 1, diff --git a/features.gypi b/features.gypi index f528452d48b..5aca98fc475 100644 --- a/features.gypi +++ b/features.gypi @@ -3,7 +3,9 @@ 'variables': { 'variables': { 'enable_osr%': 1, + 'enable_run_as_node%': 1, }, 'enable_osr%': '<(enable_osr)', + 'enable_run_as_node%': '<(enable_run_as_node)', }, } diff --git a/filenames.gypi b/filenames.gypi index 2559aeb6125..e051b4707e5 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -20,6 +20,7 @@ 'lib/browser/api/exports/electron.js', 'lib/browser/api/global-shortcut.js', 'lib/browser/api/ipc-main.js', + 'lib/browser/api/in-app-purchase.js', 'lib/browser/api/menu-item-roles.js', 'lib/browser/api/menu-item.js', 'lib/browser/api/menu.js', @@ -62,6 +63,7 @@ 'lib/renderer/init.js', 'lib/renderer/inspector.js', 'lib/renderer/override.js', + 'lib/renderer/security-warnings.js', 'lib/renderer/window-setup.js', 'lib/renderer/web-view/guest-view-internal.js', 'lib/renderer/web-view/web-view.js', @@ -98,8 +100,8 @@ 'atom/app/atom_main_delegate.cc', 'atom/app/atom_main_delegate.h', 'atom/app/atom_main_delegate_mac.mm', - 'atom/app/node_main.cc', - 'atom/app/node_main.h', + 'atom/app/command_line_args.cc', + 'atom/app/command_line_args.h', 'atom/app/uv_task_runner.cc', 'atom/app/uv_task_runner.h', 'atom/browser/api/atom_api_app.cc', @@ -120,6 +122,8 @@ 'atom/browser/api/atom_api_download_item.h', 'atom/browser/api/atom_api_global_shortcut.cc', 'atom/browser/api/atom_api_global_shortcut.h', + 'atom/browser/api/atom_api_in_app_purchase.cc', + 'atom/browser/api/atom_api_in_app_purchase.h', 'atom/browser/api/atom_api_menu.cc', 'atom/browser/api/atom_api_menu.h', 'atom/browser/api/atom_api_menu_mac.h', @@ -157,8 +161,10 @@ 'atom/browser/api/atom_api_web_request.cc', 'atom/browser/api/atom_api_web_request.h', 'atom/browser/api/atom_api_web_view_manager.cc', - 'atom/browser/api/atom_api_window.cc', - 'atom/browser/api/atom_api_window.h', + 'atom/browser/api/atom_api_browser_window.cc', + 'atom/browser/api/atom_api_browser_window.h', + 'atom/browser/api/atom_api_browser_window_mac.mm', + 'atom/browser/api/atom_api_browser_window_views.cc', 'atom/browser/api/event.cc', 'atom/browser/api/event.h', 'atom/browser/api/event_emitter.cc', @@ -218,6 +224,9 @@ 'atom/browser/javascript_environment.h', 'atom/browser/lib/bluetooth_chooser.cc', 'atom/browser/lib/bluetooth_chooser.h', + 'atom/browser/lib/power_observer.h', + 'atom/browser/lib/power_observer_linux.h', + 'atom/browser/lib/power_observer_linux.cc', 'atom/browser/loader/layered_resource_handler.cc', 'atom/browser/loader/layered_resource_handler.h', 'atom/browser/login_handler.cc', @@ -228,6 +237,10 @@ 'atom/browser/mac/atom_application_delegate.mm', 'atom/browser/mac/dict_util.h', 'atom/browser/mac/dict_util.mm', + 'atom/browser/mac/in_app_purchase.h', + 'atom/browser/mac/in_app_purchase.mm', + 'atom/browser/mac/in_app_purchase_observer.h', + 'atom/browser/mac/in_app_purchase_observer.mm', 'atom/browser/native_browser_view.cc', 'atom/browser/native_browser_view.h', 'atom/browser/native_browser_view_mac.h', @@ -250,8 +263,6 @@ 'atom/browser/net/asar/url_request_asar_job.h', 'atom/browser/net/atom_cert_verifier.cc', 'atom/browser/net/atom_cert_verifier.h', - 'atom/browser/net/atom_cookie_delegate.cc', - 'atom/browser/net/atom_cookie_delegate.h', 'atom/browser/net/atom_network_delegate.cc', 'atom/browser/net/atom_network_delegate.h', 'atom/browser/net/atom_url_request.cc', @@ -283,6 +294,8 @@ 'atom/browser/relauncher.h', 'atom/browser/render_process_preferences.cc', 'atom/browser/render_process_preferences.h', + 'atom/browser/session_preferences.cc', + 'atom/browser/session_preferences.h', 'atom/browser/ui/accelerator_util.cc', 'atom/browser/ui/accelerator_util.h', 'atom/browser/ui/accelerator_util_mac.mm', @@ -596,7 +609,6 @@ 'chromium_src/chrome/common/chrome_paths_linux.cc', 'chromium_src/chrome/common/chrome_paths_mac.mm', 'chromium_src/chrome/common/chrome_paths_win.cc', - 'chromium_src/chrome/common/chrome_utility_messages.h', 'chromium_src/chrome/common/pref_names.cc', 'chromium_src/chrome/common/pref_names.h', 'chromium_src/chrome/common/print_messages.cc', @@ -633,7 +645,6 @@ 'chromium_src/chrome/renderer/tts_dispatcher.cc', 'chromium_src/chrome/renderer/tts_dispatcher.h', 'chromium_src/chrome/utility/utility_message_handler.h', - 'chromium_src/components/pdf/common/pdf_messages.h', 'chromium_src/components/pdf/renderer/pepper_pdf_host.cc', 'chromium_src/components/pdf/renderer/pepper_pdf_host.h', 'chromium_src/extensions/browser/app_window/size_constraints.cc', @@ -721,6 +732,17 @@ 'atom/browser/osr/osr_view_proxy.h', ], }], # enable_osr==1 + ['enable_run_as_node', { + 'lib_sources': [ + 'atom/app/node_main.cc', + 'atom/app/node_main.h', + ], + }], # enable_run_as_node + ['mas_build==1', { + 'lib_sources': [ + 'atom/browser/api/atom_api_app_mas.mm', + ], + }], # mas_build==1 ], }, } diff --git a/lib/browser/api/app.js b/lib/browser/api/app.js index 381a2a86637..1ea1daddca8 100644 --- a/lib/browser/api/app.js +++ b/lib/browser/api/app.js @@ -10,6 +10,8 @@ const electron = require('electron') const {deprecate, Menu} = electron const {EventEmitter} = require('events') +let dockMenu = null + // App is an EventEmitter. Object.setPrototypeOf(App.prototype, EventEmitter.prototype) EventEmitter.call(app) @@ -49,7 +51,13 @@ if (process.platform === 'darwin') { hide: bindings.dockHide, show: bindings.dockShow, isVisible: bindings.dockIsVisible, - setMenu: bindings.dockSetMenu, + setMenu (menu) { + dockMenu = menu + bindings.dockSetMenu(menu) + }, + getMenu () { + return dockMenu + }, setIcon: bindings.dockSetIcon } } diff --git a/lib/browser/api/auto-updater/auto-updater-win.js b/lib/browser/api/auto-updater/auto-updater-win.js index fced1258797..7fead4aabb4 100644 --- a/lib/browser/api/auto-updater/auto-updater-win.js +++ b/lib/browser/api/auto-updater/auto-updater-win.js @@ -17,7 +17,19 @@ class AutoUpdater extends EventEmitter { return this.updateURL } - setFeedURL (updateURL, headers) { + setFeedURL (options) { + let updateURL + if (typeof options === 'object') { + if (typeof options.url === 'string') { + updateURL = options.url + } else { + throw new Error('Expected options object to contain a \'url\' string property in setFeedUrl call') + } + } else if (typeof options === 'string') { + updateURL = options + } else { + throw new Error('Expected an options object with a \'url\' property to be provided') + } this.updateURL = updateURL } diff --git a/lib/browser/api/auto-updater/squirrel-update-win.js b/lib/browser/api/auto-updater/squirrel-update-win.js index b0813d1fcdd..fecf7958cd0 100644 --- a/lib/browser/api/auto-updater/squirrel-update-win.js +++ b/lib/browser/api/auto-updater/squirrel-update-win.js @@ -8,19 +8,15 @@ 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) -var spawnedArgs = [] -var spawnedProcess +let spawnedArgs = [] +let spawnedProcess -var isSameArgs = function (args) { - return (args.length === spawnedArgs.length) && args.every(function (e, i) { - return e === spawnedArgs[i] - }) -} +const isSameArgs = (args) => args.length === spawnedArgs.length && args.every((e, i) => e === spawnedArgs[i]) // 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, stderr, stdout +let spawnUpdate = function (args, detached, callback) { + let error, errorEmitted, stderr, stdout try { // Ensure we don't spawn multiple squirrel processes @@ -33,7 +29,8 @@ var spawnUpdate = function (args, detached, callback) { return callback(`AutoUpdater process with arguments ${args} is already running`) } else if (!spawnedProcess) { spawnedProcess = spawn(updateExe, args, { - detached: detached + detached: detached, + windowsHide: true }) spawnedArgs = args || [] } @@ -48,17 +45,16 @@ var spawnUpdate = function (args, detached, callback) { } stdout = '' stderr = '' - spawnedProcess.stdout.on('data', function (data) { - stdout += data - }) - spawnedProcess.stderr.on('data', function (data) { - stderr += data - }) + + spawnedProcess.stdout.on('data', (data) => { stdout += data }) + spawnedProcess.stderr.on('data', (data) => { stderr += data }) + errorEmitted = false - spawnedProcess.on('error', function (error) { + spawnedProcess.on('error', (error) => { errorEmitted = true callback(error) }) + return spawnedProcess.on('exit', function (code, signal) { spawnedProcess = undefined spawnedArgs = [] diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index b977e8766f9..22565dcf987 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -169,6 +169,9 @@ Object.assign(BrowserWindow.prototype, { getURL (...args) { return this.webContents.getURL() }, + loadFile (filePath) { + return this.webContents.loadFile(filePath) + }, reload (...args) { return this.webContents.reload(...args) }, diff --git a/lib/browser/api/dialog.js b/lib/browser/api/dialog.js index 1ddc65bc3fa..d7545a7687a 100644 --- a/lib/browser/api/dialog.js +++ b/lib/browser/api/dialog.js @@ -83,7 +83,7 @@ module.exports = { } } - let {buttonLabel, defaultPath, filters, properties, title, message} = options + let {buttonLabel, defaultPath, filters, properties, title, message, securityScopedBookmarks = false} = options if (properties == null) { properties = ['openFile'] @@ -126,10 +126,10 @@ module.exports = { throw new TypeError('Message must be a string') } - const wrappedCallback = typeof callback === 'function' ? function (success, result) { - return callback(success ? result : void 0) + const wrappedCallback = typeof callback === 'function' ? function (success, result, bookmarkData) { + return success ? callback(result, bookmarkData) : callback() } : null - const settings = {title, buttonLabel, defaultPath, filters, message, window} + const settings = {title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, window} settings.properties = dialogProperties return binding.showOpenDialog(settings, wrappedCallback) }, @@ -145,7 +145,7 @@ module.exports = { } } - let {buttonLabel, defaultPath, filters, title, message, nameFieldLabel, showsTagField} = options + let {buttonLabel, defaultPath, filters, title, message, securityScopedBookmarks = false, nameFieldLabel, showsTagField} = options if (title == null) { title = '' @@ -185,10 +185,10 @@ module.exports = { showsTagField = true } - const wrappedCallback = typeof callback === 'function' ? function (success, result) { - return callback(success ? result : void 0) + const wrappedCallback = typeof callback === 'function' ? function (success, result, bookmarkData) { + return success ? callback(result, bookmarkData) : callback() } : null - const settings = {title, buttonLabel, defaultPath, filters, message, nameFieldLabel, showsTagField, window} + const settings = {title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, nameFieldLabel, showsTagField, window} return binding.showSaveDialog(settings, wrappedCallback) }, diff --git a/lib/browser/api/in-app-purchase.js b/lib/browser/api/in-app-purchase.js new file mode 100644 index 00000000000..48a35d2a4d4 --- /dev/null +++ b/lib/browser/api/in-app-purchase.js @@ -0,0 +1,14 @@ +'use strict' + +if (process.platform !== 'darwin') { + throw new Error('The inAppPurchase module can only be used on macOS') +} + +const {EventEmitter} = require('events') +const {inAppPurchase, InAppPurchase} = process.atomBinding('in_app_purchase') + +// inAppPurchase is an EventEmitter. +Object.setPrototypeOf(InAppPurchase.prototype, EventEmitter.prototype) +EventEmitter.call(inAppPurchase) + +module.exports = inAppPurchase diff --git a/lib/browser/api/menu-item-roles.js b/lib/browser/api/menu-item-roles.js index 3315d0536c7..3518796a53e 100644 --- a/lib/browser/api/menu-item-roles.js +++ b/lib/browser/api/menu-item-roles.js @@ -162,7 +162,7 @@ const roles = { } }, // Edit submenu (should fit both Mac & Windows) - editMenu: { + editmenu: { label: 'Edit', submenu: [ { @@ -185,7 +185,7 @@ const roles = { }, process.platform === 'darwin' ? { - role: 'pasteandmatchstyle' + role: 'pasteAndMatchStyle' } : null, { @@ -197,13 +197,13 @@ const roles = { } : null, { - role: 'selectall' + role: 'selectAll' } ] }, // Window submenu should be used for Mac only - windowMenu: { + windowmenu: { label: 'Window', submenu: [ { @@ -228,16 +228,13 @@ const roles = { const canExecuteRole = (role) => { if (!roles.hasOwnProperty(role)) return false if (process.platform !== 'darwin') return true + // macOS handles all roles natively except for a few return roles[role].nonNativeMacOSRole } exports.getDefaultLabel = (role) => { - if (roles.hasOwnProperty(role)) { - return roles[role].label - } else { - return '' - } + return roles.hasOwnProperty(role) ? roles[role].label : '' } exports.getDefaultAccelerator = (role) => { diff --git a/lib/browser/api/menu-item.js b/lib/browser/api/menu-item.js index e95226d3b36..1f97541607b 100644 --- a/lib/browser/api/menu-item.js +++ b/lib/browser/api/menu-item.js @@ -11,6 +11,9 @@ const MenuItem = function (options) { for (let key in options) { if (!(key in this)) this[key] = options[key] } + if (typeof this.role === 'string' || this.role instanceof String) { + this.role = this.role.toLowerCase() + } this.submenu = this.submenu || roles.getDefaultSubmenu(this.role) if (this.submenu != null && this.submenu.constructor !== Menu) { this.submenu = Menu.buildFromTemplate(this.submenu) diff --git a/lib/browser/api/menu.js b/lib/browser/api/menu.js index a340f74abcf..da5ff57a113 100644 --- a/lib/browser/api/menu.js +++ b/lib/browser/api/menu.js @@ -11,71 +11,76 @@ let groupIdIndex = 0 Object.setPrototypeOf(Menu.prototype, EventEmitter.prototype) +// Menu Delegate. +// This object should hold no reference to |Menu| to avoid cyclic reference. +const delegate = { + isCommandIdChecked: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].checked : undefined, + isCommandIdEnabled: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].enabled : undefined, + isCommandIdVisible: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].visible : undefined, + getAcceleratorForCommandId: (menu, id, useDefaultAccelerator) => { + const command = menu.commandsMap[id] + if (!command) return + if (command.accelerator) return command.accelerator + if (useDefaultAccelerator) return command.getDefaultRoleAccelerator() + }, + executeCommand: (menu, event, id) => { + const command = menu.commandsMap[id] + if (!command) return + command.click(event, BrowserWindow.getFocusedWindow(), webContents.getFocusedWebContents()) + }, + menuWillShow: (menu) => { + // Ensure radio groups have at least one menu item seleted + for (const id in menu.groupsMap) { + const found = menu.groupsMap[id].find(item => item.checked) || null + if (!found) v8Util.setHiddenValue(menu.groupsMap[id][0], 'checked', true) + } + } +} + /* Instance Methods */ Menu.prototype._init = function () { this.commandsMap = {} this.groupsMap = {} this.items = [] - this.delegate = { - isCommandIdChecked: id => this.commandsMap[id] ? this.commandsMap[id].checked : undefined, - isCommandIdEnabled: id => this.commandsMap[id] ? this.commandsMap[id].enabled : undefined, - isCommandIdVisible: id => this.commandsMap[id] ? this.commandsMap[id].visible : undefined, - getAcceleratorForCommandId: (id, useDefaultAccelerator) => { - const command = this.commandsMap[id] - if (!command) return - if (command.accelerator) return command.accelerator - if (useDefaultAccelerator) return command.getDefaultRoleAccelerator() - }, - getIconForCommandId: id => this.commandsMap[id] ? this.commandsMap[id].icon : undefined, - executeCommand: (event, id) => { - const command = this.commandsMap[id] - if (!command) return - command.click(event, BrowserWindow.getFocusedWindow(), webContents.getFocusedWebContents()) - }, - menuWillShow: () => { - // Ensure radio groups have at least one menu item seleted - for (const id in this.groupsMap) { - const found = this.groupsMap[id].find(item => item.checked) || null - if (!found) v8Util.setHiddenValue(this.groupsMap[id][0], 'checked', true) - } - } - } + this.delegate = delegate } -Menu.prototype.popup = function (window, x, y, positioningItem) { - let [newX, newY, newPosition, newWindow] = [x, y, positioningItem, window] +Menu.prototype.popup = function (options) { + let {window, x, y, positioningItem, callback} = options - // menu.popup(x, y, positioningItem) - if (!window) { - // shift over values - if (typeof window !== 'object' || window.constructor !== BrowserWindow) { - [newPosition, newY, newX, newWindow] = [y, x, window, null] + // no callback passed + if (!callback || typeof callback !== 'function') callback = () => {} + + // set defaults + if (typeof x !== 'number') x = -1 + if (typeof y !== 'number') y = -1 + if (typeof positioningItem !== 'number') positioningItem = -1 + + // find which window to use + const wins = BrowserWindow.getAllWindows() + if (!wins || wins.indexOf(window) === -1) { + window = BrowserWindow.getFocusedWindow() + if (!window && wins && wins.length > 0) { + window = wins[0] + } + if (!window) { + throw new Error(`Cannot open Menu without a BrowserWindow present`) } } - // menu.popup(window, {}) - if (x && typeof x === 'object') { - const opts = x - newX = opts.x - newY = opts.y - newPosition = opts.positioningItem - } - - // set defaults - if (typeof x !== 'number') newX = -1 - if (typeof y !== 'number') newY = -1 - if (typeof positioningItem !== 'number') newPosition = -1 - if (!window) newWindow = BrowserWindow.getFocusedWindow() - - this.popupAt(newWindow, newX, newY, newPosition) + this.popupAt(window, x, y, positioningItem, callback) + return { browserWindow: window, x, y, position: positioningItem } } Menu.prototype.closePopup = function (window) { - if (!window || window.constructor !== BrowserWindow) { - window = BrowserWindow.getFocusedWindow() + if (window && window.constructor !== BrowserWindow) { + this.closePopupAt(window.id) + } else { + // Passing -1 (invalid) would make closePopupAt close the all menu runners + // belong to this menu. + this.closePopupAt(-1) } - if (window) this.closePopupAt(window.id) } Menu.prototype.getMenuItemById = function (id) { @@ -116,7 +121,7 @@ Menu.prototype.insert = function (pos, item) { } Menu.prototype._callMenuWillShow = function () { - if (this.delegate) this.delegate.menuWillShow() + if (this.delegate) this.delegate.menuWillShow(this) this.items.forEach(item => { if (item.submenu) item.submenu._callMenuWillShow() }) @@ -151,11 +156,13 @@ Menu.buildFromTemplate = function (template) { } const menu = new Menu() + const filtered = removeExtraSeparators(template) + const positioned = [] let idx = 0 // sort template by position - template.forEach(item => { + filtered.forEach(item => { idx = (item.position) ? indexToInsertByPosition(positioned, item.position) : idx += 1 positioned.splice(idx, 0, item) }) @@ -229,6 +236,17 @@ function indexToInsertByPosition (items, position) { return (query in queries) ? queries[query](idx) : idx } +function removeExtraSeparators (items) { + // remove invisible items + let ret = items.filter(e => e.visible !== false) + + // fold adjacent separators together + ret = ret.filter((e, idx, arr) => e.type !== 'separator' || idx === 0 || arr[idx - 1].type !== 'separator') + + // remove edge separators + return ret.filter((e, idx, arr) => e.type !== 'separator' || (idx !== 0 && idx !== arr.length - 1)) +} + function insertItemByType (item, pos) { const types = { normal: () => this.insertItem(pos, item.commandId, item.label), diff --git a/lib/browser/api/module-list.js b/lib/browser/api/module-list.js index d8b20c5bec1..e6f398b47d0 100644 --- a/lib/browser/api/module-list.js +++ b/lib/browser/api/module-list.js @@ -8,6 +8,7 @@ module.exports = [ {name: 'dialog', file: 'dialog'}, {name: 'globalShortcut', file: 'global-shortcut'}, {name: 'ipcMain', file: 'ipc-main'}, + {name: 'inAppPurchase', file: 'in-app-purchase'}, {name: 'Menu', file: 'menu'}, {name: 'MenuItem', file: 'menu-item'}, {name: 'net', file: 'net'}, diff --git a/lib/browser/api/power-monitor.js b/lib/browser/api/power-monitor.js index e1dff2c3b73..deeda3bd4d4 100644 --- a/lib/browser/api/power-monitor.js +++ b/lib/browser/api/power-monitor.js @@ -5,4 +5,19 @@ const {powerMonitor, PowerMonitor} = process.atomBinding('power_monitor') Object.setPrototypeOf(PowerMonitor.prototype, EventEmitter.prototype) EventEmitter.call(powerMonitor) +// On Linux we need to call blockShutdown() to subscribe to shutdown event. +if (process.platform === 'linux') { + powerMonitor.on('newListener', (event) => { + if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) { + powerMonitor.blockShutdown() + } + }) + + powerMonitor.on('removeListener', (event) => { + if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) { + powerMonitor.unblockShutdown() + } + }) +} + module.exports = powerMonitor diff --git a/lib/browser/api/screen.js b/lib/browser/api/screen.js index 8287bfa8bfd..bbbc1bbe172 100644 --- a/lib/browser/api/screen.js +++ b/lib/browser/api/screen.js @@ -1,8 +1,17 @@ const {EventEmitter} = require('events') +const {deprecate} = require('electron') const {screen, Screen} = process.atomBinding('screen') // Screen is an EventEmitter. Object.setPrototypeOf(Screen.prototype, EventEmitter.prototype) EventEmitter.call(screen) +const nativeFn = screen.getMenuBarHeight +screen.getMenuBarHeight = function () { + if (!process.noDeprecations) { + deprecate.warn('screen.getMenuBarHeight', 'screen.getPrimaryDisplay().workArea') + } + return nativeFn.call(this) +} + module.exports = screen diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 65e9694954a..e131364921c 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -135,7 +135,21 @@ class TouchBar extends EventEmitter { class TouchBarItem extends EventEmitter { constructor () { super() - this.id = `${nextItemID++}` + this._addImmutableProperty('id', `${nextItemID++}`) + this._parents = [] + } + + _addImmutableProperty (name, value) { + Object.defineProperty(this, name, { + get: function () { + return value + }, + set: function () { + throw new Error(`Cannot override property ${name}`) + }, + enumerable: true, + configurable: false + }) } _addLiveProperty (name, initialValue) { @@ -152,22 +166,32 @@ class TouchBarItem extends EventEmitter { enumerable: true }) } + + _addParent (item) { + const existing = this._parents.some(test => test.id === item.id) + if (!existing) { + this._parents.push({ + id: item.id, + type: item.type + }) + } + } } TouchBar.TouchBarButton = class TouchBarButton extends TouchBarItem { constructor (config) { super() if (config == null) config = {} - this.type = 'button' + this._addImmutableProperty('type', 'button') const {click, icon, iconPosition, label, backgroundColor} = config this._addLiveProperty('label', label) this._addLiveProperty('backgroundColor', backgroundColor) this._addLiveProperty('icon', icon) this._addLiveProperty('iconPosition', iconPosition) if (typeof click === 'function') { - this.onInteraction = () => { + this._addImmutableProperty('onInteraction', () => { config.click() - } + }) } } } @@ -176,16 +200,16 @@ TouchBar.TouchBarColorPicker = class TouchBarColorPicker extends TouchBarItem { constructor (config) { super() if (config == null) config = {} - this.type = 'colorpicker' + this._addImmutableProperty('type', 'colorpicker') const {availableColors, change, selectedColor} = config this._addLiveProperty('availableColors', availableColors) this._addLiveProperty('selectedColor', selectedColor) if (typeof change === 'function') { - this.onInteraction = (details) => { + this._addImmutableProperty('onInteraction', (details) => { this._selectedColor = details.color change(details.color) - } + }) } } } @@ -194,11 +218,10 @@ TouchBar.TouchBarGroup = class TouchBarGroup extends TouchBarItem { constructor (config) { super() if (config == null) config = {} - this.type = 'group' - this.child = config.items - if (!(this.child instanceof TouchBar)) { - this.child = new TouchBar(this.child) - } + this._addImmutableProperty('type', 'group') + const defaultChild = (config.items instanceof TouchBar) ? config.items : new TouchBar(config.items) + this._addLiveProperty('child', defaultChild) + this.child.ordereredItems.forEach((item) => item._addParent(this)) } } @@ -206,7 +229,7 @@ TouchBar.TouchBarLabel = class TouchBarLabel extends TouchBarItem { constructor (config) { super() if (config == null) config = {} - this.type = 'label' + this._addImmutableProperty('type', 'label') this._addLiveProperty('label', config.label) this._addLiveProperty('textColor', config.textColor) } @@ -216,18 +239,13 @@ TouchBar.TouchBarPopover = class TouchBarPopover extends TouchBarItem { constructor (config) { super() if (config == null) config = {} - this.type = 'popover' + this._addImmutableProperty('type', 'popover') this._addLiveProperty('label', config.label) this._addLiveProperty('icon', config.icon) - this.showCloseButton = config.showCloseButton - this.child = config.items - if (!(this.child instanceof TouchBar)) { - this.child = new TouchBar(this.child) - } - this.child.ordereredItems.forEach((item) => { - item._popover = item._popover || [] - if (!item._popover.includes(this.id)) item._popover.push(this.id) - }) + this._addLiveProperty('showCloseButton', config.showCloseButton) + const defaultChild = (config.items instanceof TouchBar) ? config.items : new TouchBar(config.items) + this._addLiveProperty('child', defaultChild) + this.child.ordereredItems.forEach((item) => item._addParent(this)) } } @@ -235,7 +253,7 @@ TouchBar.TouchBarSlider = class TouchBarSlider extends TouchBarItem { constructor (config) { super() if (config == null) config = {} - this.type = 'slider' + this._addImmutableProperty('type', 'slider') const {change, label, minValue, maxValue, value} = config this._addLiveProperty('label', label) this._addLiveProperty('minValue', minValue) @@ -243,10 +261,10 @@ TouchBar.TouchBarSlider = class TouchBarSlider extends TouchBarItem { this._addLiveProperty('value', value) if (typeof change === 'function') { - this.onInteraction = (details) => { + this._addImmutableProperty('onInteraction', (details) => { this._value = details.value change(details.value) - } + }) } } } @@ -255,8 +273,8 @@ TouchBar.TouchBarSpacer = class TouchBarSpacer extends TouchBarItem { constructor (config) { super() if (config == null) config = {} - this.type = 'spacer' - this.size = config.size + this._addImmutableProperty('type', 'spacer') + this._addImmutableProperty('size', config.size) } } @@ -265,17 +283,17 @@ TouchBar.TouchBarSegmentedControl = class TouchBarSegmentedControl extends Touch super() if (config == null) config = {} const {segmentStyle, segments, selectedIndex, change, mode} = config - this.type = 'segmented_control' + this._addImmutableProperty('type', 'segmented_control') this._addLiveProperty('segmentStyle', segmentStyle) this._addLiveProperty('segments', segments || []) this._addLiveProperty('selectedIndex', selectedIndex) this._addLiveProperty('mode', mode) if (typeof change === 'function') { - this.onInteraction = (details) => { + this._addImmutableProperty('onInteraction', (details) => { this._selectedIndex = details.selectedIndex change(details.selectedIndex, details.isSelected) - } + }) } } } @@ -286,7 +304,7 @@ TouchBar.TouchBarScrubber = class TouchBarScrubber extends TouchBarItem { if (config == null) config = {} const {items, selectedStyle, overlayStyle, showArrowButtons, continuous, mode} = config let {select, highlight} = config - this.type = 'scrubber' + this._addImmutableProperty('type', 'scrubber') this._addLiveProperty('items', items) this._addLiveProperty('selectedStyle', selectedStyle || null) this._addLiveProperty('overlayStyle', overlayStyle || null) @@ -297,13 +315,13 @@ TouchBar.TouchBarScrubber = class TouchBarScrubber extends TouchBarItem { if (typeof select === 'function' || typeof highlight === 'function') { if (select == null) select = () => {} if (highlight == null) highlight = () => {} - this.onInteraction = (details) => { - if (details.type === 'select') { + this._addImmutableProperty('onInteraction', (details) => { + if (details.type === 'select' && typeof select === 'function') { select(details.selectedIndex) - } else if (details.type === 'highlight') { + } else if (details.type === 'highlight' && typeof highlight === 'function') { highlight(details.highlightedIndex) } - } + }) } } } diff --git a/lib/browser/api/tray.js b/lib/browser/api/tray.js index bc0a9d26f65..8f2ee5acd0a 100644 --- a/lib/browser/api/tray.js +++ b/lib/browser/api/tray.js @@ -1,6 +1,20 @@ const {EventEmitter} = require('events') +const {deprecate} = require('electron') const {Tray} = process.atomBinding('tray') Object.setPrototypeOf(Tray.prototype, EventEmitter.prototype) +// TODO(codebytere): remove in 3.0 +const nativeSetHighlightMode = Tray.prototype.setHighlightMode +Tray.prototype.setHighlightMode = function (param) { + if (!process.noDeprecations && typeof param === 'boolean') { + if (param) { + deprecate.warn('tray.setHighlightMode(true)', `tray.setHighlightMode("on")`) + } else { + deprecate.warn('tray.setHighlightMode(false)', `tray.setHighlightMode("off")`) + } + } + return nativeSetHighlightMode.call(this, param) +} + module.exports = Tray diff --git a/lib/browser/api/web-contents.js b/lib/browser/api/web-contents.js index f770dbb31b3..e4d96fafff7 100644 --- a/lib/browser/api/web-contents.js +++ b/lib/browser/api/web-contents.js @@ -2,7 +2,9 @@ const {EventEmitter} = require('events') const electron = require('electron') -const {app, ipcMain, session, NavigationController} = electron +const path = require('path') +const url = require('url') +const {app, ipcMain, session, NavigationController, deprecate} = electron // session is not used here, the purpose is to make sure session is initalized // before the webContents module. @@ -107,9 +109,7 @@ const webFrameMethods = [ 'insertCSS', 'insertText', 'setLayoutZoomLevelLimits', - 'setVisualZoomLevelLimits', - // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings - 'setZoomLevelLimits' + 'setVisualZoomLevelLimits' ] const webFrameMethodsWithResult = [] @@ -165,6 +165,14 @@ for (const method of webFrameMethodsWithResult) { } } +const nativeOpenDevTools = WebContents.prototype.openDevTools +WebContents.prototype.openDevTools = function (params) { + if (!process.noDeprecations && params && 'detach' in params) { + deprecate.warn('webContents.openDevTools({detach: true})', `webContents.openDevTools({mode: 'detach'})`) + } + return nativeOpenDevTools.call(this, params) +} + // Make sure WebContents::executeJavaScript would run the code only when the // WebContents has been loaded. WebContents.prototype.executeJavaScript = function (code, hasUserGesture, callback) { @@ -243,6 +251,17 @@ WebContents.prototype.getZoomLevel = function (callback) { }) } +WebContents.prototype.loadFile = function (filePath) { + if (typeof filePath !== 'string') { + throw new Error('Must pass filePath as a string') + } + return this.loadURL(url.format({ + protocol: 'file', + slashes: true, + pathname: path.resolve(app.getAppPath(), filePath) + })) +} + WebContents.prototype.getZoomFactor = function (callback) { if (typeof callback !== 'function') { throw new Error('Must pass function as an argument') @@ -277,10 +296,15 @@ WebContents.prototype._init = function () { }) // Handle context menu action request from pepper plugin. - this.on('pepper-context-menu', function (event, params) { - // Access Menu via electron.Menu to prevent circular require + this.on('pepper-context-menu', function (event, params, callback) { + // Access Menu via electron.Menu to prevent circular require. const menu = electron.Menu.buildFromTemplate(params.menu) - menu.popup(event.sender.getOwnerBrowserWindow(), params.x, params.y) + menu.popup({ + window: event.sender.getOwnerBrowserWindow(), + x: params.x, + y: params.y, + callback + }) }) // The devtools requests the webContents to reload. diff --git a/lib/browser/chrome-extension.js b/lib/browser/chrome-extension.js index af88438d33d..4132aedadd7 100644 --- a/lib/browser/chrome-extension.js +++ b/lib/browser/chrome-extension.js @@ -7,11 +7,6 @@ const fs = require('fs') const path = require('path') const url = require('url') -// TODO(zcbenz): Remove this when we have Object.values(). -const objectValues = function (object) { - return Object.keys(object).map(function (key) { return object[key] }) -} - // Mapping between extensionId(hostname) and manifest. const manifestMap = {} // extensionId => manifest const manifestNameMap = {} // name => manifest @@ -110,7 +105,7 @@ const removeBackgroundPages = function (manifest) { } const sendToBackgroundPages = function (...args) { - for (const page of objectValues(backgroundPages)) { + for (const page of Object.values(backgroundPages)) { page.webContents.sendToAll(...args) } } @@ -297,7 +292,7 @@ app.on('web-contents-created', function (event, webContents) { hookWebContentsEvents(webContents) webContents.on('devtools-opened', function () { - loadDevToolsExtensions(webContents, objectValues(manifestMap)) + loadDevToolsExtensions(webContents, Object.values(manifestMap)) }) }) diff --git a/lib/browser/guest-window-manager.js b/lib/browser/guest-window-manager.js index bd9473dda87..50e890e225a 100644 --- a/lib/browser/guest-window-manager.js +++ b/lib/browser/guest-window-manager.js @@ -25,6 +25,7 @@ const mergeOptions = function (child, parent, visited) { visited.add(parent) for (const key in parent) { + if (key === 'isBrowserView') continue if (!hasProp.call(parent, key)) continue if (key in child) continue diff --git a/lib/browser/init.js b/lib/browser/init.js index 192cf81656a..24a86fdfee7 100644 --- a/lib/browser/init.js +++ b/lib/browser/init.js @@ -163,7 +163,7 @@ require('./api/protocol') const mainStartupScript = packageJson.main || 'index.js' // Workaround for electron/electron#5050 and electron/electron#9046 -if (process.platform === 'linux' && ['Pantheon', 'Unity:Unity7', 'ubuntu:GNOME'].includes(process.env.XDG_CURRENT_DESKTOP)) { +if (process.platform === 'linux' && ['Pantheon', 'Unity:Unity7', 'ubuntu:GNOME', 'pop:GNOME'].includes(process.env.XDG_CURRENT_DESKTOP)) { process.env.XDG_CURRENT_DESKTOP = 'Unity' } diff --git a/lib/browser/rpc-server.js b/lib/browser/rpc-server.js index c0cd09dea42..633360db1e2 100644 --- a/lib/browser/rpc-server.js +++ b/lib/browser/rpc-server.js @@ -438,39 +438,6 @@ ipcMain.on('ELECTRON_BROWSER_SEND_TO', function (event, sendToAll, webContentsId } }) -// Implements window.alert(message, title) -ipcMain.on('ELECTRON_BROWSER_WINDOW_ALERT', function (event, message, title) { - if (message == null) message = '' - if (title == null) title = '' - - const dialogProperties = { - message: `${message}`, - title: `${title}`, - buttons: ['OK'] - } - event.returnValue = event.sender.isOffscreen() - ? electron.dialog.showMessageBox(dialogProperties) - : electron.dialog.showMessageBox( - event.sender.getOwnerBrowserWindow(), dialogProperties) -}) - -// Implements window.confirm(message, title) -ipcMain.on('ELECTRON_BROWSER_WINDOW_CONFIRM', function (event, message, title) { - if (message == null) message = '' - if (title == null) title = '' - - const dialogProperties = { - message: `${message}`, - title: `${title}`, - buttons: ['OK', 'Cancel'], - cancelId: 1 - } - event.returnValue = !(event.sender.isOffscreen() - ? electron.dialog.showMessageBox(dialogProperties) - : electron.dialog.showMessageBox( - event.sender.getOwnerBrowserWindow(), dialogProperties)) -}) - // Implements window.close() ipcMain.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) { const window = event.sender.getOwnerBrowserWindow() diff --git a/lib/common/api/clipboard.js b/lib/common/api/clipboard.js index b52524d7599..d5efbdb06ae 100644 --- a/lib/common/api/clipboard.js +++ b/lib/common/api/clipboard.js @@ -2,8 +2,41 @@ if (process.platform === 'linux' && process.type === 'renderer') { // On Linux we could not access clipboard in renderer process. module.exports = require('electron').remote.clipboard } else { + const {deprecate} = require('electron') const clipboard = process.atomBinding('clipboard') + // TODO(codebytere): remove in 3.0 + clipboard.readHtml = function () { + if (!process.noDeprecations) { + deprecate.warn('clipboard.readHtml', 'clipboard.readHTML') + } + return clipboard.readHTML() + } + + // TODO(codebytere): remove in 3.0 + clipboard.writeHtml = function () { + if (!process.noDeprecations) { + deprecate.warn('clipboard.writeHtml', 'clipboard.writeHTML') + } + return clipboard.writeHTML() + } + + // TODO(codebytere): remove in 3.0 + clipboard.readRtf = function () { + if (!process.noDeprecations) { + deprecate.warn('clipboard.readRtf', 'clipboard.writeRTF') + } + return clipboard.readRTF() + } + + // TODO(codebytere): remove in 3.0 + clipboard.writeRtf = function () { + if (!process.noDeprecations) { + deprecate.warn('clipboard.writeRtf', 'clipboard.writeRTF') + } + return clipboard.writeRTF() + } + // Read/write to find pasteboard over IPC since only main process is notified // of changes if (process.platform === 'darwin' && process.type === 'renderer') { diff --git a/lib/common/api/crash-reporter.js b/lib/common/api/crash-reporter.js index 219ae904276..6dc774049de 100644 --- a/lib/common/api/crash-reporter.js +++ b/lib/common/api/crash-reporter.js @@ -4,7 +4,7 @@ const {spawn} = require('child_process') const os = require('os') const path = require('path') const electron = require('electron') -const {app} = process.type === 'browser' ? electron : electron.remote +const {app, deprecate} = process.type === 'browser' ? electron : electron.remote const binding = process.atomBinding('crash_reporter') class CrashReporter { @@ -20,8 +20,15 @@ class CrashReporter { uploadToServer } = options - if (uploadToServer == null) uploadToServer = options.autoSubmit - if (uploadToServer == null) uploadToServer = true + if (uploadToServer == null) { + if (options.autoSubmit) { + deprecate.warn('autoSubmit', 'uploadToServer') + uploadToServer = options.autoSubmit + } else { + uploadToServer = true + } + } + if (ignoreSystemCrashHandler == null) ignoreSystemCrashHandler = false if (extra == null) extra = {} @@ -104,19 +111,6 @@ class CrashReporter { } } - // TODO(2.0) Remove - setExtraParameter (key, value) { - // TODO(alexeykuzmin): Warning disabled since it caused - // a couple of Crash Reported tests to time out on Mac. Add it back. - // https://github.com/electron/electron/issues/11012 - - // if (!process.noDeprecations) { - // deprecate.warn('crashReporter.setExtraParameter', - // 'crashReporter.addExtraParameter or crashReporter.removeExtraParameter') - // } - binding.setExtraParameter(key, value) - } - addExtraParameter (key, value) { binding.addExtraParameter(key, value) } diff --git a/lib/renderer/init.js b/lib/renderer/init.js index 0d1a251c4da..1cb69436dd4 100644 --- a/lib/renderer/init.js +++ b/lib/renderer/init.js @@ -28,6 +28,18 @@ v8Util.setHiddenValue(global, 'ipc', new events.EventEmitter()) // Use electron module after everything is ready. const electron = require('electron') +const { + warnAboutNodeWithRemoteContent, + warnAboutDisabledWebSecurity, + warnAboutInsecureContentAllowed, + warnAboutExperimentalFeatures, + warnAboutBlinkFeatures, + warnAboutInsecureResources, + warnAboutInsecureCSP, + warnAboutAllowedPopups, + shouldLogSecurityWarnings +} = require('./security-warnings') + // Call webFrame method. electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', (event, method, args) => { electron.webFrame[method](...args) @@ -67,6 +79,7 @@ electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', (ev let nodeIntegration = 'false' let webviewTag = 'false' let preloadScript = null +let preloadScripts = [] let isBackgroundPage = false let appPath = null for (let arg of process.argv) { @@ -86,9 +99,16 @@ for (let arg of process.argv) { appPath = arg.substr(arg.indexOf('=') + 1) } else if (arg.indexOf('--webview-tag=') === 0) { webviewTag = arg.substr(arg.indexOf('=') + 1) + } else if (arg.indexOf('--preload-scripts') === 0) { + preloadScripts = arg.substr(arg.indexOf('=') + 1).split(path.delimiter) } } +// The webContents preload script is loaded after the session preload scripts. +if (preloadScript) { + preloadScripts.push(preloadScript) +} + if (window.location.protocol === 'chrome-devtools:') { // Override some inspector APIs. require('./inspector') @@ -140,17 +160,6 @@ if (nodeIntegration === 'true') { } } - if (window.location.protocol === 'https:' || - window.location.protocol === 'http:' || - window.location.protocol === 'ftp:') { - let warning = 'This renderer process has Node.js integration enabled ' - warning += 'and attempted to load remote content. This exposes users of this app to severe ' - warning += 'security risks.\n' - warning += 'For more information and help, consult https://electronjs.org/docs/tutorial/security' - - console.warn('%cElectron Security Warning', 'font-weight: bold;', warning) - } - // Redirect window.onerror to uncaughtException. window.onerror = function (message, filename, lineno, colno, error) { if (global.process.listeners('uncaughtException').length > 0) { @@ -171,8 +180,8 @@ if (nodeIntegration === 'true') { }) } -// Load the script specfied by the "preload" attribute. -if (preloadScript) { +// Load the preload scripts. +for (const preloadScript of preloadScripts) { try { require(preloadScript) } catch (error) { @@ -180,3 +189,22 @@ if (preloadScript) { console.error(error.stack || error.message) } } + +// Warn about security issues +window.addEventListener('load', function loadHandler () { + if (shouldLogSecurityWarnings()) { + if (nodeIntegration === 'true') { + warnAboutNodeWithRemoteContent() + } + + warnAboutDisabledWebSecurity() + warnAboutInsecureResources() + warnAboutInsecureContentAllowed() + warnAboutExperimentalFeatures() + warnAboutBlinkFeatures() + warnAboutInsecureCSP() + warnAboutAllowedPopups() + } + + window.removeEventListener('load', loadHandler) +}) diff --git a/lib/renderer/inspector.js b/lib/renderer/inspector.js index 21aabbd347f..89048d3c792 100644 --- a/lib/renderer/inspector.js +++ b/lib/renderer/inspector.js @@ -63,7 +63,7 @@ const createMenu = function (x, y, items) { // The menu is expected to show asynchronously. setTimeout(function () { - menu.popup(remote.getCurrentWindow()) + menu.popup({window: remote.getCurrentWindow()}) }) } @@ -94,13 +94,13 @@ const getEditMenuItems = function () { role: 'paste' }, { - role: 'pasteandmatchstyle' + role: 'pasteAndMatchStyle' }, { role: 'delete' }, { - role: 'selectall' + role: 'selectAll' } ] } diff --git a/lib/renderer/security-warnings.js b/lib/renderer/security-warnings.js new file mode 100644 index 00000000000..d2da884f1d9 --- /dev/null +++ b/lib/renderer/security-warnings.js @@ -0,0 +1,277 @@ +let shouldLog = null + +/** + * This method checks if a security message should be logged. + * It does so by determining whether we're running as Electron, + * which indicates that a developer is currently looking at the + * app. + * + * @returns {boolean} - Should we log? + */ +const shouldLogSecurityWarnings = function () { + if (shouldLog !== null) { + return shouldLog + } + + const { platform, execPath, env } = process + + switch (platform) { + case 'darwin': + shouldLog = execPath.endsWith('MacOS/Electron') || + execPath.includes('Electron.app/Contents/Frameworks/') + break + case 'freebsd': + case 'linux': + shouldLog = execPath.endsWith('/electron') + break + case 'win32': + shouldLog = execPath.endsWith('\\electron.exe') + break + default: + shouldLog = false + } + + if ((env && env.ELECTRON_DISABLE_SECURITY_WARNINGS) || + (window && window.ELECTRON_DISABLE_SECURITY_WARNINGS)) { + shouldLog = false + } + + if ((env && env.ELECTRON_ENABLE_SECURITY_WARNINGS) || + (window && window.ELECTRON_ENABLE_SECURITY_WARNINGS)) { + shouldLog = true + } + + return shouldLog +} + +/** + * Check's if the current window is remote. + * + * @returns {boolean} - Is this a remote protocol? + */ +const getIsRemoteProtocol = function () { + if (window && window.location && window.location.protocol) { + return /^(http|ftp)s?/gi.test(window.location.protocol) + } +} + +/** + * Tries to determine whether a CSP without `unsafe-eval` is set. + * + * @returns {boolean} Is a CSP with `unsafe-eval` set? + */ +const isUnsafeEvalEnabled = function () { + try { + //eslint-disable-next-line + new Function(''); + return true + } catch (error) { + return false + } +} + +/** + * Attempts to get the current webContents. Returns null + * if that fails + * + * @returns {Object|null} webPreferences + */ +let webPreferences +const getWebPreferences = function () { + try { + if (webPreferences) { + return webPreferences + } + + const { remote } = require('electron') + webPreferences = remote.getCurrentWindow().webContents.getWebPreferences() + return webPreferences + } catch (error) { + return null + } +} + +const moreInformation = '\nFor more information and help, consult ' + + 'https://electronjs.org/docs/tutorial/security.\n' + + 'This warning will not show up once the app is packaged.' + +module.exports = { + shouldLogSecurityWarnings, + + /** + * #1 Only load secure content + * + * Checks the loaded resources on the current page and logs a + * message about all resources loaded over HTTP or FTP. + */ + warnAboutInsecureResources: () => { + if (!window || !window.performance || !window.performance.getEntriesByType) { + return + } + + const resources = window.performance + .getEntriesByType('resource') + .filter(({ name }) => /^(http|ftp):?/gi.test(name || '')) + .map(({ name }) => `- ${name}`) + .join('\n') + + if (!resources || resources.length === 0) { + return + } + + let warning = 'This renderer process loads resources using insecure protocols. ' + + 'This exposes users of this app to unnecessary security risks. ' + + 'Consider loading the following resources over HTTPS or FTPS. \n' + + resources + '\n' + + moreInformation + + console.warn('%cElectron Security Warning (Insecure Resources)', + 'font-weight: bold;', warning) + }, + + /** + * #2 on the checklist: Disable the Node.js integration in all renderers that + * display remote content + * + * Logs a warning message about Node integration. + */ + warnAboutNodeWithRemoteContent: () => { + if (getIsRemoteProtocol()) { + let warning = 'This renderer process has Node.js integration enabled ' + + 'and attempted to load remote content. This exposes users of this app to severe ' + + 'security risks.\n' + + moreInformation + + console.warn('%cElectron Security Warning (Node.js Integration with Remote Content)', + 'font-weight: bold;', warning) + } + }, + + // Currently missing since it has ramifications and is still experimental: + // #3 Enable context isolation in all renderers that display remote content + // + // Currently missing since we can't easily programmatically check for those cases: + // #4 Use ses.setPermissionRequestHandler() in all sessions that load remote content + + /** + * #5 on the checklist: Do not disable websecurity + * + * Logs a warning message about disabled webSecurity. + */ + warnAboutDisabledWebSecurity: () => { + const webPreferences = getWebPreferences() + if (!webPreferences || webPreferences.webSecurity !== false) return + + let warning = 'This renderer process has "webSecurity" disabled. ' + + 'This exposes users of this app to severe security risks.\n' + + moreInformation + + console.warn('%cElectron Security Warning (Disabled webSecurity)', + 'font-weight: bold;', warning) + }, + + /** + * #6 on the checklist: Define a Content-Security-Policy and use restrictive + * rules (i.e. script-src 'self') + * + * #7 on the checklist: Disable eval + * + * Logs a warning message about unset or insecure CSP + */ + warnAboutInsecureCSP: () => { + if (isUnsafeEvalEnabled()) { + let warning = 'This renderer process has either no Content Security Policy set ' + + 'or a policy with "unsafe-eval" enabled. This exposes users of this ' + + 'app to unnecessary security risks.\n' + + moreInformation + + console.warn('%cElectron Security Warning (Insecure Content-Security-Policy)', + 'font-weight: bold;', warning) + } + }, + + /** + * #8 on the checklist: Do not set allowRunningInsecureContent to true + * + * Logs a warning message about disabled webSecurity. + */ + warnAboutInsecureContentAllowed: () => { + const webPreferences = getWebPreferences() + if (!webPreferences || !webPreferences.allowRunningInsecureContent) return + + let warning = 'This renderer process has "allowRunningInsecureContent" ' + + 'enabled. This exposes users of this app to severe security risks.\n' + + moreInformation + + console.warn('%cElectron Security Warning (allowRunningInsecureContent)', + 'font-weight: bold;', warning) + }, + + /** + * #9 on the checklist: Do not enable experimental features + * + * Logs a warning message about experimental features. + */ + warnAboutExperimentalFeatures: () => { + const webPreferences = getWebPreferences() + if (!webPreferences || (!webPreferences.experimentalFeatures && + !webPreferences.experimentalCanvasFeatures)) { + return + } + + let warning = 'This renderer process has "experimentalFeatures" ' + + 'enabled. This exposes users of this app to some security risk. ' + + 'If you do not need this feature, you should disable it.\n' + + moreInformation + + console.warn('%cElectron Security Warning (experimentalFeatures)', + 'font-weight: bold;', warning) + }, + + /** + * #10 on the checklist: Do not use blinkFeatures + * + * Logs a warning message about blinkFeatures + */ + warnAboutBlinkFeatures: () => { + const webPreferences = getWebPreferences() + if (!webPreferences || !webPreferences.blinkFeatures || + (webPreferences.blinkFeatures.length && webPreferences.blinkFeatures.length === 0)) { + return + } + + let warning = 'This renderer process has additional "blinkFeatures" ' + + 'enabled. This exposes users of this app to some security risk. ' + + 'If you do not need this feature, you should disable it.\n' + + moreInformation + + console.warn('%cElectron Security Warning (blinkFeatures)', + 'font-weight: bold;', warning) + }, + + /** + * #11 on the checklist: Do Not Use allowpopups + * + * Logs a warning message about allowed popups + */ + warnAboutAllowedPopups: () => { + if (document && document.querySelectorAll) { + const domElements = document.querySelectorAll('[allowpopups]') + + if (!domElements || domElements.length === 0) { + return + } + + let warning = 'A has "allowpopups" set to true. ' + + 'This exposes users of this app to some security risk, since popups are just ' + + 'BrowserWindows. If you do not need this feature, you should disable it.\n' + + moreInformation + + console.warn('%cElectron Security Warning (allowpopups)', + 'font-weight: bold;', warning) + } + } + + // Currently missing since we can't easily programmatically check for it: + // #12WebViews: Verify the options and params of all `` tags +} diff --git a/lib/renderer/web-view/web-view-attributes.js b/lib/renderer/web-view/web-view-attributes.js index 204046bd605..16bfe7e61ac 100644 --- a/lib/renderer/web-view/web-view-attributes.js +++ b/lib/renderer/web-view/web-view-attributes.js @@ -230,7 +230,7 @@ class SrcAttribute extends WebViewAttribute { } parse () { - if (!this.webViewImpl.elementAttached || !this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId || !this.getValue()) { + if (!this.webViewImpl.elementAttached || !this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId) { return } if (this.webViewImpl.guestInstanceId == null) { @@ -240,6 +240,9 @@ class SrcAttribute extends WebViewAttribute { } return } + if (!this.getValue()) { + return + } // Navigate to |this.src|. const opts = {} diff --git a/lib/renderer/web-view/web-view.js b/lib/renderer/web-view/web-view.js index ac45ab32a1c..544652e4475 100644 --- a/lib/renderer/web-view/web-view.js +++ b/lib/renderer/web-view/web-view.js @@ -27,7 +27,7 @@ class WebViewImpl { // on* Event handlers. this.on = {} this.browserPluginNode = this.createBrowserPluginNode() - const shadowRoot = this.webviewNode.createShadowRoot() + const shadowRoot = this.webviewNode.attachShadow({mode: 'open'}) shadowRoot.innerHTML = '' this.setupWebViewAttributes() this.setupFocusPropagation() @@ -375,9 +375,7 @@ const registerWebViewElement = function () { 'send', 'sendInputEvent', 'setLayoutZoomLevelLimits', - 'setVisualZoomLevelLimits', - // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings - 'setZoomLevelLimits' + 'setVisualZoomLevelLimits' ] // Forward proto.foo* method calls to WebViewImpl.foo*. diff --git a/lib/renderer/window-setup.js b/lib/renderer/window-setup.js index b94eace5aec..ac329b43070 100644 --- a/lib/renderer/window-setup.js +++ b/lib/renderer/window-setup.js @@ -133,14 +133,6 @@ module.exports = (ipcRenderer, guestInstanceId, openerId, hiddenPage, usesNative } } - window.alert = function (message, title) { - ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_ALERT', toString(message), toString(title)) - } - - window.confirm = function (message, title) { - return ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_CONFIRM', toString(message), toString(title)) - } - // But we do not support prompt(). window.prompt = function () { throw new Error('prompt() is and will not be supported.') diff --git a/package.json b/package.json index acf290904f6..5c95b19780b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "electron", - "version": "1.8.2-beta.2", + "version": "0.0.0-dev", "repository": "https://github.com/electron/electron", "description": "Build cross platform desktop apps with JavaScript, HTML, and CSS", "devDependencies": { @@ -12,7 +12,7 @@ "dugite": "^1.45.0", "electabul": "~0.0.4", "electron-docs-linter": "^2.3.4", - "electron-typescript-definitions": "^1.2.11", + "electron-typescript-definitions": "^1.3.2", "github": "^9.2.0", "husky": "^0.14.3", "minimist": "^1.2.0", @@ -50,11 +50,11 @@ "lint-js": "standard && cd spec && standard", "lint-cpp": "python ./script/cpplint.py", "lint-py": "python ./script/pylint.py", - "lint-docs": "remark docs -qf && npm run lint-js-in-markdown && npm run create-typescript-definitions", + "lint-docs": "remark docs -qf && npm run lint-js-in-markdown && npm run create-typescript-definitions && npm run lint-docs-relative-links", + "lint-docs-relative-links": "python ./script/check-relative-doc-links.py", "lint-js-in-markdown": "standard-markdown docs", "create-api-json": "electron-docs-linter docs --outfile=out/electron-api.json --version=$npm_package_version", "create-typescript-definitions": "npm run create-api-json && electron-typescript-definitions --in=out/electron-api.json --out=out/electron.d.ts", - "merge-release": "node ./script/merge-release.js", "mock-release": "node ./script/ci-release-build.js", "preinstall": "node -e 'process.exit(0)'", "publish-to-npm": "node ./script/publish-to-npm.js", @@ -71,5 +71,8 @@ "author": "Electron Community", "keywords": [ "electron" - ] + ], + "dependencies": { + "lint": "^1.1.2" + } } diff --git a/script/build.py b/script/build.py index c97cd8336f7..0f40804db63 100755 --- a/script/build.py +++ b/script/build.py @@ -5,7 +5,8 @@ import os import subprocess import sys -from lib.config import MIPS64EL_GCC, get_target_arch, build_env +from lib.config import MIPS64EL_GCC, get_target_arch, build_env, \ + enable_verbose_mode, is_verbose_mode from lib.util import electron_gyp, import_vs_env @@ -20,14 +21,25 @@ GCLIENT_DONE = os.path.join(SOURCE_ROOT, '.gclient_done') def main(): os.chdir(SOURCE_ROOT) + args = parse_args() + if args.verbose: + enable_verbose_mode() + # 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' + # decide which ninja executable to use + ninja_path = args.ninja_path + if not ninja_path: + ninja_path = os.path.join('vendor', 'depot_tools', 'ninja') + if sys.platform == 'win32': + ninja_path += '.exe' + + # decide how to invoke ninja + ninja = [ninja_path] + if is_verbose_mode(): + ninja.append('-v') - args = parse_args() if args.libcc: if ('D' not in args.configuration or not os.path.exists(GCLIENT_DONE) @@ -39,12 +51,12 @@ def main(): script = os.path.join(LIBCC_SOURCE_ROOT, 'script', 'build') subprocess.check_call([sys.executable, script, '-D', '-t', get_target_arch()]) - subprocess.check_call([ninja, '-C', LIBCC_DIST_MAIN]) + subprocess.check_call(ninja + ['-C', LIBCC_DIST_MAIN]) env = build_env() for config in args.configuration: build_path = os.path.join('out', config[0]) - ret = subprocess.call([ninja, '-C', build_path, args.target], env=env) + ret = subprocess.call(ninja + ['-C', build_path, args.target], env=env) if ret != 0: sys.exit(ret) @@ -56,6 +68,10 @@ def parse_args(): nargs='+', default=CONFIGURATIONS, required=False) + parser.add_argument('-v', '--verbose', + action='store_true', + default=False, + help='Verbose output') parser.add_argument('-t', '--target', help='Build specified target', default=electron_gyp()['project_name%'], @@ -67,6 +83,9 @@ def parse_args(): '-d --debug_libchromiumcontent.' ), action='store_true', default=False) + parser.add_argument('--ninja-path', + help='Path of ninja command to use.', + required=False) return parser.parse_args() diff --git a/script/check-relative-doc-links.py b/script/check-relative-doc-links.py new file mode 100755 index 00000000000..9df185471c3 --- /dev/null +++ b/script/check-relative-doc-links.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python + +import os +import sys +import re + + +SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) +DOCS_DIR = os.path.join(SOURCE_ROOT, 'docs') + + +def main(): + os.chdir(SOURCE_ROOT) + + filepaths = [] + totalDirs = 0 + try: + for root, dirs, files in os.walk(DOCS_DIR): + totalDirs += len(dirs) + for f in files: + if f.endswith('.md'): + filepaths.append(os.path.join(root, f)) + except KeyboardInterrupt: + print('Keyboard interruption. Please try again.') + return + + totalBrokenLinks = 0 + for path in filepaths: + totalBrokenLinks += getBrokenLinks(path) + + print('Parsed through ' + str(len(filepaths)) + + ' files within docs directory and its ' + + str(totalDirs) + ' subdirectories.') + print('Found ' + str(totalBrokenLinks) + ' broken relative links.') + + +def getBrokenLinks(filepath): + currentDir = os.path.dirname(filepath) + brokenLinks = [] + + try: + f = open(filepath, 'r') + lines = f.readlines() + except KeyboardInterrupt: + print('Keyboard interruption whle parsing. Please try again.') + finally: + f.close() + + regexLink = re.compile('\[(.*?)\]\((?P(.*?))\)') + links = [] + for line in lines: + matchLinks = regexLink.search(line) + if matchLinks: + relativeLink = matchLinks.group('links') + if not str(relativeLink).startswith('http'): + links.append(relativeLink) + + for link in links: + sections = link.split('#') + if len(sections) > 1: + if str(link).startswith('#'): + if not checkSections(sections, lines): + brokenLinks.append(link) + else: + tempFile = os.path.join(currentDir, sections[0]) + if os.path.isfile(tempFile): + try: + newFile = open(tempFile, 'r') + newLines = newFile.readlines() + except KeyboardInterrupt: + print('Keyboard interruption whle parsing. Please try again.') + finally: + newFile.close() + + if not checkSections(sections, newLines): + brokenLinks.append(link) + else: + brokenLinks.append(link) + + else: + if not os.path.isfile(os.path.join(currentDir, link)): + brokenLinks.append(link) + + print_errors(filepath, brokenLinks) + return len(brokenLinks) + + +def checkSections(sections, lines): + sectionHeader = sections[1].replace('-', '') + regexSectionTitle = re.compile('# (?P
.*)') + for line in lines: + matchHeader = regexSectionTitle.search(line) + if matchHeader: + matchHeader = filter(str.isalnum, str(matchHeader.group('header'))) + if matchHeader.lower() == sectionHeader: + return True + return False + + +def print_errors(filepath, brokenLink): + if brokenLink: + print "File Location: " + filepath + for link in brokenLink: + print "\tBroken links: " + link + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/script/ci-release-build.js b/script/ci-release-build.js index d80025ab9d4..ec2459198bf 100644 --- a/script/ci-release-build.js +++ b/script/ci-release-build.js @@ -199,7 +199,7 @@ function runRelease (targetBranch, options) { module.exports = runRelease if (require.main === module) { - const args = require('minimist')(process.argv.slice(2)) + const args = require('minimist')(process.argv.slice(2), { boolean: 'ghRelease' }) const targetBranch = args._[0] if (args._.length < 1) { console.log(`Trigger CI to build release builds of electron. diff --git a/script/cibuild b/script/cibuild index 7186a5726d4..758dc2c794e 100755 --- a/script/cibuild +++ b/script/cibuild @@ -14,7 +14,7 @@ LINUX_DEPS = [ 'libdbus-1-dev', 'libgconf2-dev', 'libgnome-keyring-dev', - 'libgtk2.0-dev', + 'libgtk-3-dev', 'libnotify-dev', 'libnss3-dev', 'libxtst-dev', @@ -57,19 +57,6 @@ def main(): if os.environ.has_key('TARGET_ARCH'): target_arch = os.environ['TARGET_ARCH'] - is_travis = (os.getenv('TRAVIS') == 'true') - if is_travis and PLATFORM == 'linux': - print 'Setup travis CI' - execute(['sudo', 'apt-get', 'update']) - deps = LINUX_DEPS - if target_arch == 'arm': - deps += LINUX_DEPS_ARM - elif target_arch == 'arm64': - deps += LINUX_DEPS_ARM64 - else: - deps += LINUX_DEPS_NO_ARM - execute(['sudo', 'apt-get', 'install'] + deps) - if PLATFORM == 'linux' and target_arch == 'x64': os.environ['DISPLAY'] = ':99.0' execute(['sh', '-e', '/etc/init.d/xvfb', 'start']) diff --git a/script/cpplint.py b/script/cpplint.py index 0b031024383..49ddb877f11 100755 --- a/script/cpplint.py +++ b/script/cpplint.py @@ -1,73 +1,101 @@ #!/usr/bin/env python -import fnmatch +import argparse import os import sys +from lib.config import enable_verbose_mode from lib.util import execute -IGNORE_FILES = [ - os.path.join('atom', 'browser', 'mac', 'atom_application.h'), - os.path.join('atom', 'browser', 'mac', 'atom_application_delegate.h'), - os.path.join('atom', 'browser', 'resources', 'win', 'resource.h'), - os.path.join('atom', 'browser', 'ui', 'cocoa', 'atom_menu_controller.h'), - os.path.join('atom', 'browser', 'ui', 'cocoa', 'atom_touch_bar.h'), - os.path.join('atom', 'browser', 'ui', 'cocoa', - 'touch_bar_forward_declarations.h'), - os.path.join('atom', 'browser', 'ui', 'cocoa', 'NSColor+Hex.h'), - os.path.join('atom', 'browser', 'ui', 'cocoa', 'NSString+ANSI.h'), - os.path.join('atom', 'common', 'api', 'api_messages.h'), - os.path.join('atom', 'common', 'common_message_generator.cc'), - os.path.join('atom', 'common', 'common_message_generator.h'), - os.path.join('brightray', 'browser', 'mac', - 'bry_inspectable_web_contents_view.h'), - os.path.join('brightray', 'browser', 'mac', 'event_dispatching_window.h'), - os.path.join('brightray', 'browser', 'mac', - 'notification_center_delegate.h'), - os.path.join('brightray', 'browser', 'win', 'notification_presenter_win7.h'), - os.path.join('brightray', 'browser', 'win', 'win32_desktop_notifications', - 'common.h'), - os.path.join('brightray', 'browser', 'win', 'win32_desktop_notifications', - 'desktop_notification_controller.cc'), - os.path.join('brightray', 'browser', 'win', 'win32_desktop_notifications', - 'desktop_notification_controller.h'), - os.path.join('brightray', 'browser', 'win', 'win32_desktop_notifications', - 'toast.h'), - os.path.join('brightray', 'browser', 'win', 'win32_notification.h') -] +IGNORE_FILES = set(os.path.join(*components) for components in [ + ['atom', 'browser', 'mac', 'atom_application.h'], + ['atom', 'browser', 'mac', 'atom_application_delegate.h'], + ['atom', 'browser', 'resources', 'win', 'resource.h'], + ['atom', 'browser', 'ui', 'cocoa', 'atom_menu_controller.h'], + ['atom', 'browser', 'ui', 'cocoa', 'atom_touch_bar.h'], + ['atom', 'browser', 'ui', 'cocoa', 'touch_bar_forward_declarations.h'], + ['atom', 'browser', 'ui', 'cocoa', 'NSColor+Hex.h'], + ['atom', 'browser', 'ui', 'cocoa', 'NSString+ANSI.h'], + ['atom', 'common', 'api', 'api_messages.h'], + ['atom', 'common', 'common_message_generator.cc'], + ['atom', 'common', 'common_message_generator.h'], + ['atom', 'node', 'osfhandle.cc'], + ['brightray', 'browser', 'mac', 'bry_inspectable_web_contents_view.h'], + ['brightray', 'browser', 'mac', 'event_dispatching_window.h'], + ['brightray', 'browser', 'mac', 'notification_center_delegate.h'], + ['brightray', 'browser', 'win', 'notification_presenter_win7.h'], + ['brightray', 'browser', 'win', 'win32_desktop_notifications', 'common.h'], + ['brightray', 'browser', 'win', 'win32_desktop_notifications', + 'desktop_notification_controller.cc'], + ['brightray', 'browser', 'win', 'win32_desktop_notifications', + 'desktop_notification_controller.h'], + ['brightray', 'browser', 'win', 'win32_desktop_notifications', 'toast.h'], + ['brightray', 'browser', 'win', 'win32_notification.h'] +]) SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) def main(): + + parser = argparse.ArgumentParser( + description="Run cpplint on Electron's C++ files", + formatter_class=argparse.RawTextHelpFormatter + ) + parser.add_argument( + '-c', '--only-changed', + action='store_true', + default=False, + dest='only_changed', + help='only run on changed files' + ) + parser.add_argument( + '-v', '--verbose', + action='store_true', + default=False, + dest='verbose', + help='show cpplint output' + ) + args = parser.parse_args() + if not os.path.isfile(cpplint_path()): print("[INFO] Skipping cpplint, dependencies has not been bootstrapped") return + if args.verbose: + enable_verbose_mode() + os.chdir(SOURCE_ROOT) - atom_files = list_files('atom', - ['app', 'browser', 'common', 'renderer', 'utility'], - ['*.cc', '*.h']) - call_cpplint(list(set(atom_files) - set(IGNORE_FILES))) - - brightray_files = list_files('brightray', ['browser', 'common'], - ['*.cc', '*.h']) - call_cpplint(list(set(brightray_files) - set(IGNORE_FILES))) + files = find_files(['atom', 'brightray'], is_cpp_file) + files -= IGNORE_FILES + if args.only_changed: + files &= find_changed_files() + call_cpplint(files) -def list_files(parent, directories, filters): - matches = [] - for directory in directories: - for root, _, filenames, in os.walk(os.path.join(parent, directory)): - for f in filters: - for filename in fnmatch.filter(filenames, f): - matches.append(os.path.join(root, filename)) +def find_files(roots, test): + matches = set() + for root in roots: + for parent, _, children, in os.walk(root): + for child in children: + filename = os.path.join(parent, child) + if test(filename): + matches.add(filename) return matches +def is_cpp_file(filename): + return filename.endswith('.cc') or filename.endswith('.h') + + +def find_changed_files(): + return set(execute(['git', 'diff', '--name-only']).splitlines()) + + def call_cpplint(files): - cpplint = cpplint_path() - execute([sys.executable, cpplint] + files) + if files: + cpplint = cpplint_path() + execute([sys.executable, cpplint] + list(files)) def cpplint_path(): diff --git a/script/create-dist.py b/script/create-dist.py index 9eef2bffef9..de3b9ffd481 100755 --- a/script/create-dist.py +++ b/script/create-dist.py @@ -82,6 +82,9 @@ TARGET_DIRECTORIES = { def main(): args = parse_args() + if args.chromium_dir: + globals().update(CHROMIUM_DIR=args.chromium_dir) + if args.verbose: enable_verbose_mode() @@ -319,6 +322,9 @@ def parse_args(): parser.add_argument('--no_api_docs', action='store_true', help='Skip generating the Electron API Documentation!') + parser.add_argument('--chromium_dir', + help='Specify a custom libchromiumcontent dist directory ' + + 'if manually compiled') parser.add_argument('-v', '--verbose', action='store_true', help='Prints the output of the subprocesses') diff --git a/script/get-version.py b/script/get-version.py new file mode 100755 index 00000000000..fd94dbe647f --- /dev/null +++ b/script/get-version.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import sys + +from lib.util import get_electron_version + +def main(): + print get_electron_version() + +if __name__ == '__main__': + sys.exit(main()) diff --git a/script/install-sysroot.py b/script/install-sysroot.py index 05887a6f2dc..463a81d9ea9 100755 --- a/script/install-sysroot.py +++ b/script/install-sysroot.py @@ -85,11 +85,12 @@ def main(args): def InstallDefaultSysrootForArch(target_arch): if target_arch not in VALID_ARCHS: raise Error('Unknown architecture: %s' % target_arch) - InstallSysroot('Jessie', target_arch) + InstallSysroot('Stretch', target_arch) def InstallSysroot(target_platform, target_arch): - # The sysroot directory should match the one specified in build/common.gypi. + # The sysroot directory should match the one specified in + # build/config/sysroot.gni. # TODO(thestig) Consider putting this elsewhere to avoid having to recreate # it on every build. linux_dir = os.path.dirname(SCRIPT_DIR) @@ -111,8 +112,6 @@ def InstallSysroot(target_platform, target_arch): if os.path.exists(stamp): with open(stamp) as s: if s.read() == url: - print '%s %s sysroot image already up to date: %s' % \ - (target_platform, target_arch, sysroot) return print 'Installing Debian %s %s root image: %s' % \ diff --git a/script/lib/dbus_mock.py b/script/lib/dbus_mock.py new file mode 100644 index 00000000000..76f39fc2697 --- /dev/null +++ b/script/lib/dbus_mock.py @@ -0,0 +1,13 @@ +from dbusmock import DBusTestCase + +import atexit + +def cleanup(): + DBusTestCase.stop_dbus(DBusTestCase.system_bus_pid) + + +atexit.register(cleanup) +DBusTestCase.start_system_bus() +# create a mock for "org.freedesktop.login1" using python-dbusmock +# preconfigured template +(logind_mock, logind) = DBusTestCase.spawn_server_template('logind') diff --git a/script/merge-release.js b/script/merge-release.js deleted file mode 100755 index 8917da4f2f2..00000000000 --- a/script/merge-release.js +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env node - -require('colors') -const assert = require('assert') -const branchToRelease = process.argv[2] -const fail = '\u2717'.red -const { GitProcess, GitError } = require('dugite') -const pass = '\u2713'.green -const path = require('path') -const pkg = require('../package.json') - -assert(process.env.ELECTRON_GITHUB_TOKEN, 'ELECTRON_GITHUB_TOKEN not found in environment') -if (!branchToRelease) { - console.log(`Usage: merge-release branch`) - process.exit(1) -} -const gitDir = path.resolve(__dirname, '..') - -async function callGit (args, errorMessage, successMessage) { - let gitResult = await GitProcess.exec(args, gitDir) - if (gitResult.exitCode === 0) { - console.log(`${pass} ${successMessage}`) - return true - } else { - console.log(`${fail} ${errorMessage} ${gitResult.stderr}`) - process.exit(1) - } -} - -async function checkoutBranch (branchName) { - console.log(`Checking out ${branchName}.`) - let errorMessage = `Error checking out branch ${branchName}:` - let successMessage = `Successfully checked out branch ${branchName}.` - return callGit(['checkout', branchName], errorMessage, successMessage) -} - -async function commitMerge () { - console.log(`Committing the merge for v${pkg.version}`) - let errorMessage = `Error committing merge:` - let successMessage = `Successfully committed the merge for v${pkg.version}` - let gitArgs = ['commit', '-m', `v${pkg.version}`] - return callGit(gitArgs, errorMessage, successMessage) -} - -async function mergeReleaseIntoBranch (branchName) { - console.log(`Merging release branch into ${branchName}.`) - let mergeArgs = ['merge', 'release', '--squash'] - let mergeDetails = await GitProcess.exec(mergeArgs, gitDir) - if (mergeDetails.exitCode === 0) { - return true - } else { - const error = GitProcess.parseError(mergeDetails.stderr) - if (error === GitError.MergeConflicts) { - console.log(`${fail} Could not merge release branch into ${branchName} ` + - `due to merge conflicts.`) - return false - } else { - console.log(`${fail} Could not merge release branch into ${branchName} ` + - `due to an error: ${mergeDetails.stderr}.`) - process.exit(1) - } - } -} - -async function pushBranch (branchName) { - console.log(`Pushing branch ${branchName}.`) - let pushArgs = ['push', 'origin', branchName] - let errorMessage = `Could not push branch ${branchName} due to an error:` - let successMessage = `Successfully pushed branch ${branchName}.` - return callGit(pushArgs, errorMessage, successMessage) -} - -async function pull () { - console.log(`Performing a git pull`) - let errorMessage = `Could not pull due to an error:` - let successMessage = `Successfully performed a git pull` - return callGit(['pull'], errorMessage, successMessage) -} - -async function rebase (targetBranch) { - console.log(`Rebasing release branch from ${targetBranch}`) - let errorMessage = `Could not rebase due to an error:` - let successMessage = `Successfully rebased release branch from ` + - `${targetBranch}` - return callGit(['rebase', targetBranch], errorMessage, successMessage) -} - -async function mergeRelease () { - await checkoutBranch(branchToRelease) - let mergeSuccess = await mergeReleaseIntoBranch(branchToRelease) - if (mergeSuccess) { - console.log(`${pass} Successfully merged release branch into ` + - `${branchToRelease}.`) - await commitMerge() - let pushSuccess = await pushBranch(branchToRelease) - if (pushSuccess) { - console.log(`${pass} Success!!! ${branchToRelease} now has the latest release!`) - } - } else { - console.log(`Trying rebase of ${branchToRelease} into release branch.`) - await pull() - await checkoutBranch('release') - let rebaseResult = await rebase(branchToRelease) - if (rebaseResult) { - let pushResult = pushBranch('HEAD') - if (pushResult) { - console.log(`Rebase of ${branchToRelease} into release branch was ` + - `successful. Let release builds run and then try this step again.`) - } - // Exit as failure so release doesn't continue - process.exit(1) - } - } -} - -mergeRelease() diff --git a/script/prepare-release.js b/script/prepare-release.js index ccc9057693f..69882928765 100755 --- a/script/prepare-release.js +++ b/script/prepare-release.js @@ -6,11 +6,12 @@ const assert = require('assert') const ciReleaseBuild = require('./ci-release-build') const { execSync } = require('child_process') const fail = '\u2717'.red -const { GitProcess, GitError } = require('dugite') +const { GitProcess } = require('dugite') const GitHub = require('github') const pass = '\u2713'.green const path = require('path') const pkg = require('../package.json') +const readline = require('readline') const versionType = args._[0] // TODO (future) automatically determine version based on conventional commits @@ -27,36 +28,23 @@ const github = new GitHub() const gitDir = path.resolve(__dirname, '..') github.authenticate({type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN}) -async function createReleaseBranch () { - console.log(`Creating release branch.`) - let checkoutDetails = await GitProcess.exec([ 'checkout', '-b', 'release' ], gitDir) - if (checkoutDetails.exitCode === 0) { - console.log(`${pass} Successfully created the release branch.`) - } else { - const error = GitProcess.parseError(checkoutDetails.stderr) - if (error === GitError.BranchAlreadyExists) { - console.log(`${fail} Release branch already exists, aborting prepare ` + - `release process.`) - } else { - console.log(`${fail} Error creating release branch: ` + - `${checkoutDetails.stderr}`) - } - process.exit(1) - } -} - -function getNewVersion () { +function getNewVersion (dryRun) { console.log(`Bumping for new "${versionType}" version.`) let bumpScript = path.join(__dirname, 'bump-version.py') let scriptArgs = [bumpScript, `--bump ${versionType}`] if (args.stable) { scriptArgs.push('--stable') } + if (dryRun) { + scriptArgs.push('--dry-run') + } try { let bumpVersion = execSync(scriptArgs.join(' '), {encoding: 'UTF-8'}) bumpVersion = bumpVersion.substr(bumpVersion.indexOf(':') + 1).trim() let newVersion = `v${bumpVersion}` - console.log(`${pass} Successfully bumped version to ${newVersion}`) + if (!dryRun) { + console.log(`${pass} Successfully bumped version to ${newVersion}`) + } return newVersion } catch (err) { console.log(`${fail} Could not bump version, error was:`, err) @@ -92,7 +80,7 @@ async function getReleaseNotes (currentBranch) { console.log(`Checking for commits from ${pkg.version} to ${currentBranch}`) let commitComparison = await github.repos.compareCommits(githubOpts) .catch(err => { - console.log(`{$fail} Error checking for commits from ${pkg.version} to ` + + console.log(`${fail} Error checking for commits from ${pkg.version} to ` + `${currentBranch}`, err) process.exit(1) }) @@ -110,6 +98,7 @@ async function getReleaseNotes (currentBranch) { async function createRelease (branchToTarget, isBeta) { let releaseNotes = await getReleaseNotes(branchToTarget) let newVersion = getNewVersion() + await tagRelease(newVersion) const githubOpts = { owner: 'electron', repo: 'electron' @@ -119,23 +108,25 @@ async function createRelease (branchToTarget, isBeta) { .catch(err => { console.log('$fail} Could not get releases. Error was', err) }) - let drafts = releases.data.filter(release => release.draft) + let drafts = releases.data.filter(release => release.draft && + release.tag_name === newVersion) if (drafts.length > 0) { console.log(`${fail} Aborting because draft release for ${drafts[0].tag_name} already exists.`) process.exit(1) } console.log(`${pass} A draft release does not exist; creating one.`) - githubOpts.body = releaseNotes githubOpts.draft = true githubOpts.name = `electron ${newVersion}` if (isBeta) { githubOpts.body = `Note: This is a beta release. Please file new issues ` + `for any bugs you find in it.\n \n This release is published to npm ` + `under the beta tag and can be installed via npm install electron@beta, ` + - `or npm i electron@${newVersion.substr(1)}.` + `or npm i electron@${newVersion.substr(1)}.\n \n ${releaseNotes}` githubOpts.name = `${githubOpts.name}` githubOpts.prerelease = true + } else { + githubOpts.body = releaseNotes } githubOpts.tag_name = newVersion githubOpts.target_commitish = branchToTarget @@ -148,33 +139,69 @@ async function createRelease (branchToTarget, isBeta) { } async function pushRelease () { - let pushDetails = await GitProcess.exec(['push', 'origin', 'HEAD'], gitDir) + let pushDetails = await GitProcess.exec(['push', 'origin', 'HEAD', '--follow-tags'], gitDir) if (pushDetails.exitCode === 0) { - console.log(`${pass} Successfully pushed the release branch. Wait for ` + + console.log(`${pass} Successfully pushed the release. Wait for ` + `release builds to finish before running "npm run release".`) } else { - console.log(`${fail} Error pushing the release branch: ` + + console.log(`${fail} Error pushing the release: ` + `${pushDetails.stderr}`) process.exit(1) } } -async function runReleaseBuilds () { - await ciReleaseBuild('release', { +async function runReleaseBuilds (branch) { + await ciReleaseBuild(branch, { ghRelease: true }) } +async function tagRelease (version) { + console.log(`Tagging release ${version}.`) + let checkoutDetails = await GitProcess.exec([ 'tag', '-a', '-m', version, version ], gitDir) + if (checkoutDetails.exitCode === 0) { + console.log(`${pass} Successfully tagged ${version}.`) + } else { + console.log(`${fail} Error tagging ${version}: ` + + `${checkoutDetails.stderr}`) + process.exit(1) + } +} + +async function verifyNewVersion () { + let newVersion = getNewVersion(true) + let response = await promptForVersion(newVersion) + if (response.match(/^y/i)) { + console.log(`${pass} Starting release of ${newVersion}`) + } else { + console.log(`${fail} Aborting release of ${newVersion}`) + process.exit() + } +} + +async function promptForVersion (version) { + return new Promise((resolve, reject) => { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }) + rl.question(`Do you want to create the release ${version.green} (y/N)? `, (answer) => { + rl.close() + resolve(answer) + }) + }) +} + async function prepareRelease (isBeta, notesOnly) { let currentBranch = await getCurrentBranch(gitDir) if (notesOnly) { let releaseNotes = await getReleaseNotes(currentBranch) console.log(`Draft release notes are: ${releaseNotes}`) } else { - await createReleaseBranch() + await verifyNewVersion() await createRelease(currentBranch, isBeta) await pushRelease() - await runReleaseBuilds() + await runReleaseBuilds(currentBranch) } } diff --git a/script/publish-to-npm.js b/script/publish-to-npm.js index aaf93f33cd9..eb0702ff5ef 100644 --- a/script/publish-to-npm.js +++ b/script/publish-to-npm.js @@ -21,7 +21,8 @@ const files = [ 'index.js', 'install.js', 'package.json', - 'README.md' + 'README.md', + 'LICENSE' ] const jsonFields = [ @@ -49,9 +50,10 @@ new Promise((resolve, reject) => { tempDir = dirPath // copy files from `/npm` to temp directory files.forEach((name) => { + const noThirdSegment = name === 'README.md' || name === 'LICENSE' fs.writeFileSync( path.join(tempDir, name), - fs.readFileSync(path.join(__dirname, '..', name === 'README.md' ? '' : 'npm', name)) + fs.readFileSync(path.join(__dirname, '..', noThirdSegment ? '' : 'npm', name)) ) }) // copy from root package.json to temp/package.json diff --git a/script/release.js b/script/release.js index 50ab0aff089..7ddd069e216 100755 --- a/script/release.js +++ b/script/release.js @@ -6,7 +6,6 @@ const assert = require('assert') const fs = require('fs') const { execSync } = require('child_process') const GitHub = require('github') -const { GitProcess } = require('dugite') const nugget = require('nugget') const pkg = require('../package.json') const pkgVersion = `v${pkg.version}` @@ -24,28 +23,24 @@ const github = new GitHub({ followRedirects: false }) github.authenticate({type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN}) -const gitDir = path.resolve(__dirname, '..') async function getDraftRelease (version, skipValidation) { let releaseInfo = await github.repos.getReleases({owner: 'electron', repo: 'electron'}) let drafts let versionToCheck if (version) { - drafts = releaseInfo.data - .filter(release => release.tag_name === version) versionToCheck = version } else { - drafts = releaseInfo.data - .filter(release => release.draft) versionToCheck = pkgVersion } - + drafts = releaseInfo.data + .filter(release => release.tag_name === versionToCheck && + release.draft === true) const draft = drafts[0] if (!skipValidation) { failureCount = 0 check(drafts.length === 1, 'one draft exists', true) - check(draft.tag_name === versionToCheck, `draft release version matches local package.json (${versionToCheck})`) - if (versionToCheck.indexOf('beta')) { + if (versionToCheck.indexOf('beta') > -1) { check(draft.prerelease, 'draft is a prerelease') } check(draft.body.length > 50 && !draft.body.includes('(placeholder)'), 'draft has release notes') @@ -54,8 +49,8 @@ async function getDraftRelease (version, skipValidation) { return draft } -async function validateReleaseAssets (release) { - const requiredAssets = assetsForVersion(release.tag_name).sort() +async function validateReleaseAssets (release, validatingRelease) { + const requiredAssets = assetsForVersion(release.tag_name, validatingRelease).sort() const extantAssets = release.assets.map(asset => asset.name).sort() const downloadUrls = release.assets.map(asset => asset.browser_download_url).sort() @@ -65,16 +60,18 @@ async function validateReleaseAssets (release) { }) check((failureCount === 0), `All required GitHub assets exist for release`, true) - if (release.draft) { - await verifyAssets(release) - } else { - await verifyShasums(downloadUrls) - .catch(err => { - console.log(`${fail} error verifyingShasums`, err) - }) + if (!validatingRelease || !release.draft) { + if (release.draft) { + await verifyAssets(release) + } else { + await verifyShasums(downloadUrls) + .catch(err => { + console.log(`${fail} error verifyingShasums`, err) + }) + } + const s3Urls = s3UrlsForVersion(release.tag_name) + await verifyShasums(s3Urls, true) } - const s3Urls = s3UrlsForVersion(release.tag_name) - await verifyShasums(s3Urls, true) } function check (condition, statement, exitIfFail = false) { @@ -87,7 +84,7 @@ function check (condition, statement, exitIfFail = false) { } } -function assetsForVersion (version) { +function assetsForVersion (version, validatingRelease) { const patterns = [ `electron-${version}-darwin-x64-dsym.zip`, `electron-${version}-darwin-x64-symbols.zip`, @@ -123,9 +120,11 @@ function assetsForVersion (version) { `ffmpeg-${version}-linux-x64.zip`, `ffmpeg-${version}-mas-x64.zip`, `ffmpeg-${version}-win32-ia32.zip`, - `ffmpeg-${version}-win32-x64.zip`, - `SHASUMS256.txt` + `ffmpeg-${version}-win32-x64.zip` ] + if (!validatingRelease) { + patterns.push('SHASUMS256.txt') + } return patterns } @@ -259,9 +258,14 @@ async function publishRelease (release) { async function makeRelease (releaseToValidate) { if (releaseToValidate) { - console.log(`Validating release ${args.validateRelease}`) - let release = await getDraftRelease(args.validateRelease) - await validateReleaseAssets(release) + if (releaseToValidate === true) { + releaseToValidate = pkgVersion + } else { + console.log('Release to validate !=== true') + } + console.log(`Validating release ${releaseToValidate}`) + let release = await getDraftRelease(releaseToValidate) + await validateReleaseAssets(release, true) } else { checkVersion() let draftRelease = await getDraftRelease() @@ -272,7 +276,6 @@ async function makeRelease (releaseToValidate) { draftRelease = await getDraftRelease(pkgVersion, true) await validateReleaseAssets(draftRelease) await publishRelease(draftRelease) - await cleanupReleaseBranch() console.log(`${pass} SUCCESS!!! Release has been published. Please run ` + `"npm run publish-to-npm" to publish release to npm.`) } @@ -440,25 +443,4 @@ async function validateChecksums (validationArgs) { `shasums defined in ${validationArgs.shaSumFile}.`) } -async function cleanupReleaseBranch () { - console.log(`Cleaning up release branch.`) - let errorMessage = `Could not delete local release branch.` - let successMessage = `Successfully deleted local release branch.` - await callGit(['branch', '-D', 'release'], errorMessage, successMessage) - errorMessage = `Could not delete remote release branch.` - successMessage = `Successfully deleted remote release branch.` - return callGit(['push', 'origin', ':release'], errorMessage, successMessage) -} - -async function callGit (args, errorMessage, successMessage) { - let gitResult = await GitProcess.exec(args, gitDir) - if (gitResult.exitCode === 0) { - console.log(`${pass} ${successMessage}`) - return true - } else { - console.log(`${fail} ${errorMessage} ${gitResult.stderr}`) - process.exit(1) - } -} - makeRelease(args.validateRelease) diff --git a/script/sysroots.json b/script/sysroots.json index 4aa73dad0e7..d916f24a099 100644 --- a/script/sysroots.json +++ b/script/sysroots.json @@ -1,56 +1,38 @@ { - "jessie_amd64": { - "Revision": "d65c31e063bab0486665e087a1b4c5bb7bc7423c", - "Sha1Sum": "8b2167b36f3cd85ebbec5c2a39a1842ef613f6a2", - "SysrootDir": "debian_jessie_amd64-sysroot", - "Tarball": "debian_jessie_amd64_sysroot.tgz" + "stretch_amd64": { + "Revision": "02772eaba5440a79c6bd2d9cb7e42fa836950366", + "Sha1Sum": "69457fddca3500e2dde124f77f8382b0a18d765e", + "SysrootDir": "debian_stretch_amd64-sysroot", + "Tarball": "debian_stretch_amd64_sysroot.tgz" }, - "jessie_arm": { - "Revision": "d65c31e063bab0486665e087a1b4c5bb7bc7423c", - "Sha1Sum": "3fa13635be0c6d8ed461715ad51cdb3809a19422", - "SysrootDir": "debian_jessie_arm-sysroot", - "Tarball": "debian_jessie_arm_sysroot.tgz" + "stretch_arm": { + "Revision": "02772eaba5440a79c6bd2d9cb7e42fa836950366", + "Sha1Sum": "3e880f69177992ce02b05deeac619f7591b30287", + "SysrootDir": "debian_stretch_arm-sysroot", + "Tarball": "debian_stretch_arm_sysroot.tgz" }, - "jessie_arm64": { - "Revision": "d65c31e063bab0486665e087a1b4c5bb7bc7423c", - "Sha1Sum": "bcf92ed2a033b4b2d1032df3b53eac4910c78fde", - "SysrootDir": "debian_jessie_arm64-sysroot", - "Tarball": "debian_jessie_arm64_sysroot.tgz" + "stretch_arm64": { + "Revision": "02772eaba5440a79c6bd2d9cb7e42fa836950366", + "Sha1Sum": "8fd58c7d4b38fa3c6785573c6310cf6ca6c88312", + "SysrootDir": "debian_stretch_arm64-sysroot", + "Tarball": "debian_stretch_arm64_sysroot.tgz" }, - "jessie_i386": { - "Revision": "d65c31e063bab0486665e087a1b4c5bb7bc7423c", - "Sha1Sum": "4e83ed9a1b457a1ca59512c3d4823e87e950deb4", - "SysrootDir": "debian_jessie_i386-sysroot", - "Tarball": "debian_jessie_i386_sysroot.tgz" + "stretch_i386": { + "Revision": "02772eaba5440a79c6bd2d9cb7e42fa836950366", + "Sha1Sum": "1bd14db5eb0466064659126d398b38220013fb38", + "SysrootDir": "debian_stretch_i386-sysroot", + "Tarball": "debian_stretch_i386_sysroot.tgz" }, - "jessie_mips": { - "Revision": "d65c31e063bab0486665e087a1b4c5bb7bc7423c", - "Sha1Sum": "0f550cd150f077a6e749a629e2fda0f0a4348eae", - "SysrootDir": "debian_jessie_mips-sysroot", - "Tarball": "debian_jessie_mips_sysroot.tgz" + "stretch_mips": { + "Revision": "02772eaba5440a79c6bd2d9cb7e42fa836950366", + "Sha1Sum": "285751660ffab14e6d052c8ddb5c90752a51704d", + "SysrootDir": "debian_stretch_mips-sysroot", + "Tarball": "debian_stretch_mips_sysroot.tgz" }, - "wheezy_amd64": { - "Revision": "d65c31e063bab0486665e087a1b4c5bb7bc7423c", - "Sha1Sum": "fe372c4394ece7fd1d853a205de8c13b46e2f45a", - "SysrootDir": "debian_wheezy_amd64-sysroot", - "Tarball": "debian_wheezy_amd64_sysroot.tgz" - }, - "wheezy_arm": { - "Revision": "d65c31e063bab0486665e087a1b4c5bb7bc7423c", - "Sha1Sum": "73323fae0b5597398a38f0d7e64f48309da112fa", - "SysrootDir": "debian_wheezy_arm-sysroot", - "Tarball": "debian_wheezy_arm_sysroot.tgz" - }, - "wheezy_i386": { - "Revision": "d65c31e063bab0486665e087a1b4c5bb7bc7423c", - "Sha1Sum": "7e96584297a5c9916b3bed4b88abe332fc79a247", - "SysrootDir": "debian_wheezy_i386-sysroot", - "Tarball": "debian_wheezy_i386_sysroot.tgz" - }, - "wheezy_mips": { - "Revision": "d65c31e063bab0486665e087a1b4c5bb7bc7423c", - "Sha1Sum": "b2f173905a41ec9f23c329fe529508b4bdc76230", - "SysrootDir": "debian_wheezy_mips-sysroot", - "Tarball": "debian_wheezy_mips_sysroot.tgz" + "stretch_mips64el": { + "Revision": "02772eaba5440a79c6bd2d9cb7e42fa836950366", + "Sha1Sum": "23f51f29bc35a550092dde41dc823780fdb50f9e", + "SysrootDir": "debian_stretch_mips64el-sysroot", + "Tarball": "debian_stretch_mips64el_sysroot.tgz" } } diff --git a/script/test.py b/script/test.py index b09cff58280..1f90ed795fc 100755 --- a/script/test.py +++ b/script/test.py @@ -10,6 +10,20 @@ from lib.config import enable_verbose_mode from lib.util import electron_gyp, execute_stdout, rm_rf +if sys.platform == 'linux2': + # On Linux we use python-dbusmock to create a fake system bus and test + # powerMonitor interaction with org.freedesktop.login1 service. The + # dbus_mock module takes care of setting up the fake server with mock, + # while also setting DBUS_SYSTEM_BUS_ADDRESS environment variable, which + # will be picked up by electron. + try: + import lib.dbus_mock + except ImportError: + # If not available, the powerMonitor tests will be skipped since + # DBUS_SYSTEM_BUS_ADDRESS will not be set + pass + + SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) PROJECT_NAME = electron_gyp()['project_name%'] @@ -48,6 +62,7 @@ def main(): try: if args.use_instrumented_asar: install_instrumented_asar_file(resources_path) + os.environ["ELECTRON_DISABLE_SECURITY_WARNINGS"] = "1" subprocess.check_call([electron, 'spec'] + sys.argv[1:]) except subprocess.CalledProcessError as e: returncode = e.returncode diff --git a/script/update-clang.sh b/script/update-clang.sh index a35eb2f5b90..9a0a88dcb2e 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=307486 +CLANG_REVISION=310694 # 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 a207ad3a1a1..e67ac996e26 100755 --- a/script/update-external-binaries.py +++ b/script/update-external-binaries.py @@ -8,7 +8,7 @@ from lib.config import get_target_arch from lib.util import safe_mkdir, rm_rf, extract_zip, tempdir, download -VERSION = 'v1.2.2' +VERSION = 'v1.3.0' SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) FRAMEWORKS_URL = 'http://github.com/electron/electron-frameworks/releases' \ '/download/' + VERSION diff --git a/script/update.py b/script/update.py index b4f08bc28f2..891cc4389d5 100755 --- a/script/update.py +++ b/script/update.py @@ -28,8 +28,11 @@ def parse_args(): parser = argparse.ArgumentParser(description='Update build configurations') parser.add_argument('--defines', default='', help='The build variables passed to gyp') - parser.add_argument('--msvs', action='store_true', + group = parser.add_mutually_exclusive_group(required=False) + group.add_argument('--msvs', action='store_true', help='Generate Visual Studio project') + group.add_argument('--xcode', action='store_true', + help='Generate XCode project') return parser.parse_args() @@ -91,6 +94,8 @@ def run_gyp(target_arch, component): generator = 'ninja' if args.msvs: generator = 'msvs-ninja' + elif args.xcode: + generator = 'xcode-ninja' return subprocess.call([python, gyp, '-f', generator, '--depth', '.', 'electron.gyp', '-Icommon.gypi'] + defines, env=env) diff --git a/script/upload.py b/script/upload.py index 2be84be82d7..4da63043381 100755 --- a/script/upload.py +++ b/script/upload.py @@ -179,7 +179,7 @@ def get_text_with_editor(name): def create_or_get_release_draft(github, releases, tag, tag_exists): # Search for existing draft. for release in releases: - if release['draft']: + if release['draft'] and release['tag_name'] == tag: return release if tag_exists: diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index 2807df60702..66e9f4caf56 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -7,7 +7,7 @@ const path = require('path') const {ipcRenderer, remote} = require('electron') const {closeWindow} = require('./window-helpers') -const {app, BrowserWindow, ipcMain} = remote +const {app, BrowserWindow, Menu, ipcMain} = remote const isCI = remote.getGlobal('isCi') @@ -44,7 +44,7 @@ describe('app module', () => { let server, secureUrl const certPath = path.join(__dirname, 'fixtures', 'certificates') - before(() => { + before((done) => { const options = { key: fs.readFileSync(path.join(certPath, 'server.key')), cert: fs.readFileSync(path.join(certPath, 'server.pem')), @@ -69,11 +69,12 @@ describe('app module', () => { server.listen(0, '127.0.0.1', () => { const port = server.address().port secureUrl = `https://127.0.0.1:${port}` + done() }) }) - after(() => { - server.close() + after((done) => { + server.close(() => done()) }) describe('app.getVersion()', () => { @@ -157,6 +158,26 @@ describe('app module', () => { done() }) }) + + it('exits gracefully', function (done) { + if (!['darwin', 'linux'].includes(process.platform)) { + this.skip() + } + + const electronPath = remote.getGlobal('process').execPath + const appPath = path.join(__dirname, 'fixtures', 'api', 'singleton') + appProcess = ChildProcess.spawn(electronPath, [appPath]) + + // Singleton will send us greeting data to let us know it's running. + // After that, ask it to exit gracefully and confirm that it does. + appProcess.stdout.on('data', (data) => appProcess.kill()) + appProcess.on('exit', (code, sig) => { + let message = ['code:', code, 'sig:', sig].join('\n') + assert.equal(code, 0, message) + assert.equal(sig, null, message) + done() + }) + }) }) describe('app.makeSingleInstance', () => { @@ -487,7 +508,6 @@ describe('app module', () => { it('can respond with empty certificate list', (done) => { w.webContents.on('did-finish-load', () => { assert.equal(w.webContents.getTitle(), 'denied') - server.close() done() }) @@ -504,9 +524,34 @@ describe('app module', () => { '--process-start-args', `"--hidden"` ] + let Winreg + let classesKey + before(function () { if (process.platform !== 'win32') { this.skip() + } else { + Winreg = require('winreg') + + classesKey = new Winreg({ + hive: Winreg.HKCU, + key: '\\Software\\Classes\\' + }) + } + }) + + after(function (done) { + if (process.platform !== 'win32') { + done() + } else { + const protocolKey = new Winreg({ + hive: Winreg.HKCU, + key: `\\Software\\Classes\\${protocol}` + }) + + // The last test leaves the registry dirty, + // delete the protocol key for those of us who test at home + protocolKey.destroy(() => done()) } }) @@ -534,6 +579,109 @@ describe('app module', () => { assert.equal(app.isDefaultProtocolClient(protocol, updateExe, processStartArgs), true) assert.equal(app.isDefaultProtocolClient(protocol), false) }) + + it('creates a registry entry for the protocol class', (done) => { + app.setAsDefaultProtocolClient(protocol) + + classesKey.keys((error, keys) => { + if (error) { + throw error + } + + const exists = !!keys.find((key) => key.key.includes(protocol)) + assert.equal(exists, true) + + done() + }) + }) + + it('completely removes a registry entry for the protocol class', (done) => { + app.setAsDefaultProtocolClient(protocol) + app.removeAsDefaultProtocolClient(protocol) + + classesKey.keys((error, keys) => { + if (error) { + throw error + } + + const exists = !!keys.find((key) => key.key.includes(protocol)) + assert.equal(exists, false) + + done() + }) + }) + + it('only unsets a class registry key if it contains other data', (done) => { + app.setAsDefaultProtocolClient(protocol) + + const protocolKey = new Winreg({ + hive: Winreg.HKCU, + key: `\\Software\\Classes\\${protocol}` + }) + + protocolKey.set('test-value', 'REG_BINARY', '123', () => { + app.removeAsDefaultProtocolClient(protocol) + + classesKey.keys((error, keys) => { + if (error) { + throw error + } + + const exists = !!keys.find((key) => key.key.includes(protocol)) + assert.equal(exists, true) + + done() + }) + }) + }) + }) + + describe('app launch through uri', () => { + before(function () { + if (process.platform !== 'win32') { + this.skip() + } + }) + + it('does not launch for blacklisted argument', function (done) { + const appPath = path.join(__dirname, 'fixtures', 'api', 'quit-app') + // App should exit with non 123 code. + const first = ChildProcess.spawn(remote.process.execPath, [appPath, 'electron-test://?', '--no-sandbox', '--gpu-launcher=cmd.exe /c start calc']) + first.once('exit', (code) => { + assert.notEqual(code, 123) + done() + }) + }) + + it('launches successfully for multiple uris in cmd args', function (done) { + const appPath = path.join(__dirname, 'fixtures', 'api', 'quit-app') + // App should exit with code 123. + const first = ChildProcess.spawn(remote.process.execPath, [appPath, 'http://electronjs.org', 'electron-test://testdata']) + first.once('exit', (code) => { + assert.equal(code, 123) + done() + }) + }) + + it('does not launch for encoded space', function (done) { + const appPath = path.join(__dirname, 'fixtures', 'api', 'quit-app') + // App should exit with non 123 code. + const first = ChildProcess.spawn(remote.process.execPath, [appPath, 'electron-test://?', '--no-sandbox', '--gpu-launcher%20"cmd.exe /c start calc']) + first.once('exit', (code) => { + assert.notEqual(code, 123) + done() + }) + }) + + it('launches successfully for argnames similar to blacklisted ones', function (done) { + const appPath = path.join(__dirname, 'fixtures', 'api', 'quit-app') + // inspect is blacklisted, but inspector should work, and app launch should succeed + const first = ChildProcess.spawn(remote.process.execPath, [appPath, 'electron-test://?', '--inspector']) + first.once('exit', (code) => { + assert.equal(code, 123) + done() + }) + }) }) describe('getFileIcon() API', () => { @@ -735,4 +883,18 @@ describe('app module', () => { }, /before app is ready/) }) }) + + describe('dock.setMenu', () => { + before(function () { + if (process.platform !== 'darwin') { + this.skip() + } + }) + + it('keeps references to the menu', () => { + app.dock.setMenu(new Menu()) + const v8Util = process.atomBinding('v8_util') + v8Util.requestGarbageCollectionForTesting() + }) + }) }) diff --git a/spec/api-auto-updater-spec.js b/spec/api-auto-updater-spec.js index a9324c1705b..c2a26326135 100644 --- a/spec/api-auto-updater-spec.js +++ b/spec/api-auto-updater-spec.js @@ -29,24 +29,6 @@ describe('autoUpdater module', function () { }) }) - describe('setFeedURL', function () { - describe('on Mac', function () { - before(function () { - if (process.platform !== 'darwin') { - this.skip() - } - }) - - it('emits an error when the application is unsigned', function (done) { - ipcRenderer.once('auto-updater-error', function (event, message) { - assert.equal(message, 'Could not get code signature for running application') - done() - }) - autoUpdater.setFeedURL('') - }) - }) - }) - describe('getFeedURL', function () { it('returns a falsey value by default', function () { assert.ok(!autoUpdater.getFeedURL()) @@ -66,6 +48,83 @@ describe('autoUpdater module', function () { }) }) + describe('setFeedURL', function () { + describe('on Mac or Windows', () => { + const noThrow = (fn) => { + try { fn() } catch (err) {} + } + + before(function () { + if (process.platform !== 'win32' && process.platform !== 'darwin') { + this.skip() + } + }) + + it('sets url successfully using old (url, headers) syntax', () => { + noThrow(() => autoUpdater.setFeedURL('http://electronjs.org', { header: 'val' })) + assert.equal(autoUpdater.getFeedURL(), 'http://electronjs.org') + }) + + it('throws if no url is provided when using the old style', () => { + assert.throws( + () => autoUpdater.setFeedURL(), + err => err.message.includes('Expected an options object with a \'url\' property to be provided') // eslint-disable-line + ) + }) + + it('sets url successfully using new ({ url }) syntax', () => { + noThrow(() => autoUpdater.setFeedURL({ url: 'http://mymagicurl.local' })) + assert.equal(autoUpdater.getFeedURL(), 'http://mymagicurl.local') + }) + + it('throws if no url is provided when using the new style', () => { + assert.throws( + () => autoUpdater.setFeedURL({ noUrl: 'lol' }), + err => err.message.includes('Expected options object to contain a \'url\' string property in setFeedUrl call') // eslint-disable-line + ) + }) + }) + + describe('on Mac', function () { + const isServerTypeError = (err) => err.message.includes('Expected serverType to be \'default\' or \'json\'') + + before(function () { + if (process.platform !== 'darwin') { + this.skip() + } + }) + + it('emits an error when the application is unsigned', function (done) { + ipcRenderer.once('auto-updater-error', function (event, message) { + assert.equal(message, 'Could not get code signature for running application') + done() + }) + autoUpdater.setFeedURL('') + }) + + it('does not throw if default is the serverType', () => { + assert.doesNotThrow( + () => autoUpdater.setFeedURL({ url: '', serverType: 'default' }), + isServerTypeError + ) + }) + + it('does not throw if json is the serverType', () => { + assert.doesNotThrow( + () => autoUpdater.setFeedURL({ url: '', serverType: 'default' }), + isServerTypeError + ) + }) + + it('does throw if an unknown string is the serverType', () => { + assert.throws( + () => autoUpdater.setFeedURL({ url: '', serverType: 'weow' }), + isServerTypeError + ) + }) + }) + }) + describe('quitAndInstall', function () { it('emits an error on Windows when no update is available', function (done) { if (process.platform !== 'win32') { diff --git a/spec/api-browser-window-affinity-spec.js b/spec/api-browser-window-affinity-spec.js new file mode 100644 index 00000000000..f4b70cb6e13 --- /dev/null +++ b/spec/api-browser-window-affinity-spec.js @@ -0,0 +1,153 @@ +'use strict' + +const assert = require('assert') +const path = require('path') + +const { remote } = require('electron') +const { ipcMain, BrowserWindow } = remote +const {closeWindow} = require('./window-helpers') + +describe('BrowserWindow with affinity module', () => { + const fixtures = path.resolve(__dirname, 'fixtures') + const myAffinityName = 'myAffinity' + const myAffinityNameUpper = 'MYAFFINITY' + const anotherAffinityName = 'anotherAffinity' + + function createWindowWithWebPrefs (webPrefs) { + return new Promise((resolve, reject) => { + const w = new BrowserWindow({ + show: false, + width: 400, + height: 400, + webPreferences: webPrefs || {} + }) + w.webContents.on('did-finish-load', () => { + resolve(w) + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'blank.html')) + }) + } + + describe(`BrowserWindow with an affinity '${myAffinityName}'`, () => { + let mAffinityWindow + before((done) => { + createWindowWithWebPrefs({ affinity: myAffinityName }) + .then((w) => { + mAffinityWindow = w + done() + }) + }) + + after((done) => { + closeWindow(mAffinityWindow, {assertSingleWindow: false}).then(() => { + mAffinityWindow = null + done() + }) + }) + + it('should have a different process id than a default window', (done) => { + createWindowWithWebPrefs({}) + .then((w) => { + assert.notEqual(mAffinityWindow.webContents.getOSProcessId(), w.webContents.getOSProcessId(), 'Should have the different OS process Id/s') + closeWindow(w, {assertSingleWindow: false}).then(() => { + done() + }) + }) + }) + + it(`should have a different process id than a window with a different affinity '${anotherAffinityName}'`, (done) => { + createWindowWithWebPrefs({ affinity: anotherAffinityName }) + .then((w) => { + assert.notEqual(mAffinityWindow.webContents.getOSProcessId(), w.webContents.getOSProcessId(), 'Should have the different OS process Id/s') + closeWindow(w, {assertSingleWindow: false}).then(() => { + done() + }) + }) + }) + + it(`should have the same OS process id than a window with the same affinity '${myAffinityName}'`, (done) => { + createWindowWithWebPrefs({ affinity: myAffinityName }) + .then((w) => { + assert.equal(mAffinityWindow.webContents.getOSProcessId(), w.webContents.getOSProcessId(), 'Should have the same OS process Id') + closeWindow(w, {assertSingleWindow: false}).then(() => { + done() + }) + }) + }) + + it(`should have the same OS process id than a window with an equivalent affinity '${myAffinityNameUpper}' (case insensitive)`, (done) => { + createWindowWithWebPrefs({ affinity: myAffinityNameUpper }) + .then((w) => { + assert.equal(mAffinityWindow.webContents.getOSProcessId(), w.webContents.getOSProcessId(), 'Should have the same OS process Id') + closeWindow(w, {assertSingleWindow: false}).then(() => { + done() + }) + }) + }) + }) + + describe(`BrowserWindow with an affinity : nodeIntegration=false`, () => { + const preload = path.join(fixtures, 'module', 'send-later.js') + const affinityWithNodeTrue = 'affinityWithNodeTrue' + const affinityWithNodeFalse = 'affinityWithNodeFalse' + + function testNodeIntegration (present) { + return new Promise((resolve, reject) => { + ipcMain.once('answer', (event, typeofProcess, typeofBuffer) => { + if (present) { + assert.notEqual(typeofProcess, 'undefined') + assert.notEqual(typeofBuffer, 'undefined') + } else { + assert.equal(typeofProcess, 'undefined') + assert.equal(typeofBuffer, 'undefined') + } + resolve() + }) + }) + } + + it('disables node integration when specified to false', (done) => { + Promise.all([testNodeIntegration(false), createWindowWithWebPrefs({ affinity: affinityWithNodeTrue, preload: preload, nodeIntegration: false })]) + .then((args) => { + closeWindow(args[1], {assertSingleWindow: false}).then(() => { + done() + }) + }) + }) + it('disables node integration when first window is false', (done) => { + Promise.all([testNodeIntegration(false), createWindowWithWebPrefs({ affinity: affinityWithNodeTrue, preload: preload, nodeIntegration: false })]) + .then((args) => { + let w1 = args[1] + return Promise.all([testNodeIntegration(false), w1, createWindowWithWebPrefs({ affinity: affinityWithNodeTrue, preload: preload, nodeIntegration: true })]) + }) + .then((ws) => { + return Promise.all([closeWindow(ws[1], {assertSingleWindow: false}), closeWindow(ws[2], {assertSingleWindow: false})]) + }) + .then(() => { + done() + }) + }) + + it('enables node integration when specified to true', (done) => { + Promise.all([testNodeIntegration(true), createWindowWithWebPrefs({ affinity: affinityWithNodeFalse, preload: preload, nodeIntegration: true })]) + .then((args) => { + closeWindow(args[1], {assertSingleWindow: false}).then(() => { + done() + }) + }) + }) + it('enables node integration when first window is true', (done) => { + Promise.all([testNodeIntegration(true), createWindowWithWebPrefs({ affinity: affinityWithNodeFalse, preload: preload, nodeIntegration: true })]) + .then((args) => { + let w1 = args[1] + return Promise.all([testNodeIntegration(true), w1, createWindowWithWebPrefs({ affinity: affinityWithNodeFalse, preload: preload, nodeIntegration: false })]) + }) + .then((ws) => { + return Promise.all([closeWindow(ws[1], {assertSingleWindow: false}), closeWindow(ws[2], {assertSingleWindow: false})]) + }) + .then(() => { + done() + }) + }) + }) +}) diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index d04c340dbb9..9afccc7966d 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -9,7 +9,7 @@ const http = require('http') const {closeWindow} = require('./window-helpers') const {ipcRenderer, remote, screen} = require('electron') -const {app, ipcMain, BrowserWindow, BrowserView, protocol, webContents} = remote +const {app, ipcMain, BrowserWindow, BrowserView, protocol, session, webContents} = remote const isCI = remote.getGlobal('isCi') const nativeModulesEnabled = remote.getGlobal('nativeModulesEnabled') @@ -86,6 +86,16 @@ describe('BrowserWindow module', () => { return closeWindow(w).then(() => { w = null }) }) + describe('BrowserWindow constructor', () => { + it('allows passing void 0 as the webContents', () => { + w.close() + w = null + w = new BrowserWindow({ + webContents: void 0 + }) + }) + }) + describe('BrowserWindow.close()', () => { let server @@ -703,8 +713,7 @@ describe('BrowserWindow module', () => { }) }) - // FIXME(alexeykuzmin): Fails on Mac. - xdescribe('BrowserWindow.addTabbedWindow()', () => { + describe('BrowserWindow.addTabbedWindow()', () => { before(function () { if (process.platform !== 'darwin') { this.skip() @@ -716,7 +725,19 @@ describe('BrowserWindow module', () => { assert.doesNotThrow(() => { w.addTabbedWindow(tabbedWindow) }) - closeWindow(tabbedWindow).then(done) + + assert.equal(BrowserWindow.getAllWindows().length, 3) // Test window + w + tabbedWindow + + closeWindow(tabbedWindow, {assertSingleWindow: false}).then(() => { + assert.equal(BrowserWindow.getAllWindows().length, 2) // Test window + w + done() + }) + }) + + it('throws when called on itself', () => { + assert.throws(() => { + w.addTabbedWindow(w) + }, /AddTabbedWindow cannot be called by a window on itself./) }) }) @@ -912,7 +933,7 @@ describe('BrowserWindow module', () => { show: false, width: 400, height: 400, - titleBarStyle: 'hidden-inset' + titleBarStyle: 'hiddenInset' }) const contentSize = w.getContentSize() assert.equal(contentSize[1], 400) @@ -1021,6 +1042,79 @@ describe('BrowserWindow module', () => { }) }) + describe('session preload scripts', function () { + const preloads = [ + path.join(fixtures, 'module', 'set-global-preload-1.js'), + path.join(fixtures, 'module', 'set-global-preload-2.js') + ] + const defaultSession = session.defaultSession + + beforeEach(() => { + assert.deepEqual(defaultSession.getPreloads(), []) + defaultSession.setPreloads(preloads) + }) + afterEach(() => { + defaultSession.setPreloads([]) + }) + + it('can set multiple session preload script', function () { + assert.deepEqual(defaultSession.getPreloads(), preloads) + }) + + it('loads the script before other scripts in window including normal preloads', function (done) { + ipcMain.once('vars', function (event, preload1, preload2, preload3) { + assert.equal(preload1, 'preload-1') + assert.equal(preload2, 'preload-1-2') + assert.equal(preload3, 'preload-1-2-3') + done() + }) + w.destroy() + w = new BrowserWindow({ + show: false, + webPreferences: { + preload: path.join(fixtures, 'module', 'set-global-preload-3.js') + } + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'preloads.html')) + }) + }) + + describe('"additionalArguments" option', () => { + it('adds extra args to process.argv in the renderer process', (done) => { + const preload = path.join(fixtures, 'module', 'check-arguments.js') + ipcMain.once('answer', (event, argv) => { + assert.ok(argv.includes('--my-magic-arg')) + done() + }) + w.destroy() + w = new BrowserWindow({ + show: false, + webPreferences: { + preload: preload, + additionalArguments: ['--my-magic-arg'] + } + }) + w.loadURL(`file://${path.join(fixtures, 'api', 'blank.html')}`) + }) + + it('adds extra value args to process.argv in the renderer process', (done) => { + const preload = path.join(fixtures, 'module', 'check-arguments.js') + ipcMain.once('answer', (event, argv) => { + assert.ok(argv.includes('--my-magic-arg=foo')) + done() + }) + w.destroy() + w = new BrowserWindow({ + show: false, + webPreferences: { + preload: preload, + additionalArguments: ['--my-magic-arg=foo'] + } + }) + w.loadURL(`file://${path.join(fixtures, 'api', 'blank.html')}`) + }) + }) + describe('"node-integration" option', () => { it('disables node integration when specified to false', (done) => { const preload = path.join(fixtures, 'module', 'send-later.js') diff --git a/spec/api-crash-reporter-spec.js b/spec/api-crash-reporter-spec.js index a24373f2338..b4d55cd70b7 100644 --- a/spec/api-crash-reporter-spec.js +++ b/spec/api-crash-reporter-spec.js @@ -55,7 +55,6 @@ describe('crashReporter module', () => { it('should send minidump when renderer crashes', function (done) { // TODO(alexeykuzmin): Skip the test instead of marking it as passed. if (process.env.APPVEYOR === 'True') return done() - if (process.env.TRAVIS === 'true') return done() this.timeout(180000) @@ -76,7 +75,6 @@ describe('crashReporter module', () => { it('should send minidump when node processes crash', function (done) { // TODO(alexeykuzmin): Skip the test instead of marking it as passed. if (process.env.APPVEYOR === 'True') return done() - if (process.env.TRAVIS === 'true') return done() this.timeout(180000) @@ -172,7 +170,6 @@ describe('crashReporter module', () => { it('should send minidump with updated extra parameters', function (done) { // TODO(alexeykuzmin): Skip the test instead of marking it as passed. if (process.env.APPVEYOR === 'True') return done() - if (process.env.TRAVIS === 'true') return done() this.timeout(180000) @@ -261,11 +258,9 @@ describe('crashReporter module', () => { describe('getLastCrashReport', () => { it('correctly returns the most recent report', () => { - if (process.env.TRAVIS === 'False') { - const reports = crashReporter.getUploadedReports() - const lastReport = reports[0] - assert(lastReport != null) - } + const reports = crashReporter.getUploadedReports() + const lastReport = reports[0] + assert(lastReport != null) }) }) diff --git a/spec/api-debugger-spec.js b/spec/api-debugger-spec.js index 5902a5b0027..1d98e799394 100644 --- a/spec/api-debugger-spec.js +++ b/spec/api-debugger-spec.js @@ -134,7 +134,7 @@ describe('debugger module', () => { }) }) - it('handles invalid unicode characters in message', (done) => { + it('handles valid unicode characters in message', (done) => { try { w.webContents.debugger.attach() } catch (err) { @@ -145,12 +145,39 @@ describe('debugger module', () => { if (method === 'Network.loadingFinished') { w.webContents.debugger.sendCommand('Network.getResponseBody', { requestId: params.requestId - }, () => { + }, (_, data) => { + assert.equal(data.body, '\u0024') done() }) } }) + server = http.createServer((req, res) => { + res.setHeader('Content-Type', 'text/plain; charset=utf-8') + res.end('\u0024') + }) + + server.listen(0, '127.0.0.1', () => { + w.webContents.debugger.sendCommand('Network.enable') + w.loadURL(`http://127.0.0.1:${server.address().port}`) + }) + }) + + it('does not crash for invalid unicode characters in message', (done) => { + try { + w.webContents.debugger.attach() + } catch (err) { + done(`unexpected error : ${err}`) + } + + w.webContents.debugger.on('message', (event, method, params) => { + // loadingFinished indicates that page has been loaded and it did not + // crash because of invalid UTF-8 data + if (method === 'Network.loadingFinished') { + done() + } + }) + server = http.createServer((req, res) => { res.setHeader('Content-Type', 'text/plain; charset=utf-8') res.end('\uFFFF') diff --git a/spec/api-in-app-purchase-spec.js b/spec/api-in-app-purchase-spec.js new file mode 100644 index 00000000000..c47c8d752ee --- /dev/null +++ b/spec/api-in-app-purchase-spec.js @@ -0,0 +1,33 @@ +'use strict' + +const assert = require('assert') + +const {remote} = require('electron') + +describe('inAppPurchase module', () => { + if (process.platform !== 'darwin') return + + const {inAppPurchase} = remote + + it('canMakePayments() does not throw', () => { + inAppPurchase.canMakePayments() + }) + + it('getReceiptURL() returns receipt URL', () => { + assert.ok(inAppPurchase.getReceiptURL().endsWith('_MASReceipt/receipt')) + }) + + it('purchaseProduct() fails when buying invalid product', (done) => { + inAppPurchase.purchaseProduct('non-exist', 1, (success) => { + assert.ok(!success) + done() + }) + }) + + it('purchaseProduct() accepts optional arguments', (done) => { + inAppPurchase.purchaseProduct('non-exist', () => { + inAppPurchase.purchaseProduct('non-exist', 1) + done() + }) + }) +}) diff --git a/spec/api-menu-item-spec.js b/spec/api-menu-item-spec.js new file mode 100644 index 00000000000..9bbb2e38da2 --- /dev/null +++ b/spec/api-menu-item-spec.js @@ -0,0 +1,388 @@ +const assert = require('assert') + +const {remote} = require('electron') +const {BrowserWindow, app, Menu, MenuItem} = remote +const roles = require('../lib/browser/api/menu-item-roles') +const {closeWindow} = require('./window-helpers') + +describe('MenuItems', () => { + describe('MenuItem.click', () => { + it('should be called with the item object passed', (done) => { + const menu = Menu.buildFromTemplate([{ + label: 'text', + click: (item) => { + assert.equal(item.constructor.name, 'MenuItem') + assert.equal(item.label, 'text') + done() + } + }]) + menu.delegate.executeCommand(menu, {}, menu.items[0].commandId) + }) + }) + + describe('MenuItem with checked/radio property', () => { + it('clicking an checkbox item should flip the checked property', () => { + const menu = Menu.buildFromTemplate([{ + label: 'text', + type: 'checkbox' + }]) + + assert.equal(menu.items[0].checked, false) + menu.delegate.executeCommand(menu, {}, menu.items[0].commandId) + assert.equal(menu.items[0].checked, true) + }) + + it('clicking an radio item should always make checked property true', () => { + const menu = Menu.buildFromTemplate([{ + label: 'text', + type: 'radio' + }]) + + menu.delegate.executeCommand(menu, {}, menu.items[0].commandId) + assert.equal(menu.items[0].checked, true) + menu.delegate.executeCommand(menu, {}, menu.items[0].commandId) + assert.equal(menu.items[0].checked, true) + }) + + describe('MenuItem group properties', () => { + let template = [] + + const findRadioGroups = (template) => { + let groups = [] + let cur = null + for (let i = 0; i <= template.length; i++) { + if (cur && ((i === template.length) || (template[i].type !== 'radio'))) { + cur.end = i + groups.push(cur) + cur = null + } else if (!cur && i < template.length && template[i].type === 'radio') { + cur = { begin: i } + } + } + return groups + } + + // returns array of checked menuitems in [begin,end) + const findChecked = (menuItems, begin, end) => { + let checked = [] + for (let i = begin; i < end; i++) { + if (menuItems[i].checked) checked.push(i) + } + return checked + } + + beforeEach(() => { + for (let i = 0; i <= 10; i++) { + template.push({ + label: `${i}`, + type: 'radio' + }) + } + + template.push({type: 'separator'}) + + for (let i = 12; i <= 20; i++) { + template.push({ + label: `${i}`, + type: 'radio' + }) + } + }) + + it('at least have one item checked in each group', () => { + const menu = Menu.buildFromTemplate(template) + menu.delegate.menuWillShow(menu) + + const groups = findRadioGroups(template) + + groups.forEach(g => { + assert.deepEqual(findChecked(menu.items, g.begin, g.end), [g.begin]) + }) + }) + + it('should assign groupId automatically', () => { + const menu = Menu.buildFromTemplate(template) + + let usedGroupIds = new Set() + const groups = findRadioGroups(template) + groups.forEach(g => { + const groupId = menu.items[g.begin].groupId + + // groupId should be previously unused + assert(!usedGroupIds.has(groupId)) + usedGroupIds.add(groupId) + + // everything in the group should have the same id + for (let i = g.begin; i < g.end; ++i) { + assert.equal(menu.items[i].groupId, groupId) + } + }) + }) + + it("setting 'checked' should flip other items' 'checked' property", () => { + const menu = Menu.buildFromTemplate(template) + + const groups = findRadioGroups(template) + groups.forEach(g => { + assert.deepEqual(findChecked(menu.items, g.begin, g.end), []) + + menu.items[g.begin].checked = true + assert.deepEqual(findChecked(menu.items, g.begin, g.end), [g.begin]) + + menu.items[g.end - 1].checked = true + assert.deepEqual(findChecked(menu.items, g.begin, g.end), [g.end - 1]) + }) + }) + }) + }) + + describe('MenuItem role execution', () => { + it('does not try to execute roles without a valid role property', () => { + let win = new BrowserWindow({show: false, width: 200, height: 200}) + let item = new MenuItem({role: 'asdfghjkl'}) + + const canExecute = roles.execute(item.role, win, win.webContents) + assert.equal(false, canExecute) + + closeWindow(win).then(() => { win = null }) + }) + + it('executes roles with native role functions', () => { + let win = new BrowserWindow({show: false, width: 200, height: 200}) + let item = new MenuItem({role: 'reload'}) + + const canExecute = roles.execute(item.role, win, win.webContents) + assert.equal(true, canExecute) + + closeWindow(win).then(() => { win = null }) + }) + + it('execute roles with non-native role functions', () => { + let win = new BrowserWindow({show: false, width: 200, height: 200}) + let item = new MenuItem({role: 'resetzoom'}) + + const canExecute = roles.execute(item.role, win, win.webContents) + assert.equal(true, canExecute) + + closeWindow(win).then(() => { win = null }) + }) + }) + + describe('MenuItem command id', () => { + it('cannot be overwritten', () => { + const item = new MenuItem({label: 'item'}) + + const commandId = item.commandId + assert(commandId) + item.commandId = `${commandId}-modified` + assert.equal(item.commandId, commandId) + }) + }) + + describe('MenuItem with invalid type', () => { + it('throws an exception', () => { + assert.throws(() => { + Menu.buildFromTemplate([{ + label: 'text', + type: 'not-a-type' + }]) + }, /Unknown menu item type: not-a-type/) + }) + }) + + describe('MenuItem with submenu type and missing submenu', () => { + it('throws an exception', () => { + assert.throws(() => { + Menu.buildFromTemplate([{ + label: 'text', + type: 'submenu' + }]) + }, /Invalid submenu/) + }) + }) + + describe('MenuItem role', () => { + it('returns undefined for items without default accelerator', () => { + const roleList = [ + 'close', + 'copy', + 'cut', + 'forcereload', + 'hide', + 'hideothers', + 'minimize', + 'paste', + 'pasteandmatchstyle', + 'quit', + 'redo', + 'reload', + 'resetzoom', + 'selectall', + 'toggledevtools', + 'togglefullscreen', + 'undo', + 'zoomin', + 'zoomout' + ] + + for (let role in roleList) { + const item = new MenuItem({role}) + assert.equal(item.getDefaultRoleAccelerator(), undefined) + } + }) + + it('returns the correct default label', () => { + const roleList = { + 'close': process.platform === 'darwin' ? 'Close Window' : 'Close', + 'copy': 'Copy', + 'cut': 'Cut', + 'forcereload': 'Force Reload', + 'hide': 'Hide Electron Test', + 'hideothers': 'Hide Others', + 'minimize': 'Minimize', + 'paste': 'Paste', + 'pasteandmatchstyle': 'Paste and Match Style', + 'quit': (process.platform === 'darwin') ? `Quit ${app.getName()}` : (process.platform === 'win32') ? 'Exit' : 'Quit', + 'redo': 'Redo', + 'reload': 'Reload', + 'resetzoom': 'Actual Size', + 'selectall': 'Select All', + 'toggledevtools': 'Toggle Developer Tools', + 'togglefullscreen': 'Toggle Full Screen', + 'undo': 'Undo', + 'zoomin': 'Zoom In', + 'zoomout': 'Zoom Out' + } + + for (let role in roleList) { + const item = new MenuItem({role}) + assert.equal(item.label, roleList[role]) + } + }) + + it('returns the correct default accelerator', () => { + const roleList = { + 'close': 'CommandOrControl+W', + 'copy': 'CommandOrControl+C', + 'cut': 'CommandOrControl+X', + 'forcereload': 'Shift+CmdOrCtrl+R', + 'hide': 'Command+H', + 'hideothers': 'Command+Alt+H', + 'minimize': 'CommandOrControl+M', + 'paste': 'CommandOrControl+V', + 'pasteandmatchstyle': 'Shift+CommandOrControl+V', + 'quit': process.platform === 'win32' ? null : 'CommandOrControl+Q', + 'redo': process.platform === 'win32' ? 'Control+Y' : 'Shift+CommandOrControl+Z', + 'reload': 'CmdOrCtrl+R', + 'resetzoom': 'CommandOrControl+0', + 'selectall': 'CommandOrControl+A', + 'toggledevtools': process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I', + 'togglefullscreen': process.platform === 'darwin' ? 'Control+Command+F' : 'F11', + 'undo': 'CommandOrControl+Z', + 'zoomin': 'CommandOrControl+Plus', + 'zoomout': 'CommandOrControl+-' + } + + for (let role in roleList) { + const item = new MenuItem({role}) + assert.equal(item.getDefaultRoleAccelerator(), roleList[role]) + } + }) + + it('allows a custom accelerator and label to be set', () => { + const item = new MenuItem({ + role: 'close', + label: 'Custom Close!', + accelerator: 'D' + }) + + assert.equal(item.label, 'Custom Close!') + assert.equal(item.accelerator, 'D') + assert.equal(item.getDefaultRoleAccelerator(), 'CommandOrControl+W') + }) + }) + + describe('MenuItem editMenu', () => { + it('includes a default submenu layout when submenu is empty', () => { + const item = new MenuItem({role: 'editMenu'}) + + assert.equal(item.label, 'Edit') + assert.equal(item.submenu.items[0].role, 'undo') + assert.equal(item.submenu.items[1].role, 'redo') + assert.equal(item.submenu.items[2].type, 'separator') + assert.equal(item.submenu.items[3].role, 'cut') + assert.equal(item.submenu.items[4].role, 'copy') + assert.equal(item.submenu.items[5].role, 'paste') + + if (process.platform === 'darwin') { + assert.equal(item.submenu.items[6].role, 'pasteandmatchstyle') + assert.equal(item.submenu.items[7].role, 'delete') + assert.equal(item.submenu.items[8].role, 'selectall') + } + + if (process.platform === 'win32') { + assert.equal(item.submenu.items[6].role, 'delete') + assert.equal(item.submenu.items[7].type, 'separator') + assert.equal(item.submenu.items[8].role, 'selectall') + } + }) + + it('overrides default layout when submenu is specified', () => { + const item = new MenuItem({ + role: 'editMenu', + submenu: [{ + role: 'close' + }] + }) + assert.equal(item.label, 'Edit') + assert.equal(item.submenu.items[0].role, 'close') + }) + }) + + describe('MenuItem windowMenu', () => { + it('includes a default submenu layout when submenu is empty', () => { + const item = new MenuItem({role: 'windowMenu'}) + + assert.equal(item.label, 'Window') + assert.equal(item.submenu.items[0].role, 'minimize') + assert.equal(item.submenu.items[1].role, 'close') + + if (process.platform === 'darwin') { + assert.equal(item.submenu.items[2].type, 'separator') + assert.equal(item.submenu.items[3].role, 'front') + } + }) + + it('overrides default layout when submenu is specified', () => { + const item = new MenuItem({ + role: 'windowMenu', + submenu: [{role: 'copy'}] + }) + + assert.equal(item.label, 'Window') + assert.equal(item.submenu.items[0].role, 'copy') + }) + }) + + describe('MenuItem with custom properties in constructor', () => { + it('preserves the custom properties', () => { + const template = [{ + label: 'menu 1', + customProp: 'foo', + submenu: [] + }] + + const menu = Menu.buildFromTemplate(template) + menu.items[0].submenu.append(new MenuItem({ + label: 'item 1', + customProp: 'bar', + overrideProperty: 'oops not allowed' + })) + + assert.equal(menu.items[0].customProp, 'foo') + assert.equal(menu.items[0].submenu.items[0].label, 'item 1') + assert.equal(menu.items[0].submenu.items[0].customProp, 'bar') + assert.equal(typeof menu.items[0].submenu.items[0].overrideProperty, 'function') + }) + }) +}) diff --git a/spec/api-menu-spec.js b/spec/api-menu-spec.js index 538c640e712..eb8fe3d63b2 100644 --- a/spec/api-menu-spec.js +++ b/spec/api-menu-spec.js @@ -17,17 +17,9 @@ describe('Menu module', () => { }) it('does not modify the specified template', () => { - const template = ipcRenderer.sendSync('eval', "var template = [{label: 'text', submenu: [{label: 'sub'}]}];\nrequire('electron').Menu.buildFromTemplate(template);\ntemplate;") - assert.deepStrictEqual(template, [ - { - label: 'text', - submenu: [ - { - label: 'sub' - } - ] - } - ]) + const template = [{label: 'text', submenu: [{label: 'sub'}]}] + const result = ipcRenderer.sendSync('eval', `const template = [{label: 'text', submenu: [{label: 'sub'}]}]\nrequire('electron').Menu.buildFromTemplate(template)\ntemplate`) + assert.deepStrictEqual(result, template) }) it('does not throw exceptions for undefined/null values', () => { @@ -87,15 +79,18 @@ describe('Menu module', () => { it('should position at endof existing separator groups', () => { const menu = Menu.buildFromTemplate([ { - type: 'separator', - id: 'numbers' + label: 'first', + id: 'first' }, { type: 'separator', - id: 'letters' + id: 'numbers' }, { label: 'a', id: 'a', position: 'endof=letters' + }, { + type: 'separator', + id: 'letters' }, { label: '1', id: '1', @@ -118,14 +113,59 @@ describe('Menu module', () => { 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[1].id, 'numbers') + assert.equal(menu.items[2].label, '1') + assert.equal(menu.items[3].label, '2') + assert.equal(menu.items[4].label, '3') + assert.equal(menu.items[5].id, 'letters') + assert.equal(menu.items[6].label, 'a') + assert.equal(menu.items[7].label, 'b') + assert.equal(menu.items[8].label, 'c') + }) + + it('should filter excess menu separators', () => { + const menuOne = Menu.buildFromTemplate([ + { + type: 'separator' + }, { + label: 'a' + }, { + label: 'b' + }, { + label: 'c' + }, { + type: 'separator' + } + ]) + + assert.equal(menuOne.items.length, 3) + assert.equal(menuOne.items[0].label, 'a') + assert.equal(menuOne.items[1].label, 'b') + assert.equal(menuOne.items[2].label, 'c') + + const menuTwo = Menu.buildFromTemplate([ + { + type: 'separator' + }, { + type: 'separator' + }, { + label: 'a' + }, { + label: 'b' + }, { + label: 'c' + }, { + type: 'separator' + }, { + type: 'separator' + } + ]) + + assert.equal(menuTwo.items.length, 3) + assert.equal(menuTwo.items[0].label, 'a') + assert.equal(menuTwo.items[1].label, 'b') + assert.equal(menuTwo.items[2].label, 'c') }) it('should create separator group if endof does not reference existing separator group', () => { @@ -205,10 +245,6 @@ describe('Menu module', () => { label: 'Enter Fullscreen', accelerator: 'Control+Command+F', id: 'fullScreen' - }, - { - label: 'Exit Fullscreen', - accelerator: 'Control+Command+F' } ] } @@ -221,13 +257,9 @@ describe('Menu module', () => { describe('Menu.insert', () => { it('should store item in @items by its index', () => { const menu = Menu.buildFromTemplate([ - { - label: '1' - }, { - label: '2' - }, { - label: '3' - } + {label: '1'}, + {label: '2'}, + {label: '3'} ]) const item = new MenuItem({ label: 'inserted' }) @@ -243,17 +275,14 @@ describe('Menu module', () => { describe('Menu.append', () => { it('should add the item to the end of the menu', () => { const menu = Menu.buildFromTemplate([ - { - label: '1' - }, { - label: '2' - }, { - label: '3' - } + {label: '1'}, + {label: '2'}, + {label: '3'} ]) - const item = new MenuItem({ label: 'inserted' }) + const item = new MenuItem({ label: 'inserted' }) menu.append(item) + assert.equal(menu.items[0].label, '1') assert.equal(menu.items[1].label, '2') assert.equal(menu.items[2].label, '3') @@ -268,35 +297,71 @@ describe('Menu module', () => { beforeEach(() => { w = new BrowserWindow({show: false, width: 200, height: 200}) menu = Menu.buildFromTemplate([ - { - label: '1' - }, { - label: '2' - }, { - label: '3' - } + {label: '1'}, + {label: '2'}, + {label: '3'} ]) }) afterEach(() => { + menu.closePopup() + menu.closePopup(w) return closeWindow(w).then(() => { w = null }) }) + it('should emit menu-will-show event', (done) => { + menu.on('menu-will-show', () => { done() }) + menu.popup({window: w}) + }) + + it('should emit menu-will-close event', (done) => { + menu.on('menu-will-close', () => { done() }) + menu.popup({window: w}) + menu.closePopup() + }) + it('returns immediately', () => { - menu.popup(w, {x: 100, y: 100, async: true}) - menu.closePopup(w) + const input = {window: w, x: 100, y: 101} + const output = menu.popup(input) + assert.equal(output.x, input.x) + assert.equal(output.y, input.y) + assert.equal(output.browserWindow, input.window) + }) + + it('works without a given BrowserWindow and options', () => { + const {browserWindow, x, y} = menu.popup({x: 100, y: 101}) + + assert.equal(browserWindow.constructor.name, 'BrowserWindow') + assert.equal(x, 100) + assert.equal(y, 101) + }) + + it('works with a given BrowserWindow, options and callback', (done) => { + const {x, y} = menu.popup({ + window: w, + x: 100, + y: 101, + callback: () => done() + }) + + assert.equal(x, 100) + assert.equal(y, 101) + menu.closePopup() + }) + + it('works with a given BrowserWindow, no options, and a callback', (done) => { + menu.popup({window: w, callback: () => done()}) + menu.closePopup() }) }) describe('Menu.setApplicationMenu', () => { it('sets a menu', () => { const menu = Menu.buildFromTemplate([ - { - label: '1' - }, { - label: '2' - } + {label: '1'}, + {label: '2'} ]) + Menu.setApplicationMenu(menu) assert.notEqual(Menu.getApplicationMenu(), null) }) @@ -306,281 +371,4 @@ describe('Menu module', () => { assert.equal(Menu.getApplicationMenu(), null) }) }) - - describe('MenuItem.click', () => { - it('should be called with the item object passed', function (done) { - const menu = Menu.buildFromTemplate([ - { - label: 'text', - click: function (item) { - assert.equal(item.constructor.name, 'MenuItem') - assert.equal(item.label, 'text') - done() - } - } - ]) - menu.delegate.executeCommand({}, menu.items[0].commandId) - }) - }) - - describe('MenuItem with checked property', () => { - it('clicking an checkbox item should flip the checked property', () => { - const 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) - }) - - it('clicking an radio item should always make checked property true', () => { - const 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) - }) - - it('at least have one item checked in each group', () => { - const template = [] - for (let i = 0; i <= 10; i++) { - template.push({ - label: `${i}`, - type: 'radio' - }) - } - template.push({type: 'separator'}) - for (let i = 12; i <= 20; i++) { - template.push({ - label: `${i}`, - type: 'radio' - }) - } - const 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', () => { - const template = [] - for (let i = 0; i <= 10; i++) { - template.push({ - label: `${i}`, - type: 'radio' - }) - } - template.push({type: 'separator'}) - for (let i = 12; i <= 20; i++) { - template.push({ - label: `${i}`, - type: 'radio' - }) - } - const menu = Menu.buildFromTemplate(template) - const groupId = menu.items[0].groupId - for (let i = 0; i <= 10; i++) { - assert.equal(menu.items[i].groupId, groupId) - } - for (let i = 12; i <= 20; i++) { - assert.equal(menu.items[i].groupId, groupId + 1) - } - }) - - it("setting 'checked' should flip other items' 'checked' property", () => { - const template = [] - for (let i = 0; i <= 10; i++) { - template.push({ - label: `${i}`, - type: 'radio' - }) - } - template.push({type: 'separator'}) - for (let i = 12; i <= 20; i++) { - template.push({ - label: `${i}`, - type: 'radio' - }) - } - - const menu = Menu.buildFromTemplate(template) - for (let i = 0; i <= 10; i++) { - assert.equal(menu.items[i].checked, false) - } - menu.items[0].checked = true - assert.equal(menu.items[0].checked, true) - for (let i = 1; i <= 10; i++) { - assert.equal(menu.items[i].checked, false) - } - menu.items[10].checked = true - assert.equal(menu.items[10].checked, true) - for (let i = 0; i <= 9; i++) { - assert.equal(menu.items[i].checked, false) - } - for (let i = 12; i <= 20; i++) { - assert.equal(menu.items[i].checked, false) - } - menu.items[12].checked = true - assert.equal(menu.items[10].checked, true) - for (let i = 0; i <= 9; i++) { - assert.equal(menu.items[i].checked, false) - } - assert.equal(menu.items[12].checked, true) - for (let i = 13; i <= 20; i++) { - assert.equal(menu.items[i].checked, false) - } - }) - }) - - describe('MenuItem command id', () => { - it('cannot be overwritten', () => { - const item = new MenuItem({label: 'item'}) - - const commandId = item.commandId - assert(commandId) - item.commandId = `${commandId}-modified` - assert.equal(item.commandId, commandId) - }) - }) - - describe('MenuItem with invalid type', () => { - it('throws an exception', () => { - assert.throws(() => { - Menu.buildFromTemplate([ - { - label: 'text', - type: 'not-a-type' - } - ]) - }, /Unknown menu item type: not-a-type/) - }) - }) - - describe('MenuItem with submenu type and missing submenu', () => { - it('throws an exception', () => { - assert.throws(() => { - Menu.buildFromTemplate([ - { - label: 'text', - type: 'submenu' - } - ]) - }, /Invalid submenu/) - }) - }) - - describe('MenuItem role', () => { - it('includes a default label and accelerator', () => { - let item = new MenuItem({role: 'close'}) - assert.equal(item.label, process.platform === 'darwin' ? 'Close Window' : 'Close') - assert.equal(item.accelerator, undefined) - assert.equal(item.getDefaultRoleAccelerator(), 'CommandOrControl+W') - - item = new MenuItem({role: 'close', label: 'Other', accelerator: 'D'}) - assert.equal(item.label, 'Other') - assert.equal(item.accelerator, 'D') - assert.equal(item.getDefaultRoleAccelerator(), 'CommandOrControl+W') - - item = new MenuItem({role: 'help'}) - assert.equal(item.label, 'Help') - assert.equal(item.accelerator, undefined) - assert.equal(item.getDefaultRoleAccelerator(), undefined) - - item = new MenuItem({role: 'hide'}) - assert.equal(item.label, 'Hide Electron Test') - assert.equal(item.accelerator, undefined) - assert.equal(item.getDefaultRoleAccelerator(), 'Command+H') - - item = new MenuItem({role: 'undo'}) - assert.equal(item.label, 'Undo') - assert.equal(item.accelerator, undefined) - assert.equal(item.getDefaultRoleAccelerator(), 'CommandOrControl+Z') - - item = new MenuItem({role: 'redo'}) - assert.equal(item.label, 'Redo') - assert.equal(item.accelerator, undefined) - assert.equal(item.getDefaultRoleAccelerator(), process.platform === 'win32' ? 'Control+Y' : 'Shift+CommandOrControl+Z') - }) - }) - - describe('MenuItem editMenu', () => { - it('includes a default submenu layout when submenu is empty', () => { - const item = new MenuItem({role: 'editMenu'}) - assert.equal(item.label, 'Edit') - assert.equal(item.submenu.items[0].role, 'undo') - assert.equal(item.submenu.items[1].role, 'redo') - assert.equal(item.submenu.items[2].type, 'separator') - assert.equal(item.submenu.items[3].role, 'cut') - assert.equal(item.submenu.items[4].role, 'copy') - assert.equal(item.submenu.items[5].role, 'paste') - - if (process.platform === 'darwin') { - assert.equal(item.submenu.items[6].role, 'pasteandmatchstyle') - assert.equal(item.submenu.items[7].role, 'delete') - assert.equal(item.submenu.items[8].role, 'selectall') - } - - if (process.platform === 'win32') { - assert.equal(item.submenu.items[6].role, 'delete') - assert.equal(item.submenu.items[7].type, 'separator') - assert.equal(item.submenu.items[8].role, 'selectall') - } - }) - - it('overrides default layout when submenu is specified', () => { - const item = new MenuItem({role: 'editMenu', submenu: [{role: 'close'}]}) - assert.equal(item.label, 'Edit') - assert.equal(item.submenu.items[0].role, 'close') - }) - }) - - describe('MenuItem windowMenu', () => { - it('includes a default submenu layout when submenu is empty', () => { - const item = new MenuItem({role: 'windowMenu'}) - assert.equal(item.label, 'Window') - assert.equal(item.submenu.items[0].role, 'minimize') - assert.equal(item.submenu.items[1].role, 'close') - - if (process.platform === 'darwin') { - assert.equal(item.submenu.items[2].type, 'separator') - assert.equal(item.submenu.items[3].role, 'front') - } - }) - - it('overrides default layout when submenu is specified', () => { - const item = new MenuItem({role: 'windowMenu', submenu: [{role: 'copy'}]}) - assert.equal(item.label, 'Window') - assert.equal(item.submenu.items[0].role, 'copy') - }) - }) - - describe('MenuItem with custom properties in constructor', () => { - it('preserves the custom properties', () => { - const template = [{ - label: 'menu 1', - customProp: 'foo', - submenu: [] - }] - - const menu = Menu.buildFromTemplate(template) - menu.items[0].submenu.append(new MenuItem({ - label: 'item 1', - customProp: 'bar', - overrideProperty: 'oops not allowed' - })) - - assert.equal(menu.items[0].customProp, 'foo') - assert.equal(menu.items[0].submenu.items[0].label, 'item 1') - assert.equal(menu.items[0].submenu.items[0].customProp, 'bar') - assert.equal(typeof menu.items[0].submenu.items[0].overrideProperty, 'function') - }) - }) }) diff --git a/spec/api-native-image-spec.js b/spec/api-native-image-spec.js index 2b61fda68c8..87399921ff1 100644 --- a/spec/api-native-image-spec.js +++ b/spec/api-native-image-spec.js @@ -162,9 +162,6 @@ describe('nativeImage module', () => { const imageI = nativeImage.createFromBuffer(imageA.toBitmap(), {width: 538, height: 190, scaleFactor: 2.0}) expect(imageI.getSize()).to.deep.equal({width: 269, height: 95}) - - const imageJ = nativeImage.createFromBuffer(imageA.toPNG(), 2.0) - expect(imageJ.getSize()).to.deep.equal({width: 269, height: 95}) }) }) diff --git a/spec/api-notification-spec.js b/spec/api-notification-spec.js new file mode 100644 index 00000000000..f2e9a4f801c --- /dev/null +++ b/spec/api-notification-spec.js @@ -0,0 +1,91 @@ +const assert = require('assert') + +const {Notification} = require('electron').remote + +describe('Notification module', () => { + it('inits, gets and sets basic string properties correctly', () => { + const n = new Notification({ + title: 'title', + subtitle: 'subtitle', + body: 'body', + replyPlaceholder: 'replyPlaceholder', + sound: 'sound', + closeButtonText: 'closeButtonText' + }) + + assert.equal(n.title, 'title') + n.title = 'title1' + assert.equal(n.title, 'title1') + + assert.equal(n.subtitle, 'subtitle') + n.subtitle = 'subtitle1' + assert.equal(n.subtitle, 'subtitle1') + + assert.equal(n.body, 'body') + n.body = 'body1' + assert.equal(n.body, 'body1') + + assert.equal(n.replyPlaceholder, 'replyPlaceholder') + n.replyPlaceholder = 'replyPlaceholder1' + assert.equal(n.replyPlaceholder, 'replyPlaceholder1') + + assert.equal(n.sound, 'sound') + n.sound = 'sound1' + assert.equal(n.sound, 'sound1') + + assert.equal(n.closeButtonText, 'closeButtonText') + n.closeButtonText = 'closeButtonText1' + assert.equal(n.closeButtonText, 'closeButtonText1') + }) + + it('inits, gets and sets basic boolean properties correctly', () => { + const n = new Notification({ + silent: true, + hasReply: true + }) + + assert.equal(n.silent, true) + n.silent = false + assert.equal(n.silent, false) + + assert.equal(n.hasReply, true) + n.hasReply = false + assert.equal(n.hasReply, false) + }) + + it('inits, gets and sets actions correctly', () => { + const n = new Notification({ + actions: [ + { + type: 'button', + text: '1' + }, { + type: 'button', + text: '2' + } + ] + }) + + assert.equal(n.actions[0].type, 'button') + assert.equal(n.actions[0].text, '1') + assert.equal(n.actions[1].type, 'button') + assert.equal(n.actions[1].text, '2') + + n.actions = [ + { + type: 'button', + text: '3' + }, { + type: 'button', + text: '4' + } + ] + + assert.equal(n.actions[0].type, 'button') + assert.equal(n.actions[0].text, '3') + assert.equal(n.actions[1].type, 'button') + assert.equal(n.actions[1].text, '4') + }) + + // TODO(sethlu): Find way to test init with notification icon? +}) diff --git a/spec/api-power-monitor-spec.js b/spec/api-power-monitor-spec.js new file mode 100644 index 00000000000..3b8d9ab5e4a --- /dev/null +++ b/spec/api-power-monitor-spec.js @@ -0,0 +1,118 @@ +// For these tests we use a fake DBus daemon to verify powerMonitor module +// interaction with the system bus. This requires python-dbusmock installed and +// running (with the DBUS_SYSTEM_BUS_ADDRESS environment variable set). +// script/test.py will take care of spawning the fake DBus daemon and setting +// DBUS_SYSTEM_BUS_ADDRESS when python-dbusmock is installed. +// +// See https://pypi.python.org/pypi/python-dbusmock for more information about +// python-dbusmock. +const assert = require('assert') +const dbus = require('dbus-native') +const Promise = require('bluebird') + +const skip = process.platform !== 'linux' || !process.env.DBUS_SYSTEM_BUS_ADDRESS; + +(skip ? describe.skip : describe)('powerMonitor', () => { + let logindMock, powerMonitor, getCalls, emitSignal, reset + + before(async () => { + const systemBus = dbus.systemBus() + const loginService = systemBus.getService('org.freedesktop.login1') + const getInterface = Promise.promisify(loginService.getInterface, {context: loginService}) + logindMock = await getInterface('/org/freedesktop/login1', 'org.freedesktop.DBus.Mock') + getCalls = Promise.promisify(logindMock.GetCalls, {context: logindMock}) + emitSignal = Promise.promisify(logindMock.EmitSignal, {context: logindMock}) + reset = Promise.promisify(logindMock.Reset, {context: logindMock}) + }) + + after(async () => { + await reset() + }) + + describe('when powerMonitor module is loaded', () => { + function onceMethodCalled (done) { + function cb () { + logindMock.removeListener('MethodCalled', cb) + } + done() + return cb + } + + before((done) => { + logindMock.on('MethodCalled', onceMethodCalled(done)) + // lazy load powerMonitor after we listen to MethodCalled mock signal + powerMonitor = require('electron').remote.powerMonitor + }) + + it('should call Inhibit to delay suspend', async () => { + const calls = await getCalls() + assert.equal(calls.length, 1) + assert.deepEqual(calls[0].slice(1), [ + 'Inhibit', [ + [[{type: 's', child: []}], ['sleep']], + [[{type: 's', child: []}], ['electron']], + [[{type: 's', child: []}], ['Application cleanup before suspend']], + [[{type: 's', child: []}], ['delay']] + ] + ]) + }) + + describe('when PrepareForSleep(true) signal is sent by logind', () => { + it('should emit "suspend" event', (done) => { + powerMonitor.once('suspend', () => done()) + emitSignal('org.freedesktop.login1.Manager', 'PrepareForSleep', + 'b', [['b', true]]) + }) + + describe('when PrepareForSleep(false) signal is sent by logind', () => { + it('should emit "resume" event', (done) => { + powerMonitor.once('resume', () => done()) + emitSignal('org.freedesktop.login1.Manager', 'PrepareForSleep', + 'b', [['b', false]]) + }) + + it('should have called Inhibit again', async () => { + const calls = await getCalls() + assert.equal(calls.length, 2) + assert.deepEqual(calls[1].slice(1), [ + 'Inhibit', [ + [[{type: 's', child: []}], ['sleep']], + [[{type: 's', child: []}], ['electron']], + [[{type: 's', child: []}], ['Application cleanup before suspend']], + [[{type: 's', child: []}], ['delay']] + ] + ]) + }) + }) + }) + + describe('when a listener is added to shutdown event', () => { + before(async () => { + const calls = await getCalls() + assert.equal(calls.length, 2) + powerMonitor.once('shutdown', () => { }) + }) + + it('should call Inhibit to delay shutdown', async () => { + const calls = await getCalls() + assert.equal(calls.length, 3) + assert.deepEqual(calls[2].slice(1), [ + 'Inhibit', [ + [[{type: 's', child: []}], ['shutdown']], + [[{type: 's', child: []}], ['electron']], + [[{type: 's', child: []}], ['Ensure a clean shutdown']], + [[{type: 's', child: []}], ['delay']] + ] + ]) + }) + + describe('when PrepareForShutdown(true) signal is sent by logind', () => { + it('should emit "shutdown" event', (done) => { + powerMonitor.once('shutdown', () => { done() }) + emitSignal('org.freedesktop.login1.Manager', 'PrepareForShutdown', + 'b', [['b', true]]) + }) + }) + }) + }) +}) diff --git a/spec/api-protocol-spec.js b/spec/api-protocol-spec.js index f214bfc6d27..cd2e4519898 100644 --- a/spec/api-protocol-spec.js +++ b/spec/api-protocol-spec.js @@ -686,12 +686,6 @@ describe('protocol module', () => { }) it('sends error when callback is called with nothing', function (done) { - if (process.env.TRAVIS === 'true') { - // FIXME(alexeykuzmin): Skip the test. - // this.skip() - return done() - } - protocol.interceptBufferProtocol('http', emptyHandler, (error) => { if (error) return done(error) $.ajax({ diff --git a/spec/api-screen-spec.js b/spec/api-screen-spec.js index bb60826b2d0..910bca84541 100644 --- a/spec/api-screen-spec.js +++ b/spec/api-screen-spec.js @@ -18,17 +18,4 @@ describe('screen module', () => { assert(display.size.height > 0) }) }) - - describe('screen.getMenuBarHeight()', () => { - before(function () { - if (process.platform !== 'darwin') { - this.skip() - } - }) - - it('returns an integer', () => { - const screenHeight = screen.getMenuBarHeight() - assert.equal(typeof screenHeight, 'number') - }) - }) }) diff --git a/spec/api-session-spec.js b/spec/api-session-spec.js index cf6296cd448..891771f3d06 100644 --- a/spec/api-session-spec.js +++ b/spec/api-session-spec.js @@ -275,6 +275,7 @@ describe('session module', () => { describe('DownloadItem', () => { const mockPDF = Buffer.alloc(1024 * 1024 * 5) + const protocolName = 'custom-dl' let contentDisposition = 'inline; filename="mock.pdf"' const downloadFilePath = path.join(fixtures, 'mock.pdf') const downloadServer = http.createServer((req, res) => { @@ -289,11 +290,15 @@ describe('session module', () => { }) const assertDownload = (event, state, url, mimeType, receivedBytes, totalBytes, disposition, - filename, port, savePath) => { + filename, port, savePath, isCustom) => { assert.equal(state, 'completed') assert.equal(filename, 'mock.pdf') assert.equal(savePath, path.join(__dirname, 'fixtures', 'mock.pdf')) - assert.equal(url, `http://127.0.0.1:${port}/`) + if (isCustom) { + assert.equal(url, `${protocolName}://item`) + } else { + assert.equal(url, `http://127.0.0.1:${port}/`) + } assert.equal(mimeType, 'application/pdf') assert.equal(receivedBytes, mockPDF.length) assert.equal(totalBytes, mockPDF.length) @@ -318,6 +323,30 @@ describe('session module', () => { }) }) + it('can download from custom protocols using WebContents.downloadURL', (done) => { + const protocol = session.defaultSession.protocol + downloadServer.listen(0, '127.0.0.1', () => { + const port = downloadServer.address().port + const handler = (ignoredError, callback) => { + callback({url: `${url}:${port}`}) + } + protocol.registerHttpProtocol(protocolName, handler, (error) => { + if (error) return done(error) + ipcRenderer.sendSync('set-download-option', false, false) + w.webContents.downloadURL(`${protocolName}://item`) + ipcRenderer.once('download-done', (event, state, url, + mimeType, receivedBytes, + totalBytes, disposition, + filename, savePath) => { + assertDownload(event, state, url, mimeType, receivedBytes, + totalBytes, disposition, filename, port, savePath, + true) + done() + }) + }) + }) + }) + it('can download using WebView.downloadURL', (done) => { downloadServer.listen(0, '127.0.0.1', () => { const port = downloadServer.address().port diff --git a/spec/api-system-preferences-spec.js b/spec/api-system-preferences-spec.js index d6513b364b3..96e52979fdd 100644 --- a/spec/api-system-preferences-spec.js +++ b/spec/api-system-preferences-spec.js @@ -35,6 +35,48 @@ describe('systemPreferences module', () => { }) }) + describe('systemPreferences.registerDefaults(defaults)', () => { + before(function () { + if (process.platform !== 'darwin') { + this.skip() + } + }) + + it('registers defaults', () => { + const defaultsMap = [ + { key: 'one', type: 'string', value: 'ONE' }, + { key: 'two', value: 2, type: 'integer' }, + { key: 'three', value: [1, 2, 3], type: 'array' } + ] + + const defaultsDict = {} + defaultsMap.forEach(row => { defaultsDict[row.key] = row.value }) + + systemPreferences.registerDefaults(defaultsDict) + + for (const userDefault of defaultsMap) { + const { key, value: expectedValue, type } = userDefault + const actualValue = systemPreferences.getUserDefault(key, type) + assert.deepEqual(actualValue, expectedValue) + } + }) + + it('throws when bad defaults are passed', () => { + const badDefaults = [ + 1, + null, + new Date(), + { 'one': null } + ] + + for (const badDefault of badDefaults) { + assert.throws(() => { + systemPreferences.registerDefaults(badDefault) + }, 'Invalid userDefault data provided') + } + }) + }) + describe('systemPreferences.getUserDefault(key, type)', () => { before(function () { if (process.platform !== 'darwin') { diff --git a/spec/api-web-contents-spec.js b/spec/api-web-contents-spec.js index 4e5cc022df7..12217029283 100644 --- a/spec/api-web-contents-spec.js +++ b/spec/api-web-contents-spec.js @@ -92,6 +92,22 @@ describe('webContents module', () => { }) }) + describe('setDevToolsWebCotnents() API', () => { + it('sets arbitry webContents as devtools', (done) => { + let devtools = new BrowserWindow({show: false}) + devtools.webContents.once('dom-ready', () => { + assert.ok(devtools.getURL().startsWith('chrome-devtools://devtools')) + devtools.webContents.executeJavaScript('InspectorFrontendHost.constructor.name', (name) => { + assert.ok(name, 'InspectorFrontendHostImpl') + devtools.destroy() + done() + }) + }) + w.webContents.setDevToolsWebContents(devtools.webContents) + w.webContents.openDevTools() + }) + }) + describe('isFocused() API', () => { it('returns false when the window is hidden', () => { BrowserWindow.getAllWindows().forEach((window) => { diff --git a/spec/api-web-frame-spec.js b/spec/api-web-frame-spec.js index a62c075d8e8..5a03ca22685 100644 --- a/spec/api-web-frame-spec.js +++ b/spec/api-web-frame-spec.js @@ -17,7 +17,6 @@ describe('webFrame module', function () { describe('webFrame.registerURLSchemeAsPrivileged', function () { it('supports fetch api by default', function (done) { - webFrame.registerURLSchemeAsPrivileged('file') var url = 'file://' + fixtures + '/assets/logo.png' window.fetch(url).then(function (response) { assert(response.ok) @@ -135,7 +134,6 @@ describe('webFrame module', function () { it('supports setting the visual and layout zoom level limits', function () { assert.doesNotThrow(function () { - webFrame.setZoomLevelLimits(1, 100) webFrame.setVisualZoomLevelLimits(1, 50) webFrame.setLayoutZoomLevelLimits(0, 25) }) diff --git a/spec/chromium-spec.js b/spec/chromium-spec.js index f068ef75f12..062862421a7 100644 --- a/spec/chromium-spec.js +++ b/spec/chromium-spec.js @@ -4,7 +4,8 @@ const http = require('http') const path = require('path') const ws = require('ws') const url = require('url') -const {ipcRenderer, remote, webFrame} = require('electron') +const ChildProcess = require('child_process') +const {ipcRenderer, remote} = require('electron') const {closeWindow} = require('./window-helpers') const {app, BrowserWindow, ipcMain, protocol, session, webContents} = remote @@ -26,16 +27,31 @@ describe('chromium feature', () => { listener = null }) + describe('command line switches', () => { + describe('--lang switch', () => { + const testLocale = (locale, result, done) => { + const appPath = path.join(__dirname, 'fixtures', 'api', 'locale-check') + const electronPath = remote.getGlobal('process').execPath + let output = '' + let appProcess = ChildProcess.spawn(electronPath, [appPath, `--lang=${locale}`]) + + appProcess.stdout.on('data', (data) => { output += data }) + appProcess.stdout.on('end', () => { + output = output.replace(/(\r\n|\n|\r)/gm, '') + assert.equal(output, result) + done() + }) + } + + it('should set the locale', (done) => testLocale('fr', 'fr', done)) + it('should not set an invalid locale', (done) => testLocale('asdfkl', 'en-US', done)) + }) + }) + afterEach(() => closeWindow(w).then(() => { w = null })) describe('heap snapshot', () => { it('does not crash', function () { - if (process.env.TRAVIS === 'true') { - // FIXME(alexeykuzmin): Skip the test. - // this.skip() - return - } - process.atomBinding('v8_util').takeHeapSnapshot() }) }) @@ -123,7 +139,7 @@ describe('chromium feature', () => { if (args[0] === 'reload') { w.webContents.reload() } else if (args[0] === 'error') { - done(`unexpected error : ${args[1]}`) + done(new Error(`unexpected error : ${JSON.stringify(args[1])}`)) } else if (args[0] === 'response') { assert.equal(args[1], 'Hello from serviceWorker!') session.defaultSession.clearStorageData({ @@ -131,6 +147,7 @@ describe('chromium feature', () => { }, () => done()) } }) + w.webContents.on('crashed', () => done(new Error('WebContents crashed.'))) w.loadURL(`file://${fixtures}/pages/service-worker/index.html`) }) @@ -168,17 +185,12 @@ describe('chromium feature', () => { }) } }) + w.webContents.on('crashed', () => done(new Error('WebContents crashed.'))) w.loadURL(`file://${fixtures}/pages/service-worker/index.html`) }) }) describe('window.open', () => { - before(function () { - if (process.env.TRAVIS === 'true' && process.platform === 'darwin') { - this.skip() - } - }) - it('returns a BrowserWindowProxy object', () => { const b = window.open('about:blank', '', 'show=no') assert.equal(b.closed, false) @@ -1035,7 +1047,7 @@ describe('chromium feature', () => { assert.equal(parsedURL.hostname, 'pdf-viewer') assert.equal(parsedURL.query.src, pdfSourceWithParams) assert.equal(parsedURL.query.b, undefined) - assert.equal(parsedURL.search, `?src=${pdfSource}%3Fa%3D1%26b%3D2`) + assert(parsedURL.search.endsWith('%3Fa%3D1%26b%3D2')) assert.equal(w.webContents.getTitle(), 'cat.pdf') }) w.webContents.loadURL(pdfSourceWithParams) @@ -1055,13 +1067,6 @@ describe('chromium feature', () => { }) it('should not open when pdf is requested as sub resource', (done) => { - createBrowserWindow({plugins: true, preload: 'preload-pdf-loaded.js'}) - webFrame.registerURLSchemeAsPrivileged('file', { - secure: false, - bypassCSP: false, - allowServiceWorkers: false, - corsEnabled: false - }) fetch(pdfSource).then((res) => { assert.equal(res.status, 200) assert.notEqual(document.title, 'cat.pdf') @@ -1083,10 +1088,6 @@ describe('chromium feature', () => { assert.throws(() => { window.alert({toString: null}) }, /Cannot convert object to primitive value/) - - assert.throws(() => { - window.alert('message', {toString: 3}) - }, /Cannot convert object to primitive value/) }) }) @@ -1095,10 +1096,6 @@ describe('chromium feature', () => { assert.throws(() => { window.confirm({toString: null}, 'title') }, /Cannot convert object to primitive value/) - - assert.throws(() => { - window.confirm('message', {toString: 3}) - }, /Cannot convert object to primitive value/) }) }) diff --git a/spec/fixtures/api/crash-restart.html b/spec/fixtures/api/crash-restart.html index 22f3b45b5c8..618728e47f9 100644 --- a/spec/fixtures/api/crash-restart.html +++ b/spec/fixtures/api/crash-restart.html @@ -24,8 +24,8 @@ if (process.platform === 'win32') { setImmediate(() => { if (process.platform === 'darwin') { - crashReporter.setExtraParameter('extra2', 'extra2') - crashReporter.setExtraParameter('extra3', null) + crashReporter.addExtraParameter('extra2', 'extra2') + crashReporter.removeExtraParameter('extra3') } else { crashReporter.start({ productName: 'Zombies', diff --git a/spec/fixtures/api/locale-check/main.js b/spec/fixtures/api/locale-check/main.js new file mode 100644 index 00000000000..be1807bb12e --- /dev/null +++ b/spec/fixtures/api/locale-check/main.js @@ -0,0 +1,10 @@ +const {app} = require('electron') + +app.on('ready', () => { + process.stdout.write(app.getLocale()) + process.stdout.end() + + setImmediate(() => { + app.quit() + }) +}) diff --git a/spec/fixtures/api/locale-check/package.json b/spec/fixtures/api/locale-check/package.json new file mode 100644 index 00000000000..a24f536930d --- /dev/null +++ b/spec/fixtures/api/locale-check/package.json @@ -0,0 +1,5 @@ +{ + "name": "locale-check", + "main": "main.js" +} + diff --git a/spec/fixtures/api/preloads.html b/spec/fixtures/api/preloads.html new file mode 100644 index 00000000000..f13510b1b21 --- /dev/null +++ b/spec/fixtures/api/preloads.html @@ -0,0 +1,7 @@ + + + + + diff --git a/spec/fixtures/api/singleton/main.js b/spec/fixtures/api/singleton/main.js index 07176e0097e..cbf3da1117d 100644 --- a/spec/fixtures/api/singleton/main.js +++ b/spec/fixtures/api/singleton/main.js @@ -1,6 +1,8 @@ const {app} = require('electron') -console.log('started') // ping parent +app.once('ready', () => { + console.log('started') // ping parent +}) const shouldExit = app.makeSingleInstance(() => { process.nextTick(() => app.exit(0)) diff --git a/spec/fixtures/module/check-arguments.js b/spec/fixtures/module/check-arguments.js new file mode 100644 index 00000000000..12c3d4846d9 --- /dev/null +++ b/spec/fixtures/module/check-arguments.js @@ -0,0 +1,4 @@ +var ipcRenderer = require('electron').ipcRenderer +window.onload = function () { + ipcRenderer.send('answer', process.argv) +} diff --git a/spec/fixtures/module/set-global-preload-1.js b/spec/fixtures/module/set-global-preload-1.js new file mode 100644 index 00000000000..22dfdf91850 --- /dev/null +++ b/spec/fixtures/module/set-global-preload-1.js @@ -0,0 +1 @@ +window.preload1 = 'preload-1' diff --git a/spec/fixtures/module/set-global-preload-2.js b/spec/fixtures/module/set-global-preload-2.js new file mode 100644 index 00000000000..7542009f7b0 --- /dev/null +++ b/spec/fixtures/module/set-global-preload-2.js @@ -0,0 +1 @@ +window.preload2 = window.preload1 + '-2' diff --git a/spec/fixtures/module/set-global-preload-3.js b/spec/fixtures/module/set-global-preload-3.js new file mode 100644 index 00000000000..9cfef949277 --- /dev/null +++ b/spec/fixtures/module/set-global-preload-3.js @@ -0,0 +1 @@ +window.preload3 = window.preload2 + '-3' diff --git a/spec/fixtures/module/set-global.js b/spec/fixtures/module/set-global.js index ba4f30d155b..5ad98817e0b 100644 --- a/spec/fixtures/module/set-global.js +++ b/spec/fixtures/module/set-global.js @@ -1 +1 @@ -window.test = 'preload' +if (!window.test) window.test = 'preload' diff --git a/spec/fixtures/pages/base-page-security.html b/spec/fixtures/pages/base-page-security.html new file mode 100644 index 00000000000..9b58913dbb5 --- /dev/null +++ b/spec/fixtures/pages/base-page-security.html @@ -0,0 +1,9 @@ + + + + + + + diff --git a/spec/fixtures/pages/insecure-resources.html b/spec/fixtures/pages/insecure-resources.html new file mode 100644 index 00000000000..97f91b769ea --- /dev/null +++ b/spec/fixtures/pages/insecure-resources.html @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/spec/fixtures/pages/webview-allowpopups.html b/spec/fixtures/pages/webview-allowpopups.html new file mode 100644 index 00000000000..f392c0e22f0 --- /dev/null +++ b/spec/fixtures/pages/webview-allowpopups.html @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/spec/modules-spec.js b/spec/modules-spec.js index cfe840ca0a4..88a625830c8 100644 --- a/spec/modules-spec.js +++ b/spec/modules-spec.js @@ -28,7 +28,10 @@ describe('modules support', () => { }) }) - describe('ffi', () => { + // TODO(alexeykuzmin): Disabled during the Chromium 62 (Node.js 9) upgrade. + // Enable it back when "ffi" module supports Node.js 9. + // https://github.com/electron/electron/issues/11274 + xdescribe('ffi', () => { before(function () { if (!nativeModulesEnabled || process.platform === 'win32') { this.skip() diff --git a/spec/node-spec.js b/spec/node-spec.js index 48121ee84ce..e4c2655dcb2 100644 --- a/spec/node-spec.js +++ b/spec/node-spec.js @@ -135,12 +135,6 @@ describe('node feature', () => { describe('contexts', () => { describe('setTimeout in fs callback', () => { - before(function () { - if (process.env.TRAVIS === 'true') { - this.skip() - } - }) - it('does not crash', (done) => { fs.readFile(__filename, () => { setTimeout(done, 0) @@ -189,10 +183,13 @@ describe('node feature', () => { describe('setInterval called under Chromium event loop in browser process', () => { it('can be scheduled in time', (done) => { - let clear - let interval - clear = () => { + let interval = null + const clear = () => { + if (interval === null) { + return + } remote.getGlobal('clearInterval')(interval) + interval = null done() } interval = remote.getGlobal('setInterval')(clear, 10) @@ -319,6 +316,20 @@ describe('node feature', () => { buffer = Buffer.from(new Array(4097).join(' ')) assert.equal(buffer.length, 4096) }) + + it('does not crash for crypto operations', () => { + const crypto = require('crypto') + const data = 'lG9E+/g4JmRmedDAnihtBD4Dfaha/GFOjd+xUOQI05UtfVX3DjUXvrS98p7kZQwY3LNhdiFo7MY5rGft8yBuDhKuNNag9vRx/44IuClDhdQ=' + const key = 'q90K9yBqhWZnAMCMTOJfPQ==' + const cipherText = '{"error_code":114,"error_message":"Tham sแป‘ khรดng hแปฃp lแป‡","data":null}' + for (let i = 0; i < 10000; ++i) { + let iv = Buffer.from('0'.repeat(32), 'hex') + let input = Buffer.from(data, 'base64') + let decipher = crypto.createDecipheriv('aes-128-cbc', Buffer.from(key, 'base64'), iv) + let result = Buffer.concat([decipher.update(input), decipher.final()]) + assert.equal(cipherText, result) + } + }) }) describe('process.stdout', () => { diff --git a/spec/package-lock.json b/spec/package-lock.json deleted file mode 100644 index 4d082f37749..00000000000 --- a/spec/package-lock.json +++ /dev/null @@ -1,1035 +0,0 @@ -{ - "name": "electron-test", - "version": "0.1.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "assertion-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", - "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", - "dev": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "basic-auth": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", - "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=", - "dev": true - }, - "bindings": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", - "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=" - }, - "brace-expansion": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", - "dev": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "browser-stdout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", - "dev": true - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - }, - "chai": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", - "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", - "dev": true, - "requires": { - "assertion-error": "1.0.2", - "check-error": "1.0.2", - "deep-eql": "3.0.1", - "get-func-name": "2.0.0", - "pathval": "1.1.0", - "type-detect": "4.0.5" - } - }, - "charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=", - "dev": true - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, - "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.1.0" - } - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "coffee-script": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", - "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==", - "dev": true - }, - "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true, - "requires": { - "graceful-readlink": "1.0.1" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "requires": { - "type-detect": "4.0.5" - } - }, - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", - "dev": true - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true - }, - "diff": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", - "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", - "dev": true - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "encodeurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", - "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=", - "dev": true - }, - "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", - "dev": true, - "requires": { - "is-arrayish": "0.2.1" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "etag": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz", - "integrity": "sha1-A9MLX2fdbmMtKUXTDWZScxo01dg=", - "dev": true - }, - "fd-slicer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", - "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", - "dev": true, - "requires": { - "pend": "1.2.0" - } - }, - "ffi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ffi/-/ffi-2.2.0.tgz", - "integrity": "sha1-vxiwRmain3EiftVoldVDCvRwQvo=", - "optional": true, - "requires": { - "bindings": "1.2.1", - "debug": "2.6.9", - "nan": "2.7.0", - "ref": "1.3.5", - "ref-struct": "1.1.0" - } - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" - } - }, - "fresh": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz", - "integrity": "sha1-ZR+DjiJCTnVm3hYdg1jKoZn4PU8=", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "get-caller-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", - "dev": true - }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true - }, - "glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", - "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true - }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true - }, - "growl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", - "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", - "dev": true - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "hosted-git-info": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", - "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", - "dev": true - }, - "http-errors": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.5.1.tgz", - "integrity": "sha1-eIwNLB3iyBuebowBhDtrl+uSB1A=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "setprototypeof": "1.0.2", - "statuses": "1.3.1" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-buffer": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", - "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", - "dev": true - }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, - "requires": { - "builtin-modules": "1.1.1" - } - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "json3": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", - "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", - "dev": true - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "1.0.0" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" - } - }, - "lodash._baseassign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", - "dev": true, - "requires": { - "lodash._basecopy": "3.0.1", - "lodash.keys": "3.1.2" - } - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basecreate": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", - "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "lodash.create": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", - "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", - "dev": true, - "requires": { - "lodash._baseassign": "3.2.0", - "lodash._basecreate": "3.0.3", - "lodash._isiterateecall": "3.0.9" - } - }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "3.9.1", - "lodash.isarguments": "3.1.0", - "lodash.isarray": "3.0.4" - } - }, - "md5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", - "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", - "dev": true, - "requires": { - "charenc": "0.0.2", - "crypt": "0.0.2", - "is-buffer": "1.1.5" - } - }, - "mime": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", - "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "1.1.8" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "mocha": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", - "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", - "dev": true, - "requires": { - "browser-stdout": "1.3.0", - "commander": "2.9.0", - "debug": "2.6.8", - "diff": "3.2.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.1", - "growl": "1.9.2", - "he": "1.1.1", - "json3": "3.3.2", - "lodash.create": "3.1.1", - "mkdirp": "0.5.1", - "supports-color": "3.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "mocha-junit-reporter": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-1.15.0.tgz", - "integrity": "sha1-MJ9LeiD82ibQrWnJt9CAjXcjAsI=", - "dev": true, - "requires": { - "debug": "2.6.9", - "md5": "2.2.1", - "mkdirp": "0.5.1", - "xml": "1.0.1" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "multiparty": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/multiparty/-/multiparty-4.1.3.tgz", - "integrity": "sha1-PEPH/LGJbhdGBDap3Qtu8WaOT5Q=", - "dev": true, - "requires": { - "fd-slicer": "1.0.1" - } - }, - "nan": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", - "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=" - }, - "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", - "dev": true, - "requires": { - "hosted-git-info": "2.5.0", - "is-builtin-module": "1.0.0", - "semver": "5.4.1", - "validate-npm-package-license": "3.0.1" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "options": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", - "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=", - "dev": true - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true, - "requires": { - "lcid": "1.0.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "1.3.1" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "2.0.1" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - } - }, - "pathval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", - "dev": true - }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "2.0.4" - } - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "dev": true - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", - "dev": true - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.4.0", - "path-type": "1.1.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" - } - }, - "ref": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ref/-/ref-1.3.5.tgz", - "integrity": "sha512-2cBCniTtxcGUjDpvFfVpw323a83/0RLSGJJY5l5lcomZWhYpU2cuLdsvYqMixvsdLJ9+sTdzEkju8J8ZHDM2nA==", - "requires": { - "bindings": "1.2.1", - "debug": "2.6.9", - "nan": "2.7.0" - } - }, - "ref-struct": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ref-struct/-/ref-struct-1.1.0.tgz", - "integrity": "sha1-XV7mWtQc78Olxf60BYcmHkee3BM=", - "optional": true, - "requires": { - "debug": "2.6.9", - "ref": "1.3.5" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", - "dev": true - }, - "runas": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/runas/-/runas-3.1.1.tgz", - "integrity": "sha1-Ut1TjbDkF0U5lTWjRwkbpFzA6rA=", - "optional": true, - "requires": { - "nan": "2.7.0" - } - }, - "semver": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", - "dev": true - }, - "send": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.14.2.tgz", - "integrity": "sha1-ObBDiz9RC+Xcb2Z6EfcWiTaM3u8=", - "dev": true, - "requires": { - "debug": "2.2.0", - "depd": "1.1.1", - "destroy": "1.0.4", - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "etag": "1.7.0", - "fresh": "0.3.0", - "http-errors": "1.5.1", - "mime": "1.3.4", - "ms": "0.7.2", - "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.3.1" - }, - "dependencies": { - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "dev": true, - "requires": { - "ms": "0.7.1" - }, - "dependencies": { - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", - "dev": true - } - } - }, - "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", - "dev": true - } - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "setprototypeof": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.2.tgz", - "integrity": "sha1-gaVSFB7BBLiOic44MQOtXGZWTQg=", - "dev": true - }, - "spdx-correct": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", - "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", - "dev": true, - "requires": { - "spdx-license-ids": "1.2.2" - } - }, - "spdx-expression-parse": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", - "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", - "dev": true - }, - "spdx-license-ids": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", - "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", - "dev": true - }, - "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", - "dev": true - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "0.2.1" - } - }, - "supports-color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", - "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - }, - "temp": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", - "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=", - "dev": true, - "requires": { - "os-tmpdir": "1.0.2", - "rimraf": "2.2.8" - } - }, - "type-detect": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.5.tgz", - "integrity": "sha512-N9IvkQslUGYGC24RkJk1ba99foK6TkwC2FHAEBlQFBP0RxQZS8ZpJuAZcwiY/w9ZJHFQb1aOXBI60OdxhTrwEQ==", - "dev": true - }, - "ultron": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", - "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", - "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", - "dev": true, - "requires": { - "spdx-correct": "1.0.2", - "spdx-expression-parse": "1.0.4" - } - }, - "walkdir": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.11.tgz", - "integrity": "sha1-oW0CXrkxvQO1LzCMrtD0D86+lTI=", - "dev": true - }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", - "dev": true - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "ws": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.4.tgz", - "integrity": "sha1-V/QNA2gy5fUFVmKjl8Tedu1mv2E=", - "dev": true, - "requires": { - "options": "0.0.6", - "ultron": "1.0.2" - } - }, - "xml": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", - "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", - "dev": true - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, - "yargs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", - "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", - "dev": true, - "requires": { - "camelcase": "3.0.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.2", - "os-locale": "1.4.0", - "read-pkg-up": "1.0.1", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "1.0.2", - "which-module": "1.0.0", - "y18n": "3.2.1", - "yargs-parser": "4.2.1" - } - }, - "yargs-parser": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", - "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", - "dev": true, - "requires": { - "camelcase": "3.0.0" - } - } - } -} diff --git a/spec/package.json b/spec/package.json index de51b96468d..b34558416fb 100644 --- a/spec/package.json +++ b/spec/package.json @@ -5,8 +5,10 @@ "version": "0.1.0", "devDependencies": { "basic-auth": "^1.0.4", + "bluebird": "^3.5.1", "chai": "^4.1.2", "coffee-script": "1.12.7", + "dbus-native": "^0.2.3", "graceful-fs": "^4.1.9", "mkdirp": "^0.5.1", "mocha": "^3.1.0", @@ -16,11 +18,11 @@ "send": "^0.14.1", "temp": "^0.8.3", "walkdir": "0.0.11", + "winreg": "^1.2.4", "ws": "^1.1.1", "yargs": "^6.0.0" }, "optionalDependencies": { - "ffi": "2.2.0", "runas": "3.x" }, "standard": { diff --git a/spec/security-warnings-spec.js b/spec/security-warnings-spec.js new file mode 100644 index 00000000000..feac23db7e0 --- /dev/null +++ b/spec/security-warnings-spec.js @@ -0,0 +1,185 @@ +const assert = require('assert') +const http = require('http') +const fs = require('fs') +const path = require('path') +const url = require('url') + +const {remote} = require('electron') +const {BrowserWindow} = remote + +const {closeWindow} = require('./window-helpers') + +describe('security warnings', () => { + let server + let w = null + let useCsp = true + + before(() => { + // Create HTTP Server + server = http.createServer((request, response) => { + const uri = url.parse(request.url).pathname + let filename = path.join(__dirname, './fixtures/pages', uri) + + fs.stat(filename, (error, stats) => { + if (error) { + response.writeHead(404, { 'Content-Type': 'text/plain' }) + response.end() + return + } + + if (stats.isDirectory()) { + filename += '/index.html' + } + + fs.readFile(filename, 'binary', (err, file) => { + if (err) { + response.writeHead(404, { 'Content-Type': 'text/plain' }) + response.end() + return + } + + const cspHeaders = { 'Content-Security-Policy': `script-src 'self' 'unsafe-inline'` } + response.writeHead(200, useCsp ? cspHeaders : undefined) + response.write(file, 'binary') + response.end() + }) + }) + }).listen(8881) + }) + + after(() => { + // Close server + server.close() + server = null + }) + + afterEach(() => { + closeWindow(w).then(() => { w = null }) + + useCsp = true + }) + + it('should warn about Node.js integration with remote content', (done) => { + w = new BrowserWindow({ show: false }) + w.webContents.on('console-message', (e, level, message) => { + assert(message.includes('Node.js Integration with Remote Content')) + done() + }) + + w.loadURL(`http://127.0.0.1:8881/base-page-security.html`) + }) + + it('should warn about disabled webSecurity', (done) => { + w = new BrowserWindow({ + show: false, + webPreferences: { + webSecurity: false, + nodeIntegration: false + } + }) + w.webContents.on('console-message', (e, level, message) => { + assert(message.includes('Disabled webSecurity')) + done() + }) + + w.loadURL(`http://127.0.0.1:8881/base-page-security.html`) + }) + + it('should warn about insecure Content-Security-Policy', (done) => { + w = new BrowserWindow({ + show: false, + webPreferences: { + nodeIntegration: false + } + }) + + w.webContents.on('console-message', (e, level, message) => { + assert(message.includes('Insecure Content-Security-Policy')) + done() + }) + + useCsp = false + w.loadURL(`http://127.0.0.1:8881/base-page-security.html`) + }) + + it('should warn about allowRunningInsecureContent', (done) => { + w = new BrowserWindow({ + show: false, + webPreferences: { + allowRunningInsecureContent: true, + nodeIntegration: false + } + }) + w.webContents.on('console-message', (e, level, message) => { + assert(message.includes('allowRunningInsecureContent')) + done() + }) + + w.loadURL(`http://127.0.0.1:8881/base-page-security.html`) + }) + + it('should warn about experimentalFeatures', (done) => { + w = new BrowserWindow({ + show: false, + webPreferences: { + experimentalFeatures: true, + nodeIntegration: false + } + }) + w.webContents.on('console-message', (e, level, message) => { + assert(message.includes('experimentalFeatures')) + done() + }) + + w.loadURL(`http://127.0.0.1:8881/base-page-security.html`) + }) + + it('should warn about blinkFeatures', (done) => { + w = new BrowserWindow({ + show: false, + webPreferences: { + blinkFeatures: ['my-cool-feature'], + nodeIntegration: false + } + }) + w.webContents.on('console-message', (e, level, message) => { + assert(message.includes('blinkFeatures')) + done() + }) + + w.loadURL(`http://127.0.0.1:8881/base-page-security.html`) + }) + + it('should warn about allowpopups', (done) => { + w = new BrowserWindow({ + show: false, + webPreferences: { + nodeIntegration: false + } + }) + w.webContents.on('console-message', (e, level, message) => { + console.log(message) + assert(message.includes('allowpopups')) + done() + }) + + w.loadURL(`http://127.0.0.1:8881/webview-allowpopups.html`) + }) + + it('should warn about insecure resources', (done) => { + w = new BrowserWindow({ + show: true, + webPreferences: { + nodeIntegration: false + } + }) + w.webContents.on('console-message', (e, level, message) => { + console.log(message) + assert(message.includes('Insecure Resources')) + done() + }) + + w.loadURL(`http://127.0.0.1:8881/insecure-resources.html`) + w.webContents.openDevTools() + }) +}) diff --git a/spec/static/index.html b/spec/static/index.html index 39f523f7d96..ca5cc28d4f9 100644 --- a/spec/static/index.html +++ b/spec/static/index.html @@ -11,74 +11,78 @@ diff --git a/spec/static/main.js b/spec/static/main.js index 80c8e716c67..7050f3a7e43 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -29,6 +29,9 @@ app.commandLine.appendSwitch('js-flags', '--expose_gc') app.commandLine.appendSwitch('ignore-certificate-errors') app.commandLine.appendSwitch('disable-renderer-backgrounding') +// Disable security warnings (the security warnings test will enable them) +process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = true + // 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. diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 0e4d8cf291e..73df333dd2b 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -416,12 +416,6 @@ describe(' tag', function () { }) describe('allowpopups attribute', () => { - before(function () { - if (process.env.TRAVIS === 'true' && process.platform === 'darwin') { - this.skip() - } - }) - it('can not open new window when not set', (done) => { const listener = (e) => { assert.equal(e.message, 'null') @@ -506,12 +500,6 @@ describe(' tag', function () { }) describe('new-window event', () => { - before(function () { - if (process.env.TRAVIS === 'true' && process.platform === 'darwin') { - this.skip() - } - }) - it('emits when window.open is called', (done) => { webview.addEventListener('new-window', (e) => { assert.equal(e.url, 'http://host/') @@ -655,6 +643,30 @@ describe(' tag', function () { }) }) + describe('setDevToolsWebCotnents() API', () => { + it('sets webContents of webview as devtools', (done) => { + const webview2 = new WebView() + webview2.addEventListener('did-attach', () => { + webview2.addEventListener('dom-ready', () => { + const devtools = webview2.getWebContents() + assert.ok(devtools.getURL().startsWith('chrome-devtools://devtools')) + devtools.executeJavaScript('InspectorFrontendHost.constructor.name', (name) => { + assert.ok(name, 'InspectorFrontendHostImpl') + document.body.removeChild(webview2) + done() + }) + }) + webview.addEventListener('dom-ready', () => { + webview.getWebContents().setDevToolsWebContents(webview2.getWebContents()) + webview.getWebContents().openDevTools() + }) + webview.src = 'about:blank' + document.body.appendChild(webview) + }) + document.body.appendChild(webview2) + }) + }) + describe('devtools-opened event', () => { it('should fire when webview.openDevTools() is called', (done) => { const listener = () => { @@ -840,12 +852,6 @@ describe(' tag', function () { describe('executeJavaScript', () => { it('should support user gesture', function (done) { - if (process.env.TRAVIS !== 'true' || process.platform === 'darwin') { - // FIXME(alexeykuzmin): Skip the test. - // this.skip() - return done() - } - const listener = () => { webview.removeEventListener('enter-html-full-screen', listener) done() @@ -862,12 +868,6 @@ describe(' tag', function () { }) it('can return the result of the executed script', function (done) { - if (process.env.TRAVIS === 'true' && process.platform === 'darwin') { - // FIXME(alexeykuzmin): Skip the test. - // this.skip() - return done() - } - const listener = () => { const jsScript = "'4'+2" webview.executeJavaScript(jsScript, false, (result) => { @@ -1579,12 +1579,6 @@ describe(' tag', function () { }) it('can be manually resized with setSize even when attribute is present', function (done) { - if (process.env.TRAVIS === 'true') { - // FIXME(alexeykuzmin): Skip the test. - // this.skip() - return done() - } - w = new BrowserWindow({show: false, width: 200, height: 200}) w.loadURL(`file://${fixtures}/pages/webview-no-guest-resize.html`) diff --git a/toolchain.gypi b/toolchain.gypi index 6580f18a1ad..0b4ffa3e5eb 100644 --- a/toolchain.gypi +++ b/toolchain.gypi @@ -10,7 +10,7 @@ 'variables': { # The minimum macOS SDK version to use. - 'mac_sdk_min%': '10.10', + 'mac_sdk_min%': '10.12', # Set ARM architecture version. 'arm_version%': 7, @@ -53,16 +53,16 @@ ['target_arch=="arm"', { # sysroot needs to be an absolute path otherwise it generates # incorrect results when passed to pkg-config - 'sysroot%': '<(source_root)/vendor/debian_jessie_arm-sysroot', + 'sysroot%': '<(source_root)/vendor/debian_stretch_arm-sysroot', }], ['target_arch=="arm64"', { - 'sysroot%': '<(source_root)/vendor/debian_jessie_arm64-sysroot', + 'sysroot%': '<(source_root)/vendor/debian_stretch_arm64-sysroot', }], ['target_arch=="ia32"', { - 'sysroot%': '<(source_root)/vendor/debian_jessie_i386-sysroot', + 'sysroot%': '<(source_root)/vendor/debian_stretch_i386-sysroot', }], ['target_arch=="x64"', { - 'sysroot%': '<(source_root)/vendor/debian_jessie_amd64-sysroot', + 'sysroot%': '<(source_root)/vendor/debian_stretch_amd64-sysroot', }], ['target_arch=="mips64el"', { 'sysroot%': '<(source_root)/vendor/debian_jessie_mips64-sysroot', @@ -110,9 +110,6 @@ ['CXX.host', '$(CXX)'], ], 'target_defaults': { - 'cflags_cc': [ - '-std=c++11', - ], 'xcode_settings': { 'CC': '<(make_clang_dir)/bin/clang', 'LDPLUSPLUS': '<(make_clang_dir)/bin/clang++', @@ -122,16 +119,32 @@ 'GCC_C_LANGUAGE_STANDARD': 'c99', # -std=c99 'CLANG_CXX_LIBRARY': 'libc++', # -stdlib=libc++ - 'CLANG_CXX_LANGUAGE_STANDARD': 'c++11', # -std=c++11 + 'CLANG_CXX_LANGUAGE_STANDARD': 'c++14', # -std=c++14 }, 'target_conditions': [ - ['_type in ["executable", "shared_library"]', { + ['OS=="mac" and _type in ["executable", "shared_library"]', { 'xcode_settings': { # On some machines setting CLANG_CXX_LIBRARY doesn't work for # linker. 'OTHER_LDFLAGS': [ '-stdlib=libc++' ], }, }], + ['OS=="linux" and _toolset=="target"', { + 'cflags_cc': [ + '-std=gnu++14', + '-nostdinc++', + '-isystem<(libchromiumcontent_src_dir)/buildtools/third_party/libc++/trunk/include', + '-isystem<(libchromiumcontent_src_dir)/buildtools/third_party/libc++abi/trunk/include', + ], + 'ldflags': [ + '-nostdlib++', + ], + }], + ['OS=="linux" and _toolset=="host"', { + 'cflags_cc': [ + '-std=gnu++14', + ], + }], ], }, }], # clang==1 @@ -145,7 +158,7 @@ ], 'target_defaults': { 'cflags_cc': [ - '-std=c++11', + '-std=gnu++14', ], }, }], @@ -164,7 +177,11 @@ 'target_defaults': { 'target_conditions': [ ['_toolset=="target"', { - 'cflags': [ + # Do not use 'cflags' to make sure sysroot is appended at last. + 'cflags_cc': [ + '--sysroot=<(sysroot)', + ], + 'cflags_c': [ '--sysroot=<(sysroot)', ], 'ldflags': [ diff --git a/tools/dump-version-info.js b/tools/dump-version-info.js index 7376825aab8..67bc2f99e72 100644 --- a/tools/dump-version-info.js +++ b/tools/dump-version-info.js @@ -16,7 +16,7 @@ function getDate () { function getInfoForCurrentVersion () { var json = {} - json.version = process.versions['atom-shell'] + json.version = process.versions.electron json.date = getDate() var names = ['node', 'v8', 'uv', 'zlib', 'openssl', 'modules', 'chrome'] diff --git a/tools/run-electron.sh b/tools/run-electron.sh new file mode 100644 index 00000000000..47b191d04f6 --- /dev/null +++ b/tools/run-electron.sh @@ -0,0 +1,4 @@ +export DISPLAY=":99.0" +sh -e /etc/init.d/xvfb start +cd /tmp/workspace/project/ +out/D/electron --version diff --git a/vendor/crashpad b/vendor/crashpad index 07072bfae5f..561996f78f8 160000 --- a/vendor/crashpad +++ b/vendor/crashpad @@ -1 +1 @@ -Subproject commit 07072bfae5f0d8630657eba34c5ddae9e038db94 +Subproject commit 561996f78f8396d4784e9ceed427f605d4b84ad2 diff --git a/vendor/gyp b/vendor/gyp index 549e55a2276..eea8c793998 160000 --- a/vendor/gyp +++ b/vendor/gyp @@ -1 +1 @@ -Subproject commit 549e55a22765ccf99e46b419b2dff6338bcac64a +Subproject commit eea8c793998f7d8b3c58bd7414b3fea62343df25 diff --git a/vendor/libchromiumcontent b/vendor/libchromiumcontent index 6a0a4bfc65c..8c526e52d02 160000 --- a/vendor/libchromiumcontent +++ b/vendor/libchromiumcontent @@ -1 +1 @@ -Subproject commit 6a0a4bfc65c96e423f8ea14dc32315514f9c45a8 +Subproject commit 8c526e52d02a574b6d3cbaf2014cdccb9a0e3185 diff --git a/vendor/native_mate b/vendor/native_mate index bf92fa88b73..99d9e262eb3 160000 --- a/vendor/native_mate +++ b/vendor/native_mate @@ -1 +1 @@ -Subproject commit bf92fa88b735b8c9ff951e6ef78a531bd10e8778 +Subproject commit 99d9e262eb36cb9dc8e83f61e026d2a7ad1e96ab diff --git a/vendor/node b/vendor/node index d969dd20b68..3895d8f1ca3 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit d969dd20b689b14e7bbda6be03b361638809ef55 +Subproject commit 3895d8f1ca32edce4c9e04b28948ddbd05c21269