Compare commits

..

172 Commits

Author SHA1 Message Date
Cheng Zhao
4b5dd2ed4b Bump v0.18.1. 2014-10-17 17:00:26 +08:00
Cheng Zhao
85afa851dd win: Fix toggling the menubar, closes #681 2014-10-17 16:43:57 +08:00
Cheng Zhao
57acdc1bf6 Use menu bar features in default_app 2014-10-17 16:43:37 +08:00
Cheng Zhao
ae76657e17 Still requires unity for global app menubar
Fixes #709 and atom/atom#3854.
2014-10-17 16:06:45 +08:00
Cheng Zhao
4ca6ac34ac Do not create native Event object when not needed 2014-10-17 14:36:43 +08:00
Cheng Zhao
e4a71b86df Caching object templates for Event, fixes #705 2014-10-17 13:53:18 +08:00
Cheng Zhao
4dd7848084 Run idle GC in browser every 1m 2014-10-17 12:41:40 +08:00
Cheng Zhao
a95679c212 Enable mnemonics in menu, fixes atom/atom#3844 2014-10-16 21:22:22 +08:00
Cheng Zhao
b41d356143 --harmony_collections is not needed anymore 2014-10-16 20:59:01 +08:00
Cheng Zhao
feb6a93881 Merge pull request #708 from matttbe/globalmenu
linux: GlobalMenu: UBUNTU_MENUPROXY with >1 char
2014-10-16 18:53:24 +08:00
Matthieu Baerts
940b40439f linux: GlobalMenu: UBUNTU_MENUPROXY with >1 char
When testing if $UBUNTU_MENUPROXY is set, we should also check if it
contains more than one character. Avoid cases when $UBUNTU_MENUPROXY is
set to 0 or 1.
2014-10-15 14:17:18 +02:00
Cheng Zhao
ed07bd202f Bump v0.18.0. 2014-10-14 20:24:56 +08:00
Cheng Zhao
8c29ffd084 Make __filename normalized
This can make sure __filename and __dirname on Windows use "\" as path
delimeter.
2014-10-14 19:45:57 +08:00
Cheng Zhao
dba2fa31b6 Fix __dirname for page in asar package
Fixes #694.
2014-10-14 19:27:47 +08:00
Cheng Zhao
2de80571d8 Add spec for #694 2014-10-14 19:27:38 +08:00
Cheng Zhao
51acba594b Merge pull request #698 from matttbe/master
linux: Rename window's class to Atom
2014-10-14 18:58:37 +08:00
Cheng Zhao
a194d45dfa Merge pull request #700 from atom/chrome38
Upgrade to Chrome 38
2014-10-14 18:42:21 +08:00
Cheng Zhao
9356296a84 Merge pull request #703 from waywardmonkeys/cleanup-asar-docs
[docs] Clean up some grammar around ASAR.
2014-10-14 18:40:47 +08:00
Bruce Mitchener
2fa0b1117c [docs] Clean up some grammar around ASAR. 2014-10-14 08:16:22 +07:00
Cheng Zhao
49814e1919 Just pass spec when mkdir failed 2014-10-13 23:48:56 +08:00
Cheng Zhao
74da83a0bb Upgrade libchromiumcontent to fix node.lib 2014-10-13 23:26:13 +08:00
Cheng Zhao
643d1dcdd1 win: Fix building 2014-10-13 22:47:13 +08:00
Cheng Zhao
b717add81b Print error when mkdir failed 2014-10-13 19:08:23 +08:00
Cheng Zhao
c36c4e36c5 Upgrade libchromiumcontent to fix linking error 2014-10-13 18:11:19 +08:00
Cheng Zhao
0b48c3ea90 Merge pull request #699 from matttbe/globalmenu
linux: GlobalMenu: only if UBUNTU_MENUPROXY is set
2014-10-13 16:31:04 +08:00
Cheng Zhao
68e28159bb Upgrade to apm@0.102.0 2014-10-13 15:00:05 +08:00
Cheng Zhao
69a89303d0 Fix building on Linux 2014-10-13 11:03:56 +08:00
Cheng Zhao
9e87037d34 Upgrade to libchromiumcontent 44c71d8 2014-10-13 10:09:58 +08:00
Matthieu Baerts
7cd4d35778 linux: GlobalMenu: only if UBUNTU_MENUPROXY is set
When checking if we should react differently when GlobalMenu bar is
used, we should check if UBUNTU_MENUPROXY env var is (correctly) set
instead of checking if we're using Unity on a Unity session.

It's better to do that because we can use Compiz on a Unity session
without Unity. Or we can also use Unity in a different session.
2014-10-12 21:16:32 +02:00
Matthieu Baerts
8296178e33 linux: Rename window's class to Atom
The window's class should be Atom instead of Atom Shell because the
launcher and the binary to launch Atom are called 'atom' and not 'atom
shell'. This is why currently all Atom's windows will not be linked to
their launcher in a dock (e.g. with Cairo-Dock).

Note that it's not advised to add white-spaces in a window's class
('Atom Shell').
2014-10-12 18:47:49 +02:00
Cheng Zhao
32dff999a5 Fix API changes of Chrome 38 2014-10-11 19:11:34 +08:00
Cheng Zhao
13e5cf32d5 Upgrade brightray for Chrome 38 2014-10-11 19:11:07 +08:00
Cheng Zhao
11d9e522d5 Update node for V8 changes 2014-10-11 19:10:57 +08:00
Cheng Zhao
4aac0d6d1c Upgrade to Chrome 38.0.2125.102 2014-10-11 19:10:32 +08:00
Cheng Zhao
ddf4f14dba Merge pull request #685 from atom/speech
Add support for speech synthesizer and recognizer
2014-10-08 19:05:00 +08:00
Cheng Zhao
b560176aeb Set google API key 2014-10-08 18:27:39 +08:00
Cheng Zhao
802f964cd3 Enable AVFoundation 2014-10-08 18:17:27 +08:00
Cheng Zhao
0c349c047d Fix cpplint warning 2014-10-08 17:47:47 +08:00
Cheng Zhao
d4e3c39fa5 Add AtomSpeechRecognitionManagerDelegate 2014-10-08 11:55:14 +08:00
Cheng Zhao
3a177d55f8 Add linux tts implementation from Chrome 2014-10-08 02:14:12 +00:00
Cheng Zhao
b2741a8316 Upgrade libchromiuncontent to 3245ef8 2014-10-08 02:05:19 +00:00
Cheng Zhao
909f1bcf3c Upgrade brightray 2014-10-08 01:34:24 +00:00
Cheng Zhao
009412d738 Upgrade libchromiumcontent to f0c3a45 2014-10-08 01:27:07 +00:00
Cheng Zhao
33c622ec86 Install tts dispatcher 2014-10-07 21:27:15 +08:00
Cheng Zhao
05b602d0ce Import Chrome's tts code 2014-10-07 21:18:44 +08:00
Cheng Zhao
8519ea3299 Bump v0.17.2. 2014-10-06 19:13:26 +08:00
Cheng Zhao
aad46b0894 Upgrade to Chrome af66653 2014-10-06 19:02:54 +08:00
Cheng Zhao
5dfbaebd4c Upgrade brightray 2014-10-06 11:23:53 +08:00
Cheng Zhao
b2b70bb37c Upgrade libchromiumcontent to 440833b, fixes #462 2014-10-06 10:54:14 +08:00
Cheng Zhao
35d37c3463 Merge pull request #680 from Subash/patch-1
Optimize fs.stat
2014-10-03 18:09:57 +08:00
Kevin Sawicki
eea82efbf8 Merge pull request #682 from joshmarinacci/patch-1
speling error.
2014-10-02 14:51:41 -07:00
Josh Marinacci
7659edd139 speling error. 2014-10-02 14:51:05 -07:00
Subash Pathak
9f8a5a7af3 Optimize fs.stat 2014-10-02 23:35:37 +05:45
Cheng Zhao
cc4897f8c1 Add process.versions['chrome'], fixes #675 2014-10-02 23:48:17 +08:00
Cheng Zhao
ee5335ca6e Merge pull request #674 from Subash/fs-stat
Fs stat fixes #672
2014-10-02 11:46:06 +08:00
Subash Pathak
dde8e47add Time Shim 2014-10-01 22:24:51 +05:45
Subash Pathak
0040f07097 Added Stat Time 2014-10-01 22:01:57 +05:45
Cheng Zhao
64b2c9b36c Bump v0.17.1. 2014-10-01 20:41:35 +08:00
Cheng Zhao
d754b2bda7 Break on first found app 2014-10-01 20:40:52 +08:00
Cheng Zhao
9dd68c7add Bump v0.17.0. 2014-10-01 18:12:03 +08:00
Cheng Zhao
1499d44584 gtk: Fix focusing on file dialog
Fixes atom/atom#3626.
2014-10-01 17:02:00 +08:00
Cheng Zhao
039903c6b2 mac: Don't create button without title, fixes #631 2014-10-01 15:51:32 +08:00
Cheng Zhao
5df1716144 Upgrade brightray 2014-10-01 15:26:16 +08:00
Cheng Zhao
f6d6a12c1a win: uv_poll_get_timeout is removed 2014-09-30 23:27:36 +08:00
Cheng Zhao
e316e4a267 Upgrade node to v0.11.4, fixes #669 2014-09-30 23:14:25 +08:00
Cheng Zhao
9d84f139eb Merge pull request #665 from atom/asar
Support loading apps in asar format
2014-09-30 20:53:38 +08:00
Cheng Zhao
ad70cb27bd linux: Fix compilation warning 2014-09-30 20:52:57 +08:00
Cheng Zhao
98fec2f317 Still use 14 for node_module_version
We haven't broken abi yet, still use 14 to be compatible with previous
versions.
2014-09-30 20:49:34 +08:00
Cheng Zhao
72fc1e8dc6 Increase node_module_version, fixes #533 2014-09-30 20:32:14 +08:00
Cheng Zhao
301014e4a6 win: asar: Support "\" as path separator 2014-09-30 20:12:48 +08:00
Cheng Zhao
927ec6ab7a spec: asar: fs.realpath 2014-09-30 15:37:46 +08:00
Cheng Zhao
37d45e2881 spec: asar: fs.realpathSync 2014-09-30 15:17:48 +08:00
Cheng Zhao
915c1b19d3 asar: Fix fs.realpath on package's root 2014-09-30 15:09:50 +08:00
Cheng Zhao
b87dfb964c asar: Add support in fs.realpath 2014-09-30 14:57:49 +08:00
Cheng Zhao
885ac53a48 asar: Add support in fs.realpathSync 2014-09-30 14:53:58 +08:00
Cheng Zhao
d77bf0440c docs: Add usage on app packaging 2014-09-29 23:05:02 +08:00
Cheng Zhao
5a0be6672d docs: Add reference on app packaging 2014-09-29 21:34:54 +08:00
Cheng Zhao
724cae7de1 Merge pull request #670 from alexanderneu/master
win: Fix total value in SetProgressBar API.
2014-09-29 20:59:24 +08:00
Alexander Neu
6c9769999b win: Fix total value in SetProgressBar API. 2014-09-29 12:50:51 +02:00
Cheng Zhao
2bf2ad094c spec: asar: Testing getting file in web page 2014-09-29 16:58:54 +08:00
Cheng Zhao
3eaf0fe82b spec: asar: child_process.fork 2014-09-29 16:41:49 +08:00
Cheng Zhao
013e7fb611 spec: asar: fs.open 2014-09-29 16:29:10 +08:00
Cheng Zhao
e24976c59f Fix overriding async node API 2014-09-29 16:28:51 +08:00
Cheng Zhao
e3ae062c5c spec: asar: fs.open 2014-09-29 16:05:19 +08:00
Cheng Zhao
5e6e173d59 spec: asar: fs.readdir 2014-09-29 15:30:56 +08:00
Cheng Zhao
3fcd571db0 spec: asar: Test getting stats of root in fs.lstat 2014-09-29 15:25:28 +08:00
Cheng Zhao
988fa73696 spec: asar: fs.readdirSync 2014-09-29 15:24:01 +08:00
Cheng Zhao
3c412e1cb8 Fix readdir on a linked directory 2014-09-29 15:23:28 +08:00
Cheng Zhao
a579f58454 spec: asar: fs.lstat 2014-09-29 15:00:13 +08:00
Cheng Zhao
35e867820e Make sure fs.stat and fs.lstat are async 2014-09-29 14:59:44 +08:00
Cheng Zhao
a757c62da5 Use "null" instead of "undefined" as no error 2014-09-29 14:57:10 +08:00
Cheng Zhao
370dd26745 spec: asar: fs.lstatSync 2014-09-29 14:45:19 +08:00
Cheng Zhao
e20697b870 spec: asar: fs.readFile 2014-09-28 23:36:45 +08:00
Cheng Zhao
4d01aa2772 Fix shifting args in fs.readFile 2014-09-28 23:36:12 +08:00
Cheng Zhao
4a485f9819 spec: asar: fs.readFileSync 2014-09-28 23:02:14 +08:00
Cheng Zhao
cebafeae40 Fix getting file from symbol linked directory. 2014-09-28 22:46:29 +08:00
Cheng Zhao
150739e19e Fix calling fs.open in fs.readFile wrapper 2014-09-28 22:45:29 +08:00
Cheng Zhao
38f83cacf9 Make some APIs work with archive.copyFileOut API. 2014-09-25 23:25:17 +08:00
Cheng Zhao
fc8ff314e2 Make docs follow 80 columns rule. 2014-09-25 23:22:29 +08:00
Cheng Zhao
8acf96d268 Make spliting paths faster. 2014-09-25 22:18:40 +08:00
Cheng Zhao
c49a44f944 Remove unneeded ArchiveFactory. 2014-09-25 21:54:59 +08:00
Cheng Zhao
390b804ca0 Make process.dlopen work for asar packages. 2014-09-25 21:49:28 +08:00
Cheng Zhao
05317ad81e Clean cached asar archives when quitting. 2014-09-25 21:49:01 +08:00
Cheng Zhao
d559275711 Emit "exit" event for "process" when quitting. 2014-09-25 21:48:30 +08:00
Cheng Zhao
909ff085ac Add "quit" event for app. 2014-09-25 21:48:15 +08:00
Cheng Zhao
dbbfef38b1 Cache asar archives on JavaScript side. 2014-09-25 20:48:32 +08:00
Cheng Zhao
4006b6407c Just use plain pointer for weak reference. 2014-09-25 20:38:12 +08:00
Cheng Zhao
c95a93ef1c Add a way to copy a file in archive into filesystem. 2014-09-25 16:56:50 +08:00
Cheng Zhao
e5e1e207b6 Also search for app.asar when starting app. 2014-09-24 20:09:41 +08:00
Cheng Zhao
e0c469183d Make sure fs.readdir calls its callback asynchronously. 2014-09-24 19:10:37 +08:00
Cheng Zhao
4d2e4ed573 Fill the stats object as much as we can. 2014-09-24 19:10:13 +08:00
Cheng Zhao
0cab034dab Make fs.readdir support asar package. 2014-09-24 18:44:00 +08:00
Cheng Zhao
9f9d209e3d Make options of fs.readFile work. 2014-09-24 16:24:22 +08:00
Cheng Zhao
8740147aa2 Make fs.readFile support asar package 2014-09-24 15:38:07 +08:00
Cheng Zhao
9b755620d3 Make fs.stat support asar package 2014-09-24 15:38:02 +08:00
Cheng Zhao
fa287c2422 Fix getting information for root. 2014-09-24 13:42:04 +08:00
Cheng Zhao
b6cded379e Fix __dirname and __filename in asar: protocol. 2014-09-24 13:23:37 +08:00
Cheng Zhao
8199ad2ae6 Add asar.stat method. 2014-09-24 12:02:33 +08:00
Cheng Zhao
0d09143a77 Add JavaScript bindings of asar::Archive. 2014-09-24 11:10:07 +08:00
Cheng Zhao
7081f7799b Separate the archive cache out to ArchiveFactory. 2014-09-23 22:31:45 +08:00
Cheng Zhao
b6583635d4 Caching the Archive object. 2014-09-23 21:48:40 +08:00
Cheng Zhao
b01db4aa09 Send file content in asar:// 2014-09-23 20:30:07 +08:00
Cheng Zhao
6d712da7e3 Read the archive's header when there is a url request 2014-09-23 19:14:30 +08:00
Cheng Zhao
9b71117171 Add asar:// protocol handler. 2014-09-23 12:13:46 +08:00
Cheng Zhao
50ea0f0b45 Merge pull request #659 from hokein/master
mac: Fix dock progress bar doesn't show after hiding, fixes #658.
2014-09-22 20:52:32 +08:00
Haojian Wu
fa8e158587 mac: Fix dock progress bar doesn't show after hiding, fixes #658. 2014-09-21 18:56:03 +08:00
Cheng Zhao
2768b1ff64 Fix creating empty chromedriver archive. 2014-09-20 15:29:46 +00:00
Cheng Zhao
b3770bc407 Bump v0.16.3. 2014-09-20 23:12:05 +08:00
Cheng Zhao
8f44046f9a Fix chromedriver's version in archive. 2014-09-20 23:09:49 +08:00
Cheng Zhao
a717235212 Only include chromedriver in vX.X.0 releases. 2014-09-20 22:39:52 +08:00
Cheng Zhao
805215be78 Merge pull request #655 from hokein/master
SetProgressBar API Implementation, fixes #635
2014-09-20 11:19:34 +08:00
Haojian Wu
e7fbe84644 Use app name as desktop name by default. 2014-09-18 22:58:17 +08:00
Cheng Zhao
9653f20995 win: Add "direct-write" option for BrowserWindow.
For atom/atom#3540.
2014-09-18 21:49:04 +08:00
Haojian Wu
e959a40b49 docs: setProgressBar API. 2014-09-18 19:32:58 +08:00
Haojian Wu
d9ce3f0ca3 linux: Implement SetProgressBar API. 2014-09-18 19:26:52 +08:00
Haojian Wu
d8f57a0ecc Correct code style. 2014-09-18 16:48:00 +08:00
Haojian Wu
c5e0b65cc7 mac: Implement SetProgressBar API. 2014-09-18 10:20:55 +08:00
Haojian Wu
b5e82dac6f win: Implement SetProgressBar API. 2014-09-17 09:42:47 +08:00
Cheng Zhao
1381d16f9c Merge pull request #652 from atom/chromedriver
Ship chromedriver and add docs on how to use it
2014-09-16 17:45:39 +08:00
Cheng Zhao
268508764f docs: use => using 2014-09-13 00:16:32 +08:00
Cheng Zhao
34109fa741 docs: Document how to use chromedriver. 2014-09-13 00:07:21 +08:00
Cheng Zhao
925ff2da5b Pretend to be Chrome by default.
This is used to cheat client web drivers.
2014-09-12 23:28:14 +08:00
Cheng Zhao
b8a6658ba9 Make our user agent string follow standard. 2014-09-12 23:08:13 +08:00
Cheng Zhao
4a4814b41c default_app: Don't quit when started as web driver. 2014-09-12 22:54:00 +08:00
Cheng Zhao
f952dae0d0 Create dist for chromedriver and upload it. 2014-09-12 22:10:06 +08:00
Cheng Zhao
cba155bcfb Add action to copy chromedriver. 2014-09-12 21:48:45 +08:00
Cheng Zhao
0f714c81cd Merge pull request #644 from lusbuab/patch-1
Correct parameter type of setHightlightMode()
2014-09-10 10:30:42 +09:00
Cheng Zhao
92b5dab3f9 Merge pull request #642 from hokein/master
Add Volume keys support in global-shortcut API, fix #630.
2014-09-10 10:30:26 +09:00
Florian
6ca238852a Correct parameter type of setHightlightMode() 2014-09-09 15:04:00 +02:00
Haojian Wu
d2368d2d3b Add Volume keys support in global-shortcut API, fix #630. 2014-09-09 20:56:47 +08:00
Cheng Zhao
88269a613a Bump v0.16.2. 2014-09-09 20:07:08 +08:00
Cheng Zhao
5696fe8ec8 No need to set "--harmony" in renderer process.
After Chrome 37 renderer process can work correctly without it.
2014-09-09 20:05:43 +08:00
Cheng Zhao
ba439b6824 Merge pull request #643 from atom/mac-tray
Add some OS X only Tray APIs
2014-09-09 21:00:27 +09:00
Cheng Zhao
c8a8576970 docs: Document the new Tray APIs. 2014-09-09 19:50:50 +08:00
Cheng Zhao
67cbecaba0 mac: Add "double-clicked" event for Tray. 2014-09-09 19:45:21 +08:00
Cheng Zhao
ec1db0c7bb mac: Add Tray.setHighlightMode API, fixes #425. 2014-09-09 19:39:39 +08:00
Cheng Zhao
4330d67e0d mac: Add Tray.setTitle API, fixes #560. 2014-09-09 19:36:15 +08:00
Cheng Zhao
db8de9e60d Make default_app focus the main window on startup. 2014-09-09 18:33:36 +08:00
Cheng Zhao
9c9a306095 Upgrade brightray. 2014-09-09 18:33:22 +08:00
Cheng Zhao
bda317b000 views: Set devtools window's icon, fixes #429. 2014-09-09 15:30:33 +08:00
Cheng Zhao
700510d63a mac: Don't activate window when showing. 2014-09-09 14:47:04 +08:00
Cheng Zhao
ab2714fda9 Merge pull request #641 from atom/web-runtime-flags
Add options for web runtime features
2014-09-09 14:43:08 +08:00
Cheng Zhao
33b94edcf0 Use PersistentDictionary to store web perferences. 2014-09-09 14:13:21 +08:00
Cheng Zhao
44d3e58ddb Make code more tidy. 2014-09-09 13:21:15 +08:00
Cheng Zhao
f08c3f9134 docs: Add options for web runtime features. 2014-09-09 11:14:44 +08:00
Cheng Zhao
8de90db429 Pass web runtime features by command line. 2014-09-09 11:08:30 +08:00
Cheng Zhao
81241b38eb Add switches of web runtime flags. 2014-09-09 10:33:31 +08:00
Cheng Zhao
1c07b9c85b Bump v0.16.1. 2014-09-08 16:00:32 +08:00
Cheng Zhao
1199224086 BrowserWindow.show() should not focus window, fixes #609. 2014-09-08 15:28:34 +08:00
Cheng Zhao
add4e3c6f5 docs: Now atom-shell's version should be used when building modules. 2014-09-08 15:07:33 +08:00
Cheng Zhao
eb55f1cf47 Merge pull request #636 from kitematic/master
Fixing dialog api parameter parsing
2014-09-08 11:26:03 +08:00
Jeffrey Morgan
8367071dc6 Fixing dialog api parameter parsing 2014-09-07 15:14:43 -07:00
123 changed files with 5680 additions and 222 deletions

View File

@@ -39,6 +39,7 @@
'atom/common/api/lib/screen.coffee',
'atom/common/api/lib/shell.coffee',
'atom/common/lib/init.coffee',
'atom/common/lib/asar.coffee',
'atom/renderer/lib/chrome-api.coffee',
'atom/renderer/lib/init.coffee',
'atom/renderer/lib/inspector.coffee',
@@ -100,6 +101,8 @@
'atom/browser/atom_javascript_dialog_manager.h',
'atom/browser/atom_resource_dispatcher_host_delegate.cc',
'atom/browser/atom_resource_dispatcher_host_delegate.h',
'atom/browser/atom_speech_recognition_manager_delegate.cc',
'atom/browser/atom_speech_recognition_manager_delegate.h',
'atom/browser/browser.cc',
'atom/browser/browser.h',
'atom/browser/browser_linux.cc',
@@ -121,6 +124,10 @@
'atom/browser/native_window_observer.h',
'atom/browser/net/adapter_request_job.cc',
'atom/browser/net/adapter_request_job.h',
'atom/browser/net/asar/asar_protocol_handler.cc',
'atom/browser/net/asar/asar_protocol_handler.h',
'atom/browser/net/asar/url_request_asar_job.cc',
'atom/browser/net/asar/url_request_asar_job.h',
'atom/browser/net/atom_url_request_job_factory.cc',
'atom/browser/net/atom_url_request_job_factory.h',
'atom/browser/net/url_request_string_job.cc',
@@ -174,6 +181,7 @@
'atom/browser/window_list.h',
'atom/browser/window_list_observer.h',
'atom/common/api/api_messages.h',
'atom/common/api/atom_api_asar.cc',
'atom/common/api/atom_api_clipboard.cc',
'atom/common/api/atom_api_crash_reporter.cc',
'atom/common/api/atom_api_id_weak_map.cc',
@@ -186,6 +194,10 @@
'atom/common/api/atom_bindings.h',
'atom/common/api/object_life_monitor.cc',
'atom/common/api/object_life_monitor.h',
'atom/common/asar/archive.cc',
'atom/common/asar/archive.h',
'atom/common/asar/scoped_temporary_file.cc',
'atom/common/asar/scoped_temporary_file.h',
'atom/common/common_message_generator.cc',
'atom/common/common_message_generator.h',
'atom/common/crash_reporter/crash_reporter.cc',
@@ -204,6 +216,7 @@
'atom/common/crash_reporter/win/crash_service_main.h',
'atom/common/draggable_region.cc',
'atom/common/draggable_region.h',
'atom/common/google_api_key.h',
'atom/common/linux/application_info.cc',
'atom/common/native_mate_converters/accelerator_converter.cc',
'atom/common/native_mate_converters/accelerator_converter.h',
@@ -269,6 +282,16 @@
'chromium_src/chrome/browser/printing/printing_message_filter.h',
'chromium_src/chrome/browser/printing/printing_ui_web_contents_observer.cc',
'chromium_src/chrome/browser/printing/printing_ui_web_contents_observer.h',
'chromium_src/chrome/browser/speech/tts_controller.h',
'chromium_src/chrome/browser/speech/tts_controller_impl.cc',
'chromium_src/chrome/browser/speech/tts_controller_impl.h',
'chromium_src/chrome/browser/speech/tts_linux.cc',
'chromium_src/chrome/browser/speech/tts_mac.mm',
'chromium_src/chrome/browser/speech/tts_message_filter.cc',
'chromium_src/chrome/browser/speech/tts_message_filter.h',
'chromium_src/chrome/browser/speech/tts_platform.cc',
'chromium_src/chrome/browser/speech/tts_platform.h',
'chromium_src/chrome/browser/speech/tts_win.cc',
'chromium_src/chrome/browser/ui/libgtk2ui/app_indicator_icon_menu.cc',
'chromium_src/chrome/browser/ui/libgtk2ui/app_indicator_icon_menu.h',
'chromium_src/chrome/browser/ui/libgtk2ui/gtk2_status_icon.cc',
@@ -279,11 +302,17 @@
'chromium_src/chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h',
'chromium_src/chrome/common/print_messages.cc',
'chromium_src/chrome/common/print_messages.h',
'chromium_src/chrome/common/tts_messages.h',
'chromium_src/chrome/common/tts_utterance_request.cc',
'chromium_src/chrome/common/tts_utterance_request.h',
'chromium_src/chrome/renderer/printing/print_web_view_helper.cc',
'chromium_src/chrome/renderer/printing/print_web_view_helper_linux.cc',
'chromium_src/chrome/renderer/printing/print_web_view_helper_mac.mm',
'chromium_src/chrome/renderer/printing/print_web_view_helper_win.cc',
'chromium_src/chrome/renderer/printing/print_web_view_helper.h',
'chromium_src/chrome/renderer/tts_dispatcher.cc',
'chromium_src/chrome/renderer/tts_dispatcher.h',
'chromium_src/library_loaders/libspeechd_loader.cc',
'<@(native_mate_files)',
],
'framework_sources': [
@@ -539,6 +568,7 @@
'vendor/breakpad/src',
],
'cflags': [
'-Wno-deprecated-register',
'-Wno-empty-body',
],
'dependencies': [
@@ -688,6 +718,36 @@
}], # OS=="linux"
],
}, # target <(project_name>_dump_symbols
{
'target_name': 'copy_chromedriver',
'type': 'none',
'actions': [
{
'action_name': 'Copy ChromeDriver Binary',
'variables': {
'conditions': [
['OS=="win"', {
'chromedriver_binary': 'chromedriver.exe',
},{
'chromedriver_binary': 'chromedriver',
}],
],
},
'inputs': [
'<(libchromiumcontent_library_dir)/<(chromedriver_binary)',
],
'outputs': [
'<(PRODUCT_DIR)/<(chromedriver_binary)',
],
'action': [
'python',
'tools/copy_binary.py',
'<@(_inputs)',
'<@(_outputs)',
],
}
],
}, # copy_chromedriver
],
'conditions': [
['OS=="mac"', {

View File

@@ -7,6 +7,8 @@
#include <string>
#include <vector>
#include "atom/common/chrome_version.h"
namespace atom {
AtomContentClient::AtomContentClient() {
@@ -15,6 +17,10 @@ AtomContentClient::AtomContentClient() {
AtomContentClient::~AtomContentClient() {
}
std::string AtomContentClient::GetProduct() const {
return "Chrome/" CHROME_VERSION_STRING;
}
void AtomContentClient::AddAdditionalSchemes(
std::vector<std::string>* standard_schemes,
std::vector<std::string>* savable_schemes) {

View File

@@ -19,6 +19,7 @@ class AtomContentClient : public brightray::ContentClient {
protected:
// content::ContentClient:
virtual std::string GetProduct() const OVERRIDE;
virtual void AddAdditionalSchemes(
std::vector<std::string>* standard_schemes,
std::vector<std::string>* savable_schemes) OVERRIDE;

View File

@@ -8,9 +8,11 @@
#include "atom/app/atom_content_client.h"
#include "atom/browser/atom_browser_client.h"
#include "atom/common/google_api_key.h"
#include "atom/renderer/atom_renderer_client.h"
#include "base/command_line.h"
#include "base/debug/stack_trace.h"
#include "base/environment.h"
#include "base/logging.h"
#include "content/public/common/content_switches.h"
#include "ui/base/resource/resource_bundle.h"
@@ -52,6 +54,11 @@ bool AtomMainDelegate::BasicStartupComplete(int* exit_code) {
void AtomMainDelegate::PreSandboxStartup() {
brightray::MainDelegate::PreSandboxStartup();
// Set google API key.
scoped_ptr<base::Environment> env(base::Environment::Create());
if (!env->HasVar("GOOGLE_API_KEY"))
env->SetVar("GOOGLE_API_KEY", GOOGLEAPIS_API_KEY);
CommandLine* command_line = CommandLine::ForCurrentProcess();
std::string process_type = command_line->GetSwitchValueASCII(
switches::kProcessType);
@@ -73,6 +80,11 @@ void AtomMainDelegate::PreSandboxStartup() {
// Disable renderer sandbox for most of node's functions.
command_line->AppendSwitch(switches::kNoSandbox);
#if defined(OS_MACOSX)
// Enable AVFoundation.
command_line->AppendSwitch("enable-avfoundation");
#endif
// Add a flag to mark the end of switches added by atom-shell.
command_line->AppendSwitch("atom-shell-switches-end");
}

View File

@@ -19,6 +19,7 @@
#include "native_mate/callback.h"
#include "native_mate/dictionary.h"
#include "native_mate/object_template_builder.h"
#include "net/base/load_flags.h"
#include "net/proxy/proxy_service.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
@@ -46,10 +47,10 @@ class ResolveProxyHelper {
// Start the request.
int result = proxy_service->ResolveProxy(
url, &proxy_info_,
url, net::LOAD_NORMAL, &proxy_info_,
base::Bind(&ResolveProxyHelper::OnResolveProxyCompleted,
base::Unretained(this)),
&pac_req_, net::BoundNetLog());
&pac_req_, nullptr, net::BoundNetLog());
// Completed synchronously.
if (result != net::ERR_IO_PENDING)
@@ -91,6 +92,10 @@ void App::OnWindowAllClosed() {
Emit("window-all-closed");
}
void App::OnQuit() {
Emit("quit");
}
void App::OnOpenFile(bool* prevent_default, const std::string& file_path) {
base::ListValue args;
args.AppendString(file_path);
@@ -134,6 +139,13 @@ void App::ResolveProxy(const GURL& url, ResolveProxyCallback callback) {
new ResolveProxyHelper(url, callback);
}
void App::SetDesktopName(const std::string& desktop_name) {
#if defined(OS_LINUX)
scoped_ptr<base::Environment> env(base::Environment::Create());
env->SetVar("CHROME_DESKTOP", desktop_name);
#endif
}
mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder(
v8::Isolate* isolate) {
Browser* browser = Browser::Get();
@@ -151,7 +163,8 @@ mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder(
.SetMethod("setName", base::Bind(&Browser::SetName,
base::Unretained(browser)))
.SetMethod("getDataPath", &App::GetDataPath)
.SetMethod("resolveProxy", &App::ResolveProxy);
.SetMethod("resolveProxy", &App::ResolveProxy)
.SetMethod("setDesktopName", &App::SetDesktopName);
}
// static

View File

@@ -36,6 +36,7 @@ class App : public mate::EventEmitter,
// BrowserObserver implementations:
virtual void OnWillQuit(bool* prevent_default) OVERRIDE;
virtual void OnWindowAllClosed() OVERRIDE;
virtual void OnQuit() OVERRIDE;
virtual void OnOpenFile(bool* prevent_default,
const std::string& file_path) OVERRIDE;
virtual void OnOpenURL(const std::string& url) OVERRIDE;
@@ -50,6 +51,7 @@ class App : public mate::EventEmitter,
private:
base::FilePath GetDataPath();
void ResolveProxy(const GURL& url, ResolveProxyCallback callback);
void SetDesktopName(const std::string& desktop_name);
DISALLOW_COPY_AND_ASSIGN(App);
};

View File

@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include <set>
#include <string>
#include "atom/common/native_mate_converters/file_path_converter.h"
#include "base/bind.h"
@@ -31,17 +32,30 @@ struct Converter<std::set<T> > {
};
template<>
struct Converter<TracingController::Options> {
struct Converter<base::debug::CategoryFilter> {
static bool FromV8(v8::Isolate* isolate,
v8::Handle<v8::Value> val,
TracingController::Options* out) {
if (!val->IsNumber())
base::debug::CategoryFilter* out) {
std::string filter;
if (!ConvertFromV8(isolate, val, &filter))
return false;
*out = static_cast<TracingController::Options>(val->IntegerValue());
*out = base::debug::CategoryFilter(filter);
return true;
}
};
template<>
struct Converter<base::debug::TraceOptions> {
static bool FromV8(v8::Isolate* isolate,
v8::Handle<v8::Value> val,
base::debug::TraceOptions* out) {
std::string options;
if (!ConvertFromV8(isolate, val, &options))
return false;
return out->SetFromString(options);
}
};
} // namespace mate
namespace {

View File

@@ -23,7 +23,7 @@ struct Converter<file_dialog::Filter> {
static bool FromV8(v8::Isolate* isolate,
v8::Handle<v8::Value> val,
file_dialog::Filter* out) {
mate::Dictionary dict(isolate);
mate::Dictionary dict;
if (!ConvertFromV8(isolate, val, &dict))
return false;
if (!dict.Get("name", &(out->first)))

View File

@@ -17,14 +17,15 @@ MenuViews::MenuViews() {
void MenuViews::Popup(Window* window) {
gfx::Point cursor = gfx::Screen::GetNativeScreen()->GetCursorScreenPoint();
views::MenuRunner menu_runner(model());
views::MenuRunner menu_runner(
model(),
views::MenuRunner::CONTEXT_MENU | views::MenuRunner::HAS_MNEMONICS);
ignore_result(menu_runner.RunMenuAt(
static_cast<NativeWindowViews*>(window->window())->widget(),
NULL,
gfx::Rect(cursor, gfx::Size()),
views::MENU_ANCHOR_TOPLEFT,
ui::MENU_SOURCE_MOUSE,
views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU));
ui::MENU_SOURCE_MOUSE));
}
// static

View File

@@ -36,6 +36,10 @@ void Tray::OnClicked() {
Emit("clicked");
}
void Tray::OnDoubleClicked() {
Emit("double-clicked");
}
void Tray::SetImage(const gfx::ImageSkia& image) {
tray_icon_->SetImage(image);
}
@@ -48,6 +52,14 @@ void Tray::SetToolTip(const std::string& tool_tip) {
tray_icon_->SetToolTip(tool_tip);
}
void Tray::SetTitle(const std::string& title) {
tray_icon_->SetTitle(title);
}
void Tray::SetHighlightMode(bool highlight) {
tray_icon_->SetHighlightMode(highlight);
}
void Tray::SetContextMenu(Menu* menu) {
tray_icon_->SetContextMenu(menu->model());
}
@@ -59,6 +71,8 @@ void Tray::BuildPrototype(v8::Isolate* isolate,
.SetMethod("setImage", &Tray::SetImage)
.SetMethod("setPressedImage", &Tray::SetPressedImage)
.SetMethod("setToolTip", &Tray::SetToolTip)
.SetMethod("setTitle", &Tray::SetTitle)
.SetMethod("setHighlightMode", &Tray::SetHighlightMode)
.SetMethod("_setContextMenu", &Tray::SetContextMenu);
}

View File

@@ -37,10 +37,13 @@ class Tray : public mate::EventEmitter,
// TrayIcon implementations:
virtual void OnClicked() OVERRIDE;
virtual void OnDoubleClicked() OVERRIDE;
void SetImage(const gfx::ImageSkia& image);
void SetPressedImage(const gfx::ImageSkia& image);
void SetToolTip(const std::string& tool_tip);
void SetTitle(const std::string& title);
void SetHighlightMode(bool highlight);
void SetContextMenu(Menu* menu);
private:

View File

@@ -35,10 +35,10 @@ void WebContents::RenderProcessGone(base::TerminationStatus status) {
Emit("crashed");
}
void WebContents::DidFinishLoad(int64 frame_id,
const GURL& validated_url,
bool is_main_frame,
content::RenderViewHost* render_view_host) {
void WebContents::DidFinishLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url) {
bool is_main_frame = !render_frame_host->GetParent();
base::ListValue args;
args.AppendBoolean(is_main_frame);
Emit("did-frame-finish-load", args);

View File

@@ -52,11 +52,8 @@ class WebContents : public mate::EventEmitter,
// content::WebContentsObserver implementations:
virtual void RenderViewDeleted(content::RenderViewHost*) OVERRIDE;
virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE;
virtual void DidFinishLoad(
int64 frame_id,
const GURL& validated_url,
bool is_main_frame,
content::RenderViewHost* render_view_host) OVERRIDE;
virtual void DidFinishLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url) OVERRIDE;
virtual void DidStartLoading(
content::RenderViewHost* render_view_host) OVERRIDE;
virtual void DidStopLoading(

View File

@@ -366,6 +366,10 @@ void Window::Print(mate::Arguments* args) {
window_->Print(settings.silent, settings.print_backgournd);
}
void Window::SetProgressBar(double progress) {
window_->SetProgressBar(progress);
}
mate::Handle<WebContents> Window::GetWebContents(v8::Isolate* isolate) const {
return WebContents::Create(isolate, window_->GetWebContents());
}
@@ -428,6 +432,7 @@ void Window::BuildPrototype(v8::Isolate* isolate,
.SetMethod("isWebViewFocused", &Window::IsWebViewFocused)
.SetMethod("capturePage", &Window::CapturePage)
.SetMethod("print", &Window::Print)
.SetMethod("setProgressBar", &Window::SetProgressBar)
.SetMethod("_getWebContents", &Window::GetWebContents)
.SetMethod("_getDevToolsWebContents", &Window::GetDevToolsWebContents);
}

View File

@@ -103,6 +103,7 @@ class Window : public mate::EventEmitter,
bool IsDocumentEdited();
void CapturePage(mate::Arguments* args);
void Print(mate::Arguments* args);
void SetProgressBar(double progress);
// APIs for WebContents.
mate::Handle<WebContents> GetWebContents(v8::Isolate* isolate) const;

View File

@@ -11,6 +11,12 @@
namespace mate {
namespace {
v8::Persistent<v8::ObjectTemplate> template_;
} // namespace
Event::Event()
: sender_(NULL),
message_(NULL),
@@ -21,9 +27,14 @@ Event::~Event() {
}
ObjectTemplateBuilder Event::GetObjectTemplateBuilder(v8::Isolate* isolate) {
return ObjectTemplateBuilder(isolate)
.SetMethod("preventDefault", &Event::PreventDefault)
.SetMethod("sendReply", &Event::SendReply);
if (template_.IsEmpty())
template_.Reset(isolate, ObjectTemplateBuilder(isolate)
.SetMethod("preventDefault", &Event::PreventDefault)
.SetMethod("sendReply", &Event::SendReply)
.Build());
return ObjectTemplateBuilder(
isolate, v8::Local<v8::ObjectTemplate>::New(isolate, template_));
}
void Event::SetSenderAndMessage(content::WebContents* sender,

View File

@@ -10,11 +10,37 @@
#include "atom/common/native_mate_converters/v8_value_converter.h"
#include "base/memory/scoped_ptr.h"
#include "base/values.h"
#include "native_mate/arguments.h"
#include "native_mate/object_template_builder.h"
#include "atom/common/node_includes.h"
namespace mate {
namespace {
v8::Persistent<v8::ObjectTemplate> event_template;
void PreventDefault(mate::Arguments* args) {
args->GetThis()->SetHiddenValue(
StringToV8(args->isolate(), "prevent_default"),
v8::True(args->isolate()));
}
// Create a pure JavaScript Event object.
v8::Local<v8::Object> CreateEventObject(v8::Isolate* isolate) {
if (event_template.IsEmpty()) {
event_template.Reset(isolate, ObjectTemplateBuilder(isolate)
.SetMethod("preventDefault", &PreventDefault)
.Build());
}
return v8::Local<v8::ObjectTemplate>::New(
isolate, event_template)->NewInstance();
}
} // namespace
EventEmitter::EventEmitter() {
}
@@ -38,15 +64,22 @@ bool EventEmitter::Emit(const base::StringPiece& name,
v8::Handle<v8::Context> context = isolate->GetCurrentContext();
scoped_ptr<atom::V8ValueConverter> converter(new atom::V8ValueConverter);
mate::Handle<mate::Event> event = mate::Event::Create(isolate);
if (sender && message)
event->SetSenderAndMessage(sender, message);
v8::Handle<v8::Object> event;
bool use_native_event = sender && message;
if (use_native_event) {
mate::Handle<mate::Event> native_event = mate::Event::Create(isolate);
native_event->SetSenderAndMessage(sender, message);
event = v8::Handle<v8::Object>::Cast(native_event.ToV8());
} else {
event = CreateEventObject(isolate);
}
// v8_args = [name, event, args...];
std::vector<v8::Handle<v8::Value>> v8_args;
v8_args.reserve(args.GetSize() + 2);
v8_args.push_back(mate::StringToV8(isolate, name));
v8_args.push_back(event.ToV8());
v8_args.push_back(event);
for (size_t i = 0; i < args.GetSize(); i++) {
const base::Value* value(NULL);
if (args.Get(i, &value))
@@ -57,7 +90,17 @@ bool EventEmitter::Emit(const base::StringPiece& name,
node::MakeCallback(isolate, GetWrapper(isolate), "emit", v8_args.size(),
&v8_args[0]);
return event->prevent_default();
if (use_native_event) {
Handle<Event> native_event;
if (ConvertFromV8(isolate, event, &native_event))
return native_event->prevent_default();
}
v8::Handle<v8::Value> prevent_default =
event->GetHiddenValue(StringToSymbol(isolate, "prevent_default"));
if (prevent_default.IsEmpty())
return false;
return prevent_default->BooleanValue();
}
} // namespace mate

View File

@@ -51,7 +51,7 @@ module.exports =
window,
wrappedCallback
showSaveDialog: (window, options, callback) ->
showSaveDialog: (args...) ->
[window, options, callback] = parseArgs args...
options ?= title: 'Save'
@@ -71,7 +71,7 @@ module.exports =
window,
wrappedCallback
showMessageBox: (window, options, callback) ->
showMessageBox: (args...) ->
[window, options, callback] = parseArgs args...
options ?= type: 'none'

View File

@@ -7,10 +7,7 @@
#include <utility>
#include "atom/browser/atom_browser_context.h"
#ifndef GOOGLEAPIS_API_KEY
#define GOOGLEAPIS_API_KEY "AIzaSyAQfxPJiounkhOjODEO5ZieffeBv6yft2Q"
#endif
#include "atom/common/google_api_key.h"
namespace atom {

View File

@@ -8,16 +8,18 @@
#include "atom/browser/atom_browser_context.h"
#include "atom/browser/atom_browser_main_parts.h"
#include "atom/browser/atom_resource_dispatcher_host_delegate.h"
#include "atom/browser/atom_speech_recognition_manager_delegate.h"
#include "atom/browser/native_window.h"
#include "atom/browser/window_list.h"
#include "chrome/browser/printing/printing_message_filter.h"
#include "chrome/browser/speech/tts_message_filter.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/resource_dispatcher_host.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/web_preferences.h"
#include "ui/base/l10n/l10n_util.h"
#include "webkit/common/webpreferences.h"
namespace atom {
@@ -51,7 +53,9 @@ AtomBrowserClient::~AtomBrowserClient() {
void AtomBrowserClient::RenderProcessWillLaunch(
content::RenderProcessHost* host) {
int id = host->GetID();
host->AddFilter(new PrintingMessageFilter(host->GetID()));
host->AddFilter(new TtsMessageFilter(id, host->GetBrowserContext()));
}
void AtomBrowserClient::ResourceDispatcherHostCreated() {
@@ -60,6 +64,11 @@ void AtomBrowserClient::ResourceDispatcherHostCreated() {
resource_dispatcher_delegate_.get());
}
content::SpeechRecognitionManagerDelegate*
AtomBrowserClient::GetSpeechRecognitionManagerDelegate() {
return new AtomSpeechRecognitionManagerDelegate;
}
content::AccessTokenStore* AtomBrowserClient::CreateAccessTokenStore() {
return new AtomAccessTokenStore;
}
@@ -67,7 +76,7 @@ content::AccessTokenStore* AtomBrowserClient::CreateAccessTokenStore() {
void AtomBrowserClient::OverrideWebkitPrefs(
content::RenderViewHost* render_view_host,
const GURL& url,
WebPreferences* prefs) {
content::WebPreferences* prefs) {
prefs->javascript_enabled = true;
prefs->web_security_enabled = true;
prefs->javascript_can_open_windows_automatically = true;

View File

@@ -23,10 +23,12 @@ class AtomBrowserClient : public brightray::BrowserClient {
virtual void RenderProcessWillLaunch(
content::RenderProcessHost* host) OVERRIDE;
virtual void ResourceDispatcherHostCreated() OVERRIDE;
virtual content::SpeechRecognitionManagerDelegate*
GetSpeechRecognitionManagerDelegate() override;
virtual content::AccessTokenStore* CreateAccessTokenStore() OVERRIDE;
virtual void OverrideWebkitPrefs(content::RenderViewHost* render_view_host,
const GURL& url,
WebPreferences* prefs) OVERRIDE;
content::WebPreferences* prefs) OVERRIDE;
virtual bool ShouldSwapBrowsingInstancesForNavigation(
content::SiteInstance* site_instance,
const GURL& current_url,

View File

@@ -6,6 +6,7 @@
#include "atom/browser/atom_browser_main_parts.h"
#include "atom/browser/net/atom_url_request_job_factory.h"
#include "atom/browser/net/asar/asar_protocol_handler.h"
#include "base/threading/sequenced_worker_pool.h"
#include "base/threading/worker_pool.h"
#include "chrome/browser/browser_process.h"
@@ -20,6 +21,12 @@ using content::BrowserThread;
namespace atom {
namespace {
const char* kAsarScheme = "asar";
} // namespace
AtomBrowserContext::AtomBrowserContext()
: fake_browser_process_(new BrowserProcess),
job_factory_(new AtomURLRequestJobFactory) {
@@ -44,6 +51,10 @@ net::URLRequestJobFactory* AtomBrowserContext::CreateURLRequestJobFactory(
url::kFileScheme, new net::FileProtocolHandler(
BrowserThread::GetBlockingPool()->GetTaskRunnerWithShutdownBehavior(
base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)));
job_factory->SetProtocolHandler(
kAsarScheme, new asar::AsarProtocolHandler(
BrowserThread::GetBlockingPool()->GetTaskRunnerWithShutdownBehavior(
base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)));
// Set up interceptors in the reverse order.
scoped_ptr<net::URLRequestJobFactory> top_job_factory =

View File

@@ -28,7 +28,8 @@ AtomBrowserMainParts* AtomBrowserMainParts::self_ = NULL;
AtomBrowserMainParts::AtomBrowserMainParts()
: browser_(new Browser),
node_bindings_(NodeBindings::Create(true)),
atom_bindings_(new AtomBindings) {
atom_bindings_(new AtomBindings),
gc_timer_(true, true) {
DCHECK(!self_) << "Cannot have two AtomBrowserMainParts";
self_ = this;
}
@@ -75,6 +76,13 @@ void AtomBrowserMainParts::PreMainMessageLoopRun() {
node_bindings_->PrepareMessageLoop();
node_bindings_->RunMessageLoop();
// Start idle gc.
gc_timer_.Start(
FROM_HERE, base::TimeDelta::FromMinutes(1),
base::Bind(base::IgnoreResult(&v8::Isolate::IdleNotification),
base::Unretained(js_env_->isolate()),
1000));
brightray::BrowserMainParts::PreMainMessageLoopRun();
#if defined(USE_X11)

View File

@@ -5,6 +5,7 @@
#ifndef ATOM_BROWSER_ATOM_BROWSER_MAIN_PARTS_H_
#define ATOM_BROWSER_ATOM_BROWSER_MAIN_PARTS_H_
#include "base/timer/timer.h"
#include "brightray/browser/browser_main_parts.h"
namespace atom {
@@ -43,6 +44,8 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts {
scoped_ptr<AtomBindings> atom_bindings_;
scoped_ptr<NodeDebugger> node_debugger_;
base::Timer gc_timer_;
static AtomBrowserMainParts* self_;
DISALLOW_COPY_AND_ASSIGN(AtomBrowserMainParts);

View File

@@ -0,0 +1,74 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/atom_speech_recognition_manager_delegate.h"
#include <string>
#include "base/callback.h"
namespace atom {
AtomSpeechRecognitionManagerDelegate::AtomSpeechRecognitionManagerDelegate() {
}
AtomSpeechRecognitionManagerDelegate::~AtomSpeechRecognitionManagerDelegate() {
}
void AtomSpeechRecognitionManagerDelegate::OnRecognitionStart(int session_id) {
}
void AtomSpeechRecognitionManagerDelegate::OnAudioStart(int session_id) {
}
void AtomSpeechRecognitionManagerDelegate::OnEnvironmentEstimationComplete(
int session_id) {
}
void AtomSpeechRecognitionManagerDelegate::OnSoundStart(int session_id) {
}
void AtomSpeechRecognitionManagerDelegate::OnSoundEnd(int session_id) {
}
void AtomSpeechRecognitionManagerDelegate::OnAudioEnd(int session_id) {
}
void AtomSpeechRecognitionManagerDelegate::OnRecognitionEnd(int session_id) {
}
void AtomSpeechRecognitionManagerDelegate::OnRecognitionResults(
int session_id, const content::SpeechRecognitionResults& result) {
}
void AtomSpeechRecognitionManagerDelegate::OnRecognitionError(
int session_id, const content::SpeechRecognitionError& error) {
}
void AtomSpeechRecognitionManagerDelegate::OnAudioLevelsChange(
int session_id, float volume, float noise_volume) {
}
void AtomSpeechRecognitionManagerDelegate::GetDiagnosticInformation(
bool* can_report_metrics, std::string* hardware_info) {
*can_report_metrics = false;
}
void AtomSpeechRecognitionManagerDelegate::CheckRecognitionIsAllowed(
int session_id,
base::Callback<void(bool ask_user, bool is_allowed)> callback) {
callback.Run(true, true);
}
content::SpeechRecognitionEventListener*
AtomSpeechRecognitionManagerDelegate::GetEventListener() {
return this;
}
bool AtomSpeechRecognitionManagerDelegate::FilterProfanities(
int render_process_id) {
return false;
}
} // namespace atom

View File

@@ -0,0 +1,52 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_ATOM_SPEECH_RECOGNITION_MANAGER_DELEGATE_H_
#define ATOM_BROWSER_ATOM_SPEECH_RECOGNITION_MANAGER_DELEGATE_H_
#include <string>
#include "content/public/browser/speech_recognition_event_listener.h"
#include "content/public/browser/speech_recognition_manager_delegate.h"
namespace atom {
class AtomSpeechRecognitionManagerDelegate
: public content::SpeechRecognitionManagerDelegate,
public content::SpeechRecognitionEventListener {
public:
AtomSpeechRecognitionManagerDelegate();
virtual ~AtomSpeechRecognitionManagerDelegate();
// content::SpeechRecognitionEventListener:
virtual void OnRecognitionStart(int session_id) override;
virtual void OnAudioStart(int session_id) override;
virtual void OnEnvironmentEstimationComplete(int session_id) override;
virtual void OnSoundStart(int session_id) override;
virtual void OnSoundEnd(int session_id) override;
virtual void OnAudioEnd(int session_id) override;
virtual void OnRecognitionEnd(int session_id) override;
virtual void OnRecognitionResults(
int session_id, const content::SpeechRecognitionResults& result) override;
virtual void OnRecognitionError(
int session_id, const content::SpeechRecognitionError& error) override;
virtual void OnAudioLevelsChange(int session_id, float volume,
float noise_volume) override;
// content::SpeechRecognitionManagerDelegate:
virtual void GetDiagnosticInformation(bool* can_report_metrics,
std::string* hardware_info) override;
virtual void CheckRecognitionIsAllowed(
int session_id,
base::Callback<void(bool ask_user, bool is_allowed)> callback) override;
virtual content::SpeechRecognitionEventListener* GetEventListener() override;
virtual bool FilterProfanities(int render_process_id) override;
private:
DISALLOW_COPY_AND_ASSIGN(AtomSpeechRecognitionManagerDelegate);
};
} // namespace atom
#endif // ATOM_BROWSER_ATOM_SPEECH_RECOGNITION_MANAGER_DELEGATE_H_

View File

@@ -37,6 +37,8 @@ void Browser::Quit() {
}
void Browser::Shutdown() {
FOR_EACH_OBSERVER(BrowserObserver, observers_, OnQuit());
is_quiting_ = true;
base::MessageLoop::current()->Quit();
}

View File

@@ -18,6 +18,9 @@ class BrowserObserver {
// method will not be called, instead it will call OnWillQuit.
virtual void OnWindowAllClosed() {}
// The browser is quitting.
virtual void OnQuit() {}
// The browser has opened a file by double clicking in Finder or dragging the
// file to the Dock icon. (OS X only)
virtual void OnOpenFile(bool* prevent_default,

View File

@@ -12,15 +12,15 @@ app.on('window-all-closed', function() {
});
app.on('ready', function() {
app.commandLine.appendSwitch('js-flags', '--harmony_collections');
mainWindow = new BrowserWindow({
width: 800,
height: 600,
resizable: false,
'auto-hide-menu-bar': true,
'use-content-size': true,
});
mainWindow.loadUrl('file://' + __dirname + '/index.html');
mainWindow.focus();
if (process.platform == 'darwin') {
var template = [
@@ -151,33 +151,33 @@ app.on('ready', function() {
} else {
var template = [
{
label: 'File',
label: '&File',
submenu: [
{
label: 'Open',
label: '&Open',
accelerator: 'Ctrl+O',
},
{
label: 'Close',
label: '&Close',
accelerator: 'Ctrl+W',
click: function() { mainWindow.close(); }
},
]
},
{
label: 'View',
label: '&View',
submenu: [
{
label: 'Reload',
label: '&Reload',
accelerator: 'Ctrl+R',
click: function() { mainWindow.restart(); }
},
{
label: 'Enter Fullscreen',
label: '&Enter Fullscreen',
click: function() { mainWindow.setFullScreen(true); }
},
{
label: 'Toggle DevTools',
label: '&Toggle DevTools',
accelerator: 'Alt+Ctrl+I',
click: function() { mainWindow.toggleDevTools(); }
},

View File

@@ -2,12 +2,6 @@
<head>
<title>Atom Shell</title>
<style>
html {
height: 100%;
width: 100%;
overflow: hidden;
}
body {
color: #555;
font-family: 'Open Sans',Helvetica,Arial,sans-serif;

View File

@@ -11,11 +11,13 @@ app.on('window-all-closed', function() {
// Parse command line options.
var argv = process.argv.slice(1);
var option = { file: null, version: null };
var option = { file: null, version: null, webdriver: null };
for (var i in argv) {
if (argv[i] == '--version' || argv[i] == '-v') {
option.version = true;
break;
} else if (argv[i] == '--test-type=webdriver') {
option.webdriver = true;
} else if (argv[i][0] == '-') {
continue;
} else {
@@ -26,7 +28,7 @@ for (var i in argv) {
// Start the specified app if there is one specified in command line, otherwise
// start the default app.
if (option.file) {
if (option.file && !option.webdriver) {
try {
// Override app name and version.
var packagePath = path.resolve(option.file);

View File

@@ -52,20 +52,26 @@ setImmediate ->
detail: message
buttons: ['OK']
# Emit 'exit' event on quit.
require('app').on 'quit', ->
process.emit 'exit'
# Load the RPC server.
require './rpc-server.js'
# Now we try to load app's package.json.
packageJson = null
packagePath = path.join process.resourcesPath, 'app'
try
# First we try to load process.resourcesPath/app
packageJson = JSON.parse(fs.readFileSync(path.join(packagePath, 'package.json')))
catch error
# If not found then we load browser/default_app
packagePath = path.join process.resourcesPath, 'default_app'
packageJson = JSON.parse(fs.readFileSync(path.join(packagePath, 'package.json')))
searchPaths = [ 'app', 'app.asar', 'default_app' ]
for packagePath in searchPaths
try
packagePath = path.join process.resourcesPath, packagePath
packageJson = JSON.parse(fs.readFileSync(path.join(packagePath, 'package.json')))
break
catch e
continue
throw new Error("Unable to find a valid app") unless packageJson?
# Set application's version.
app = require 'app'
@@ -77,6 +83,12 @@ setImmediate ->
else if packageJson.name?
app.setName packageJson.name
# Set application's desktop name.
if packageJson.desktopName?
app.setDesktopName packageJson.desktopName
else
app.setDesktopName '#{app.getName()}.desktop'
# Load the chrome extension support.
require './chrome-extension.js'

View File

@@ -41,20 +41,43 @@
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/renderer_preferences.h"
#include "content/public/common/user_agent.h"
#include "content/public/common/web_preferences.h"
#include "ipc/ipc_message_macros.h"
#include "native_mate/dictionary.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/point.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/size.h"
#include "webkit/common/webpreferences.h"
using content::NavigationEntry;
namespace atom {
namespace {
// Array of available web runtime features.
const char* kWebRuntimeFeatures[] = {
switches::kExperimentalFeatures,
switches::kExperimentalCanvasFeatures,
switches::kSubpixelFontScaling,
switches::kOverlayScrollbars,
switches::kOverlayFullscreenVideo,
switches::kSharedWorker,
};
std::string RemoveWhitespace(const std::string& str) {
std::string trimmed;
if (base::RemoveChars(str, " ", &trimmed))
return trimmed;
else
return str;
}
} // namespace
NativeWindow::NativeWindow(content::WebContents* web_contents,
const mate::Dictionary& options)
: content::WebContentsObserver(web_contents),
@@ -92,8 +115,8 @@ NativeWindow::NativeWindow(content::WebContents* web_contents,
// Override the user agent to contain application and atom-shell's version.
Browser* browser = Browser::Get();
std::string product_name = base::StringPrintf(
"%s/%s Chrome/%s Atom-Shell/" ATOM_VERSION_STRING,
browser->GetName().c_str(),
"%s/%s Chrome/%s AtomShell/" ATOM_VERSION_STRING,
RemoveWhitespace(browser->GetName()).c_str(),
browser->GetVersion().c_str(),
CHROME_VERSION_STRING);
web_contents->GetMutableRendererPrefs()->user_agent_override =
@@ -225,9 +248,8 @@ bool NativeWindow::IsDevToolsOpened() {
void NativeWindow::InspectElement(int x, int y) {
OpenDevTools();
content::RenderViewHost* rvh = GetWebContents()->GetRenderViewHost();
scoped_refptr<content::DevToolsAgentHost> agent(
content::DevToolsAgentHost::GetOrCreateFor(rvh));
content::DevToolsAgentHost::GetOrCreateFor(GetWebContents()));
agent->InspectElement(x, y);
}
@@ -264,7 +286,7 @@ void NativeWindow::CapturePage(const gfx::Rect& rect,
base::Bind(&NativeWindow::OnCapturePageDone,
weak_factory_.GetWeakPtr(),
callback),
SkBitmap::kARGB_8888_Config);
kAlpha_8_SkColorType);
}
void NativeWindow::DestroyWebContents() {
@@ -325,33 +347,50 @@ void NativeWindow::AppendExtraCommandLineSwitches(
if (zoom_factor_ != 1.0)
command_line->AppendSwitchASCII(switches::kZoomFactor,
base::DoubleToString(zoom_factor_));
if (web_preferences_.IsEmpty())
return;
bool b;
#if defined(OS_WIN)
// Check if DirectWrite is disabled.
if (web_preferences_.Get(switches::kDirectWrite, &b) && !b)
command_line->AppendSwitch(::switches::kDisableDirectWrite);
#endif
// This set of options are not availabe in WebPreferences, so we have to pass
// them via command line and enable them in renderer procss.
for (size_t i = 0; i < arraysize(kWebRuntimeFeatures); ++i) {
const char* feature = kWebRuntimeFeatures[i];
if (web_preferences_.Get(feature, &b))
command_line->AppendSwitchASCII(feature, b ? "true" : "false");
}
}
void NativeWindow::OverrideWebkitPrefs(const GURL& url, WebPreferences* prefs) {
void NativeWindow::OverrideWebkitPrefs(const GURL& url,
content::WebPreferences* prefs) {
if (web_preferences_.IsEmpty())
return;
bool b;
std::vector<base::FilePath> list;
mate::Dictionary web_preferences(web_preferences_.isolate(),
web_preferences_.NewHandle());
if (web_preferences.Get("javascript", &b))
if (web_preferences_.Get("javascript", &b))
prefs->javascript_enabled = b;
if (web_preferences.Get("web-security", &b))
if (web_preferences_.Get("web-security", &b))
prefs->web_security_enabled = b;
if (web_preferences.Get("images", &b))
if (web_preferences_.Get("images", &b))
prefs->images_enabled = b;
if (web_preferences.Get("java", &b))
if (web_preferences_.Get("java", &b))
prefs->java_enabled = b;
if (web_preferences.Get("text-areas-are-resizable", &b))
if (web_preferences_.Get("text-areas-are-resizable", &b))
prefs->text_areas_are_resizable = b;
if (web_preferences.Get("webgl", &b))
if (web_preferences_.Get("webgl", &b))
prefs->experimental_webgl_enabled = b;
if (web_preferences.Get("webaudio", &b))
if (web_preferences_.Get("webaudio", &b))
prefs->webaudio_enabled = b;
if (web_preferences.Get("plugins", &b))
if (web_preferences_.Get("plugins", &b))
prefs->plugins_enabled = b;
if (web_preferences.Get("extra-plugin-dirs", &list))
if (web_preferences_.Get("extra-plugin-dirs", &list))
for (size_t i = 0; i < list.size(); ++i)
content::PluginService::GetInstance()->AddExtraPluginDir(list[i]);
}

View File

@@ -20,11 +20,9 @@
#include "brightray/browser/inspectable_web_contents_impl.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_observer.h"
#include "native_mate/scoped_persistent.h"
#include "native_mate/persistent_dictionary.h"
#include "ui/gfx/image/image_skia.h"
struct WebPreferences;
namespace base {
class CommandLine;
}
@@ -32,6 +30,7 @@ class CommandLine;
namespace content {
class BrowserContext;
class WebContents;
struct WebPreferences;
}
namespace gfx {
@@ -140,6 +139,7 @@ class NativeWindow : public brightray::DefaultWebContentsDelegate,
virtual void SetMenu(ui::MenuModel* menu);
virtual bool HasModalDialog();
virtual gfx::NativeWindow GetNativeWindow() = 0;
virtual void SetProgressBar(double progress) = 0;
virtual bool IsClosed() const { return is_closed_; }
virtual void OpenDevTools();
@@ -177,7 +177,7 @@ class NativeWindow : public brightray::DefaultWebContentsDelegate,
// Called when renderer process is going to be started.
void AppendExtraCommandLineSwitches(base::CommandLine* command_line,
int child_process_id);
void OverrideWebkitPrefs(const GURL& url, WebPreferences* prefs);
void OverrideWebkitPrefs(const GURL& url, content::WebPreferences* prefs);
// Public API used by platform-dependent delegates and observers to send UI
// related notifications.
@@ -298,7 +298,7 @@ class NativeWindow : public brightray::DefaultWebContentsDelegate,
base::CancelableClosure window_unresposive_closure_;
// Web preferences.
mate::ScopedPersistent<v8::Object> web_preferences_;
mate::PersistentDictionary web_preferences_;
// Page's default zoom factor.
double zoom_factor_;

View File

@@ -65,6 +65,7 @@ class NativeWindowMac : public NativeWindow {
virtual bool IsDocumentEdited() OVERRIDE;
virtual bool HasModalDialog() OVERRIDE;
virtual gfx::NativeWindow GetNativeWindow() OVERRIDE;
virtual void SetProgressBar(double progress) OVERRIDE;
// Returns true if |point| in local Cocoa coordinate system falls within
// the draggable region.

View File

@@ -193,6 +193,39 @@ static const CGFloat kAtomWindowCornerRadius = 4.0;
@end
@interface AtomProgressBar : NSProgressIndicator
@end
@implementation AtomProgressBar
- (void)drawRect:(NSRect)dirtyRect {
if (self.style != NSProgressIndicatorBarStyle)
return;
// Draw edges of rounded rect.
NSRect rect = NSInsetRect([self bounds], 1.0, 1.0);
CGFloat radius = rect.size.height / 2;
NSBezierPath* bezier_path = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:radius yRadius:radius];
[bezier_path setLineWidth:2.0];
[[NSColor grayColor] set];
[bezier_path stroke];
// Fill the rounded rect.
rect = NSInsetRect(rect, 2.0, 2.0);
radius = rect.size.height / 2;
bezier_path = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:radius yRadius:radius];
[bezier_path setLineWidth:1.0];
[bezier_path addClip];
// Calculate the progress width.
rect.size.width = floor(rect.size.width * ([self doubleValue] / [self maxValue]));
// Fill the progress bar with color blue.
[[NSColor colorWithSRGBRed:0.2 green:0.6 blue:1 alpha:1] set];
NSRectFill(rect);
}
@end
namespace atom {
NativeWindowMac::NativeWindowMac(content::WebContents* web_contents,
@@ -301,7 +334,7 @@ bool NativeWindowMac::IsFocused() {
}
void NativeWindowMac::Show() {
[window_ makeKeyAndOrderFront:nil];
[window_ orderFrontRegardless];
}
void NativeWindowMac::Hide() {
@@ -517,6 +550,42 @@ gfx::NativeWindow NativeWindowMac::GetNativeWindow() {
return window_;
}
void NativeWindowMac::SetProgressBar(double progress) {
NSDockTile* dock_tile = [NSApp dockTile];
// For the first time API invoked, we need to create a ContentView in DockTile.
if (dock_tile.contentView == NULL) {
NSImageView* image_view = [[NSImageView alloc] init];
[image_view setImage:[NSApp applicationIconImage]];
[dock_tile setContentView:image_view];
NSProgressIndicator* progress_indicator = [[AtomProgressBar alloc]
initWithFrame:NSMakeRect(0.0f, 0.0f, dock_tile.size.width, 15.0)];
[progress_indicator setStyle:NSProgressIndicatorBarStyle];
[progress_indicator setIndeterminate:NO];
[progress_indicator setBezeled:YES];
[progress_indicator setMinValue:0];
[progress_indicator setMaxValue:1];
[progress_indicator setHidden:NO];
[image_view addSubview:progress_indicator];
}
NSProgressIndicator* progress_indicator =
static_cast<NSProgressIndicator*>([[[dock_tile contentView] subviews]
objectAtIndex:0]);
if (progress < 0) {
[progress_indicator setHidden:YES];
} else if (progress > 1) {
[progress_indicator setHidden:NO];
[progress_indicator setIndeterminate:YES];
[progress_indicator setDoubleValue:1];
} else {
[progress_indicator setHidden:NO];
[progress_indicator setDoubleValue:progress];
}
[dock_tile display];
}
bool NativeWindowMac::IsWithinDraggableRegion(NSPoint point) const {
if (!draggable_region_)
return false;

View File

@@ -43,7 +43,9 @@
#elif defined(OS_WIN)
#include "atom/browser/ui/views/win_frame_view.h"
#include "base/win/scoped_comptr.h"
#include "base/win/windows_version.h"
#include "ui/base/win/shell.h"
#include "ui/views/win/hwnd_util.h"
#endif
namespace atom {
@@ -66,8 +68,12 @@ bool ShouldUseGlobalMenuBar() {
// Some DE would pretend to be Unity but don't have global application menu,
// so we can not trust unity::IsRunning().
scoped_ptr<base::Environment> env(base::Environment::Create());
return unity::IsRunning() && (base::nix::GetDesktopEnvironment(env.get()) ==
base::nix::DESKTOP_ENVIRONMENT_UNITY);
bool is_unity = unity::IsRunning() &&
base::nix::GetDesktopEnvironment(env.get()) ==
base::nix::DESKTOP_ENVIRONMENT_UNITY;
std::string menu_proxy;
return is_unity && env->GetVar("UBUNTU_MENUPROXY", &menu_proxy) &&
menu_proxy.length() > 1;
}
#endif
@@ -153,8 +159,8 @@ NativeWindowViews::NativeWindowViews(content::WebContents* web_contents,
"%s/%s/%d", "Atom Shell", Browser::Get()->GetName().c_str(),
++kWindowsCreated);
// Set WM_CLASS.
params.wm_class_name = "atom-shell";
params.wm_class_class = "Atom Shell";
params.wm_class_name = "atom";
params.wm_class_class = "Atom";
#endif
window_->Init(params);
@@ -226,7 +232,7 @@ bool NativeWindowViews::IsFocused() {
}
void NativeWindowViews::Show() {
window_->Show();
window_->ShowInactive();
}
void NativeWindowViews::Hide() {
@@ -479,6 +485,33 @@ gfx::NativeWindow NativeWindowViews::GetNativeWindow() {
return window_->GetNativeWindow();
}
void NativeWindowViews::SetProgressBar(double progress) {
#if defined(OS_WIN)
if (base::win::GetVersion() < base::win::VERSION_WIN7)
return;
base::win::ScopedComPtr<ITaskbarList3> taskbar;
if (FAILED(taskbar.CreateInstance(CLSID_TaskbarList, NULL,
CLSCTX_INPROC_SERVER) ||
FAILED(taskbar->HrInit()))) {
return;
}
HWND frame = views::HWNDForNativeWindow(GetNativeWindow());
if (progress > 1.0) {
taskbar->SetProgressState(frame, TBPF_INDETERMINATE);
} else if (progress < 0) {
taskbar->SetProgressState(frame, TBPF_NOPROGRESS);
} else if (progress >= 0) {
taskbar->SetProgressValue(frame,
static_cast<int>(progress * 100),
100);
}
#elif defined(USE_X11)
if (unity::IsRunning()) {
unity::SetProgressFraction(progress);
}
#endif
}
gfx::AcceleratedWidget NativeWindowViews::GetAcceleratedWidget() {
return GetNativeWindow()->GetHost()->GetAcceleratedWidget();
}
@@ -613,6 +646,10 @@ views::NonClientFrameView* NativeWindowViews::CreateNonClientFrameView(
return NULL;
}
gfx::ImageSkia NativeWindowViews::GetDevToolsWindowIcon() {
return GetWindowAppIcon();
}
void NativeWindowViews::HandleMouseDown() {
// Hide menu bar when web view is clicked.
if (menu_bar_autohide_ && menu_bar_visible_) {
@@ -655,7 +692,10 @@ void NativeWindowViews::HandleKeyboardEvent(
// When a single Alt is pressed:
menu_bar_alt_pressed_ = true;
} else if (event.type == blink::WebInputEvent::KeyUp && IsAltKey(event) &&
event.modifiers == 0 && menu_bar_alt_pressed_) {
#if defined(USE_X11)
event.modifiers == 0 &&
#endif
menu_bar_alt_pressed_) {
// When a single Alt is released right after a Alt is pressed:
menu_bar_alt_pressed_ = false;
SetMenuBarVisibility(!menu_bar_visible_);

View File

@@ -71,6 +71,7 @@ class NativeWindowViews : public NativeWindow,
virtual bool IsKiosk() OVERRIDE;
virtual void SetMenu(ui::MenuModel* menu_model) OVERRIDE;
virtual gfx::NativeWindow GetNativeWindow() OVERRIDE;
virtual void SetProgressBar(double value) OVERRIDE;
gfx::AcceleratedWidget GetAcceleratedWidget();
@@ -105,6 +106,9 @@ class NativeWindowViews : public NativeWindow,
virtual views::NonClientFrameView* CreateNonClientFrameView(
views::Widget* widget) OVERRIDE;
// brightray::InspectableWebContentsDelegate:
virtual gfx::ImageSkia GetDevToolsWindowIcon() OVERRIDE;
// content::WebContentsDelegate:
virtual void HandleMouseDown() OVERRIDE;
virtual void HandleKeyboardEvent(

View File

@@ -0,0 +1,91 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/net/asar/asar_protocol_handler.h"
#include "atom/browser/net/asar/url_request_asar_job.h"
#include "atom/common/asar/archive.h"
#include "net/base/filename_util.h"
#include "net/base/net_errors.h"
#include "net/url_request/url_request_error_job.h"
#include "net/url_request/url_request_file_job.h"
namespace asar {
namespace {
const base::FilePath::CharType kAsarExtension[] = FILE_PATH_LITERAL(".asar");
// Get the relative path in asar archive.
bool GetAsarPath(const base::FilePath& full_path,
base::FilePath* asar_path,
base::FilePath* relative_path) {
base::FilePath iter = full_path;
while (true) {
base::FilePath dirname = iter.DirName();
if (iter.MatchesExtension(kAsarExtension))
break;
else if (iter == dirname)
return false;
iter = dirname;
}
base::FilePath tail;
if (!iter.AppendRelativePath(full_path, &tail))
return false;
*asar_path = iter;
*relative_path = tail;
return true;
}
} // namespace
AsarProtocolHandler::AsarProtocolHandler(
const scoped_refptr<base::TaskRunner>& file_task_runner)
: file_task_runner_(file_task_runner) {}
AsarProtocolHandler::~AsarProtocolHandler() {
}
Archive* AsarProtocolHandler::GetOrCreateAsarArchive(
const base::FilePath& path) const {
if (!archives_.contains(path)) {
scoped_ptr<Archive> archive(new Archive(path));
if (!archive->Init())
return nullptr;
archives_.set(path, archive.Pass());
}
return archives_.get(path);
}
net::URLRequestJob* AsarProtocolHandler::MaybeCreateJob(
net::URLRequest* request,
net::NetworkDelegate* network_delegate) const {
base::FilePath full_path;
net::FileURLToFilePath(request->url(), &full_path);
// Create asar:// job when the path contains "xxx.asar/", otherwise treat the
// URL request as file://.
base::FilePath asar_path, relative_path;
if (!GetAsarPath(full_path, &asar_path, &relative_path))
return new net::URLRequestFileJob(request, network_delegate, full_path,
file_task_runner_);
Archive* archive = GetOrCreateAsarArchive(asar_path);
if (!archive)
return new net::URLRequestErrorJob(request, network_delegate,
net::ERR_FILE_NOT_FOUND);
return new URLRequestAsarJob(request, network_delegate, archive,
relative_path, file_task_runner_);
}
bool AsarProtocolHandler::IsSafeRedirectTarget(const GURL& location) const {
return false;
}
} // namespace asar

View File

@@ -0,0 +1,45 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_NET_ASAR_ASAR_PROTOCOL_HANDLER_H_
#define ATOM_BROWSER_NET_ASAR_ASAR_PROTOCOL_HANDLER_H_
#include "base/containers/scoped_ptr_hash_map.h"
#include "base/files/file_path.h"
#include "base/memory/ref_counted.h"
#include "net/url_request/url_request_job_factory.h"
namespace base {
class TaskRunner;
}
namespace asar {
class Archive;
class AsarProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler {
public:
explicit AsarProtocolHandler(
const scoped_refptr<base::TaskRunner>& file_task_runner);
virtual ~AsarProtocolHandler();
Archive* GetOrCreateAsarArchive(const base::FilePath& path) const;
// net::URLRequestJobFactory::ProtocolHandler:
virtual net::URLRequestJob* MaybeCreateJob(
net::URLRequest* request,
net::NetworkDelegate* network_delegate) const OVERRIDE;
virtual bool IsSafeRedirectTarget(const GURL& location) const OVERRIDE;
private:
const scoped_refptr<base::TaskRunner> file_task_runner_;
mutable base::ScopedPtrHashMap<base::FilePath, Archive> archives_;
DISALLOW_COPY_AND_ASSIGN(AsarProtocolHandler);
};
} // namespace asar
#endif // ATOM_BROWSER_NET_ASAR_ASAR_PROTOCOL_HANDLER_H_

View File

@@ -0,0 +1,142 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/net/asar/url_request_asar_job.h"
#include <string>
#include "net/base/file_stream.h"
#include "net/base/io_buffer.h"
#include "net/base/mime_util.h"
#include "net/base/net_errors.h"
#include "net/url_request/url_request_status.h"
namespace asar {
URLRequestAsarJob::URLRequestAsarJob(
net::URLRequest* request,
net::NetworkDelegate* network_delegate,
Archive* archive,
const base::FilePath& file_path,
const scoped_refptr<base::TaskRunner>& file_task_runner)
: net::URLRequestJob(request, network_delegate),
archive_(archive),
file_path_(file_path),
stream_(new net::FileStream(file_task_runner)),
remaining_bytes_(0),
file_task_runner_(file_task_runner),
weak_ptr_factory_(this) {}
URLRequestAsarJob::~URLRequestAsarJob() {}
void URLRequestAsarJob::Start() {
if (!archive_ || !archive_->GetFileInfo(file_path_, &file_info_)) {
NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
net::ERR_FILE_NOT_FOUND));
return;
}
remaining_bytes_ = static_cast<int64>(file_info_.size);
int flags = base::File::FLAG_OPEN |
base::File::FLAG_READ |
base::File::FLAG_ASYNC;
int rv = stream_->Open(archive_->path(), flags,
base::Bind(&URLRequestAsarJob::DidOpen,
weak_ptr_factory_.GetWeakPtr()));
if (rv != net::ERR_IO_PENDING)
DidOpen(rv);
}
void URLRequestAsarJob::Kill() {
weak_ptr_factory_.InvalidateWeakPtrs();
URLRequestJob::Kill();
}
bool URLRequestAsarJob::ReadRawData(net::IOBuffer* dest,
int dest_size,
int* bytes_read) {
if (remaining_bytes_ < dest_size)
dest_size = static_cast<int>(remaining_bytes_);
// If we should copy zero bytes because |remaining_bytes_| is zero, short
// circuit here.
if (!dest_size) {
*bytes_read = 0;
return true;
}
int rv = stream_->Read(dest,
dest_size,
base::Bind(&URLRequestAsarJob::DidRead,
weak_ptr_factory_.GetWeakPtr(),
make_scoped_refptr(dest)));
if (rv >= 0) {
// Data is immediately available.
*bytes_read = rv;
remaining_bytes_ -= rv;
DCHECK_GE(remaining_bytes_, 0);
return true;
}
// Otherwise, a read error occured. We may just need to wait...
if (rv == net::ERR_IO_PENDING) {
SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
} else {
NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, rv));
}
return false;
}
bool URLRequestAsarJob::GetMimeType(std::string* mime_type) const {
return net::GetMimeTypeFromFile(file_path_, mime_type);
}
void URLRequestAsarJob::DidOpen(int result) {
if (result != net::OK) {
NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result));
return;
}
int rv = stream_->Seek(base::File::FROM_BEGIN,
file_info_.offset,
base::Bind(&URLRequestAsarJob::DidSeek,
weak_ptr_factory_.GetWeakPtr()));
if (rv != net::ERR_IO_PENDING) {
// stream_->Seek() failed, so pass an intentionally erroneous value
// into DidSeek().
DidSeek(-1);
}
}
void URLRequestAsarJob::DidSeek(int64 result) {
if (result != static_cast<int64>(file_info_.offset)) {
NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
return;
}
set_expected_content_size(remaining_bytes_);
NotifyHeadersComplete();
}
void URLRequestAsarJob::DidRead(scoped_refptr<net::IOBuffer> buf, int result) {
if (result > 0) {
SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status
remaining_bytes_ -= result;
DCHECK_GE(remaining_bytes_, 0);
}
buf = NULL;
if (result == 0) {
NotifyDone(net::URLRequestStatus());
} else if (result < 0) {
NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result));
}
NotifyReadComplete(result);
}
} // namespace asar

View File

@@ -0,0 +1,72 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_NET_ASAR_URL_REQUEST_ASAR_JOB_H_
#define ATOM_BROWSER_NET_ASAR_URL_REQUEST_ASAR_JOB_H_
#include <string>
#include "atom/common/asar/archive.h"
#include "base/files/file_path.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "net/url_request/url_request_job.h"
namespace base {
class TaskRunner;
}
namespace net {
class FileStream;
}
namespace asar {
class URLRequestAsarJob : public net::URLRequestJob {
public:
URLRequestAsarJob(net::URLRequest* request,
net::NetworkDelegate* network_delegate,
Archive* archive,
const base::FilePath& file_path,
const scoped_refptr<base::TaskRunner>& file_task_runner);
// net::URLRequestJob:
virtual void Start() OVERRIDE;
virtual void Kill() OVERRIDE;
virtual bool ReadRawData(net::IOBuffer* buf,
int buf_size,
int* bytes_read) OVERRIDE;
virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
protected:
virtual ~URLRequestAsarJob();
private:
// Callback after opening file on a background thread.
void DidOpen(int result);
// Callback after seeking to the beginning of |byte_range_| in the file
// on a background thread.
void DidSeek(int64 result);
// Callback after data is asynchronously read from the file into |buf|.
void DidRead(scoped_refptr<net::IOBuffer> buf, int result);
Archive* archive_;
Archive::FileInfo file_info_;
base::FilePath file_path_;
scoped_ptr<net::FileStream> stream_;
int64 remaining_bytes_;
const scoped_refptr<base::TaskRunner> file_task_runner_;
base::WeakPtrFactory<URLRequestAsarJob> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(URLRequestAsarJob);
};
} // namespace asar
#endif // ATOM_BROWSER_NET_ASAR_URL_REQUEST_ASAR_JOB_H_

View File

@@ -17,7 +17,7 @@
<key>CFBundleIconFile</key>
<string>atom.icns</string>
<key>CFBundleVersion</key>
<string>0.16.0</string>
<string>0.18.1</string>
<key>LSMinimumSystemVersion</key>
<string>10.8.0</string>
<key>NSMainNibFile</key>

View File

@@ -50,8 +50,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,16,0,0
PRODUCTVERSION 0,16,0,0
FILEVERSION 0,18,1,0
PRODUCTVERSION 0,18,1,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -68,12 +68,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "GitHub, Inc."
VALUE "FileDescription", "Atom-Shell"
VALUE "FileVersion", "0.16.0"
VALUE "FileVersion", "0.18.1"
VALUE "InternalName", "atom.exe"
VALUE "LegalCopyright", "Copyright (C) 2013 GitHub, Inc. All rights reserved."
VALUE "OriginalFilename", "atom.exe"
VALUE "ProductName", "Atom-Shell"
VALUE "ProductVersion", "0.16.0"
VALUE "ProductVersion", "0.18.1"
VALUE "SquirrelAwareVersion", "1"
END
END

View File

@@ -91,7 +91,7 @@ bool StringToAccelerator(const std::string& description,
LOG(ERROR) << "The accelerator string can only contain ASCII characters";
return false;
}
std::string shortcut(StringToLowerASCII(description));
std::string shortcut(base::StringToLowerASCII(description));
std::vector<std::string> tokens;
base::SplitString(shortcut, '+', &tokens);

View File

@@ -21,6 +21,7 @@
#include "chrome/browser/ui/libgtk2ui/gtk2_signal.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
namespace file_dialog {
@@ -114,6 +115,11 @@ class FileChooserDialog {
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
// make sure window gets correctly raised and gets focus.
int time = views::X11DesktopHandler::get()->wm_user_time_ms();
gtk_window_present_with_time(GTK_WINDOW(dialog_), time);
}
void RunSaveAsynchronous(const SaveDialogCallback& callback) {

View File

@@ -75,6 +75,9 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window,
for (size_t i = 0; i < buttons.size(); ++i) {
NSString* title = base::SysUTF8ToNSString(buttons[i]);
// An empty title causes crash on OS X.
if (buttons[i].empty())
title = @"(empty)";
NSButton* button = [alert addButtonWithTitle:title];
[button setTag:i];
}

View File

@@ -145,7 +145,7 @@ MessageDialog::MessageDialog(NativeWindow* parent_window,
views::LabelButton* button = new views::LabelButton(
this, base::UTF8ToUTF16(buttons[i]));
button->set_tag(i);
button->set_min_size(gfx::Size(60, 30));
button->SetMinSize(gfx::Size(60, 30));
button->SetStyle(views::Button::STYLE_BUTTON);
button->SetGroup(kButtonGroup);

View File

@@ -12,8 +12,18 @@ TrayIcon::TrayIcon() {
TrayIcon::~TrayIcon() {
}
void TrayIcon::SetTitle(const std::string& title) {
}
void TrayIcon::SetHighlightMode(bool highlight) {
}
void TrayIcon::NotifyClicked() {
FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnClicked());
}
void TrayIcon::NotifyDoubleClicked() {
FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnDoubleClicked());
}
} // namespace atom

View File

@@ -31,12 +31,21 @@ class TrayIcon {
// status icon (e.g. Ubuntu Unity).
virtual void SetToolTip(const std::string& tool_tip) = 0;
// Sets the title displayed aside of the status icon in the status bar. This
// only works on OS X.
virtual void SetTitle(const std::string& title);
// Sets whether the status icon is highlighted when it is clicked. This only
// works on OS X.
virtual void SetHighlightMode(bool highlight);
// Set the context menu for this icon.
virtual void SetContextMenu(ui::SimpleMenuModel* menu_model) = 0;
void AddObserver(TrayIconObserver* obs) { observers_.AddObserver(obs); }
void RemoveObserver(TrayIconObserver* obs) { observers_.RemoveObserver(obs); }
void NotifyClicked();
void NotifyDoubleClicked();
protected:
TrayIcon();

View File

@@ -25,6 +25,8 @@ class TrayIconCocoa : public TrayIcon {
virtual void SetImage(const gfx::ImageSkia& image) OVERRIDE;
virtual void SetPressedImage(const gfx::ImageSkia& image) OVERRIDE;
virtual void SetToolTip(const std::string& tool_tip) OVERRIDE;
virtual void SetTitle(const std::string& title) OVERRIDE;
virtual void SetHighlightMode(bool highlight) OVERRIDE;
virtual void SetContextMenu(ui::SimpleMenuModel* menu_model) OVERRIDE;
private:

View File

@@ -13,6 +13,7 @@
}
- (id)initWithIcon:(atom::TrayIconCocoa*)icon;
- (void)handleClick:(id)sender;
- (void)handleDoubleClick:(id)sender;
@end // @interface StatusItemController
@@ -24,10 +25,13 @@
}
- (void)handleClick:(id)sender {
DCHECK(trayIcon_);
trayIcon_->NotifyClicked();
}
- (void)handleDoubleClick:(id)sender {
trayIcon_->NotifyDoubleClicked();
}
@end
namespace atom {
@@ -40,6 +44,7 @@ TrayIconCocoa::TrayIconCocoa() {
[item_ setEnabled:YES];
[item_ setTarget:controller_];
[item_ setAction:@selector(handleClick:)];
[item_ setDoubleAction:@selector(handleDoubleClick:)];
[item_ setHighlightMode:YES];
}
@@ -68,6 +73,14 @@ void TrayIconCocoa::SetToolTip(const std::string& tool_tip) {
[item_ setToolTip:base::SysUTF8ToNSString(tool_tip)];
}
void TrayIconCocoa::SetTitle(const std::string& title) {
[item_ setTitle:base::SysUTF8ToNSString(title)];
}
void TrayIconCocoa::SetHighlightMode(bool highlight) {
[item_ setHighlightMode:highlight];
}
void TrayIconCocoa::SetContextMenu(ui::SimpleMenuModel* menu_model) {
menu_.reset([[AtomMenuController alloc] initWithModel:menu_model]);
[item_ setMenu:[menu_ menu]];

View File

@@ -10,6 +10,7 @@ namespace atom {
class TrayIconObserver {
public:
virtual void OnClicked() {}
virtual void OnDoubleClicked() {}
protected:
virtual ~TrayIconObserver() {}

View File

@@ -35,14 +35,15 @@ void MenuDelegate::RunMenu(ui::MenuModel* model, views::MenuButton* button) {
id_ = button->tag();
views::MenuItemView* item = BuildMenu(model);
views::MenuRunner menu_runner(item);
views::MenuRunner menu_runner(
item,
views::MenuRunner::CONTEXT_MENU | views::MenuRunner::HAS_MNEMONICS);
ignore_result(menu_runner.RunMenuAt(
button->GetWidget()->GetTopLevelWidget(),
button,
bounds,
views::MENU_ANCHOR_TOPRIGHT,
ui::MENU_SOURCE_MOUSE,
views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU));
ui::MENU_SOURCE_MOUSE));
}
views::MenuItemView* MenuDelegate::BuildMenu(ui::MenuModel* model) {

View File

@@ -60,14 +60,15 @@ void NotifyIcon::HandleClickEvent(const gfx::Point& cursor_pos,
if (!SetForegroundWindow(window_))
return;
views::MenuRunner menu_runner(menu_model_);
views::MenuRunner menu_runner(
menu_model_,
views::MenuRunner::CONTEXT_MENU | views::MenuRunner::HAS_MNEMONICS);
ignore_result(menu_runner.RunMenuAt(
NULL,
NULL,
gfx::Rect(cursor_pos, gfx::Size()),
views::MENU_ANCHOR_TOPLEFT,
ui::MENU_SOURCE_MOUSE,
views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU));
ui::MENU_SOURCE_MOUSE));
}
void NotifyIcon::ResetIcon() {

View File

@@ -9,6 +9,7 @@
#include "base/values.h"
#include "content/public/common/common_param_traits.h"
#include "ipc/ipc_message_macros.h"
#include "ui/gfx/ipc/gfx_param_traits.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.

View File

@@ -0,0 +1,117 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include <vector>
#include "atom/common/asar/archive.h"
#include "atom/common/native_mate_converters/file_path_converter.h"
#include "native_mate/arguments.h"
#include "native_mate/dictionary.h"
#include "native_mate/object_template_builder.h"
#include "native_mate/wrappable.h"
#include "atom/common/node_includes.h"
namespace {
class Archive : public mate::Wrappable {
public:
static v8::Handle<v8::Value> Create(v8::Isolate* isolate,
const base::FilePath& path) {
scoped_ptr<asar::Archive> archive(new asar::Archive(path));
if (!archive->Init())
return v8::False(isolate);
return (new Archive(archive.Pass()))->GetWrapper(isolate);
}
protected:
explicit Archive(scoped_ptr<asar::Archive> archive)
: archive_(archive.Pass()) {}
// Reads the offset and size of file.
v8::Handle<v8::Value> GetFileInfo(v8::Isolate* isolate,
const base::FilePath& path) {
asar::Archive::FileInfo info;
if (!archive_ || !archive_->GetFileInfo(path, &info))
return v8::False(isolate);
mate::Dictionary dict(isolate, v8::Object::New(isolate));
dict.Set("size", info.size);
dict.Set("offset", info.offset);
return dict.GetHandle();
}
// Returns a fake result of fs.stat(path).
v8::Handle<v8::Value> Stat(v8::Isolate* isolate,
const base::FilePath& path) {
asar::Archive::Stats stats;
if (!archive_ || !archive_->Stat(path, &stats))
return v8::False(isolate);
mate::Dictionary dict(isolate, v8::Object::New(isolate));
dict.Set("size", stats.size);
dict.Set("offset", stats.offset);
dict.Set("isFile", stats.is_file);
dict.Set("isDirectory", stats.is_directory);
dict.Set("isLink", stats.is_link);
return dict.GetHandle();
}
// Returns all files under a directory.
v8::Handle<v8::Value> Readdir(v8::Isolate* isolate,
const base::FilePath& path) {
std::vector<base::FilePath> files;
if (!archive_ || !archive_->Readdir(path, &files))
return v8::False(isolate);
return mate::ConvertToV8(isolate, files);
}
// Returns the path of file with symbol link resolved.
v8::Handle<v8::Value> Realpath(v8::Isolate* isolate,
const base::FilePath& path) {
base::FilePath realpath;
if (!archive_ || !archive_->Realpath(path, &realpath))
return v8::False(isolate);
return mate::ConvertToV8(isolate, realpath);
}
// Copy the file out into a temporary file and returns the new path.
v8::Handle<v8::Value> CopyFileOut(v8::Isolate* isolate,
const base::FilePath& path) {
base::FilePath new_path;
if (!archive_ || !archive_->CopyFileOut(path, &new_path))
return v8::False(isolate);
return mate::ConvertToV8(isolate, new_path);
}
// Free the resources used by archive.
void Destroy() {
archive_.reset();
}
// mate::Wrappable:
mate::ObjectTemplateBuilder GetObjectTemplateBuilder(v8::Isolate* isolate) {
return mate::ObjectTemplateBuilder(isolate)
.SetValue("path", archive_->path())
.SetMethod("getFileInfo", &Archive::GetFileInfo)
.SetMethod("stat", &Archive::Stat)
.SetMethod("readdir", &Archive::Readdir)
.SetMethod("realpath", &Archive::Realpath)
.SetMethod("copyFileOut", &Archive::CopyFileOut)
.SetMethod("destroy", &Archive::Destroy);
}
private:
scoped_ptr<asar::Archive> archive_;
DISALLOW_COPY_AND_ASSIGN(Archive);
};
void Initialize(v8::Handle<v8::Object> exports, v8::Handle<v8::Value> unused,
v8::Handle<v8::Context> context, void* priv) {
mate::Dictionary dict(context->GetIsolate(), exports);
dict.SetMethod("createArchive", &Archive::Create);
}
} // namespace
NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_common_asar, Initialize)

View File

@@ -8,6 +8,7 @@
#include <string>
#include "atom/common/atom_version.h"
#include "atom/common/chrome_version.h"
#include "atom/common/native_mate_converters/string16_converter.h"
#include "base/logging.h"
#include "native_mate/callback.h"
@@ -78,9 +79,12 @@ void AtomBindings::BindTo(v8::Isolate* isolate,
base::Bind(&AtomBindings::ActivateUVLoop, base::Unretained(this)));
v8::Handle<v8::Object> versions;
if (dict.Get("versions", &versions))
if (dict.Get("versions", &versions)) {
versions->Set(mate::StringToV8(isolate, "atom-shell"),
mate::StringToV8(isolate, ATOM_VERSION_STRING));
versions->Set(mate::StringToV8(isolate, "chrome"),
mate::StringToV8(isolate, CHROME_VERSION_STRING));
}
}
void AtomBindings::ActivateUVLoop(v8::Isolate* isolate) {

252
atom/common/asar/archive.cc Normal file
View File

@@ -0,0 +1,252 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/common/asar/archive.h"
#include <string>
#include <vector>
#include "atom/common/asar/scoped_temporary_file.h"
#include "base/files/file.h"
#include "base/logging.h"
#include "base/pickle.h"
#include "base/json/json_string_value_serializer.h"
#include "base/strings/string_number_conversions.h"
namespace asar {
namespace {
#if defined(OS_WIN)
const char kSeparators[] = "\\/";
#else
const char kSeparators[] = "/";
#endif
bool GetNodeFromPath(std::string path,
const base::DictionaryValue* root,
const base::DictionaryValue** out);
// Gets the "files" from "dir".
bool GetFilesNode(const base::DictionaryValue* root,
const base::DictionaryValue* dir,
const base::DictionaryValue** out) {
// Test for symbol linked directory.
std::string link;
if (dir->GetStringWithoutPathExpansion("link", &link)) {
const base::DictionaryValue* linked_node = NULL;
if (!GetNodeFromPath(link, root, &linked_node))
return false;
dir = linked_node;
}
return dir->GetDictionaryWithoutPathExpansion("files", out);
}
// Gets sub-file "name" from "dir".
bool GetChildNode(const base::DictionaryValue* root,
const std::string& name,
const base::DictionaryValue* dir,
const base::DictionaryValue** out) {
const base::DictionaryValue* files = NULL;
return GetFilesNode(root, dir, &files) &&
files->GetDictionaryWithoutPathExpansion(name, out);
}
// Gets the node of "path" from "root".
bool GetNodeFromPath(std::string path,
const base::DictionaryValue* root,
const base::DictionaryValue** out) {
if (path == "") {
*out = root;
return true;
}
const base::DictionaryValue* dir = root;
for (size_t delimiter_position = path.find_first_of(kSeparators);
delimiter_position != std::string::npos;
delimiter_position = path.find_first_of(kSeparators)) {
const base::DictionaryValue* child = NULL;
if (!GetChildNode(root, path.substr(0, delimiter_position), dir, &child))
return false;
dir = child;
path.erase(0, delimiter_position + 1);
}
return GetChildNode(root, path, dir, out);
}
bool FillFileInfoWithNode(Archive::FileInfo* info,
uint32 header_size,
const base::DictionaryValue* node) {
std::string offset;
if (!node->GetString("offset", &offset))
return false;
if (!base::StringToUint64(offset, &info->offset))
return false;
int size;
if (!node->GetInteger("size", &size))
return false;
info->offset += header_size;
info->size = static_cast<uint32>(size);
return true;
}
} // namespace
Archive::Archive(const base::FilePath& path)
: path_(path),
header_size_(0) {
}
Archive::~Archive() {
}
bool Archive::Init() {
base::File file(path_, base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!file.IsValid())
return false;
std::vector<char> buf;
int len;
buf.resize(8);
len = file.ReadAtCurrentPos(buf.data(), buf.size());
if (len != static_cast<int>(buf.size())) {
PLOG(ERROR) << "Failed to read header size from " << path_.value();
return false;
}
uint32 size;
if (!PickleIterator(Pickle(buf.data(), buf.size())).ReadUInt32(&size)) {
LOG(ERROR) << "Failed to parse header size from " << path_.value();
return false;
}
buf.resize(size);
len = file.ReadAtCurrentPos(buf.data(), buf.size());
if (len != static_cast<int>(buf.size())) {
PLOG(ERROR) << "Failed to read header from " << path_.value();
return false;
}
std::string header;
if (!PickleIterator(Pickle(buf.data(), buf.size())).ReadString(&header)) {
LOG(ERROR) << "Failed to parse header from " << path_.value();
return false;
}
std::string error;
JSONStringValueSerializer serializer(&header);
base::Value* value = serializer.Deserialize(NULL, &error);
if (!value || !value->IsType(base::Value::TYPE_DICTIONARY)) {
LOG(ERROR) << "Failed to parse header: " << error;
return false;
}
header_size_ = 8 + size;
header_.reset(static_cast<base::DictionaryValue*>(value));
return true;
}
bool Archive::GetFileInfo(const base::FilePath& path, FileInfo* info) {
if (!header_)
return false;
const base::DictionaryValue* node;
if (!GetNodeFromPath(path.AsUTF8Unsafe(), header_.get(), &node))
return false;
std::string link;
if (node->GetString("link", &link))
return GetFileInfo(base::FilePath::FromUTF8Unsafe(link), info);
return FillFileInfoWithNode(info, header_size_, node);
}
bool Archive::Stat(const base::FilePath& path, Stats* stats) {
if (!header_)
return false;
const base::DictionaryValue* node;
if (!GetNodeFromPath(path.AsUTF8Unsafe(), header_.get(), &node))
return false;
if (node->HasKey("link")) {
stats->is_file = false;
stats->is_link = true;
return true;
}
if (node->HasKey("files")) {
stats->is_file = false;
stats->is_directory = true;
return true;
}
return FillFileInfoWithNode(stats, header_size_, node);
}
bool Archive::Readdir(const base::FilePath& path,
std::vector<base::FilePath>* list) {
if (!header_)
return false;
const base::DictionaryValue* node;
if (!GetNodeFromPath(path.AsUTF8Unsafe(), header_.get(), &node))
return false;
const base::DictionaryValue* files;
if (!GetFilesNode(header_.get(), node, &files))
return false;
base::DictionaryValue::Iterator iter(*files);
while (!iter.IsAtEnd()) {
list->push_back(base::FilePath::FromUTF8Unsafe(iter.key()));
iter.Advance();
}
return true;
}
bool Archive::Realpath(const base::FilePath& path, base::FilePath* realpath) {
if (!header_)
return false;
const base::DictionaryValue* node;
if (!GetNodeFromPath(path.AsUTF8Unsafe(), header_.get(), &node))
return false;
std::string link;
if (node->GetString("link", &link)) {
*realpath = base::FilePath::FromUTF8Unsafe(link);
return true;
}
*realpath = path;
return true;
}
bool Archive::CopyFileOut(const base::FilePath& path, base::FilePath* out) {
if (external_files_.contains(path)) {
*out = external_files_.get(path)->path();
return true;
}
FileInfo info;
if (!GetFileInfo(path, &info))
return false;
scoped_ptr<ScopedTemporaryFile> temp_file(new ScopedTemporaryFile);
if (!temp_file->InitFromFile(path_, info.offset, info.size))
return false;
*out = temp_file->path();
external_files_.set(path, temp_file.Pass());
return true;
}
} // namespace asar

View File

@@ -0,0 +1,76 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_COMMON_ASAR_ARCHIVE_H_
#define ATOM_COMMON_ASAR_ARCHIVE_H_
#include <vector>
#include "base/containers/scoped_ptr_hash_map.h"
#include "base/files/file_path.h"
#include "base/memory/scoped_ptr.h"
namespace base {
class DictionaryValue;
}
namespace asar {
class ScopedTemporaryFile;
// This class represents an asar package, and provides methods to read
// information from it.
class Archive {
public:
struct FileInfo {
FileInfo() : size(0), offset(0) {}
uint32 size;
uint64 offset;
};
struct Stats : public FileInfo {
Stats() : is_file(true), is_directory(false), is_link(false) {}
bool is_file;
bool is_directory;
bool is_link;
};
explicit Archive(const base::FilePath& path);
virtual ~Archive();
// Read and parse the header.
bool Init();
// Get the info of a file.
bool GetFileInfo(const base::FilePath& path, FileInfo* info);
// Fs.stat(path).
bool Stat(const base::FilePath& path, Stats* stats);
// Fs.readdir(path).
bool Readdir(const base::FilePath& path, std::vector<base::FilePath>* files);
// Fs.realpath(path).
bool Realpath(const base::FilePath& path, base::FilePath* realpath);
// Copy the file into a temporary file, and return the new path.
bool CopyFileOut(const base::FilePath& path, base::FilePath* out);
base::FilePath path() const { return path_; }
base::DictionaryValue* header() const { return header_.get(); }
private:
base::FilePath path_;
uint32 header_size_;
scoped_ptr<base::DictionaryValue> header_;
// Cached external temporary files.
base::ScopedPtrHashMap<base::FilePath, ScopedTemporaryFile> external_files_;
DISALLOW_COPY_AND_ASSIGN(Archive);
};
} // namespace asar
#endif // ATOM_COMMON_ASAR_ARCHIVE_H_

View File

@@ -0,0 +1,54 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/common/asar/scoped_temporary_file.h"
#include <vector>
#include "base/file_util.h"
#include "base/threading/thread_restrictions.h"
namespace asar {
ScopedTemporaryFile::ScopedTemporaryFile() {
}
ScopedTemporaryFile::~ScopedTemporaryFile() {
if (!path_.empty()) {
base::ThreadRestrictions::ScopedAllowIO allow_io;
base::DeleteFile(path_, false);
}
}
bool ScopedTemporaryFile::Init() {
if (!path_.empty())
return true;
base::ThreadRestrictions::ScopedAllowIO allow_io;
return base::CreateTemporaryFile(&path_);
}
bool ScopedTemporaryFile::InitFromFile(const base::FilePath& path,
uint64 offset, uint64 size) {
if (!Init())
return false;
base::File src(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!src.IsValid())
return false;
std::vector<char> buf(size);
int len = src.Read(offset, buf.data(), buf.size());
if (len != static_cast<int>(size))
return false;
base::File dest(path_, base::File::FLAG_OPEN | base::File::FLAG_WRITE);
if (!dest.IsValid())
return false;
return dest.WriteAtCurrentPos(buf.data(), buf.size()) ==
static_cast<int>(size);
}
} // namespace asar

View File

@@ -0,0 +1,37 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_COMMON_ASAR_SCOPED_TEMPORARY_FILE_H_
#define ATOM_COMMON_ASAR_SCOPED_TEMPORARY_FILE_H_
#include "base/files/file_path.h"
namespace asar {
// An object representing a temporary file that should be cleaned up when this
// object goes out of scope. Note that since deletion occurs during the
// destructor, no further error handling is possible if the directory fails to
// be deleted. As a result, deletion is not guaranteed by this class.
class ScopedTemporaryFile {
public:
ScopedTemporaryFile();
virtual ~ScopedTemporaryFile();
// Init an empty temporary file.
bool Init();
// Init an temporary file and fill it with content of |path|.
bool InitFromFile(const base::FilePath& path, uint64 offset, uint64 size);
base::FilePath path() const { return path_; }
private:
base::FilePath path_;
DISALLOW_COPY_AND_ASSIGN(ScopedTemporaryFile);
};
} // namespace asar
#endif // ATOM_COMMON_ASAR_SCOPED_TEMPORARY_FILE_H_

View File

@@ -6,8 +6,8 @@
#define ATOM_VERSION_H
#define ATOM_MAJOR_VERSION 0
#define ATOM_MINOR_VERSION 16
#define ATOM_PATCH_VERSION 0
#define ATOM_MINOR_VERSION 18
#define ATOM_PATCH_VERSION 1
#define ATOM_VERSION_IS_RELEASE 1

View File

@@ -8,7 +8,7 @@
#ifndef ATOM_COMMON_CHROME_VERSION_H_
#define ATOM_COMMON_CHROME_VERSION_H_
#define CHROME_VERSION_STRING "37.0.2062.102"
#define CHROME_VERSION_STRING "38.0.2125.101"
#define CHROME_VERSION "v" CHROME_VERSION_STRING
#endif // ATOM_COMMON_CHROME_VERSION_H_

View File

@@ -6,3 +6,4 @@
#include "atom/common/api/api_messages.h"
#include "chrome/common/print_messages.h"
#include "chrome/common/tts_messages.h"

View File

@@ -0,0 +1,12 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_COMMON_GOOGLE_API_KEY_H_
#define ATOM_COMMON_GOOGLE_API_KEY_H_
#ifndef GOOGLEAPIS_API_KEY
#define GOOGLEAPIS_API_KEY "AIzaSyAQfxPJiounkhOjODEO5ZieffeBv6yft2Q"
#endif
#endif // ATOM_COMMON_GOOGLE_API_KEY_H_

301
atom/common/lib/asar.coffee Normal file
View File

@@ -0,0 +1,301 @@
asar = process.atomBinding 'asar'
child_process = require 'child_process'
fs = require 'fs'
path = require 'path'
util = require 'util'
# Cache asar archive objects.
cachedArchives = {}
getOrCreateArchive = (p) ->
archive = cachedArchives[p]
return archive if archive?
archive = asar.createArchive p
return false unless archive
cachedArchives[p] = archive
# Clean cache on quit.
process.on 'exit', ->
archive.destroy() for p, archive of cachedArchives
# Separate asar package's path from full path.
splitPath = (p) ->
return [false] if typeof p isnt 'string'
return [true, p, ''] if p.substr(-5) is '.asar'
index = p.lastIndexOf ".asar#{path.sep}"
return [false] if index is -1
[true, p.substr(0, index + 5), p.substr(index + 6)]
# Convert asar archive's Stats object to fs's Stats object.
nextInode = 0
uid = if process.getuid? then process.getuid() else 0
gid = if process.getgid? then process.getgid() else 0
fakeTime = new Date()
asarStatsToFsStats = (stats) ->
{
dev: 1,
ino: ++nextInode,
mode: 33188,
nlink: 1,
uid: uid,
gid: gid,
rdev: 0,
atime: stats.atime || fakeTime,
birthtime: stats.birthtime || fakeTime,
mtime: stats.mtime || fakeTime,
ctime: stats.ctime || fakeTime,
size: stats.size,
isFile: -> stats.isFile
isDirectory: -> stats.isDirectory
isSymbolicLink: -> stats.isLink
isBlockDevice: -> false
isCharacterDevice: -> false
isFIFO: -> false
isSocket: -> false
}
# Create a ENOENT error.
createNotFoundError = (asarPath, filePath) ->
error = new Error("ENOENT, #{filePath} not found in #{asarPath}")
error.code = "ENOENT"
error.errno = -2
error
# Override fs APIs.
lstatSync = fs.lstatSync
fs.lstatSync = (p) ->
[isAsar, asarPath, filePath] = splitPath p
return lstatSync p unless isAsar
archive = getOrCreateArchive asarPath
throw new Error("Invalid package #{asarPath}") unless archive
stats = archive.stat filePath
throw createNotFoundError(asarPath, filePath) unless stats
asarStatsToFsStats stats
lstat = fs.lstat
fs.lstat = (p, callback) ->
[isAsar, asarPath, filePath] = splitPath p
return lstat p, callback unless isAsar
archive = getOrCreateArchive asarPath
return callback new Error("Invalid package #{asarPath}") unless archive
stats = getOrCreateArchive(asarPath).stat filePath
return callback createNotFoundError(asarPath, filePath) unless stats
process.nextTick -> callback null, asarStatsToFsStats stats
statSync = fs.statSync
fs.statSync = (p) ->
[isAsar, asarPath, filePath] = splitPath p
return statSync p unless isAsar
# Do not distinguish links for now.
fs.lstatSync p
stat = fs.stat
fs.stat = (p, callback) ->
[isAsar, asarPath, filePath] = splitPath p
return stat p, callback unless isAsar
# Do not distinguish links for now.
process.nextTick -> fs.lstat p, callback
statSyncNoException = fs.statSyncNoException
fs.statSyncNoException = (p) ->
[isAsar, asarPath, filePath] = splitPath p
return statSyncNoException p unless isAsar
archive = getOrCreateArchive asarPath
return false unless archive
stats = archive.stat filePath
return false unless stats
asarStatsToFsStats stats
realpathSync = fs.realpathSync
fs.realpathSync = (p) ->
[isAsar, asarPath, filePath] = splitPath p
return realpathSync.apply this, arguments unless isAsar
archive = getOrCreateArchive asarPath
throw new Error("Invalid package #{asarPath}") unless archive
real = archive.realpath filePath
throw createNotFoundError(asarPath, filePath) if real is false
path.join realpathSync(asarPath), real
realpath = fs.realpath
fs.realpath = (p, cache, callback) ->
[isAsar, asarPath, filePath] = splitPath p
return realpath.apply this, arguments unless isAsar
if typeof cache is 'function'
callback = cache
cache = undefined
archive = getOrCreateArchive asarPath
return callback new Error("Invalid package #{asarPath}") unless archive
real = archive.realpath filePath
return callback createNotFoundError(asarPath, filePath) if real is false
realpath asarPath, (err, p) ->
return callback err if err
callback null, path.join(p, real)
exists = fs.exists
fs.exists = (p, callback) ->
[isAsar, asarPath, filePath] = splitPath p
return exists p, callback unless isAsar
archive = getOrCreateArchive asarPath
return callback new Error("Invalid package #{asarPath}") unless archive
process.nextTick -> callback archive.stat(filePath) isnt false
existsSync = fs.existsSync
fs.existsSync = (p) ->
[isAsar, asarPath, filePath] = splitPath p
return existsSync p unless isAsar
archive = getOrCreateArchive asarPath
return false unless archive
archive.stat(filePath) isnt false
open = fs.open
readFile = fs.readFile
fs.readFile = (p, options, callback) ->
[isAsar, asarPath, filePath] = splitPath p
return readFile.apply this, arguments unless isAsar
if typeof options is 'function'
callback = options
options = undefined
archive = getOrCreateArchive asarPath
return callback new Error("Invalid package #{asarPath}") unless archive
info = archive.getFileInfo filePath
return callback createNotFoundError(asarPath, filePath) unless info
if not options
options = encoding: null, flag: 'r'
else if util.isString options
options = encoding: options, flag: 'r'
else if not util.isObject options
throw new TypeError('Bad arguments')
flag = options.flag || 'r'
encoding = options.encoding
buffer = new Buffer(info.size)
open archive.path, flag, (error, fd) ->
return callback error if error
fs.read fd, buffer, 0, info.size, info.offset, (error) ->
fs.close fd, ->
callback error, if encoding then buffer.toString encoding else buffer
openSync = fs.openSync
readFileSync = fs.readFileSync
fs.readFileSync = (p, options) ->
[isAsar, asarPath, filePath] = splitPath p
return readFileSync.apply this, arguments unless isAsar
archive = getOrCreateArchive asarPath
throw new Error("Invalid package #{asarPath}") unless archive
info = archive.getFileInfo filePath
throw createNotFoundError(asarPath, filePath) unless info
if not options
options = encoding: null, flag: 'r'
else if util.isString options
options = encoding: options, flag: 'r'
else if not util.isObject options
throw new TypeError('Bad arguments')
flag = options.flag || 'r'
encoding = options.encoding
buffer = new Buffer(info.size)
fd = openSync archive.path, flag
try
fs.readSync fd, buffer, 0, info.size, info.offset
catch e
throw e
finally
fs.closeSync fd
if encoding then buffer.toString encoding else buffer
readdir = fs.readdir
fs.readdir = (p, callback) ->
[isAsar, asarPath, filePath] = splitPath p
return readdir.apply this, arguments unless isAsar
archive = getOrCreateArchive asarPath
return callback new Error("Invalid package #{asarPath}") unless archive
files = archive.readdir filePath
return callback createNotFoundError(asarPath, filePath) unless files
process.nextTick -> callback null, files
readdirSync = fs.readdirSync
fs.readdirSync = (p) ->
[isAsar, asarPath, filePath] = splitPath p
return readdirSync.apply this, arguments unless isAsar
archive = getOrCreateArchive asarPath
throw new Error("Invalid package #{asarPath}") unless archive
files = archive.readdir filePath
throw createNotFoundError(asarPath, filePath) unless files
files
# Override APIs that rely on passing file path instead of content to C++.
overrideAPISync = (module, name, arg = 0) ->
old = module[name]
module[name] = ->
p = arguments[arg]
[isAsar, asarPath, filePath] = splitPath p
return old.apply this, arguments unless isAsar
archive = getOrCreateArchive asarPath
throw new Error("Invalid package #{asarPath}") unless archive
newPath = archive.copyFileOut filePath
throw createNotFoundError(asarPath, filePath) unless newPath
arguments[arg] = newPath
old.apply this, arguments
overrideAPI = (module, name, arg = 0) ->
old = module[name]
module[name] = ->
p = arguments[arg]
[isAsar, asarPath, filePath] = splitPath p
return old.apply this, arguments unless isAsar
callback = arguments[arguments.length - 1]
return overrideAPISync module, name, arg unless typeof callback is 'function'
archive = getOrCreateArchive asarPath
return callback new Error("Invalid package #{asarPath}") unless archive
newPath = archive.copyFileOut filePath
return callback createNotFoundError(asarPath, filePath) unless newPath
arguments[arg] = newPath
old.apply this, arguments
overrideAPI fs, 'open'
overrideAPI child_process, 'execFile'
overrideAPISync process, 'dlopen', 1
overrideAPISync require('module')._extensions, '.node', 1
overrideAPISync fs, 'openSync'
overrideAPISync child_process, 'fork'

View File

@@ -6,7 +6,7 @@ process.atomBinding = (name) ->
try
process.binding "atom_#{process.type}_#{name}"
catch e
process.binding "atom_common_#{name}" if e.message is 'No such module'
process.binding "atom_common_#{name}" if /No such module/.test e.message
# Add common/api/lib to module search paths.
globalPaths = Module.globalPaths
@@ -33,3 +33,6 @@ global.clearImmediate = timers.clearImmediate
if process.type is 'browser'
global.setTimeout = wrapWithActivateUvLoop timers.setTimeout
global.setInterval = wrapWithActivateUvLoop timers.setInterval
# Add support for asar packages.
require './asar'

View File

@@ -69,6 +69,7 @@ REFERENCE_MODULE(atom_browser_protocol);
REFERENCE_MODULE(atom_browser_global_shortcut);
REFERENCE_MODULE(atom_browser_tray);
REFERENCE_MODULE(atom_browser_window);
REFERENCE_MODULE(atom_common_asar);
REFERENCE_MODULE(atom_common_clipboard);
REFERENCE_MODULE(atom_common_crash_reporter);
REFERENCE_MODULE(atom_common_id_weak_map);

View File

@@ -39,7 +39,7 @@ void NodeBindingsWin::PollEvents() {
ULONG_PTR key;
OVERLAPPED* overlapped;
timeout = uv_get_poll_timeout(uv_loop_);
timeout = uv_backend_timeout(uv_loop_);
GetQueuedCompletionStatus(uv_loop_->iocp,
&bytes,
&key,

View File

@@ -57,6 +57,17 @@ const char kEnableLargerThanScreen[] = "enable-larger-than-screen";
// Forces to use dark theme on Linux.
const char kDarkTheme[] = "dark-theme";
// Enable DirectWrite on Windows.
const char kDirectWrite[] = "direct-write";
// Web runtime features.
const char kExperimentalFeatures[] = "experimental-features";
const char kExperimentalCanvasFeatures[] = "experimental-canvas-features";
const char kSubpixelFontScaling[] = "subpixel-font-scaling";
const char kOverlayScrollbars[] = "overlay-scrollbars";
const char kOverlayFullscreenVideo[] = "overlay-fullscreen-video";
const char kSharedWorker[] = "shared-worker";
} // namespace switches
} // namespace atom

View File

@@ -35,6 +35,14 @@ extern const char kZoomFactor[];
extern const char kAutoHideMenuBar[];
extern const char kEnableLargerThanScreen[];
extern const char kDarkTheme[];
extern const char kDirectWrite[];
extern const char kExperimentalFeatures[];
extern const char kExperimentalCanvasFeatures[];
extern const char kSubpixelFontScaling[];
extern const char kOverlayScrollbars[];
extern const char kOverlayFullscreenVideo[];
extern const char kSharedWorker[];
} // namespace switches

View File

@@ -12,6 +12,7 @@
#include "atom/renderer/api/atom_renderer_bindings.h"
#include "atom/renderer/atom_render_view_observer.h"
#include "chrome/renderer/printing/print_web_view_helper.h"
#include "chrome/renderer/tts_dispatcher.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_frame_observer.h"
#include "content/public/renderer/render_thread.h"
@@ -20,6 +21,7 @@
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebFrame.h"
#include "third_party/WebKit/public/web/WebKit.h"
#include "third_party/WebKit/public/web/WebRuntimeFeatures.h"
#include "atom/common/node_includes.h"
@@ -34,6 +36,19 @@ const char* kSecurityManualEnableIframe = "manual-enable-iframe";
const char* kSecurityDisable = "disable";
const char* kSecurityEnableNodeIntegration = "enable-node-integration";
bool IsSwitchEnabled(base::CommandLine* command_line,
const char* switch_string,
bool* enabled) {
std::string value = command_line->GetSwitchValueASCII(switch_string);
if (value == "true")
*enabled = true;
else if (value == "false")
*enabled = false;
else
return false;
return true;
}
// Helper class to forward the WillReleaseScriptContext message to the client.
class AtomRenderFrameObserver : public content::RenderFrameObserver {
public:
@@ -73,10 +88,6 @@ AtomRendererClient::AtomRendererClient()
node_integration_ = ALL;
if (IsNodeBindingEnabled()) {
// Always enable harmony when node binding is on.
std::string flags("--harmony");
v8::V8::SetFlagsFromString(flags.c_str(), static_cast<int>(flags.size()));
node_bindings_.reset(NodeBindings::Create(false));
atom_bindings_.reset(new AtomRendererBindings);
}
@@ -86,6 +97,8 @@ AtomRendererClient::~AtomRendererClient() {
}
void AtomRendererClient::WebKitInitialized() {
EnableWebRuntimeFeatures();
if (!IsNodeBindingEnabled())
return;
@@ -116,6 +129,11 @@ void AtomRendererClient::RenderViewCreated(content::RenderView* render_view) {
new AtomRenderViewObserver(render_view, this);
}
blink::WebSpeechSynthesizer* AtomRendererClient::OverrideSpeechSynthesizer(
blink::WebSpeechSynthesizerClient* client) {
return new TtsDispatcher(client);
}
void AtomRendererClient::DidCreateScriptContext(blink::WebFrame* frame,
v8::Handle<v8::Context> context,
int extension_group,
@@ -220,4 +238,21 @@ bool AtomRendererClient::IsNodeBindingEnabled(blink::WebFrame* frame) {
return true;
}
void AtomRendererClient::EnableWebRuntimeFeatures() {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
bool b;
if (IsSwitchEnabled(command_line, switches::kExperimentalFeatures, &b))
blink::WebRuntimeFeatures::enableExperimentalFeatures(b);
if (IsSwitchEnabled(command_line, switches::kExperimentalCanvasFeatures, &b))
blink::WebRuntimeFeatures::enableExperimentalCanvasFeatures(b);
if (IsSwitchEnabled(command_line, switches::kSubpixelFontScaling, &b))
blink::WebRuntimeFeatures::enableSubpixelFontScaling(b);
if (IsSwitchEnabled(command_line, switches::kOverlayScrollbars, &b))
blink::WebRuntimeFeatures::enableOverlayScrollbars(b);
if (IsSwitchEnabled(command_line, switches::kOverlayFullscreenVideo, &b))
blink::WebRuntimeFeatures::enableOverlayFullscreenVideo(b);
if (IsSwitchEnabled(command_line, switches::kSharedWorker, &b))
blink::WebRuntimeFeatures::enableSharedWorker(b);
}
} // namespace atom

View File

@@ -50,6 +50,8 @@ class AtomRendererClient : public content::ContentRendererClient,
virtual void RenderThreadStarted() OVERRIDE;
virtual void RenderFrameCreated(content::RenderFrame* render_frame) OVERRIDE;
virtual void RenderViewCreated(content::RenderView*) OVERRIDE;
virtual blink::WebSpeechSynthesizer* OverrideSpeechSynthesizer(
blink::WebSpeechSynthesizerClient* client);
virtual void DidCreateScriptContext(blink::WebFrame* frame,
v8::Handle<v8::Context> context,
int extension_group,
@@ -61,6 +63,8 @@ class AtomRendererClient : public content::ContentRendererClient,
bool is_server_redirect,
bool* send_referrer) OVERRIDE;
void EnableWebRuntimeFeatures();
std::vector<node::Environment*> web_page_envs_;
scoped_ptr<NodeBindings> node_bindings_;

View File

@@ -24,14 +24,18 @@ require path.resolve(__dirname, '..', '..', 'common', 'lib', 'init.js')
global.require = require
global.module = module
# Set the __filename to the path of html file if it's file:// protocol.
if window.location.protocol is 'file:'
# Emit the 'exit' event when page is unloading.
window.addEventListener 'unload', ->
process.emit 'exit'
# Set the __filename to the path of html file if it's file: or asar: protocol.
if window.location.protocol in ['file:', 'asar:']
pathname =
if process.platform is 'win32'
if process.platform is 'win32' and window.location.pathname[0] is '/'
window.location.pathname.substr 1
else
window.location.pathname
global.__filename = decodeURIComponent pathname
global.__filename = path.normalize decodeURIComponent(pathname)
global.__dirname = path.dirname global.__filename
# Set module's filename so relative require can work as expected.

View File

@@ -21,8 +21,8 @@ namespace extensions {
// forwards its output to the base class for processing.
//
// This class does two things:
// 1. Intercepts media keys. Uses an event tap for intercepting media keys
// (PlayPause, NextTrack, PreviousTrack).
// 1. Intercepts media/volume keys. Uses an event tap for intercepting media keys
// (PlayPause, NextTrack, PreviousTrack) and volume keys(VolumeUp, VolumeDown, VolumeMute).
// 2. Binds keyboard shortcuts (hot keys). Carbon RegisterEventHotKey API for
// binding to non-media key global hot keys (eg. Command-Shift-1).
class GlobalShortcutListenerMac : public GlobalShortcutListener {
@@ -38,7 +38,7 @@ class GlobalShortcutListenerMac : public GlobalShortcutListener {
// Keyboard event callbacks.
void OnHotKeyEvent(EventHotKeyID hot_key_id);
bool OnMediaKeyEvent(int key_code);
bool OnMediaOrVolumeKeyEvent(int key_code);
// GlobalShortcutListener implementation.
virtual void StartListening() OVERRIDE;
@@ -52,16 +52,16 @@ class GlobalShortcutListenerMac : public GlobalShortcutListener {
bool RegisterHotKey(const ui::Accelerator& accelerator, KeyId hot_key_id);
void UnregisterHotKey(const ui::Accelerator& accelerator);
// Enable and disable the media key event tap.
void StartWatchingMediaKeys();
void StopWatchingMediaKeys();
// Enable and disable the media/volume key event tap.
void StartWatchingMediaOrVolumeKeys();
void StopWatchingMediaOrVolumeKeys();
// Enable and disable the hot key event handler.
void StartWatchingHotKeys();
void StopWatchingHotKeys();
// Whether or not any media keys are currently registered.
bool IsAnyMediaKeyRegistered();
// Whether or not any media/volume keys are currently registered.
bool IsAnyMediaOrVolumeKeyRegistered();
// Whether or not any hot keys are currently registered.
bool IsAnyHotKeyRegistered();
@@ -80,7 +80,7 @@ class GlobalShortcutListenerMac : public GlobalShortcutListener {
// The hotkey identifier for the next global shortcut that is added.
KeyId hot_key_id_;
// A map of all hotkeys (media keys and shortcuts) mapping to their
// A map of all hotkeys (media/volume keys and shortcuts) mapping to their
// corresponding hotkey IDs. For quickly finding if an accelerator is
// registered.
AcceleratorIdMap accelerator_ids_;
@@ -91,7 +91,7 @@ class GlobalShortcutListenerMac : public GlobalShortcutListener {
// Keyboard shortcut IDs to hotkeys map for unregistration.
IdHotKeyRefMap id_hot_key_refs_;
// Event tap for intercepting mac media keys.
// Event tap for intercepting mac media/volume keys.
CFMachPortRef event_tap_;
CFRunLoopSourceRef event_tap_source_;

View File

@@ -19,11 +19,11 @@ using extensions::GlobalShortcutListenerMac;
namespace {
// The media keys subtype. No official docs found, but widely known.
// The media/volume keys subtype. No official docs found, but widely known.
// http://lists.apple.com/archives/cocoa-dev/2007/Aug/msg00499.html
const int kSystemDefinedEventMediaKeysSubtype = 8;
const int kSystemDefinedEventMediaAndVolumeKeysSubtype = 8;
ui::KeyboardCode MediaKeyCodeToKeyboardCode(int key_code) {
ui::KeyboardCode MediaOrVolumeKeyCodeToKeyboardCode(int key_code) {
switch (key_code) {
case NX_KEYTYPE_PLAY:
return ui::VKEY_MEDIA_PLAY_PAUSE;
@@ -33,17 +33,26 @@ ui::KeyboardCode MediaKeyCodeToKeyboardCode(int key_code) {
case NX_KEYTYPE_NEXT:
case NX_KEYTYPE_FAST:
return ui::VKEY_MEDIA_NEXT_TRACK;
case NX_KEYTYPE_SOUND_UP:
return ui::VKEY_VOLUME_UP;
case NX_KEYTYPE_SOUND_DOWN:
return ui::VKEY_VOLUME_DOWN;
case NX_KEYTYPE_MUTE:
return ui::VKEY_VOLUME_MUTE;
}
return ui::VKEY_UNKNOWN;
}
bool IsMediaKey(const ui::Accelerator& accelerator) {
bool IsMediaOrVolumeKey(const ui::Accelerator& accelerator) {
if (accelerator.modifiers() != 0)
return false;
return (accelerator.key_code() == ui::VKEY_MEDIA_NEXT_TRACK ||
accelerator.key_code() == ui::VKEY_MEDIA_PREV_TRACK ||
accelerator.key_code() == ui::VKEY_MEDIA_PLAY_PAUSE ||
accelerator.key_code() == ui::VKEY_MEDIA_STOP);
accelerator.key_code() == ui::VKEY_MEDIA_STOP ||
accelerator.key_code() == ui::VKEY_VOLUME_UP ||
accelerator.key_code() == ui::VKEY_VOLUME_DOWN ||
accelerator.key_code() == ui::VKEY_VOLUME_MUTE);
}
} // namespace
@@ -77,8 +86,8 @@ GlobalShortcutListenerMac::~GlobalShortcutListenerMac() {
// If keys are still registered, make sure we stop the tap. Again, this
// should never happen.
if (IsAnyMediaKeyRegistered())
StopWatchingMediaKeys();
if (IsAnyMediaOrVolumeKeyRegistered())
StopWatchingMediaOrVolumeKeys();
if (IsAnyHotKeyRegistered())
StopWatchingHotKeys();
@@ -114,9 +123,11 @@ void GlobalShortcutListenerMac::OnHotKeyEvent(EventHotKeyID hot_key_id) {
NotifyKeyPressed(accelerator);
}
bool GlobalShortcutListenerMac::OnMediaKeyEvent(int media_key_code) {
bool GlobalShortcutListenerMac::OnMediaOrVolumeKeyEvent(
int media_or_volume_key_code) {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ui::KeyboardCode key_code = MediaKeyCodeToKeyboardCode(media_key_code);
ui::KeyboardCode key_code = MediaOrVolumeKeyCodeToKeyboardCode(
media_or_volume_key_code);
// Create an accelerator corresponding to the keyCode.
ui::Accelerator accelerator(key_code, 0);
// Look for a match with a bound hot_key.
@@ -133,10 +144,10 @@ bool GlobalShortcutListenerMac::RegisterAcceleratorImpl(
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(accelerator_ids_.find(accelerator) == accelerator_ids_.end());
if (IsMediaKey(accelerator)) {
if (!IsAnyMediaKeyRegistered()) {
// If this is the first media key registered, start the event tap.
StartWatchingMediaKeys();
if (IsMediaOrVolumeKey(accelerator)) {
if (!IsAnyMediaOrVolumeKeyRegistered()) {
// If this is the first media/volume key registered, start the event tap.
StartWatchingMediaOrVolumeKeys();
}
} else {
// Register hot_key if they are non-media keyboard shortcuts.
@@ -161,7 +172,7 @@ void GlobalShortcutListenerMac::UnregisterAcceleratorImpl(
DCHECK(accelerator_ids_.find(accelerator) != accelerator_ids_.end());
// Unregister the hot_key if it's a keyboard shortcut.
if (!IsMediaKey(accelerator))
if (!IsMediaOrVolumeKey(accelerator))
UnregisterHotKey(accelerator);
// Remove hot_key from the mappings.
@@ -169,11 +180,11 @@ void GlobalShortcutListenerMac::UnregisterAcceleratorImpl(
id_accelerators_.erase(key_id);
accelerator_ids_.erase(accelerator);
if (IsMediaKey(accelerator)) {
// If we unregistered a media key, and now no media keys are registered,
// stop the media key tap.
if (!IsAnyMediaKeyRegistered())
StopWatchingMediaKeys();
if (IsMediaOrVolumeKey(accelerator)) {
// If we unregistered a media/volume key, and now no media/volume keys are registered,
// stop the media/volume key tap.
if (!IsAnyMediaOrVolumeKeyRegistered())
StopWatchingMediaOrVolumeKeys();
} else {
// If we unregistered a hot key, and no more hot keys are registered, remove
// the hot key handler.
@@ -226,12 +237,12 @@ void GlobalShortcutListenerMac::UnregisterHotKey(
id_hot_key_refs_.erase(key_id);
}
void GlobalShortcutListenerMac::StartWatchingMediaKeys() {
void GlobalShortcutListenerMac::StartWatchingMediaOrVolumeKeys() {
// Make sure there's no existing event tap.
DCHECK(event_tap_ == NULL);
DCHECK(event_tap_source_ == NULL);
// Add an event tap to intercept the system defined media key events.
// Add an event tap to intercept the system defined media/volume key events.
event_tap_ = CGEventTapCreate(kCGSessionEventTap,
kCGHeadInsertEventTap,
kCGEventTapOptionDefault,
@@ -254,7 +265,7 @@ void GlobalShortcutListenerMac::StartWatchingMediaKeys() {
kCFRunLoopCommonModes);
}
void GlobalShortcutListenerMac::StopWatchingMediaKeys() {
void GlobalShortcutListenerMac::StopWatchingMediaOrVolumeKeys() {
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), event_tap_source_,
kCFRunLoopCommonModes);
// Ensure both event tap and source are initialized.
@@ -287,11 +298,11 @@ void GlobalShortcutListenerMac::StopWatchingHotKeys() {
event_handler_ = NULL;
}
bool GlobalShortcutListenerMac::IsAnyMediaKeyRegistered() {
// Iterate through registered accelerators, looking for media keys.
bool GlobalShortcutListenerMac::IsAnyMediaOrVolumeKeyRegistered() {
// Iterate through registered accelerators, looking for media/volume keys.
AcceleratorIdMap::iterator it;
for (it = accelerator_ids_.begin(); it != accelerator_ids_.end(); ++it) {
if (IsMediaKey(it->first))
if (IsMediaOrVolumeKey(it->first))
return true;
}
return false;
@@ -300,7 +311,7 @@ bool GlobalShortcutListenerMac::IsAnyMediaKeyRegistered() {
bool GlobalShortcutListenerMac::IsAnyHotKeyRegistered() {
AcceleratorIdMap::iterator it;
for (it = accelerator_ids_.begin(); it != accelerator_ids_.end(); ++it) {
if (!IsMediaKey(it->first))
if (!IsMediaOrVolumeKey(it->first))
return true;
}
return false;
@@ -329,20 +340,24 @@ CGEventRef GlobalShortcutListenerMac::EventTapCallback(
return event;
}
// Ignore events that are not system defined media keys.
// Ignore events that are not system defined media/volume keys.
if (type != NX_SYSDEFINED ||
[ns_event type] != NSSystemDefined ||
[ns_event subtype] != kSystemDefinedEventMediaKeysSubtype) {
[ns_event subtype] != kSystemDefinedEventMediaAndVolumeKeysSubtype) {
return event;
}
NSInteger data1 = [ns_event data1];
// Ignore media keys that aren't previous, next and play/pause.
// Ignore media keys that aren't previous, next and play/pause and
// volume keys that aren't up, down and mute.
// Magical constants are from http://weblog.rogueamoeba.com/2007/09/29/
int key_code = (data1 & 0xFFFF0000) >> 16;
if (key_code != NX_KEYTYPE_PLAY && key_code != NX_KEYTYPE_NEXT &&
key_code != NX_KEYTYPE_PREVIOUS && key_code != NX_KEYTYPE_FAST &&
key_code != NX_KEYTYPE_REWIND) {
key_code != NX_KEYTYPE_REWIND &&
key_code != NX_KEYTYPE_SOUND_UP &&
key_code != NX_KEYTYPE_SOUND_DOWN &&
key_code != NX_KEYTYPE_MUTE) {
return event;
}
@@ -353,8 +368,8 @@ CGEventRef GlobalShortcutListenerMac::EventTapCallback(
if (!is_key_pressed)
return event;
// Now we have a media key that we care about. Send it to the caller.
bool was_handled = shortcut_listener->OnMediaKeyEvent(key_code);
// Now we have a media/volume key that we care about. Send it to the caller.
bool was_handled = shortcut_listener->OnMediaOrVolumeKeyEvent(key_code);
// Prevent event from proagating to other apps if handled by Chrome.
if (was_handled)

View File

@@ -0,0 +1,343 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_SPEECH_TTS_CONTROLLER_H_
#define CHROME_BROWSER_SPEECH_TTS_CONTROLLER_H_
#include <queue>
#include <set>
#include <string>
#include <vector>
#include "base/memory/scoped_ptr.h"
#include "base/memory/singleton.h"
#include "base/memory/weak_ptr.h"
#include "url/gurl.h"
class Utterance;
class TtsPlatformImpl;
namespace base {
class Value;
}
namespace content {
class BrowserContext;
}
// Events sent back from the TTS engine indicating the progress.
enum TtsEventType {
TTS_EVENT_START,
TTS_EVENT_END,
TTS_EVENT_WORD,
TTS_EVENT_SENTENCE,
TTS_EVENT_MARKER,
TTS_EVENT_INTERRUPTED,
TTS_EVENT_CANCELLED,
TTS_EVENT_ERROR,
TTS_EVENT_PAUSE,
TTS_EVENT_RESUME
};
enum TtsGenderType {
TTS_GENDER_NONE,
TTS_GENDER_MALE,
TTS_GENDER_FEMALE
};
// Returns true if this event type is one that indicates an utterance
// is finished and can be destroyed.
bool IsFinalTtsEventType(TtsEventType event_type);
// The continuous parameters that apply to a given utterance.
struct UtteranceContinuousParameters {
UtteranceContinuousParameters();
double rate;
double pitch;
double volume;
};
// Information about one voice.
struct VoiceData {
VoiceData();
~VoiceData();
std::string name;
std::string lang;
TtsGenderType gender;
std::string extension_id;
std::set<TtsEventType> events;
// If true, the synthesis engine is a remote network resource.
// It may be higher latency and may incur bandwidth costs.
bool remote;
// If true, this is implemented by this platform's subclass of
// TtsPlatformImpl. If false, this is implemented by an extension.
bool native;
std::string native_voice_identifier;
};
// Interface that delegates TTS requests to user-installed extensions.
class TtsEngineDelegate {
public:
virtual ~TtsEngineDelegate() {}
// Return a list of all available voices registered.
virtual void GetVoices(content::BrowserContext* browser_context,
std::vector<VoiceData>* out_voices) = 0;
// Speak the given utterance by sending an event to the given TTS engine.
virtual void Speak(Utterance* utterance, const VoiceData& voice) = 0;
// Stop speaking the given utterance by sending an event to the target
// associated with this utterance.
virtual void Stop(Utterance* utterance) = 0;
// Pause in the middle of speaking this utterance.
virtual void Pause(Utterance* utterance) = 0;
// Resume speaking this utterance.
virtual void Resume(Utterance* utterance) = 0;
// Load the built-in component extension for ChromeOS.
virtual bool LoadBuiltInTtsExtension(
content::BrowserContext* browser_context) = 0;
};
// Class that wants to receive events on utterances.
class UtteranceEventDelegate {
public:
virtual ~UtteranceEventDelegate() {}
virtual void OnTtsEvent(Utterance* utterance,
TtsEventType event_type,
int char_index,
const std::string& error_message) = 0;
};
// Class that wants to be notified when the set of
// voices has changed.
class VoicesChangedDelegate {
public:
virtual ~VoicesChangedDelegate() {}
virtual void OnVoicesChanged() = 0;
};
// One speech utterance.
class Utterance {
public:
// Construct an utterance given a profile and a completion task to call
// when the utterance is done speaking. Before speaking this utterance,
// its other parameters like text, rate, pitch, etc. should all be set.
explicit Utterance(content::BrowserContext* browser_context);
~Utterance();
// Sends an event to the delegate. If the event type is TTS_EVENT_END
// or TTS_EVENT_ERROR, deletes the utterance. If |char_index| is -1,
// uses the last good value.
void OnTtsEvent(TtsEventType event_type,
int char_index,
const std::string& error_message);
// Finish an utterance without sending an event to the delegate.
void Finish();
// Getters and setters for the text to speak and other speech options.
void set_text(const std::string& text) { text_ = text; }
const std::string& text() const { return text_; }
void set_options(const base::Value* options);
const base::Value* options() const { return options_.get(); }
void set_src_extension_id(const std::string& src_extension_id) {
src_extension_id_ = src_extension_id;
}
const std::string& src_extension_id() { return src_extension_id_; }
void set_src_id(int src_id) { src_id_ = src_id; }
int src_id() { return src_id_; }
void set_src_url(const GURL& src_url) { src_url_ = src_url; }
const GURL& src_url() { return src_url_; }
void set_voice_name(const std::string& voice_name) {
voice_name_ = voice_name;
}
const std::string& voice_name() const { return voice_name_; }
void set_lang(const std::string& lang) {
lang_ = lang;
}
const std::string& lang() const { return lang_; }
void set_gender(TtsGenderType gender) {
gender_ = gender;
}
TtsGenderType gender() const { return gender_; }
void set_continuous_parameters(const UtteranceContinuousParameters& params) {
continuous_parameters_ = params;
}
const UtteranceContinuousParameters& continuous_parameters() {
return continuous_parameters_;
}
void set_can_enqueue(bool can_enqueue) { can_enqueue_ = can_enqueue; }
bool can_enqueue() const { return can_enqueue_; }
void set_required_event_types(const std::set<TtsEventType>& types) {
required_event_types_ = types;
}
const std::set<TtsEventType>& required_event_types() const {
return required_event_types_;
}
void set_desired_event_types(const std::set<TtsEventType>& types) {
desired_event_types_ = types;
}
const std::set<TtsEventType>& desired_event_types() const {
return desired_event_types_;
}
const std::string& extension_id() const { return extension_id_; }
void set_extension_id(const std::string& extension_id) {
extension_id_ = extension_id;
}
UtteranceEventDelegate* event_delegate() const {
return event_delegate_.get();
}
void set_event_delegate(
base::WeakPtr<UtteranceEventDelegate> event_delegate) {
event_delegate_ = event_delegate;
}
// Getters and setters for internal state.
content::BrowserContext* browser_context() const { return browser_context_; }
int id() const { return id_; }
bool finished() const { return finished_; }
private:
// The BrowserContext that initiated this utterance.
content::BrowserContext* browser_context_;
// The extension ID of the extension providing TTS for this utterance, or
// empty if native TTS is being used.
std::string extension_id_;
// The unique ID of this utterance, used to associate callback functions
// with utterances.
int id_;
// The id of the next utterance, so we can associate requests with
// responses.
static int next_utterance_id_;
// The text to speak.
std::string text_;
// The full options arg passed to tts.speak, which may include fields
// other than the ones we explicitly parse, below.
scoped_ptr<base::Value> options_;
// The extension ID of the extension that called speak() and should
// receive events.
std::string src_extension_id_;
// The source extension's ID of this utterance, so that it can associate
// events with the appropriate callback.
int src_id_;
// The URL of the page where the source extension called speak.
GURL src_url_;
// The delegate to be called when an utterance event is fired.
base::WeakPtr<UtteranceEventDelegate> event_delegate_;
// The parsed options.
std::string voice_name_;
std::string lang_;
TtsGenderType gender_;
UtteranceContinuousParameters continuous_parameters_;
bool can_enqueue_;
std::set<TtsEventType> required_event_types_;
std::set<TtsEventType> desired_event_types_;
// The index of the current char being spoken.
int char_index_;
// True if this utterance received an event indicating it's done.
bool finished_;
};
// Singleton class that manages text-to-speech for the TTS and TTS engine
// extension APIs, maintaining a queue of pending utterances and keeping
// track of all state.
class TtsController {
public:
// Get the single instance of this class.
static TtsController* GetInstance();
// Returns true if we're currently speaking an utterance.
virtual bool IsSpeaking() = 0;
// Speak the given utterance. If the utterance's can_enqueue flag is true
// and another utterance is in progress, adds it to the end of the queue.
// Otherwise, interrupts any current utterance and speaks this one
// immediately.
virtual void SpeakOrEnqueue(Utterance* utterance) = 0;
// Stop all utterances and flush the queue. Implies leaving pause mode
// as well.
virtual void Stop() = 0;
// Pause the speech queue. Some engines may support pausing in the middle
// of an utterance.
virtual void Pause() = 0;
// Resume speaking.
virtual void Resume() = 0;
// Handle events received from the speech engine. Events are forwarded to
// the callback function, and in addition, completion and error events
// trigger finishing the current utterance and starting the next one, if
// any.
virtual void OnTtsEvent(int utterance_id,
TtsEventType event_type,
int char_index,
const std::string& error_message) = 0;
// Return a list of all available voices, including the native voice,
// if supported, and all voices registered by extensions.
virtual void GetVoices(content::BrowserContext* browser_context,
std::vector<VoiceData>* out_voices) = 0;
// Called by the extension system or platform implementation when the
// list of voices may have changed and should be re-queried.
virtual void VoicesChanged() = 0;
// Add a delegate that wants to be notified when the set of voices changes.
virtual void AddVoicesChangedDelegate(VoicesChangedDelegate* delegate) = 0;
// Remove delegate that wants to be notified when the set of voices changes.
virtual void RemoveVoicesChangedDelegate(VoicesChangedDelegate* delegate) = 0;
// Set the delegate that processes TTS requests with user-installed
// extensions.
virtual void SetTtsEngineDelegate(TtsEngineDelegate* delegate) = 0;
// Get the delegate that processes TTS requests with user-installed
// extensions.
virtual TtsEngineDelegate* GetTtsEngineDelegate() = 0;
// For unit testing.
virtual void SetPlatformImpl(TtsPlatformImpl* platform_impl) = 0;
virtual int QueueSize() = 0;
protected:
virtual ~TtsController() {}
};
#endif // CHROME_BROWSER_SPEECH_TTS_CONTROLLER_H_

View File

@@ -0,0 +1,464 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/speech/tts_controller_impl.h"
#include <string>
#include <vector>
#include "base/float_util.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/speech/tts_platform.h"
namespace {
// A value to be used to indicate that there is no char index available.
const int kInvalidCharIndex = -1;
// Given a language/region code of the form 'fr-FR', returns just the basic
// language portion, e.g. 'fr'.
std::string TrimLanguageCode(std::string lang) {
if (lang.size() >= 5 && lang[2] == '-')
return lang.substr(0, 2);
else
return lang;
}
} // namespace
bool IsFinalTtsEventType(TtsEventType event_type) {
return (event_type == TTS_EVENT_END ||
event_type == TTS_EVENT_INTERRUPTED ||
event_type == TTS_EVENT_CANCELLED ||
event_type == TTS_EVENT_ERROR);
}
//
// UtteranceContinuousParameters
//
UtteranceContinuousParameters::UtteranceContinuousParameters()
: rate(-1),
pitch(-1),
volume(-1) {}
//
// VoiceData
//
VoiceData::VoiceData()
: gender(TTS_GENDER_NONE),
remote(false),
native(false) {}
VoiceData::~VoiceData() {}
//
// Utterance
//
// static
int Utterance::next_utterance_id_ = 0;
Utterance::Utterance(content::BrowserContext* browser_context)
: browser_context_(browser_context),
id_(next_utterance_id_++),
src_id_(-1),
gender_(TTS_GENDER_NONE),
can_enqueue_(false),
char_index_(0),
finished_(false) {
options_.reset(new base::DictionaryValue());
}
Utterance::~Utterance() {
DCHECK(finished_);
}
void Utterance::OnTtsEvent(TtsEventType event_type,
int char_index,
const std::string& error_message) {
if (char_index >= 0)
char_index_ = char_index;
if (IsFinalTtsEventType(event_type))
finished_ = true;
if (event_delegate_)
event_delegate_->OnTtsEvent(this, event_type, char_index, error_message);
if (finished_)
event_delegate_.reset();
}
void Utterance::Finish() {
finished_ = true;
}
void Utterance::set_options(const base::Value* options) {
options_.reset(options->DeepCopy());
}
TtsController* TtsController::GetInstance() {
return TtsControllerImpl::GetInstance();
}
//
// TtsControllerImpl
//
// static
TtsControllerImpl* TtsControllerImpl::GetInstance() {
return Singleton<TtsControllerImpl>::get();
}
TtsControllerImpl::TtsControllerImpl()
: current_utterance_(NULL),
paused_(false),
platform_impl_(NULL),
tts_engine_delegate_(NULL) {
}
TtsControllerImpl::~TtsControllerImpl() {
if (current_utterance_) {
current_utterance_->Finish();
delete current_utterance_;
}
// Clear any queued utterances too.
ClearUtteranceQueue(false); // Don't sent events.
}
void TtsControllerImpl::SpeakOrEnqueue(Utterance* utterance) {
// If we're paused and we get an utterance that can't be queued,
// flush the queue but stay in the paused state.
if (paused_ && !utterance->can_enqueue()) {
Stop();
paused_ = true;
delete utterance;
return;
}
if (paused_ || (IsSpeaking() && utterance->can_enqueue())) {
utterance_queue_.push(utterance);
} else {
Stop();
SpeakNow(utterance);
}
}
void TtsControllerImpl::SpeakNow(Utterance* utterance) {
// Ensure we have all built-in voices loaded. This is a no-op if already
// loaded.
bool loaded_built_in =
GetPlatformImpl()->LoadBuiltInTtsExtension(utterance->browser_context());
// Get all available voices and try to find a matching voice.
std::vector<VoiceData> voices;
GetVoices(utterance->browser_context(), &voices);
int index = GetMatchingVoice(utterance, voices);
VoiceData voice;
if (index != -1) {
// Select the matching voice.
voice = voices[index];
} else {
// However, if no match was found on a platform without native tts voices,
// attempt to get a voice based only on the current locale without respect
// to any supplied voice names.
std::vector<VoiceData> native_voices;
if (GetPlatformImpl()->PlatformImplAvailable())
GetPlatformImpl()->GetVoices(&native_voices);
if (native_voices.empty() && !voices.empty()) {
// TODO(dtseng): Notify extension caller of an error.
utterance->set_voice_name("");
// TODO(gaochun): Replace the global variable g_browser_process with
// GetContentClient()->browser() to eliminate the dependency of browser
// once TTS implementation was moved to content.
utterance->set_lang(g_browser_process->GetApplicationLocale());
index = GetMatchingVoice(utterance, voices);
// If even that fails, just take the first available voice.
if (index == -1)
index = 0;
voice = voices[index];
} else {
// Otherwise, simply give native voices a chance to handle this utterance.
voice.native = true;
}
}
GetPlatformImpl()->WillSpeakUtteranceWithVoice(utterance, voice);
if (!voice.native) {
#if !defined(OS_ANDROID)
DCHECK(!voice.extension_id.empty());
current_utterance_ = utterance;
utterance->set_extension_id(voice.extension_id);
if (tts_engine_delegate_)
tts_engine_delegate_->Speak(utterance, voice);
bool sends_end_event =
voice.events.find(TTS_EVENT_END) != voice.events.end();
if (!sends_end_event) {
utterance->Finish();
delete utterance;
current_utterance_ = NULL;
SpeakNextUtterance();
}
#endif
} else {
// It's possible for certain platforms to send start events immediately
// during |speak|.
current_utterance_ = utterance;
GetPlatformImpl()->clear_error();
bool success = GetPlatformImpl()->Speak(
utterance->id(),
utterance->text(),
utterance->lang(),
voice,
utterance->continuous_parameters());
if (!success)
current_utterance_ = NULL;
// If the native voice wasn't able to process this speech, see if
// the browser has built-in TTS that isn't loaded yet.
if (!success && loaded_built_in) {
utterance_queue_.push(utterance);
return;
}
if (!success) {
utterance->OnTtsEvent(TTS_EVENT_ERROR, kInvalidCharIndex,
GetPlatformImpl()->error());
delete utterance;
return;
}
}
}
void TtsControllerImpl::Stop() {
paused_ = false;
if (current_utterance_ && !current_utterance_->extension_id().empty()) {
#if !defined(OS_ANDROID)
if (tts_engine_delegate_)
tts_engine_delegate_->Stop(current_utterance_);
#endif
} else {
GetPlatformImpl()->clear_error();
GetPlatformImpl()->StopSpeaking();
}
if (current_utterance_)
current_utterance_->OnTtsEvent(TTS_EVENT_INTERRUPTED, kInvalidCharIndex,
std::string());
FinishCurrentUtterance();
ClearUtteranceQueue(true); // Send events.
}
void TtsControllerImpl::Pause() {
paused_ = true;
if (current_utterance_ && !current_utterance_->extension_id().empty()) {
#if !defined(OS_ANDROID)
if (tts_engine_delegate_)
tts_engine_delegate_->Pause(current_utterance_);
#endif
} else if (current_utterance_) {
GetPlatformImpl()->clear_error();
GetPlatformImpl()->Pause();
}
}
void TtsControllerImpl::Resume() {
paused_ = false;
if (current_utterance_ && !current_utterance_->extension_id().empty()) {
#if !defined(OS_ANDROID)
if (tts_engine_delegate_)
tts_engine_delegate_->Resume(current_utterance_);
#endif
} else if (current_utterance_) {
GetPlatformImpl()->clear_error();
GetPlatformImpl()->Resume();
} else {
SpeakNextUtterance();
}
}
void TtsControllerImpl::OnTtsEvent(int utterance_id,
TtsEventType event_type,
int char_index,
const std::string& error_message) {
// We may sometimes receive completion callbacks "late", after we've
// already finished the utterance (for example because another utterance
// interrupted or we got a call to Stop). This is normal and we can
// safely just ignore these events.
if (!current_utterance_ || utterance_id != current_utterance_->id()) {
return;
}
current_utterance_->OnTtsEvent(event_type, char_index, error_message);
if (current_utterance_->finished()) {
FinishCurrentUtterance();
SpeakNextUtterance();
}
}
void TtsControllerImpl::GetVoices(content::BrowserContext* browser_context,
std::vector<VoiceData>* out_voices) {
#if !defined(OS_ANDROID)
if (browser_context && tts_engine_delegate_)
tts_engine_delegate_->GetVoices(browser_context, out_voices);
#endif
TtsPlatformImpl* platform_impl = GetPlatformImpl();
if (platform_impl) {
// Ensure we have all built-in voices loaded. This is a no-op if already
// loaded.
platform_impl->LoadBuiltInTtsExtension(browser_context);
if (platform_impl->PlatformImplAvailable())
platform_impl->GetVoices(out_voices);
}
}
bool TtsControllerImpl::IsSpeaking() {
return current_utterance_ != NULL || GetPlatformImpl()->IsSpeaking();
}
void TtsControllerImpl::FinishCurrentUtterance() {
if (current_utterance_) {
if (!current_utterance_->finished())
current_utterance_->OnTtsEvent(TTS_EVENT_INTERRUPTED, kInvalidCharIndex,
std::string());
delete current_utterance_;
current_utterance_ = NULL;
}
}
void TtsControllerImpl::SpeakNextUtterance() {
if (paused_)
return;
// Start speaking the next utterance in the queue. Keep trying in case
// one fails but there are still more in the queue to try.
while (!utterance_queue_.empty() && !current_utterance_) {
Utterance* utterance = utterance_queue_.front();
utterance_queue_.pop();
SpeakNow(utterance);
}
}
void TtsControllerImpl::ClearUtteranceQueue(bool send_events) {
while (!utterance_queue_.empty()) {
Utterance* utterance = utterance_queue_.front();
utterance_queue_.pop();
if (send_events)
utterance->OnTtsEvent(TTS_EVENT_CANCELLED, kInvalidCharIndex,
std::string());
else
utterance->Finish();
delete utterance;
}
}
void TtsControllerImpl::SetPlatformImpl(
TtsPlatformImpl* platform_impl) {
platform_impl_ = platform_impl;
}
int TtsControllerImpl::QueueSize() {
return static_cast<int>(utterance_queue_.size());
}
TtsPlatformImpl* TtsControllerImpl::GetPlatformImpl() {
if (!platform_impl_)
platform_impl_ = TtsPlatformImpl::GetInstance();
return platform_impl_;
}
int TtsControllerImpl::GetMatchingVoice(
const Utterance* utterance, std::vector<VoiceData>& voices) {
// Make two passes: the first time, do strict language matching
// ('fr-FR' does not match 'fr-CA'). The second time, do prefix
// language matching ('fr-FR' matches 'fr' and 'fr-CA')
for (int pass = 0; pass < 2; ++pass) {
for (size_t i = 0; i < voices.size(); ++i) {
const VoiceData& voice = voices[i];
if (!utterance->extension_id().empty() &&
utterance->extension_id() != voice.extension_id) {
continue;
}
if (!voice.name.empty() &&
!utterance->voice_name().empty() &&
voice.name != utterance->voice_name()) {
continue;
}
if (!voice.lang.empty() && !utterance->lang().empty()) {
std::string voice_lang = voice.lang;
std::string utterance_lang = utterance->lang();
if (pass == 1) {
voice_lang = TrimLanguageCode(voice_lang);
utterance_lang = TrimLanguageCode(utterance_lang);
}
if (voice_lang != utterance_lang) {
continue;
}
}
if (voice.gender != TTS_GENDER_NONE &&
utterance->gender() != TTS_GENDER_NONE &&
voice.gender != utterance->gender()) {
continue;
}
if (utterance->required_event_types().size() > 0) {
bool has_all_required_event_types = true;
for (std::set<TtsEventType>::const_iterator iter =
utterance->required_event_types().begin();
iter != utterance->required_event_types().end();
++iter) {
if (voice.events.find(*iter) == voice.events.end()) {
has_all_required_event_types = false;
break;
}
}
if (!has_all_required_event_types)
continue;
}
return static_cast<int>(i);
}
}
return -1;
}
void TtsControllerImpl::VoicesChanged() {
for (std::set<VoicesChangedDelegate*>::iterator iter =
voices_changed_delegates_.begin();
iter != voices_changed_delegates_.end(); ++iter) {
(*iter)->OnVoicesChanged();
}
}
void TtsControllerImpl::AddVoicesChangedDelegate(
VoicesChangedDelegate* delegate) {
voices_changed_delegates_.insert(delegate);
}
void TtsControllerImpl::RemoveVoicesChangedDelegate(
VoicesChangedDelegate* delegate) {
voices_changed_delegates_.erase(delegate);
}
void TtsControllerImpl::SetTtsEngineDelegate(
TtsEngineDelegate* delegate) {
tts_engine_delegate_ = delegate;
}
TtsEngineDelegate* TtsControllerImpl::GetTtsEngineDelegate() {
return tts_engine_delegate_;
}

View File

@@ -0,0 +1,104 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_SPEECH_TTS_CONTROLLER_IMPL_H_
#define CHROME_BROWSER_SPEECH_TTS_CONTROLLER_IMPL_H_
#include <queue>
#include <set>
#include <string>
#include <vector>
#include "base/memory/scoped_ptr.h"
#include "base/memory/singleton.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/speech/tts_controller.h"
#include "url/gurl.h"
namespace content {
class BrowserContext;
}
// Singleton class that manages text-to-speech for the TTS and TTS engine
// extension APIs, maintaining a queue of pending utterances and keeping
// track of all state.
class TtsControllerImpl : public TtsController {
public:
// Get the single instance of this class.
static TtsControllerImpl* GetInstance();
// TtsController methods
virtual bool IsSpeaking() OVERRIDE;
virtual void SpeakOrEnqueue(Utterance* utterance) OVERRIDE;
virtual void Stop() OVERRIDE;
virtual void Pause() OVERRIDE;
virtual void Resume() OVERRIDE;
virtual void OnTtsEvent(int utterance_id,
TtsEventType event_type,
int char_index,
const std::string& error_message) OVERRIDE;
virtual void GetVoices(content::BrowserContext* browser_context,
std::vector<VoiceData>* out_voices) OVERRIDE;
virtual void VoicesChanged() OVERRIDE;
virtual void AddVoicesChangedDelegate(
VoicesChangedDelegate* delegate) OVERRIDE;
virtual void RemoveVoicesChangedDelegate(
VoicesChangedDelegate* delegate) OVERRIDE;
virtual void SetTtsEngineDelegate(TtsEngineDelegate* delegate) OVERRIDE;
virtual TtsEngineDelegate* GetTtsEngineDelegate() OVERRIDE;
virtual void SetPlatformImpl(TtsPlatformImpl* platform_impl) OVERRIDE;
virtual int QueueSize() OVERRIDE;
protected:
TtsControllerImpl();
virtual ~TtsControllerImpl();
private:
// Get the platform TTS implementation (or injected mock).
TtsPlatformImpl* GetPlatformImpl();
// Start speaking the given utterance. Will either take ownership of
// |utterance| or delete it if there's an error. Returns true on success.
void SpeakNow(Utterance* utterance);
// Clear the utterance queue. If send_events is true, will send
// TTS_EVENT_CANCELLED events on each one.
void ClearUtteranceQueue(bool send_events);
// Finalize and delete the current utterance.
void FinishCurrentUtterance();
// Start speaking the next utterance in the queue.
void SpeakNextUtterance();
// Given an utterance and a vector of voices, return the
// index of the voice that best matches the utterance.
int GetMatchingVoice(const Utterance* utterance,
std::vector<VoiceData>& voices);
friend struct DefaultSingletonTraits<TtsControllerImpl>;
// The current utterance being spoken.
Utterance* current_utterance_;
// Whether the queue is paused or not.
bool paused_;
// A queue of utterances to speak after the current one finishes.
std::queue<Utterance*> utterance_queue_;
// A set of delegates that want to be notified when the voices change.
std::set<VoicesChangedDelegate*> voices_changed_delegates_;
// A pointer to the platform implementation of text-to-speech, for
// dependency injection.
TtsPlatformImpl* platform_impl_;
// The delegate that processes TTS requests with user-installed extensions.
TtsEngineDelegate* tts_engine_delegate_;
DISALLOW_COPY_AND_ASSIGN(TtsControllerImpl);
};
#endif // CHROME_BROWSER_SPEECH_TTS_CONTROLLER_IMPL_H_

View File

@@ -0,0 +1,347 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <math.h>
#include <map>
#include "base/command_line.h"
#include "base/debug/leak_annotations.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/singleton.h"
#include "base/synchronization/lock.h"
#include "chrome/browser/speech/tts_platform.h"
#include "content/public/browser/browser_thread.h"
#include "library_loaders/libspeechd.h"
using content::BrowserThread;
namespace {
const char kNotSupportedError[] =
"Native speech synthesis not supported on this platform.";
struct SPDChromeVoice {
std::string name;
std::string module;
};
} // namespace
class TtsPlatformImplLinux : public TtsPlatformImpl {
public:
virtual bool PlatformImplAvailable() OVERRIDE;
virtual bool Speak(
int utterance_id,
const std::string& utterance,
const std::string& lang,
const VoiceData& voice,
const UtteranceContinuousParameters& params) OVERRIDE;
virtual bool StopSpeaking() OVERRIDE;
virtual void Pause() OVERRIDE;
virtual void Resume() OVERRIDE;
virtual bool IsSpeaking() OVERRIDE;
virtual void GetVoices(std::vector<VoiceData>* out_voices) OVERRIDE;
void OnSpeechEvent(SPDNotificationType type);
// Get the single instance of this class.
static TtsPlatformImplLinux* GetInstance();
private:
TtsPlatformImplLinux();
virtual ~TtsPlatformImplLinux();
// Initiate the connection with the speech dispatcher.
void Initialize();
// Resets the connection with speech dispatcher.
void Reset();
static void NotificationCallback(size_t msg_id,
size_t client_id,
SPDNotificationType type);
static void IndexMarkCallback(size_t msg_id,
size_t client_id,
SPDNotificationType state,
char* index_mark);
static SPDNotificationType current_notification_;
base::Lock initialization_lock_;
LibSpeechdLoader libspeechd_loader_;
SPDConnection* conn_;
// These apply to the current utterance only.
std::string utterance_;
int utterance_id_;
// Map a string composed of a voicename and module to the voicename. Used to
// uniquely identify a voice across all available modules.
scoped_ptr<std::map<std::string, SPDChromeVoice> > all_native_voices_;
friend struct DefaultSingletonTraits<TtsPlatformImplLinux>;
DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplLinux);
};
// static
SPDNotificationType TtsPlatformImplLinux::current_notification_ =
SPD_EVENT_END;
TtsPlatformImplLinux::TtsPlatformImplLinux()
: utterance_id_(0) {
BrowserThread::PostTask(BrowserThread::FILE,
FROM_HERE,
base::Bind(&TtsPlatformImplLinux::Initialize,
base::Unretained(this)));
}
void TtsPlatformImplLinux::Initialize() {
base::AutoLock lock(initialization_lock_);
if (!libspeechd_loader_.Load("libspeechd.so.2"))
return;
{
// spd_open has memory leaks which are hard to suppress.
// http://crbug.com/317360
ANNOTATE_SCOPED_MEMORY_LEAK;
conn_ = libspeechd_loader_.spd_open(
"chrome", "extension_api", NULL, SPD_MODE_SINGLE);
}
if (!conn_)
return;
// Register callbacks for all events.
conn_->callback_begin =
conn_->callback_end =
conn_->callback_cancel =
conn_->callback_pause =
conn_->callback_resume =
&NotificationCallback;
conn_->callback_im = &IndexMarkCallback;
libspeechd_loader_.spd_set_notification_on(conn_, SPD_BEGIN);
libspeechd_loader_.spd_set_notification_on(conn_, SPD_END);
libspeechd_loader_.spd_set_notification_on(conn_, SPD_CANCEL);
libspeechd_loader_.spd_set_notification_on(conn_, SPD_PAUSE);
libspeechd_loader_.spd_set_notification_on(conn_, SPD_RESUME);
}
TtsPlatformImplLinux::~TtsPlatformImplLinux() {
base::AutoLock lock(initialization_lock_);
if (conn_) {
libspeechd_loader_.spd_close(conn_);
conn_ = NULL;
}
}
void TtsPlatformImplLinux::Reset() {
base::AutoLock lock(initialization_lock_);
if (conn_)
libspeechd_loader_.spd_close(conn_);
conn_ = libspeechd_loader_.spd_open(
"chrome", "extension_api", NULL, SPD_MODE_SINGLE);
}
bool TtsPlatformImplLinux::PlatformImplAvailable() {
if (!initialization_lock_.Try())
return false;
bool result = libspeechd_loader_.loaded() && (conn_ != NULL);
initialization_lock_.Release();
return result;
}
bool TtsPlatformImplLinux::Speak(
int utterance_id,
const std::string& utterance,
const std::string& lang,
const VoiceData& voice,
const UtteranceContinuousParameters& params) {
if (!PlatformImplAvailable()) {
error_ = kNotSupportedError;
return false;
}
// Speech dispatcher's speech params are around 3x at either limit.
float rate = params.rate > 3 ? 3 : params.rate;
rate = params.rate < 0.334 ? 0.334 : rate;
float pitch = params.pitch > 3 ? 3 : params.pitch;
pitch = params.pitch < 0.334 ? 0.334 : pitch;
std::map<std::string, SPDChromeVoice>::iterator it =
all_native_voices_->find(voice.name);
if (it != all_native_voices_->end()) {
libspeechd_loader_.spd_set_output_module(conn_, it->second.module.c_str());
libspeechd_loader_.spd_set_synthesis_voice(conn_, it->second.name.c_str());
}
// Map our multiplicative range to Speech Dispatcher's linear range.
// .334 = -100.
// 3 = 100.
libspeechd_loader_.spd_set_voice_rate(conn_, 100 * log10(rate) / log10(3));
libspeechd_loader_.spd_set_voice_pitch(conn_, 100 * log10(pitch) / log10(3));
utterance_ = utterance;
utterance_id_ = utterance_id;
if (libspeechd_loader_.spd_say(conn_, SPD_TEXT, utterance.c_str()) == -1) {
Reset();
return false;
}
return true;
}
bool TtsPlatformImplLinux::StopSpeaking() {
if (!PlatformImplAvailable())
return false;
if (libspeechd_loader_.spd_stop(conn_) == -1) {
Reset();
return false;
}
return true;
}
void TtsPlatformImplLinux::Pause() {
if (!PlatformImplAvailable())
return;
libspeechd_loader_.spd_pause(conn_);
}
void TtsPlatformImplLinux::Resume() {
if (!PlatformImplAvailable())
return;
libspeechd_loader_.spd_resume(conn_);
}
bool TtsPlatformImplLinux::IsSpeaking() {
return current_notification_ == SPD_EVENT_BEGIN;
}
void TtsPlatformImplLinux::GetVoices(
std::vector<VoiceData>* out_voices) {
if (!all_native_voices_.get()) {
all_native_voices_.reset(new std::map<std::string, SPDChromeVoice>());
char** modules = libspeechd_loader_.spd_list_modules(conn_);
if (!modules)
return;
for (int i = 0; modules[i]; i++) {
char* module = modules[i];
libspeechd_loader_.spd_set_output_module(conn_, module);
SPDVoice** native_voices =
libspeechd_loader_.spd_list_synthesis_voices(conn_);
if (!native_voices) {
free(module);
continue;
}
for (int j = 0; native_voices[j]; j++) {
SPDVoice* native_voice = native_voices[j];
SPDChromeVoice native_data;
native_data.name = native_voice->name;
native_data.module = module;
std::string key;
key.append(native_data.name);
key.append(" ");
key.append(native_data.module);
all_native_voices_->insert(
std::pair<std::string, SPDChromeVoice>(key, native_data));
free(native_voices[j]);
}
free(modules[i]);
}
}
for (std::map<std::string, SPDChromeVoice>::iterator it =
all_native_voices_->begin();
it != all_native_voices_->end();
it++) {
out_voices->push_back(VoiceData());
VoiceData& voice = out_voices->back();
voice.native = true;
voice.name = it->first;
voice.events.insert(TTS_EVENT_START);
voice.events.insert(TTS_EVENT_END);
voice.events.insert(TTS_EVENT_CANCELLED);
voice.events.insert(TTS_EVENT_MARKER);
voice.events.insert(TTS_EVENT_PAUSE);
voice.events.insert(TTS_EVENT_RESUME);
}
}
void TtsPlatformImplLinux::OnSpeechEvent(SPDNotificationType type) {
TtsController* controller = TtsController::GetInstance();
switch (type) {
case SPD_EVENT_BEGIN:
controller->OnTtsEvent(utterance_id_, TTS_EVENT_START, 0, std::string());
break;
case SPD_EVENT_RESUME:
controller->OnTtsEvent(utterance_id_, TTS_EVENT_RESUME, 0, std::string());
break;
case SPD_EVENT_END:
controller->OnTtsEvent(
utterance_id_, TTS_EVENT_END, utterance_.size(), std::string());
break;
case SPD_EVENT_PAUSE:
controller->OnTtsEvent(
utterance_id_, TTS_EVENT_PAUSE, utterance_.size(), std::string());
break;
case SPD_EVENT_CANCEL:
controller->OnTtsEvent(
utterance_id_, TTS_EVENT_CANCELLED, 0, std::string());
break;
case SPD_EVENT_INDEX_MARK:
controller->OnTtsEvent(utterance_id_, TTS_EVENT_MARKER, 0, std::string());
break;
}
}
// static
void TtsPlatformImplLinux::NotificationCallback(
size_t msg_id, size_t client_id, SPDNotificationType type) {
// We run Speech Dispatcher in threaded mode, so these callbacks should always
// be in a separate thread.
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
current_notification_ = type;
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(&TtsPlatformImplLinux::OnSpeechEvent,
base::Unretained(TtsPlatformImplLinux::GetInstance()),
type));
}
}
// static
void TtsPlatformImplLinux::IndexMarkCallback(size_t msg_id,
size_t client_id,
SPDNotificationType state,
char* index_mark) {
// TODO(dtseng): index_mark appears to specify an index type supplied by a
// client. Need to explore how this is used before hooking it up with existing
// word, sentence events.
// We run Speech Dispatcher in threaded mode, so these callbacks should always
// be in a separate thread.
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
current_notification_ = state;
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&TtsPlatformImplLinux::OnSpeechEvent,
base::Unretained(TtsPlatformImplLinux::GetInstance()),
state));
}
}
// static
TtsPlatformImplLinux* TtsPlatformImplLinux::GetInstance() {
return Singleton<TtsPlatformImplLinux,
LeakySingletonTraits<TtsPlatformImplLinux> >::get();
}
// static
TtsPlatformImpl* TtsPlatformImpl::GetInstance() {
return TtsPlatformImplLinux::GetInstance();
}

View File

@@ -0,0 +1,352 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include "base/mac/scoped_nsobject.h"
#include "base/memory/singleton.h"
#include "base/strings/sys_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/speech/tts_controller.h"
#include "chrome/browser/speech/tts_platform.h"
#import <Cocoa/Cocoa.h>
class TtsPlatformImplMac;
@interface ChromeTtsDelegate : NSObject <NSSpeechSynthesizerDelegate> {
@private
TtsPlatformImplMac* ttsImplMac_; // weak.
}
- (id)initWithPlatformImplMac:(TtsPlatformImplMac*)ttsImplMac;
@end
// Subclass of NSSpeechSynthesizer that takes an utterance
// string on initialization, retains it and only allows it
// to be spoken once.
//
// We construct a new NSSpeechSynthesizer for each utterance, for
// two reasons:
// 1. To associate delegate callbacks with a particular utterance,
// without assuming anything undocumented about the protocol.
// 2. To work around http://openradar.appspot.com/radar?id=2854403,
// where Nuance voices don't retain the utterance string and
// crash when trying to call willSpeakWord.
@interface SingleUseSpeechSynthesizer : NSSpeechSynthesizer {
@private
base::scoped_nsobject<NSString> utterance_;
bool didSpeak_;
}
- (id)initWithUtterance:(NSString*)utterance;
- (bool)startSpeakingRetainedUtterance;
- (bool)startSpeakingString:(NSString*)utterance;
@end
class TtsPlatformImplMac : public TtsPlatformImpl {
public:
virtual bool PlatformImplAvailable() OVERRIDE {
return true;
}
virtual bool Speak(
int utterance_id,
const std::string& utterance,
const std::string& lang,
const VoiceData& voice,
const UtteranceContinuousParameters& params) OVERRIDE;
virtual bool StopSpeaking() OVERRIDE;
virtual void Pause() OVERRIDE;
virtual void Resume() OVERRIDE;
virtual bool IsSpeaking() OVERRIDE;
virtual void GetVoices(std::vector<VoiceData>* out_voices) OVERRIDE;
// Called by ChromeTtsDelegate when we get a callback from the
// native speech engine.
void OnSpeechEvent(NSSpeechSynthesizer* sender,
TtsEventType event_type,
int char_index,
const std::string& error_message);
// Get the single instance of this class.
static TtsPlatformImplMac* GetInstance();
private:
TtsPlatformImplMac();
virtual ~TtsPlatformImplMac();
base::scoped_nsobject<SingleUseSpeechSynthesizer> speech_synthesizer_;
base::scoped_nsobject<ChromeTtsDelegate> delegate_;
int utterance_id_;
std::string utterance_;
int last_char_index_;
bool paused_;
friend struct DefaultSingletonTraits<TtsPlatformImplMac>;
DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplMac);
};
// static
TtsPlatformImpl* TtsPlatformImpl::GetInstance() {
return TtsPlatformImplMac::GetInstance();
}
bool TtsPlatformImplMac::Speak(
int utterance_id,
const std::string& utterance,
const std::string& lang,
const VoiceData& voice,
const UtteranceContinuousParameters& params) {
// TODO: convert SSML to SAPI xml. http://crbug.com/88072
utterance_ = utterance;
paused_ = false;
NSString* utterance_nsstring =
[NSString stringWithUTF8String:utterance_.c_str()];
// Deliberately construct a new speech synthesizer every time Speak is
// called, otherwise there's no way to know whether calls to the delegate
// apply to the current utterance or a previous utterance. In
// experimentation, the overhead of constructing and destructing a
// NSSpeechSynthesizer is minimal.
speech_synthesizer_.reset(
[[SingleUseSpeechSynthesizer alloc]
initWithUtterance:utterance_nsstring]);
[speech_synthesizer_ setDelegate:delegate_];
if (!voice.native_voice_identifier.empty()) {
NSString* native_voice_identifier =
[NSString stringWithUTF8String:voice.native_voice_identifier.c_str()];
[speech_synthesizer_ setVoice:native_voice_identifier];
}
utterance_id_ = utterance_id;
// TODO: support languages other than the default: crbug.com/88059
if (params.rate >= 0.0) {
// The TTS api defines rate via words per minute. Let 200 be the default.
[speech_synthesizer_
setObject:[NSNumber numberWithInt:params.rate * 200]
forProperty:NSSpeechRateProperty error:nil];
}
if (params.pitch >= 0.0) {
// The input is a float from 0.0 to 2.0, with 1.0 being the default.
// Get the default pitch for this voice and modulate it by 50% - 150%.
NSError* errorCode;
NSNumber* defaultPitchObj =
[speech_synthesizer_ objectForProperty:NSSpeechPitchBaseProperty
error:&errorCode];
int defaultPitch = defaultPitchObj ? [defaultPitchObj intValue] : 48;
int newPitch = static_cast<int>(defaultPitch * (0.5 * params.pitch + 0.5));
[speech_synthesizer_
setObject:[NSNumber numberWithInt:newPitch]
forProperty:NSSpeechPitchBaseProperty error:nil];
}
if (params.volume >= 0.0) {
[speech_synthesizer_
setObject: [NSNumber numberWithFloat:params.volume]
forProperty:NSSpeechVolumeProperty error:nil];
}
bool success = [speech_synthesizer_ startSpeakingRetainedUtterance];
if (success) {
TtsController* controller = TtsController::GetInstance();
controller->OnTtsEvent(utterance_id_, TTS_EVENT_START, 0, "");
}
return success;
}
bool TtsPlatformImplMac::StopSpeaking() {
if (speech_synthesizer_.get()) {
[speech_synthesizer_ stopSpeaking];
speech_synthesizer_.reset(nil);
}
paused_ = false;
return true;
}
void TtsPlatformImplMac::Pause() {
if (speech_synthesizer_.get() && utterance_id_ && !paused_) {
[speech_synthesizer_ pauseSpeakingAtBoundary:NSSpeechImmediateBoundary];
paused_ = true;
TtsController::GetInstance()->OnTtsEvent(
utterance_id_, TTS_EVENT_PAUSE, last_char_index_, "");
}
}
void TtsPlatformImplMac::Resume() {
if (speech_synthesizer_.get() && utterance_id_ && paused_) {
[speech_synthesizer_ continueSpeaking];
paused_ = false;
TtsController::GetInstance()->OnTtsEvent(
utterance_id_, TTS_EVENT_RESUME, last_char_index_, "");
}
}
bool TtsPlatformImplMac::IsSpeaking() {
if (speech_synthesizer_)
return [speech_synthesizer_ isSpeaking];
return false;
}
void TtsPlatformImplMac::GetVoices(std::vector<VoiceData>* outVoices) {
NSArray* voices = [NSSpeechSynthesizer availableVoices];
// Create a new temporary array of the available voices with
// the default voice first.
NSMutableArray* orderedVoices =
[NSMutableArray arrayWithCapacity:[voices count]];
NSString* defaultVoice = [NSSpeechSynthesizer defaultVoice];
if (defaultVoice) {
[orderedVoices addObject:defaultVoice];
}
for (NSString* voiceIdentifier in voices) {
if (![voiceIdentifier isEqualToString:defaultVoice])
[orderedVoices addObject:voiceIdentifier];
}
for (NSString* voiceIdentifier in orderedVoices) {
outVoices->push_back(VoiceData());
VoiceData& data = outVoices->back();
NSDictionary* attributes =
[NSSpeechSynthesizer attributesForVoice:voiceIdentifier];
NSString* name = [attributes objectForKey:NSVoiceName];
NSString* gender = [attributes objectForKey:NSVoiceGender];
NSString* localeIdentifier =
[attributes objectForKey:NSVoiceLocaleIdentifier];
data.native = true;
data.native_voice_identifier = base::SysNSStringToUTF8(voiceIdentifier);
data.name = base::SysNSStringToUTF8(name);
NSDictionary* localeComponents =
[NSLocale componentsFromLocaleIdentifier:localeIdentifier];
NSString* language = [localeComponents objectForKey:NSLocaleLanguageCode];
NSString* country = [localeComponents objectForKey:NSLocaleCountryCode];
if (language && country) {
data.lang =
[[NSString stringWithFormat:@"%@-%@", language, country] UTF8String];
} else {
data.lang = base::SysNSStringToUTF8(language);
}
if ([gender isEqualToString:NSVoiceGenderMale])
data.gender = TTS_GENDER_MALE;
else if ([gender isEqualToString:NSVoiceGenderFemale])
data.gender = TTS_GENDER_FEMALE;
else
data.gender = TTS_GENDER_NONE;
data.events.insert(TTS_EVENT_START);
data.events.insert(TTS_EVENT_END);
data.events.insert(TTS_EVENT_WORD);
data.events.insert(TTS_EVENT_ERROR);
data.events.insert(TTS_EVENT_CANCELLED);
data.events.insert(TTS_EVENT_INTERRUPTED);
data.events.insert(TTS_EVENT_PAUSE);
data.events.insert(TTS_EVENT_RESUME);
}
}
void TtsPlatformImplMac::OnSpeechEvent(
NSSpeechSynthesizer* sender,
TtsEventType event_type,
int char_index,
const std::string& error_message) {
// Don't send events from an utterance that's already completed.
// This depends on the fact that we construct a new NSSpeechSynthesizer
// each time we call Speak.
if (sender != speech_synthesizer_.get())
return;
if (event_type == TTS_EVENT_END)
char_index = utterance_.size();
TtsController* controller = TtsController::GetInstance();
controller->OnTtsEvent(
utterance_id_, event_type, char_index, error_message);
last_char_index_ = char_index;
}
TtsPlatformImplMac::TtsPlatformImplMac() {
utterance_id_ = -1;
paused_ = false;
delegate_.reset([[ChromeTtsDelegate alloc] initWithPlatformImplMac:this]);
}
TtsPlatformImplMac::~TtsPlatformImplMac() {
}
// static
TtsPlatformImplMac* TtsPlatformImplMac::GetInstance() {
return Singleton<TtsPlatformImplMac>::get();
}
@implementation ChromeTtsDelegate
- (id)initWithPlatformImplMac:(TtsPlatformImplMac*)ttsImplMac {
if ((self = [super init])) {
ttsImplMac_ = ttsImplMac;
}
return self;
}
- (void)speechSynthesizer:(NSSpeechSynthesizer*)sender
didFinishSpeaking:(BOOL)finished_speaking {
ttsImplMac_->OnSpeechEvent(sender, TTS_EVENT_END, 0, "");
}
- (void)speechSynthesizer:(NSSpeechSynthesizer*)sender
willSpeakWord:(NSRange)character_range
ofString:(NSString*)string {
ttsImplMac_->OnSpeechEvent(sender, TTS_EVENT_WORD,
character_range.location, "");
}
- (void)speechSynthesizer:(NSSpeechSynthesizer*)sender
didEncounterErrorAtIndex:(NSUInteger)character_index
ofString:(NSString*)string
message:(NSString*)message {
std::string message_utf8 = base::SysNSStringToUTF8(message);
ttsImplMac_->OnSpeechEvent(sender, TTS_EVENT_ERROR, character_index,
message_utf8);
}
@end
@implementation SingleUseSpeechSynthesizer
- (id)initWithUtterance:(NSString*)utterance {
self = [super init];
if (self) {
utterance_.reset([utterance retain]);
didSpeak_ = false;
}
return self;
}
- (bool)startSpeakingRetainedUtterance {
CHECK(!didSpeak_);
CHECK(utterance_);
didSpeak_ = true;
return [super startSpeakingString:utterance_];
}
- (bool)startSpeakingString:(NSString*)utterance {
CHECK(false);
return false;
}
@end

View File

@@ -0,0 +1,176 @@
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/speech/tts_message_filter.h"
#include "base/bind.h"
#include "base/logging.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/render_process_host.h"
using content::BrowserThread;
TtsMessageFilter::TtsMessageFilter(int render_process_id,
content::BrowserContext* browser_context)
: BrowserMessageFilter(TtsMsgStart),
render_process_id_(render_process_id),
browser_context_(browser_context),
weak_ptr_factory_(this) {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
TtsController::GetInstance()->AddVoicesChangedDelegate(this);
// Balanced in OnChannelClosingInUIThread() to keep the ref-count be non-zero
// until all WeakPtr's are invalidated.
AddRef();
}
void TtsMessageFilter::OverrideThreadForMessage(
const IPC::Message& message, BrowserThread::ID* thread) {
switch (message.type()) {
case TtsHostMsg_InitializeVoiceList::ID:
case TtsHostMsg_Speak::ID:
case TtsHostMsg_Pause::ID:
case TtsHostMsg_Resume::ID:
case TtsHostMsg_Cancel::ID:
*thread = BrowserThread::UI;
break;
}
}
bool TtsMessageFilter::OnMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(TtsMessageFilter, message)
IPC_MESSAGE_HANDLER(TtsHostMsg_InitializeVoiceList, OnInitializeVoiceList)
IPC_MESSAGE_HANDLER(TtsHostMsg_Speak, OnSpeak)
IPC_MESSAGE_HANDLER(TtsHostMsg_Pause, OnPause)
IPC_MESSAGE_HANDLER(TtsHostMsg_Resume, OnResume)
IPC_MESSAGE_HANDLER(TtsHostMsg_Cancel, OnCancel)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void TtsMessageFilter::OnChannelClosing() {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&TtsMessageFilter::OnChannelClosingInUIThread, this));
}
void TtsMessageFilter::OnDestruct() const {
BrowserThread::DeleteOnUIThread::Destruct(this);
}
void TtsMessageFilter::OnInitializeVoiceList() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
TtsController* tts_controller = TtsController::GetInstance();
std::vector<VoiceData> voices;
tts_controller->GetVoices(browser_context_, &voices);
std::vector<TtsVoice> out_voices;
out_voices.resize(voices.size());
for (size_t i = 0; i < voices.size(); ++i) {
TtsVoice& out_voice = out_voices[i];
out_voice.voice_uri = voices[i].name;
out_voice.name = voices[i].name;
out_voice.lang = voices[i].lang;
out_voice.local_service = !voices[i].remote;
out_voice.is_default = (i == 0);
}
Send(new TtsMsg_SetVoiceList(out_voices));
}
void TtsMessageFilter::OnSpeak(const TtsUtteranceRequest& request) {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
scoped_ptr<Utterance> utterance(new Utterance(browser_context_));
utterance->set_src_id(request.id);
utterance->set_text(request.text);
utterance->set_lang(request.lang);
utterance->set_voice_name(request.voice);
utterance->set_can_enqueue(true);
UtteranceContinuousParameters params;
params.rate = request.rate;
params.pitch = request.pitch;
params.volume = request.volume;
utterance->set_continuous_parameters(params);
utterance->set_event_delegate(weak_ptr_factory_.GetWeakPtr());
TtsController::GetInstance()->SpeakOrEnqueue(utterance.release());
}
void TtsMessageFilter::OnPause() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
TtsController::GetInstance()->Pause();
}
void TtsMessageFilter::OnResume() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
TtsController::GetInstance()->Resume();
}
void TtsMessageFilter::OnCancel() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
TtsController::GetInstance()->Stop();
}
void TtsMessageFilter::OnTtsEvent(Utterance* utterance,
TtsEventType event_type,
int char_index,
const std::string& error_message) {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
switch (event_type) {
case TTS_EVENT_START:
Send(new TtsMsg_DidStartSpeaking(utterance->src_id()));
break;
case TTS_EVENT_END:
Send(new TtsMsg_DidFinishSpeaking(utterance->src_id()));
break;
case TTS_EVENT_WORD:
Send(new TtsMsg_WordBoundary(utterance->src_id(), char_index));
break;
case TTS_EVENT_SENTENCE:
Send(new TtsMsg_SentenceBoundary(utterance->src_id(), char_index));
break;
case TTS_EVENT_MARKER:
Send(new TtsMsg_MarkerEvent(utterance->src_id(), char_index));
break;
case TTS_EVENT_INTERRUPTED:
Send(new TtsMsg_WasInterrupted(utterance->src_id()));
break;
case TTS_EVENT_CANCELLED:
Send(new TtsMsg_WasCancelled(utterance->src_id()));
break;
case TTS_EVENT_ERROR:
Send(new TtsMsg_SpeakingErrorOccurred(
utterance->src_id(), error_message));
break;
case TTS_EVENT_PAUSE:
Send(new TtsMsg_DidPauseSpeaking(utterance->src_id()));
break;
case TTS_EVENT_RESUME:
Send(new TtsMsg_DidResumeSpeaking(utterance->src_id()));
break;
}
}
void TtsMessageFilter::OnVoicesChanged() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
OnInitializeVoiceList();
}
void TtsMessageFilter::OnChannelClosingInUIThread() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
TtsController::GetInstance()->RemoveVoicesChangedDelegate(this);
weak_ptr_factory_.InvalidateWeakPtrs();
Release(); // Balanced in TtsMessageFilter().
}
TtsMessageFilter::~TtsMessageFilter() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!weak_ptr_factory_.HasWeakPtrs());
TtsController::GetInstance()->RemoveVoicesChangedDelegate(this);
}

View File

@@ -0,0 +1,64 @@
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_SPEECH_TTS_MESSAGE_FILTER_H_
#define CHROME_BROWSER_SPEECH_TTS_MESSAGE_FILTER_H_
#include "base/memory/weak_ptr.h"
#include "chrome/browser/speech/tts_controller.h"
#include "chrome/common/tts_messages.h"
#include "content/public/browser/browser_message_filter.h"
namespace content {
class BrowserContext;
}
class TtsMessageFilter
: public content::BrowserMessageFilter,
public UtteranceEventDelegate,
public VoicesChangedDelegate {
public:
explicit TtsMessageFilter(int render_process_id,
content::BrowserContext* browser_context);
// content::BrowserMessageFilter implementation.
virtual void OverrideThreadForMessage(
const IPC::Message& message,
content::BrowserThread::ID* thread) OVERRIDE;
virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
virtual void OnChannelClosing() OVERRIDE;
virtual void OnDestruct() const OVERRIDE;
// UtteranceEventDelegate implementation.
virtual void OnTtsEvent(Utterance* utterance,
TtsEventType event_type,
int char_index,
const std::string& error_message) OVERRIDE;
// VoicesChangedDelegate implementation.
virtual void OnVoicesChanged() OVERRIDE;
private:
friend class content::BrowserThread;
friend class base::DeleteHelper<TtsMessageFilter>;
virtual ~TtsMessageFilter();
void OnInitializeVoiceList();
void OnSpeak(const TtsUtteranceRequest& utterance);
void OnPause();
void OnResume();
void OnCancel();
void OnChannelClosingInUIThread();
int render_process_id_;
content::BrowserContext* browser_context_;
base::WeakPtrFactory<TtsMessageFilter> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(TtsMessageFilter);
};
#endif // CHROME_BROWSER_SPEECH_TTS_MESSAGE_FILTER_H_

View File

@@ -0,0 +1,28 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/speech/tts_platform.h"
#include <string>
bool TtsPlatformImpl::LoadBuiltInTtsExtension(
content::BrowserContext* browser_context) {
return false;
}
std::string TtsPlatformImpl::error() {
return error_;
}
void TtsPlatformImpl::clear_error() {
error_ = std::string();
}
void TtsPlatformImpl::set_error(const std::string& error) {
error_ = error;
}
void TtsPlatformImpl::WillSpeakUtteranceWithVoice(const Utterance* utterance,
const VoiceData& voice_data) {
}

View File

@@ -0,0 +1,81 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_SPEECH_TTS_PLATFORM_H_
#define CHROME_BROWSER_SPEECH_TTS_PLATFORM_H_
#include <string>
#include "chrome/browser/speech/tts_controller.h"
// Abstract class that defines the native platform TTS interface,
// subclassed by specific implementations on Win, Mac, etc.
class TtsPlatformImpl {
public:
static TtsPlatformImpl* GetInstance();
// Returns true if this platform implementation is supported and available.
virtual bool PlatformImplAvailable() = 0;
// Some platforms may provide a built-in TTS extension. Returns true
// if the extension was not previously loaded and is now loading, and
// false if it's already loaded or if there's no extension to load.
// Will call TtsController::RetrySpeakingQueuedUtterances when
// the extension finishes loading.
virtual bool LoadBuiltInTtsExtension(
content::BrowserContext* browser_context);
// Speak the given utterance with the given parameters if possible,
// and return true on success. Utterance will always be nonempty.
// If rate, pitch, or volume are -1.0, they will be ignored.
//
// The TtsController will only try to speak one utterance at
// a time. If it wants to interrupt speech, it will always call Stop
// before speaking again.
virtual bool Speak(
int utterance_id,
const std::string& utterance,
const std::string& lang,
const VoiceData& voice,
const UtteranceContinuousParameters& params) = 0;
// Stop speaking immediately and return true on success.
virtual bool StopSpeaking() = 0;
// Returns whether any speech is on going.
virtual bool IsSpeaking() = 0;
// Append information about voices provided by this platform implementation
// to |out_voices|.
virtual void GetVoices(std::vector<VoiceData>* out_voices) = 0;
// Pause the current utterance, if any, until a call to Resume,
// Speak, or StopSpeaking.
virtual void Pause() = 0;
// Resume speaking the current utterance, if it was paused.
virtual void Resume() = 0;
// Allows the platform to monitor speech commands and the voices used
// for each one.
virtual void WillSpeakUtteranceWithVoice(const Utterance* utterance,
const VoiceData& voice_data);
virtual std::string error();
virtual void clear_error();
virtual void set_error(const std::string& error);
protected:
TtsPlatformImpl() {}
// On some platforms this may be a leaky singleton - do not rely on the
// destructor being called! http://crbug.com/122026
virtual ~TtsPlatformImpl() {}
std::string error_;
DISALLOW_COPY_AND_ASSIGN(TtsPlatformImpl);
};
#endif // CHROME_BROWSER_SPEECH_TTS_PLATFORM_H_

View File

@@ -0,0 +1,257 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <math.h>
#include <sapi.h>
#include "base/memory/singleton.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "base/win/scoped_comptr.h"
#include "chrome/browser/speech/tts_controller.h"
#include "chrome/browser/speech/tts_platform.h"
class TtsPlatformImplWin : public TtsPlatformImpl {
public:
virtual bool PlatformImplAvailable() {
return true;
}
virtual bool Speak(
int utterance_id,
const std::string& utterance,
const std::string& lang,
const VoiceData& voice,
const UtteranceContinuousParameters& params);
virtual bool StopSpeaking();
virtual void Pause();
virtual void Resume();
virtual bool IsSpeaking();
virtual void GetVoices(std::vector<VoiceData>* out_voices) OVERRIDE;
// Get the single instance of this class.
static TtsPlatformImplWin* GetInstance();
static void __stdcall SpeechEventCallback(WPARAM w_param, LPARAM l_param);
private:
TtsPlatformImplWin();
virtual ~TtsPlatformImplWin() {}
void OnSpeechEvent();
base::win::ScopedComPtr<ISpVoice> speech_synthesizer_;
// These apply to the current utterance only.
std::wstring utterance_;
int utterance_id_;
int prefix_len_;
ULONG stream_number_;
int char_position_;
bool paused_;
friend struct DefaultSingletonTraits<TtsPlatformImplWin>;
DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplWin);
};
// static
TtsPlatformImpl* TtsPlatformImpl::GetInstance() {
return TtsPlatformImplWin::GetInstance();
}
bool TtsPlatformImplWin::Speak(
int utterance_id,
const std::string& src_utterance,
const std::string& lang,
const VoiceData& voice,
const UtteranceContinuousParameters& params) {
std::wstring prefix;
std::wstring suffix;
if (!speech_synthesizer_.get())
return false;
// TODO(dmazzoni): support languages other than the default: crbug.com/88059
if (params.rate >= 0.0) {
// Map our multiplicative range of 0.1x to 10.0x onto Microsoft's
// linear range of -10 to 10:
// 0.1 -> -10
// 1.0 -> 0
// 10.0 -> 10
speech_synthesizer_->SetRate(static_cast<int32>(10 * log10(params.rate)));
}
if (params.pitch >= 0.0) {
// The TTS api allows a range of -10 to 10 for speech pitch.
// TODO(dtseng): cleanup if we ever use any other properties that
// require xml.
std::wstring pitch_value =
base::IntToString16(static_cast<int>(params.pitch * 10 - 10));
prefix = L"<pitch absmiddle=\"" + pitch_value + L"\">";
suffix = L"</pitch>";
}
if (params.volume >= 0.0) {
// The TTS api allows a range of 0 to 100 for speech volume.
speech_synthesizer_->SetVolume(static_cast<uint16>(params.volume * 100));
}
// TODO(dmazzoni): convert SSML to SAPI xml. http://crbug.com/88072
utterance_ = base::UTF8ToWide(src_utterance);
utterance_id_ = utterance_id;
char_position_ = 0;
std::wstring merged_utterance = prefix + utterance_ + suffix;
prefix_len_ = prefix.size();
HRESULT result = speech_synthesizer_->Speak(
merged_utterance.c_str(),
SPF_ASYNC,
&stream_number_);
return (result == S_OK);
}
bool TtsPlatformImplWin::StopSpeaking() {
if (speech_synthesizer_.get()) {
// Clear the stream number so that any further events relating to this
// utterance are ignored.
stream_number_ = 0;
if (IsSpeaking()) {
// Stop speech by speaking the empty string with the purge flag.
speech_synthesizer_->Speak(L"", SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL);
}
if (paused_) {
speech_synthesizer_->Resume();
paused_ = false;
}
}
return true;
}
void TtsPlatformImplWin::Pause() {
if (speech_synthesizer_.get() && utterance_id_ && !paused_) {
speech_synthesizer_->Pause();
paused_ = true;
TtsController::GetInstance()->OnTtsEvent(
utterance_id_, TTS_EVENT_PAUSE, char_position_, "");
}
}
void TtsPlatformImplWin::Resume() {
if (speech_synthesizer_.get() && utterance_id_ && paused_) {
speech_synthesizer_->Resume();
paused_ = false;
TtsController::GetInstance()->OnTtsEvent(
utterance_id_, TTS_EVENT_RESUME, char_position_, "");
}
}
bool TtsPlatformImplWin::IsSpeaking() {
if (speech_synthesizer_.get()) {
SPVOICESTATUS status;
HRESULT result = speech_synthesizer_->GetStatus(&status, NULL);
if (result == S_OK) {
if (status.dwRunningState == 0 || // 0 == waiting to speak
status.dwRunningState == SPRS_IS_SPEAKING) {
return true;
}
}
}
return false;
}
void TtsPlatformImplWin::GetVoices(
std::vector<VoiceData>* out_voices) {
// TODO: get all voices, not just default voice.
// http://crbug.com/88059
out_voices->push_back(VoiceData());
VoiceData& voice = out_voices->back();
voice.native = true;
voice.name = "native";
voice.events.insert(TTS_EVENT_START);
voice.events.insert(TTS_EVENT_END);
voice.events.insert(TTS_EVENT_MARKER);
voice.events.insert(TTS_EVENT_WORD);
voice.events.insert(TTS_EVENT_SENTENCE);
voice.events.insert(TTS_EVENT_PAUSE);
voice.events.insert(TTS_EVENT_RESUME);
}
void TtsPlatformImplWin::OnSpeechEvent() {
TtsController* controller = TtsController::GetInstance();
SPEVENT event;
while (S_OK == speech_synthesizer_->GetEvents(1, &event, NULL)) {
if (event.ulStreamNum != stream_number_)
continue;
switch (event.eEventId) {
case SPEI_START_INPUT_STREAM:
controller->OnTtsEvent(
utterance_id_, TTS_EVENT_START, 0, std::string());
break;
case SPEI_END_INPUT_STREAM:
char_position_ = utterance_.size();
controller->OnTtsEvent(
utterance_id_, TTS_EVENT_END, char_position_, std::string());
break;
case SPEI_TTS_BOOKMARK:
controller->OnTtsEvent(
utterance_id_, TTS_EVENT_MARKER, char_position_, std::string());
break;
case SPEI_WORD_BOUNDARY:
char_position_ = static_cast<ULONG>(event.lParam) - prefix_len_;
controller->OnTtsEvent(
utterance_id_, TTS_EVENT_WORD, char_position_,
std::string());
break;
case SPEI_SENTENCE_BOUNDARY:
char_position_ = static_cast<ULONG>(event.lParam) - prefix_len_;
controller->OnTtsEvent(
utterance_id_, TTS_EVENT_SENTENCE, char_position_,
std::string());
break;
}
}
}
TtsPlatformImplWin::TtsPlatformImplWin()
: utterance_id_(0),
prefix_len_(0),
stream_number_(0),
char_position_(0),
paused_(false) {
speech_synthesizer_.CreateInstance(CLSID_SpVoice);
if (speech_synthesizer_.get()) {
ULONGLONG event_mask =
SPFEI(SPEI_START_INPUT_STREAM) |
SPFEI(SPEI_TTS_BOOKMARK) |
SPFEI(SPEI_WORD_BOUNDARY) |
SPFEI(SPEI_SENTENCE_BOUNDARY) |
SPFEI(SPEI_END_INPUT_STREAM);
speech_synthesizer_->SetInterest(event_mask, event_mask);
speech_synthesizer_->SetNotifyCallbackFunction(
TtsPlatformImplWin::SpeechEventCallback, 0, 0);
}
}
// static
TtsPlatformImplWin* TtsPlatformImplWin::GetInstance() {
return Singleton<TtsPlatformImplWin,
LeakySingletonTraits<TtsPlatformImplWin> >::get();
}
// static
void TtsPlatformImplWin::SpeechEventCallback(
WPARAM w_param, LPARAM l_param) {
GetInstance()->OnSpeechEvent();
}

View File

@@ -0,0 +1,69 @@
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Multiply-included message file, hence no include guard.
#include <vector>
#include "chrome/common/tts_utterance_request.h"
#include "ipc/ipc_message_macros.h"
#include "ipc/ipc_param_traits.h"
#define IPC_MESSAGE_START TtsMsgStart
IPC_STRUCT_TRAITS_BEGIN(TtsUtteranceRequest)
IPC_STRUCT_TRAITS_MEMBER(id)
IPC_STRUCT_TRAITS_MEMBER(text)
IPC_STRUCT_TRAITS_MEMBER(lang)
IPC_STRUCT_TRAITS_MEMBER(voice)
IPC_STRUCT_TRAITS_MEMBER(volume)
IPC_STRUCT_TRAITS_MEMBER(rate)
IPC_STRUCT_TRAITS_MEMBER(pitch)
IPC_STRUCT_TRAITS_END()
IPC_STRUCT_TRAITS_BEGIN(TtsVoice)
IPC_STRUCT_TRAITS_MEMBER(voice_uri)
IPC_STRUCT_TRAITS_MEMBER(name)
IPC_STRUCT_TRAITS_MEMBER(lang)
IPC_STRUCT_TRAITS_MEMBER(local_service)
IPC_STRUCT_TRAITS_MEMBER(is_default)
IPC_STRUCT_TRAITS_END()
// Renderer -> Browser messages.
IPC_MESSAGE_CONTROL0(TtsHostMsg_InitializeVoiceList)
IPC_MESSAGE_CONTROL1(TtsHostMsg_Speak,
TtsUtteranceRequest)
IPC_MESSAGE_CONTROL0(TtsHostMsg_Pause)
IPC_MESSAGE_CONTROL0(TtsHostMsg_Resume)
IPC_MESSAGE_CONTROL0(TtsHostMsg_Cancel)
// Browser -> Renderer messages.
IPC_MESSAGE_CONTROL1(TtsMsg_SetVoiceList,
std::vector<TtsVoice>)
IPC_MESSAGE_CONTROL1(TtsMsg_DidStartSpeaking,
int /* utterance id */)
IPC_MESSAGE_CONTROL1(TtsMsg_DidFinishSpeaking,
int /* utterance id */)
IPC_MESSAGE_CONTROL1(TtsMsg_DidPauseSpeaking,
int /* utterance id */)
IPC_MESSAGE_CONTROL1(TtsMsg_DidResumeSpeaking,
int /* utterance id */)
IPC_MESSAGE_CONTROL2(TtsMsg_WordBoundary,
int /* utterance id */,
int /* char index */)
IPC_MESSAGE_CONTROL2(TtsMsg_SentenceBoundary,
int /* utterance id */,
int /* char index */)
IPC_MESSAGE_CONTROL2(TtsMsg_MarkerEvent,
int /* utterance id */,
int /* char index */)
IPC_MESSAGE_CONTROL1(TtsMsg_WasInterrupted,
int /* utterance id */)
IPC_MESSAGE_CONTROL1(TtsMsg_WasCancelled,
int /* utterance id */)
IPC_MESSAGE_CONTROL2(TtsMsg_SpeakingErrorOccurred,
int /* utterance id */,
std::string /* error message */)

View File

@@ -0,0 +1,30 @@
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/common/tts_utterance_request.h"
TtsUtteranceRequest::TtsUtteranceRequest()
: id(0),
volume(1.0),
rate(1.0),
pitch(1.0) {
}
TtsUtteranceRequest::~TtsUtteranceRequest() {
}
TtsVoice::TtsVoice()
: local_service(true),
is_default(false) {
}
TtsVoice::~TtsVoice() {
}
TtsUtteranceResponse::TtsUtteranceResponse()
: id(0) {
}
TtsUtteranceResponse::~TtsUtteranceResponse() {
}

View File

@@ -0,0 +1,44 @@
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_COMMON_TTS_UTTERANCE_REQUEST_H_
#define CHROME_COMMON_TTS_UTTERANCE_REQUEST_H_
#include <vector>
#include "base/basictypes.h"
#include "base/strings/string16.h"
struct TtsUtteranceRequest {
TtsUtteranceRequest();
~TtsUtteranceRequest();
int id;
std::string text;
std::string lang;
std::string voice;
float volume;
float rate;
float pitch;
};
struct TtsVoice {
TtsVoice();
~TtsVoice();
std::string voice_uri;
std::string name;
std::string lang;
bool local_service;
bool is_default;
};
struct TtsUtteranceResponse {
TtsUtteranceResponse();
~TtsUtteranceResponse();
int id;
};
#endif // CHROME_COMMON_TTS_UTTERANCE_REQUEST_H_

View File

@@ -17,10 +17,10 @@
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/common/print_messages.h"
#include "content/public/common/web_preferences.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_thread.h"
#include "content/public/renderer/render_view.h"
#include "content/public/renderer/web_preferences.h"
#include "net/base/escape.h"
#include "printing/metafile.h"
#include "printing/metafile_impl.h"
@@ -42,7 +42,8 @@
#include "third_party/WebKit/public/web/WebView.h"
#include "third_party/WebKit/public/web/WebViewClient.h"
#include "ui/base/resource/resource_bundle.h"
#include "webkit/common/webpreferences.h"
using content::WebPreferences;
namespace printing {
@@ -547,7 +548,7 @@ void PrepareFrameAndViewForPrint::CopySelection(
blink::WebView* web_view = blink::WebView::create(this);
owns_web_view_ = true;
content::ApplyWebPreferences(prefs, web_view);
content::RenderView::ApplyWebPreferences(prefs, web_view);
web_view->setMainFrame(blink::WebLocalFrame::create(this));
frame_.Reset(web_view->mainFrame()->toWebLocalFrame());
node_to_print_.reset();

View File

@@ -0,0 +1,200 @@
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/renderer/tts_dispatcher.h"
#include "base/basictypes.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/common/tts_messages.h"
#include "chrome/common/tts_utterance_request.h"
#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"
#include "third_party/WebKit/public/platform/WebVector.h"
using content::RenderThread;
using blink::WebSpeechSynthesizerClient;
using blink::WebSpeechSynthesisUtterance;
using blink::WebSpeechSynthesisVoice;
using blink::WebString;
using blink::WebVector;
int TtsDispatcher::next_utterance_id_ = 1;
TtsDispatcher::TtsDispatcher(WebSpeechSynthesizerClient* client)
: synthesizer_client_(client) {
RenderThread::Get()->AddObserver(this);
}
TtsDispatcher::~TtsDispatcher() {
RenderThread::Get()->RemoveObserver(this);
}
bool TtsDispatcher::OnControlMessageReceived(const IPC::Message& message) {
IPC_BEGIN_MESSAGE_MAP(TtsDispatcher, message)
IPC_MESSAGE_HANDLER(TtsMsg_SetVoiceList, OnSetVoiceList)
IPC_MESSAGE_HANDLER(TtsMsg_DidStartSpeaking, OnDidStartSpeaking)
IPC_MESSAGE_HANDLER(TtsMsg_DidFinishSpeaking, OnDidFinishSpeaking)
IPC_MESSAGE_HANDLER(TtsMsg_DidPauseSpeaking, OnDidPauseSpeaking)
IPC_MESSAGE_HANDLER(TtsMsg_DidResumeSpeaking, OnDidResumeSpeaking)
IPC_MESSAGE_HANDLER(TtsMsg_WordBoundary, OnWordBoundary)
IPC_MESSAGE_HANDLER(TtsMsg_SentenceBoundary, OnSentenceBoundary)
IPC_MESSAGE_HANDLER(TtsMsg_MarkerEvent, OnMarkerEvent)
IPC_MESSAGE_HANDLER(TtsMsg_WasInterrupted, OnWasInterrupted)
IPC_MESSAGE_HANDLER(TtsMsg_WasCancelled, OnWasCancelled)
IPC_MESSAGE_HANDLER(TtsMsg_SpeakingErrorOccurred, OnSpeakingErrorOccurred)
IPC_END_MESSAGE_MAP()
// Always return false because there may be multiple TtsDispatchers
// and we want them all to have a chance to handle this message.
return false;
}
void TtsDispatcher::updateVoiceList() {
RenderThread::Get()->Send(new TtsHostMsg_InitializeVoiceList());
}
void TtsDispatcher::speak(const WebSpeechSynthesisUtterance& web_utterance) {
int id = next_utterance_id_++;
utterance_id_map_[id] = web_utterance;
TtsUtteranceRequest utterance;
utterance.id = id;
utterance.text = web_utterance.text().utf8();
utterance.lang = web_utterance.lang().utf8();
utterance.voice = web_utterance.voice().utf8();
utterance.volume = web_utterance.volume();
utterance.rate = web_utterance.rate();
utterance.pitch = web_utterance.pitch();
RenderThread::Get()->Send(new TtsHostMsg_Speak(utterance));
}
void TtsDispatcher::pause() {
RenderThread::Get()->Send(new TtsHostMsg_Pause());
}
void TtsDispatcher::resume() {
RenderThread::Get()->Send(new TtsHostMsg_Resume());
}
void TtsDispatcher::cancel() {
RenderThread::Get()->Send(new TtsHostMsg_Cancel());
}
WebSpeechSynthesisUtterance TtsDispatcher::FindUtterance(int utterance_id) {
base::hash_map<int, WebSpeechSynthesisUtterance>::const_iterator iter =
utterance_id_map_.find(utterance_id);
if (iter == utterance_id_map_.end())
return WebSpeechSynthesisUtterance();
return iter->second;
}
void TtsDispatcher::OnSetVoiceList(const std::vector<TtsVoice>& voices) {
WebVector<WebSpeechSynthesisVoice> out_voices(voices.size());
for (size_t i = 0; i < voices.size(); ++i) {
out_voices[i] = WebSpeechSynthesisVoice();
out_voices[i].setVoiceURI(WebString::fromUTF8(voices[i].voice_uri));
out_voices[i].setName(WebString::fromUTF8(voices[i].name));
out_voices[i].setLanguage(WebString::fromUTF8(voices[i].lang));
out_voices[i].setIsLocalService(voices[i].local_service);
out_voices[i].setIsDefault(voices[i].is_default);
}
synthesizer_client_->setVoiceList(out_voices);
}
void TtsDispatcher::OnDidStartSpeaking(int utterance_id) {
if (utterance_id_map_.find(utterance_id) == utterance_id_map_.end())
return;
WebSpeechSynthesisUtterance utterance = FindUtterance(utterance_id);
if (utterance.isNull())
return;
synthesizer_client_->didStartSpeaking(utterance);
}
void TtsDispatcher::OnDidFinishSpeaking(int utterance_id) {
WebSpeechSynthesisUtterance utterance = FindUtterance(utterance_id);
if (utterance.isNull())
return;
synthesizer_client_->didFinishSpeaking(utterance);
utterance_id_map_.erase(utterance_id);
}
void TtsDispatcher::OnDidPauseSpeaking(int utterance_id) {
WebSpeechSynthesisUtterance utterance = FindUtterance(utterance_id);
if (utterance.isNull())
return;
synthesizer_client_->didPauseSpeaking(utterance);
}
void TtsDispatcher::OnDidResumeSpeaking(int utterance_id) {
WebSpeechSynthesisUtterance utterance = FindUtterance(utterance_id);
if (utterance.isNull())
return;
synthesizer_client_->didResumeSpeaking(utterance);
}
void TtsDispatcher::OnWordBoundary(int utterance_id, int char_index) {
CHECK(char_index >= 0);
WebSpeechSynthesisUtterance utterance = FindUtterance(utterance_id);
if (utterance.isNull())
return;
synthesizer_client_->wordBoundaryEventOccurred(
utterance, static_cast<unsigned>(char_index));
}
void TtsDispatcher::OnSentenceBoundary(int utterance_id, int char_index) {
CHECK(char_index >= 0);
WebSpeechSynthesisUtterance utterance = FindUtterance(utterance_id);
if (utterance.isNull())
return;
synthesizer_client_->sentenceBoundaryEventOccurred(
utterance, static_cast<unsigned>(char_index));
}
void TtsDispatcher::OnMarkerEvent(int utterance_id, int char_index) {
// Not supported yet.
}
void TtsDispatcher::OnWasInterrupted(int utterance_id) {
WebSpeechSynthesisUtterance utterance = FindUtterance(utterance_id);
if (utterance.isNull())
return;
// The web speech API doesn't support "interrupted".
synthesizer_client_->didFinishSpeaking(utterance);
utterance_id_map_.erase(utterance_id);
}
void TtsDispatcher::OnWasCancelled(int utterance_id) {
WebSpeechSynthesisUtterance utterance = FindUtterance(utterance_id);
if (utterance.isNull())
return;
// The web speech API doesn't support "cancelled".
synthesizer_client_->didFinishSpeaking(utterance);
utterance_id_map_.erase(utterance_id);
}
void TtsDispatcher::OnSpeakingErrorOccurred(int utterance_id,
const std::string& error_message) {
WebSpeechSynthesisUtterance utterance = FindUtterance(utterance_id);
if (utterance.isNull())
return;
// The web speech API doesn't support an error message.
synthesizer_client_->speakingErrorOccurred(utterance);
utterance_id_map_.erase(utterance_id);
}

View File

@@ -0,0 +1,78 @@
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_RENDERER_TTS_DISPATCHER_H_
#define CHROME_RENDERER_TTS_DISPATCHER_H_
#include <vector>
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/containers/hash_tables.h"
#include "content/public/renderer/render_process_observer.h"
#include "third_party/WebKit/public/platform/WebSpeechSynthesizer.h"
#include "third_party/WebKit/public/platform/WebSpeechSynthesizerClient.h"
namespace IPC {
class Message;
}
struct TtsVoice;
// TtsDispatcher is a delegate for methods used by Blink for speech synthesis
// APIs. It's the complement of TtsDispatcherHost (owned by RenderViewHost).
// Each TtsDispatcher is owned by the WebSpeechSynthesizerClient in Blink;
// it registers itself to listen to IPC upon construction and unregisters
// itself when deleted. There can be multiple TtsDispatchers alive at once,
// so each one routes IPC messages to its WebSpeechSynthesizerClient only if
// the utterance id (which is globally unique) matches.
class TtsDispatcher
: public blink::WebSpeechSynthesizer,
public content::RenderProcessObserver {
public:
explicit TtsDispatcher(blink::WebSpeechSynthesizerClient* client);
private:
virtual ~TtsDispatcher();
// RenderProcessObserver override.
virtual bool OnControlMessageReceived(const IPC::Message& message) OVERRIDE;
// blink::WebSpeechSynthesizer implementation.
virtual void updateVoiceList() OVERRIDE;
virtual void speak(const blink::WebSpeechSynthesisUtterance& utterance)
OVERRIDE;
virtual void pause() OVERRIDE;
virtual void resume() OVERRIDE;
virtual void cancel() OVERRIDE;
blink::WebSpeechSynthesisUtterance FindUtterance(int utterance_id);
void OnSetVoiceList(const std::vector<TtsVoice>& voices);
void OnDidStartSpeaking(int utterance_id);
void OnDidFinishSpeaking(int utterance_id);
void OnDidPauseSpeaking(int utterance_id);
void OnDidResumeSpeaking(int utterance_id);
void OnWordBoundary(int utterance_id, int char_index);
void OnSentenceBoundary(int utterance_id, int char_index);
void OnMarkerEvent(int utterance_id, int char_index);
void OnWasInterrupted(int utterance_id);
void OnWasCancelled(int utterance_id);
void OnSpeakingErrorOccurred(int utterance_id,
const std::string& error_message);
// The WebKit client class that we use to send events back to the JS world.
// Weak reference, this will be valid as long as this object exists.
blink::WebSpeechSynthesizerClient* synthesizer_client_;
// Next utterance id, used to map response IPCs to utterance objects.
static int next_utterance_id_;
// Map from id to utterance objects.
base::hash_map<int, blink::WebSpeechSynthesisUtterance> utterance_id_map_;
DISALLOW_COPY_AND_ASSIGN(TtsDispatcher);
};
#endif // CHROME_RENDERER_TTS_DISPATCHER_H_

View File

@@ -0,0 +1,231 @@
// This is generated file. Do not modify directly.
// Path to the code generator: tools/generate_library_loader/generate_library_loader.py .
#include "library_loaders/libspeechd.h"
#include <dlfcn.h>
// Put these sanity checks here so that they fire at most once
// (to avoid cluttering the build output).
#if !defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN) && !defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
#error neither LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN nor LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED defined
#endif
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN) && defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
#error both LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN and LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED defined
#endif
LibSpeechdLoader::LibSpeechdLoader() : loaded_(false) {
}
LibSpeechdLoader::~LibSpeechdLoader() {
CleanUp(loaded_);
}
bool LibSpeechdLoader::Load(const std::string& library_name) {
if (loaded_)
return false;
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
library_ = dlopen(library_name.c_str(), RTLD_LAZY);
if (!library_)
return false;
#endif
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
spd_open =
reinterpret_cast<typeof(this->spd_open)>(
dlsym(library_, "spd_open"));
#endif
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
spd_open = &::spd_open;
#endif
if (!spd_open) {
CleanUp(true);
return false;
}
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
spd_say =
reinterpret_cast<typeof(this->spd_say)>(
dlsym(library_, "spd_say"));
#endif
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
spd_say = &::spd_say;
#endif
if (!spd_say) {
CleanUp(true);
return false;
}
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
spd_stop =
reinterpret_cast<typeof(this->spd_stop)>(
dlsym(library_, "spd_stop"));
#endif
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
spd_stop = &::spd_stop;
#endif
if (!spd_stop) {
CleanUp(true);
return false;
}
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
spd_close =
reinterpret_cast<typeof(this->spd_close)>(
dlsym(library_, "spd_close"));
#endif
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
spd_close = &::spd_close;
#endif
if (!spd_close) {
CleanUp(true);
return false;
}
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
spd_pause =
reinterpret_cast<typeof(this->spd_pause)>(
dlsym(library_, "spd_pause"));
#endif
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
spd_pause = &::spd_pause;
#endif
if (!spd_pause) {
CleanUp(true);
return false;
}
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
spd_resume =
reinterpret_cast<typeof(this->spd_resume)>(
dlsym(library_, "spd_resume"));
#endif
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
spd_resume = &::spd_resume;
#endif
if (!spd_resume) {
CleanUp(true);
return false;
}
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
spd_set_notification_on =
reinterpret_cast<typeof(this->spd_set_notification_on)>(
dlsym(library_, "spd_set_notification_on"));
#endif
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
spd_set_notification_on = &::spd_set_notification_on;
#endif
if (!spd_set_notification_on) {
CleanUp(true);
return false;
}
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
spd_set_voice_rate =
reinterpret_cast<typeof(this->spd_set_voice_rate)>(
dlsym(library_, "spd_set_voice_rate"));
#endif
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
spd_set_voice_rate = &::spd_set_voice_rate;
#endif
if (!spd_set_voice_rate) {
CleanUp(true);
return false;
}
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
spd_set_voice_pitch =
reinterpret_cast<typeof(this->spd_set_voice_pitch)>(
dlsym(library_, "spd_set_voice_pitch"));
#endif
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
spd_set_voice_pitch = &::spd_set_voice_pitch;
#endif
if (!spd_set_voice_pitch) {
CleanUp(true);
return false;
}
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
spd_list_synthesis_voices =
reinterpret_cast<typeof(this->spd_list_synthesis_voices)>(
dlsym(library_, "spd_list_synthesis_voices"));
#endif
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
spd_list_synthesis_voices = &::spd_list_synthesis_voices;
#endif
if (!spd_list_synthesis_voices) {
CleanUp(true);
return false;
}
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
spd_set_synthesis_voice =
reinterpret_cast<typeof(this->spd_set_synthesis_voice)>(
dlsym(library_, "spd_set_synthesis_voice"));
#endif
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
spd_set_synthesis_voice = &::spd_set_synthesis_voice;
#endif
if (!spd_set_synthesis_voice) {
CleanUp(true);
return false;
}
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
spd_list_modules =
reinterpret_cast<typeof(this->spd_list_modules)>(
dlsym(library_, "spd_list_modules"));
#endif
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
spd_list_modules = &::spd_list_modules;
#endif
if (!spd_list_modules) {
CleanUp(true);
return false;
}
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
spd_set_output_module =
reinterpret_cast<typeof(this->spd_set_output_module)>(
dlsym(library_, "spd_set_output_module"));
#endif
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
spd_set_output_module = &::spd_set_output_module;
#endif
if (!spd_set_output_module) {
CleanUp(true);
return false;
}
loaded_ = true;
return true;
}
void LibSpeechdLoader::CleanUp(bool unload) {
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
if (unload) {
dlclose(library_);
library_ = NULL;
}
#endif
loaded_ = false;
spd_open = NULL;
spd_say = NULL;
spd_stop = NULL;
spd_close = NULL;
spd_pause = NULL;
spd_resume = NULL;
spd_set_notification_on = NULL;
spd_set_voice_rate = NULL;
spd_set_voice_pitch = NULL;
spd_list_synthesis_voices = NULL;
spd_set_synthesis_voice = NULL;
spd_list_modules = NULL;
spd_set_output_module = NULL;
}

View File

@@ -1,6 +1,7 @@
{
'variables': {
'clang': 0,
'openssl_no_asm': 1,
'conditions': [
['OS=="mac" or OS=="linux"', {
'clang': 1,

View File

@@ -2,8 +2,10 @@
* [Quick start](tutorial/quick-start.md)
* [Application distribution](tutorial/application-distribution.md)
* [Use native node modules](tutorial/use-native-node-modules.md)
* [Application packaging](tutorial/application-packaging.md)
* [Using native node modules](tutorial/using-native-node-modules.md)
* [Debugging browser process](tutorial/debugging-browser-process.md)
* [Using Selenium and WebDriver](tutorial/using-selenium-and-webdriver.md)
* [DevTools extension](tutorial/devtools-extension.md)
## API references

View File

@@ -45,6 +45,10 @@ terminating the application.
See description of `window-all-closed` for the differences between `will-quit`
and it.
## Event: quit
Emitted when application is quitting.
## Event: open-file
* `event` Event

View File

@@ -80,6 +80,14 @@ normal browsers, see [Web Security](web-security.md) for more.
should use `__dirname` or `process.resourcesPath` to join the paths to
make them absolute, using relative paths would make atom-shell search
under current working directory.
* `experimental-features` Boolean
* `experimental-canvas-features` Boolean
* `subpixel-font-scaling` Boolean
* `overlay-scrollbars` Boolean
* `overlay-fullscreen-video` Boolean
* `shared-worker` Boolean
* `direct-write` Boolean - Whether the DirectWrite font rendering system on
Windows is enabled
Creates a new `BrowserWindow` with native properties set by the `options`.
Usually you only need to set the `width` and `height`, other properties will
@@ -480,6 +488,19 @@ Sets the `menu` as the window top menu.
__Note:__ This API is not available on OS X.
### BrowserWindow.setProgressBar(progress)
* `progress` Double
Sets progress value in progress bar. Valid range is [0, 1.0].
Remove progress bar when progress < 0;
Change to indeterminate mode when progress > 1.
On Linux platform, only supports Unity desktop environment, you need to specify
the `*.desktop` file name to `desktopName` field in `package.json`. By default,
it will assume `app.getName().desktop`.
## Class: WebContents
A `WebContents` is responsible for rendering and controlling a web page.

View File

@@ -5,4 +5,5 @@ upstream node:
* `process.type` String - Process's type, can be `browser` or `renderer`.
* `process.versions['atom-shell']` String - Version of atom-shell.
* `process.versions['chrome']` String - Version of Chromium.
* `process.resourcesPath` String - Path to JavaScript source codes.

View File

@@ -48,6 +48,12 @@ Creates a new tray icon associated with the `image`.
Emitted when the tray icon is clicked.
### Event: 'double-clicked'
Emitted when the tray icon is double clicked.
This is only implmented on OS X.
### Tray.setImage(image)
* `image` [Image](image.md)
@@ -64,8 +70,28 @@ Sets the `image` associated with this tray icon when pressed.
* `toolTip` String
Sets the hover text for this tray icon.
### Tray.setTitle(title)
* `title` String
Sets the title displayed aside of the tray icon in the status bar.
This is only implmented on OS X.
### Tray.setHighlightMode(highlight)
* `highlight` Boolean
Sets whether the tray icon is highlighted when it is clicked.
This is only implmented on OS X.
### Tray.setContextMenu(menu)
* `menu` Menu
Set the context menu for this icon.
[event-emitter]: http://nodejs.org/api/events.html#events_class_events_eventemitter

Some files were not shown because too many files have changed in this diff Show More