diff --git a/AUTHORS b/AUTHORS index 9ec27ad472..0679037506 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,6 +5,7 @@ Roger Wang Zhao Cheng +Ma Donghao # Thanks for project logo design: @@ -18,3 +19,21 @@ Zhang Chaobin Michael Morrison William Towe Toni Lähdekorpi +Youngwook Kim +Zhao Zeyi (richardcypher) +Fabrice Weinberg +Lv Kaiyang +Lukas Benes +Lithare Emileit +Jefry Tedjokusumo +Wu Haojian +Bas Wegh +Joachim Bauch +Cong Liu +Eric Newport +Marco Fabbri +Daniel Braun +Chase Willden +Anton Khlynovskiy +Wu Yuehang +Rick Edgecombe diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..55766cde31 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,427 @@ +0.12.3 / 07-31-2015 +=================== +- Support Mac App Store with the 'macappstore' flavor +- [WIN] Screen.DesktopCaptureMonitor API: https://github.com/nwjs/nw.js/wiki/Screen#screendesktopcapturemonitor (Thanks to Rick Edgecombe) +- [HighDPI][WIN] fix for Tray menu is huge on High-DPI Windows machine (#2847) (Thanks to Jefry) + +0.12.2 / 05-22-2015 +=================== +- Fix #2723: [OSX] cpu hog in some cases +- Fix #3361: application cache +- Fix #2720: [Linux] launching sudo hits error: effective uid is not 0 +- Fix #2819: enable cookie support for web sockets +- Fix #2713: crash with 'new-win-policy' and opening window from iframe +- Fix #3123: support no-displaying-insecure-content and allow-running-insecure-content +- [Screen Selection] add application name to the UI; cancelChooseDesktopMedia implementation +- [Notification] [WIN] disable audio for toast notification, better fallback for toast notification +- Change cache backend from "simple" to "blockfile" + +0.12.1 / 04-13-2015 +=================== +- Fix crash dump generation +- [WIN] Fix blurry text with High DPI display +- Fix: Webview : contentWindow not available at this time (#3126) +- More precise RegExp for App.argv filtering (Thanks to Anton Khlynovskiy) +- Fix #3143: remote debugging devtools page blank (Thanks to Yuehang Wu) +- [Notification][Win] fix for missing windows events +- add Window 'progress' event (Thanks to vadim-kudr ) +- nw-headers is built automatically now in buildbot (Thanks to Xue Yang) + +0.12.0 / 03-05-2015 +======================= +- Chromium updated to 41.0.2272.76 +- [Screen Selection] OSX and Win implementation (Thanks to Jefry) +- Fix #3165: crash on auth in webview + +0.12.0-rc1 / 02-27-2015 +======================= +- new 'nwjc' tool replaces 'nwsnapshot'; size limit removed +- add Window.evalNWBin() to work with nwjc +- Fix #2923: support pepper flash plugin on Linux +- Fix #2961: nwdirectory file dialog +- Fix #2996: setting breakpoints in Node context in devtools + +0.12.0-alpha3 / 02-13-2015 +========================== +- Chromium updated to 41.0.2272.32 +- io.js updated to 1.2.0 + +0.12.0-alpha2 / 01-18-2015 +========================== +- Fix: -webkit-app-region: drag; stopped working in version 0.12.0-alpha1 #2963 +- Fix: [WIN] ReferrenceError in native module function createWritableDummyStream #2933 +- support bypassing frame-ancestors CSP in Node frame #2967 + +0.12.0-alpha1 / 01-15-2015 +========================== +- renamed NW.js +- Chromium is updated to 41.0.2236.2 +- migrated to io.js 1.0.0 +- new chrome.webrequest API +- new 'webview' tag from Chrome extensions +- new 'bg-script' field in the manifest + +0.11.3 / 12-16-2014 +=================== +- new method in 'new-win-policy' event handler to control the options for new popup windows +- Fix: nw methods cannot be called from normal frames +- Extend Tray click event with position (Thanks to Marco Fabbri) (#1874) +- [OSX] Fix Window.focus() not taking focus (#2724) +- Add API methods and support for styling of icons (Tray, MenuItem) under Mac OS X (Yosemite) Dark Mode (#2775) +- [OSX] Fix alticon property of Tray not being updated properly (#703) +- Add Window.setVisibleOnAllWorkspaces API (#2722) +- Fix #2469: Changed Window.open to ignore slashes in parameters +- fix crash in window.open in some cases + +0.11.2 / 11-26-2014 +=================== +- Support window transparency (#132, Thanks to Jefry Tedjokusumo) +- Fix: [Linux] broken window events (focus, blur, etc, #2631) +- Fix: memory leak on setting tray icon (#2666) +- Fix: child_process.fork() (#2664) +- Fix: bad Buffer created from strings from DOM (#1669, #2439) (Thank to Liu Cong) +- Fix: Segmentation fault by starting nw on command line with parameters #2671 +- Fix: crashes if http.request gets blocked with Little Snitch (mac only) #2585 +- Fix: Windows 7 64/32 - frame doesn't show #2657 +- Fix: AutoFill Crashes NodeWebkit #2653 +- Fix "Cancel Desktop Notification" for all platform. and implement it for win8 (toast notification) + +0.11.1 / 11-20-2014 +=================== +- add nwsnapshot +- Support setting additional root certificates on supported platforms (thanks to Joachim Bauch) +- Support SetProxyConfig API (#916) +- [WIN] Fix startup crash on high DPI systems (#2649) +- Fix #1021: maximize frameless window in windows 8 +- Fix #2590: save as Filetypes not populating +- Fix #2592: zoomLevel +- Fix #2595: Linux MenuBar crash +- Fix #2393: link target with "_blank" opens page in current window +- Fix: Don't activate app unconditionally on window "Show". + +0.11.0 / 11-11-2014 +=================== +- Fix: notification and screen geometry API +- Fix: windows printing crash (#2515) +- Fix: mac: Fix build with 10.9 SDK +- Fix: enable 'high-dpi-support' for windows (#2524) +- Fix: 'resizable' is broken in manifest (#2319) +- Fix: crash on Flash context menu +- Fix: console.log() changes value (#2431) +- Fix: various crash cases (#2545, #2549) +- Fix: 'undefined' network request in devtools (#2529) +- Fix: devtools - breakpoints do not work (#2538) +- Fix: Jailed devtools broken in nw 0.11.0-rc1 (#2569) +- Fix: Window.setResizable(false) twice makes window resizable (#2299) +- Fix: win.setPosition('center') Invalid (#2307) + +0.11.0-rc1 / 10-27-2014 +======================= +- Chromium updated to 38.0.2125.104 +- Fix memory leak on navigation +- Show commit id in 'nw:version' page +- Fix: fullscreen in manifest (Linux) +- Fix: #430: handle event when quit from OSX dock + +0.10.5 / 09-16-2014 +=================== +- Fix: support more Chromium command line args (#1743, Thanks to Joachim Bauch) +- Fix #2171: crash when opening window +- Fix #2326: some websites crashes NW in Windows (fixed with the same file as updating to VS2013 Update 2) +- Fix: win: crash on invalid parameter error (thanks to Mikael Roos) +- Fix #1991 in a better way: [WIN] stalling for seconds under threaded composition on some hardware (#1991) +- Fix: [WIN] single-instance crash +- Fix: autofill crash when filling number in input box (#2310) +- Fix: CSP is not effective (#1672) +- Fix: crash when calling console.log() with a cross-domain window object in some cases (#1573) +- Fix: crash when stepping into a breakpoint set in scripts loaded by require() (#2214) + +0.10.4 / 09-05-2014 +=================== +- Fix: [WIN] child_process.fork() by making nw executable run as node +- Fix: [WIN] stalling for seconds under threaded composition on some hardware (#1991) +- Fix: [OSX] disable File Quarantine (#2294) +- Fix: [OSX] disable sound for notification +- support 'chrome://gpu' diagnosic page + +0.10.3 / 09-01-2014 +=================== +- Fix: child_process.fork() (#213) by making nw executable run as node +- Fix: [OSX] process.nextTick() blocked in some cases (#2170) + +0.10.2 / 08-12-2014 +=================== +- Support screen geometry API (#2178 Thanks to Jefry Tedjokusumo +- Support progress bar (#2175 Thanks to Jefry Tedjokusumo) +- Window.requestAttention() now accepts an integer parameter [4] +- Fix: JS source code snapshot is now working +- Fix: Linux: shift modifier not working for window menu +- Fix: Win: window.navigator.language is empty +- Fix: require works in wrong path in new-instance window (#2167) +- Fix: support for screencapture media requests (Thanks to Joachim Bauch) +- Fix: Win: cursor not loaded in some cases (#2150, #2152) +- Fix: crash on some cases (#2155, #2148) +- Fix: Large combo-box does not scroll properly and values overlaps on each other (#2161; upstream #357480) + +0.10.1 / 07-30-2014 +=================== +- Support Desktop notification (#27 Thanks to Jefry Tedjokusumo) +- Support Fullscreen API (#55) +- Official 64bit binary for Mac OS X (#296) +- Support F-keys in global shortcut (Thanks to Bas Wegh) +- Option to hide "Edit" and "Window" OS X menus (Thanks to Eric Newport) +- Fix #2072: [WIN] context menu popup in wrong (screen) position +- Fix #2136: crash when popup new window in some cases +- Fix: Linux symbol files are incomplete in 0.10.0 +- Fix #1908: allows redirection to App protocol for OAuth usage +- Fix: new-win-policy is fired on each navigation + +0.10.0 / 07-22-2014 +=================== +- Fix: [WIN] download dialog +- Fix: [WIN] MenuItem.enabled and other properties needs to be called twice to work (#1132) + +0.10.0-rc2 / 07-18-2014 +======================= + +- [API] Implement register/unregister global desktop keyboard shortcut. (#1735, thanks to Chaobin Zhang and Haojian Wu) +- support for nwsnapshot is back +- Fix: authentication dialog +- check invalid URL on navigation +- [OSX] Fix #1996: resize with flash on 10.9 (upstream #385120) +- Fix regression: node-main +- Fix regression: [WIN] resize and drag frameless window +- Fix: crash on Win XP (#1985) + +0.10.0-rc1 / 06-22-2014 +======================= + +- Chromium is updated to 35.0.1916.113 +- Node.js is updated to 0.11.13 +- Window event listeners in iframe works now +- New API: App.addOriginAccessWhitelistEntry & App.removeOriginAccessWhitelistEntry (#1016, #1514) +- API to set Menu item shortcut +- [OSX] Create & access to built-in menu from JS +- SetBadgeLabel support on OSX and Win +- Support for 'single-instance' when app is started in folder +- better handling of MIME types and dataURI in capture page API + + +0.8.6 / 04-18-2014 +================== +- Fix: new instance window is force closed and cannot receive 'close' event on App.closeAllWindows() (Cmd-Q) (#1713, #1778) + +0.8.5 / 02-26-2014 +================== +Backport 0.9 features to 0.8 branch + +- Support `'inject-js-start'` and `'inject-js-end'` in manifest (#1585) +- `'new-win-policy'` event now works for windows opened by `nwgui.Window.open()` (#1519) +- `'new-win-policy'` handler supports Ctrl click and middle click (#1547) +- Support NTLM and the settings (`--auth-server-whitelist`, `--auth-schemes`, etc) (#590) +- Support app frameworks like AngularJS better by returning HTTP response code in `'app://'` protocol handler (#1314) +- Injecting JavaScript in window or iframe in various cases: + - 'inject-js' option in Window.open or manifest + - 'document-start' and 'document-end' event for iframe: + - Window.eval() to execute JavaScript in target window or iframe +- Handler to decide how new window is request from iframe, see 'new-win-policy' event in https://github.com/rogerwang/node-webkit/wiki/Window +- Overriding 'User-Agent' in iframe: https://github.com/rogerwang/node-webkit/wiki/Changes-to-dom#nwUserAgent + +0.9.2 / 02-20-2014 +================== + +- Support skipping taskbar or dock: `Window.setShowInTaskbar(bool)` is added. It can be set from manifest as well. (#1076) (Thanks to Zhang Chaobin & Zhao Zeyi) +- Support `'inject-js-start'` and `'inject-js-end'` in manifest (#1585) +- `'new-win-policy'` event now works for windows opened by `nwgui.Window.open()` (#1519) +- `'new-win-policy'` handler supports Ctrl click and middle click (#1547) +- Support NTLM and the settings (`--auth-server-whitelist`, `--auth-schemes`, etc) (#590) +- Support app frameworks like AngularJS better by returning HTTP response code in `'app://'` protocol handler (#1314) +- Fix: crashing when setting DOM event handler `global.window.onkeydown` from Node context (#1562) + + +0.9.1 / 02-10-2014 +================== + +- Chromium is updated to 32.0.1700.107 +- [OSX] `process.nextTick` will not fire +- Fix: crashing on `alert()` + + +0.9.0 / 02-08-2014 +================== + +- Chromium updated to version 32 +- Node.js updated to v0.11 +- nw-gyp updated to v0.12.2 to support VS2013 (Thanks to Robert Konrad) +- Injecting JavaScript in window or iframe in various cases: + - 'inject-js' option in Window.open or manifest + - 'document-start' and 'document-end' event for iframe: + - Window.eval() to execute JavaScript in target window or iframe +- Better experience for working with native modules if they support Node v0.11. +- Handler to decide how new window is request from iframe, see 'new-win-policy' event in https://github.com/rogerwang/node-webkit/wiki/Window +- Overriding 'User-Agent' in iframe: https://github.com/rogerwang/node-webkit/wiki/Changes-to-dom#nwUserAgent +- Disable uploading of crash dumps in 0.9.0-rc1 +- Don't use "Upload" in directory file dialog UI (#1457) +- Remove '--enable-experimental-web-platform-features' from default cmdline to render pages correctly in OSX in some cases +- Call Node's uncaughtException handler in Node frames +- Export v8 symbols properly for native modules (#1522) +- Enable scripts to emulate user gestures +- Fix: Uncaught exception warnings of SetZoomLevel +- Fix: Crashing on window.print() (#1545) +- Fix: Clicking the app reload button followed by a call to the cookies api causes a crash (#1146) +- Fix: Can't close NW after refresh while using Dev Tools on Windows (#1330) +- Fix: Fatal error in ../../v8/src/mark-compact.cc, line 2751 (#1379) + + +0.8.4 / 12-30-2013 +================== + +- Fix for OSX: the message loop integration between Chromium and Node.js has improved. +- Fix: Need a way to differentiate Cmd-Q from closing window(s) (#430) +- Fix: Fix the way we integrate libuv to NSApp (#82) + + +0.8.3 / 12-20-2013 +================== + +- Remove duplicate Node.js license notice (#1178) +- Fix: maximize and unmaximize event not fired. (#753) +- Fix: crash in ClearCache in some cases +- Fix: Fast open&close devtools crashes node-webkit 0.8.2 on OSX (#1391) +- Fix: `focus` doesn't work on devtools window (#1387) + + +0.8.2 / 12-06-2013 +================== + +- Support native Node.js module better by adding deprecated MakeWeak function (#533 #1355). Some native modules can be built with 'nw-gyp' now. +- Support 'move' and 'resize' events of the Window object (#799) +- Event support on devtools window object +- HTTP (proxy) login dialog will be shown for only once on multiple initial requests. +- [OSX] Turn on `--enable-threaded-compositing` by default (#1340): this fixes the flash for CSS3 animation +- Cherry-pick upstream fix: Update OOM killer to patch out CFAllocator on 10.9 (#1271 #1277) +- Crashes when open some window again (#1364) +- Do not quit on API call on invalid object (#1339) + + +0.8.1 / 11-22-2013 +================== + +- Node updated to 0.10.22 +- New Window.cookie API: It's for manipulating cookies. See https://github.com/rogerwang/node-webkit/wiki/Window +- Window.showDevTools() now returns a Window object for the devtools window. So it can be moved/resized etc. Note that the events on this Window is not working yet. +- Fix: WebRTC audio gets choppy because the device is enumerated twice (#1270) +- Fix: Screen capture regression (#1309) +- Fix: [WIN] crash sometimes when Back/Forward key is received +- Fix: Crash when access xhr.response as ArrayBuffer from Node context +- Fix: [security] app:// protocol available in iframe with nwdisable (#1255) + + +0.8.0 / 10-30-2013 +================== + +- Chromium updated to 30.0.1599.66 (now shown in nw:version) +- Support crash dumping: if node-webkit crashes, your users or you can attach the dump file in bug reports. It contains stack trace information which is helpful to locate the root cause. See https://github.com/rogerwang/node-webkit/wiki/Crash-dump +- i18n of built-in mac menus: the built-in menus created for Mac are now translated to 53 languages with strings from upstream. For the list of languages, see https://github.com/rogerwang/node-webkit/tree/master/src/resources/locale +- Linux: environment variable override for the proxy settings. Use '--v=1' to see whether it's applied from the environment or GSettings. The following environment variables are supported: 'all_proxy', 'http_proxy', 'https_proxy', 'ftp_proxy', 'no_proxy', 'SOCKS_VERSION', 'SOCKS_SERVER'. +- Fix: Date pickers language is always English regardless of current locale. (#669) +- Fix: File dialog's language is always English regardless of current locale.(#103) +- Fix: gui.App error in CallStaticMethodSync when using multiple windows (#1187) +- Fix: console.log() output in terminal. +- Fix: -webkit-app-region: drag don't work any more once it maximized. +- Fix: crash on using filesystem API: quota and persistent storage +- nw.gui.Window: add 'devtools-closed' event; always send 'devtools-opened' event. +- nw.gui.Window: add 'isDevToolsOpen()' to query the status of devtools. +- Support media enumerable API from upstream 30 (#632) +- Win: embedding manifest to fix tooltip +- Override lang setting from cmdline argument (--lang) +- Use current locale in Header 'Accept-Language' (#1240) +- Mac: Fallback to 'en-US' when locale pak file is missing and don't quit. +- Fix crash on exit: PageClickTracker is deleted twice +- Linux: make frameless window resizable (#142) +- Fix crash when there is apostrophe in the full path (#1206) +- Fix Devtools: Use the most recent version of script for the same URL +- undefine window.webkitSpeechRecognition before it's supported + +0.7.2 / 08-26-2013 +================== + +- Window.showDevTools() accepts iframe object as the first parameter +- Fix: Mac - 0.7.x crash after copy/paste/edit (keypress) input field (introduced in upstream) (#1017) +- Fix: 0.7.0 nw.exe still in memory after using a "close/quit" command bug (#984) +- Fix: have some means to disable the debug.log file; debug.log file is now disabled by default. Use '--enable-logging' to turn it on. (#1031) + +0.7.1 / 08-19-2013 +================== + +- Enable the screen capture support for getUserMedia (#576) +- nw-gyp updated to 0.10.9 to work with latest Node (v0.10.16) +- Fix regression: app cannot start from path contains '\x' (win) +- Fix: A query string in app:// address in "main" property inside the manifest freezes node-webkit (#990) +- Fix: nw.exe still in memory after calling 'App.quit()' in some cases (#984) +- Fix: window unmaximizes when show is called (win) (#987) +- Fix: Application Crash when quitting with secondary window on top (OSX) (#486) +- Fix: Headers file is updated to allow building native modules +- Report a clear message to console when nw.pak is missing + + +0.7.0 / 08-12-2013 +================== + +- Support `app://` protocol (#363) +- Support setting value for file input (#927) +- New API: get the values from manifest with `App.manifest` +- Changed API: full command line is passed as the parameter of `App.open` event +- Fix: remove `alert()` dialog title on OSX +- Fix: Sometimes `Window.focus()` doesn't work (#933) + + +0.6.3 / 07-23-2013 +================== + +- Add App.getProxyForURL() to query proxy for URL (#130) +- Save breakpoints and settings in devtools (#98) +- [Win] keep the ampersands character in dialogs (#901) +- Fix: can't access `file://` URIs when using a remotely hosted app (#871) +- Fix: devtools freeze on breakpoints in Ubuntu 12 VM + + +0.6.2 / 07-08-2013 +================== + +- Node.js updated to v0.10.12 +- New way to start applications: placing 'package.nw' in the same directory with node-webkit executable. NW will search for app as proposed in #843. +- [Mac] show the window without the focus side effect (#497) +- [Win] embed manifest to apply visual style for showing tooltip (#72 #810) +- Fix: `'require()'` is not defined when opening devtools (#809) +- Fix `'dom_storage_quota'` +- Fix regression of using datalist for autocompletion (#434) +- Fix regression: node modules cannot be stopped in debugger (#719) + + +0.6.1 / 06-23-2013 +================== + +- Use 'App.dataPath' to get the application's data path in user's directory +- Fixed regression: source map in devtools is working after setting `"node-remote"` to `"127.0.0.1"` in manifest (#727) +- 'App.open' event is sent to all the windows now (#787) +- 'dom_storage_quota' in manifest is fixed + + +0.6.0 / 06-17-2013 +================== + +- Chromium updated to 28.0.1500.11. +- A new feature "Devtools jail" is introduced for IDE developers. +- A new API function `App.clearCache()` is added to clear HTTP cache in both memory and disk. +- A new field in manifest `dom-storage-quota` to specify the number of mega bytes for the quota of the DOM storage. +- Chromedriver binary is provided to use automatic testing tools such as `selenium` with node-webkit applications. +- Fix: Crash when using devtools (#793) +- `dom-storage-quota` in manifest is renamed to `dom_storage_quota` +- Using correct icon size on Windows to fix the one in win7 audio mixer +- Running nw from the command line on Mac always opens a second instance (#726) +- How to clear the page cache in js? (#672) +- OSX Page redrawing issues with twitter bootstrap (#476) +- NW + WebGL (three.js) problem (#452) +- Canvas freezes window on OSX (#381) +- Popping up a menu twice will crash nw on ubuntu. (#721) diff --git a/LICENSE b/LICENSE index e963ddfc56..ba52b7c91d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ -Copyright (c) 2012 Intel Corp -Copyright (c) 2012 The Chromium Authors +Copyright (c) 2012-2015 Intel Corp +Copyright (c) 2012-2015 The Chromium Authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in th diff --git a/README.md b/README.md index 2aa0ecc3cd..5f1c5ba13c 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,92 @@ +## node-webkit is renamed NW.js + +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/nwjs/nw.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +Official site: http://nwjs.io +[Announcement](https://groups.google.com/d/msg/nwjs-general/V1FhvfaFIzQ/720xKVd0jNkJ) ## Introduction -node-webkit is an app runtime based on `Chromium` and `node.js`. You can -write native apps in HTML and Javascript with node-webkit. It also lets you -to call Node.js modules directly from DOM and enables a new way of writing +NW.js is an app runtime based on `Chromium` and `node.js`. You can +write native apps in HTML and JavaScript with NW.js. It also lets you +call Node.js modules directly from the DOM and enables a new way of writing native applications with all Web technologies. -It's created and developed in Intel Open Source Technology Center. +It was created in the Intel Open Source Technology Center. -[Introduction to node-webkit (slides)](https://speakerdeck.com/u/zcbenz/p/node-webkit-app-runtime-based-on-chromium-and-node-dot-js). +[Introduction to node-webkit (slides)](https://speakerdeck.com/u/zcbenz/p/node-webkit-app-runtime-based-on-chromium-and-node-dot-js) +[Creating Desktop Applications With node-webkit](http://strongloop.com/strongblog/creating-desktop-applications-with-node-webkit/) +[WebApp to DesktopApp with node-webkit (slides)](http://oldgeeksguide.github.io/presentations/html5devconf2013/wtod.html) +[Essay on the history and internals of the project](http://yedingding.com/2014/08/01/node-webkit-intro-en.html) ## Features * Apps written in modern HTML5, CSS3, JS and WebGL. * Complete support for [Node.js APIs](http://nodejs.org/api/) and all its [third party modules](https://npmjs.org). -* Good performance: Node and WebKit runs in the same thread: Function calls are made straightforward; objects are in the same heap and can just reference each other; +* Good performance: Node and WebKit run in the same thread: Function calls are made straightforward; objects are in the same heap and can just reference each other; * Easy to package and distribute apps. -* Available on Linux, Mac OSX and Windows +* Available on Linux, Mac OS X and Windows ## Downloads -[v0.5.1 release note](https://groups.google.com/d/msg/node-webkit/c9YS5ChkgfI/82aBVN1ttQsJ) +* **v0.12.3:** (Jul 31, 2015, based off of IO.js v1.2.0, Chromium 41.0.2272.76): [release notes](https://groups.google.com/d/msg/nwjs-general/hhXCS4aXGV0/TUQmcu5XDwAJ) + * Linux: [32bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-linux-ia32.tar.gz) / [64bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-linux-x64.tar.gz) + * Windows: [32bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-win-ia32.zip) / [64bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-win-x64.zip) + * Mac 10.7+: [32bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-osx-ia32.zip) / [64bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-osx-x64.zip) + +* **v0.13.0-alpha1:** (Jun 11, 2015, based off of IO.js v1.5.1, Chromium 43.0.2357.45): [release notes](https://groups.google.com/d/msg/nwjs-general/c25l_jGMqj8/rsAtdSQuxeUJ) + **NOTE** You might want the **SDK build**. Please read the release notes + * Linux: [32bit](http://dl.nwjs.io/v0.13.0/alpha1/nwjs-v0.13.0-alpha1-linux-ia32.tar.gz) / [64bit](http://dl.nwjs.io/v0.13.0/alpha1/nwjs-v0.13.0-alpha1-linux-x64.tar.gz) + * Windows: [32bit](http://dl.nwjs.io/v0.13.0/alpha1/nwjs-v0.13.0-alpha1-win-ia32.zip) / [64bit](http://dl.nwjs.io/v0.13.0/alpha1/nwjs-v0.13.0-alpha1-win-x64.zip) + * Mac 10.7+: [32bit](http://dl.nwjs.io/v0.13.0/alpha1/nwjs-v0.13.0-alpha1-osx-ia32.zip) / [64bit](http://dl.nwjs.io/v0.13.0/alpha1/nwjs-v0.13.0-alpha1-osx-x64.zip) + +* **0.8.6:** (Apr 18, 2014, based off of Node v0.10.22, Chrome 30.0.1599.66) **If your native Node module works only with Node v0.10, then you should use node-webkit v0.8.x, which is also a maintained branch. [More info](https://groups.google.com/d/msg/nwjs-general/2OJ1cEMPLlA/09BvpTagSA0J)** +[release notes](https://groups.google.com/d/msg/nwjs-general/CLPkgfV-i7s/hwkkQuJ1kngJ) -Prebuilt binaries (v0.5.1 - Apr 27, 2013): + * Linux: [32bit](http://dl.node-webkit.org/v0.8.6/node-webkit-v0.8.6-linux-ia32.tar.gz) / [64bit](http://dl.node-webkit.org/v0.8.6/node-webkit-v0.8.6-linux-x64.tar.gz) + * Windows: [win32](http://dl.node-webkit.org/v0.8.6/node-webkit-v0.8.6-win-ia32.zip) + * Mac: [32bit, 10.7+](http://dl.node-webkit.org/v0.8.6/node-webkit-v0.8.6-osx-ia32.zip) -* Linux: [32bit](https://s3.amazonaws.com/node-webkit/v0.5.1/node-webkit-v0.5.1-linux-ia32.tar.gz) / [64bit] (https://s3.amazonaws.com/node-webkit/v0.5.1/node-webkit-v0.5.1-linux-x64.tar.gz) -* Windows: [win32](https://s3.amazonaws.com/node-webkit/v0.5.1/node-webkit-v0.5.1-win-ia32.zip) -* Mac: [32bit, 10.7+](https://s3.amazonaws.com/node-webkit/v0.5.1/node-webkit-v0.5.1-osx-ia32.zip) +* **latest live build**: git tip version; build triggered from every git commit: http://dl.nwjs.io/live-build/ -[Looking for older versions?](https://github.com/rogerwang/node-webkit/wiki/Downloads-of-old-versions) +* [Previous versions](https://github.com/rogerwang/node-webkit/wiki/Downloads-of-old-versions) ###Demos and real apps -You may also be interested in [our demos repository](https://github.com/zcbenz/nw-sample-apps) and the [List of apps and companies using node-webkit](https://github.com/rogerwang/node-webkit/wiki/List-of-apps-and-companies-using-node-webkit). +You may also be interested in [our demos repository](https://github.com/zcbenz/nw-sample-apps) and the [List of apps and companies using nw.js](https://github.com/nwjs/nw.js/wiki/List-of-apps-and-companies-using-nw.js). ## Quick Start Create `index.html`: -````html +```html + - -Hello World! - - -

Hello World!

-We are using node.js - + + Hello World! + + +

Hello World!

+ We are using node.js . + -```` +``` Create `package.json`: -````json +```json { "name": "nw-demo", + "version": "0.0.1", "main": "index.html" } -```` - -Compress `index.html` and `package.json` into a zip archive, and rename -it to `app.nw`: - - app.nw - |-- package.json - `-- index.html +``` -Download the prebuilt binary for your platform and use it to open the -`app.nw` file: +Run: +```bash +$ /path/to/nw . (suppose the current directory contains 'package.json') +``` -````bash -$ ./nw app.nw -```` +Note: on Windows, you can drag the folder containing `package.json` to `nw.exe` to open it. -Note: on Windows, you can drag the `app.nw` to `nw.exe` to open it. +Note: on OSX, the executable binary is in a hidden directory within the .app file. To run node-webkit on OSX, type: +`/path/to/nwjs.app/Contents/MacOS/nwjs .` *(suppose the current directory contains 'package.json')* ## Documents @@ -78,15 +94,25 @@ For more information on how to write/package/run apps, see: * [How to run apps](https://github.com/rogerwang/node-webkit/wiki/How-to-run-apps) * [How to package and distribute your apps](https://github.com/rogerwang/node-webkit/wiki/How-to-package-and-distribute-your-apps) -* [How to use 3rd party node.js modules in node-webkit](https://github.com/rogerwang/node-webkit/wiki/How-to-use-3rd-party-node.js-modules-in-node-webkit) +* [How to use Node.js modules in node-webkit](https://github.com/rogerwang/node-webkit/wiki/Using-Node-modules) And our [Wiki](https://github.com/rogerwang/node-webkit/wiki) for much more. ## Community -We use [node-webkit group](http://groups.google.com/group/node-webkit) as -our mailing list, subscribe via [node-webkit+subscribe@googlegroups.com](mailto:node-webkit+subscribe@googlegroups.com). +We use the [google group](https://groups.google.com/d/forum/nwjs-general) as +our mailing list (use English only). Subscribe via [nwjs-general+subscribe@googlegroups.com](mailto:nwjs-general+subscribe@googlegroups.com). + +*NOTE*: Links to the old google group (e.g. `https://groups.google.com/forum/#!msg/node-webkit/doRWZ07LgWQ/4fheV8FF8zsJ`) that are no more working can be fixed by replacing `node-webkit` with `nwjs-general` (e.g `https://groups.google.com/forum/#!msg/nwjs-general/doRWZ07LgWQ/4fheV8FF8zsJ`). + +Issues are being tracked here on GitHub. ## License -`node-webkit`'s code uses the MIT license, see our `LICENSE` file. +`node-webkit`'s code in this repo uses the MIT license, see our `LICENSE` file. To redistribute the binary, see [How to package and distribute your apps](https://github.com/rogerwang/node-webkit/wiki/How-to-package-and-distribute-your-apps) + +## Sponsors + +The work is being sponsored by: +* [Intel](http://www.intel.com) +* [Gnor Tech](http://gnor.net) diff --git a/nw.gypi b/nw.gypi index 7a6d87e0de..0b4bc0f863 100644 --- a/nw.gypi +++ b/nw.gypi @@ -4,21 +4,60 @@ { 'variables': { - 'nw_product_name': 'node-webkit', + 'nw_product_name': 'nwjs', + 'mac_strip_release': 1, + 'nw_gen_path': '<(SHARED_INTERMEDIATE_DIR)/nw', + 'nw_id_script_base': 'commit_id.py', + 'nw_id_script': '<(nw_gen_path)/<(nw_id_script_base)', + 'nw_id_header_base': 'commit.h', + 'nw_id_header': '<(nw_gen_path)/id/<(nw_id_header_base)', + 'nw_use_commit_id%': ' windows = content::Shell::windows(); +RenderProcessHost* GetRenderProcessHost() { + RenderProcessHost* render_process_host = NULL; + std::vector windows = Shell::windows(); for (size_t i = 0; i < windows.size(); ++i) { if (!windows[i]->is_devtools()) { render_process_host = windows[i]->web_contents()->GetRenderProcessHost(); @@ -49,29 +79,57 @@ content::RenderProcessHost* GetRenderProcessHost() { return render_process_host; } +void GetRenderProcessHosts(std::set& rphs) { + RenderProcessHost* render_process_host = NULL; + std::vector windows = Shell::windows(); + for (size_t i = 0; i < windows.size(); ++i) { + if (!windows[i]->is_devtools()) { + render_process_host = windows[i]->web_contents()->GetRenderProcessHost(); + rphs.insert(render_process_host); + } + } +} + +void SetProxyConfigCallback( + base::WaitableEvent* done, + net::URLRequestContextGetter* url_request_context_getter, + const net::ProxyConfig& proxy_config) { + net::ProxyService* proxy_service = + url_request_context_getter->GetURLRequestContext()->proxy_service(); + proxy_service->ResetConfigService( + new net::ProxyConfigServiceFixed(proxy_config)); + done->Signal(); +} + } // namespace - + // static void App::Call(const std::string& method, const base::ListValue& arguments) { if (method == "Quit") { - Quit(GetRenderProcessHost()); - return; + Quit(); } else if (method == "CloseAllWindows") { CloseAllWindows(); - return; + } else if (method == "CrashBrowser") { + int* ptr = NULL; + *ptr = 1; + } else { + NOTREACHED() << "Calling unknown method " << method << " of App."; } - - NOTREACHED() << "Calling unknown method " << method << " of App"; } - // static -void App::Call(content::Shell* shell, +void App::Call(Shell* shell, const std::string& method, const base::ListValue& arguments, - base::ListValue* result) { - if (method == "GetArgv") { + base::ListValue* result, + DispatcherHost* dispatcher_host) { + if (method == "GetDataPath") { + ShellBrowserContext* browser_context = + static_cast(shell->web_contents()->GetBrowserContext()); + result->AppendString(browser_context->GetPath().value()); + return; + }else if (method == "GetArgv") { nw::Package* package = shell->GetPackage(); CommandLine* command_line = CommandLine::ForCurrentProcess(); CommandLine::StringVector args = command_line->GetArgs(); @@ -88,6 +146,69 @@ void App::Call(content::Shell* shell, result->AppendString(argv[i]); } + return; + } else if (method == "ClearCache") { + ClearCache(GetRenderProcessHost()); + return; + } else if (method == "CreateShortcut") { +#if defined(OS_WIN) + base::string16 path; + arguments.GetString(0, &path); + + base::win::ShortcutProperties props; + base::string16 appID; + if (content::Shell::GetPackage()->root()->GetString("app-id", &appID) == false) + content::Shell::GetPackage()->root()->GetString(switches::kmName, &appID); + const std::wstring appName = base::UTF8ToWide(content::Shell::GetPackage()->GetName()); + props.set_app_id(appID); + + base::FilePath processPath; + PathService::Get(base::FILE_EXE, &processPath); + props.set_target(processPath); + + base::FilePath shortcutPath(path); + result->AppendBoolean(base::win::CreateOrUpdateShortcutLink(shortcutPath, props, + base::PathExists(shortcutPath) ? base::win::SHORTCUT_UPDATE_EXISTING : base::win::SHORTCUT_CREATE_ALWAYS)); +#else + result->AppendBoolean(false); +#endif + return; + } else if (method == "GetPackage") { + result->AppendString(shell->GetPackage()->package_string()); + return; + } else if (method == "SetCrashDumpDir") { + std::string path; + arguments.GetString(0, &path); + //FIXME: result->AppendBoolean(SetCrashDumpPath(path.c_str())); + return; + } else if (method == "RegisterGlobalHotKey") { + int object_id = -1; + arguments.GetInteger(0, &object_id); + Shortcut* shortcut = + static_cast(DispatcherHost::GetApiObject(object_id)); + bool success = GlobalShortcutListener::GetInstance()->RegisterAccelerator( + shortcut->GetAccelerator(), shortcut); + if (!success) + shortcut->OnFailed("Register global desktop keyboard shortcut failed."); + + result->AppendBoolean(success); + return; + } else if (method == "UnregisterGlobalHotKey") { + int object_id = -1; + arguments.GetInteger(0, &object_id); + Shortcut* shortcut = + static_cast(DispatcherHost::GetApiObject(object_id)); + GlobalShortcutListener::GetInstance()->UnregisterAccelerator( + shortcut->GetAccelerator(), shortcut); + return; + } else if (method == "SetProxyConfig") { + std::string proxy_config, pac_url; + arguments.GetString(0, &proxy_config); + arguments.GetString(1, &pac_url); + SetProxyConfig(GetRenderProcessHost(), proxy_config, pac_url); + return; + } else if (method == "DoneMenuShow") { + dispatcher_host->quit_run_loop(); return; } @@ -95,37 +216,112 @@ void App::Call(content::Shell* shell, } // static -void App::CloseAllWindows() { - std::vector windows = content::Shell::windows(); +void App::CloseAllWindows(bool force, bool quit) { + std::vector windows = Shell::windows(); for (size_t i = 0; i < windows.size(); ++i) { // Only send close event to browser windows, since devtools windows will // be automatically closed. if (!windows[i]->is_devtools()) { // If there is no js object bound to the window, then just close. - if (windows[i]->ShouldCloseWindow()) + if (force || windows[i]->ShouldCloseWindow(quit)) + // we used to delete the Shell object here + // but it should be deleted on native window destruction + windows[i]->window()->Close(); + } + } + if (force) { + // in a special force close case, since we're going to exit the + // main loop soon, we should delete the shell object asap so the + // render widget can be closed on the renderer side + windows = Shell::windows(); + for (size_t i = 0; i < windows.size(); ++i) { + if (!windows[i]->is_devtools()) delete windows[i]; } } } // static -void App::Quit(content::RenderProcessHost* render_process_host) { +void App::Quit(RenderProcessHost* render_process_host) { // Send the quit message. int no_use; - render_process_host->Send(new ViewMsg_WillQuit(&no_use)); + if (render_process_host) { + render_process_host->Send(new ViewMsg_WillQuit(&no_use)); + }else{ + std::set rphs; + std::set::iterator it; + + GetRenderProcessHosts(rphs); + for (it = rphs.begin(); it != rphs.end(); it++) { + RenderProcessHost* rph = *it; + DCHECK(rph != NULL); + rph->Send(new ViewMsg_WillQuit(&no_use)); + } + CloseAllWindows(true); + } // Then quit. MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure()); } // static void App::EmitOpenEvent(const std::string& path) { - // Get the app's renderer process. - content::RenderProcessHost* render_process_host = GetRenderProcessHost(); - DCHECK(render_process_host != NULL); + std::set rphs; + std::set::iterator it; + + GetRenderProcessHosts(rphs); + for (it = rphs.begin(); it != rphs.end(); it++) { + RenderProcessHost* rph = *it; + DCHECK(rph != NULL); - render_process_host->Send(new ShellViewMsg_Open(path)); + rph->Send(new ShellViewMsg_Open(path)); + } +} + +// static +void App::EmitReopenEvent() { + std::set rphs; + std::set::iterator it; + + GetRenderProcessHosts(rphs); + for (it = rphs.begin(); it != rphs.end(); it++) { + RenderProcessHost* rph = *it; + DCHECK(rph != NULL); + + rph->Send(new ShellViewMsg_Reopen()); + } +} + +void App::ClearCache(content::RenderProcessHost* render_process_host) { + render_process_host->Send(new ShellViewMsg_ClearCache()); + nw::RemoveHttpDiskCache(render_process_host->GetBrowserContext(), + render_process_host->GetID()); } -} // namespace api +void App::SetProxyConfig(content::RenderProcessHost* render_process_host, + const std::string& proxy_config, + const std::string& pac_url) { + net::ProxyConfig config; + if (!pac_url.empty()) { + if (pac_url == "") + config = net::ProxyConfig::CreateDirect(); + else if (pac_url == "") + config = net::ProxyConfig::CreateAutoDetect(); + else + config = net::ProxyConfig::CreateFromCustomPacURL(GURL(pac_url)); + } else + config.proxy_rules().ParseFromString(proxy_config); + net::URLRequestContextGetter* context_getter = + render_process_host->GetBrowserContext()-> + GetRequestContextForRenderProcess(render_process_host->GetID()); + + base::WaitableEvent done(false, false); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&SetProxyConfigCallback, &done, + make_scoped_refptr(context_getter), config)); + done.Wait(); + +} +} // namespace nwapi diff --git a/src/api/app/app.h b/src/api/app/app.h index 3a90bfee32..969b01fadc 100644 --- a/src/api/app/app.h +++ b/src/api/app/app.h @@ -22,6 +22,7 @@ #define CONTENT_NW_SRC_API_APP_APP_H_ #include "base/basictypes.h" +#include "../dispatcher_host.h" #include @@ -34,8 +35,8 @@ class RenderProcessHost; class Shell; } -namespace api { - +namespace nwapi { + class App { public: static void Call(const std::string& method, @@ -44,24 +45,33 @@ class App { static void Call(content::Shell* shell, const std::string& method, const base::ListValue& arguments, - base::ListValue* result); + base::ListValue* result, + DispatcherHost* dispatcher_host); // Try to close all windows (then will cause whole app to quit). - static void CloseAllWindows(); + static void CloseAllWindows(bool force = false, bool quit = false); // Quit the whole app. - static void Quit(content::RenderProcessHost* render_view_host); + static void Quit(content::RenderProcessHost* rph = NULL); // Post "open" event. static void EmitOpenEvent(const std::string& path); + // Post "reopen" event. + // (This event is received when the user clicked the icon in the Dock). + static void EmitReopenEvent(); + + static void ClearCache(content::RenderProcessHost* render_view_host); + static void SetProxyConfig(content::RenderProcessHost* render_process_host, + const std::string& proxy_config, + const std::string& pac_url); + private: App(); - DISALLOW_COPY_AND_ASSIGN(App); }; -} // namespace api +} // namespace nwapi #endif // CONTENT_NW_SRC_API_APP_APP_H_ diff --git a/src/api/app/app.js b/src/api/app/app.js index 29c8ad4e20..7ff009ffe3 100644 --- a/src/api/app/app.js +++ b/src/api/app/app.js @@ -18,17 +18,18 @@ // ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -var argv, fullArgv; +var argv, fullArgv, dataPath, manifest; +var v8_util = process.binding('v8_util'); function App() { } require('util').inherits(App, exports.Base); App.filteredArgv = [ - /--no-toolbar/, - /--url=.*/, - /--remote-debugging-port=.*/, - /--renderer-cmd-prefix.*/, + /^--no-toolbar$/, + /^--url=/, + /^--remote-debugging-port=/, + /^--renderer-cmd-prefix/, ]; App.prototype.quit = function() { @@ -39,6 +40,63 @@ App.prototype.closeAllWindows = function() { nw.callStaticMethod('App', 'CloseAllWindows', [ ]); } +App.prototype.crashBrowser = function() { + nw.callStaticMethod('App', 'CrashBrowser', [ ]); +} + +App.prototype.crashRenderer = function() { + nw.crashRenderer(); +} + +App.prototype.setCrashDumpDir = function(dir) { + nw.setCrashDumpDir(dir); // for windows renderer process + return nw.callStaticMethodSync('App', 'SetCrashDumpDir', [ dir ]); +} + +App.prototype.createShortcut = function(dir) { + return nw.callStaticMethodSync('App', 'CreateShortcut', [ dir ]); +} + +App.prototype.clearCache = function() { + nw.callStaticMethodSync('App', 'ClearCache', [ ]); +} + +App.prototype.doneMenuShow = function() { + nw.callStaticMethodSync('App', 'DoneMenuShow', [ ]); +} + +App.prototype.getProxyForURL = function (url) { + return nw.callStaticMethodSync('App', 'getProxyForURL', [ url ]); +} + +App.prototype.setProxyConfig = function (proxy_config, pac_url) { + return nw.callStaticMethodSync('App', 'SetProxyConfig', [ proxy_config, pac_url ]); +} + +App.prototype.addOriginAccessWhitelistEntry = function(sourceOrigin, destinationProtocol, destinationHost, allowDestinationSubdomains) { + return nw.callStaticMethodSync('App', 'AddOriginAccessWhitelistEntry', sourceOrigin, destinationProtocol, destinationHost, allowDestinationSubdomains); +} + +App.prototype.removeOriginAccessWhitelistEntry = function(sourceOrigin, destinationProtocol, destinationHost, allowDestinationSubdomains) { + return nw.callStaticMethodSync('App', 'RemoveOriginAccessWhitelistEntry', sourceOrigin, destinationProtocol, destinationHost, allowDestinationSubdomains); +} + +App.prototype.registerGlobalHotKey = function(shortcut) { + if (v8_util.getConstructorName(shortcut) != "Shortcut") + throw new TypeError("Invaild parameter, need Shortcut object."); + + return nw.callStaticMethodSync('App', + 'RegisterGlobalHotKey', + [ shortcut.id ])[0]; +} + +App.prototype.unregisterGlobalHotKey = function(shortcut) { + if (v8_util.getConstructorName(shortcut) != "Shortcut") + throw new TypeError("Invaild parameter, need Shortcut object."); + + nw.callStaticMethodSync('App', 'UnregisterGlobalHotKey', [ shortcut.id ]); +} + App.prototype.__defineGetter__('argv', function() { if (!argv) { var fullArgv = this.fullArgv; @@ -68,6 +126,21 @@ App.prototype.__defineGetter__('fullArgv', function() { return fullArgv; }); +App.prototype.__defineGetter__('dataPath', function() { + if (!dataPath) + dataPath = nw.callStaticMethodSync('App', 'GetDataPath', [ ])[0]; + + return dataPath; +}); + +App.prototype.__defineGetter__('manifest', function() { + if (!manifest) { + manifest = JSON.parse( + nw.callStaticMethodSync('App', 'GetPackage', [ ])[0]); + } + return manifest; +}); + // Store App object in node's context. if (process['_nw_app']) { exports.App = process['_nw_app']; diff --git a/src/api/base/base.cc b/src/api/base/base.cc index 5e7d7f2d68..e085467d8d 100644 --- a/src/api/base/base.cc +++ b/src/api/base/base.cc @@ -23,10 +23,10 @@ #include "base/logging.h" #include "base/values.h" -namespace api { +namespace nwapi { Base::Base(int id, - DispatcherHost* dispatcher_host, + const base::WeakPtr& dispatcher_host, const base::DictionaryValue& option) : id_(id), dispatcher_host_(dispatcher_host) { @@ -50,4 +50,4 @@ void Base::CallSync(const std::string& method, << " arguments:" << arguments; } -} // namespace api +} // namespace nwapi diff --git a/src/api/base/base.h b/src/api/base/base.h index 105ae50d8e..028a800cca 100644 --- a/src/api/base/base.h +++ b/src/api/base/base.h @@ -22,6 +22,7 @@ #define CONTENT_NW_SRC_API_BASE_BASE_H_ #include "base/basictypes.h" +#include "base/memory/weak_ptr.h" #include @@ -30,14 +31,14 @@ class DictionaryValue; class ListValue; } -namespace api { +namespace nwapi { class DispatcherHost; class Base { public: Base(int id, - DispatcherHost* dispatcher_host, + const base::WeakPtr& dispatcher_host, const base::DictionaryValue& option); virtual ~Base(); @@ -48,15 +49,15 @@ class Base { base::ListValue* result); int id() const { return id_; } - DispatcherHost* dispatcher_host() const { return dispatcher_host_; } + DispatcherHost* dispatcher_host() const { return dispatcher_host_.get(); } private: int id_; - DispatcherHost* dispatcher_host_; + base::WeakPtr dispatcher_host_; DISALLOW_COPY_AND_ASSIGN(Base); }; -} // namespace api +} // namespace nwapi #endif // CONTENT_NW_SRC_API_BASE_BASE_H_ diff --git a/src/api/bindings_common.cc b/src/api/bindings_common.cc index 804d4c159d..fbe444edc9 100644 --- a/src/api/bindings_common.cc +++ b/src/api/bindings_common.cc @@ -26,19 +26,21 @@ #include "content/public/renderer/render_view.h" #include "content/public/renderer/render_thread.h" #include "content/public/renderer/v8_value_converter.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/public/web/WebView.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" #include "ui/base/resource/resource_bundle.h" using content::RenderView; using content::RenderThread; using content::V8ValueConverter; -using WebKit::WebFrame; -using WebKit::WebView; - -RenderView* GetCurrentRenderView() { - WebFrame* frame = WebFrame::frameForCurrentContext(); - if (!frame) +using blink::WebFrame; +using blink::WebLocalFrame; +using blink::WebView; + +namespace { +RenderView* GetRenderView(v8::Handle ctx) { + WebLocalFrame* frame = WebLocalFrame::frameForContext(ctx); + if (!frame || !frame->isNodeJS()) return NULL; WebView* view = frame->view(); @@ -49,26 +51,52 @@ RenderView* GetCurrentRenderView() { return render_view; } +} + +RenderView* GetCurrentRenderView() { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Local ctx = isolate->GetCurrentContext(); + return GetRenderView(ctx); +} + +RenderView* GetEnteredRenderView() { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Local ctx = isolate->GetEnteredContext(); + return GetRenderView(ctx); +} + base::StringPiece GetStringResource(int resource_id) { return ResourceBundle::GetSharedInstance().GetRawDataResource(resource_id); } namespace remote { +v8::Handle AllocateId(int routing_id) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::EscapableHandleScope scope(isolate); + + int result = 0; + RenderThread::Get()->Send(new ShellViewHostMsg_AllocateId( + routing_id, + &result)); + return scope.Escape(v8::Integer::New(isolate, result)); +} + v8::Handle AllocateObject(int routing_id, int object_id, const std::string& type, v8::Handle options) { - v8::HandleScope handle_scope; + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::EscapableHandleScope handle_scope(isolate); scoped_ptr converter(V8ValueConverter::create()); converter->SetStripNullFromObjects(true); scoped_ptr value_option( - converter->FromV8Value(options, v8::Context::GetCurrent())); + converter->FromV8Value(options, isolate->GetCurrentContext())); if (!value_option.get() || !value_option->IsType(base::Value::TYPE_DICTIONARY)) - return v8::ThrowException(v8::Exception::Error(v8::String::New( + return isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "Unable to convert 'option' passed to AllocateObject"))); DVLOG(1) << "remote::AllocateObject(routing_id=" << routing_id << ", object_id=" << object_id << ")"; @@ -78,14 +106,15 @@ v8::Handle AllocateObject(int routing_id, object_id, type, *static_cast(value_option.get()))); - return v8::Undefined(); + return v8::Undefined(isolate); } v8::Handle DeallocateObject(int routing_id, int object_id) { RenderThread::Get()->Send(new ShellViewHostMsg_Deallocate_Object( routing_id, object_id)); - return v8::Undefined(); + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + return v8::Undefined(isolate); } v8::Handle CallObjectMethod(int routing_id, @@ -93,13 +122,14 @@ v8::Handle CallObjectMethod(int routing_id, const std::string& type, const std::string& method, v8::Handle args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); scoped_ptr converter(V8ValueConverter::create()); scoped_ptr value_args( - converter->FromV8Value(args, v8::Context::GetCurrent())); + converter->FromV8Value(args, isolate->GetCurrentContext())); if (!value_args.get() || !value_args->IsType(base::Value::TYPE_LIST)) - return v8::ThrowException(v8::Exception::Error(v8::String::New( + return isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "Unable to convert 'args' passed to CallObjectMethod"))); RenderThread::Get()->Send(new ShellViewHostMsg_Call_Object_Method( @@ -108,7 +138,7 @@ v8::Handle CallObjectMethod(int routing_id, type, method, *static_cast(value_args.get()))); - return v8::Undefined(); + return v8::Undefined(isolate); } v8::Handle CallObjectMethodSync(int routing_id, @@ -116,13 +146,14 @@ v8::Handle CallObjectMethodSync(int routing_id, const std::string& type, const std::string& method, v8::Handle args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); scoped_ptr converter(V8ValueConverter::create()); scoped_ptr value_args( - converter->FromV8Value(args, v8::Context::GetCurrent())); + converter->FromV8Value(args, isolate->GetCurrentContext())); if (!value_args.get() || !value_args->IsType(base::Value::TYPE_LIST)) - return v8::ThrowException(v8::Exception::Error(v8::String::New( + return isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "Unable to convert 'args' passed to CallObjectMethodSync"))); base::ListValue result; @@ -133,7 +164,7 @@ v8::Handle CallObjectMethodSync(int routing_id, method, *static_cast(value_args.get()), &result)); - return converter->ToV8Value(&result, v8::Context::GetCurrent()); + return converter->ToV8Value(&result, isolate->GetCurrentContext()); } } // namespace remote diff --git a/src/api/bindings_common.h b/src/api/bindings_common.h index 77157e9a44..f53e6caa2b 100644 --- a/src/api/bindings_common.h +++ b/src/api/bindings_common.h @@ -21,7 +21,7 @@ #ifndef CONTENT_NW_SRC_API_BINDINGS_COMMON_H_ #define CONTENT_NW_SRC_API_BINDINGS_COMMON_H_ -#include "base/string_piece.h" +#include "base/strings/string_piece.h" #include "v8/include/v8.h" namespace content { @@ -30,12 +30,15 @@ class RenderView; // Get RenderView from current js context (only works under window context). content::RenderView* GetCurrentRenderView(); +content::RenderView* GetEnteredRenderView(); // Get string from resource_id. base::StringPiece GetStringResource(int resource_id); namespace remote { +v8::Handle AllocateId(int routing_id); + // Tell browser to allocate a new object. // function AllocateObject(id, name, options); v8::Handle AllocateObject(int routing_id, diff --git a/src/api/clipboard/clipboard.cc b/src/api/clipboard/clipboard.cc index 02a59fcc58..399d13616c 100644 --- a/src/api/clipboard/clipboard.cc +++ b/src/api/clipboard/clipboard.cc @@ -21,15 +21,15 @@ #include "content/nw/src/api/clipboard/clipboard.h" #include "base/values.h" -#include "base/utf_string_conversions.h" -#include "base/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "base/strings/string16.h" #include "content/nw/src/api/dispatcher_host.h" #include "ui/base/clipboard/clipboard.h" -namespace api { +namespace nwapi { Clipboard::Clipboard(int id, - DispatcherHost* dispatcher_host, + const base::WeakPtr& dispatcher_host, const base::DictionaryValue& option) : Base(id, dispatcher_host, option) { } @@ -68,19 +68,19 @@ void Clipboard::SetText(std::string& text) { ui::Clipboard::ObjectMap map; map[ui::Clipboard::CBF_TEXT].push_back( std::vector(text.begin(), text.end())); - clipboard->WriteObjects(ui::Clipboard::BUFFER_STANDARD, map, NULL); + clipboard->WriteObjects(ui::CLIPBOARD_TYPE_COPY_PASTE, map); } std::string Clipboard::GetText() { ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); - string16 text; - clipboard->ReadText(ui::Clipboard::BUFFER_STANDARD, &text); - return UTF16ToUTF8(text); + base::string16 text; + clipboard->ReadText(ui::CLIPBOARD_TYPE_COPY_PASTE, &text); + return base::UTF16ToUTF8(text); } void Clipboard::Clear() { ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); - clipboard->Clear(ui::Clipboard::BUFFER_STANDARD); + clipboard->Clear(ui::CLIPBOARD_TYPE_COPY_PASTE); } -} // namespace api +} // namespace nwapi diff --git a/src/api/clipboard/clipboard.h b/src/api/clipboard/clipboard.h index 27e19712e8..3d8451387f 100644 --- a/src/api/clipboard/clipboard.h +++ b/src/api/clipboard/clipboard.h @@ -24,20 +24,20 @@ #include "base/compiler_specific.h" #include "content/nw/src/api/base/base.h" -namespace api { +namespace nwapi { class Clipboard : public Base { public: Clipboard(int id, - DispatcherHost* dispatcher_host, + const base::WeakPtr& dispatcher_host, const base::DictionaryValue& option); - virtual ~Clipboard(); + ~Clipboard() override; - virtual void Call(const std::string& method, - const base::ListValue& arguments) OVERRIDE; - virtual void CallSync(const std::string& method, + void Call(const std::string& method, + const base::ListValue& arguments) override; + void CallSync(const std::string& method, const base::ListValue& arguments, - base::ListValue* result) OVERRIDE; + base::ListValue* result) override; private: void SetText(std::string& text); @@ -47,6 +47,6 @@ class Clipboard : public Base { DISALLOW_COPY_AND_ASSIGN(Clipboard); }; -} // namespace api +} // namespace nwapi #endif // CONTENT_NW_SRC_API_CLIPBOARD_CLIPBOARD_H_ diff --git a/src/api/clipboard/clipboard.js b/src/api/clipboard/clipboard.js index f4340bdae8..7202d313bb 100644 --- a/src/api/clipboard/clipboard.js +++ b/src/api/clipboard/clipboard.js @@ -30,7 +30,7 @@ Clipboard.prototype.set = function(data, type) { type = 'text'; if (type != 'text') - throw new String("Type of '" + type + "' is not supported"); + throw new TypeError("Type of '" + type + "' is not supported"); nw.callObjectMethod(this, 'Set', [ data, type ]); } @@ -40,7 +40,7 @@ Clipboard.prototype.get = function(type) { type = 'text'; if (type != 'text') - throw new String('Only support getting plain text from Clipboard'); + throw new TypeError('Only support getting plain text from Clipboard'); var result = nw.callObjectMethodSync(this, 'Get', [ type ]); if (type == 'text') diff --git a/src/api/dispatcher.cc b/src/api/dispatcher.cc index 3c9168c982..edfc4965ae 100644 --- a/src/api/dispatcher.cc +++ b/src/api/dispatcher.cc @@ -18,19 +18,40 @@ // ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#define V8_USE_UNSAFE_HANDLES + #include "content/nw/src/api/dispatcher.h" #include "content/nw/src/api/api_messages.h" #include "content/public/renderer/render_view.h" #include "content/renderer/v8_value_converter_impl.h" #include "third_party/node/src/node.h" +#undef CHECK #include "third_party/node/src/req_wrap.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" +#include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" +#include "third_party/WebKit/public/web/WebView.h" #include "v8/include/v8.h" -namespace api { +#undef LOG +#undef ASSERT +#undef FROM_HERE + +#if defined(OS_WIN) +#define _USE_MATH_DEFINES +#include +#endif +#include "third_party/WebKit/Source/config.h" +#include "third_party/WebKit/Source/core/frame/Frame.h" +#include "third_party/WebKit/Source/web/WebLocalFrameImpl.h" +#include "V8HTMLElement.h" + +namespace nwapi { + +static inline v8::Local v8_str(const char* x) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + return v8::String::NewFromUtf8(isolate, x); +} Dispatcher::Dispatcher(content::RenderView* render_view) : content::RenderViewObserver(render_view) { @@ -49,8 +70,8 @@ bool Dispatcher::OnMessageReceived(const IPC::Message& message) { return handled; } -void Dispatcher::DraggableRegionsChanged(WebKit::WebFrame* frame) { - WebKit::WebVector webregions = +void Dispatcher::DraggableRegionsChanged(blink::WebFrame* frame) { + blink::WebVector webregions = frame->document().draggableRegions(); std::vector regions; for (size_t i = 0; i < webregions.size(); ++i) { @@ -65,49 +86,192 @@ void Dispatcher::DraggableRegionsChanged(WebKit::WebFrame* frame) { void Dispatcher::OnEvent(int object_id, std::string event, const base::ListValue& arguments) { - v8::HandleScope scope; - WebKit::WebView* web_view = render_view()->GetWebView(); + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope scope(isolate); + blink::WebView* web_view = render_view()->GetWebView(); if (web_view == NULL) return; DVLOG(1) << "Dispatcher::OnEvent(object_id=" << object_id << ", event=\"" << event << "\")"; content::V8ValueConverterImpl converter; - v8::Handle args = converter.ToV8Value(&arguments, node::g_context); + v8::Local context = + v8::Local::New(isolate, node::g_context); + + v8::Handle args = converter.ToV8Value(&arguments, context); DCHECK(!args.IsEmpty()) << "Invalid 'arguments' in Dispatcher::OnEvent"; v8::Handle argv[] = { - v8::Integer::New(object_id), v8::String::New(event.c_str()), args }; + v8::Integer::New(isolate, object_id), v8_str(event.c_str()), args }; // __nwObjectsRegistry.handleEvent(object_id, event, arguments); v8::Handle val = - node::g_context->Global()->Get(v8::String::New("__nwObjectsRegistry")); + context->Global()->Get(v8_str("__nwObjectsRegistry")); if (val->IsNull() || val->IsUndefined()) return; // need to find out why it's undefined here in debugger v8::Handle objects_registry = val->ToObject(); DVLOG(1) << "handleEvent(object_id=" << object_id << ", event=\"" << event << "\")"; - node::MakeCallback(objects_registry, "handleEvent", 3, argv); + node::MakeCallback(isolate, objects_registry, "handleEvent", 3, argv); +} + +v8::Handle Dispatcher::GetObjectRegistry() { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Local context = + v8::Local::New(isolate, node::g_context); + // need to enter node context to access the registry in + // some cases, e.g. normal frame in #1519 + context->Enter(); + v8::Handle registry = + context->Global()->Get(v8_str("__nwObjectsRegistry")); + context->Exit(); + ASSERT(!(registry->IsNull() || registry->IsUndefined())); + // if (registry->IsNull() || registry->IsUndefined()) + // return v8::Undefined(); + return registry->ToObject(); } -void Dispatcher::ZoomLevelChanged() { - WebKit::WebView* web_view = render_view()->GetWebView(); +v8::Handle Dispatcher::GetWindowId(blink::WebFrame* frame) { + v8::Handle v8win = frame->mainWorldScriptContext()->Global(); + v8::Handle val = v8win->ToObject()->Get(v8_str("__nwWindowId")); + + return val; +} + +void Dispatcher::ZoomLevelChanged(blink::WebView* web_view) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope scope(isolate); + float zoom_level = web_view->zoomLevel(); - v8::Handle v8win = web_view->mainFrame()-> - mainWorldScriptContext()->Global(); - v8::Handle val = v8win->ToObject()->Get(v8::String::New("__nwWindowId")); + v8::Handle val = GetWindowId(web_view->mainFrame()); + + if (val.IsEmpty()) + return; if (val->IsNull() || val->IsUndefined()) return; - v8::Handle registry = - node::g_context->Global()->Get(v8::String::New("__nwObjectsRegistry")); - if (registry->IsNull() || registry->IsUndefined()) + v8::Handle objects_registry = GetObjectRegistry(); + if (objects_registry->IsUndefined()) + return; + + v8::Local args = v8::Array::New(isolate); + args->Set(0, v8::Number::New(isolate, zoom_level)); + v8::Handle argv[] = {val, v8_str("zoom"), args }; + + node::MakeCallback(isolate, objects_registry, "handleEvent", 3, argv); +} + +void Dispatcher::DidCreateDocumentElement(blink::WebLocalFrame* frame) { + documentCallback("document-start", frame); +} + +void Dispatcher::DidFinishDocumentLoad(blink::WebLocalFrame* frame) { + documentCallback("document-end", frame); +} + +void Dispatcher::documentCallback(const char* ev, blink::WebLocalFrame* frame) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + blink::WebView* web_view = render_view()->GetWebView(); + v8::HandleScope scope(isolate); + + if (!web_view) + return; + v8::Context::Scope cscope (web_view->mainFrame()->mainWorldScriptContext()); + + v8::Handle val = GetWindowId(web_view->mainFrame()); + if (val.IsEmpty()) return; - v8::Handle objects_registry = registry->ToObject(); + if (val->IsNull() || val->IsUndefined()) + return; + + v8::Handle objects_registry = GetObjectRegistry(); + if (objects_registry->IsUndefined()) + return; + + v8::Local args = v8::Array::New(isolate); + v8::Handle element = v8::Null(isolate); + blink::LocalFrame* core_frame = blink::toWebLocalFrameImpl(frame)->frame(); + if (core_frame->deprecatedLocalOwner()) { + element = blink::toV8((blink::HTMLElement*)core_frame->deprecatedLocalOwner(), + frame->mainWorldScriptContext()->Global(), + frame->mainWorldScriptContext()->GetIsolate()); + } + args->Set(0, element); + v8::Handle argv[] = {val, v8_str(ev), args }; + + node::MakeCallback(isolate, objects_registry, "handleEvent", 3, argv); +} + +void Dispatcher::willHandleNavigationPolicy( + content::RenderView* rv, + blink::WebFrame* frame, + const blink::WebURLRequest& request, + blink::WebNavigationPolicy* policy, + blink::WebString* manifest) { + + blink::WebView* web_view = rv->GetWebView(); + + if (!web_view) + return; + + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope handleScope(isolate); + + v8::Handle id_val; + if (web_view->mainFrame() && !web_view->mainFrame()->mainWorldScriptContext().IsEmpty()) { + v8::Context::Scope cscope (web_view->mainFrame()->mainWorldScriptContext()); + id_val = nwapi::Dispatcher::GetWindowId(web_view->mainFrame()); + } + if (id_val.IsEmpty()) + return; + if (id_val->IsUndefined() || id_val->IsNull()) + return; + + v8::Handle objects_registry = nwapi::Dispatcher::GetObjectRegistry(); + if (objects_registry->IsUndefined()) + return; + + v8::Context::Scope cscope (web_view->mainFrame()->mainWorldScriptContext()); + + v8::Local args = v8::Array::New(isolate); + v8::Handle element = v8::Null(isolate); + v8::Handle policy_obj = v8::Object::New(isolate); - v8::Local args = v8::Array::New(); - args->Set(0, v8::Number::New(zoom_level)); - v8::Handle argv[] = {val, v8::String::New("zoom"), args }; + blink::LocalFrame* core_frame = blink::toWebLocalFrameImpl(frame)->frame(); + if (core_frame->deprecatedLocalOwner()) { + element = blink::toV8((blink::HTMLElement*)core_frame->deprecatedLocalOwner(), + frame->mainWorldScriptContext()->Global(), + frame->mainWorldScriptContext()->GetIsolate()); + } + args->Set(0, element); + args->Set(1, v8_str(request.url().string().utf8().c_str())); + args->Set(2, policy_obj); + + v8::Handle argv[] = {id_val, v8_str("new-win-policy"), args }; - node::MakeCallback(objects_registry, "handleEvent", 3, argv); + node::MakeCallback(isolate, objects_registry, "handleEvent", 3, argv); + v8::Local manifest_val = policy_obj->Get(v8_str("manifest")); + + //TODO: change this to object + if (manifest_val->IsString()) { + v8::String::Utf8Value manifest_str(manifest_val); + if (manifest) + *manifest = blink::WebString::fromUTF8(*manifest_str); + } + + v8::Local val = policy_obj->Get(v8_str("val")); + if (!val->IsString()) + return; + v8::String::Utf8Value policy_str(val); + if (!strcmp(*policy_str, "ignore")) + *policy = blink::WebNavigationPolicyIgnore; + else if (!strcmp(*policy_str, "download")) + *policy = blink::WebNavigationPolicyDownload; + else if (!strcmp(*policy_str, "current")) + *policy = blink::WebNavigationPolicyCurrentTab; + else if (!strcmp(*policy_str, "new-window")) + *policy = blink::WebNavigationPolicyNewWindow; + else if (!strcmp(*policy_str, "new-popup")) + *policy = blink::WebNavigationPolicyNewPopup; } -} // namespace api + +} // namespace nwapi diff --git a/src/api/dispatcher.h b/src/api/dispatcher.h index b3d5ccbdd3..0193f55d66 100644 --- a/src/api/dispatcher.h +++ b/src/api/dispatcher.h @@ -23,27 +23,49 @@ #include "base/basictypes.h" #include "content/public/renderer/render_view_observer.h" +#include "third_party/WebKit/public/web/WebNavigationPolicy.h" +#include +#include namespace base { class ListValue; } -namespace WebKit { +namespace content { +class RenderView; +} + +namespace blink { class WebFrame; +class WebURLRequest; +class WebView; } -namespace api { +namespace nwapi { class Dispatcher : public content::RenderViewObserver { public: explicit Dispatcher(content::RenderView* render_view); - virtual ~Dispatcher(); + ~Dispatcher() final; + + static v8::Handle GetObjectRegistry(); + static v8::Handle GetWindowId(blink::WebFrame* frame); + static void ZoomLevelChanged(blink::WebView* web_view); + static void willHandleNavigationPolicy( + content::RenderView* rv, + blink::WebFrame* frame, + const blink::WebURLRequest& request, + blink::WebNavigationPolicy* policy, + blink::WebString* manifest); private: // RenderViewObserver implementation. - virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; - virtual void DraggableRegionsChanged(WebKit::WebFrame* frame) OVERRIDE; - virtual void ZoomLevelChanged() OVERRIDE; + bool OnMessageReceived(const IPC::Message& message) override; + void DraggableRegionsChanged(blink::WebFrame* frame) override; + void DidFinishDocumentLoad(blink::WebLocalFrame* frame) override; + void DidCreateDocumentElement(blink::WebLocalFrame* frame) override; + + void documentCallback(const char* ev, blink::WebLocalFrame* frame); void OnEvent(int object_id, std::string event, @@ -52,7 +74,7 @@ class Dispatcher : public content::RenderViewObserver { DISALLOW_COPY_AND_ASSIGN(Dispatcher); }; -} // namespace api +} // namespace nwapi #endif // CONTENT_NW_SRC_API_DISPATCHER_H_ diff --git a/src/api/dispatcher_bindings.cc b/src/api/dispatcher_bindings.cc index 3fefd9245d..44f7c58b3b 100644 --- a/src/api/dispatcher_bindings.cc +++ b/src/api/dispatcher_bindings.cc @@ -21,65 +21,86 @@ #include "content/nw/src/api/dispatcher_bindings.h" #include "base/files/file_path.h" -#include "base/file_util.h" +#include "base/files/file_util.h" #include "base/logging.h" #include "base/values.h" #include "base/command_line.h" -#include "chrome/renderer/static_v8_external_string_resource.h" +#include "content/nw/src/breakpad_linux.h" #include "content/nw/src/api/api_messages.h" #include "content/nw/src/api/bindings_common.h" #include "content/public/renderer/render_view.h" #include "content/public/renderer/render_thread.h" #include "content/public/renderer/v8_value_converter.h" +#include "extensions/renderer/static_v8_external_one_byte_string_resource.h" #include "grit/nw_resources.h" +#include "third_party/node/src/node.h" +#undef CHECK +#include "third_party/node/src/node_internals.h" +#include "third_party/node/src/req_wrap.h" +#include "third_party/WebKit/public/web/WebSecurityPolicy.h" +#include "url/gurl.h" using content::RenderView; using content::V8ValueConverter; using base::FilePath; -namespace api { +namespace nwapi { namespace { + v8::Handle WrapSource(v8::Handle source) { - v8::HandleScope handle_scope; + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::EscapableHandleScope handle_scope(isolate); v8::Handle left = - v8::String::New("(function(nw, exports) {"); - v8::Handle right = v8::String::New("\n})"); - return handle_scope.Close( + v8::String::NewFromUtf8(isolate, "(function(nw, exports, window) {"); + v8::Handle right = v8::String::NewFromUtf8(isolate, "\n})"); + return handle_scope.Escape( v8::String::Concat(left, v8::String::Concat(source, right))); } // Similar to node's `require` function, save functions in `exports`. void RequireFromResource(v8::Handle root, v8::Handle gui, + v8::Handle window, v8::Handle name, int resource_id) { - v8::HandleScope scope; + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope handle_scope(isolate); - v8::Handle source = v8::String::NewExternal( - new StaticV8ExternalAsciiStringResource( + v8::Handle source = v8::String::NewExternal(isolate, + new extensions::StaticV8ExternalOneByteStringResource( GetStringResource(resource_id))); v8::Handle wrapped_source = WrapSource(source); - v8::Handle script(v8::Script::New(wrapped_source, name)); - v8::Handle func = v8::Handle::Cast(script->Run()); - v8::Handle args[] = { root, gui }; - func->Call(root, 2, args); + { + v8::TryCatch try_catch; + v8::Handle script(v8::Script::Compile(wrapped_source, name)); + v8::Handle func = v8::Handle::Cast(script->Run()); + v8::Handle args[] = { root, gui, window }; + func->Call(root, 3, args); + if (try_catch.HasCaught()) { + v8::String::Utf8Value stack(try_catch.StackTrace()); + LOG(FATAL) << *stack; + } + } + } bool MakePathAbsolute(FilePath* file_path) { DCHECK(file_path); FilePath current_directory; - if (!file_util::GetCurrentDirectory(¤t_directory)) + if (!base::GetCurrentDirectory(¤t_directory)) return false; if (file_path->IsAbsolute()) return true; - if (current_directory.empty()) - return file_util::AbsolutePath(file_path); + if (current_directory.empty()) { + *file_path = base::MakeAbsoluteFilePath(*file_path); + return true; + } if (!current_directory.IsAbsolute()) return false; @@ -96,137 +117,183 @@ DispatcherBindings::DispatcherBindings() IDR_NW_API_DISPATCHER_BINDINGS_JS).data(), 0, // num dependencies. NULL, // dependencies array. - GetStringResource( + (int)GetStringResource( IDR_NW_API_DISPATCHER_BINDINGS_JS).size()) { +#if defined(OS_MACOSX) + InitMsgIDMap(); +#endif } DispatcherBindings::~DispatcherBindings() { } v8::Handle -DispatcherBindings::GetNativeFunction(v8::Handle name) { - if (name->Equals(v8::String::New("RequireNwGui"))) - return v8::FunctionTemplate::New(RequireNwGui); - else if (name->Equals(v8::String::New("GetAbsolutePath"))) - return v8::FunctionTemplate::New(GetAbsolutePath); - else if (name->Equals(v8::String::New("GetShellIdForCurrentContext"))) - return v8::FunctionTemplate::New(GetShellIdForCurrentContext); - else if (name->Equals(v8::String::New("GetRoutingIDForCurrentContext"))) - return v8::FunctionTemplate::New(GetRoutingIDForCurrentContext); - else if (name->Equals(v8::String::New("CreateShell"))) - return v8::FunctionTemplate::New(CreateShell); - else if (name->Equals(v8::String::New("AllocateObject"))) - return v8::FunctionTemplate::New(AllocateObject); - else if (name->Equals(v8::String::New("DeallocateObject"))) - return v8::FunctionTemplate::New(DeallocateObject); - else if (name->Equals(v8::String::New("CallObjectMethod"))) - return v8::FunctionTemplate::New(CallObjectMethod); - else if (name->Equals(v8::String::New("CallObjectMethodSync"))) - return v8::FunctionTemplate::New(CallObjectMethodSync); - else if (name->Equals(v8::String::New("CallStaticMethod"))) - return v8::FunctionTemplate::New(CallStaticMethod); - else if (name->Equals(v8::String::New("CallStaticMethodSync"))) - return v8::FunctionTemplate::New(CallStaticMethodSync); - +DispatcherBindings::GetNativeFunctionTemplate( + v8::Isolate* isolate, + v8::Handle name) { + if (name->Equals(v8::String::NewFromUtf8(isolate, "RequireNwGui"))) + return v8::FunctionTemplate::New(isolate, RequireNwGui); + else if (name->Equals(v8::String::NewFromUtf8(isolate, "GetAbsolutePath"))) + return v8::FunctionTemplate::New(isolate, GetAbsolutePath); + else if (name->Equals(v8::String::NewFromUtf8(isolate, "GetShellIdForCurrentContext"))) + return v8::FunctionTemplate::New(isolate, GetShellIdForCurrentContext); + else if (name->Equals(v8::String::NewFromUtf8(isolate, "GetRoutingIDForCurrentContext"))) + return v8::FunctionTemplate::New(isolate, GetRoutingIDForCurrentContext); + else if (name->Equals(v8::String::NewFromUtf8(isolate, "CreateShell"))) + return v8::FunctionTemplate::New(isolate, CreateShell); + else if (name->Equals(v8::String::NewFromUtf8(isolate, "AllocateObject"))) + return v8::FunctionTemplate::New(isolate, AllocateObject); + else if (name->Equals(v8::String::NewFromUtf8(isolate, "DeallocateObject"))) + return v8::FunctionTemplate::New(isolate, DeallocateObject); + else if (name->Equals(v8::String::NewFromUtf8(isolate, "CallObjectMethod"))) + return v8::FunctionTemplate::New(isolate, CallObjectMethod); + else if (name->Equals(v8::String::NewFromUtf8(isolate, "CallObjectMethodSync"))) + return v8::FunctionTemplate::New(isolate, CallObjectMethodSync); + else if (name->Equals(v8::String::NewFromUtf8(isolate, "CallStaticMethod"))) + return v8::FunctionTemplate::New(isolate, CallStaticMethod); + else if (name->Equals(v8::String::NewFromUtf8(isolate, "CallStaticMethodSync"))) + return v8::FunctionTemplate::New(isolate, CallStaticMethodSync); + else if (name->Equals(v8::String::NewFromUtf8(isolate, "CrashRenderer"))) + return v8::FunctionTemplate::New(isolate, CrashRenderer); + else if (name->Equals(v8::String::NewFromUtf8(isolate, "SetCrashDumpDir"))) + return v8::FunctionTemplate::New(isolate, SetCrashDumpDir); + else if (name->Equals(v8::String::NewFromUtf8(isolate, "AllocateId"))) + return v8::FunctionTemplate::New(isolate, AllocateId); +#if defined(OS_MACOSX) + else if (name->Equals(v8::String::NewFromUtf8(isolate, "GetNSStringWithFixup"))) + return v8::FunctionTemplate::New(isolate, GetNSStringWithFixup); + else if (name->Equals(v8::String::NewFromUtf8(isolate, "GetNSStringFWithFixup"))) + return v8::FunctionTemplate::New(isolate, GetNSStringFWithFixup); +#else + else if (name->Equals(v8::String::NewFromUtf8(isolate, "GetNSStringWithFixup"))) + return v8::FunctionTemplate::New(isolate); + else if (name->Equals(v8::String::NewFromUtf8(isolate, "GetNSStringFWithFixup"))) + return v8::FunctionTemplate::New(isolate); +#endif NOTREACHED() << "Trying to get an non-exist function in DispatcherBindings:" << *v8::String::Utf8Value(name); - return v8::FunctionTemplate::New(); + return v8::FunctionTemplate::New(isolate); } // static -v8::Handle -DispatcherBindings::RequireNwGui(const v8::Arguments& args) { - v8::HandleScope scope; +void +DispatcherBindings::RequireNwGui(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::EscapableHandleScope handle_scope(isolate); // Initialize lazily - v8::Local NwGuiSymbol = v8::String::NewSymbol("nwGui"); + v8::Local NwGuiSymbol = v8::String::NewFromUtf8(isolate, "nwGui", v8::String::kInternalizedString); v8::Local NwGuiHidden = args.This()->Get(NwGuiSymbol); - if (NwGuiHidden->IsObject()) - return scope.Close(NwGuiHidden); + if (NwGuiHidden->IsObject()) { + args.GetReturnValue().Set(handle_scope.Escape(NwGuiHidden)); + return; + } + + v8::Local context = isolate->GetEnteredContext(); + v8::Local global = context->Global(); + ASSERT(!global->IsUndefined()); + v8::Local g_context = + v8::Local::New(isolate, node::g_context); - v8::Local NwGui = v8::Object::New(); + g_context->Enter(); + v8::Local NwGui = v8::Object::New(isolate); args.This()->Set(NwGuiSymbol, NwGui); RequireFromResource(args.This(), - NwGui, v8::String::New("base.js"), IDR_NW_API_BASE_JS); + NwGui, global, v8::String::NewFromUtf8(isolate, "base.js"), IDR_NW_API_BASE_JS); RequireFromResource(args.This(), - NwGui, v8::String::New("menuitem.js"), IDR_NW_API_MENUITEM_JS); + NwGui, global, v8::String::NewFromUtf8(isolate, "menuitem.js"), IDR_NW_API_MENUITEM_JS); RequireFromResource(args.This(), - NwGui, v8::String::New("menu.js"), IDR_NW_API_MENU_JS); + NwGui, global, v8::String::NewFromUtf8(isolate, "menu.js"), IDR_NW_API_MENU_JS); RequireFromResource(args.This(), - NwGui, v8::String::New("tray.js"), IDR_NW_API_TRAY_JS); + NwGui, global, v8::String::NewFromUtf8(isolate, "tray.js"), IDR_NW_API_TRAY_JS); RequireFromResource(args.This(), - NwGui, v8::String::New("clipboard.js"), IDR_NW_API_CLIPBOARD_JS); + NwGui, global, v8::String::NewFromUtf8(isolate, "clipboard.js"), IDR_NW_API_CLIPBOARD_JS); RequireFromResource(args.This(), - NwGui, v8::String::New("window.js"), IDR_NW_API_WINDOW_JS); + NwGui, global, v8::String::NewFromUtf8(isolate, "window.js"), IDR_NW_API_WINDOW_JS); RequireFromResource(args.This(), - NwGui, v8::String::New("shell.js"), IDR_NW_API_SHELL_JS); + NwGui, global, v8::String::NewFromUtf8(isolate, "shell.js"), IDR_NW_API_SHELL_JS); RequireFromResource(args.This(), - NwGui, v8::String::New("app.js"), IDR_NW_API_APP_JS); + NwGui, global, v8::String::NewFromUtf8(isolate, "app.js"), IDR_NW_API_APP_JS); + RequireFromResource(args.This(), + NwGui, global, v8::String::NewFromUtf8(isolate, "shortcut.js"), IDR_NW_API_SHORTCUT_JS); + RequireFromResource(args.This(), + NwGui, global, v8::String::NewFromUtf8(isolate, "screen.js"), IDR_NW_API_SCREEN_JS); - return scope.Close(NwGui); + g_context->Exit(); + args.GetReturnValue().Set(handle_scope.Escape(NwGui)); } // static -v8::Handle -DispatcherBindings::GetAbsolutePath(const v8::Arguments& args) { +void +DispatcherBindings::GetAbsolutePath(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); FilePath path = FilePath::FromUTF8Unsafe(*v8::String::Utf8Value(args[0])); MakePathAbsolute(&path); #if defined(OS_POSIX) - return v8::String::New(path.value().c_str()); + args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, path.value().c_str())); #else - return v8::String::New(path.AsUTF8Unsafe().c_str()); + args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, path.AsUTF8Unsafe().c_str())); #endif } // static -v8::Handle -DispatcherBindings::GetShellIdForCurrentContext(const v8::Arguments& args) { +void +DispatcherBindings::GetShellIdForCurrentContext(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); RenderView* render_view = GetCurrentRenderView(); if (!render_view) { - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "Unable to get render view in CallStaticMethodSync"))); + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "Unable to get render view in CallStaticMethodSync")))); + return; } int id = -1; render_view->Send(new ShellViewHostMsg_GetShellId(MSG_ROUTING_NONE, &id)); - return v8::Integer::New(id); + args.GetReturnValue().Set(v8::Integer::New(isolate, id)); } // static -v8::Handle -DispatcherBindings::GetRoutingIDForCurrentContext(const v8::Arguments& args) { +void +DispatcherBindings::GetRoutingIDForCurrentContext(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); RenderView* render_view = GetCurrentRenderView(); if (!render_view) { - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "Unable to get render view in GetRoutingIDForCurrentContext"))); + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "Unable to get render view in GetRoutingIDForCurrentContext")))); + return; } - return v8::Integer::New(render_view->GetRoutingID()); + args.GetReturnValue().Set(v8::Integer::New(isolate, render_view->GetRoutingID())); } // static -v8::Handle -DispatcherBindings::CreateShell(const v8::Arguments& args) { - if (args.Length() < 2) - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "CreateShell requries 2 arguments"))); +void +DispatcherBindings::CreateShell(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + if (args.Length() < 2) { + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "CreateShell requries 2 arguments")))); + return; + } std::string url = *v8::String::Utf8Value(args[0]); scoped_ptr converter(V8ValueConverter::create()); scoped_ptr value_manifest( - converter->FromV8Value(args[1], v8::Context::GetCurrent())); + converter->FromV8Value(args[1], isolate->GetCurrentContext())); if (!value_manifest.get() || !value_manifest->IsType(base::Value::TYPE_DICTIONARY)) { - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "Unable to convert 'options' passed to CreateShell"))); + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "Unable to convert 'options' passed to CreateShell")))); + return; } RenderView* render_view = GetCurrentRenderView(); if (!render_view) { - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "Unable to get render view in CallStaticMethod"))); + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "Unable to get render view in CreateShell")))); + return; } int routing_id = -1; @@ -236,73 +303,99 @@ DispatcherBindings::CreateShell(const v8::Arguments& args) { *static_cast(value_manifest.get()), &routing_id)); - return v8::Integer::New(routing_id); + args.GetReturnValue().Set(v8::Integer::New(isolate, routing_id)); } // static -v8::Handle -DispatcherBindings::AllocateObject(const v8::Arguments& args) { - if (args.Length() < 3) - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "AllocateObject requries 3 arguments"))); +void +DispatcherBindings::AllocateId(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + RenderView* render_view = GetCurrentRenderView(); + if (!render_view) { + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "Unable to get render view in AllocateId")))); + return; + } + + args.GetReturnValue().Set(remote::AllocateId(render_view->GetRoutingID())); +} + +void +DispatcherBindings::AllocateObject(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + if (args.Length() < 3) { + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "AllocateObject requries 3 arguments")))); + return; + } int object_id = args[0]->Int32Value(); std::string name = *v8::String::Utf8Value(args[1]); RenderView* render_view = GetCurrentRenderView(); if (!render_view) { - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "Unable to get render view in AllocateObject"))); + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "Unable to get render view in AllocateObject")))); + return; } - return remote::AllocateObject( - render_view->GetRoutingID(), object_id, name, args[2]); + args.GetReturnValue().Set(remote::AllocateObject(render_view->GetRoutingID(), object_id, name, args[2])); } // static -v8::Handle -DispatcherBindings::DeallocateObject(const v8::Arguments& args) { +void +DispatcherBindings::DeallocateObject(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); RenderView* render_view = GetCurrentRenderView(); if (!render_view) { - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "Unable to get render view in DeallocateObject"))); + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "Unable to get render view in DeallocateObject")))); + return; } - if (args.Length() < 1) - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "DeallocateObject requries 1 arguments"))); - - return remote::DeallocateObject(render_view->GetRoutingID(), - args[0]->Int32Value()); + if (args.Length() < 1) { + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "DeallocateObject requries 1 arguments")))); + return; + } + args.GetReturnValue().Set(remote::DeallocateObject(render_view->GetRoutingID(), args[0]->Int32Value())); } // static -v8::Handle -DispatcherBindings::CallObjectMethod(const v8::Arguments& args) { - if (args.Length() < 4) - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "CallObjectMethod requries 4 arguments"))); +void +DispatcherBindings::CallObjectMethod(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + if (args.Length() < 4) { + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "CallObjectMethod requries 4 arguments")))); + return; + } int object_id = args[0]->Int32Value(); std::string type = *v8::String::Utf8Value(args[1]); std::string method = *v8::String::Utf8Value(args[2]); RenderView* render_view = GetCurrentRenderView(); + if (!render_view) + render_view = GetEnteredRenderView(); if (!render_view) { - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "Unable to get render view in CallObjectMethod"))); + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "Unable to get render view in CallObjectMethod")))); + return; } - return remote::CallObjectMethod( - render_view->GetRoutingID(), object_id, type, method, args[3]); + args.GetReturnValue().Set(remote::CallObjectMethod(render_view->GetRoutingID(), object_id, type, method, args[3])); } // static -v8::Handle DispatcherBindings::CallObjectMethodSync( - const v8::Arguments& args) { - if (args.Length() < 4) - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "CallObjectMethodSync requries 4 arguments"))); +void DispatcherBindings::CallObjectMethodSync( + const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + if (args.Length() < 4) { + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "CallObjectMethodSync requries 4 arguments")))); + return; + } int object_id = args[0]->Int32Value(); std::string type = *v8::String::Utf8Value(args[1]); @@ -310,20 +403,22 @@ v8::Handle DispatcherBindings::CallObjectMethodSync( RenderView* render_view = GetCurrentRenderView(); if (!render_view) { - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "Unable to get render view in CallObjectMethod"))); + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "Unable to get render view in CallObjectMethod")))); + return; } - return remote::CallObjectMethodSync( - render_view->GetRoutingID(), object_id, type, method, args[3]); + args.GetReturnValue().Set(remote::CallObjectMethodSync(render_view->GetRoutingID(), object_id, type, method, args[3])); } // static -v8::Handle DispatcherBindings::CallStaticMethod( - const v8::Arguments& args) { +void DispatcherBindings::CallStaticMethod( + const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); if (args.Length() < 3) { - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "CallStaticMethod requries 3 arguments"))); + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "CallStaticMethod requries 3 arguments")))); + return; } std::string type = *v8::String::Utf8Value(args[0]); @@ -332,17 +427,19 @@ v8::Handle DispatcherBindings::CallStaticMethod( scoped_ptr converter(V8ValueConverter::create()); scoped_ptr value_args( - converter->FromV8Value(args[2], v8::Context::GetCurrent())); + converter->FromV8Value(args[2], isolate->GetCurrentContext())); if (!value_args.get() || !value_args->IsType(base::Value::TYPE_LIST)) { - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "Unable to convert 'args' passed to CallStaticMethod"))); + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "Unable to convert 'args' passed to CallStaticMethod")))); + return; } RenderView* render_view = GetCurrentRenderView(); if (!render_view) { - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "Unable to get render view in CallStaticMethod"))); + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "Unable to get render view in CallStaticMethod")))); + return; } render_view->Send(new ShellViewHostMsg_Call_Static_Method( @@ -350,15 +447,40 @@ v8::Handle DispatcherBindings::CallStaticMethod( type, method, *static_cast(value_args.get()))); - return v8::Undefined(); + args.GetReturnValue().Set(v8::Undefined(isolate)); +} + +// static +void DispatcherBindings::CrashRenderer( + const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + RenderView* render_view = GetCurrentRenderView(); + if (!render_view) { + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "Unable to get render view in CallObjectMethod")))); + return; + } + int* ptr = NULL; + *ptr = 1; +} + +// static +void DispatcherBindings::SetCrashDumpDir( + const v8::FunctionCallbackInfo& args) { +#if defined(OS_WIN) || defined(OS_MACOSX) + //std::string path = *v8::String::Utf8Value(args[0]); + //FIXME: SetCrashDumpPath(path.c_str()); +#endif } // static -v8::Handle DispatcherBindings::CallStaticMethodSync( - const v8::Arguments& args) { +void DispatcherBindings::CallStaticMethodSync( + const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); if (args.Length() < 3) { - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "CallStaticMethodSync requries 3 arguments"))); + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "CallStaticMethodSync requries 3 arguments")))); + return; } std::string type = *v8::String::Utf8Value(args[0]); @@ -366,18 +488,64 @@ v8::Handle DispatcherBindings::CallStaticMethodSync( scoped_ptr converter(V8ValueConverter::create()); + RenderView* render_view = GetEnteredRenderView(); + if (!render_view) { + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "Unable to get render view in CallStaticMethodSync")))); + return; + } + + if (type == "App" && method == "getProxyForURL") { + std::string url = *v8::String::Utf8Value(args[2]); + GURL gurl(url); + if (!gurl.is_valid()) { + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "Invalid URL passed to App.getProxyForURL()")))); + return; + } + std::string proxy; + bool result = content::RenderThread::Get()->ResolveProxy(gurl, &proxy); + if (!result) { + args.GetReturnValue().Set(v8::Undefined(isolate)); + return; + } + args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, proxy.c_str())); + return; + } + + if (type == "App" && method == "AddOriginAccessWhitelistEntry") { + std::string sourceOrigin = *v8::String::Utf8Value(args[2]); + std::string destinationProtocol = *v8::String::Utf8Value(args[3]); + std::string destinationHost = *v8::String::Utf8Value(args[4]); + bool allowDestinationSubdomains = args[5]->ToBoolean()->Value(); + + blink::WebSecurityPolicy::addOriginAccessWhitelistEntry(GURL(sourceOrigin), + blink::WebString::fromUTF8(destinationProtocol), + blink::WebString::fromUTF8(destinationHost), + allowDestinationSubdomains); + args.GetReturnValue().Set(v8::Undefined(isolate)); + return; + } + if (type == "App" && method == "RemoveOriginAccessWhitelistEntry") { + std::string sourceOrigin = *v8::String::Utf8Value(args[2]); + std::string destinationProtocol = *v8::String::Utf8Value(args[3]); + std::string destinationHost = *v8::String::Utf8Value(args[4]); + bool allowDestinationSubdomains = args[5]->ToBoolean()->Value(); + + blink::WebSecurityPolicy::removeOriginAccessWhitelistEntry(GURL(sourceOrigin), + blink::WebString::fromUTF8(destinationProtocol), + blink::WebString::fromUTF8(destinationHost), + allowDestinationSubdomains); + args.GetReturnValue().Set(v8::Undefined(isolate)); + return; + } scoped_ptr value_args( - converter->FromV8Value(args[2], v8::Context::GetCurrent())); + converter->FromV8Value(args[2], isolate->GetCurrentContext())); if (!value_args.get() || !value_args->IsType(base::Value::TYPE_LIST)) { - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "Unable to convert 'args' passed to CallStaticMethodSync"))); - } - - RenderView* render_view = GetCurrentRenderView(); - if (!render_view) { - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "Unable to get render view in CallStaticMethodSync"))); + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "Unable to convert 'args' passed to CallStaticMethodSync")))); + return; } base::ListValue result; @@ -387,7 +555,7 @@ v8::Handle DispatcherBindings::CallStaticMethodSync( method, *static_cast(value_args.get()), &result)); - return converter->ToV8Value(&result, v8::Context::GetCurrent()); + args.GetReturnValue().Set(converter->ToV8Value(&result, isolate->GetCurrentContext())); } -} // namespace api +} // namespace nwapi diff --git a/src/api/dispatcher_bindings.h b/src/api/dispatcher_bindings.h index 069ac0627e..28cf641d51 100644 --- a/src/api/dispatcher_bindings.h +++ b/src/api/dispatcher_bindings.h @@ -25,44 +25,54 @@ #include "base/compiler_specific.h" #include "v8/include/v8.h" -namespace api { +namespace nwapi { class DispatcherBindings : public v8::Extension { public: DispatcherBindings(); - virtual ~DispatcherBindings(); + ~DispatcherBindings() final; // v8::Extension implementation. - virtual v8::Handle - GetNativeFunction(v8::Handle name) OVERRIDE; + v8::Handle + GetNativeFunctionTemplate( + v8::Isolate* isolate, + v8::Handle name) override; private: // Helper functions for bindings. - static v8::Handle RequireNwGui(const v8::Arguments& args); - static v8::Handle GetAbsolutePath(const v8::Arguments& args); + static void RequireNwGui(const v8::FunctionCallbackInfo& args); + static void GetAbsolutePath(const v8::FunctionCallbackInfo& args); // Get Shell's corresponding js object's id. - static v8::Handle GetShellIdForCurrentContext( - const v8::Arguments& args); + static void GetShellIdForCurrentContext( + const v8::FunctionCallbackInfo& args); // Get current routing id. - static v8::Handle GetRoutingIDForCurrentContext( - const v8::Arguments& args); + static void GetRoutingIDForCurrentContext( + const v8::FunctionCallbackInfo& args); // Create new shell and returns its routing id. - static v8::Handle CreateShell(const v8::Arguments& args); + static void CreateShell(const v8::FunctionCallbackInfo& args); // Remote objects. - static v8::Handle AllocateObject(const v8::Arguments& args); - static v8::Handle DeallocateObject(const v8::Arguments& args); - static v8::Handle CallObjectMethod(const v8::Arguments& args); - static v8::Handle CallObjectMethodSync(const v8::Arguments& args); - static v8::Handle CallStaticMethod(const v8::Arguments& args); - static v8::Handle CallStaticMethodSync(const v8::Arguments& args); + static void AllocateId(const v8::FunctionCallbackInfo& args); + static void AllocateObject(const v8::FunctionCallbackInfo& args); + static void DeallocateObject(const v8::FunctionCallbackInfo& args); + static void CallObjectMethod(const v8::FunctionCallbackInfo& args); + static void CallObjectMethodSync(const v8::FunctionCallbackInfo& args); + static void CallStaticMethod(const v8::FunctionCallbackInfo& args); + static void CallStaticMethodSync(const v8::FunctionCallbackInfo& args); + static void CrashRenderer(const v8::FunctionCallbackInfo& args); + static void SetCrashDumpDir(const v8::FunctionCallbackInfo& args); +#if defined(OS_MACOSX) + static void InitMsgIDMap(); + static void GetNSStringWithFixup(const v8::FunctionCallbackInfo& args); + static void GetNSStringFWithFixup(const v8::FunctionCallbackInfo& args); +#endif DISALLOW_COPY_AND_ASSIGN(DispatcherBindings); }; -} // namespace api +} // namespace nwapi #endif // CONTENT_NW_SRC_API_DISPATCHER_BINDINGS_H_ diff --git a/src/api/dispatcher_bindings.js b/src/api/dispatcher_bindings.js index 659c1ebc7a..6c1939f505 100644 --- a/src/api/dispatcher_bindings.js +++ b/src/api/dispatcher_bindings.js @@ -28,12 +28,18 @@ var nwDispatcher = nwDispatcher || {}; native function GetRoutingIDForCurrentContext(); native function CreateShell(); + native function AllocateId(); native function AllocateObject(); native function DeallocateObject(); native function CallObjectMethod(); native function CallObjectMethodSync(); native function CallStaticMethod(); native function CallStaticMethodSync(); + native function CrashRenderer(); + native function SetCrashDumpDir(); + + native function GetNSStringWithFixup(); + native function GetNSStringFWithFixup(); nwDispatcher.requireNwGui = RequireNwGui; @@ -41,7 +47,7 @@ var nwDispatcher = nwDispatcher || {}; nwDispatcher.allocateObject = function(object, option) { var v8_util = process.binding('v8_util'); - var id = global.__nwObjectsRegistry.allocateId(); + var id = AllocateId(); AllocateObject(id, v8_util.getConstructorName(object), option); // Store object id and make it readonly @@ -90,4 +96,12 @@ var nwDispatcher = nwDispatcher || {}; nwDispatcher.getShellIdForCurrentContext = GetShellIdForCurrentContext; nwDispatcher.getRoutingIDForCurrentContext = GetRoutingIDForCurrentContext; nwDispatcher.createShell = CreateShell; + + nwDispatcher.crashRenderer = CrashRenderer; + nwDispatcher.setCrashDumpDir = SetCrashDumpDir; + nwDispatcher.allocateId = AllocateId; + + nwDispatcher.getNSStringWithFixup = GetNSStringWithFixup; + nwDispatcher.getNSStringFWithFixup = GetNSStringFWithFixup; + })(); diff --git a/src/api/dispatcher_bindings_mac.mm b/src/api/dispatcher_bindings_mac.mm new file mode 100644 index 0000000000..e77be987fe --- /dev/null +++ b/src/api/dispatcher_bindings_mac.mm @@ -0,0 +1,78 @@ +#include "content/nw/src/api/dispatcher_bindings.h" + +#include + +#include "base/containers/hash_tables.h" +#include "grit/nw_strings.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/l10n/l10n_util_mac.h" + +#import + +namespace nwapi { + +typedef struct { + std::string msgstr; + int msgid; +} MsgMapEntry; + +const MsgMapEntry msg_map[] = { + { "IDS_ABOUT_MAC", IDS_ABOUT_MAC }, + { "IDS_HIDE_APP_MAC", IDS_HIDE_APP_MAC}, + { "IDS_HIDE_OTHERS_MAC", IDS_HIDE_OTHERS_MAC}, + { "IDS_SHOW_ALL_MAC", IDS_SHOW_ALL_MAC }, + { "IDS_EXIT_MAC", IDS_EXIT_MAC }, + { "IDS_EDIT_MENU_MAC", IDS_EDIT_MENU_MAC }, + { "IDS_EDIT_UNDO_MAC", IDS_EDIT_UNDO_MAC }, + { "IDS_EDIT_REDO_MAC", IDS_EDIT_REDO_MAC }, + { "IDS_CUT_MAC", IDS_CUT_MAC }, + { "IDS_COPY_MAC", IDS_COPY_MAC }, + { "IDS_PASTE_MAC", IDS_PASTE_MAC }, + { "IDS_EDIT_DELETE_MAC", IDS_EDIT_DELETE_MAC }, + { "IDS_EDIT_SELECT_ALL_MAC", IDS_EDIT_SELECT_ALL_MAC }, + { "IDS_WINDOW_MENU_MAC", IDS_WINDOW_MENU_MAC }, + { "IDS_MINIMIZE_WINDOW_MAC", IDS_MINIMIZE_WINDOW_MAC }, + { "IDS_CLOSE_WINDOW_MAC", IDS_CLOSE_WINDOW_MAC }, + { "IDS_ALL_WINDOWS_FRONT_MAC", IDS_ALL_WINDOWS_FRONT_MAC }, +}; + +typedef base::hash_map MsgIDMap; +MsgIDMap g_msgid_map; + +void DispatcherBindings::InitMsgIDMap() { + g_msgid_map.clear(); + for (size_t i = 0; i < arraysize(msg_map); i++) { + g_msgid_map.insert(std::make_pair(msg_map[i].msgstr, msg_map[i].msgid)); + } +} + +// static +void DispatcherBindings::GetNSStringWithFixup( + const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + std::string msgstr = *v8::String::Utf8Value(args[0]); + MsgIDMap::iterator it = g_msgid_map.find(msgstr); + if (it != g_msgid_map.end()) { + int msgid = it->second; + args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, [l10n_util::GetNSStringWithFixup(msgid) UTF8String])); + return; + } + args.GetReturnValue().Set(v8::Undefined(isolate)); +} + +// static +void DispatcherBindings::GetNSStringFWithFixup( + const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + std::string msgstr = *v8::String::Utf8Value(args[0]); + base::string16 arg = *v8::String::Value(args[1]); + MsgIDMap::iterator it = g_msgid_map.find(msgstr); + if (it != g_msgid_map.end()) { + int msgid = it->second; + args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, [l10n_util::GetNSStringFWithFixup(msgid, arg) UTF8String])); + return; + } + args.GetReturnValue().Set(v8::Undefined(isolate)); +} + +} // namespace nwapi diff --git a/src/api/dispatcher_host.cc b/src/api/dispatcher_host.cc index 47f2b7c6cc..dbbcd7b86c 100644 --- a/src/api/dispatcher_host.cc +++ b/src/api/dispatcher_host.cc @@ -21,38 +21,80 @@ #include "content/nw/src/api/dispatcher_host.h" #include "base/logging.h" +#include "base/run_loop.h" +#include "base/threading/thread_restrictions.h" #include "base/values.h" +#include "content/browser/child_process_security_policy_impl.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/nw/src/api/api_messages.h" #include "content/nw/src/api/app/app.h" #include "content/nw/src/api/base/base.h" #include "content/nw/src/api/clipboard/clipboard.h" +#include "content/nw/src/api/event/event.h" #include "content/nw/src/api/menu/menu.h" #include "content/nw/src/api/menuitem/menuitem.h" +#include "content/nw/src/api/screen/screen.h" +#include "content/nw/src/api/screen/desktop_capture_monitor.h" #include "content/nw/src/api/shell/shell.h" +#include "content/nw/src/api/shortcut/shortcut.h" #include "content/nw/src/api/tray/tray.h" #include "content/nw/src/api/window/window.h" #include "content/nw/src/common/shell_switches.h" #include "content/nw/src/shell_browser_context.h" #include "content/nw/src/nw_shell.h" #include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" using content::WebContents; using content::ShellBrowserContext; +using content::Shell; -namespace api { +namespace nwapi { -DispatcherHost::DispatcherHost(content::RenderViewHost* render_view_host) - : content::RenderViewHostObserver(render_view_host) { +IDMap nwapi::DispatcherHost::objects_registry_; +int nwapi::DispatcherHost::next_object_id_ = 1; +static std::map g_dispatcher_host_map; + +DispatcherHost::DispatcherHost(content::RenderViewHost* host) + : content::WebContentsObserver(content::WebContents::FromRenderViewHost(host)), + render_view_host_(host), + weak_ptr_factory_(this), + run_loop_(NULL) { + g_dispatcher_host_map[render_view_host_] = this; } DispatcherHost::~DispatcherHost() { + g_dispatcher_host_map.erase(render_view_host()); + std::set::iterator it; + for (it = objects_.begin(); it != objects_.end(); it++) { + if (objects_registry_.Lookup(*it)) + objects_registry_.Remove(*it); + } +} + +DispatcherHost* +FindDispatcherHost(content::RenderViewHost* render_view_host) { + std::map::iterator it + = g_dispatcher_host_map.find(render_view_host); + if (it == g_dispatcher_host_map.end()) + return NULL; + return it->second; } +void DispatcherHost::ClearObjectRegistry() { + objects_registry_.Clear(); +} + +// static Base* DispatcherHost::GetApiObject(int id) { return objects_registry_.Lookup(id); } +// static +int DispatcherHost::AllocateId() { + return next_object_id_++; +} + void DispatcherHost::SendEvent(Base* object, const std::string& event, const base::ListValue& arguments) { @@ -61,11 +103,24 @@ void DispatcherHost::SendEvent(Base* object, } bool DispatcherHost::Send(IPC::Message* message) { - return content::RenderViewHostObserver::Send(message); + return render_view_host_->Send(message); } -bool DispatcherHost::OnMessageReceived(const IPC::Message& message) { +void DispatcherHost::quit_run_loop() { + if (run_loop_) + run_loop_->Quit(); + run_loop_ = NULL; +} + +bool DispatcherHost::OnMessageReceived( + content::RenderViewHost* render_view_host, + const IPC::Message& message) { + if (render_view_host != render_view_host_) + return false; + bool handled = true; + base::ThreadRestrictions::ScopedAllowIO allow_io; + base::ThreadRestrictions::ScopedAllowWait allow_wait; IPC_BEGIN_MESSAGE_MAP(DispatcherHost, message) IPC_MESSAGE_HANDLER(ShellViewHostMsg_Allocate_Object, OnAllocateObject) IPC_MESSAGE_HANDLER(ShellViewHostMsg_Deallocate_Object, OnDeallocateObject) @@ -79,12 +134,21 @@ bool DispatcherHost::OnMessageReceived(const IPC::Message& message) { OnUncaughtException); IPC_MESSAGE_HANDLER(ShellViewHostMsg_GetShellId, OnGetShellId); IPC_MESSAGE_HANDLER(ShellViewHostMsg_CreateShell, OnCreateShell); + IPC_MESSAGE_HANDLER(ShellViewHostMsg_AllocateId, OnAllocateId); + IPC_MESSAGE_HANDLER(ShellViewHostMsg_SetForceClose, OnSetForceClose); IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; } +void DispatcherHost::RenderViewHostChanged(content::RenderViewHost* old_host, + content::RenderViewHost* new_host) { + // LOG(INFO) << "RenderViewHostChanged(" << this << "): " << old_host << " --> " << new_host << " ; " << render_view_host_; + if (render_view_host_ != new_host) + delete this; +} + void DispatcherHost::OnAllocateObject(int object_id, const std::string& type, const base::DictionaryValue& option) { @@ -93,26 +157,35 @@ void DispatcherHost::OnAllocateObject(int object_id, << " option:" << option; if (type == "Menu") { - objects_registry_.AddWithID(new Menu(object_id, this, option), object_id); + objects_registry_.AddWithID(new Menu(object_id, weak_ptr_factory_.GetWeakPtr(), option), object_id); } else if (type == "MenuItem") { objects_registry_.AddWithID( - new MenuItem(object_id, this, option), object_id); + new MenuItem(object_id, weak_ptr_factory_.GetWeakPtr(), option), object_id); } else if (type == "Tray") { - objects_registry_.AddWithID(new Tray(object_id, this, option), object_id); + objects_registry_.AddWithID(new Tray(object_id, weak_ptr_factory_.GetWeakPtr(), option), object_id); } else if (type == "Clipboard") { objects_registry_.AddWithID( - new Clipboard(object_id, this, option), object_id); + new Clipboard(object_id, weak_ptr_factory_.GetWeakPtr(), option), object_id); } else if (type == "Window") { - objects_registry_.AddWithID(new Window(object_id, this, option), object_id); + objects_registry_.AddWithID(new Window(object_id, weak_ptr_factory_.GetWeakPtr(), option), object_id); + } else if (type == "Shortcut") { + objects_registry_.AddWithID(new Shortcut(object_id, weak_ptr_factory_.GetWeakPtr(), option), object_id); + } else if (type == "Screen") { + objects_registry_.AddWithID(new EventListener(object_id, weak_ptr_factory_.GetWeakPtr(), option), object_id); + }else if (type == "DesktopCaptureMonitor") { + objects_registry_.AddWithID(new DesktopCaptureMonitor(object_id, weak_ptr_factory_.GetWeakPtr(), option), object_id); } else { LOG(ERROR) << "Allocate an object of unknown type: " << type; - objects_registry_.AddWithID(new Base(object_id, this, option), object_id); + objects_registry_.AddWithID(new Base(object_id, weak_ptr_factory_.GetWeakPtr(), option), object_id); } + objects_.insert(object_id); } void DispatcherHost::OnDeallocateObject(int object_id) { DLOG(INFO) << "OnDeallocateObject: object_id:" << object_id; - objects_registry_.Remove(object_id); + if (objects_registry_.Lookup(object_id)) + objects_registry_.Remove(object_id); + objects_.erase(object_id); } void DispatcherHost::OnCallObjectMethod( @@ -126,8 +199,13 @@ void DispatcherHost::OnCallObjectMethod( << " arguments:" << arguments; Base* object = GetApiObject(object_id); - DCHECK(object) << "Unknown object: " << object_id; - object->Call(method, arguments); + if (object) + object->Call(method, arguments); + else + DLOG(WARNING) << "Unknown object: " << object_id + << " type:" << type + << " method:" << method + << " arguments:" << arguments; } void DispatcherHost::OnCallObjectMethodSync( @@ -142,8 +220,13 @@ void DispatcherHost::OnCallObjectMethodSync( << " arguments:" << arguments; Base* object = GetApiObject(object_id); - DCHECK(object) << "Unknown object: " << object_id; - object->CallSync(method, arguments, result); + if (object) + object->CallSync(method, arguments, result); + else + DLOG(WARNING) << "Unknown object: " << object_id + << " type:" << type + << " method:" << method + << " arguments:" << arguments; } void DispatcherHost::OnCallStaticMethod( @@ -156,10 +239,10 @@ void DispatcherHost::OnCallStaticMethod( << " arguments:" << arguments; if (type == "Shell") { - api::Shell::Call(method, arguments); + nwapi::Shell::Call(method, arguments); return; } else if (type == "App") { - api::App::Call(method, arguments); + nwapi::App::Call(method, arguments); return; } @@ -179,7 +262,10 @@ void DispatcherHost::OnCallStaticMethodSync( if (type == "App") { content::Shell* shell = content::Shell::FromRenderViewHost(render_view_host()); - api::App::Call(shell, method, arguments, result); + nwapi::App::Call(shell, method, arguments, result, this); + return; + } else if (type == "Screen") { + nwapi::Screen::Call(this, method, arguments, result); return; } @@ -201,8 +287,7 @@ void DispatcherHost::OnGetShellId(int* id) { void DispatcherHost::OnCreateShell(const std::string& url, const base::DictionaryValue& manifest, int* routing_id) { - WebContents* base_web_contents = - content::Shell::FromRenderViewHost(render_view_host())->web_contents(); + WebContents* base_web_contents = web_contents(); ShellBrowserContext* browser_context = static_cast(base_web_contents->GetBrowserContext()); scoped_ptr new_manifest(manifest.DeepCopy()); @@ -214,18 +299,45 @@ void DispatcherHost::OnCreateShell(const std::string& url, WebContents::CreateParams create_params(browser_context, new_renderer ? NULL : base_web_contents->GetSiteInstance()); + std::string filename; + if (new_manifest->GetString(switches::kmInjectJSDocStart, &filename)) + create_params.nw_inject_js_doc_start = filename; + if (new_manifest->GetString(switches::kmInjectJSDocEnd, &filename)) + create_params.nw_inject_js_doc_end = filename; + if (new_manifest->GetString(switches::kmInjectCSS, &filename)) + create_params.nw_inject_css_fn = filename; + WebContents* web_contents = content::WebContentsImpl::CreateWithOpener( create_params, static_cast(base_web_contents)); + content::Shell::Create(base_web_contents, - GURL(url), - new_manifest.get(), - web_contents); + GURL(url), + new_manifest.get(), + web_contents); - if (new_renderer) + if (new_renderer) { browser_context->set_pinning_renderer(true); + } *routing_id = web_contents->GetRoutingID(); + + int object_id = 0; + if (new_manifest->GetInteger("object_id", &object_id)) { + DispatcherHost* dhost = FindDispatcherHost(web_contents->GetRenderViewHost()); + dhost->OnAllocateObject(object_id, "Window", *new_manifest.get()); + } +} + +void DispatcherHost::OnAllocateId(int * ret) { + *ret = AllocateId(); +} + +void DispatcherHost::OnSetForceClose(bool force, int* ret) { + content::Shell* shell = + content::Shell::FromRenderViewHost(render_view_host()); + shell->set_force_close(force); + *ret = 0; } -} // namespace api +} // namespace nwapi diff --git a/src/api/dispatcher_host.h b/src/api/dispatcher_host.h index a14713d1ec..cdd8326875 100644 --- a/src/api/dispatcher_host.h +++ b/src/api/dispatcher_host.h @@ -23,52 +23,84 @@ #include "base/basictypes.h" #include "base/id_map.h" -#include "content/public/browser/render_view_host_observer.h" +#include "base/memory/weak_ptr.h" +#include "content/public/browser/web_contents_observer.h" #include +#include namespace base { class DictionaryValue; class ListValue; +class RunLoop; } namespace WebKit { class WebFrame; } -namespace api { +namespace content { +class Shell; +} + +namespace nwapi { class Base; -class DispatcherHost : public content::RenderViewHostObserver { +class DispatcherHost : public content::WebContentsObserver { public: explicit DispatcherHost(content::RenderViewHost* render_view_host); - virtual ~DispatcherHost(); + ~DispatcherHost() final; // Get C++ object from its id. - Base* GetApiObject(int id); + static Base* GetApiObject(int id); + + static int AllocateId(); // Helper function to convert type. template - T* GetApiObject(int id) { + static T* GetApiObject(int id) { return static_cast(GetApiObject(id)); } + static void ClearObjectRegistry(); + // Send event to C++ object's corresponding js object. void SendEvent(Base* object, const std::string& event, const base::ListValue& arguments); - virtual bool Send(IPC::Message* message) OVERRIDE; + bool Send(IPC::Message* message) override; + void RenderViewHostChanged(content::RenderViewHost* old_host, + content::RenderViewHost* new_host) override; content::RenderViewHost* render_view_host() const { - return content::RenderViewHostObserver::render_view_host(); + return render_view_host_; } + void set_run_loop(base::RunLoop* run_loop) { run_loop_ = run_loop; } + void quit_run_loop(); + base::RunLoop* run_loop() { return run_loop_; } + private: - IDMap objects_registry_; + content::RenderViewHost* render_view_host_; + friend class content::Shell; + + static IDMap objects_registry_; + static int next_object_id_; + + std::set objects_; + + // Factory to generate weak pointer + base::WeakPtrFactory weak_ptr_factory_; + + base::RunLoop* run_loop_; // RenderViewHostObserver implementation. - virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + // WebContentsObserver implementation: + bool OnMessageReceived( + content::RenderViewHost* render_view_host, + const IPC::Message& message) override; + void OnAllocateObject(int object_id, const std::string& type, @@ -95,10 +127,14 @@ class DispatcherHost : public content::RenderViewHostObserver { void OnCreateShell(const std::string& url, const base::DictionaryValue& manifest, int* routing_id); + void OnAllocateId(int* ret); + void OnSetForceClose(bool force, int* ret); DISALLOW_COPY_AND_ASSIGN(DispatcherHost); }; -} // namespace api +nwapi::DispatcherHost* FindDispatcherHost(content::RenderViewHost* render_view_host); + +} // namespace nwapi #endif // CONTENT_NW_SRC_API_DISPATCHER_HOST_H_ diff --git a/src/api/event/event.cc b/src/api/event/event.cc new file mode 100644 index 0000000000..b42649314e --- /dev/null +++ b/src/api/event/event.cc @@ -0,0 +1,41 @@ +// Copyright (c) 2014 Jefry Tedjokusumo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +#include "content/nw/src/api/event/event.h" +#include "base/values.h" +#include "content/nw/src/api/dispatcher_host.h" +#include "ui/gfx/screen.h" + + +namespace nwapi { + +EventListener::EventListener(int id, + const base::WeakPtr& dispatcher_host, + const base::DictionaryValue& option) : Base(id, dispatcher_host, option) { + +} + +EventListener::~EventListener() { + for (std::map::iterator i = listerners_.begin(); i != listerners_.end(); i++) { + delete i->second; + } +} + +} // namespace nwapi diff --git a/src/api/event/event.h b/src/api/event/event.h new file mode 100644 index 0000000000..cfe81a6bf7 --- /dev/null +++ b/src/api/event/event.h @@ -0,0 +1,83 @@ +// Copyright (c) 2014 Jefry Tedjokusumo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +#ifndef CONTENT_NW_SRC_API_EVENT_EVENT_H_ +#define CONTENT_NW_SRC_API_EVENT_EVENT_H_ + + +#include "base/basictypes.h" + +#include "content/nw/src/api/base/base.h" +#include "ui/gfx/display_observer.h" + +#include + +namespace nwapi { + +class BaseEvent { + friend class EventListener; + DISALLOW_COPY_AND_ASSIGN(BaseEvent); + +protected: + BaseEvent(){} + virtual ~BaseEvent(){} +}; + +class EventListener : public Base { + std::map listerners_; + +public: + EventListener(int id, + const base::WeakPtr& dispatcher_host, + const base::DictionaryValue& option); + + ~EventListener() override; + + static int getUID() { + static int id = 0; + return ++id; + } + + template T* AddListener() { + std::map::iterator i = listerners_.find(T::id); + if (i==listerners_.end()) { + T* listener_object = new T(this); + listerners_[T::id] = listener_object; + return listener_object; + } + return NULL; + } + + template bool RemoveListener() { + std::map::iterator i = listerners_.find(T::id); + if (i!=listerners_.end()) { + delete i->second; + listerners_.erase(i); + return true; + } + return false; + } +private: + DISALLOW_COPY_AND_ASSIGN(EventListener); +}; + +} // namespace nwapi + +#endif //CONTENT_NW_SRC_API_EVENT_EVENT_H_ diff --git a/src/api/menu/menu.cc b/src/api/menu/menu.cc index 203525f02b..688603fc98 100644 --- a/src/api/menu/menu.cc +++ b/src/api/menu/menu.cc @@ -25,12 +25,12 @@ #include "content/nw/src/api/menuitem/menuitem.h" #include "content/nw/src/nw_shell.h" -namespace api { +namespace nwapi { Menu::Menu(int id, - DispatcherHost* dispatcher_host, + const base::WeakPtr& dispatcher_host, const base::DictionaryValue& option) - : Base(id, dispatcher_host, option) { + : Base(id, dispatcher_host, option), enable_show_event_(false) { Create(option); } @@ -63,10 +63,12 @@ void Menu::Call(const std::string& method, arguments.GetInteger(1, &y); Popup(x, y, content::Shell::FromRenderViewHost( dispatcher_host()->render_view_host())); + } else if (method == "EnableShowEvent") { + arguments.GetBoolean(0, &enable_show_event_); } else { NOTREACHED() << "Invalid call to Menu method:" << method << " arguments:" << arguments; } } -} // namespace api +} // namespace nwapi diff --git a/src/api/menu/menu.h b/src/api/menu/menu.h index e15c8da0ed..0b9148d260 100644 --- a/src/api/menu/menu.h +++ b/src/api/menu/menu.h @@ -28,28 +28,33 @@ #include #include +#if defined(OS_WIN) +#include "ui/views/controls/menu/native_menu_win.h" +#endif + #if defined(OS_MACOSX) #if __OBJC__ @class NSMenu; +@class NWMenuDelegate; #else class NSMenu; +class NWMenuDelegate; #endif // __OBJC__ namespace nw { class NativeWindowCocoa; } -#elif defined(TOOLKIT_GTK) -#include +#elif defined(OS_WIN) || defined(OS_LINUX) +#include "content/nw/src/api/menu/menu_delegate.h" +#include "chrome/browser/status_icons/status_icon_menu_model.h" +#include "ui/views/focus/focus_manager.h" namespace nw { -class NativeWindowGtk; +class NativeWindowAura; } -#elif defined(OS_WIN) -#include "content/nw/src/api/menu/menu_delegate_win.h" -#include "ui/views/controls/menu/native_menu_win.h" -namespace nw { -class NativeWindowWin; +namespace nwapi { +class Menu; } namespace ui { @@ -63,7 +68,10 @@ class NwMenuModel : public SimpleMenuModel { NwMenuModel(Delegate* delegate); // Overridden from MenuModel: - virtual bool HasIcons() const OVERRIDE; + bool HasIcons() const override; + +protected: + friend class nwapi::Menu; }; } // namespace ui @@ -74,19 +82,28 @@ namespace content { class Shell; } -namespace api { +namespace nwapi { class MenuItem; class Menu : public Base { public: Menu(int id, - DispatcherHost* dispatcher_host, + const base::WeakPtr& dispatcher_host, const base::DictionaryValue& option); - virtual ~Menu(); + ~Menu() override; + + void Call(const std::string& method, + const base::ListValue& arguments) override; - virtual void Call(const std::string& method, - const base::ListValue& arguments) OVERRIDE; +#if defined(OS_WIN) || defined(OS_LINUX) + void UpdateKeys(views::FocusManager *focus_manager); + ui::NwMenuModel* model() { return menu_model_.get(); } +#endif + + bool enable_show_event() { return enable_show_event_; } + protected: + bool enable_show_event_; private: friend class MenuItem; @@ -100,17 +117,40 @@ class Menu : public Base { void Remove(MenuItem* menu_item, int pos); void Popup(int x, int y, content::Shell*); +#if defined(OS_LINUX) + std::vector menu_items; +#endif + #if defined(OS_MACOSX) friend class nw::NativeWindowCocoa; NSMenu* menu_; -#elif defined(TOOLKIT_GTK) - friend class nw::NativeWindowGtk; - GtkWidget* menu_; + NWMenuDelegate* menu_delegate_; +#elif defined(OS_LINUX) + friend class nw::NativeWindowAura; + + views::FocusManager *focus_manager_; + std::vector menu_items_; + nw::NativeWindowAura* window_; + // Flag to indicate the menu has been modified since last show, so we should + // rebuild the menu before next show. + bool is_menu_modified_; + + scoped_ptr menu_delegate_; + scoped_ptr menu_model_; + void UpdateStates(); + #elif defined(OS_WIN) - friend class nw::NativeWindowWin; + friend class nw::NativeWindowAura; void Rebuild(const HMENU *parent_menu = NULL); - + void UpdateStates(); + void SetWindow(nw::NativeWindowAura* win); + + //**Never Try to free this pointer** + //We get it from top widget + views::FocusManager *focus_manager_; + std::vector menu_items_; + nw::NativeWindowAura* window_; // Flag to indicate the menu has been modified since last show, so we should // rebuild the menu before next show. bool is_menu_modified_; @@ -126,6 +166,6 @@ class Menu : public Base { DISALLOW_COPY_AND_ASSIGN(Menu); }; -} // namespace api +} // namespace nwapi #endif // CONTENT_NW_SRC_API_MENU_MENU_H_ diff --git a/src/api/menu/menu.js b/src/api/menu/menu.js index c33bfd489c..82b284e36b 100644 --- a/src/api/menu/menu.js +++ b/src/api/menu/menu.js @@ -19,13 +19,15 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. var v8_util = process.binding('v8_util'); +var EventEmitter = process.EventEmitter; + function Menu(option) { if (typeof option != 'object') option = { type: 'contextmenu' }; if (option.type != 'contextmenu' && option.type != 'menubar') - throw new String('Invalid menu type: ' + option.type); + throw new TypeError('Invalid menu type: ' + option.type); this.type = option.type; v8_util.setHiddenValue(this, 'items', []); @@ -38,12 +40,12 @@ Menu.prototype.__defineGetter__('items', function() { }); Menu.prototype.__defineSetter__('items', function(val) { - throw new String('Menu.items is immutable'); + throw new Error('Menu.items is immutable'); }); Menu.prototype.append = function(menu_item) { if (v8_util.getConstructorName(menu_item) != 'MenuItem') - throw new String("Menu.append() requires a valid MenuItem"); + throw new TypeError("Menu.append() requires a valid MenuItem"); this.items.push(menu_item); nw.callObjectMethod(this, 'Append', [ menu_item.id ]); @@ -69,4 +71,126 @@ Menu.prototype.popup = function(x, y) { nw.callObjectMethod(this, 'Popup', [ x, y ]); } +if (require('os').platform() === 'darwin'){ + Menu.prototype.on = Menu.prototype.addListener = function(ev, callback) { + if (ev == 'show') { + nw.callObjectMethod(this, 'EnableShowEvent', [ true ]); + } + // Call parent. + EventEmitter.prototype.addListener.apply(this, arguments); + } + + Menu.prototype.removeListener = function(ev, callback) { + // Call parent. + EventEmitter.prototype.removeListener.apply(this, arguments); + if (ev == 'show' && EventEmitter.listenerCount(this, 'show') === 0) { + nw.callObjectMethod(this, 'EnableShowEvent', [ false ]); + } + } + + Menu.prototype.createMacBuiltin = function (app_name, options) { + var appleMenu = new Menu(), + options = options || {}; + + appleMenu.append(new exports.MenuItem({ + label: nw.getNSStringFWithFixup("IDS_ABOUT_MAC", app_name), + selector: "orderFrontStandardAboutPanel:" + })); + appleMenu.append(new exports.MenuItem({ + type: "separator" + })); + appleMenu.append(new exports.MenuItem({ + label: nw.getNSStringFWithFixup("IDS_HIDE_APP_MAC", app_name), + selector: "hide:", + key: "h" + })); + appleMenu.append(new exports.MenuItem({ + label: nw.getNSStringWithFixup("IDS_HIDE_OTHERS_MAC"), + selector: "hideOtherApplications:", + key: "h", + modifiers: "cmd-alt" + })); + appleMenu.append(new exports.MenuItem({ + label: nw.getNSStringWithFixup("IDS_SHOW_ALL_MAC"), + selector: "unhideAllApplications:", + })); + appleMenu.append(new exports.MenuItem({ + type: "separator" + })); + appleMenu.append(new exports.MenuItem({ + label: nw.getNSStringFWithFixup("IDS_EXIT_MAC", app_name), + selector: "closeAllWindowsQuit:", + key: "q" + })); + this.append(new exports.MenuItem({ label:'', submenu: appleMenu})); + + if (!options.hideEdit) { + var editMenu = new Menu(); + editMenu.append(new exports.MenuItem({ + label: nw.getNSStringWithFixup("IDS_EDIT_UNDO_MAC"), + selector: "undo:", + key: "z" + })); + editMenu.append(new exports.MenuItem({ + label: nw.getNSStringWithFixup("IDS_EDIT_REDO_MAC"), + selector: "redo:", + key: "z", + modifiers: "cmd-shift" + })); + editMenu.append(new exports.MenuItem({ + type: "separator" + })); + editMenu.append(new exports.MenuItem({ + label: nw.getNSStringWithFixup("IDS_CUT_MAC"), + selector: "cut:", + key: "x" + })); + editMenu.append(new exports.MenuItem({ + label: nw.getNSStringWithFixup("IDS_COPY_MAC"), + selector: "copy:", + key: "c" + })); + editMenu.append(new exports.MenuItem({ + label: nw.getNSStringWithFixup("IDS_PASTE_MAC"), + selector: "paste:", + key: "v" + })); + editMenu.append(new exports.MenuItem({ + label: nw.getNSStringWithFixup("IDS_EDIT_DELETE_MAC"), + selector: "delete:", + key: "" + })); + editMenu.append(new exports.MenuItem({ + label: nw.getNSStringWithFixup("IDS_EDIT_SELECT_ALL_MAC"), + selector: "selectAll:", + key: "a" + })); + this.append(new exports.MenuItem({ label: nw.getNSStringWithFixup("IDS_EDIT_MENU_MAC"), + submenu: editMenu})); + } + + if (!options.hideWindow) { + var winMenu = new Menu(); + winMenu.append(new exports.MenuItem({ + label: nw.getNSStringWithFixup("IDS_MINIMIZE_WINDOW_MAC"), + selector: "performMiniaturize:", + key: "m" + })); + winMenu.append(new exports.MenuItem({ + label: nw.getNSStringWithFixup("IDS_CLOSE_WINDOW_MAC"), + selector: "performClose:", + key: "w" + })); + winMenu.append(new exports.MenuItem({ + type: "separator" + })); + winMenu.append(new exports.MenuItem({ + label: nw.getNSStringWithFixup("IDS_ALL_WINDOWS_FRONT_MAC"), + selector: "arrangeInFront:", + })); + this.append(new exports.MenuItem({ label: nw.getNSStringWithFixup("IDS_WINDOW_MENU_MAC"), + submenu: winMenu})); + } + } +} exports.Menu = Menu; diff --git a/src/api/menu/menu_delegate_win.cc b/src/api/menu/menu_delegate.cc similarity index 83% rename from src/api/menu/menu_delegate_win.cc rename to src/api/menu/menu_delegate.cc index 15765b013c..a094211020 100644 --- a/src/api/menu/menu_delegate_win.cc +++ b/src/api/menu/menu_delegate.cc @@ -1,92 +1,109 @@ -// Copyright (c) 2012 Intel Corp -// Copyright (c) 2012 The Chromium Authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co -// pies of the Software, and to permit persons to whom the Software is furnished -// to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in al -// l copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM -// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES -// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH -// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -#include "content/nw/src/api/menu/menu_delegate_win.h" - -#include "base/logging.h" -#include "base/string16.h" -#include "content/nw/src/api/dispatcher_host.h" -#include "content/nw/src/api/menuitem/menuitem.h" - -namespace api { - -MenuDelegate::MenuDelegate(DispatcherHost* dispatcher_host) - : dispatcher_host_(dispatcher_host) { -} - -MenuDelegate::~MenuDelegate() { -} - -bool MenuDelegate::IsCommandIdChecked(int command_id) const { - if (command_id < 0) - return false; - - MenuItem* item = dispatcher_host_->GetApiObject(command_id); - return item->is_checked_; -} - -bool MenuDelegate::IsCommandIdEnabled(int command_id) const { - if (command_id < 0) - return false; - - MenuItem* item = dispatcher_host_->GetApiObject(command_id); - return item->is_enabled_; -} - -bool MenuDelegate::IsItemForCommandIdDynamic(int command_id) const { - if (command_id < 0) - return false; - - MenuItem* item = dispatcher_host_->GetApiObject(command_id); - return item->is_modified_; -} - -string16 MenuDelegate::GetLabelForCommandId(int command_id) const { - MenuItem* item = dispatcher_host_->GetApiObject(command_id); - return item->label_; -} - -bool MenuDelegate::GetIconForCommandId(int command_id, - gfx::Image* icon) const { - MenuItem* item = dispatcher_host_->GetApiObject(command_id); - if (item->icon_.IsEmpty()) - return false; - - *icon = item->icon_; - return true; -} - -void MenuDelegate::ExecuteCommand(int command_id) { - if (command_id < 0) - return; - - MenuItem* item = dispatcher_host_->GetApiObject(command_id); - item->OnClick(); -} - -bool MenuDelegate::HasIcon(int command_id) { - if (command_id < 0) - return false; - - MenuItem* item = dispatcher_host_->GetApiObject(command_id); - return !item->icon_.IsEmpty(); -} - -} // namespace api +// Copyright (c) 2012 Intel Corp +// Copyright (c) 2012 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "content/nw/src/api/menu/menu_delegate.h" + +#include "base/logging.h" +#include "base/strings/string16.h" +#include "content/nw/src/api/dispatcher_host.h" +#include "content/nw/src/api/menuitem/menuitem.h" + +namespace nwapi { + +MenuDelegate::MenuDelegate(DispatcherHost* dispatcher_host) + : dispatcher_host_(dispatcher_host) { +} + +MenuDelegate::~MenuDelegate() { +} + +bool MenuDelegate::IsCommandIdChecked(int command_id) const { + if (command_id < 0) + return false; + + MenuItem* item = dispatcher_host_->GetApiObject(command_id); + return item->is_checked_; +} + +bool MenuDelegate::IsCommandIdEnabled(int command_id) const { + if (command_id < 0) + return false; + + MenuItem* item = dispatcher_host_->GetApiObject(command_id); + if (!item) + return false; + return item->is_enabled_; +} + +bool MenuDelegate::IsItemForCommandIdDynamic(int command_id) const { + if (command_id < 0) + return false; + + MenuItem* item = dispatcher_host_->GetApiObject(command_id); + if (!item) + return false; + return item->is_modified_; +} + +base::string16 MenuDelegate::GetLabelForCommandId(int command_id) const { + MenuItem* item = dispatcher_host_->GetApiObject(command_id); + return item->label_; +} + + +bool MenuDelegate::GetAcceleratorForCommandId( + int command_id, + ui::Accelerator* accelerator) { + return false; +} + +bool MenuDelegate::GetIconForCommandId(int command_id, + gfx::Image* icon) const { + MenuItem* item = dispatcher_host_->GetApiObject(command_id); + if (!item) + return false; + if (item->icon_.IsEmpty()) + return false; + + *icon = item->icon_; + return true; +} + +void MenuDelegate::ExecuteCommand(int command_id, int event_flags) { + if (command_id < 0) + return; + + MenuItem* item = dispatcher_host_->GetApiObject(command_id); + if (!item) + return; + item->OnClick(); +} + +bool MenuDelegate::HasIcon(int command_id) { + if (command_id < 0) + return false; + + MenuItem* item = dispatcher_host_->GetApiObject(command_id); + if (!item) + return false; + return !item->icon_.IsEmpty(); +} + +} // namespace nwapi diff --git a/src/api/menu/menu_delegate_win.h b/src/api/menu/menu_delegate.h similarity index 69% rename from src/api/menu/menu_delegate_win.h rename to src/api/menu/menu_delegate.h index be030f40e9..5723cfda8b 100644 --- a/src/api/menu/menu_delegate_win.h +++ b/src/api/menu/menu_delegate.h @@ -1,59 +1,59 @@ -// Copyright (c) 2012 Intel Corp -// Copyright (c) 2012 The Chromium Authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co -// pies of the Software, and to permit persons to whom the Software is furnished -// to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in al -// l copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM -// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES -// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH -// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -#ifndef CONTENT_NW_SRC_API_MENU_MENU_DELEGATE_H_ -#define CONTENT_NW_SRC_API_MENU_MENU_DELEGATE_H_ - -#include "ui/base/models/simple_menu_model.h" - -namespace api { - -class DispatcherHost; - -class MenuDelegate : public ui::SimpleMenuModel::Delegate { - public: - MenuDelegate(DispatcherHost* dispatcher_host); - virtual ~MenuDelegate(); - - virtual bool IsCommandIdChecked(int command_id) const OVERRIDE; - virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE; - - virtual bool GetAcceleratorForCommandId( - int command_id, - ui::Accelerator* accelerator) { return false; } - - virtual bool IsItemForCommandIdDynamic(int command_id) const OVERRIDE; - virtual string16 GetLabelForCommandId(int command_id) const OVERRIDE; - virtual bool GetIconForCommandId(int command_id, - gfx::Image* icon) const OVERRIDE; - - virtual void ExecuteCommand(int command_id) OVERRIDE; - - virtual bool HasIcon(int command_id) OVERRIDE; - - private: - DispatcherHost* dispatcher_host_; - - DISALLOW_COPY_AND_ASSIGN(MenuDelegate); -}; - -} // namespace api - -#endif // CONTENT_NW_SRC_API_MENU_MENU_DELEGATE_H_ +// Copyright (c) 2012 Intel Corp +// Copyright (c) 2012 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef CONTENT_NW_SRC_API_MENU_MENU_DELEGATE_H_ +#define CONTENT_NW_SRC_API_MENU_MENU_DELEGATE_H_ + +#include "ui/base/models/simple_menu_model.h" + +namespace nwapi { + +class DispatcherHost; + +class MenuDelegate : public ui::SimpleMenuModel::Delegate { + public: + MenuDelegate(DispatcherHost* dispatcher_host); + ~MenuDelegate() override; + + bool IsCommandIdChecked(int command_id) const override; + bool IsCommandIdEnabled(int command_id) const override; + + bool GetAcceleratorForCommandId( + int command_id, + ui::Accelerator* accelerator) override; + + bool IsItemForCommandIdDynamic(int command_id) const override; + base::string16 GetLabelForCommandId(int command_id) const override; + bool GetIconForCommandId(int command_id, + gfx::Image* icon) const override; + + void ExecuteCommand(int command_id, int event_flags) override; + + bool HasIcon(int command_id) override; + + private: + DispatcherHost* dispatcher_host_; + + DISALLOW_COPY_AND_ASSIGN(MenuDelegate); +}; + +} // namespace nwapi + +#endif // CONTENT_NW_SRC_API_MENU_MENU_DELEGATE_H_ diff --git a/src/common/gpu_internals.h b/src/api/menu/menu_delegate_mac.h similarity index 76% rename from src/common/gpu_internals.h rename to src/api/menu/menu_delegate_mac.h index a8a1f27774..9421784bd4 100644 --- a/src/common/gpu_internals.h +++ b/src/api/menu/menu_delegate_mac.h @@ -18,10 +18,21 @@ // ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -#ifndef CONTENT_NW_SRC_COMMON_GPU_INTERNALS_H_ -#define CONTENT_NW_SRC_COMMON_GPU_INTERNALS_H_ +#ifndef CONTENT_NW_SRC_API_MENU_MENU_DELEGATE_MAC_H_ +#define CONTENT_NW_SRC_API_MENU_MENU_DELEGATE_MAC_H_ -void PrintGpuInfo(); -void PrintClientInfo(); +#import -#endif // CONTENT_NW_SRC_COMMON_GPU_INTERNALS_H_ +namespace nwapi { +class Menu; +} + +@interface NWMenuDelegate : NSObject { + @private + nwapi::Menu* nwmenu_; +} + +- (id)initWithMenu:(nwapi::Menu*)menu; + +@end +#endif // CONTENT_NW_SRC_API_MENU_MENU_DELEGATE_MAC_H_ diff --git a/src/api/menu/menu_delegate_mac.mm b/src/api/menu/menu_delegate_mac.mm new file mode 100644 index 0000000000..88ed52c903 --- /dev/null +++ b/src/api/menu/menu_delegate_mac.mm @@ -0,0 +1,64 @@ +// Copyright (c) 2012 Intel Corp +// Copyright (c) 2012 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "base/run_loop.h" +#include "content/nw/src/api/dispatcher_host.h" +#include "content/nw/src/api/menu/menu.h" +#include "content/nw/src/api/menu/menu_delegate_mac.h" +#include "content/nw/src/browser/native_window.h" +#include "content/nw/src/nw_shell.h" + +@implementation NWMenuDelegate + +- (id)initWithMenu:(nwapi::Menu*) menu { + if ((self = [super init])) { + nwmenu_ = menu; + } + return self; +} + +- (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action { + return NO; +} + +- (void)menuNeedsUpdate:(NSMenu*)menu { + + if (!nwmenu_->enable_show_event() || nwmenu_->dispatcher_host()->run_loop()) + return; + + // NSEvent* event = [NSApp currentEvent]; + // NSLog (@"%@\n", event); + // Cocoa will try to populate menu on every keystoke of the key equivlants, + // which is slow. The following bypassed it + + // if ([event type] != NSSystemDefined || [event subtype] == 8) + // return; + + if (!nwmenu_->enable_show_event()) + return; + + base::ListValue args; + base::RunLoop run_loop; + nwmenu_->dispatcher_host()->set_run_loop(&run_loop); + nwmenu_->dispatcher_host()->SendEvent(nwmenu_, "show", args); + run_loop.Run(); +} + +@end diff --git a/src/api/menu/menu_gtk.cc b/src/api/menu/menu_gtk.cc index 24e952f6b7..893c0ab45d 100644 --- a/src/api/menu/menu_gtk.cc +++ b/src/api/menu/menu_gtk.cc @@ -27,8 +27,10 @@ #include "content/public/browser/web_contents.h" #include "content/public/browser/render_widget_host_view.h" #include "ui/gfx/point.h" +#include "vector" +#include "gtk/gtk.h" -namespace api { +namespace nwapi { namespace { @@ -86,6 +88,7 @@ void PointMenuPositionFunc(GtkMenu* menu, } // namespace void Menu::Create(const base::DictionaryValue& option) { + gtk_accel_group = NULL; std::string type; if (option.GetString("type", &type) && type == "menubar") menu_ = gtk_menu_bar_new(); @@ -102,25 +105,60 @@ void Menu::Destroy() { } void Menu::Append(MenuItem* menu_item) { + menu_items.push_back(menu_item); + if (GTK_IS_ACCEL_GROUP(gtk_accel_group)){ + menu_item->UpdateKeys(gtk_accel_group); + } gtk_menu_shell_append(GTK_MENU_SHELL(menu_), menu_item->menu_item_); } void Menu::Insert(MenuItem* menu_item, int pos) { + std::vector::iterator begin = menu_items.begin(); + menu_items.insert(begin+pos,menu_item); + if (GTK_IS_ACCEL_GROUP(gtk_accel_group)){ + menu_item->UpdateKeys(gtk_accel_group); + } gtk_menu_shell_insert(GTK_MENU_SHELL(menu_), menu_item->menu_item_, pos); } void Menu::Remove(MenuItem* menu_item, int pos) { + std::vector::iterator begin = menu_items.begin(); + menu_items.erase(begin+pos); gtk_container_remove(GTK_CONTAINER(menu_), menu_item->menu_item_); } void Menu::Popup(int x, int y, content::Shell* shell) { - GdkEventButton* event = shell->web_contents()->GetRenderWidgetHostView()-> - GetLastMouseDown(); + GdkEventButton* event = NULL; //FIXME: shell->web_contents()->GetRenderWidgetHostView()->GetLastMouseDown(); uint32_t triggering_event_time = event ? event->time : GDK_CURRENT_TIME; - gfx::Point point(event->x_root, event->y_root); - gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, + gfx::Point point; + if (!event) { + // gfx::Rect bounds = shell->web_contents()->GetRenderWidgetHostView()->GetViewBounds(); + // point = gfx::Point(x + bounds.x(), y + bounds.y()); + DVLOG(1) << "no last mouse down event"; + point = gfx::Point(x, y); + }else + point = gfx::Point(event->x_root, event->y_root); + + gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, PointMenuPositionFunc, &point, 3, triggering_event_time); } -} // namespace api +void Menu::UpdateKeys(GtkAccelGroup *gtk_accel_group){ + this->gtk_accel_group = gtk_accel_group; + if (!GTK_IS_ACCEL_GROUP(gtk_accel_group)){ + return ; + } else { + std::vector::iterator menu_item_iterator = menu_items.begin(); + std::vector::iterator menu_item_end = menu_items.end(); + while (menu_item_iterator != menu_item_end){ + MenuItem *menu_item = *menu_item_iterator; + if (menu_item!=NULL && GTK_IS_MENU_ITEM(menu_item->menu_item_)){ + menu_item->UpdateKeys(gtk_accel_group); + } + ++menu_item_iterator; + } + } +} + +} // namespace nwapi diff --git a/src/api/menu/menu_mac.mm b/src/api/menu/menu_mac.mm index 8affc03602..34a27a1079 100644 --- a/src/api/menu/menu_mac.mm +++ b/src/api/menu/menu_mac.mm @@ -1,16 +1,16 @@ // Copyright (c) 2012 Intel Corp // Copyright (c) 2012 The Chromium Authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy +// +// Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co // pies of the Software, and to permit persons to whom the Software is furnished // to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in al // l copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM // PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES // S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS @@ -20,32 +20,29 @@ #include "content/nw/src/api/menu/menu.h" -#include "base/message_loop.h" +#include "base/message_loop/message_loop.h" #include "base/mac/scoped_sending_event.h" #include "base/values.h" #import #include "content/public/browser/web_contents.h" -#include "content/public/browser/web_contents_view.h" +#include "content/nw/src/api/dispatcher_host.h" +#include "content/nw/src/api/menu/menu_delegate_mac.h" #include "content/nw/src/api/menuitem/menuitem.h" #include "content/nw/src/browser/native_window_mac.h" #include "content/nw/src/nw_shell.h" -namespace api { +namespace nwapi { void Menu::Create(const base::DictionaryValue& option) { menu_ = [[NSMenu alloc] initWithTitle:@"NW Menu"]; [menu_ setAutoenablesItems:NO]; - - std::string type; - if (option.GetString("type", &type) && type == "menubar") { - // Preserve the apple menu. - [menu_ addItem:[[[NSMenuItem alloc] - initWithTitle:@"" action:nil keyEquivalent:@""] autorelease]]; - } + menu_delegate_ = [[NWMenuDelegate alloc] initWithMenu:this]; + [menu_ setDelegate:menu_delegate_]; } void Menu::Destroy() { [menu_ release]; + [menu_delegate_ release]; } void Menu::Append(MenuItem* menu_item) { @@ -65,7 +62,7 @@ NSWindow* window = static_cast(shell->window())->window(); NSEvent* currentEvent = [NSApp currentEvent]; - NSView* web_view = shell->web_contents()->GetView()->GetNativeView(); + NSView* web_view = shell->web_contents()->GetNativeView(); NSPoint position = { x, web_view.bounds.size.height - y }; NSTimeInterval eventTime = [currentEvent timestamp]; NSEvent* clickEvent = [NSEvent mouseEventWithType:NSRightMouseDown @@ -80,7 +77,7 @@ { // Make sure events can be pumped while the menu is up. - MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current()); + base::MessageLoop::ScopedNestableTaskAllower allow(base::MessageLoop::current()); // One of the events that could be pumped is |window.close()|. // User-initiated event-tracking loops protect against this by @@ -96,4 +93,4 @@ } } -} // namespace api +} // namespace nwapi diff --git a/src/api/menu/menu_win.cc b/src/api/menu/menu_views.cc similarity index 69% rename from src/api/menu/menu_win.cc rename to src/api/menu/menu_views.cc index 96050b2973..11d8d9ae97 100644 --- a/src/api/menu/menu_win.cc +++ b/src/api/menu/menu_views.cc @@ -21,20 +21,31 @@ #include "content/nw/src/api/menu/menu.h" #include "base/values.h" +#include "base/strings/utf_string_conversions.h" #include "content/nw/src/api/dispatcher_host.h" #include "content/nw/src/api/menuitem/menuitem.h" -#include "content/nw/src/browser/native_window_win.h" +#include "content/nw/src/browser/native_window_aura.h" #include "content/nw/src/nw_shell.h" #include "content/public/browser/web_contents.h" -#include "content/public/browser/web_contents_view.h" #include "skia/ext/image_operations.h" +#include "ui/aura/client/screen_position_client.h" +#include "ui/aura/window.h" +#include "ui/aura/window_tree_host.h" +#include "ui/views/controls/menu/menu_runner.h" +#include "ui/views/widget/widget.h" +#include "ui/views/focus/focus_manager.h" +#include "vector" + +#if defined(OS_WIN) #include "ui/gfx/gdi_util.h" #include "ui/gfx/icon_util.h" #include "ui/views/controls/menu/menu_2.h" -#include "ui/views/widget/widget.h" +#endif namespace { +#if defined(OS_WIN) + HBITMAP GetNativeBitmapFromSkBitmap(const SkBitmap& bitmap) { int width = bitmap.width(); int height = bitmap.height(); @@ -56,6 +67,7 @@ HBITMAP GetNativeBitmapFromSkBitmap(const SkBitmap& bitmap) { return native_bitmap; } +#endif } // namespace @@ -71,28 +83,41 @@ bool NwMenuModel::HasIcons() const { } // namespace ui -namespace api { +namespace nwapi { +#if defined(OS_WIN) // The width of the icon for the menuitem static const int kIconWidth = 16; // The height of the icon for the menuitem static const int kIconHeight = 16; +#endif void Menu::Create(const base::DictionaryValue& option) { is_menu_modified_ = true; menu_delegate_.reset(new MenuDelegate(dispatcher_host())); menu_model_.reset(new ui::NwMenuModel(menu_delegate_.get())); +#if defined(OS_WIN) menu_.reset(new views::NativeMenuWin(menu_model_.get(), NULL)); +#endif + + focus_manager_ = NULL; + window_ = NULL; std::string type; + +#if defined(OS_WIN) if (option.GetString("type", &type) && type == "menubar") menu_->set_is_popup_menu(false); +#endif + menu_items_.empty(); } void Menu::Destroy() { +#if defined(OS_WIN) for (size_t index = 0; index < icon_bitmaps_.size(); ++index) { ::DeleteObject(icon_bitmaps_[index]); } +#endif } void Menu::Append(MenuItem* menu_item) { @@ -107,6 +132,8 @@ void Menu::Append(MenuItem* menu_item) { menu_model_->AddSeparator(ui::NORMAL_SEPARATOR); is_menu_modified_ = true; + menu_items_.push_back(menu_item); + menu_item->menu_ = this; } void Menu::Insert(MenuItem* menu_item, int pos) { @@ -121,25 +148,45 @@ void Menu::Insert(MenuItem* menu_item, int pos) { menu_model_->InsertSeparatorAt(pos, ui::NORMAL_SEPARATOR); is_menu_modified_ = true; + menu_item->menu_ = this; + } void Menu::Remove(MenuItem* menu_item, int pos) { - menu_model_->RemoveAt(pos); + menu_model_->RemoveItemAt(pos); is_menu_modified_ = true; + menu_item->menu_ = NULL; } void Menu::Popup(int x, int y, content::Shell* shell) { - Rebuild(); + // Rebuild(); // Map point from document to screen. - POINT screen_point = { x, y }; - ClientToScreen(shell->web_contents()->GetView()->GetNativeView(), - &screen_point); - - menu_->RunMenuAt(gfx::Point(screen_point.x, screen_point.y), - views::Menu2::ALIGN_TOPLEFT); + gfx::Point screen_point(x, y); + + // Convert from content coordinates to window coordinates. + // This code copied from chrome_web_contents_view_delegate_views.cc + aura::Window* web_contents_window = + shell->web_contents()->GetNativeView(); + aura::Window* root_window = web_contents_window->GetRootWindow(); + aura::client::ScreenPositionClient* screen_position_client = + aura::client::GetScreenPositionClient(root_window); + if (screen_position_client) { + screen_position_client->ConvertPointToScreen(web_contents_window, + &screen_point); + } + views::MenuRunner runner(menu_model_.get(), views::MenuRunner::CONTEXT_MENU); + if (views::MenuRunner::MENU_DELETED == + runner.RunMenuAt(static_cast(shell->window())->window(), + NULL, + gfx::Rect(screen_point, gfx::Size()), + views::MENU_ANCHOR_TOPRIGHT, + ui::MENU_SOURCE_NONE)) + return; + // menu_->RunMenuAt(screen_point, views::Menu2::ALIGN_TOPLEFT); } +#if defined(OS_WIN) void Menu::Rebuild(const HMENU *parent_menu) { if (is_menu_modified_) { // Refresh menu before show. @@ -184,5 +231,41 @@ void Menu::Rebuild(const HMENU *parent_menu) { is_menu_modified_ = false; } } +#endif + +void Menu::UpdateKeys(views::FocusManager *focus_manager){ + if (focus_manager == NULL){ + return ; + } else { + focus_manager_ = focus_manager; + std::vector::iterator it = menu_items_.begin(); + while(it!=menu_items_.end()){ + (*it)->UpdateKeys(focus_manager); + ++it; + } + } +} + +void Menu::UpdateStates() { +#if defined(OS_WIN) + if (window_) + window_->menu_->menu_->UpdateStates(); +#endif +} + +#if defined(OS_WIN) +void Menu::SetWindow(nw::NativeWindowAura* win) { + window_ = win; + for (int model_index = 0; + model_index < menu_model_->GetItemCount(); + ++model_index) { + int command_id = menu_model_->GetCommandIdAt(model_index); + MenuItem* item = dispatcher_host()->GetApiObject(command_id); + if (item != NULL && item->submenu_) { + item->submenu_->SetWindow(win); + } + } +} +#endif -} // namespace api +} // namespace nwapi diff --git a/src/api/menuitem/menuitem.cc b/src/api/menuitem/menuitem.cc index 24188ea5b5..e706617f66 100644 --- a/src/api/menuitem/menuitem.cc +++ b/src/api/menuitem/menuitem.cc @@ -27,10 +27,10 @@ #include -namespace api { +namespace nwapi { MenuItem::MenuItem(int id, - DispatcherHost* dispatcher_host, + const base::WeakPtr& dispatcher_host, const base::DictionaryValue& option) : Base(id, dispatcher_host, option) { Create(option); @@ -50,6 +50,10 @@ void MenuItem::Call(const std::string& method, std::string icon; arguments.GetString(0, &icon); SetIcon(icon); + } else if (method == "SetIconIsTemplate") { + bool isTemplate; + arguments.GetBoolean(0, &isTemplate); + SetIconIsTemplate(isTemplate); } else if (method == "SetTooltip") { std::string tooltip; arguments.GetString(0, &tooltip); @@ -66,10 +70,20 @@ void MenuItem::Call(const std::string& method, int object_id = 0; arguments.GetInteger(0, &object_id); SetSubmenu(dispatcher_host()->GetApiObject(object_id)); +#if defined(OS_MACOSX) + } else if (method == "SetKey") { + std::string key; + arguments.GetString(0, &key); + SetKey(key); + } else if (method == "SetModifiers") { + std::string mod; + arguments.GetString(0, &mod); + SetModifiers(mod); +#endif } else { NOTREACHED() << "Invalid call to MenuItem method:" << method << " arguments:" << arguments; } } -} // namespace api +} // namespace nwapi diff --git a/src/api/menuitem/menuitem.h b/src/api/menuitem/menuitem.h index 1efcd0d1d2..84af31c199 100644 --- a/src/api/menuitem/menuitem.h +++ b/src/api/menuitem/menuitem.h @@ -1,16 +1,16 @@ // Copyright (c) 2012 Intel Corp // Copyright (c) 2012 The Chromium Authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy +// +// Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co // pies of the Software, and to permit persons to whom the Software is furnished // to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in al // l copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM // PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES // S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS @@ -19,7 +19,7 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #ifndef CONTENT_NW_SRC_API_MENUITEM_MENUITEM_H_ -#define CONTENT_NW_SRC_API_MENUITEM_MENUITEM_H_ +#define CONTENT_NW_SRC_API_MENUITEM_MENUITEM_H_ #include "base/compiler_specific.h" #include "content/nw/src/api/base/base.h" @@ -34,32 +34,40 @@ class NSMenuItem; class MenuItemDelegate; #endif // __OBJC__ -#elif defined(TOOLKIT_GTK) -#include -#include "ui/base/gtk/gtk_signal.h" -#elif defined(OS_WIN) -#include "base/string16.h" +#elif defined(OS_WIN) || defined(OS_LINUX) +#include "base/strings/string16.h" #include "ui/gfx/image/image.h" +#include "ui/base/accelerators/accelerator.h" +#include "ui/views/focus/focus_manager.h" #endif // defined(OS_MACOSX) -namespace api { +namespace nwapi { class Menu; +#if defined(OS_WIN) || defined(OS_LINUX) +class MenuItem : public Base , + public ui::AcceleratorTarget { +#else class MenuItem : public Base { +#endif public: MenuItem(int id, - DispatcherHost* dispatcher_host, + const base::WeakPtr& dispatcher_host, const base::DictionaryValue& option); - virtual ~MenuItem(); + ~MenuItem() override; - virtual void Call(const std::string& method, - const base::ListValue& arguments) OVERRIDE; + void Call(const std::string& method, + const base::ListValue& arguments) override; -#if defined(OS_MACOSX) || defined(OS_WIN) - void OnClick(); +#if defined(OS_WIN) || defined(OS_LINUX) + bool AcceleratorPressed(const ui::Accelerator& accelerator) override; + bool CanHandleAccelerators() const override; + void UpdateKeys(views::FocusManager *focus_manager); #endif + void OnClick(); + private: friend class Menu; @@ -69,26 +77,32 @@ class MenuItem : public Base { void SetLabel(const std::string& label); void SetIcon(const std::string& icon); void SetTooltip(const std::string& tooltip); + void SetKey(const std::string& key); + void SetModifiers(const std::string& modifiers); void SetEnabled(bool enabled); void SetChecked(bool checked); void SetSubmenu(Menu* sub_menu); + // Template icon works only on Mac OS X + void SetIconIsTemplate(bool isTemplate); + #if defined(OS_MACOSX) std::string type_; NSMenuItem* menu_item_; MenuItemDelegate* delegate_; -#elif defined(TOOLKIT_GTK) - GtkWidget* menu_item_; + bool iconIsTemplate; - // Don't send click event on active. - bool block_active_; - - // Callback invoked when user left-clicks on the menu item. - CHROMEGTK_CALLBACK_0(MenuItem, void, OnClick); -#elif defined(OS_WIN) +#elif defined(OS_WIN) || defined(OS_LINUX) friend class MenuDelegate; + Menu* menu_; + //**Never Try to free this pointer** + //We get it from top widget + views::FocusManager *focus_manager_; + + ui::Accelerator accelerator_; + // Flag to indicate we need refresh. bool is_modified_; @@ -97,14 +111,19 @@ class MenuItem : public Base { bool is_enabled_; gfx::Image icon_; std::string type_; - string16 label_; - string16 tooltip_; + base::string16 label_; + base::string16 tooltip_; Menu* submenu_; + bool enable_shortcut_; + + bool super_down_flag_; + bool meta_down_flag_; + #endif DISALLOW_COPY_AND_ASSIGN(MenuItem); }; -} // namespace api +} // namespace nwapi #endif // CONTENT_NW_SRC_API_MENUITEM_MENUITEM_H_ diff --git a/src/api/menuitem/menuitem.js b/src/api/menuitem/menuitem.js index 0d372d9141..afb7e8aef9 100644 --- a/src/api/menuitem/menuitem.js +++ b/src/api/menuitem/menuitem.js @@ -22,7 +22,7 @@ var v8_util = process.binding('v8_util'); function MenuItem(option) { if (typeof option != 'object') - throw new String('Invalid option.'); + throw new TypeError('Invalid option.'); if (!option.hasOwnProperty('type')) option.type = 'normal'; @@ -30,14 +30,14 @@ function MenuItem(option) { if (option.type != 'normal' && option.type != 'checkbox' && option.type != 'separator') - throw new String('Invalid MenuItem type: ' + option.type); + throw new TypeError('Invalid MenuItem type: ' + option.type); if (option.type == 'normal' || option.type == 'checkbox') { if (option.type == 'checkbox') option.checked = Boolean(option.checked); if (!option.hasOwnProperty('label')) - throw new String('A normal MenuItem must have a label'); + throw new TypeError('A normal MenuItem must have a label'); else option.label = String(option.label); @@ -46,6 +46,11 @@ function MenuItem(option) { option.icon = nw.getAbsolutePath(option.icon); } + if (option.hasOwnProperty('iconIsTemplate')) + option.iconIsTemplate = Boolean(option.iconIsTemplate); + else + option.iconIsTemplate = true; + if (option.hasOwnProperty('tooltip')) option.tooltip = String(option.tooltip); @@ -54,7 +59,7 @@ function MenuItem(option) { if (option.hasOwnProperty('submenu')) { if (v8_util.getConstructorName(option.submenu) != 'Menu') - throw new String("'submenu' must be a valid Menu"); + throw new TypeError("'submenu' must be a valid Menu"); // Transfer only object id v8_util.setHiddenValue(this, 'submenu', option.submenu); @@ -63,7 +68,7 @@ function MenuItem(option) { if (option.hasOwnProperty('click')) { if (typeof option.click != 'function') - throw new String("'click' must be a valid Function"); + throw new TypeError("'click' must be a valid Function"); else this.click = option.click; } @@ -83,6 +88,10 @@ function MenuItem(option) { option.tooltip = ''; if (!option.hasOwnProperty('enabled')) option.enabled = true; + if (!option.hasOwnProperty('key')) + option.key = ""; + if (!option.hasOwnProperty('modifiers')) + option.modifiers = ""; } require('util').inherits(MenuItem, exports.Base); @@ -91,7 +100,7 @@ MenuItem.prototype.__defineGetter__('type', function() { }); MenuItem.prototype.__defineSetter__('type', function() { - throw new String("'type' is immutable at runtime"); + throw new Error("'type' is immutable at runtime"); }); MenuItem.prototype.__defineGetter__('label', function() { @@ -112,6 +121,14 @@ MenuItem.prototype.__defineSetter__('icon', function(val) { this.handleSetter('icon', 'SetIcon', String, real_path); }); +MenuItem.prototype.__defineGetter__('iconIsTemplate', function() { + return this.handleGetter('iconIsTemplate'); +}); + +MenuItem.prototype.__defineSetter__('iconIsTemplate', function(val) { + this.handleSetter('iconIsTemplate', 'SetIconIsTemplate', Boolean, val); +}); + MenuItem.prototype.__defineGetter__('tooltip', function() { return this.handleGetter('tooltip'); }); @@ -120,17 +137,33 @@ MenuItem.prototype.__defineSetter__('tooltip', function(val) { this.handleSetter('tooltip', 'SetTooltip', String, val); }); +MenuItem.prototype.__defineGetter__('key', function() { + return this.handleGetter('key'); +}); + +MenuItem.prototype.__defineSetter__('key', function(val) { + this.handleSetter('key', 'SetKey', String, val); +}); + +MenuItem.prototype.__defineGetter__('modifiers', function() { + return this.handleGetter('modifiers'); +}); + +MenuItem.prototype.__defineSetter__('modifiers', function(val) { + this.handleSetter('modifiers', 'SetModifiers', String, val); +}); + MenuItem.prototype.__defineGetter__('checked', function() { if (this.type != 'checkbox') return undefined; - + return this.handleGetter('checked'); }); MenuItem.prototype.__defineSetter__('checked', function(val) { if (this.type != 'checkbox') - throw new String("'checked' property is only available for checkbox"); - + throw new TypeError("'checked' property is only available for checkbox"); + this.handleSetter('checked', 'SetChecked', Boolean, val); }); @@ -148,7 +181,7 @@ MenuItem.prototype.__defineGetter__('submenu', function() { MenuItem.prototype.__defineSetter__('submenu', function(val) { if (v8_util.getConstructorName(val) != 'Menu') - throw new String("'submenu' property requries a valid Menu"); + throw new TypeError("'submenu' property requries a valid Menu"); v8_util.setHiddenValue(this, 'submenu', val); nw.callObjectMethod(this, 'SetSubmenu', [ val.id ]); diff --git a/src/api/menuitem/menuitem_delegate_mac.h b/src/api/menuitem/menuitem_delegate_mac.h index bf1133cc2b..6eda148760 100644 --- a/src/api/menuitem/menuitem_delegate_mac.h +++ b/src/api/menuitem/menuitem_delegate_mac.h @@ -20,15 +20,15 @@ #import -namespace api { +namespace nwapi { class MenuItem; } @interface MenuItemDelegate : NSObject { - api::MenuItem* menu_item_; + nwapi::MenuItem* menu_item_; } --(id)initWithMenuItem: (api::MenuItem*)item; +-(id)initWithMenuItem: (nwapi::MenuItem*)item; -(void)invoke: (id)sender; @end diff --git a/src/api/menuitem/menuitem_delegate_mac.mm b/src/api/menuitem/menuitem_delegate_mac.mm index f0d15b298d..5223f5d6d8 100644 --- a/src/api/menuitem/menuitem_delegate_mac.mm +++ b/src/api/menuitem/menuitem_delegate_mac.mm @@ -1,16 +1,16 @@ // Copyright (c) 2012 Intel Corp // Copyright (c) 2012 The Chromium Authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy +// +// Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co // pies of the Software, and to permit persons to whom the Software is furnished // to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in al // l copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM // PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES // S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS @@ -26,7 +26,7 @@ @implementation MenuItemDelegate --(id)initWithMenuItem: (api::MenuItem*)item { +-(id)initWithMenuItem: (nwapi::MenuItem*)item { if ([super init]) { menu_item_ = item; } diff --git a/src/api/menuitem/menuitem_gtk.cc b/src/api/menuitem/menuitem_gtk.cc index 64b7680e32..91955e2924 100644 --- a/src/api/menuitem/menuitem_gtk.cc +++ b/src/api/menuitem/menuitem_gtk.cc @@ -23,12 +23,15 @@ #include "base/values.h" #include "content/nw/src/api/dispatcher_host.h" #include "content/nw/src/api/menu/menu.h" +#include "gdk/gdkkeysyms.h"//to get keyval from name -namespace api { +namespace nwapi { void MenuItem::Create(const base::DictionaryValue& option) { std::string type; option.GetString("type", &type); + submenu_ = NULL; + gtk_accel_group = NULL; if (type == "separator") { menu_item_ = gtk_separator_menu_item_new(); @@ -60,6 +63,36 @@ void MenuItem::Create(const base::DictionaryValue& option) { int menu_id; if (option.GetInteger("submenu", &menu_id)) SetSubmenu(dispatcher_host()->GetApiObject(menu_id)); + std::string key; + if (option.GetString("key",&key)){ + enable_shortcut = true; + std::string modifiers = ""; + option.GetString("modifiers",&modifiers); + modifiers_mask = GdkModifierType(0); + if (modifiers.size() != 0){ + if (modifiers.find("ctrl") != std::string::npos){ + modifiers_mask = GdkModifierType(modifiers_mask|GDK_CONTROL_MASK); + } + if (modifiers.find("alt") != std::string::npos){ + modifiers_mask = GdkModifierType(modifiers_mask|GDK_MOD1_MASK); + } + if (modifiers.find("super") != std::string::npos){ + modifiers_mask = GdkModifierType(modifiers_mask|GDK_SUPER_MASK); + } + if (modifiers.find("meta") != std::string::npos){ + modifiers_mask = GdkModifierType(modifiers_mask|GDK_META_MASK); + } + + if (modifiers.find("shift") != std::string::npos){ + modifiers_mask = GdkModifierType(modifiers_mask|GDK_SHIFT_MASK); + } + + } + keyval = gdk_keyval_from_name(key.c_str()); + + } else { + enable_shortcut = false; + } block_active_ = false; g_signal_connect(menu_item_, "activate", @@ -76,6 +109,7 @@ void MenuItem::Destroy() { } void MenuItem::SetLabel(const std::string& label) { + label_ = label; gtk_menu_item_set_label(GTK_MENU_ITEM(menu_item_), label.c_str()); } @@ -90,6 +124,9 @@ void MenuItem::SetIcon(const std::string& icon) { } } +void MenuItem::SetIconIsTemplate(bool isTemplate) { +} + void MenuItem::SetTooltip(const std::string& tooltip) { gtk_widget_set_tooltip_text(menu_item_, tooltip.c_str()); } @@ -106,6 +143,10 @@ void MenuItem::SetChecked(bool checked) { } void MenuItem::SetSubmenu(Menu* sub_menu) { + submenu_ = sub_menu; + if (GTK_IS_ACCEL_GROUP(gtk_accel_group)){ + sub_menu->UpdateKeys(gtk_accel_group); + } if (sub_menu == NULL) gtk_menu_item_remove_submenu(GTK_MENU_ITEM(menu_item_)); else @@ -120,4 +161,24 @@ void MenuItem::OnClick(GtkWidget* widget) { dispatcher_host()->SendEvent(this, "click", args); } -} // namespace api + +void MenuItem::UpdateKeys(GtkAccelGroup *gtk_accel_group){ + this->gtk_accel_group = gtk_accel_group; + if (enable_shortcut && GTK_IS_ACCEL_GROUP(gtk_accel_group)){ + gtk_widget_add_accelerator( + menu_item_, + "activate", + gtk_accel_group, + keyval, + modifiers_mask, + GTK_ACCEL_VISIBLE); + } + if (submenu_ != NULL){ + submenu_->UpdateKeys(gtk_accel_group); + } + return; +} + +} // namespace nwapi + + diff --git a/src/api/menuitem/menuitem_mac.mm b/src/api/menuitem/menuitem_mac.mm index 9b9ad4de06..ed4ebf7fb5 100644 --- a/src/api/menuitem/menuitem_mac.mm +++ b/src/api/menuitem/menuitem_mac.mm @@ -1,16 +1,16 @@ // Copyright (c) 2012 Intel Corp // Copyright (c) 2012 The Chromium Authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy +// +// Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co // pies of the Software, and to permit persons to whom the Software is furnished // to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in al // l copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM // PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES // S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS @@ -26,7 +26,7 @@ #include "content/nw/src/api/menu/menu.h" #include "content/nw/src/api/menuitem/menuitem_delegate_mac.h" -namespace api { +namespace nwapi { void MenuItem::Create(const base::DictionaryValue& option) { std::string type; @@ -43,13 +43,23 @@ std::string label; option.GetString("label", &label); - menu_item_ = [[NSMenuItem alloc] - initWithTitle:[NSString stringWithUTF8String:label.c_str()] - action: @selector(invoke:) - keyEquivalent: @""]; - - delegate_ = [[MenuItemDelegate alloc] initWithMenuItem:this]; - [menu_item_ setTarget:delegate_]; + std::string selector; + option.GetString("selector", &selector); + + if(!selector.empty()) { + menu_item_ = [[NSMenuItem alloc] + initWithTitle:[NSString stringWithUTF8String:label.c_str()] + action: NSSelectorFromString([NSString stringWithUTF8String:selector.c_str()]) + keyEquivalent: @""]; + delegate_ = [MenuItemDelegate alloc]; + } else { + menu_item_ = [[NSMenuItem alloc] + initWithTitle:[NSString stringWithUTF8String:label.c_str()] + action: @selector(invoke:) + keyEquivalent: @""]; + delegate_ = [[MenuItemDelegate alloc] initWithMenuItem:this]; + [menu_item_ setTarget:delegate_]; + } if (type == "checkbox") { bool checked = false; @@ -61,6 +71,10 @@ if (option.GetBoolean("enabled", &enabled)) SetEnabled(enabled); + bool isTemplate; + if (option.GetBoolean("iconIsTemplate", &isTemplate)) + SetIconIsTemplate(isTemplate); + std::string icon; if (option.GetString("icon", &icon) && !icon.empty()) SetIcon(icon); @@ -69,6 +83,15 @@ if (option.GetString("tooltip", &tooltip)) SetTooltip(tooltip); + std::string key; + if (option.GetString("key", &key)) + SetKey(key); + + std::string modifiers; + if (option.GetString("modifiers", &modifiers)) { + SetModifiers(modifiers); + } + int menu_id; if (option.GetInteger("submenu", &menu_id)) SetSubmenu(dispatcher_host()->GetApiObject(menu_id)); @@ -94,10 +117,30 @@ [menu_item_ setTitle:[NSString stringWithUTF8String:label.c_str()]]; } +void MenuItem::SetKey(const std::string& key) { + [menu_item_ setKeyEquivalent:[NSString stringWithUTF8String:key.c_str()]]; + VLOG(1) << "setkey: " << key; +} + +void MenuItem::SetModifiers(const std::string& modifiers) { + NSUInteger mask = 0; + NSString* nsmodifiers = [NSString stringWithUTF8String:modifiers.c_str()]; + if([nsmodifiers rangeOfString:@"shift"].location != NSNotFound) + mask = mask|NSShiftKeyMask; + if([nsmodifiers rangeOfString:@"cmd"].location != NSNotFound) + mask = mask|NSCommandKeyMask; + if([nsmodifiers rangeOfString:@"alt"].location != NSNotFound) + mask = mask|NSAlternateKeyMask; + if([nsmodifiers rangeOfString:@"ctrl"].location != NSNotFound) + mask = mask|NSControlKeyMask; + [menu_item_ setKeyEquivalentModifierMask:mask]; +} + void MenuItem::SetIcon(const std::string& icon) { if (!icon.empty()) { NSImage* image = [[NSImage alloc] initWithContentsOfFile:[NSString stringWithUTF8String:icon.c_str()]]; + [image setTemplate:iconIsTemplate]; [menu_item_ setImage:image]; [image release]; } else { @@ -105,6 +148,12 @@ } } +void MenuItem::SetIconIsTemplate(bool isTemplate) { + iconIsTemplate = isTemplate; + if ([menu_item_ image] != nil) + [[menu_item_ image] setTemplate:isTemplate]; +} + void MenuItem::SetTooltip(const std::string& tooltip) { [menu_item_ setToolTip:[NSString stringWithUTF8String:tooltip.c_str()]]; } @@ -125,4 +174,4 @@ [menu_item_ setSubmenu:sub_menu->menu_]; } -} // namespace api +} // namespace nwapi diff --git a/src/api/menuitem/menuitem_views.cc b/src/api/menuitem/menuitem_views.cc new file mode 100644 index 0000000000..238a7c46fc --- /dev/null +++ b/src/api/menuitem/menuitem_views.cc @@ -0,0 +1,296 @@ +// Copyright (c) 2012 Intel Corp +// Copyright (c) 2012 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "content/nw/src/api/menuitem/menuitem.h" + +#include "base/files/file_path.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "content/nw/src/api/dispatcher_host.h" +#include "content/nw/src/api/menu/menu.h" +#include "content/nw/src/nw_package.h" +#include "content/nw/src/nw_shell.h" +#include "ui/base/accelerators/accelerator.h" +#include "ui/events/event_constants.h"//for modifier key code +#include "ui/events/keycodes/keyboard_codes.h"//for keycode +#include "base/logging.h" + +ui::KeyboardCode GetKeycodeFromText(std::string text); + +namespace nwapi { + +void MenuItem::Create(const base::DictionaryValue& option) { + is_modified_ = false; + is_checked_ = false; + is_enabled_ = true; + type_ = "normal"; + submenu_ = NULL; + super_down_flag_ = false; + meta_down_flag_ = false; + + focus_manager_ = NULL; + menu_ = NULL; + + option.GetString("type", &type_); + option.GetString("label", &label_); + option.GetString("tooltip", &tooltip_); + option.GetBoolean("checked", &is_checked_); + option.GetBoolean("enabled", &is_enabled_); + + std::string key; + std::string modifiers; + option.GetString("key",&key); + option.GetString("modifiers",&modifiers); + + ui::KeyboardCode keyval = ui::VKEY_UNKNOWN; + + + if (key.size() == 0){ + enable_shortcut_ = false; + } else { + enable_shortcut_ = true; + keyval = ::GetKeycodeFromText(key); + } + + //only code for ctrl, shift, alt, super and meta modifiers + int modifiers_value = ui::EF_NONE; + if (modifiers.find("ctrl")!=std::string::npos){ + modifiers_value = modifiers_value | ui::EF_CONTROL_DOWN; + } + if (modifiers.find("shift")!=std::string::npos){ + modifiers_value = modifiers_value | ui::EF_SHIFT_DOWN ; + } + if (modifiers.find("alt")!=std::string::npos){ + modifiers_value = modifiers_value | ui::EF_ALT_DOWN; + } + if (modifiers.find("super")!=std::string::npos){ + super_down_flag_ = true; + } + if (modifiers.find("meta")!=std::string::npos){ + meta_down_flag_ = true; + } + accelerator_ = ui::Accelerator(keyval,modifiers_value); + + + std::string icon; + if (option.GetString("icon", &icon) && !icon.empty()) + SetIcon(icon); + + int menu_id; + if (option.GetInteger("submenu", &menu_id)) + SetSubmenu(dispatcher_host()->GetApiObject(menu_id)); +} + +void MenuItem::Destroy() { +} + +void MenuItem::OnClick() { + // Automatically flip checkbox. + if (type_ == "checkbox") + is_checked_ = !is_checked_; + + // Send event. + base::ListValue args; + dispatcher_host()->SendEvent(this, "click", args); +} + +void MenuItem::SetLabel(const std::string& label) { + is_modified_ = true; + label_ = base::UTF8ToUTF16(label); + +#if 0//FIXME + if (menu_) + menu_->UpdateStates(); +#endif +} + +void MenuItem::SetIcon(const std::string& icon) { + is_modified_ = true; + if (icon.empty()) { + icon_ = gfx::Image(); + return; + } + + content::Shell* shell = content::Shell::FromRenderViewHost( + dispatcher_host()->render_view_host()); + nw::Package* package = shell->GetPackage(); + package->GetImage(base::FilePath::FromUTF8Unsafe(icon), &icon_); +} + +void MenuItem::SetIconIsTemplate(bool isTemplate) { +} + +void MenuItem::SetTooltip(const std::string& tooltip) { + tooltip_ = base::UTF8ToUTF16(tooltip); + if (menu_) + menu_->UpdateStates(); +} + +void MenuItem::SetEnabled(bool enabled) { + is_enabled_ = enabled; + if (menu_) + menu_->UpdateStates(); +} + +void MenuItem::SetChecked(bool checked) { + is_checked_ = checked; + if (menu_) + menu_->UpdateStates(); +} + +void MenuItem::SetSubmenu(Menu* menu) { + submenu_ = menu; +} + +void MenuItem::UpdateKeys(views::FocusManager *focus_manager){ + if (focus_manager == NULL){ + return ; + } else { + focus_manager_ = focus_manager; + if (enable_shortcut_){ + focus_manager->RegisterAccelerator( + accelerator_, + ui::AcceleratorManager::kHighPriority, + this); + } + if (submenu_ != NULL){ + submenu_->UpdateKeys(focus_manager); + } + } +} + +#if defined(OS_WIN) || defined(OS_LINUX) +bool MenuItem::AcceleratorPressed(const ui::Accelerator& accelerator) { + +#if defined(OS_WIN) + if (super_down_flag_){ + if ( ( (::GetKeyState(VK_LWIN) & 0x8000) != 0x8000) + || ( (::GetKeyState(VK_LWIN) & 0x8000) != 0x8000) ){ + return true; + } + } + if (meta_down_flag_){ + if ( (::GetKeyState(VK_APPS) & 0x8000) != 0x8000 ){ + return true; + } + } +#endif + OnClick(); + return true; +} + +bool MenuItem::CanHandleAccelerators() const { + return true; +} + +#endif +} // namespace nwapi + + + +ui::KeyboardCode GetKeycodeFromText(std::string text){ + ui::KeyboardCode retval = ui::VKEY_UNKNOWN; + if (text.size() != 0){ + for (unsigned int i=0;i='0' && key<='9'){//handle digital + retval = (ui::KeyboardCode)(ui::VKEY_0 + key - '0'); + } else if (key>='A'&&key<='Z'){//handle alphabet + retval = (ui::KeyboardCode)(ui::VKEY_A + key - 'A'); + } else if (key == '`'){//handle all special symbols + retval = ui::VKEY_OEM_3; + } else if (key == ','){ + retval = ui::VKEY_OEM_COMMA; + } else if (key == '.'){ + retval = ui::VKEY_OEM_PERIOD; + } else if (key == '/'){ + retval = ui::VKEY_OEM_2; + } else if (key == ';'){ + retval = ui::VKEY_OEM_1; + } else if (key == '\''){ + retval = ui::VKEY_OEM_7; + } else if (key == '['){ + retval = ui::VKEY_OEM_4; + } else if (key == ']'){ + retval = ui::VKEY_OEM_6; + } else if (key == '\\'){ + retval = ui::VKEY_OEM_5; + } else if (key == '-'){ + retval = ui::VKEY_OEM_MINUS; + } else if (key == '='){ + retval = ui::VKEY_OEM_PLUS; + } + } else {//handle long key name + if (!text.compare("ESC")){ + retval = ui::VKEY_ESCAPE; + } else if (!text.compare("BACKSPACE")){ + retval = ui::VKEY_BACK; + } else if (!text.compare("F1")){ + retval = ui::VKEY_F1; + } else if (!text.compare("F2")){ + retval = ui::VKEY_F2; + } else if (!text.compare("F3")){ + retval = ui::VKEY_F3; + } else if (!text.compare("F4")){ + retval = ui::VKEY_F4; + } else if (!text.compare("F5")){ + retval = ui::VKEY_F5; + } else if (!text.compare("F6")){ + retval = ui::VKEY_F6; + } else if (!text.compare("F7")){ + retval = ui::VKEY_F7; + } else if (!text.compare("F8")){ + retval = ui::VKEY_F8; + } else if (!text.compare("F9")){ + retval = ui::VKEY_F9; + } else if (!text.compare("F10")){ + retval = ui::VKEY_F10; + } else if (!text.compare("F11")){ + retval = ui::VKEY_F11; + } else if (!text.compare("F12")){ + retval = ui::VKEY_F12; + } else if (!text.compare("INSERT")){ + retval = ui::VKEY_INSERT; + } else if (!text.compare("HOME")){ + retval = ui::VKEY_HOME; + } else if (!text.compare("DELETE")){ + retval = ui::VKEY_DELETE; + } else if (!text.compare("END")){ + retval = ui::VKEY_END; + } else if (!text.compare("PAGEUP")){ + retval = ui::VKEY_PRIOR; + } else if (!text.compare("PAGEDOWN")){ + retval = ui::VKEY_NEXT; + } else if (!text.compare("UP")){ + retval = ui::VKEY_UP; + } else if (!text.compare("LEFT")){ + retval = ui::VKEY_LEFT; + } else if (!text.compare("DOWN")){ + retval = ui::VKEY_DOWN; + } else if (!text.compare("RIGHT")){ + retval = ui::VKEY_RIGHT; + } + } + } + return retval; +} diff --git a/src/api/menuitem/menuitem_win.cc b/src/api/menuitem/menuitem_win.cc deleted file mode 100644 index 1a7db08221..0000000000 --- a/src/api/menuitem/menuitem_win.cc +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) 2012 Intel Corp -// Copyright (c) 2012 The Chromium Authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co -// pies of the Software, and to permit persons to whom the Software is furnished -// to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in al -// l copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM -// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES -// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH -// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -#include "content/nw/src/api/menuitem/menuitem.h" - -#include "base/files/file_path.h" -#include "base/utf_string_conversions.h" -#include "base/values.h" -#include "content/nw/src/api/dispatcher_host.h" -#include "content/nw/src/api/menu/menu.h" -#include "content/nw/src/nw_package.h" -#include "content/nw/src/nw_shell.h" - -namespace api { - -void MenuItem::Create(const base::DictionaryValue& option) { - is_modified_ = false; - is_checked_ = false; - is_enabled_ = true; - type_ = "normal"; - submenu_ = NULL; - - option.GetString("type", &type_); - option.GetString("label", &label_); - option.GetString("tooltip", &tooltip_); - option.GetBoolean("checked", &is_checked_); - option.GetBoolean("enabled", &is_enabled_); - - std::string icon; - if (option.GetString("icon", &icon) && !icon.empty()) - SetIcon(icon); - - int menu_id; - if (option.GetInteger("submenu", &menu_id)) - SetSubmenu(dispatcher_host()->GetApiObject(menu_id)); -} - -void MenuItem::Destroy() { -} - -void MenuItem::OnClick() { - // Automatically flip checkbox. - if (type_ == "checkbox") - is_checked_ = !is_checked_; - - // Send event. - base::ListValue args; - dispatcher_host()->SendEvent(this, "click", args); -} - -void MenuItem::SetLabel(const std::string& label) { - is_modified_ = true; - label_ = UTF8ToUTF16(label); -} - -void MenuItem::SetIcon(const std::string& icon) { - is_modified_ = true; - if (icon.empty()) { - icon_ = gfx::Image(); - return; - } - - content::Shell* shell = content::Shell::FromRenderViewHost( - dispatcher_host()->render_view_host()); - nw::Package* package = shell->GetPackage(); - package->GetImage(base::FilePath::FromUTF8Unsafe(icon), &icon_); -} - -void MenuItem::SetTooltip(const std::string& tooltip) { - tooltip_ = UTF8ToUTF16(tooltip); -} - -void MenuItem::SetEnabled(bool enabled) { - is_enabled_ = enabled; -} - -void MenuItem::SetChecked(bool checked) { - is_checked_ = checked; -} - -void MenuItem::SetSubmenu(Menu* menu) { - submenu_ = menu; -} - -} // namespace api diff --git a/src/api/screen/desktop_capture_api.cc b/src/api/screen/desktop_capture_api.cc new file mode 100644 index 0000000000..91339c79b6 --- /dev/null +++ b/src/api/screen/desktop_capture_api.cc @@ -0,0 +1,224 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/nw/src/api/screen/desktop_capture_api.h" + +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/media/native_desktop_media_list.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "content/nw/src/nw_package.h" +#include "content/nw/src/nw_shell.h" +#include "net/base/net_util.h" +#include "third_party/webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "third_party/webrtc/modules/desktop_capture/screen_capturer.h" +#include "third_party/webrtc/modules/desktop_capture/window_capturer.h" + +namespace nwapi { + +namespace { + +const char kEmptySourcesListError[] = + "At least one source type must be specified."; + +DesktopCaptureChooseDesktopMediaFunction::PickerFactory* g_picker_factory = + NULL; + +} // namespace + +// static +void DesktopCaptureChooseDesktopMediaFunction::SetPickerFactoryForTests( + PickerFactory* factory) { + g_picker_factory = factory; +} + +DesktopCaptureChooseDesktopMediaFunction:: + DesktopCaptureChooseDesktopMediaFunction() { +} + +DesktopCaptureChooseDesktopMediaFunction:: + ~DesktopCaptureChooseDesktopMediaFunction() { + // RenderViewHost may be already destroyed. + if (render_view_host()) { + DesktopCaptureRequestsRegistry::GetInstance()->RemoveRequest( + render_view_host()->GetProcess()->GetID(), request_id_); + } +} + +void DesktopCaptureChooseDesktopMediaFunction::Cancel() { + // Keep reference to |this| to ensure the object doesn't get destroyed before + // we return. + scoped_refptr self(this); + if (picker_) { + picker_.reset(); + SetResult(new base::StringValue(std::string())); + SendResponse(true); + } +} + +bool DesktopCaptureChooseDesktopMediaFunction::RunSync() { + + DesktopCaptureRequestsRegistry::GetInstance()->AddRequest( + render_view_host()->GetProcess()->GetID(), request_id_, this); + + // |web_contents| is the WebContents for which the stream is created, and will + // also be used to determine where to show the picker's UI. + content::WebContents* web_contents = content::WebContents::FromRenderViewHost(render_view_host()); + DCHECK(web_contents); + content::Shell* shell = content::Shell::windows()[0]; + base::string16 app_name = base::UTF8ToUTF16(shell->GetPackage()->GetName()); + + // Register to be notified when the tab is closed. + Observe(web_contents); + + bool show_screens = false; + bool show_windows = false; + + const base::ListValue* capture_param = NULL; + if(args_->GetList(1,&capture_param)) { + for(base::ListValue::const_iterator i = capture_param->begin(); i != capture_param->end(); i++) { + const base::Value* val = static_cast(*i); + std::string str; + if(val->GetAsString(&str)) { + if(!str.compare("window")) { + show_windows = true; + continue; + } + + if(!str.compare("screen")) { + show_screens = true; + continue; + } + } + } + } + + if (!show_screens && !show_windows) { + error_ = kEmptySourcesListError; + return false; + } + + const gfx::NativeWindow parent_window = + web_contents->GetTopLevelNativeWindow(); + scoped_ptr media_list; + if (g_picker_factory) { + media_list = g_picker_factory->CreateModel( + show_screens, show_windows); + picker_ = g_picker_factory->CreatePicker(); + } else { + { + webrtc::DesktopCaptureOptions options = + webrtc::DesktopCaptureOptions::CreateDefault(); + options.set_disable_effects(false); + scoped_ptr screen_capturer( + show_screens ? webrtc::ScreenCapturer::Create(options) : NULL); + scoped_ptr window_capturer( + show_windows ? webrtc::WindowCapturer::Create(options) : NULL); + + media_list.reset(new NativeDesktopMediaList( + screen_capturer.Pass(), window_capturer.Pass())); + } + + // DesktopMediaPicker is implemented only for Windows, OSX and + // Aura Linux builds. +#if defined(TOOLKIT_VIEWS) || defined(OS_MACOSX) + picker_ = DesktopMediaPicker::Create(); +#else + error_ = "Desktop Capture API is not yet implemented for this platform."; + return false; +#endif + } + DesktopMediaPicker::DoneCallback callback = base::Bind( + &DesktopCaptureChooseDesktopMediaFunction::OnPickerDialogResults, this); + + picker_->Show(web_contents, + parent_window, + parent_window, + app_name, + app_name, + media_list.Pass(), + callback); + return true; +} + +void DesktopCaptureChooseDesktopMediaFunction::WebContentsDestroyed() { + Cancel(); +} + +void DesktopCaptureChooseDesktopMediaFunction::OnPickerDialogResults( + content::DesktopMediaID source) { + std::string result; + if (source.type != content::DesktopMediaID::TYPE_NONE && + web_contents()) { + result = source.ToString(); + } + + SetResult(new base::StringValue(result)); + SendResponse(true); +} + +DesktopCaptureRequestsRegistry::RequestId::RequestId(int process_id, + int request_id) + : process_id(process_id), + request_id(request_id) { +} + +bool DesktopCaptureRequestsRegistry::RequestId::operator<( + const RequestId& other) const { + if (process_id != other.process_id) { + return process_id < other.process_id; + } else { + return request_id < other.request_id; + } +} + +DesktopCaptureCancelChooseDesktopMediaFunction:: + DesktopCaptureCancelChooseDesktopMediaFunction() {} + +DesktopCaptureCancelChooseDesktopMediaFunction:: + ~DesktopCaptureCancelChooseDesktopMediaFunction() {} + +bool DesktopCaptureCancelChooseDesktopMediaFunction::RunSync() { + int request_id; + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &request_id)); + + DesktopCaptureRequestsRegistry::GetInstance()->CancelRequest( + render_view_host()->GetProcess()->GetID(), request_id); + return true; +} + +DesktopCaptureRequestsRegistry::DesktopCaptureRequestsRegistry() {} +DesktopCaptureRequestsRegistry::~DesktopCaptureRequestsRegistry() {} + +// static +DesktopCaptureRequestsRegistry* DesktopCaptureRequestsRegistry::GetInstance() { + return Singleton::get(); +} + +void DesktopCaptureRequestsRegistry::AddRequest( + int process_id, + int request_id, + DesktopCaptureChooseDesktopMediaFunction* handler) { + requests_.insert( + RequestsMap::value_type(RequestId(process_id, request_id), handler)); +} + +void DesktopCaptureRequestsRegistry::RemoveRequest(int process_id, + int request_id) { + requests_.erase(RequestId(process_id, request_id)); +} + +void DesktopCaptureRequestsRegistry::CancelRequest(int process_id, + int request_id) { + RequestsMap::iterator it = requests_.find(RequestId(process_id, request_id)); + if (it != requests_.end()) + it->second->Cancel(); +} + + +} // namespace extensions diff --git a/src/api/screen/desktop_capture_api.h b/src/api/screen/desktop_capture_api.h new file mode 100644 index 0000000000..89b8360873 --- /dev/null +++ b/src/api/screen/desktop_capture_api.h @@ -0,0 +1,115 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NWAPI_DESKTOP_CAPTURE_DESKTOP_CAPTURE_API_H_ +#define NWAPI_DESKTOP_CAPTURE_DESKTOP_CAPTURE_API_H_ + +#include + +#include "base/memory/singleton.h" +#include "chrome/browser/media/desktop_media_list.h" +#include "chrome/browser/media/desktop_media_picker.h" +#include "chrome/browser/media/native_desktop_media_list.h" +#include "extensions/browser/extension_function.h" +#include "content/public/browser/web_contents_observer.h" +#include "url/gurl.h" + +namespace nwapi { + +class DesktopCaptureChooseDesktopMediaFunction + : public SyncExtensionFunction, + public content::WebContentsObserver { + public: + + // Factory creating DesktopMediaList and DesktopMediaPicker instances. + // Used for tests to supply fake picker. + class PickerFactory { + public: + virtual scoped_ptr CreateModel(bool show_screens, + bool show_windows) = 0; + virtual scoped_ptr CreatePicker() = 0; + protected: + virtual ~PickerFactory() {} + }; + + // Used to set PickerFactory used to create mock DesktopMediaPicker instances + // for tests. Calling tests keep ownership of the factory. Can be called with + // |factory| set to NULL at the end of the test. + static void SetPickerFactoryForTests(PickerFactory* factory); + + DesktopCaptureChooseDesktopMediaFunction(); + + void Cancel(); + + // ExtensionFunction overrides. + bool RunSync() override; + + private: + ~DesktopCaptureChooseDesktopMediaFunction() override; + + // content::WebContentsObserver overrides. + void WebContentsDestroyed() override; + + void OnPickerDialogResults(content::DesktopMediaID source); + + int request_id_; + + // URL of page that desktop capture was requested for. + GURL origin_; + + scoped_ptr picker_; +}; + +// this api is not exposed in nwjs yet +class DesktopCaptureCancelChooseDesktopMediaFunction + : public SyncExtensionFunction { + public: + DesktopCaptureCancelChooseDesktopMediaFunction(); + + private: + ~DesktopCaptureCancelChooseDesktopMediaFunction() override; + + // ExtensionFunction overrides. + bool RunSync() override; +}; + +// this class is only needed if we want the ability to cancel "choose desktop media" +// currently not used +class DesktopCaptureRequestsRegistry { + public: + DesktopCaptureRequestsRegistry(); + ~DesktopCaptureRequestsRegistry(); + + static DesktopCaptureRequestsRegistry* GetInstance(); + + void AddRequest(int process_id, + int request_id, + DesktopCaptureChooseDesktopMediaFunction* handler); + void RemoveRequest(int process_id, int request_id); + void CancelRequest(int process_id, int request_id); + + private: + friend struct DefaultSingletonTraits; + + struct RequestId { + RequestId(int process_id, int request_id); + + // Need to use RequestId as a key in std::map<>. + bool operator<(const RequestId& other) const; + + int process_id; + int request_id; + }; + + typedef std::map RequestsMap; + + RequestsMap requests_; + + DISALLOW_COPY_AND_ASSIGN(DesktopCaptureRequestsRegistry); +}; + +} // namespace extensions + +#endif // NWAPI_DESKTOP_CAPTURE_DESKTOP_CAPTURE_API_H_ diff --git a/src/api/screen/desktop_capture_monitor.cc b/src/api/screen/desktop_capture_monitor.cc new file mode 100644 index 0000000000..6afa1e291d --- /dev/null +++ b/src/api/screen/desktop_capture_monitor.cc @@ -0,0 +1,169 @@ +// Copyright (c) 2012 Intel Corp +// Copyright (c) 2012 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "content/nw/src/api/screen/desktop_capture_monitor.h" + +#include "base/values.h" +#include "base/strings/utf_string_conversions.h" +#include "base/strings/string16.h" +#include "content/nw/src/api/dispatcher_host.h" +#include "chrome/browser/media/desktop_streams_registry.h" +#include "chrome/browser/media/media_capture_devices_dispatcher.h" + +#include "third_party/webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "third_party/webrtc/modules/desktop_capture/screen_capturer.h" +#include "third_party/webrtc/modules/desktop_capture/window_capturer.h" + +#include "base/base64.h" +#include "ui/gfx/codec/png_codec.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_skia.h" + +#ifdef _WIN32 +#include +#endif + +namespace nwapi { + +DesktopCaptureMonitor::DesktopCaptureMonitor(int id, + const base::WeakPtr& dispatcher_host, + const base::DictionaryValue& option) + : Base(id, dispatcher_host, option) { +} + +DesktopCaptureMonitor::~DesktopCaptureMonitor() {} + +int DesktopCaptureMonitor::GetPrimaryMonitorIndex(){ +#ifdef _WIN32 + int count=0; + for (int i = 0;; ++i) { + DISPLAY_DEVICE device; + device.cb = sizeof(device); + BOOL ret = EnumDisplayDevices(NULL, i, &device, 0); + if(!ret) + break; + if (device.StateFlags & DISPLAY_DEVICE_ACTIVE){ + if (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE){ + return count; + } + count++; + } + } +#endif + return -1; +} + +void DesktopCaptureMonitor::CallSync(const std::string& method, const base::ListValue& arguments, base::ListValue* result){ + if (method == "start") { + bool screens, windows; + if (arguments.GetBoolean(0, &screens) && arguments.GetBoolean(1, &windows)) + Start(screens, windows); + }else if (method == "stop") { + Stop(); + } + else { + NOTREACHED() << "Invalid call to DesktopCapture method:" << method + << " arguments:" << arguments; + } +} + +void DesktopCaptureMonitor::Start(bool screens, bool windows) { + webrtc::DesktopCaptureOptions options = webrtc::DesktopCaptureOptions::CreateDefault(); + options.set_disable_effects(false); + scoped_ptr screen_capturer(screens ? webrtc::ScreenCapturer::Create(options) : NULL); + scoped_ptr window_capturer(windows ? webrtc::WindowCapturer::Create(options) : NULL); + + media_list_.reset(new NativeDesktopMediaList(screen_capturer.Pass(), window_capturer.Pass())); + + media_list_->StartUpdating(this); +} + +void DesktopCaptureMonitor::Stop() { + media_list_.reset(); +} + +void DesktopCaptureMonitor::OnSourceAdded(int index){ + DesktopMediaList::Source src = media_list_->GetSource(index); + + std::string type; + if (src.id.type == content::DesktopMediaID::TYPE_AURA_WINDOW || src.id.type == content::DesktopMediaID::TYPE_WINDOW){ + type = "window"; + } + else if (src.id.type == content::DesktopMediaID::TYPE_SCREEN){ + type = "screen"; + } + else if (src.id.type == content::DesktopMediaID::TYPE_NONE){ + type = "none"; + } + else{ + type = "unknown"; + } + + base::ListValue param; + param.AppendString(src.id.ToString()); + param.AppendString(src.name); + param.AppendInteger(index); + param.AppendString(type); + if(src.id.type == content::DesktopMediaID::TYPE_SCREEN){ + param.AppendBoolean(GetPrimaryMonitorIndex()==index); + } + this->dispatcher_host()->SendEvent(this, "__nw_desktop_capture_monitor_listner_added", param); +} +void DesktopCaptureMonitor::OnSourceRemoved(int index){ + base::ListValue param; + param.AppendInteger(index); //pass by index here, because the information about which ID was at that index is lost before the removed callback is called. Its saved in the javascript though, so we can look it up there + this->dispatcher_host()->SendEvent(this, "__nw_desktop_capture_monitor_listner_removed", param); +} +void DesktopCaptureMonitor::OnSourceMoved(int old_index, int new_index){ + DesktopMediaList::Source src = media_list_->GetSource(new_index); + base::ListValue param; + param.AppendString(src.id.ToString()); + param.AppendInteger(new_index); + param.AppendInteger(old_index); + this->dispatcher_host()->SendEvent(this, "__nw_desktop_capture_monitor_listner_moved", param); +} +void DesktopCaptureMonitor::OnSourceNameChanged(int index){ + DesktopMediaList::Source src = media_list_->GetSource(index); + + base::ListValue param; + param.AppendString(src.id.ToString()); + param.AppendString(src.name); + this->dispatcher_host()->SendEvent(this, "__nw_desktop_capture_monitor_listner_namechanged", param); +} + +void DesktopCaptureMonitor::OnSourceThumbnailChanged(int index){ + std::string base64; + + DesktopMediaList::Source src = media_list_->GetSource(index); + SkBitmap bitmap = src.thumbnail.GetRepresentation(1).sk_bitmap(); + SkAutoLockPixels lock_image(bitmap); + std::vector data; + bool success = gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &data); + if (success){ + base::StringPiece raw_str(reinterpret_cast(&data[0]), data.size()); + base::Base64Encode(raw_str, &base64); + } + base::ListValue param; + param.AppendString(src.id.ToString()); + param.AppendString(base64); + this->dispatcher_host()->SendEvent(this, "__nw_desktop_capture_monitor_listner_thumbnailchanged", param); +} + +} // namespace nwapi diff --git a/src/api/screen/desktop_capture_monitor.h b/src/api/screen/desktop_capture_monitor.h new file mode 100644 index 0000000000..5397897425 --- /dev/null +++ b/src/api/screen/desktop_capture_monitor.h @@ -0,0 +1,50 @@ +// Copyright (c) 2012 Intel Corp +// Copyright (c) 2012 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#ifndef CONTENT_NW_SRC_API_DESKTOPCAPTURE_H_ +#define CONTENT_NW_SRC_API_DESKTOPCAPTURE_H_ +#include "base/compiler_specific.h" +#include "content/nw/src/api/base/base.h" +#include "chrome/browser/media/desktop_media_list_observer.h" +#include "chrome/browser/media/native_desktop_media_list.h" + +namespace nwapi { + class DesktopCaptureMonitor : public Base, public DesktopMediaListObserver { + public: + DesktopCaptureMonitor(int id, + const base::WeakPtr& dispatcher_host, + const base::DictionaryValue& option); + ~DesktopCaptureMonitor() override; + void CallSync(const std::string& method, const base::ListValue& arguments, base::ListValue* result) override; + void OnSourceAdded(int index) override; + void OnSourceRemoved(int index) override; + void OnSourceMoved(int old_index, int new_index) override; + void OnSourceNameChanged(int index) override; + void OnSourceThumbnailChanged(int index) override; + private: + DesktopCaptureMonitor(); + DISALLOW_COPY_AND_ASSIGN(DesktopCaptureMonitor); + int GetPrimaryMonitorIndex(); + void Start(bool screens, bool windows); + void Stop(); + + scoped_ptr media_list_; + }; +} // namespace api +#endif // CONTENT_NW_SRC_API_DESKTOPCAPTURE_H_ diff --git a/src/api/screen/screen.cc b/src/api/screen/screen.cc new file mode 100644 index 0000000000..51ca93028b --- /dev/null +++ b/src/api/screen/screen.cc @@ -0,0 +1,179 @@ +// Copyright (c) 2014 Jefry Tedjokusumo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +#include "base/values.h" +#include "content/nw/src/api/screen/screen.h" +#include "content/nw/src/api/screen/desktop_capture_api.h" +#include "content/nw/src/api/dispatcher_host.h" +#include "content/nw/src/api/event/event.h" +#include "content/nw/src/nw_shell.h" +#include "ui/gfx/display_observer.h" +#include "ui/gfx/screen.h" + +namespace nwapi { +std::string DisplayToJSON(const gfx::Display& display) { + std::stringstream ret; + gfx::Rect rect = display.bounds(); + + ret << "{\"id\":" << display.id(); + + ret << ",\"bounds\":{\"x\":" << rect.x() + << ", \"y\":" << rect.y() + << ", \"width\":" << rect.width() + << ", \"height\":" << rect.height() << "}"; + + rect = display.work_area(); + ret << ",\"work_area\":{\"x\":" << rect.x() + << ", \"y\":" << rect.y() + << ", \"width\":" << rect.width() + << ", \"height\":" << rect.height() << "}"; + + ret << ",\"scaleFactor\":" << display.device_scale_factor(); + ret << ",\"isBuiltIn\":" << (display.IsInternal() ? "true" : "false"); + ret << ",\"rotation\":" << display.RotationAsDegree(); + ret << ",\"touchSupport\":" << display.touch_support(); + ret << "}"; + + return ret.str(); +} + +class JavaScriptDisplayObserver : BaseEvent, public gfx::DisplayObserver { + friend class EventListener; + EventListener* object_; + gfx::Screen* screen_; + + // Called when the |display|'s bound has changed. + void OnDisplayMetricsChanged(const gfx::Display& display, uint32_t changed_metrics) override { + base::ListValue arguments; + arguments.AppendString(DisplayToJSON(display)); + arguments.AppendInteger(changed_metrics); + object_->dispatcher_host()->SendEvent(object_, "displayBoundsChanged", arguments); + } + + // Called when |new_display| has been added. + void OnDisplayAdded(const gfx::Display& new_display) override { + base::ListValue arguments; + arguments.AppendString(DisplayToJSON(new_display)); + object_->dispatcher_host()->SendEvent(object_, "displayAdded", arguments); + + } + + // Called when |old_display| has been removed. + void OnDisplayRemoved(const gfx::Display& old_display) override { + base::ListValue arguments; + arguments.AppendString(DisplayToJSON(old_display)); + object_->dispatcher_host()->SendEvent(object_, "displayRemoved", arguments); + } + + static const int id; + + JavaScriptDisplayObserver(EventListener* object) : object_(object), screen_(NULL){ + } + + ~JavaScriptDisplayObserver() override { + if(screen_) + screen_->RemoveObserver(this); + } + +public: + void setScreen(gfx::Screen* screen) { + if(screen_) screen_->RemoveObserver(this); + screen_ = screen; + if(screen_) screen_->AddObserver(this); + } +}; + +const int JavaScriptDisplayObserver::id = EventListener::getUID(); + +scoped_refptr gpDCCDMF; + +void ChooseDesktopMediaCallback(EventListener* event_listener, + ExtensionFunction::ResponseType type, + const base::ListValue& results, + const std::string& error) { + + event_listener->dispatcher_host()->SendEvent(event_listener, "chooseDesktopMedia", results); + gpDCCDMF = NULL; +} + // static +void Screen::Call(DispatcherHost* dispatcher_host, + const std::string& method, + const base::ListValue& arguments, + base::ListValue* result) { + + if (method == "GetScreens") { + std::stringstream ret; + const std::vector& displays = gfx::Screen::GetNativeScreen()->GetAllDisplays(); + + if (displays.size() == 0) { + result->AppendString("{}"); + return; + } + + for (size_t i=0; iAppendString("["+ret.str()+"]"); + return; + } else if (method == "AddScreenChangeCallback") { + int object_id = 0; + arguments.GetInteger(0, &object_id); + EventListener* event_listener = dispatcher_host->GetApiObject(object_id); + JavaScriptDisplayObserver* listener = event_listener->AddListener(); + if (listener) listener->setScreen(gfx::Screen::GetNativeScreen()); + result->AppendBoolean(listener != NULL); + return; + } else if (method == "RemoveScreenChangeCallback") { + int object_id = 0; + arguments.GetInteger(0, &object_id); + EventListener* event_listener = dispatcher_host->GetApiObject(object_id); + bool res = event_listener->RemoveListener(); + result->AppendBoolean(res); + return; + } else if (method == "ChooseDesktopMedia") { + if (gpDCCDMF == NULL) { + gpDCCDMF = new DesktopCaptureChooseDesktopMediaFunction(); + gpDCCDMF->SetArgs(&arguments); + gpDCCDMF->SetRenderViewHost(dispatcher_host->render_view_host()); + + int object_id = 0; + arguments.GetInteger(0, &object_id); + EventListener* event_listener = dispatcher_host->GetApiObject(object_id); + ExtensionFunction::ResponseCallback callback = base::Bind(&ChooseDesktopMediaCallback, event_listener); + gpDCCDMF->set_response_callback(callback); + result->AppendBoolean(gpDCCDMF->RunSync()); + } else { + // Screen Picker GUI is still active, return false; + result->AppendBoolean(false); + } + } else if (method == "CancelChooseDesktopMedia") { + if (gpDCCDMF) { + gpDCCDMF->Cancel(); + gpDCCDMF = NULL; + result->AppendBoolean(true); + } else { + result->AppendBoolean(false); + } + } + +} + +} // namespace nwapi diff --git a/src/api/screen/screen.h b/src/api/screen/screen.h new file mode 100644 index 0000000000..9499a8a7f5 --- /dev/null +++ b/src/api/screen/screen.h @@ -0,0 +1,48 @@ +// Copyright (c) 2014 Jefry Tedjokusumo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +#ifndef CONTENT_NW_SRC_API_SCREEN_SCREEN_H_ +#define CONTENT_NW_SRC_API_SCREEN_SCREEN_H_ + +#include "base/basictypes.h" + +#include + +namespace nwapi { + +class DispatcherHost; +class Screen { +public: + + static void Call(DispatcherHost* dispatcher_host, + const std::string& method, + const base::ListValue& arguments, + base::ListValue* result); + +private: + Screen(); + DISALLOW_COPY_AND_ASSIGN(Screen); +}; + +} // namespace nwapi + + + +#endif //CONTENT_NW_SRC_API_SCREEN_SCREEN_H_ diff --git a/src/api/screen/screen.js b/src/api/screen/screen.js new file mode 100644 index 0000000000..0f2c290fef --- /dev/null +++ b/src/api/screen/screen.js @@ -0,0 +1,172 @@ +// Copyright (c) 2012 Intel Corp +// Copyright (c) 2014 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +function DesktopCaptureMonitor() { + nw.allocateObject(this, {}); + this.sources = new Array(); + this.started = false; +} +require('util').inherits(DesktopCaptureMonitor, exports.Base); + + +DesktopCaptureMonitor.prototype.start = function (screens, windows) { + if (this.started) + return false; + this.started = true; + nw.callObjectMethodSync(this, 'start', [screens, windows]); + return true; +} + +DesktopCaptureMonitor.prototype.stop = function () { + if (!this.started) + return false; + nw.callObjectMethodSync(this, 'stop', []); + this.started = false; + this.sources = new Array(); + return true; +} + +DesktopCaptureMonitor.prototype.on('__nw_desktop_capture_monitor_listner_added', function (id, name, order, type, primaryindex) { + if(this.sources.indexOf(id)!=-1) + { + //TODO: Find out what this event comes twice on some platforms + return; + } + this.sources.splice(order, 0, id); + this.emit("added", id, name, order, type, primaryindex); + for (var i = order + 1; i <= this.sources.length - 1; i++) { + this.emit("orderchanged", this.sources[i], i, i - 1); + } +}); + + +DesktopCaptureMonitor.prototype.on('__nw_desktop_capture_monitor_listner_removed', function (index) { + var id = this.sources[index]; + if (index != -1) { + this.sources.splice(index, 1); + this.emit("removed", id); + for (var i = index; i <= this.sources.length - 1; i++) { + this.emit("orderchanged", this.sources[i], i, i + 1); + } + } +}); + +DesktopCaptureMonitor.prototype.on('__nw_desktop_capture_monitor_listner_moved', function (id, new_index, old_index) { + var temp = this.sources[old_index]; + this.sources.splice(old_index, 1); + this.sources.splice(new_index, 0, temp); + this.emit("orderchanged", temp, new_index, old_index); + for (var i = new_index; i < old_index; i++) + this.emit("orderchanged", this.sources[i + 1], i + 1, i); +}); + +DesktopCaptureMonitor.prototype.on('__nw_desktop_capture_monitor_listner_namechanged', function (id, name) { + this.emit("namechanged", id, name); +}); + +DesktopCaptureMonitor.prototype.on('__nw_desktop_capture_monitor_listner_thumbnailchanged', function (id, thumbnail) { + this.emit("thumbnailchanged", id, thumbnail); +}); + +var listenerCount=0; +DesktopCaptureMonitor.prototype.on = DesktopCaptureMonitor.prototype.addListener = function (ev, callback) { + //throw except if unsupported event + if (ev != "added" && ev != "removed" && ev != "orderchanged" && ev != "namechanged" && ev != "thumbnailchanged") + throw new String("only following events can be listened: added, removed, moved, namechanged, thumbnailchanged"); + + process.EventEmitter.prototype.addListener.apply(this, arguments); +} + + +exports.DesktopCaptureMonitor=DesktopCaptureMonitor; + +var screenInstance = null; + +function Screen() { + nw.allocateObject(this, {}); + this._numListener = 0; + this.DesktopCaptureMonitor = new DesktopCaptureMonitor(); +} +require('util').inherits(Screen, exports.Base); + +// Override the addListener method. +Screen.prototype.on = Screen.prototype.addListener = function(ev, callback) { + if ( ev != "displayBoundsChanged" && ev != "displayAdded" && ev != "displayRemoved" && ev != "chooseDesktopMedia") + throw new TypeError('only following event can be listened: displayBoundsChanged, displayAdded, displayRemoved'); + + var onRemoveListener = function (type, listener) { + if (this._numListener > 0) { + this._numListener--; + if (this._numListener == 0) { + process.EventEmitter.prototype.removeListener.apply(this, ["removeListener", onRemoveListener]); + nw.callStaticMethodSync('Screen', 'RemoveScreenChangeCallback', [ this.id ]); + } + } + }; + + if(this._numListener == 0) { + if (nw.callStaticMethodSync('Screen', 'AddScreenChangeCallback', [ this.id ])[0] == false ) { + throw new Error('nw.callStaticMethodSync(Screen, AddScreenChangeCallback) fails'); + return; + } + process.EventEmitter.prototype.addListener.apply(this, ["removeListener", onRemoveListener]); + } + + // Call parent. + process.EventEmitter.prototype.addListener.apply(this, arguments); + this._numListener++; +} + +// Route events. +Screen.prototype.handleEvent = function(ev) { + if (ev != "chooseDesktopMedia") + arguments[1] = JSON.parse(arguments[1]); + // Call parent. + this.emit.apply(this, arguments); +} + +Screen.prototype.__defineGetter__('screens', function() { + return JSON.parse(nw.callStaticMethodSync('Screen', 'GetScreens', [ ])); +}); + +Screen.prototype.chooseDesktopMedia = function(array, callback) { + if(nw.callStaticMethodSync('Screen', 'ChooseDesktopMedia', [ this.id, array ])[0]) { + this.once('chooseDesktopMedia', callback); + return true; + } + return false; +} + +Screen.prototype.cancelChooseDesktopMedia = function() { + return nw.callStaticMethodSync('Screen', 'CancelChooseDesktopMedia', [ this.id ])[0]; +} +// ======== Screen functions End ======== + +// Store App object in node's context. +exports.Screen = { +Init: function() { + if (screenInstance == null) { + screenInstance = new Screen(); + } + exports.Screen = screenInstance; + return screenInstance; +} +}; + diff --git a/src/api/shell/shell.cc b/src/api/shell/shell.cc index 0bf6fba2c4..ff00722667 100644 --- a/src/api/shell/shell.cc +++ b/src/api/shell/shell.cc @@ -24,11 +24,11 @@ #include "base/logging.h" #include "base/values.h" #include "chrome/browser/platform_util.h" -#include "googleurl/src/gurl.h" +#include "url/gurl.h" using base::FilePath; -namespace api { +namespace nwapi { // static void Shell::Call(const std::string& method, @@ -36,18 +36,18 @@ void Shell::Call(const std::string& method, if (method == "OpenExternal") { std::string uri; arguments.GetString(0, &uri); - platform_util::OpenExternal(GURL(uri)); + platform_util::OpenExternal(NULL, GURL(uri)); } else if (method == "OpenItem") { std::string full_path; arguments.GetString(0, &full_path); - platform_util::OpenItem(FilePath::FromUTF8Unsafe(full_path)); + platform_util::OpenItem(NULL, FilePath::FromUTF8Unsafe(full_path)); } else if (method == "ShowItemInFolder") { std::string full_path; arguments.GetString(0, &full_path); - platform_util::ShowItemInFolder(FilePath::FromUTF8Unsafe(full_path)); + platform_util::ShowItemInFolder(NULL, FilePath::FromUTF8Unsafe(full_path)); } else { NOTREACHED() << "Calling unknown method " << method << " of Shell"; } } -} // namespace api +} // namespace nwapi diff --git a/src/api/shell/shell.h b/src/api/shell/shell.h index 370ca55058..47862ad1c6 100644 --- a/src/api/shell/shell.h +++ b/src/api/shell/shell.h @@ -29,7 +29,7 @@ namespace base { class ListValue; } -namespace api { +namespace nwapi { class Shell { public: @@ -42,6 +42,6 @@ class Shell { DISALLOW_COPY_AND_ASSIGN(Shell); }; -} // namespace api +} // namespace nwapi #endif // CONTENT_NW_SRC_API_SHELL_SHELL_H_ diff --git a/src/api/shortcut/global_shortcut_listener.cc b/src/api/shortcut/global_shortcut_listener.cc new file mode 100644 index 0000000000..f0fc047252 --- /dev/null +++ b/src/api/shortcut/global_shortcut_listener.cc @@ -0,0 +1,137 @@ +// Copyright (c) 2014 Intel Corp +// Copyright (c) 2014 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "content/nw/src/api/shortcut/global_shortcut_listener.h" + +#include "base/logging.h" +#include "content/public/browser/browser_thread.h" +#include "ui/base/accelerators/accelerator.h" + +using content::BrowserThread; + +namespace nwapi { + +GlobalShortcutListener::GlobalShortcutListener() + : shortcut_handling_suspended_(false) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); +} + +GlobalShortcutListener::~GlobalShortcutListener() { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); +} + +bool GlobalShortcutListener::RegisterAccelerator( + const ui::Accelerator& accelerator, Observer* observer) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (IsShortcutHandlingSuspended()) + return false; + + AcceleratorMap::const_iterator it = accelerator_map_.find(accelerator); + if (it != accelerator_map_.end()) { + // The accelerator has been registered. + return false; + } + + if (!RegisterAcceleratorImpl(accelerator)) { + // If the platform-specific registration fails, mostly likely the shortcut + // has been registered by other native applications. + return false; + } + + if (accelerator_map_.empty()) + StartListening(); + + accelerator_map_[accelerator] = observer; + return true; +} + +void GlobalShortcutListener::UnregisterAccelerator( + const ui::Accelerator& accelerator, Observer* observer) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (IsShortcutHandlingSuspended()) + return; + + AcceleratorMap::iterator it = accelerator_map_.find(accelerator); + if (it == accelerator_map_.end()) + return; + // The caller should call this function with the right observer. + DCHECK(it->second == observer); + + UnregisterAcceleratorImpl(accelerator); + accelerator_map_.erase(it); + if (accelerator_map_.empty()) + StopListening(); +} + +void GlobalShortcutListener::UnregisterAccelerators(Observer* observer) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (IsShortcutHandlingSuspended()) + return; + + AcceleratorMap::iterator it = accelerator_map_.begin(); + while (it != accelerator_map_.end()) { + if (it->second == observer) { + AcceleratorMap::iterator to_remove = it++; + UnregisterAccelerator(to_remove->first, observer); + } else { + ++it; + } + } +} + +void GlobalShortcutListener::SetShortcutHandlingSuspended(bool suspended) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (shortcut_handling_suspended_ == suspended) + return; + + shortcut_handling_suspended_ = suspended; + for (AcceleratorMap::iterator it = accelerator_map_.begin(); + it != accelerator_map_.end(); + ++it) { + // On Linux, when shortcut handling is suspended we cannot simply early + // return in NotifyKeyPressed (similar to what we do for non-global + // shortcuts) because we'd eat the keyboard event thereby preventing the + // user from setting the shortcut. Therefore we must unregister while + // handling is suspended and register when handling resumes. + if (shortcut_handling_suspended_) + UnregisterAcceleratorImpl(it->first); + else + RegisterAcceleratorImpl(it->first); + } +} + +bool GlobalShortcutListener::IsShortcutHandlingSuspended() const { + return shortcut_handling_suspended_; +} + +void GlobalShortcutListener::NotifyKeyPressed( + const ui::Accelerator& accelerator) { + AcceleratorMap::iterator iter = accelerator_map_.find(accelerator); + if (iter == accelerator_map_.end()) { + // This should never occur, because if it does, we have failed to unregister + // or failed to clean up the map after unregistering the shortcut. + NOTREACHED(); + return; // No-one is listening to this key. + } + + iter->second->OnKeyPressed(accelerator); +} + +} // namespace nwapi diff --git a/src/api/shortcut/global_shortcut_listener.h b/src/api/shortcut/global_shortcut_listener.h new file mode 100644 index 0000000000..633aca7658 --- /dev/null +++ b/src/api/shortcut/global_shortcut_listener.h @@ -0,0 +1,115 @@ +// Copyright (c) 2014 Intel Corp +// Copyright (c) 2014 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef CONTENT_NW_SRC_API_SHORTCUT_GLOBAL_SHORTCUT_LISTENER_H_ +#define CONTENT_NW_SRC_API_SHORTCUT_GLOBAL_SHORTCUT_LISTENER_H_ + +#include + +#include "base/basictypes.h" +#include "ui/events/keycodes/keyboard_codes.h" + +namespace ui { +class Accelerator; +} + +namespace nwapi { + +// Platform-neutral implementation of a class that keeps track of observers and +// monitors keystrokes. It relays messages to the appropriate observer when a +// global shortcut has been struck by the user. +class GlobalShortcutListener { + public: + class Observer { + public: + // Called when your global shortcut (|accelerator|) is struck. + virtual void OnKeyPressed(const ui::Accelerator& accelerator) = 0; + }; + + virtual ~GlobalShortcutListener(); + + static GlobalShortcutListener* GetInstance(); + + // Register an observer for when a certain |accelerator| is struck. Returns + // true if register successfully, or false if 1) the specificied |accelerator| + // has been registered by another caller or other native applications, or + // 2) shortcut handling is suspended. + // + // Note that we do not support recognizing that an accelerator has been + // registered by another application on all platforms. This is a per-platform + // consideration. + bool RegisterAccelerator(const ui::Accelerator& accelerator, + Observer* observer); + + // Stop listening for the given |accelerator|, does nothing if shortcut + // handling is suspended. + void UnregisterAccelerator(const ui::Accelerator& accelerator, + Observer* observer); + + // Stop listening for all accelerators of the given |observer|, does nothing + // if shortcut handling is suspended. + void UnregisterAccelerators(Observer* observer); + + // Suspend/Resume global shortcut handling. Note that when suspending, + // RegisterAccelerator/UnregisterAccelerator/UnregisterAccelerators are not + // allowed to be called until shortcut handling has been resumed. + void SetShortcutHandlingSuspended(bool suspended); + + // Returns whether shortcut handling is currently suspended. + bool IsShortcutHandlingSuspended() const; + + protected: + GlobalShortcutListener(); + + // Called by platform specific implementations of this class whenever a key + // is struck. Only called for keys that have an observer registered. + void NotifyKeyPressed(const ui::Accelerator& accelerator); + + private: + // The following methods are implemented by platform-specific implementations + // of this class. + // + // Start/StopListening are called when transitioning between zero and nonzero + // registered accelerators. StartListening will be called after + // RegisterAcceleratorImpl and StopListening will be called after + // UnregisterAcceleratorImpl. + // + // For RegisterAcceleratorImpl, implementations return false if registration + // did not complete successfully. + virtual void StartListening() = 0; + virtual void StopListening() = 0; + virtual bool RegisterAcceleratorImpl(const ui::Accelerator& accelerator) = 0; + virtual void UnregisterAcceleratorImpl( + const ui::Accelerator& accelerator) = 0; + + // The map of accelerators that have been successfully registered as global + // shortcuts and their observer. + typedef std::map AcceleratorMap; + AcceleratorMap accelerator_map_; + + // Keeps track of whether shortcut handling is currently suspended. + bool shortcut_handling_suspended_; + + DISALLOW_COPY_AND_ASSIGN(GlobalShortcutListener); +}; + +} // namespace nwapi + +#endif // CONTENT_NW_SRC_API_SHORTCUT_GLOBAL_SHORTCUT_LISTENER_H_ diff --git a/src/api/shortcut/global_shortcut_listener_mac.h b/src/api/shortcut/global_shortcut_listener_mac.h new file mode 100644 index 0000000000..8502d8b871 --- /dev/null +++ b/src/api/shortcut/global_shortcut_listener_mac.h @@ -0,0 +1,122 @@ +// Copyright (c) 2014 Intel Corp +// Copyright (c) 2014 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef CONTENT_NW_SRC_API_SHORTCUT_GLOBAL_SHORTCUT_LISTENER_MAC_H_ +#define CONTENT_NW_SRC_API_SHORTCUT_GLOBAL_SHORTCUT_LISTENER_MAC_H_ + +#include "content/nw/src/api/shortcut/global_shortcut_listener.h" + +#include +#include + +#include + +#include "base/mac/scoped_nsobject.h" + +namespace nwapi { + +// Mac-specific implementation of the GlobalShortcutListener class that +// listens for global shortcuts. Handles basic keyboard intercepting and +// 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). +// 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 { + public: + GlobalShortcutListenerMac(); + virtual ~GlobalShortcutListenerMac(); + + private: + typedef int KeyId; + typedef std::map AcceleratorIdMap; + typedef std::map IdAcceleratorMap; + typedef std::map IdHotKeyRefMap; + + // Keyboard event callbacks. + void OnHotKeyEvent(EventHotKeyID hot_key_id); + bool OnMediaKeyEvent(int key_code); + + // GlobalShortcutListener implementation. + virtual void StartListening() override; + virtual void StopListening() override; + virtual bool RegisterAcceleratorImpl( + const ui::Accelerator& accelerator) override; + virtual void UnregisterAcceleratorImpl( + const ui::Accelerator& accelerator) override; + + // Mac-specific functions for registering hot keys with modifiers. + 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 hot key event handler. + void StartWatchingHotKeys(); + void StopWatchingHotKeys(); + + // Whether or not any media keys are currently registered. + bool IsAnyMediaKeyRegistered(); + + // Whether or not any hot keys are currently registered. + bool IsAnyHotKeyRegistered(); + + // The callback for when an event tap happens. + static CGEventRef EventTapCallback( + CGEventTapProxy proxy, CGEventType type, CGEventRef event, void* refcon); + + // The callback for when a hot key event happens. + static OSStatus HotKeyHandler( + EventHandlerCallRef next_handler, EventRef event, void* user_data); + + // Whether this object is listening for global shortcuts. + bool is_listening_; + + // 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 + // corresponding hotkey IDs. For quickly finding if an accelerator is + // registered. + AcceleratorIdMap accelerator_ids_; + + // The inverse map for quickly looking up accelerators by hotkey id. + IdAcceleratorMap id_accelerators_; + + // Keyboard shortcut IDs to hotkeys map for unregistration. + IdHotKeyRefMap id_hot_key_refs_; + + // Event tap for intercepting mac media keys. + CFMachPortRef event_tap_; + CFRunLoopSourceRef event_tap_source_; + + // Event handler for keyboard shortcut hot keys. + EventHandlerRef event_handler_; + + DISALLOW_COPY_AND_ASSIGN(GlobalShortcutListenerMac); +}; + +} // namespace nwapi + +#endif // CONTENT_NW_SRC_API_SHORTCUT_GLOBAL_SHORTCUT_LISTENER_MAC_H_ diff --git a/src/api/shortcut/global_shortcut_listener_mac.mm b/src/api/shortcut/global_shortcut_listener_mac.mm new file mode 100644 index 0000000000..43132af46f --- /dev/null +++ b/src/api/shortcut/global_shortcut_listener_mac.mm @@ -0,0 +1,399 @@ +// Copyright (c) 2014 Intel Corp +// Copyright (c) 2014 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "content/nw/src/api/shortcut/global_shortcut_listener_mac.h" + +#include +#import +#include + +#import "base/mac/foundation_util.h" +#include "content/public/browser/browser_thread.h" +#include "ui/base/accelerators/accelerator.h" +#include "ui/events/event.h" +#import "ui/events/keycodes/keyboard_code_conversion_mac.h" + +using content::BrowserThread; +using nwapi::GlobalShortcutListenerMac; + +namespace { + +// The media keys subtype. No official docs found, but widely known. +// http://lists.apple.com/archives/cocoa-dev/2007/Aug/msg00499.html +const int kSystemDefinedEventMediaKeysSubtype = 8; + +ui::KeyboardCode MediaKeyCodeToKeyboardCode(int key_code) { + switch (key_code) { + case NX_KEYTYPE_PLAY: + return ui::VKEY_MEDIA_PLAY_PAUSE; + case NX_KEYTYPE_PREVIOUS: + case NX_KEYTYPE_REWIND: + return ui::VKEY_MEDIA_PREV_TRACK; + case NX_KEYTYPE_NEXT: + case NX_KEYTYPE_FAST: + return ui::VKEY_MEDIA_NEXT_TRACK; + } + return ui::VKEY_UNKNOWN; +} + +bool IsMediaKey(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); +} + +} // namespace + +namespace nwapi { + +// static +GlobalShortcutListener* GlobalShortcutListener::GetInstance() { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + static GlobalShortcutListenerMac* instance = + new GlobalShortcutListenerMac(); + return instance; +} + +GlobalShortcutListenerMac::GlobalShortcutListenerMac() + : is_listening_(false), + hot_key_id_(0), + event_tap_(NULL), + event_tap_source_(NULL), + event_handler_(NULL) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); +} + +GlobalShortcutListenerMac::~GlobalShortcutListenerMac() { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + // By this point, UnregisterAccelerator should have been called for all + // keyboard shortcuts. Still we should clean up. + if (is_listening_) + StopListening(); + + // If keys are still registered, make sure we stop the tap. Again, this + // should never happen. + if (IsAnyMediaKeyRegistered()) + StopWatchingMediaKeys(); + + if (IsAnyHotKeyRegistered()) + StopWatchingHotKeys(); +} + +void GlobalShortcutListenerMac::StartListening() { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + DCHECK(!accelerator_ids_.empty()); + DCHECK(!id_accelerators_.empty()); + DCHECK(!is_listening_); + + is_listening_ = true; +} + +void GlobalShortcutListenerMac::StopListening() { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + DCHECK(accelerator_ids_.empty()); // Make sure the set is clean. + DCHECK(id_accelerators_.empty()); + DCHECK(is_listening_); + + is_listening_ = false; +} + +void GlobalShortcutListenerMac::OnHotKeyEvent(EventHotKeyID hot_key_id) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + // This hot key should be registered. + DCHECK(id_accelerators_.find(hot_key_id.id) != id_accelerators_.end()); + // Look up the accelerator based on this hot key ID. + const ui::Accelerator& accelerator = id_accelerators_[hot_key_id.id]; + NotifyKeyPressed(accelerator); +} + +bool GlobalShortcutListenerMac::OnMediaKeyEvent(int media_key_code) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + ui::KeyboardCode key_code = MediaKeyCodeToKeyboardCode(media_key_code); + // Create an accelerator corresponding to the keyCode. + ui::Accelerator accelerator(key_code, 0); + // Look for a match with a bound hot_key. + if (accelerator_ids_.find(accelerator) != accelerator_ids_.end()) { + // If matched, callback to the event handling system. + NotifyKeyPressed(accelerator); + return true; + } + return false; +} + +bool GlobalShortcutListenerMac::RegisterAcceleratorImpl( + const ui::Accelerator& accelerator) { + 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(); + } + } else { + // Register hot_key if they are non-media keyboard shortcuts. + if (!RegisterHotKey(accelerator, hot_key_id_)) + return false; + + if (!IsAnyHotKeyRegistered()) { + StartWatchingHotKeys(); + } + } + + // Store the hotkey-ID mappings we will need for lookup later. + id_accelerators_[hot_key_id_] = accelerator; + accelerator_ids_[accelerator] = hot_key_id_; + ++hot_key_id_; + return true; +} + +void GlobalShortcutListenerMac::UnregisterAcceleratorImpl( + const ui::Accelerator& accelerator) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(accelerator_ids_.find(accelerator) != accelerator_ids_.end()); + + // Unregister the hot_key if it's a keyboard shortcut. + if (!IsMediaKey(accelerator)) + UnregisterHotKey(accelerator); + + // Remove hot_key from the mappings. + KeyId key_id = accelerator_ids_[accelerator]; + 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(); + } else { + // If we unregistered a hot key, and no more hot keys are registered, remove + // the hot key handler. + if (!IsAnyHotKeyRegistered()) { + StopWatchingHotKeys(); + } + } +} + +bool GlobalShortcutListenerMac::RegisterHotKey( + const ui::Accelerator& accelerator, KeyId hot_key_id) { + EventHotKeyID event_hot_key_id; + + // Signature uniquely identifies the application that owns this hot_key. + event_hot_key_id.signature = base::mac::CreatorCodeForApplication(); + event_hot_key_id.id = hot_key_id; + + // Translate ui::Accelerator modifiers to cmdKey, altKey, etc. + int modifiers = 0; + modifiers |= (accelerator.IsShiftDown() ? shiftKey : 0); + modifiers |= (accelerator.IsCtrlDown() ? controlKey : 0); + modifiers |= (accelerator.IsAltDown() ? optionKey : 0); + modifiers |= (accelerator.IsCmdDown() ? cmdKey : 0); + + int key_code = ui::MacKeyCodeForWindowsKeyCode(accelerator.key_code(), 0, + NULL, NULL); + + // Register the event hot key. + EventHotKeyRef hot_key_ref; + OSStatus status = RegisterEventHotKey(key_code, modifiers, event_hot_key_id, + GetApplicationEventTarget(), 0, &hot_key_ref); + if (status != noErr) + return false; + + id_hot_key_refs_[hot_key_id] = hot_key_ref; + return true; +} + +void GlobalShortcutListenerMac::UnregisterHotKey( + const ui::Accelerator& accelerator) { + // Ensure this accelerator is already registered. + DCHECK(accelerator_ids_.find(accelerator) != accelerator_ids_.end()); + // Get the ref corresponding to this accelerator. + KeyId key_id = accelerator_ids_[accelerator]; + EventHotKeyRef ref = id_hot_key_refs_[key_id]; + // Unregister the event hot key. + UnregisterEventHotKey(ref); + + // Remove the event from the mapping. + id_hot_key_refs_.erase(key_id); +} + +void GlobalShortcutListenerMac::StartWatchingMediaKeys() { + // 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. + event_tap_ = CGEventTapCreate(kCGSessionEventTap, + kCGHeadInsertEventTap, + kCGEventTapOptionDefault, + CGEventMaskBit(NX_SYSDEFINED), + EventTapCallback, + this); + if (event_tap_ == NULL) { + LOG(ERROR) << "Error: failed to create event tap."; + return; + } + + event_tap_source_ = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault, + event_tap_, 0); + if (event_tap_source_ == NULL) { + LOG(ERROR) << "Error: failed to create new run loop source."; + return; + } + + CFRunLoopAddSource(CFRunLoopGetCurrent(), event_tap_source_, + kCFRunLoopCommonModes); +} + +void GlobalShortcutListenerMac::StopWatchingMediaKeys() { + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), event_tap_source_, + kCFRunLoopCommonModes); + // Ensure both event tap and source are initialized. + DCHECK(event_tap_ != NULL); + DCHECK(event_tap_source_ != NULL); + + // Invalidate the event tap. + CFMachPortInvalidate(event_tap_); + CFRelease(event_tap_); + event_tap_ = NULL; + + // Release the event tap source. + CFRelease(event_tap_source_); + event_tap_source_ = NULL; +} + +void GlobalShortcutListenerMac::StartWatchingHotKeys() { + DCHECK(!event_handler_); + EventHandlerUPP hot_key_function = NewEventHandlerUPP(HotKeyHandler); + EventTypeSpec event_type; + event_type.eventClass = kEventClassKeyboard; + event_type.eventKind = kEventHotKeyPressed; + InstallApplicationEventHandler( + hot_key_function, 1, &event_type, this, &event_handler_); +} + +void GlobalShortcutListenerMac::StopWatchingHotKeys() { + DCHECK(event_handler_); + RemoveEventHandler(event_handler_); + event_handler_ = NULL; +} + +bool GlobalShortcutListenerMac::IsAnyMediaKeyRegistered() { + // Iterate through registered accelerators, looking for media keys. + AcceleratorIdMap::iterator it; + for (it = accelerator_ids_.begin(); it != accelerator_ids_.end(); ++it) { + if (IsMediaKey(it->first)) + return true; + } + return false; +} + +bool GlobalShortcutListenerMac::IsAnyHotKeyRegistered() { + AcceleratorIdMap::iterator it; + for (it = accelerator_ids_.begin(); it != accelerator_ids_.end(); ++it) { + if (!IsMediaKey(it->first)) + return true; + } + return false; +} + +// Processed events should propagate if they aren't handled by any listeners. +// For events that don't matter, this handler should return as quickly as +// possible. +// Returning event causes the event to propagate to other applications. +// Returning NULL prevents the event from propagating. +// static +CGEventRef GlobalShortcutListenerMac::EventTapCallback( + CGEventTapProxy proxy, CGEventType type, CGEventRef event, void* refcon) { + GlobalShortcutListenerMac* shortcut_listener = + static_cast(refcon); + + // Handle the timeout case by re-enabling the tap. + if (type == kCGEventTapDisabledByTimeout) { + CGEventTapEnable(shortcut_listener->event_tap_, TRUE); + return event; + } + + // Convert the CGEvent to an NSEvent for access to the data1 field. + NSEvent* ns_event = [NSEvent eventWithCGEvent:event]; + if (ns_event == nil) { + return event; + } + + // Ignore events that are not system defined media keys. + if (type != NX_SYSDEFINED || + [ns_event type] != NSSystemDefined || + [ns_event subtype] != kSystemDefinedEventMediaKeysSubtype) { + return event; + } + + NSInteger data1 = [ns_event data1]; + // Ignore media keys that aren't previous, next and play/pause. + // 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) { + return event; + } + + int key_flags = data1 & 0x0000FFFF; + bool is_key_pressed = ((key_flags & 0xFF00) >> 8) == 0xA; + + // If the key wasn't pressed (eg. was released), ignore this event. + 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); + + // Prevent event from proagating to other apps if handled by Chrome. + if (was_handled) + return NULL; + + // By default, pass the event through. + return event; +} + +// static +OSStatus GlobalShortcutListenerMac::HotKeyHandler( + EventHandlerCallRef next_handler, EventRef event, void* user_data) { + // Extract the hotkey from the event. + EventHotKeyID hot_key_id; + OSStatus result = GetEventParameter(event, kEventParamDirectObject, + typeEventHotKeyID, NULL, sizeof(hot_key_id), NULL, &hot_key_id); + if (result != noErr) + return result; + + GlobalShortcutListenerMac* shortcut_listener = + static_cast(user_data); + shortcut_listener->OnHotKeyEvent(hot_key_id); + return noErr; +} + +} // namespace nwapi diff --git a/src/api/shortcut/global_shortcut_listener_win.cc b/src/api/shortcut/global_shortcut_listener_win.cc new file mode 100644 index 0000000000..73ca5ca299 --- /dev/null +++ b/src/api/shortcut/global_shortcut_listener_win.cc @@ -0,0 +1,121 @@ +// Copyright (c) 2014 Intel Corp +// Copyright (c) 2014 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "content/nw/src/api/shortcut/global_shortcut_listener_win.h" + +#include "base/win/win_util.h" +#include "content/public/browser/browser_thread.h" +#include "ui/base/accelerators/accelerator.h" +#include "ui/events/event_constants.h" +#include "ui/events/keycodes/keyboard_code_conversion_win.h" + +using content::BrowserThread; + +namespace nwapi { + +// static +GlobalShortcutListener* GlobalShortcutListener::GetInstance() { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + static GlobalShortcutListenerWin* instance = + new GlobalShortcutListenerWin(); + return instance; +} + +GlobalShortcutListenerWin::GlobalShortcutListenerWin() + : is_listening_(false) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); +} + +GlobalShortcutListenerWin::~GlobalShortcutListenerWin() { + if (is_listening_) + StopListening(); +} + +void GlobalShortcutListenerWin::StartListening() { + DCHECK(!is_listening_); // Don't start twice. + DCHECK(!hotkey_ids_.empty()); // Also don't start if no hotkey is registered. + gfx::SingletonHwnd::GetInstance()->AddObserver(this); + is_listening_ = true; +} + +void GlobalShortcutListenerWin::StopListening() { + DCHECK(is_listening_); // No point if we are not already listening. + DCHECK(hotkey_ids_.empty()); // Make sure the map is clean before ending. + gfx::SingletonHwnd::GetInstance()->RemoveObserver(this); + is_listening_ = false; +} + +void GlobalShortcutListenerWin::OnWndProc(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { + if (message != WM_HOTKEY) + return; + + int key_code = HIWORD(lparam); + int modifiers = 0; + modifiers |= (LOWORD(lparam) & MOD_SHIFT) ? ui::EF_SHIFT_DOWN : 0; + modifiers |= (LOWORD(lparam) & MOD_ALT) ? ui::EF_ALT_DOWN : 0; + modifiers |= (LOWORD(lparam) & MOD_CONTROL) ? ui::EF_CONTROL_DOWN : 0; + ui::Accelerator accelerator( + ui::KeyboardCodeForWindowsKeyCode(key_code), modifiers); + + NotifyKeyPressed(accelerator); +} + +bool GlobalShortcutListenerWin::RegisterAcceleratorImpl( + const ui::Accelerator& accelerator) { + DCHECK(hotkey_ids_.find(accelerator) == hotkey_ids_.end()); + + int modifiers = 0; + modifiers |= accelerator.IsShiftDown() ? MOD_SHIFT : 0; + modifiers |= accelerator.IsCtrlDown() ? MOD_CONTROL : 0; + modifiers |= accelerator.IsAltDown() ? MOD_ALT : 0; + static int hotkey_id = 0; + bool success = !!RegisterHotKey( + gfx::SingletonHwnd::GetInstance()->hwnd(), + hotkey_id, + modifiers, + accelerator.key_code()); + + if (!success) { + // Most likely error: 1409 (Hotkey already registered). + return false; + } + + hotkey_ids_[accelerator] = hotkey_id++; + return true; +} + +void GlobalShortcutListenerWin::UnregisterAcceleratorImpl( + const ui::Accelerator& accelerator) { + HotkeyIdMap::iterator it = hotkey_ids_.find(accelerator); + DCHECK(it != hotkey_ids_.end()); + + bool success = !!UnregisterHotKey( + gfx::SingletonHwnd::GetInstance()->hwnd(), it->second); + // This call should always succeed, as long as we pass in the right HWND and + // an id we've used to register before. + DCHECK(success); + + hotkey_ids_.erase(it); +} + +} // namespace nwapi diff --git a/src/api/shortcut/global_shortcut_listener_win.h b/src/api/shortcut/global_shortcut_listener_win.h new file mode 100644 index 0000000000..563e94bf44 --- /dev/null +++ b/src/api/shortcut/global_shortcut_listener_win.h @@ -0,0 +1,67 @@ +// Copyright (c) 2014 Intel Corp +// Copyright (c) 2014 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef CONTENT_NW_SRC_API_SHORTCUT_GLOBAL_SHORTCUT_LISTENER_WIN_H_ +#define CONTENT_NW_SRC_API_SHORTCUT_GLOBAL_SHORTCUT_LISTENER_WIN_H_ + +#include + +#include "content/nw/src/api/shortcut/global_shortcut_listener.h" +#include "ui/gfx/win/singleton_hwnd.h" + +namespace nwapi { + +// Windows-specific implementation of the GlobalShortcutListener class that +// listens for global shortcuts. Handles setting up a keyboard hook and +// forwarding its output to the base class for processing. +class GlobalShortcutListenerWin : public GlobalShortcutListener, + public gfx::SingletonHwnd::Observer { + public: + GlobalShortcutListenerWin(); + virtual ~GlobalShortcutListenerWin(); + + private: + // The implementation of our Window Proc, called by SingletonHwnd. + virtual void OnWndProc(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) override; + + // GlobalShortcutListener implementation. + virtual void StartListening() override; + virtual void StopListening() override; + virtual bool RegisterAcceleratorImpl( + const ui::Accelerator& accelerator) override; + virtual void UnregisterAcceleratorImpl( + const ui::Accelerator& accelerator) override; + + // Whether this object is listening for global shortcuts. + bool is_listening_; + + // A map of registered accelerators and their registration ids. + typedef std::map HotkeyIdMap; + HotkeyIdMap hotkey_ids_; + + DISALLOW_COPY_AND_ASSIGN(GlobalShortcutListenerWin); +}; + +} // namespace nwapi + +#endif // CONTENT_NW_SRC_API_SHORTCUT_GLOBAL_SHORTCUT_LISTENER_WIN_H_ diff --git a/src/api/shortcut/global_shortcut_listener_x11.cc b/src/api/shortcut/global_shortcut_listener_x11.cc new file mode 100644 index 0000000000..11d514248e --- /dev/null +++ b/src/api/shortcut/global_shortcut_listener_x11.cc @@ -0,0 +1,193 @@ +// Copyright (c) 2014 Intel Corp +// Copyright (c) 2014 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "content/nw/src/api/shortcut/global_shortcut_listener_x11.h" + +#include "content/public/browser/browser_thread.h" +#include "ui/base/accelerators/accelerator.h" +#include "ui/events/keycodes/keyboard_code_conversion_x.h" +#include "ui/gfx/x/x11_error_tracker.h" +#include "ui/gfx/x/x11_types.h" + +#if defined(OS_LINUX) +#include +#endif + +using content::BrowserThread; + +namespace { + +// The modifiers masks used for grabing keys. Due to XGrabKey only working on +// exact modifiers, we need to grab all key combination including zero or more +// of the following: Num lock, Caps lock and Scroll lock. So that we can make +// sure the behavior of global shortcuts is consistent on all platforms. +const unsigned int kModifiersMasks[] = { + 0, // No additional modifier. + Mod2Mask, // Num lock + LockMask, // Caps lock + Mod5Mask, // Scroll lock + Mod2Mask | LockMask, + Mod2Mask | Mod5Mask, + LockMask | Mod5Mask, + Mod2Mask | LockMask | Mod5Mask +}; + +int GetNativeModifiers(const ui::Accelerator& accelerator) { + int modifiers = 0; + modifiers |= accelerator.IsShiftDown() ? ShiftMask : 0; + modifiers |= accelerator.IsCtrlDown() ? ControlMask : 0; + modifiers |= accelerator.IsAltDown() ? Mod1Mask : 0; + + return modifiers; +} + +} // namespace + +namespace nwapi { + +// static +GlobalShortcutListener* GlobalShortcutListener::GetInstance() { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + static GlobalShortcutListenerX11* instance = + new GlobalShortcutListenerX11(); + return instance; +} + +GlobalShortcutListenerX11::GlobalShortcutListenerX11() + : is_listening_(false), + x_display_(gfx::GetXDisplay()), + x_root_window_(DefaultRootWindow(x_display_)) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); +} + +GlobalShortcutListenerX11::~GlobalShortcutListenerX11() { + if (is_listening_) + StopListening(); +} + +void GlobalShortcutListenerX11::StartListening() { + DCHECK(!is_listening_); // Don't start twice. + DCHECK(!registered_hot_keys_.empty()); // Also don't start if no hotkey is + // registered. +#if defined(OS_LINUX) + gdk_window_add_filter(gdk_get_default_root_window(), + &GlobalShortcutListenerX11::OnXEventThunk, + this); +#else + base::MessagePumpX11::Current()->AddDispatcherForRootWindow(this); +#endif + + is_listening_ = true; +} + +void GlobalShortcutListenerX11::StopListening() { + DCHECK(is_listening_); // No point if we are not already listening. + DCHECK(registered_hot_keys_.empty()); // Make sure the set is clean before + // ending. + +#if defined(OS_LINUX) + gdk_window_remove_filter(NULL, + &GlobalShortcutListenerX11::OnXEventThunk, + this); +#else + base::MessagePumpX11::Current()->RemoveDispatcherForRootWindow(this); +#endif + + is_listening_ = false; +} + +uint32_t GlobalShortcutListenerX11::Dispatch(const base::NativeEvent& event) { + if (event->type == KeyPress) + OnXKeyPressEvent(event); + + return POST_DISPATCH_NONE; +} + +bool GlobalShortcutListenerX11::RegisterAcceleratorImpl( + const ui::Accelerator& accelerator) { + DCHECK(registered_hot_keys_.find(accelerator) == registered_hot_keys_.end()); + + int modifiers = GetNativeModifiers(accelerator); + KeyCode keycode = XKeysymToKeycode(x_display_, + XKeysymForWindowsKeyCode(accelerator.key_code(), false)); + gfx::X11ErrorTracker err_tracker; + + // Because XGrabKey only works on the exact modifiers mask, we should register + // our hot keys with modifiers that we want to ignore, including Num lock, + // Caps lock, Scroll lock. See comment about |kModifiersMasks|. + for (size_t i = 0; i < arraysize(kModifiersMasks); ++i) { + XGrabKey(x_display_, keycode, modifiers | kModifiersMasks[i], + x_root_window_, False, GrabModeAsync, GrabModeAsync); + } + + if (err_tracker.FoundNewError()) { + // We may have part of the hotkeys registered, clean up. + for (size_t i = 0; i < arraysize(kModifiersMasks); ++i) { + XUngrabKey(x_display_, keycode, modifiers | kModifiersMasks[i], + x_root_window_); + } + + return false; + } + + registered_hot_keys_.insert(accelerator); + return true; +} + +void GlobalShortcutListenerX11::UnregisterAcceleratorImpl( + const ui::Accelerator& accelerator) { + DCHECK(registered_hot_keys_.find(accelerator) != registered_hot_keys_.end()); + + int modifiers = GetNativeModifiers(accelerator); + KeyCode keycode = XKeysymToKeycode(x_display_, + XKeysymForWindowsKeyCode(accelerator.key_code(), false)); + + for (size_t i = 0; i < arraysize(kModifiersMasks); ++i) { + XUngrabKey(x_display_, keycode, modifiers | kModifiersMasks[i], + x_root_window_); + } + registered_hot_keys_.erase(accelerator); +} + +#if defined(OS_LINUX) +GdkFilterReturn GlobalShortcutListenerX11::OnXEvent(GdkXEvent* gdk_x_event, + GdkEvent* gdk_event) { + XEvent* x_event = static_cast(gdk_x_event); + if (x_event->type == KeyPress) + OnXKeyPressEvent(x_event); + + return GDK_FILTER_CONTINUE; +} +#endif + +void GlobalShortcutListenerX11::OnXKeyPressEvent(::XEvent* x_event) { + DCHECK(x_event->type == KeyPress); + int modifiers = 0; + modifiers |= (x_event->xkey.state & ShiftMask) ? ui::EF_SHIFT_DOWN : 0; + modifiers |= (x_event->xkey.state & ControlMask) ? ui::EF_CONTROL_DOWN : 0; + modifiers |= (x_event->xkey.state & Mod1Mask) ? ui::EF_ALT_DOWN : 0; + + ui::Accelerator accelerator( + ui::KeyboardCodeFromXKeyEvent(x_event), modifiers); + if (registered_hot_keys_.find(accelerator) != registered_hot_keys_.end()) + NotifyKeyPressed(accelerator); +} + +} // namespace nwapi diff --git a/src/api/shortcut/global_shortcut_listener_x11.h b/src/api/shortcut/global_shortcut_listener_x11.h new file mode 100644 index 0000000000..633bb716fa --- /dev/null +++ b/src/api/shortcut/global_shortcut_listener_x11.h @@ -0,0 +1,88 @@ +// Copyright (c) 2014 Intel Corp +// Copyright (c) 2014 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef CONTENT_NW_SRC_API_SHORTCUT_GLOBAL_SHORTCUT_LISTENER_X11_H_ +#define CONTENT_NW_SRC_API_SHORTCUT_GLOBAL_SHORTCUT_LISTENER_X11_H_ + +#include +#include + +#include "base/compiler_specific.h" +#include "base/message_loop/message_pump_dispatcher.h" +#include "content/nw/src/api/shortcut/global_shortcut_listener.h" + +#if defined(OS_LINUX) +#include +#include "chrome/browser/ui/libgtk2ui/gtk2_signal.h" +#endif // defined(TOOLKIT_GTK) + +namespace nwapi { + +// X11-specific implementation of the GlobalShortcutListener class that +// listens for global shortcuts. Handles basic keyboard intercepting and +// forwards its output to the base class for processing. +class GlobalShortcutListenerX11 + : +#if !defined(TOOLKIT_GTK) + public base::MessagePumpDispatcher, +#endif + public GlobalShortcutListener { + public: + GlobalShortcutListenerX11(); + ~GlobalShortcutListenerX11() final; + + // base::MessagePumpDispatcher implementation. + uint32_t Dispatch(const base::NativeEvent& event) override; + + private: + // GlobalShortcutListener implementation. + void StartListening() override; + void StopListening() override; + bool RegisterAcceleratorImpl( + const ui::Accelerator& accelerator) override; + void UnregisterAcceleratorImpl( + const ui::Accelerator& accelerator) override; + +#if defined(OS_LINUX) + // Callback for XEvents of the default root window. + CHROMEG_CALLBACK_1(GlobalShortcutListenerX11, GdkFilterReturn, + OnXEvent, GdkXEvent*, GdkEvent*); +#endif + + // Invoked when a global shortcut is pressed. + void OnXKeyPressEvent(::XEvent* x_event); + + // Whether this object is listening for global shortcuts. + bool is_listening_; + + // The x11 default display and the native root window. + ::Display* x_display_; + ::Window x_root_window_; + + // A set of registered accelerators. + typedef std::set RegisteredHotKeys; + RegisteredHotKeys registered_hot_keys_; + + DISALLOW_COPY_AND_ASSIGN(GlobalShortcutListenerX11); +}; + +} // namespace nwapi + +#endif // CONTENT_NW_SRC_API_SHORTCUT_GLOBAL_SHORTCUT_LISTENER_X11_H_ diff --git a/src/api/shortcut/shorcut.js b/src/api/shortcut/shorcut.js new file mode 100644 index 0000000000..f51ec49a8f --- /dev/null +++ b/src/api/shortcut/shorcut.js @@ -0,0 +1,66 @@ +// Copyright (c) 2014 Intel Corp +// Copyright (c) 2014 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +var v8_util = process.binding('v8_util'); + +function Shortcut(option) { + if (typeof option != 'object') + throw new TypeError('Invalid option.'); + + if (!option.hasOwnProperty('key')) + throw new TypeError("Shortcut requires 'key' to specify key combinations."); + + option.key = String(option.key); + this.key = option.key; + + if (option.hasOwnProperty('active')) { + if (typeof option.active != 'function') + throw new TypeError("'active' must be a valid function."); + else + this.active = option.active; + } + + if (option.hasOwnProperty('failed')) { + if (typeof option.failed != 'function') + throw new TypeError("'failed' must be a valid function."); + else + this.failed = option.failed; + } + + v8_util.setHiddenValue(this, 'option', option); + nw.allocateObject(this, option); +} + +require('util').inherits(Shortcut, exports.Base); + +Shortcut.prototype.handleEvent = function(ev) { + if (ev == 'active') { + if (typeof this.active == 'function') + this.active(); + } else if (ev == 'failed') { + if (typeof this.failed == 'function') + this.failed(arguments[1]); + } + + // Emit generate event handler + exports.Base.prototype.handleEvent.apply(this, arguments); +} + +exports.Shortcut = Shortcut; diff --git a/src/api/shortcut/shortcut.cc b/src/api/shortcut/shortcut.cc new file mode 100644 index 0000000000..16dea75e38 --- /dev/null +++ b/src/api/shortcut/shortcut.cc @@ -0,0 +1,239 @@ +// Copyright (c) 2014 Intel Corp +// Copyright (c) 2014 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "content/nw/src/api/shortcut/shortcut.h" + +#include + +#include "base/compiler_specific.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/values.h" +#include "content/nw/src/api/dispatcher_host.h" +#include "content/nw/src/api/shortcut/shortcut_constants.h" + +namespace nwapi { + +ui::Accelerator Parse(const std::string& shortcut) { + // Convert to lower case, see + // https://github.com/rogerwang/node-webkit/pull/1735. + std::string lower_shortcut = base::StringToLowerASCII(shortcut); + + std::vector tokens; + base::SplitString(lower_shortcut, '+', &tokens); + if (tokens.size() == 0) + return ui::Accelerator(); + + int modifiers = ui::EF_NONE; + ui::KeyboardCode key = ui::VKEY_UNKNOWN; + for (size_t i = 0; i < tokens.size(); i++) { + if (tokens[i] == kKeyCtrl) { +#if defined(OS_MACOSX) + modifiers |= ui::EF_COMMAND_DOWN; +#else + modifiers |= ui::EF_CONTROL_DOWN; +#endif + } else if (tokens[i] == kKeyAlt) { + modifiers |= ui::EF_ALT_DOWN; + } else if (tokens[i] == kKeyShift) { + modifiers |= ui::EF_SHIFT_DOWN; + } else if (tokens[i].size() == 1 || // A-Z, 0-9. + tokens[i] == kKeyComma || + tokens[i] == kKeyPeriod || + tokens[i] == kKeyUp || + tokens[i] == kKeyDown || + tokens[i] == kKeyLeft || + tokens[i] == kKeyRight || + tokens[i] == kKeyIns || + tokens[i] == kKeyDel || + tokens[i] == kKeyHome || + tokens[i] == kKeyEnd || + tokens[i] == kKeyPgUp || + tokens[i] == kKeyPgDwn || + tokens[i] == kKeyTab || + tokens[i] == kKeyF1 || + tokens[i] == kKeyF2 || + tokens[i] == kKeyF3 || + tokens[i] == kKeyF4 || + tokens[i] == kKeyF5 || + tokens[i] == kKeyF6 || + tokens[i] == kKeyF7 || + tokens[i] == kKeyF8 || + tokens[i] == kKeyF9 || + tokens[i] == kKeyF10 || + tokens[i] == kKeyF11 || + tokens[i] == kKeyF12 || + tokens[i] == kKeyF13 || + tokens[i] == kKeyF14 || + tokens[i] == kKeyF15 || + tokens[i] == kKeyF16 || + tokens[i] == kKeyF17 || + tokens[i] == kKeyF18 || + tokens[i] == kKeyF19 || + tokens[i] == kKeyF20 || + tokens[i] == kKeyF21 || + tokens[i] == kKeyF22 || + tokens[i] == kKeyF23 || + tokens[i] == kKeyF24 || + tokens[i] == kKeyMediaNextTrack || + tokens[i] == kKeyMediaPlayPause || + tokens[i] == kKeyMediaPrevTrack || + tokens[i] == kKeyMediaStop) { + if (key != ui::VKEY_UNKNOWN) { + // Multiple key assignments. + key = ui::VKEY_UNKNOWN; + break; + } + + if (tokens[i] == kKeyComma) { + key = ui::VKEY_OEM_COMMA; + } else if (tokens[i] == kKeyPeriod) { + key = ui::VKEY_OEM_PERIOD; + } else if (tokens[i] == kKeyUp) { + key = ui::VKEY_UP; + } else if (tokens[i] == kKeyDown) { + key = ui::VKEY_DOWN; + } else if (tokens[i] == kKeyLeft) { + key = ui::VKEY_LEFT; + } else if (tokens[i] == kKeyRight) { + key = ui::VKEY_RIGHT; + } else if (tokens[i] == kKeyIns) { + key = ui::VKEY_INSERT; + } else if (tokens[i] == kKeyDel) { + key = ui::VKEY_DELETE; + } else if (tokens[i] == kKeyHome) { + key = ui::VKEY_HOME; + } else if (tokens[i] == kKeyEnd) { + key = ui::VKEY_END; + } else if (tokens[i] == kKeyPgUp) { + key = ui::VKEY_PRIOR; + } else if (tokens[i] == kKeyPgDwn) { + key = ui::VKEY_NEXT; + } else if (tokens[i] == kKeyTab) { + key = ui::VKEY_TAB; + } else if (tokens[i] == kKeyF1) { + key = ui::VKEY_F1; + } else if (tokens[i] == kKeyF2) { + key = ui::VKEY_F2; + } else if (tokens[i] == kKeyF3) { + key = ui::VKEY_F3; + } else if (tokens[i] == kKeyF4) { + key = ui::VKEY_F4; + } else if (tokens[i] == kKeyF5) { + key = ui::VKEY_F5; + } else if (tokens[i] == kKeyF6) { + key = ui::VKEY_F6; + } else if (tokens[i] == kKeyF7) { + key = ui::VKEY_F7; + } else if (tokens[i] == kKeyF8) { + key = ui::VKEY_F8; + } else if (tokens[i] == kKeyF9) { + key = ui::VKEY_F9; + } else if (tokens[i] == kKeyF10) { + key = ui::VKEY_F10; + } else if (tokens[i] == kKeyF11) { + key = ui::VKEY_F11; + } else if (tokens[i] == kKeyF12) { + key = ui::VKEY_F12; + } else if (tokens[i] == kKeyF13) { + key = ui::VKEY_F13; + } else if (tokens[i] == kKeyF14) { + key = ui::VKEY_F14; + } else if (tokens[i] == kKeyF15) { + key = ui::VKEY_F15; + } else if (tokens[i] == kKeyF16) { + key = ui::VKEY_F16; + } else if (tokens[i] == kKeyF17) { + key = ui::VKEY_F17; + } else if (tokens[i] == kKeyF18) { + key = ui::VKEY_F18; + } else if (tokens[i] == kKeyF19) { + key = ui::VKEY_F19; + } else if (tokens[i] == kKeyF20) { + key = ui::VKEY_F20; + } else if (tokens[i] == kKeyF21) { + key = ui::VKEY_F21; + } else if (tokens[i] == kKeyF22) { + key = ui::VKEY_F22; + } else if (tokens[i] == kKeyF23) { + key = ui::VKEY_F23; + } else if (tokens[i] == kKeyF24) { + key = ui::VKEY_F24; + } else if (tokens[i] == kKeyMediaNextTrack) { + key = ui::VKEY_MEDIA_NEXT_TRACK; + } else if (tokens[i] == kKeyMediaPlayPause) { + key = ui::VKEY_MEDIA_PLAY_PAUSE; + } else if (tokens[i] == kKeyMediaPrevTrack) { + key = ui::VKEY_MEDIA_PREV_TRACK; + } else if (tokens[i] == kKeyMediaStop) { + key = ui::VKEY_MEDIA_STOP; + } else if (tokens[i].size() == 1 && + tokens[i][0] >= 'a' && tokens[i][0] <= 'z') { + key = static_cast(ui::VKEY_A + (tokens[i][0] - 'a')); + } else if (tokens[i].size() == 1 && + tokens[i][0] >= '0' && tokens[i][0] <= '9') { + key = static_cast(ui::VKEY_0 + (tokens[i][0] - '0')); + } else { + key = ui::VKEY_UNKNOWN; + break; + } + } + } + + return ui::Accelerator(key, modifiers); +} + +Shortcut::Shortcut(int id, + const base::WeakPtr& dispatcher_host, + const base::DictionaryValue& option) + : Base(id, dispatcher_host, option) { + std::string shortcut; + option.GetString("key", &shortcut); + accelerator_ = Parse(shortcut); + if (accelerator_.key_code() == ui::VKEY_UNKNOWN) + OnFailed("Can not parse shortcut: " + shortcut + "."); +} + +Shortcut::~Shortcut() { +} + +void Shortcut::OnActive() { + base::ListValue args; + dispatcher_host()->SendEvent(this, "active", args); +} + +void Shortcut::OnFailed(const std::string failed_msg) { + base::ListValue args; + args.AppendString(failed_msg); + dispatcher_host()->SendEvent(this, "failed", args); +} + +void Shortcut::OnKeyPressed(const ui::Accelerator& accelerator) { + if (accelerator != accelerator_) { + // This should never occur, because if it does, GlobalShortcutListener + // notifes us with wrong accelerator. + NOTREACHED(); + return; + } + + OnActive(); +} + +} // namespace nwapi diff --git a/src/api/shortcut/shortcut.h b/src/api/shortcut/shortcut.h new file mode 100644 index 0000000000..60dc428aa6 --- /dev/null +++ b/src/api/shortcut/shortcut.h @@ -0,0 +1,55 @@ +// Copyright (c) 2014 Intel Corp +// Copyright (c) 2014 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef CONTENT_NW_SRC_API_SHORTCUT_SHORTCUT_H_ +#define CONTENT_NW_SRC_API_SHORTCUT_SHORTCUT_H_ + +#include + +#include "content/nw/src/api/base/base.h" +#include "content/nw/src/api/shortcut/global_shortcut_listener.h" +#include "ui/base/accelerators/accelerator.h" + +namespace nwapi { + +class Shortcut : public Base, public GlobalShortcutListener::Observer { + public: + Shortcut(int id, + const base::WeakPtr& dispatcher_host, + const base::DictionaryValue& option); + ~Shortcut() override; + + const ui::Accelerator& GetAccelerator() const { + return accelerator_; + } + + void OnActive(); + void OnFailed(const std::string failed_msg); + + // GlobalShortcutListener::Observer implementation. + void OnKeyPressed(const ui::Accelerator& accelerator) override; + + private: + ui::Accelerator accelerator_; +}; + +} // namespace nwapi + +#endif // CONTENT_NW_SRC_API_SHORTCUT_SHORTCUT_H_ diff --git a/src/api/shortcut/shortcut_constants.cc b/src/api/shortcut/shortcut_constants.cc new file mode 100644 index 0000000000..0759889822 --- /dev/null +++ b/src/api/shortcut/shortcut_constants.cc @@ -0,0 +1,72 @@ +// Copyright (c) 2014 Intel Corp +// Copyright (c) 2014 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "content/nw/src/api/shortcut/shortcut_constants.h" + +namespace nwapi { + +const char kKeyAlt[] = "alt"; +const char kKeyComma[] = "comma"; +const char kKeyCommand[] = "command"; +const char kKeyCtrl[] = "ctrl"; +const char kKeyDel[] = "delete"; +const char kKeyDown[] = "down"; +const char kKeyEnd[] = "end"; +const char kKeyHome[] = "home"; +const char kKeyIns[] = "insert"; +const char kKeyLeft[] = "left"; +const char kKeyMediaNextTrack[] = "medianexttrack"; +const char kKeyMediaPlayPause[] = "mediaplaypause"; +const char kKeyMediaPrevTrack[] = "mediaprevtrack"; +const char kKeyMediaStop[] = "mediastop"; +const char kKeyPgDwn[] = "pagedown"; +const char kKeyPgUp[] = "pageup"; +const char kKeyPeriod[] = "period"; +const char kKeyRight[] = "right"; +const char kKeySeparator[] = "+"; +const char kKeyShift[] = "shift"; +const char kKeyTab[] = "tab"; +const char kKeyUp[] = "up"; +const char kKeyF1[] = "f1"; +const char kKeyF2[] = "f2"; +const char kKeyF3[] = "f3"; +const char kKeyF4[] = "f4"; +const char kKeyF5[] = "f5"; +const char kKeyF6[] = "f6"; +const char kKeyF7[] = "f7"; +const char kKeyF8[] = "f8"; +const char kKeyF9[] = "f9"; +const char kKeyF10[] = "f10"; +const char kKeyF11[] = "f11"; +const char kKeyF12[] = "f12"; +const char kKeyF13[] = "f13"; +const char kKeyF14[] = "f14"; +const char kKeyF15[] = "f15"; +const char kKeyF16[] = "f16"; +const char kKeyF17[] = "f17"; +const char kKeyF18[] = "f18"; +const char kKeyF19[] = "f19"; +const char kKeyF20[] = "f20"; +const char kKeyF21[] = "f21"; +const char kKeyF22[] = "f22"; +const char kKeyF23[] = "f23"; +const char kKeyF24[] = "f24"; + +} // namespace nwapi diff --git a/src/api/shortcut/shortcut_constants.h b/src/api/shortcut/shortcut_constants.h new file mode 100644 index 0000000000..db6f0f6ebb --- /dev/null +++ b/src/api/shortcut/shortcut_constants.h @@ -0,0 +1,76 @@ +// Copyright (c) 2014 Intel Corp +// Copyright (c) 2014 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef CONTENT_NW_SRC_API_SHORTCUT_SHORTCUT_CONSTANTS_H_ +#define CONTENT_NW_SRC_API_SHORTCUT_SHORTCUT_CONSTANTS_H_ + +namespace nwapi { + +extern const char kKeyAlt[]; +extern const char kKeyComma[]; +extern const char kKeyCommand[]; +extern const char kKeyCtrl[]; +extern const char kKeyDel[]; +extern const char kKeyDown[]; +extern const char kKeyEnd[]; +extern const char kKeyHome[]; +extern const char kKeyIns[]; +extern const char kKeyLeft[]; +extern const char kKeyMediaNextTrack[]; +extern const char kKeyMediaPlayPause[]; +extern const char kKeyMediaPrevTrack[]; +extern const char kKeyMediaStop[]; +extern const char kKeyPgDwn[]; +extern const char kKeyPgUp[]; +extern const char kKeyPeriod[]; +extern const char kKeyRight[]; +extern const char kKeySeparator[]; +extern const char kKeyShift[]; +extern const char kKeyTab[]; +extern const char kKeyUp[]; +extern const char kKeyF1[]; +extern const char kKeyF2[]; +extern const char kKeyF3[]; +extern const char kKeyF4[]; +extern const char kKeyF5[]; +extern const char kKeyF6[]; +extern const char kKeyF7[]; +extern const char kKeyF8[]; +extern const char kKeyF9[]; +extern const char kKeyF10[]; +extern const char kKeyF11[]; +extern const char kKeyF12[]; +extern const char kKeyF13[]; +extern const char kKeyF14[]; +extern const char kKeyF15[]; +extern const char kKeyF16[]; +extern const char kKeyF17[]; +extern const char kKeyF18[]; +extern const char kKeyF19[]; +extern const char kKeyF20[]; +extern const char kKeyF21[]; +extern const char kKeyF22[]; +extern const char kKeyF23[]; +extern const char kKeyF24[]; + + +} // namespace nwapi + +#endif // CONTENT_NW_SRC_API_SHORTCUT_SHORTCUT_CONSTANTS_H_ diff --git a/src/api/tray/tray.cc b/src/api/tray/tray.cc index 2b7006e95a..0259984c6b 100644 --- a/src/api/tray/tray.cc +++ b/src/api/tray/tray.cc @@ -25,10 +25,10 @@ #include "content/nw/src/api/dispatcher_host.h" #include "content/nw/src/api/menu/menu.h" -namespace api { +namespace nwapi { Tray::Tray(int id, - DispatcherHost* dispatcher_host, + const base::WeakPtr& dispatcher_host, const base::DictionaryValue& option) : Base(id, dispatcher_host, option) { Create(option); @@ -37,6 +37,10 @@ Tray::Tray(int id, if (option.GetString("title", &title)) SetTitle(title); + bool areTemplates; + if (option.GetBoolean("iconsAreTemplates", &areTemplates)) + SetIconsAreTemplates(areTemplates); + std::string icon; if (option.GetString("icon", &icon) && !icon.empty()) SetIcon(icon); @@ -47,7 +51,7 @@ Tray::Tray(int id, std::string tooltip; if (option.GetString("tooltip", &tooltip)) - SetTitle(tooltip); + SetTooltip(tooltip); int menu_id; if (option.GetInteger("menu", &menu_id)) @@ -74,6 +78,10 @@ void Tray::Call(const std::string& method, std::string alticon; arguments.GetString(0, &alticon); SetAltIcon(alticon); + } else if (method == "SetIconsAreTemplates") { + bool areTemplates; + arguments.GetBoolean(0, &areTemplates); + SetIconsAreTemplates(areTemplates); } else if (method == "SetTooltip") { std::string tooltip; arguments.GetString(0, &tooltip); @@ -90,4 +98,4 @@ void Tray::Call(const std::string& method, } } -} // namespace api +} // namespace nwapi diff --git a/src/api/tray/tray.h b/src/api/tray/tray.h index f1e5594628..095d9e7d34 100644 --- a/src/api/tray/tray.h +++ b/src/api/tray/tray.h @@ -29,18 +29,20 @@ #if defined(OS_MACOSX) #if __OBJC__ @class NSStatusItem; +@class MacTrayObserver; #else class NSStatusItem; +class MacTrayObserver; #endif // __OBJC__ -#elif defined(TOOLKIT_GTK) +#elif 0 #include -#include "ui/base/gtk/gtk_signal.h" -#elif defined(OS_WIN) +#include "chrome/browser/ui/libgtk2ui/gtk2_signal.h" +#elif defined(OS_WIN) || defined(OS_LINUX) class StatusIcon; class StatusTray; #endif // defined(OS_MACOSX) -namespace api { +namespace nwapi { class Menu; class TrayObserver; @@ -48,12 +50,12 @@ class TrayObserver; class Tray : public Base { public: Tray(int id, - DispatcherHost* dispatcher_host, + const base::WeakPtr& dispatcher_host, const base::DictionaryValue& option); - virtual ~Tray(); + ~Tray() override; - virtual void Call(const std::string& method, - const base::ListValue& arguments) OVERRIDE; + void Call(const std::string& method, + const base::ListValue& arguments) override; private: // Platform-independent implementations @@ -62,15 +64,19 @@ class Tray : public Base { void Destroy(); void SetTitle(const std::string& title); void SetIcon(const std::string& icon_path); - void SetTooltip(const std::string& title); + void SetTooltip(const std::string& tooltip); void SetMenu(Menu* menu); void Remove(); // Alternate icons only work with Macs void SetAltIcon(const std::string& alticon_path); + // Template icons only work with Macs + void SetIconsAreTemplates(bool areTemplates); #if defined(OS_MACOSX) __block NSStatusItem* status_item_; -#elif defined(TOOLKIT_GTK) + MacTrayObserver* status_observer_; + bool iconsAreTemplates; +#elif 0 GtkStatusIcon* status_item_; // Reference to the associated menu. @@ -80,7 +86,7 @@ class Tray : public Base { CHROMEGTK_CALLBACK_0(Tray, void, OnClick); // Callback invoked when user right-clicks on the status icon. CHROMEGTK_CALLBACK_2(Tray, void, OnPopupMenu, guint, guint); -#elif defined(OS_WIN) +#elif defined(OS_WIN) || defined(OS_LINUX) // The global presentation of system tray. static StatusTray* status_tray_; @@ -94,6 +100,6 @@ class Tray : public Base { DISALLOW_COPY_AND_ASSIGN(Tray); }; -} // namespace api +} // namespace nwapi #endif // CONTENT_NW_SRC_API_TRAY_TRAY_H_ diff --git a/src/api/tray/tray.js b/src/api/tray/tray.js index de2389d304..2721ede410 100644 --- a/src/api/tray/tray.js +++ b/src/api/tray/tray.js @@ -22,10 +22,10 @@ var v8_util = process.binding('v8_util'); function Tray(option) { if (typeof option != 'object') - throw new String('Invalid option'); + throw new TypeError('Invalid option'); if (!option.hasOwnProperty('title') && !option.hasOwnProperty('icon')) - throw new String("Must set 'title' or 'icon' field in option"); + throw new TypeError("Must set 'title' or 'icon' field in option"); if (!option.hasOwnProperty('title')) option.title = ''; @@ -36,18 +36,31 @@ function Tray(option) { option.shadowIcon = String(option.icon); option.icon = nw.getAbsolutePath(option.icon); } - + if (option.hasOwnProperty('alticon')) { option.shadowAlticon = String(option.alticon); option.alticon = nw.getAbsolutePath(option.alticon); } + if (option.hasOwnProperty('iconsAreTemplates')) + option.iconsAreTemplates = Boolean(option.iconsAreTemplates); + else + option.iconsAreTemplates = true; + if (option.hasOwnProperty('tooltip')) option.tooltip = String(option.tooltip); + if (option.hasOwnProperty('click')) { + if (typeof option.click != 'function') { + throw new TypeError("'click' must be a valid Function"); + } else { + this.click = option.click; + } + } + if (option.hasOwnProperty('menu')) { if (v8_util.getConstructorName(option.menu) != 'Menu') - throw new String("'menu' must be a valid Menu"); + throw new TypeError("'menu' must be a valid Menu"); // Transfer only object id v8_util.setHiddenValue(this, 'menu', option.menu); @@ -92,7 +105,15 @@ Tray.prototype.__defineSetter__('icon', function(val) { Tray.prototype.__defineSetter__('alticon', function(val) { v8_util.getHiddenValue(this, 'option').shadowAlticon = String(val); var real_path = val == '' ? '' : nw.getAbsolutePath(val); - this.handleSetter('alticon', 'SetAlticon', String, real_path); + this.handleSetter('alticon', 'SetAltIcon', String, real_path); +}); + +Tray.prototype.__defineGetter__('iconsAreTemplates', function() { + return this.handleGetter('iconsAreTemplates'); +}); + +Tray.prototype.__defineSetter__('iconsAreTemplates', function(val) { + this.handleSetter('iconsAreTemplates', 'SetIconsAreTemplates', Boolean, val); }); Tray.prototype.__defineGetter__('tooltip', function() { @@ -109,7 +130,7 @@ Tray.prototype.__defineGetter__('menu', function() { Tray.prototype.__defineSetter__('menu', function(val) { if (v8_util.getConstructorName(val) != 'Menu') - throw new String("'menu' property requries a valid Menu"); + throw new TypeError("'menu' property requries a valid Menu"); v8_util.setHiddenValue(this, 'menu', val); nw.callObjectMethod(this, 'SetMenu', [ val.id ]); @@ -119,4 +140,14 @@ Tray.prototype.remove = function() { nw.callObjectMethod(this, 'Remove', []); } +Tray.prototype.handleEvent = function(ev) { + if (ev == 'click') { + // Emit click handler + if (typeof this.click == 'function'){ + this.click(); + } + } + // Emit generate event handler + exports.Base.prototype.handleEvent.apply(this, arguments); +} exports.Tray = Tray; diff --git a/src/api/tray/tray_win.cc b/src/api/tray/tray_aura.cc similarity index 79% rename from src/api/tray/tray_win.cc rename to src/api/tray/tray_aura.cc index a29851db98..7bd9623fe2 100644 --- a/src/api/tray/tray_win.cc +++ b/src/api/tray/tray_aura.cc @@ -21,7 +21,7 @@ #include "content/nw/src/api/tray/tray.h" #include "base/files/file_path.h" -#include "base/utf_string_conversions.h" +#include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/status_icons/status_icon.h" #include "chrome/browser/status_icons/status_icon_observer.h" @@ -30,9 +30,10 @@ #include "content/nw/src/api/menu/menu.h" #include "content/nw/src/nw_package.h" #include "content/nw/src/nw_shell.h" +#include "ui/gfx/screen.h" #include "ui/gfx/image/image.h" -namespace api { +namespace nwapi { StatusTray* Tray::status_tray_ = NULL; @@ -42,11 +43,17 @@ class TrayObserver : public StatusIconObserver { : tray_(tray) { } - virtual ~TrayObserver() { + ~TrayObserver() final { } - virtual void OnStatusIconClicked() OVERRIDE { + void OnStatusIconClicked() override { base::ListValue args; + base::DictionaryValue* data = new base::DictionaryValue; + gfx::Point cursor_pos( + gfx::Screen::GetNativeScreen()->GetCursorScreenPoint()); + data->SetInteger("x", cursor_pos.x()); + data->SetInteger("y", cursor_pos.y()); + args.Append(data); tray_->dispatcher_host()->SendEvent(tray_, "click", args); } @@ -56,9 +63,10 @@ class TrayObserver : public StatusIconObserver { void Tray::Create(const base::DictionaryValue& option) { if (!status_tray_) - status_tray_ = StatusTray::Create(); + status_tray_ = StatusTray::GetSingleton(); - status_icon_ = status_tray_->CreateStatusIcon(); + status_icon_ = status_tray_->CreateStatusIcon(StatusTray::NOTIFICATION_TRAY_ICON, + gfx::ImageSkia(), base::string16()); status_observer_ = new TrayObserver(this); status_icon_->AddObserver(status_observer_); } @@ -86,7 +94,7 @@ void Tray::SetIcon(const std::string& path) { } void Tray::SetTooltip(const std::string& tooltip) { - status_icon_->SetToolTip(UTF8ToUTF16(tooltip)); + status_icon_->SetToolTip(base::UTF8ToUTF16(tooltip)); } void Tray::SetMenu(Menu* menu) { @@ -106,4 +114,7 @@ void Tray::Remove() { void Tray::SetAltIcon(const std::string& alticon_path) { } -} // namespace api +void Tray::SetIconsAreTemplates(bool areTemplates) { +} + +} // namespace nwapi diff --git a/src/api/tray/tray_gtk.cc b/src/api/tray/tray_gtk.cc index 9014a9b7b8..12b5990ef2 100644 --- a/src/api/tray/tray_gtk.cc +++ b/src/api/tray/tray_gtk.cc @@ -24,7 +24,7 @@ #include "content/nw/src/api/dispatcher_host.h" #include "content/nw/src/api/menu/menu.h" -namespace api { +namespace nwapi { void Tray::Create(const base::DictionaryValue& option) { menu_ = NULL; @@ -71,14 +71,19 @@ void Tray::OnClick(GtkWidget* widget) { } void Tray::OnPopupMenu(GtkWidget* widget, guint button, guint time) { +#if 0//FIXME if (menu_) { gtk_menu_popup(GTK_MENU(menu_->menu_), NULL, NULL, gtk_status_icon_position_menu, status_item_, button, time); } +#endif } void Tray::SetAltIcon(const std::string& alticon_path) { } -} // namespace api +void Tray::SetIconsAreTemplates(bool areTemplates) { +} + +} // namespace nwapi diff --git a/src/api/tray/tray_mac.mm b/src/api/tray/tray_mac.mm index 9e5a555d32..8c8575306d 100644 --- a/src/api/tray/tray_mac.mm +++ b/src/api/tray/tray_mac.mm @@ -22,15 +22,49 @@ #include "base/values.h" #import +#include "ui/gfx/screen.h" +#include "content/nw/src/api/dispatcher_host.h" #include "content/nw/src/api/menu/menu.h" -namespace api { +@interface MacTrayObserver : NSObject { +@private + nwapi::Tray* tray_; +} +- (void)setBacking:(nwapi::Tray*)tray_; +- (void)onClick:(id)sender; +@end + +@implementation MacTrayObserver +- (void)setBacking:(nwapi::Tray*)newTray { + tray_ = newTray; +} +- (void)onClick:(id)sender { + base::ListValue args; + base::DictionaryValue* data = new base::DictionaryValue; + // Get the position of the frame of the NSStatusItem + NSPoint pos = ([[[NSApp currentEvent] window] frame]).origin; + // Flip coordinates to gfx (0,0 in top-left corner) using primary screen. + NSScreen* screen = [[NSScreen screens] objectAtIndex:0]; + pos.y = NSMaxY([screen frame]) - pos.y; + data->SetInteger("x", pos.x); + data->SetInteger("y", pos.y); + args.Append(data); + tray_->dispatcher_host()->SendEvent(tray_,"click",args); +} +@end + +namespace nwapi { + void Tray::Create(const base::DictionaryValue& option) { NSStatusBar *status_bar = [NSStatusBar systemStatusBar]; + MacTrayObserver* observer = [[MacTrayObserver alloc] init]; + [observer setBacking:this]; status_item_ = [status_bar statusItemWithLength:NSVariableStatusItemLength]; [status_item_ setHighlightMode:YES]; [status_item_ retain]; + [status_item_ setTarget:observer]; + [status_item_ setAction:@selector(onClick:)]; } void Tray::ShowAfterCreate() { @@ -41,6 +75,11 @@ } void Tray::SetTitle(const std::string& title) { + // note: this is kind of mad but the first time we set the title property + // we have to call setTitle twice or it won't get the right dimensions + if ([status_item_ title] != nil) { + [status_item_ setTitle:[NSString stringWithUTF8String:title.c_str()]]; + } [status_item_ setTitle:[NSString stringWithUTF8String:title.c_str()]]; } @@ -48,6 +87,7 @@ if (!icon.empty()) { NSImage* image = [[NSImage alloc] initWithContentsOfFile:[NSString stringWithUTF8String:icon.c_str()]]; + [image setTemplate:iconsAreTemplates]; [status_item_ setImage:image]; [image release]; } else { @@ -59,6 +99,7 @@ if (!alticon.empty()) { NSImage* image = [[NSImage alloc] initWithContentsOfFile:[NSString stringWithUTF8String:alticon.c_str()]]; + [image setTemplate:iconsAreTemplates]; [status_item_ setAlternateImage:image]; [image release]; } else { @@ -66,11 +107,23 @@ } } +void Tray::SetIconsAreTemplates(bool areTemplates) { + iconsAreTemplates = areTemplates; + if ([status_item_ image] != nil) { + [[status_item_ image] setTemplate:areTemplates]; + } + if ([status_item_ alternateImage] != nil) { + [[status_item_ alternateImage] setTemplate:areTemplates]; + } +} + void Tray::SetTooltip(const std::string& tooltip) { [status_item_ setToolTip:[NSString stringWithUTF8String:tooltip.c_str()]]; } void Tray::SetMenu(Menu* menu) { + [status_item_ setTarget:nil]; + [status_item_ setAction:nil]; [status_item_ setMenu:menu->menu_]; } @@ -78,4 +131,4 @@ [[NSStatusBar systemStatusBar] removeStatusItem:status_item_]; } -} // namespace api +} // namespace nwapi diff --git a/src/api/v8_internal_helper.cc b/src/api/v8_internal_helper.cc new file mode 100644 index 0000000000..5d2961e47a --- /dev/null +++ b/src/api/v8_internal_helper.cc @@ -0,0 +1,13 @@ +#include "v8/src/v8.h" + +using namespace v8; + + +void FixSourceNWBin(Isolate* v8_isolate, Handle script) { + i::Isolate* isolate = reinterpret_cast(v8_isolate); + i::Handle obj = + i::Handle::cast(v8::Utils::OpenHandle(*script)); + i::Handle + function_info(i::SharedFunctionInfo::cast(*obj), obj->GetIsolate()); + reinterpret_cast(function_info->script())->set_source(isolate->heap()->undefined_value()); +} diff --git a/src/api/window/window.cc b/src/api/window/window.cc index 9584795e28..cb6f3d9fd1 100644 --- a/src/api/window/window.cc +++ b/src/api/window/window.cc @@ -21,15 +21,150 @@ #include "content/nw/src/api/window/window.h" #include "base/values.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/chrome_notification_types.h" #include "content/nw/src/api/dispatcher_host.h" #include "content/nw/src/api/menu/menu.h" #include "content/nw/src/browser/native_window.h" #include "content/nw/src/nw_shell.h" +#include "content/nw/src/shell_browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/common/url_constants.h" +#include "net/cookies/canonical_cookie.h" +#include "net/cookies/cookie_constants.h" +#include "net/cookies/cookie_monster.h" +#include "net/cookies/cookie_util.h" +#include "net/url_request/url_request_context.h" +#include "url/gurl.h" -namespace api { +using content::BrowserThread; +using content::ShellBrowserContext; + +namespace { + +const char kCauseKey[] = "cause"; +const char kCookieKey[] = "cookie"; +//const char kDomainKey[] = "domain"; +//const char kIdKey[] = "id"; +const char kRemovedKey[] = "removed"; +//const char kTabIdsKey[] = "tabIds"; + +// Cause Constants +const char kEvictedChangeCause[] = "evicted"; +const char kExpiredChangeCause[] = "expired"; +const char kExpiredOverwriteChangeCause[] = "expired_overwrite"; +const char kExplicitChangeCause[] = "explicit"; +const char kOverwriteChangeCause[] = "overwrite"; + +GURL GetURLFromCanonicalCookie(const net::CanonicalCookie& cookie) { + const std::string& domain_key = cookie.Domain(); + const std::string scheme = + cookie.IsSecure() ? "https" : "http"; + const std::string host = + domain_key.find('.') != 0 ? domain_key : domain_key.substr(1); + return GURL(scheme + url::kStandardSchemeSeparator + host + "/"); +} + +void GetCookieListFromStore( + net::CookieStore* cookie_store, const GURL& url, + const net::CookieMonster::GetCookieListCallback& callback) { + DCHECK(cookie_store); + net::CookieMonster* monster = cookie_store->GetCookieMonster(); + if (!url.is_empty()) { + DCHECK(url.is_valid()); + monster->GetAllCookiesForURLAsync(url, callback); + } else { + monster->GetAllCookiesAsync(callback); + } +} + +bool MatchesDomain(base::DictionaryValue* details, const std::string& domain) { + std::string val; + if (!details->GetString("domain", &val)) + return true; + + // Add a leading '.' character to the filter domain if it doesn't exist. + if (net::cookie_util::DomainIsHostOnly(val)) + val.insert(0, "."); + + std::string sub_domain(domain); + // Strip any leading '.' character from the input cookie domain. + if (!net::cookie_util::DomainIsHostOnly(sub_domain)) + sub_domain = sub_domain.substr(1); + + // Now check whether the domain argument is a subdomain of the filter domain. + for (sub_domain.insert(0, "."); + sub_domain.length() >= val.length();) { + if (sub_domain == val) + return true; + const size_t next_dot = sub_domain.find('.', 1); // Skip over leading dot. + sub_domain.erase(0, next_dot); + } + return false; +} + +bool MatchesCookie(base::DictionaryValue* details, + const net::CanonicalCookie& cookie) { + std::string val; + + bool flag; + if (details->GetString("name", &val)) + if (val != cookie.Name()) + return false; + + if (!MatchesDomain(details, cookie.Domain())) + return false; + + if (details->GetString("path", &val)) + if (val != cookie.Path()) + return false; + + if (details->GetBoolean("secure", &flag)) + if (flag != cookie.IsSecure()) + return false; + + if (details->GetBoolean("session", &flag)) + if (flag != cookie.IsPersistent()) + return false; + + return true; +} + +base::DictionaryValue* +PopulateCookieObject(const net::CanonicalCookie& canonical_cookie) { + + base::DictionaryValue* result = new base::DictionaryValue(); + // A cookie is a raw byte sequence. By explicitly parsing it as UTF-8, we + // apply error correction, so the string can be safely passed to the renderer. + result->SetString("name", base::UTF16ToUTF8(base::UTF8ToUTF16(canonical_cookie.Name()))); + result->SetString("value", base::UTF16ToUTF8(base::UTF8ToUTF16(canonical_cookie.Value()))); + result->SetString("domain", canonical_cookie.Domain()); + result->SetBoolean("host_only", net::cookie_util::DomainIsHostOnly( + canonical_cookie.Domain())); + // A non-UTF8 path is invalid, so we just replace it with an empty string. + result->SetString("path", base::IsStringUTF8(canonical_cookie.Path()) ? canonical_cookie.Path() + : std::string()); + result->SetBoolean("secure", canonical_cookie.IsSecure()); + result->SetBoolean("http_only", canonical_cookie.IsHttpOnly()); + result->SetBoolean("session", !canonical_cookie.IsPersistent()); + if (canonical_cookie.IsPersistent()) { + result->SetDouble("expiration_date", + canonical_cookie.ExpiryDate().ToDoubleT()); + } + return result; +} + +} // namespace + +namespace nwapi { + +CookieAPIContext::~CookieAPIContext() {} Window::Window(int id, - DispatcherHost* dispatcher_host, + const base::WeakPtr& dispatcher_host, const base::DictionaryValue& option) : Base(id, dispatcher_host, option), shell_(content::Shell::FromRenderViewHost(dispatcher_host-> @@ -37,6 +172,10 @@ Window::Window(int id, DVLOG(1) << "Window::Window(" << id << ")"; // Set ID for Shell shell_->set_id(id); + CHECK(registrar_.IsEmpty()); + registrar_.Add(this, + chrome::NOTIFICATION_COOKIE_CHANGED, + content::NotificationService::AllBrowserContextsAndSources()); } Window::~Window() { @@ -66,6 +205,10 @@ void Window::Call(const std::string& method, shell_->window()->Minimize(); } else if (method == "Restore") { shell_->window()->Restore(); + } else if (method == "Focus") { + shell_->window()->Focus(true); + } else if (method == "Blur") { + shell_->window()->Focus(false); } else if (method == "EnterFullscreen") { shell_->window()->SetFullscreen(true); } else if (method == "LeaveFullscreen") { @@ -78,8 +221,13 @@ void Window::Call(const std::string& method, shell_->window()->SetKiosk(false); } else if (method == "ToggleKioskMode") { shell_->window()->SetKiosk(!shell_->window()->IsKiosk()); - } else if (method == "ShowDevTools") { - shell_->ShowDevTools(); + } else if (method == "CloseDevTools") { + shell_->CloseDevTools(); + } else if (method == "SetPosition") { + std::string position; + if (arguments.GetString(0, &position)){ + shell_->window()->SetPosition(position); + } } else if (method == "ResizeTo") { int width, height; if (arguments.GetInteger(0, &width) && @@ -103,19 +251,41 @@ void Window::Call(const std::string& method, bool top; if (arguments.GetBoolean(0, &top)) shell_->window()->SetAlwaysOnTop(top); + } else if (method == "SetShowInTaskbar" ) { + bool show; + if (arguments.GetBoolean(0, &show)) + shell_->window()->SetShowInTaskbar(show); + } else if (method == "SetVisibleOnAllWorkspaces") { + bool all_workspaces; + if (arguments.GetBoolean(0, &all_workspaces)) + shell_->window()->SetVisibleOnAllWorkspaces(all_workspaces); } else if (method == "MoveTo") { int x, y; if (arguments.GetInteger(0, &x) && arguments.GetInteger(1, &y)) shell_->window()->SetPosition(gfx::Point(x, y)); } else if (method == "RequestAttention") { - bool flash; - if (arguments.GetBoolean(0, &flash)) - shell_->window()->FlashFrame(flash); + int count; + if (arguments.GetInteger(0, &count)) + shell_->window()->FlashFrame(count); + } else if (method == "SetBadgeLabel") { + std::string label; + if (arguments.GetString(0, &label)) + shell_->window()->SetBadgeLabel(label); + } else if (method == "SetTransparent") { + bool transparent; + if (arguments.GetBoolean(0, &transparent)) + shell_->window()->SetTransparent(transparent); + } else if (method == "SetProgressBar") { + double progress; + if (arguments.GetDouble(0, &progress)) + shell_->window()->SetProgressBar(progress); } else if (method == "SetMenu") { int id; if (arguments.GetInteger(0, &id)) shell_->window()->SetMenu(dispatcher_host()->GetApiObject(id)); + } else if (method == "ClearMenu") { + shell_->window()->ClearMenu(); } else if (method == "Reload") { int type; if (arguments.GetInteger(0, &type)) @@ -124,6 +294,14 @@ void Window::Call(const std::string& method, std::string image_format_str; if (arguments.GetString(0, &image_format_str)) shell_->window()->CapturePage(image_format_str); + } else if (method == "CookieGet") { + CookieGet(arguments); + } else if (method == "CookieGetAll") { + CookieGet(arguments, true); + } else if (method == "CookieRemove") { + CookieRemove(arguments); + } else if (method == "CookieSet") { + CookieSet(arguments); } else { NOTREACHED() << "Invalid call to Window method:" << method << " arguments:" << arguments; @@ -145,10 +323,339 @@ void Window::CallSync(const std::string& method, gfx::Point position = shell_->window()->GetPosition(); result->AppendInteger(position.x()); result->AppendInteger(position.y()); + } else if (method == "IsTransparent") { + bool transparent = shell_->window()->IsTransparent(); + result->AppendBoolean(transparent); + } else if (method == "IsDevToolsOpen") { + result->AppendBoolean(shell_->devToolsOpen()); + } else if (method == "ShowDevTools") { + std::string jail_id; + bool headless = false; + arguments.GetString(0, &jail_id); + arguments.GetBoolean(1, &headless); + shell_->ShowDevTools(jail_id.c_str(), headless); + int object_id = 0; + if (!headless) + object_id = shell_->WrapDevToolsWindow(); + result->AppendInteger(object_id); } else { NOTREACHED() << "Invalid call to Window method:" << method << " arguments:" << arguments; } } -} // namespace api +void Window::CookieRemove(const base::ListValue& arguments) { + if (!dispatcher_host()) + return; + CookieAPIContext* api_context = new CookieAPIContext(dispatcher_host(), arguments); + bool rv = BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&Window::RemoveCookieOnIOThread, + base::Unretained(this), + make_scoped_refptr(api_context))); + DCHECK(rv); +} + +void Window::CookieSet(const base::ListValue& arguments) { + if (!dispatcher_host()) + return; + CookieAPIContext* api_context = new CookieAPIContext(dispatcher_host(), arguments); + bool rv = BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&Window::SetCookieOnIOThread, + base::Unretained(this), + make_scoped_refptr(api_context))); + DCHECK(rv); +} + +void Window::RemoveCookieOnIOThread(CookieAPIContext* api_context) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + // Remove the cookie + net::CookieStore* cookie_store = + api_context->store_context_->GetURLRequestContext()->cookie_store(); + std::string name; + api_context->details_->GetString("name", &name); + cookie_store->DeleteCookieAsync( + api_context->url_, name, + base::Bind(&Window::RemoveCookieCallback, base::Unretained(this), + make_scoped_refptr(api_context))); +} + +void Window::RemoveCookieCallback(CookieAPIContext* api_context) { + std::string name; + api_context->details_->GetString("name", &name); + + base::DictionaryValue* result = new base::DictionaryValue(); + result->SetString("name", name); + result->SetString("url", api_context->url_.spec()); + api_context->result_->Append(result); + + // Return to UI thread + bool rv = BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&Window::RespondOnUIThread, base::Unretained(this), + make_scoped_refptr(api_context))); + DCHECK(rv); +} + +CookieAPIContext::CookieAPIContext(DispatcherHost* dispatcher_host, + const base::ListValue& arguments) { + content::RenderProcessHost* render_process_host = + dispatcher_host->render_view_host()->GetProcess(); + net::URLRequestContextGetter* context_getter = + render_process_host->GetBrowserContext()-> + GetRequestContextForRenderProcess(render_process_host->GetID()); + + const base::DictionaryValue* details = NULL; + std::string url; + + store_context_ = context_getter; + arguments.GetInteger(0, &req_id_); + arguments.GetDictionary(1, &details); + if (details) { + details_.reset(details->DeepCopyWithoutEmptyChildren()); + details->GetString("url", &url); + } + + url_ = GURL(url); + result_.reset(new base::ListValue); +} + +void Window::CookieGet(const base::ListValue& arguments, bool get_all) { + + if (!dispatcher_host()) + return; + CookieAPIContext* api_context = new CookieAPIContext(dispatcher_host(), arguments); + + if (get_all) { + bool rv = BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&Window::GetAllCookieOnIOThread, + base::Unretained(this), + make_scoped_refptr(api_context))); + DCHECK(rv); + }else{ + bool rv = BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&Window::GetCookieOnIOThread, + base::Unretained(this), + make_scoped_refptr(api_context))); + DCHECK(rv); + } +} + +void Window::GetAllCookieOnIOThread(CookieAPIContext* api_context) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + net::CookieStore* cookie_store = + api_context->store_context_->GetURLRequestContext()->cookie_store(); + GetCookieListFromStore( + cookie_store, api_context->url_, + base::Bind(&Window::GetAllCookieCallback, base::Unretained(this), + make_scoped_refptr(api_context))); +} + +void Window::GetCookieOnIOThread(CookieAPIContext* api_context) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + net::CookieStore* cookie_store = + api_context->store_context_->GetURLRequestContext()->cookie_store(); + GetCookieListFromStore( + cookie_store, api_context->url_, + base::Bind(&Window::GetCookieCallback, base::Unretained(this), + make_scoped_refptr(api_context))); +} + +void Window::GetAllCookieCallback(CookieAPIContext* api_context, + const net::CookieList& cookie_list) { + net::CookieList::const_iterator it; + api_context->result_->Clear(); + for (it = cookie_list.begin(); it != cookie_list.end(); ++it) { + if (MatchesCookie(api_context->details_.get(), *it)) { + api_context->result_->Append(PopulateCookieObject(*it)); + } + } + + bool rv = BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&Window::RespondOnUIThread, base::Unretained(this), + make_scoped_refptr(api_context))); + DCHECK(rv); +} + +void Window::GetCookieCallback(CookieAPIContext* api_context, + const net::CookieList& cookie_list) { + net::CookieList::const_iterator it; + std::string name; + api_context->details_->GetString("name", &name); + + api_context->result_->Clear(); + + for (it = cookie_list.begin(); it != cookie_list.end(); ++it) { + // Return the first matching cookie. Relies on the fact that the + // CookieMonster returns them in canonical order (longest path, then + // earliest creation time). + + if (it->Name() == name) { + api_context->result_->Append(PopulateCookieObject(*it)); + break; + } + } + + bool rv = BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&Window::RespondOnUIThread, base::Unretained(this), + make_scoped_refptr(api_context))); + DCHECK(rv); +} + +void Window::RespondOnUIThread(CookieAPIContext* api_context) { + if (!dispatcher_host()) + return; + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + base::ListValue ret; + ret.Append(api_context->result_.release()); + dispatcher_host()->SendEvent(this, base::StringPrintf("__nw_gotcookie%d", api_context->req_id_), ret); +} + +void Window::SetCookieOnIOThread(CookieAPIContext* api_context) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + net::CookieMonster* cookie_monster = + api_context->store_context_->GetURLRequestContext()->cookie_store()-> + GetCookieMonster(); + + base::Time expiration_time; + double expiration_date; + + if (api_context->details_->GetDouble("expirationDate", &expiration_date)) { + // Time::FromDoubleT converts double time 0 to empty Time object. So we need + // to do special handling here. + expiration_time = (expiration_date == 0) ? + base::Time::UnixEpoch() : + base::Time::FromDoubleT(expiration_date); + } + + const base::DictionaryValue* details = api_context->details_.get(); + std::string name, value, domain, path; + details->GetString("name", &name); + details->GetString("value", &value); + details->GetString("domain", &domain); + details->GetString("path", &path); + + bool secure = false, http_only = false; + details->GetBoolean("httpOnly", &http_only); + details->GetBoolean("secure", &secure); + + cookie_monster->SetCookieWithDetailsAsync( + api_context->url_, + name, value, domain, path, + expiration_time, + secure, http_only, + net::COOKIE_PRIORITY_DEFAULT, + base::Bind(&Window::PullCookie, base::Unretained(this), + make_scoped_refptr(api_context))); +} + +void Window::PullCookie(CookieAPIContext* api_context, bool set_cookie_result) { + // Pull the newly set cookie. + net::CookieMonster* cookie_monster = + api_context->store_context_->GetURLRequestContext()->cookie_store()-> + GetCookieMonster(); + api_context->success_ = set_cookie_result; + GetCookieListFromStore( + cookie_monster, api_context->url_, + base::Bind(&Window::PullCookieCallback, + base::Unretained(this), + make_scoped_refptr(api_context))); +} + +void Window::PullCookieCallback(CookieAPIContext* api_context, + const net::CookieList& cookie_list) { + net::CookieList::const_iterator it; + for (it = cookie_list.begin(); it != cookie_list.end(); ++it) { + // Return the first matching cookie. Relies on the fact that the + // CookieMonster returns them in canonical order (longest path, then + // earliest creation time). + std::string name; + api_context->details_->GetString("name", &name); + if (it->Name() == name) { + api_context->result_->Append(PopulateCookieObject(*it)); + break; + } + } + + bool rv = BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&Window::RespondOnUIThread, base::Unretained(this), + make_scoped_refptr(api_context))); + DCHECK(rv); +} + +void Window::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + + ShellBrowserContext* browser_context = + content::Source(source).ptr(); + + switch (type) { + case chrome::NOTIFICATION_COOKIE_CHANGED: + CookieChanged( + browser_context, + content::Details(details).ptr()); + break; + + default: + NOTREACHED(); + } +} + +void Window::CookieChanged( + ShellBrowserContext* browser_context, + ChromeCookieDetails* details) { + if (!dispatcher_host()) + return; + scoped_ptr args(new base::ListValue()); + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetBoolean(kRemovedKey, details->removed); + dict->Set(kCookieKey, PopulateCookieObject(*details->cookie)); + + // Map the internal cause to an external string. + std::string cause; + switch (details->cause) { + case net::CookieMonster::Delegate::CHANGE_COOKIE_EXPLICIT: + cause = kExplicitChangeCause; + break; + + case net::CookieMonster::Delegate::CHANGE_COOKIE_OVERWRITE: + cause = kOverwriteChangeCause; + break; + + case net::CookieMonster::Delegate::CHANGE_COOKIE_EXPIRED: + cause = kExpiredChangeCause; + break; + + case net::CookieMonster::Delegate::CHANGE_COOKIE_EVICTED: + cause = kEvictedChangeCause; + break; + + case net::CookieMonster::Delegate::CHANGE_COOKIE_EXPIRED_OVERWRITE: + cause = kExpiredOverwriteChangeCause; + break; + + default: + NOTREACHED(); + } + dict->SetString(kCauseKey, cause); + + args->Append(dict); + + GURL cookie_domain = + GetURLFromCanonicalCookie(*details->cookie); + + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + dispatcher_host()->SendEvent(this, "__nw_cookie_changed", *args); + +} + +} // namespace nwapi diff --git a/src/api/window/window.h b/src/api/window/window.h index 3f1f0140d9..3e5e9f8c25 100644 --- a/src/api/window/window.h +++ b/src/api/window/window.h @@ -22,32 +22,85 @@ #define CONTENT_NW_SRC_API_WINDOW_WINDOW_H_ #include "base/compiler_specific.h" +#include "base/values.h" +#include "chrome/browser/net/chrome_cookie_notification_details.h" #include "content/nw/src/api/base/base.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "content/public/browser/render_process_host.h" +#include "net/cookies/canonical_cookie.h" +#include "net/url_request/url_request_context_getter.h" +#include "url/gurl.h" + namespace content { class Shell; +class ShellBrowserContext; } -namespace api { +namespace nwapi { + +class CookieAPIContext : public base::RefCountedThreadSafe { +public: + CookieAPIContext(DispatcherHost* dispatcher_host, + const base::ListValue& arguments); + + net::URLRequestContextGetter* store_context_; + scoped_ptr details_; + scoped_ptr result_; + GURL url_; + int req_id_; + bool success_; +private: + friend class base::RefCountedThreadSafe; + ~CookieAPIContext(); +}; + -class Window : public Base { +class Window : public Base, public content::NotificationObserver { public: Window(int id, - DispatcherHost* dispatcher_host, + const base::WeakPtr& dispatcher_host, const base::DictionaryValue& option); - virtual ~Window(); + ~Window() override; - virtual void Call(const std::string& method, - const base::ListValue& arguments) OVERRIDE; - virtual void CallSync(const std::string& method, + void Call(const std::string& method, + const base::ListValue& arguments) override; + void CallSync(const std::string& method, const base::ListValue& arguments, - base::ListValue* result) OVERRIDE; + base::ListValue* result) override; + + void CookieGet(const base::ListValue& arguments, bool get_all = false); + void GetCookieOnIOThread(CookieAPIContext*); + void GetAllCookieOnIOThread(CookieAPIContext*); + void GetCookieCallback(CookieAPIContext*, const net::CookieList& cookie_list); + void GetAllCookieCallback(CookieAPIContext*, const net::CookieList& cookie_list); + void RespondOnUIThread(CookieAPIContext*); + void RemoveCookieCallback(CookieAPIContext* api_context); + void RemoveCookieOnIOThread(CookieAPIContext*); + void CookieRemove(const base::ListValue& arguments); + void CookieSet(const base::ListValue& arguments); + void PullCookieCallback(CookieAPIContext* api_context, + const net::CookieList& cookie_list); + void PullCookie(CookieAPIContext* api_context, bool set_cookie_result); + void SetCookieOnIOThread(CookieAPIContext* api_context); + private: + // content::NotificationObserver implementation. + void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) override; + + // Handler for the COOKIE_CHANGED event. The method takes the details of such + // an event and constructs a suitable JSON formatted extension event from it. + void CookieChanged(content::ShellBrowserContext*, ChromeCookieDetails* details); + content::Shell* shell_; + content::NotificationRegistrar registrar_; DISALLOW_COPY_AND_ASSIGN(Window); }; -} // namespace api +} // namespace nwapi #endif // CONTENT_NW_SRC_API_WINDOW_WINDOW_H_ diff --git a/src/api/window/window.js b/src/api/window/window.js index 4539de064e..00bacf3034 100644 --- a/src/api/window/window.js +++ b/src/api/window/window.js @@ -18,6 +18,9 @@ // ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// OS X and Linux only, Windows does not have a concept of workspaces +var canSetVisibleOnAllWorkspaces = /(darwin|linux)/.test(require('os').platform()); + exports.Window = { get: function(other) { // Return other window. @@ -32,21 +35,23 @@ exports.Window = { id = nw.getShellIdForCurrentContext(); // Return API's window object from id. + var ret; if (id > 0) { window.__nwWindowId = id; - return global.__nwWindowsStore[window.__nwWindowId]; + ret = global.__nwWindowsStore[window.__nwWindowId]; + if (!ret) { + ret = new global.Window(nw.getRoutingIDForCurrentContext(), true, id); + } + return ret; } - // Otherwise create it. - var win = new global.Window(nw.getRoutingIDForCurrentContext()); - window.__nwWindowId = win.id; - return win; + return new global.Window(nw.getRoutingIDForCurrentContext()); }, open: function(url, options) { // Conver relative url to full url. var protocol = url.match(/^[a-z]+:\/\//i); if (protocol == null || protocol.length == 0) { - var href = window.location.href; + var href = window.location.href.split(/\?|#/)[0]; url = href.substring(0, href.lastIndexOf('/') + 1) + url; } @@ -54,8 +59,18 @@ exports.Window = { if (typeof options != 'object') options = {}; + if (!('focus' in options)) { + options.focus = false; + } // Create new shell and get it's routing id. + var id = nw.allocateId(); + options.object_id = id; + options.nw_win_id = id; var routing_id = nw.createShell(url, options); - return new global.Window(routing_id); + + return new global.Window(routing_id, true, id); + }, + canSetVisibleOnAllWorkspaces: function() { + return canSetVisibleOnAllWorkspaces; } }; diff --git a/src/api/window_bindings.cc b/src/api/window_bindings.cc index 70bb1ae85a..00625340ef 100644 --- a/src/api/window_bindings.cc +++ b/src/api/window_bindings.cc @@ -18,16 +18,52 @@ // ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + #include "content/nw/src/api/window_bindings.h" #include "base/values.h" -#include "content/common/child_thread.h" +#include "content/child/child_thread.h" #include "content/nw/src/api/bindings_common.h" +#include "content/nw/src/api/dispatcher.h" #include "content/renderer/render_view_impl.h" #include "grit/nw_resources.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" -namespace api { +#undef LOG +using namespace blink; +#if defined(OS_WIN) +#define _USE_MATH_DEFINES +#include +#endif + +#undef FROM_HERE + +#include "third_party/WebKit/Source/config.h" +#include "third_party/WebKit/Source/core/html/HTMLIFrameElement.h" +#include "third_party/WebKit/Source/core/dom/Document.h" +#include "third_party/WebKit/Source/core/frame/LocalFrame.h" +#include "third_party/WebKit/public/web/WebFrame.h" +#include "third_party/WebKit/public/web/WebView.h" +#include "third_party/WebKit/Source/web/WebLocalFrameImpl.h" +#include "third_party/WebKit/public/web/WebScriptSource.h" + +#undef BLINK_IMPLEMENTATION +#define BLINK_IMPLEMENTATION 1 +#include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/Source/platform/heap/Handle.h" +//#include "third_party/WebKit/Source/core/inspector/InspectorInstrumentation.h" +//#include "third_party/WebKit/Source/core/inspector/InspectorResourceAgent.h" + +#undef CHECK +#include "V8HTMLIFrameElement.h" + +extern void FixSourceNWBin(v8::Isolate* v8_isolate, v8::Handle script); + +using blink::WebScriptSource; +using blink::WebFrame; +//using blink::InstrumentingAgents; +//using blink::InspectorResourceAgent; + +namespace nwapi { WindowBindings::WindowBindings() : v8::Extension("window_bindings.js", @@ -43,97 +79,206 @@ WindowBindings::~WindowBindings() { } v8::Handle -WindowBindings::GetNativeFunction(v8::Handle name) { - if (name->Equals(v8::String::New("BindToShell"))) - return v8::FunctionTemplate::New(BindToShell); - else if (name->Equals(v8::String::New("CallObjectMethod"))) - return v8::FunctionTemplate::New(CallObjectMethod); - else if (name->Equals(v8::String::New("CallObjectMethodSync"))) - return v8::FunctionTemplate::New(CallObjectMethodSync); - else if (name->Equals(v8::String::New("GetWindowObject"))) - return v8::FunctionTemplate::New(GetWindowObject); - - return v8::FunctionTemplate::New(); +WindowBindings::GetNativeFunctionTemplate( + v8::Isolate* isolate, + v8::Handle name) { + if (name->Equals(v8::String::NewFromUtf8(isolate, "BindToShell"))) + return v8::FunctionTemplate::New(isolate, BindToShell); + else if (name->Equals(v8::String::NewFromUtf8(isolate, "CallObjectMethod"))) + return v8::FunctionTemplate::New(isolate, CallObjectMethod); + else if (name->Equals(v8::String::NewFromUtf8(isolate, "CallObjectMethodSync"))) + return v8::FunctionTemplate::New(isolate, CallObjectMethodSync); + else if (name->Equals(v8::String::NewFromUtf8(isolate, "GetWindowObject"))) + return v8::FunctionTemplate::New(isolate, GetWindowObject); + else if (name->Equals(v8::String::NewFromUtf8(isolate, "AllocateId"))) + return v8::FunctionTemplate::New(isolate, AllocateId); + + return v8::FunctionTemplate::New(isolate); } // static -v8::Handle -WindowBindings::BindToShell(const v8::Arguments& args) { +void +WindowBindings::BindToShell(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); int routing_id = args[0]->Int32Value(); int object_id = args[1]->Int32Value(); - remote::AllocateObject(routing_id, object_id, "Window", v8::Object::New()); + remote::AllocateObject(routing_id, object_id, "Window", v8::Object::New(isolate)); + + args.GetReturnValue().Set(v8::Undefined(isolate)); +} + +void +WindowBindings::AllocateId(const v8::FunctionCallbackInfo& args) { + content::RenderViewImpl* render_view = static_cast(GetEnteredRenderView()); + int routing_id = render_view->GetRoutingID(); - return v8::Undefined(); + args.GetReturnValue().Set(remote::AllocateId(routing_id)); } // static -v8::Handle -WindowBindings::CallObjectMethod(const v8::Arguments& args) { +void +WindowBindings::CallObjectMethod(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::EscapableHandleScope scope(isolate); + v8::Local self = args[0]->ToObject(); - int routing_id = self->Get(v8::String::New("routing_id"))->Int32Value(); - int object_id = self->Get(v8::String::New("id"))->Int32Value(); + int routing_id = self->Get(v8::String::NewFromUtf8(isolate, "routing_id"))->Int32Value(); + int object_id = self->Get(v8::String::NewFromUtf8(isolate, "id"))->Int32Value(); std::string method = *v8::String::Utf8Value(args[1]); + content::RenderViewImpl* render_view = static_cast( + content::RenderViewImpl::FromRoutingID(routing_id)); + if (!render_view) + render_view = static_cast(GetEnteredRenderView()); - return remote::CallObjectMethod( - routing_id, object_id, "Window", method, args[2]); + if (!render_view) { + std::string msg = "Unable to get render view in " + method; + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, msg.c_str())))); + return; + } + + WebFrame* main_frame = render_view->GetWebView()->mainFrame(); + if (method == "EvaluateScript") { + v8::Handle result; + v8::Handle frm = v8::Handle::Cast(args[2]); + WebFrame* web_frame = NULL; + if (frm->IsNull()) { + web_frame = main_frame; + }else{ + blink::HTMLIFrameElement* iframe = blink::V8HTMLIFrameElement::toImpl(frm); + web_frame = blink::WebFrame::fromFrame(iframe->contentFrame()); + } +#if defined(OS_WIN) + base::string16 jscript((WCHAR*)*v8::String::Value(args[3])); +#else + base::string16 jscript = *v8::String::Value(args[3]); +#endif + if (web_frame) { + result = web_frame->executeScriptAndReturnValue(WebScriptSource(jscript)); + } + args.GetReturnValue().Set(result); + return; + } else if (method == "EvaluateNWBin") { +#if defined(OS_WIN) + base::FilePath path((WCHAR*)*v8::String::Value(args[3])); +#else + base::FilePath path(*v8::String::Utf8Value(args[3])); +#endif + base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ); + if (file.IsValid()) { + int64 length = file.GetLength(); + if (length > 0 && length < INT_MAX) { + int size = static_cast(length); + std::vector raw_data; + raw_data.resize(size); + uint8_t* data = reinterpret_cast(&(raw_data.front())); + if (file.ReadAtCurrentPos((char*)data, size) == length) { + v8::Handle source_string = v8::String::NewFromUtf8(isolate, ""); + v8::ScriptCompiler::CachedData* cache; + cache = new v8::ScriptCompiler::CachedData( + data, length, v8::ScriptCompiler::CachedData::BufferNotOwned); + v8::ScriptCompiler::Source source(source_string, cache); + v8::Local script; + script = v8::ScriptCompiler::CompileUnbound( + isolate, &source, v8::ScriptCompiler::kConsumeCodeCache); + ASSERT(!cache->rejected); + v8::Handle result; + v8::Handle frm = v8::Handle::Cast(args[2]); + WebFrame* web_frame = NULL; + if (frm->IsNull()) { + web_frame = main_frame; + }else{ + blink::HTMLIFrameElement* iframe = blink::V8HTMLIFrameElement::toImpl(frm); + web_frame = blink::WebFrame::fromFrame(iframe->contentFrame()); + } + v8::Context::Scope cscope (web_frame->mainWorldScriptContext()); + FixSourceNWBin(isolate, script); + result = script->BindToCurrentContext()->Run(); + args.GetReturnValue().Set(result); + } + } + } + return; + } else if (method == "setDevToolsJail") { + v8::Handle frm = v8::Handle::Cast(args[2]); + if (frm->IsNull()) { + main_frame->setDevtoolsJail(NULL); + }else{ + blink::HTMLIFrameElement* iframe = blink::V8HTMLIFrameElement::toImpl(frm); + main_frame->setDevtoolsJail(blink::WebFrame::fromFrame(iframe->contentFrame())); + } + args.GetReturnValue().Set(v8::Undefined(isolate)); + return; + } else if (method == "setCacheDisabled") { +#if 0 //FIXME + RefPtrWillBePersistent document = static_cast >(main_frame->document()); + InstrumentingAgents* instrumentingAgents = instrumentationForPage(document->page()); + if (instrumentingAgents) { + bool disable = args[2]->ToBoolean()->Value(); + InspectorResourceAgent* resAgent = instrumentingAgents->inspectorResourceAgent(); + resAgent->setCacheDisabled(NULL, disable); + args.GetReturnValue().Set(true); + } else + args.GetReturnValue().Set(false); + return; +#endif + } + + args.GetReturnValue().Set(remote::CallObjectMethod(render_view->GetRoutingID(), + object_id, + "Window", method, args[2])); } // static -v8::Handle -WindowBindings::CallObjectMethodSync(const v8::Arguments& args) { - v8::HandleScope scope; +void +WindowBindings::CallObjectMethodSync(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::EscapableHandleScope scope(isolate); v8::Local self = args[0]->ToObject(); - int routing_id = self->Get(v8::String::New("routing_id"))->Int32Value(); - int object_id = self->Get(v8::String::New("id"))->Int32Value(); + int routing_id = self->Get(v8::String::NewFromUtf8(isolate, "routing_id"))->Int32Value(); + int object_id = self->Get(v8::String::NewFromUtf8(isolate, "id"))->Int32Value(); std::string method = *v8::String::Utf8Value(args[1]); + content::RenderViewImpl* render_view = static_cast( + content::RenderViewImpl::FromRoutingID(routing_id)); + if (!render_view) { + std::string msg = "Unable to get render view in " + method; + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, msg.c_str())))); + return; + } if (method == "GetZoomLevel") { - content::RenderViewImpl* render_view = static_cast( - content::ChildThread::current()->ResolveRoute(routing_id)); - if (!render_view) - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "Unable to get render view in GetZoomLevel"))); - float zoom_level = render_view->GetWebView()->zoomLevel(); - v8::Local array = v8::Array::New(); - array->Set(0, v8::Number::New(zoom_level)); - return scope.Close(array); - } - - if (method == "SetZoomLevel") { - content::RenderViewImpl* render_view = static_cast( - content::ChildThread::current()->ResolveRoute(routing_id)); - if (!render_view) - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "Unable to get render view in SetZoomLevel"))); - + v8::Local array = v8::Array::New(isolate); + array->Set(0, v8::Number::New(isolate, zoom_level)); + args.GetReturnValue().Set(scope.Escape(array)); + return; + }else if (method == "SetZoomLevel") { double zoom_level = args[2]->ToNumber()->Value(); - render_view->OnSetZoomLevel(zoom_level); - return v8::Undefined(); + render_view->GetWebView()->setZoomLevel(zoom_level); + nwapi::Dispatcher::ZoomLevelChanged(render_view->GetWebView()); + args.GetReturnValue().Set(v8::Undefined(isolate)); + return; } - - return remote::CallObjectMethodSync( - routing_id, object_id, "Window", method, args[2]); + args.GetReturnValue().Set(remote::CallObjectMethodSync(routing_id, object_id, "Window", method, args[2])); } // static -v8::Handle -WindowBindings::GetWindowObject(const v8::Arguments& args) { +void +WindowBindings::GetWindowObject(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); int routing_id = args[0]->Int32Value(); // Dark magic to digg out the RenderView from its id. content::RenderViewImpl* render_view = static_cast( - content::ChildThread::current()->ResolveRoute(routing_id)); - if (!render_view) - return v8::ThrowException(v8::Exception::Error(v8::String::New( - "Unable to get render view in GetWindowObject"))); - + content::RenderViewImpl::FromRoutingID(routing_id)); + if (!render_view) { + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "Unable to get render view in GetWindowObject")))); + return; + } // Return the window object. - return render_view->GetWebView()->mainFrame()->mainWorldScriptContext()-> - Global(); + args.GetReturnValue().Set(render_view->GetWebView()->mainFrame()->mainWorldScriptContext()->Global()); } -} // namespace api +} // namespace nwapi diff --git a/src/api/window_bindings.h b/src/api/window_bindings.h index cc2820bf77..4bb99d6d87 100644 --- a/src/api/window_bindings.h +++ b/src/api/window_bindings.h @@ -25,33 +25,36 @@ #include "base/compiler_specific.h" #include "v8/include/v8.h" -namespace api { +namespace nwapi { class WindowBindings : public v8::Extension { public: WindowBindings(); - virtual ~WindowBindings(); + ~WindowBindings() override; // v8::Extension implementation. - virtual v8::Handle - GetNativeFunction(v8::Handle name) OVERRIDE; - + v8::Handle + GetNativeFunctionTemplate( + v8::Isolate* isolate, + v8::Handle name) override; private: + static void AllocateId(const v8::FunctionCallbackInfo& args); + // Tell browser to bind a js object to Shell. - static v8::Handle BindToShell(const v8::Arguments& args); + static void BindToShell(const v8::FunctionCallbackInfo& args); // Call method of an object in browser. - static v8::Handle CallObjectMethod(const v8::Arguments& args); + static void CallObjectMethod(const v8::FunctionCallbackInfo& args); // Call method of an object in browser synchrounously. - static v8::Handle CallObjectMethodSync(const v8::Arguments& args); + static void CallObjectMethodSync(const v8::FunctionCallbackInfo& args); // Get the window object of current render view. - static v8::Handle GetWindowObject(const v8::Arguments& args); + static void GetWindowObject(const v8::FunctionCallbackInfo& args); DISALLOW_COPY_AND_ASSIGN(WindowBindings); }; -} // namespace api +} // namespace nwapi #endif // CONTENT_NW_SRC_API_WINDOW_BINDINGS_H_ diff --git a/src/api/window_bindings.js b/src/api/window_bindings.js index a29d0583e5..c5e987b523 100644 --- a/src/api/window_bindings.js +++ b/src/api/window_bindings.js @@ -1,21 +1,83 @@ -function Window(routing_id) { - // Get and set id. - var id = global.__nwObjectsRegistry.allocateId(); - Object.defineProperty(this, 'id', { - value: id, - writable: false - }); - - // Store routing id (need for IPC since we are in node's context). - this.routing_id = routing_id; - - // Store myself in node's context. - global.__nwWindowsStore[id] = this; - global.__nwObjectsRegistry.set(id, this); +function Window(routing_id, nobind, predefined_id) { + // Get and set id. + native function CallObjectMethod(); + native function CallObjectMethodSync(); + native function AllocateId(); + + var id; + if (predefined_id) + id = predefined_id; + else + id = AllocateId(); + + Object.defineProperty(this, 'id', { + value: id, + writable: false + }); - // Tell Shell I'm the js delegate of it. - native function BindToShell(); - BindToShell(this.routing_id, this.id); + // Store routing id (need for IPC since we are in node's context). + this.routing_id = routing_id; + + // Store myself in node's context. + global.__nwWindowsStore[id] = this; + global.__nwObjectsRegistry.set(id, this); + + // Tell Shell I'm the js delegate of it. + native function BindToShell(); + if (!nobind) + BindToShell(this.routing_id, this.id); + + var that = this; + this.cookies = { + req_id : 0, + get : function(details, cb) { + this.req_id++; + if (typeof cb == 'function') { + that.once('__nw_gotcookie' + this.req_id, function(cookie) { + if (cookie.length > 0) + cb(cookie[0]); + else + cb(null); + }); + } + CallObjectMethod(that, 'CookieGet', [ this.req_id, details ]); + }, + getAll : function(details, cb) { + this.req_id++; + if (typeof cb == 'function') { + that.once('__nw_gotcookie' + this.req_id, function(cookie) { + cb(cookie); + }); + } + CallObjectMethod(that, 'CookieGetAll', [ this.req_id, details ]); + }, + remove : function(details, cb) { + this.req_id++; + if (typeof cb == 'function') { + that.once('__nw_gotcookie' + this.req_id, function(details) { + cb(details); + }); + } + CallObjectMethod(that, 'CookieRemove', [ this.req_id, details ]); + }, + set : function(details, cb) { + this.req_id++; + if (typeof cb == 'function') { + that.once('__nw_gotcookie' + this.req_id, function(cookie) { + cb(cookie); + }); + } + CallObjectMethod(that, 'CookieSet', [ this.req_id, details ]); + }, + onChanged : { + addListener : function(cb) { + that.on('__nw_cookie_changed', cb); + }, + removeListener : function(cb) { + that.removeListener('__nw_cookie_changed', cb); + } + } + } } // Window will inherit EventEmitter in "third_party/node/src/node.js", do @@ -34,7 +96,7 @@ native function CallObjectMethodSync(); Window.prototype.on = Window.prototype.addListener = function(ev, callback) { // Save window id of where the callback is created. var closure = v8_util.getCreationContext(callback); - if (v8_util.getConstructorName(closure) == 'Window' && + if (v8_util.getConstructorName(closure) == 'Window' && closure.hasOwnProperty('nwDispatcher')) { v8_util.setHiddenValue(callback, '__nwWindowId', closure.nwDispatcher.requireNwGui().Window.get().id); @@ -51,10 +113,11 @@ Window.prototype.handleEvent = function(ev) { for (var i = 0; i < listeners_copy.length; ++i) { var original_closure = v8_util.getCreationContext(listeners_copy[i]); - // Skip for node context. + // Skip for node context. if (v8_util.getConstructorName(original_closure) != 'Window') continue; + var window_id = v8_util.getHiddenValue(listeners_copy[i], '__nwWindowId'); // Remove callback if original window is closed (not in __nwWindowsStore). @@ -68,12 +131,23 @@ Window.prototype.handleEvent = function(ev) { // Do nothing if nothing is changed. if (original_hash == current_hash) continue; + if (original_closure.window.top === global.__nwWindowsStore[window_id].window) //iframe case + continue; } - console.warn('Remove zombie callback for window id ' + window_id); + console.warn('Remove zombie callback for window id ' + window_id + " ev: " + ev); this.removeListener(ev, listeners_copy[i]); } + if (ev == 'new-win-policy' && arguments.length > 3) { + var policy = arguments[3]; + policy.ignore = function () { this.val = 'ignore'; }; + policy.forceCurrent = function () { this.val = 'current'; }; + policy.forceDownload = function () { this.val = 'download'; }; + policy.forceNewWindow = function () { this.val = 'new-window'; }; + policy.forceNewPopup = function () { this.val = 'new-popup'; }; + policy.setNewWindowManifest = function (m) { this.manifest = JSON.stringify(m); }; + } // Route events to EventEmitter. this.emit.apply(this, arguments); @@ -142,11 +216,15 @@ Window.prototype.__defineGetter__('zoomLevel', function() { }); Window.prototype.__defineSetter__('menu', function(menu) { + if(!menu) { + CallObjectMethod(this, "ClearMenu", []); + return; + } if (v8_util.getConstructorName(menu) != 'Menu') - throw new String("'menu' property requries a valid Menu"); + throw new TypeError("'menu' property requries a valid Menu"); if (menu.type != 'menubar') - throw new String('Only menu of type "menubar" can be used as this.window menu'); + throw new TypeError('Only menu of type "menubar" can be used as this.window menu'); v8_util.setHiddenValue(this, 'menu', menu); CallObjectMethod(this, 'SetMenu', [ menu.id ]); @@ -163,10 +241,20 @@ Window.prototype.__defineSetter__('isFullscreen', function(flag) { this.leaveFullscreen(); }); +Window.prototype.isDevToolsOpen = function () { + var result = CallObjectMethodSync(this, 'IsDevToolsOpen', []); + return Boolean(result[0]); +} + Window.prototype.__defineGetter__('isFullscreen', function() { var result = CallObjectMethodSync(this, 'IsFullscreen', []); return Boolean(result[0]); }); + +Window.prototype.__defineGetter__('isTransparent', function() { + var result = CallObjectMethodSync(this, 'IsTransparent', []); + return Boolean(result[0]); +}); Window.prototype.__defineSetter__('isKioskMode', function(flag) { if (flag) @@ -199,15 +287,21 @@ Window.prototype.resizeBy = function(width, height) { } Window.prototype.focus = function(flag) { - if (typeof flag == 'undefined' || Boolean(flag)) - this.window.focus(); - else + if (typeof flag == 'undefined' || Boolean(flag)) { + if (this.__nw_is_devtools) + CallObjectMethod(this, 'Focus', []); + else + this.window.focus(); + } else this.blur(); -} +}; Window.prototype.blur = function() { - this.window.blur(); -} + if (this.__nw_is_devtools) + CallObjectMethod(this, 'Blur', []); + else + this.window.blur(); +}; Window.prototype.show = function(flag) { if (typeof flag == 'undefined' || Boolean(flag)) @@ -220,10 +314,6 @@ Window.prototype.hide = function() { CallObjectMethod(this, 'Hide', []); } -Window.prototype.hide = function() { - CallObjectMethod(this, 'Hide', []); -} - Window.prototype.close = function(force) { CallObjectMethod(this, 'Close', [ Boolean(force) ]); } @@ -268,8 +358,37 @@ Window.prototype.toggleKioskMode = function() { CallObjectMethod(this, 'ToggleKioskMode', []); } -Window.prototype.showDevTools = function() { - CallObjectMethod(this, 'ShowDevTools', []); +Window.prototype.closeDevTools = function() { + CallObjectMethod(this, 'CloseDevTools', []); +} + +Window.prototype.showDevTools = function(frm, headless) { + var id = ''; + if (typeof frm === 'string') { + id = frm; + this._pending_devtools_jail = null; + }else{ + this._pending_devtools_jail = frm; + } + var win_id = CallObjectMethodSync(this, 'ShowDevTools', [id, Boolean(headless)])[0]; + var ret; + if (typeof win_id == 'number' && win_id > 0) { + ret = global.__nwWindowsStore[win_id]; + if (!ret) { + ret = new global.Window(this.window.nwDispatcher.getRoutingIDForCurrentContext(), true, win_id); + ret.__nw_is_devtools = true; + } + return ret; + } +} + +Window.prototype.__setDevToolsJail = function(id) { + var frm = null; + if (id) + frm = this.window.document.getElementById(id); + else + frm = this._pending_devtools_jail || null; + CallObjectMethod(this, 'setDevToolsJail', frm); } Window.prototype.setMinimumSize = function(width, height) { @@ -289,14 +408,41 @@ Window.prototype.setAlwaysOnTop = function(flag) { CallObjectMethod(this, 'SetAlwaysOnTop', [ Boolean(flag) ]); } +Window.prototype.setShowInTaskbar = function(flag) { + flag = Boolean(flag); + CallObjectMethod(this, 'SetShowInTaskbar', [ flag ]); +} + +Window.prototype.setVisibleOnAllWorkspaces = function(flag) { + CallObjectMethod(this, 'SetVisibleOnAllWorkspaces', [ Boolean(flag) ]); +} + Window.prototype.requestAttention = function(flash) { - flash = Boolean(flash); + if (typeof flash == 'boolean') { + // boolean true is redirected as -1 value + flash = flash ? -1 : 0; + } CallObjectMethod(this, 'RequestAttention', [ flash ]); } +Window.prototype.setBadgeLabel = function(label) { + label = "" + label; + CallObjectMethod(this, 'SetBadgeLabel', [ label ]); +} + +Window.prototype.setProgressBar = function(progress) { + if (typeof progress != "number") + throw new TypeError('progress must be a number'); + CallObjectMethod(this, 'SetProgressBar', [ progress ]); +} + +Window.prototype.setTransparent = function(transparent) { + CallObjectMethod(this, 'SetTransparent', [ transparent ]); +} + Window.prototype.setPosition = function(position) { if (position != 'center' && position != 'mouse') - throw new String('Invalid postion'); + throw new TypeError('Invalid postion'); CallObjectMethod(this, 'SetPosition', [ position ]); } @@ -320,18 +466,56 @@ Window.prototype.reloadDev = function() { this.reload(3); } -Window.prototype.capturePage = function(callback, image_format) { - if (image_format != 'jpeg' && image_format != 'png') { - image_format = 'jpeg'; +var mime_types = { + 'jpeg' : 'image/jpeg', + 'png' : 'image/png' +} + +Window.prototype.capturePage = function(callback, image_format_options) { + var options; + + // Be compatible with the old api capturePage(callback, [format string]) + if (typeof image_format_options == 'string' || image_format_options instanceof String) { + options = { + format : image_format_options + }; + } else { + options = image_format_options || {}; + } + + if (options.format != 'jpeg' && options.format != 'png') { + options.format = 'jpeg'; } if (typeof callback == 'function') { - this.once('capturepagedone', function(imgdata) { - callback(imgdata); + this.once('__nw_capturepagedone', function(imgdata) { + switch(options.datatype){ + case 'buffer' : + callback(new Buffer(imgdata, "base64")); + break; + case 'raw' : + callback(imgdata); + case 'datauri' : + default : + callback('data:' + mime_types[options.format] + ';base64,' + imgdata ); + } }); } - CallObjectMethod(this, 'CapturePage', [image_format]); -} + CallObjectMethod(this, 'CapturePage', [options.format]); +}; + +Window.prototype.eval = function(frame, script) { + return CallObjectMethod(this, 'EvaluateScript', frame, script); +}; + +Window.prototype.evalNWBin = function(frame, script) { + return CallObjectMethod(this, 'EvaluateNWBin', frame, script); +}; + +Window.prototype.disableCache = function(flag) { + return CallObjectMethod(this, 'setCacheDisabled', flag); +}; + } // function Window.init diff --git a/src/breakpad_linux.cc b/src/breakpad_linux.cc new file mode 100644 index 0000000000..2f1d8fcb49 --- /dev/null +++ b/src/breakpad_linux.cc @@ -0,0 +1,1412 @@ +// 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. + +// For linux_syscall_support.h. This makes it safe to call embedded system +// calls when in seccomp mode. + +#include "components/breakpad/app/breakpad_linux.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "base/base_switches.h" +#include "base/command_line.h" +#include "base/debug/crash_logging.h" +#include "base/debug/dump_without_crashing.h" +#include "base/files/file_path.h" +#include "base/linux_util.h" +#include "base/path_service.h" +#include "base/posix/eintr_wrapper.h" +#include "base/posix/global_descriptors.h" +#include "base/process/memory.h" +#include "base/strings/string_util.h" +#include "breakpad/src/client/linux/handler/exception_handler.h" +#include "breakpad/src/client/linux/minidump_writer/directory_reader.h" +#include "breakpad/src/common/linux/linux_libc_support.h" +#include "breakpad/src/common/memory.h" +#include "chrome/common/child_process_logging.h" +#include "chrome/common/chrome_paths.h" +#include "components/breakpad/app/breakpad_client.h" +#include "components/breakpad/app/breakpad_linux_impl.h" +#include "content/public/common/content_descriptors.h" +#include "content/public/common/content_switches.h" + +#if defined(OS_ANDROID) +#include +#include + +#include "base/android/build_info.h" +#include "base/android/path_utils.h" +#endif +#include "third_party/lss/linux_syscall_support.h" + +#if defined(ADDRESS_SANITIZER) +#include // for getcontext(). +#endif + +#if defined(OS_ANDROID) +#define STAT_STRUCT struct stat +#define FSTAT_FUNC fstat +#else +#define STAT_STRUCT struct kernel_stat +#define FSTAT_FUNC sys_fstat +#endif + +// Some versions of gcc are prone to warn about unused return values. In cases +// where we either a) know the call cannot fail, or b) there is nothing we +// can do when a call fails, we mark the return code as ignored. This avoids +// spurious compiler warnings. +#define IGNORE_RET(x) do { if (x); } while (0) + +using google_breakpad::ExceptionHandler; +using google_breakpad::MinidumpDescriptor; + +using breakpad::GetBreakpadClient; + +namespace breakpad { +ExceptionHandler* g_breakpad = NULL; +namespace { + +const char kUploadURL[] = "https://clients2.google.com/cr/report"; + +bool g_is_crash_reporter_enabled = false; +uint64_t g_process_start_time = 0; +char* g_crash_log_path = NULL; + +#if defined(ADDRESS_SANITIZER) +const char* g_asan_report_str = NULL; +#endif +#if defined(OS_ANDROID) +char* g_process_type = NULL; +#endif + +CrashKeyStorage* g_crash_keys = NULL; + +// Writes the value |v| as 16 hex characters to the memory pointed at by +// |output|. +void write_uint64_hex(char* output, uint64_t v) { + static const char hextable[] = "0123456789abcdef"; + + for (int i = 15; i >= 0; --i) { + output[i] = hextable[v & 15]; + v >>= 4; + } +} + +// The following helper functions are for calculating uptime. + +// Converts a struct timeval to milliseconds. +uint64_t timeval_to_ms(struct timeval *tv) { + uint64_t ret = tv->tv_sec; // Avoid overflow by explicitly using a uint64_t. + ret *= 1000; + ret += tv->tv_usec / 1000; + return ret; +} + +// Converts a struct timeval to milliseconds. +uint64_t kernel_timeval_to_ms(struct kernel_timeval *tv) { + uint64_t ret = tv->tv_sec; // Avoid overflow by explicitly using a uint64_t. + ret *= 1000; + ret += tv->tv_usec / 1000; + return ret; +} + +// String buffer size to use to convert a uint64_t to string. +const size_t kUint64StringSize = 21; + +void SetProcessStartTime() { + // Set the base process start time value. + struct timeval tv; + if (!gettimeofday(&tv, NULL)) + g_process_start_time = timeval_to_ms(&tv); + else + g_process_start_time = 0; +} + +// uint64_t version of my_int_len() from +// breakpad/src/common/linux/linux_libc_support.h. Return the length of the +// given, non-negative integer when expressed in base 10. +unsigned my_uint64_len(uint64_t i) { + if (!i) + return 1; + + unsigned len = 0; + while (i) { + len++; + i /= 10; + } + + return len; +} + +// uint64_t version of my_uitos() from +// breakpad/src/common/linux/linux_libc_support.h. Convert a non-negative +// integer to a string (not null-terminated). +void my_uint64tos(char* output, uint64_t i, unsigned i_len) { + for (unsigned index = i_len; index; --index, i /= 10) + output[index - 1] = '0' + (i % 10); +} + +#if defined(OS_ANDROID) +char* my_strncpy(char* dst, const char* src, size_t len) { + int i = len; + char* p = dst; + if (!dst || !src) + return dst; + while (i != 0 && *src != '\0') { + *p++ = *src++; + i--; + } + while (i != 0) { + *p++ = '\0'; + i--; + } + return dst; +} + +char* my_strncat(char *dest, const char* src, size_t len) { + char* ret = dest; + while (*dest) + dest++; + while (len--) + if (!(*dest++ = *src++)) + return ret; + *dest = 0; + return ret; +} +#endif + +size_t LengthWithoutTrailingSpaces(const char* str, size_t len) { + while (len > 0 && str[len - 1] == ' ') { + len--; + } + return len; +} + +void SetClientIdFromCommandLine(const CommandLine& command_line) { + // Get the guid and linux distro from the command line switch. + std::string switch_value = + command_line.GetSwitchValueASCII(switches::kEnableCrashReporter); + GetBreakpadClient()->SetBreakpadClientIdFromGUID(switch_value); +} + +// MIME substrings. +#if defined(OS_CHROMEOS) +const char g_sep[] = ":"; +#endif +const char g_rn[] = "\r\n"; +const char g_form_data_msg[] = "Content-Disposition: form-data; name=\""; +const char g_quote_msg[] = "\""; +const char g_dashdash_msg[] = "--"; +const char g_dump_msg[] = "upload_file_minidump\"; filename=\"dump\""; +#if defined(ADDRESS_SANITIZER) +const char g_log_msg[] = "upload_file_log\"; filename=\"log\""; +#endif +const char g_content_type_msg[] = "Content-Type: application/octet-stream"; + +// MimeWriter manages an iovec for writing MIMEs to a file. +class MimeWriter { + public: + static const int kIovCapacity = 30; + static const size_t kMaxCrashChunkSize = 64; + + MimeWriter(int fd, const char* const mime_boundary); + ~MimeWriter(); + + // Append boundary. + virtual void AddBoundary(); + + // Append end of file boundary. + virtual void AddEnd(); + + // Append key/value pair with specified sizes. + virtual void AddPairData(const char* msg_type, + size_t msg_type_size, + const char* msg_data, + size_t msg_data_size); + + // Append key/value pair. + void AddPairString(const char* msg_type, + const char* msg_data) { + AddPairData(msg_type, my_strlen(msg_type), msg_data, my_strlen(msg_data)); + } + + // Append key/value pair, splitting value into chunks no larger than + // |chunk_size|. |chunk_size| cannot be greater than |kMaxCrashChunkSize|. + // The msg_type string will have a counter suffix to distinguish each chunk. + virtual void AddPairDataInChunks(const char* msg_type, + size_t msg_type_size, + const char* msg_data, + size_t msg_data_size, + size_t chunk_size, + bool strip_trailing_spaces); + + // Add binary file contents to be uploaded with the specified filename. + virtual void AddFileContents(const char* filename_msg, + uint8_t* file_data, + size_t file_size); + + // Flush any pending iovecs to the output file. + void Flush() { + IGNORE_RET(sys_writev(fd_, iov_, iov_index_)); + iov_index_ = 0; + } + + void AddItem(const void* base, size_t size); + +protected: + // Minor performance trade-off for easier-to-maintain code. + void AddString(const char* str) { + AddItem(str, my_strlen(str)); + } + void AddItemWithoutTrailingSpaces(const void* base, size_t size); + + struct kernel_iovec iov_[kIovCapacity]; + int iov_index_; + + // Output file descriptor. + int fd_; + + const char* const mime_boundary_; + + DISALLOW_COPY_AND_ASSIGN(MimeWriter); +}; + +MimeWriter::MimeWriter(int fd, const char* const mime_boundary) + : iov_index_(0), + fd_(fd), + mime_boundary_(mime_boundary) { +} + +MimeWriter::~MimeWriter() { +} + +void MimeWriter::AddBoundary() { + AddString(mime_boundary_); + AddString(g_rn); +} + +void MimeWriter::AddEnd() { + AddString(mime_boundary_); + AddString(g_dashdash_msg); + AddString(g_rn); +} + +void MimeWriter::AddPairData(const char* msg_type, + size_t msg_type_size, + const char* msg_data, + size_t msg_data_size) { + AddString(g_form_data_msg); + AddItem(msg_type, msg_type_size); + AddString(g_quote_msg); + AddString(g_rn); + AddString(g_rn); + AddItem(msg_data, msg_data_size); + AddString(g_rn); +} + +void MimeWriter::AddPairDataInChunks(const char* msg_type, + size_t msg_type_size, + const char* msg_data, + size_t msg_data_size, + size_t chunk_size, + bool strip_trailing_spaces) { + if (chunk_size > kMaxCrashChunkSize) + return; + + unsigned i = 0; + size_t done = 0, msg_length = msg_data_size; + + while (msg_length) { + char num[kUint64StringSize]; + const unsigned num_len = my_uint_len(++i); + my_uitos(num, i, num_len); + + size_t chunk_len = std::min(chunk_size, msg_length); + + AddString(g_form_data_msg); + AddItem(msg_type, msg_type_size); + AddItem(num, num_len); + AddString(g_quote_msg); + AddString(g_rn); + AddString(g_rn); + if (strip_trailing_spaces) { + AddItemWithoutTrailingSpaces(msg_data + done, chunk_len); + } else { + AddItem(msg_data + done, chunk_len); + } + AddString(g_rn); + AddBoundary(); + Flush(); + + done += chunk_len; + msg_length -= chunk_len; + } +} + +void MimeWriter::AddFileContents(const char* filename_msg, uint8_t* file_data, + size_t file_size) { + AddString(g_form_data_msg); + AddString(filename_msg); + AddString(g_rn); + AddString(g_content_type_msg); + AddString(g_rn); + AddString(g_rn); + AddItem(file_data, file_size); + AddString(g_rn); +} + +void MimeWriter::AddItem(const void* base, size_t size) { + // Check if the iovec is full and needs to be flushed to output file. + if (iov_index_ == kIovCapacity) { + Flush(); + } + iov_[iov_index_].iov_base = const_cast(base); + iov_[iov_index_].iov_len = size; + ++iov_index_; +} + +void MimeWriter::AddItemWithoutTrailingSpaces(const void* base, size_t size) { + AddItem(base, LengthWithoutTrailingSpaces(static_cast(base), + size)); +} + +#if defined(OS_CHROMEOS) +// This subclass is used on Chromium OS to report crashes in a format easy for +// the central crash reporting facility to understand. +// Format is :: +class CrashReporterWriter : public MimeWriter +{ + public: + explicit CrashReporterWriter(int fd); + + virtual void AddBoundary() OVERRIDE; + + virtual void AddEnd() OVERRIDE; + + virtual void AddPairData(const char* msg_type, + size_t msg_type_size, + const char* msg_data, + size_t msg_data_size) OVERRIDE; + + virtual void AddPairDataInChunks(const char* msg_type, + size_t msg_type_size, + const char* msg_data, + size_t msg_data_size, + size_t chunk_size, + bool strip_trailing_spaces) OVERRIDE; + + virtual void AddFileContents(const char* filename_msg, + uint8_t* file_data, + size_t file_size) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(CrashReporterWriter); +}; + + +CrashReporterWriter::CrashReporterWriter(int fd) : MimeWriter(fd, "") {} + +// No-ops. +void CrashReporterWriter::AddBoundary() {} +void CrashReporterWriter::AddEnd() {} + +void CrashReporterWriter::AddPairData(const char* msg_type, + size_t msg_type_size, + const char* msg_data, + size_t msg_data_size) { + char data[kUint64StringSize]; + const unsigned data_len = my_uint_len(msg_data_size); + my_uitos(data, msg_data_size, data_len); + + AddItem(msg_type, msg_type_size); + AddString(g_sep); + AddItem(data, data_len); + AddString(g_sep); + AddItem(msg_data, msg_data_size); + Flush(); +} + +void CrashReporterWriter::AddPairDataInChunks(const char* msg_type, + size_t msg_type_size, + const char* msg_data, + size_t msg_data_size, + size_t chunk_size, + bool strip_trailing_spaces) { + if (chunk_size > kMaxCrashChunkSize) + return; + + unsigned i = 0; + size_t done = 0; + size_t msg_length = msg_data_size; + + while (msg_length) { + char num[kUint64StringSize]; + const unsigned num_len = my_uint_len(++i); + my_uitos(num, i, num_len); + + size_t chunk_len = std::min(chunk_size, msg_length); + + size_t write_len = chunk_len; + if (strip_trailing_spaces) { + // Take care of this here because we need to know the exact length of + // what is going to be written. + write_len = LengthWithoutTrailingSpaces(msg_data + done, write_len); + } + + char data[kUint64StringSize]; + const unsigned data_len = my_uint_len(write_len); + my_uitos(data, write_len, data_len); + + AddItem(msg_type, msg_type_size); + AddItem(num, num_len); + AddString(g_sep); + AddItem(data, data_len); + AddString(g_sep); + AddItem(msg_data + done, write_len); + Flush(); + + done += chunk_len; + msg_length -= chunk_len; + } +} + +void CrashReporterWriter::AddFileContents(const char* filename_msg, + uint8_t* file_data, + size_t file_size) { + char data[kUint64StringSize]; + const unsigned data_len = my_uint_len(file_size); + my_uitos(data, file_size, data_len); + + AddString(filename_msg); + AddString(g_sep); + AddItem(data, data_len); + AddString(g_sep); + AddItem(file_data, file_size); + Flush(); +} +#endif + +void DumpProcess() { + if (g_breakpad) + g_breakpad->WriteMinidump(); +} + +const char kGoogleBreakpad[] = "google-breakpad"; + +size_t WriteLog(const char* buf, size_t nbytes) { +#if defined(OS_ANDROID) + return __android_log_write(ANDROID_LOG_WARN, kGoogleBreakpad, buf); +#else + return sys_write(2, buf, nbytes); +#endif +} + +#if defined(OS_ANDROID) +// Android's native crash handler outputs a diagnostic tombstone to the device +// log. By returning false from the HandlerCallbacks, breakpad will reinstall +// the previous (i.e. native) signal handlers before returning from its own +// handler. A Chrome build fingerprint is written to the log, so that the +// specific build of Chrome and the location of the archived Chrome symbols can +// be determined directly from it. +bool FinalizeCrashDoneAndroid() { + base::android::BuildInfo* android_build_info = + base::android::BuildInfo::GetInstance(); + + __android_log_write(ANDROID_LOG_WARN, kGoogleBreakpad, + "### ### ### ### ### ### ### ### ### ### ### ### ###"); + __android_log_write(ANDROID_LOG_WARN, kGoogleBreakpad, + "Chrome build fingerprint:"); + __android_log_write(ANDROID_LOG_WARN, kGoogleBreakpad, + android_build_info->package_version_name()); + __android_log_write(ANDROID_LOG_WARN, kGoogleBreakpad, + android_build_info->package_version_code()); + __android_log_write(ANDROID_LOG_WARN, kGoogleBreakpad, + CHROME_BUILD_ID); + __android_log_write(ANDROID_LOG_WARN, kGoogleBreakpad, + "### ### ### ### ### ### ### ### ### ### ### ### ###"); + return false; +} +#endif + +bool CrashDone(const MinidumpDescriptor& minidump, + const bool upload, + const bool succeeded) { + // WARNING: this code runs in a compromised context. It may not call into + // libc nor allocate memory normally. + if (!succeeded) { + const char msg[] = "Failed to generate minidump."; + WriteLog(msg, sizeof(msg) - 1); + return false; + } + + DCHECK(!minidump.IsFD()); + + BreakpadInfo info = {0}; + info.filename = minidump.path(); + info.fd = minidump.fd(); +#if defined(ADDRESS_SANITIZER) + google_breakpad::PageAllocator allocator; + const size_t log_path_len = my_strlen(minidump.path()); + char* log_path = reinterpret_cast(allocator.Alloc(log_path_len + 1)); + my_memcpy(log_path, minidump.path(), log_path_len); + my_memcpy(log_path + log_path_len - 4, ".log", 4); + log_path[log_path_len] = '\0'; + info.log_filename = log_path; +#endif + info.process_type = "browser"; + info.process_type_length = 7; + info.distro = base::g_linux_distro; + info.distro_length = my_strlen(base::g_linux_distro); + info.upload = upload; + info.process_start_time = g_process_start_time; + info.oom_size = base::g_oom_size; + info.pid = 0; + info.crash_keys = g_crash_keys; + HandleCrashDump(info); +#if defined(OS_ANDROID) + return FinalizeCrashDoneAndroid(); +#else + return true; +#endif +} + +// Wrapper function, do not add more code here. +bool CrashDoneNoUpload(const MinidumpDescriptor& minidump, + void* context, + bool succeeded) { + return CrashDone(minidump, false, succeeded); +} + +#if !defined(OS_ANDROID) +// Wrapper function, do not add more code here. +bool CrashDoneUpload(const MinidumpDescriptor& minidump, + void* context, + bool succeeded) { + return CrashDone(minidump, true, succeeded); +} +#endif + +#if defined(ADDRESS_SANITIZER) +extern "C" +void __asan_set_error_report_callback(void (*cb)(const char*)); + +extern "C" +void AsanLinuxBreakpadCallback(const char* report) { + g_asan_report_str = report; + // Send minidump here. + g_breakpad->SimulateSignalDelivery(SIGKILL); +} +#endif + +void EnableCrashDumping(bool unattended) { + g_is_crash_reporter_enabled = true; + + base::FilePath tmp_path("/tmp"); + PathService::Get(base::DIR_TEMP, &tmp_path); + + base::FilePath dumps_path(tmp_path); + if (breakpad::GetBreakpadClient()->GetCrashDumpLocation(&dumps_path)) { + base::FilePath logfile = dumps_path.Append( + breakpad::GetBreakpadClient()->GetReporterLogFilename()); + std::string logfile_str = logfile.value(); + const size_t crash_log_path_len = logfile_str.size() + 1; + g_crash_log_path = new char[crash_log_path_len]; + strncpy(g_crash_log_path, logfile_str.c_str(), crash_log_path_len); + } + DCHECK(!g_breakpad); + MinidumpDescriptor minidump_descriptor(dumps_path.value()); + minidump_descriptor.set_size_limit(kMaxMinidumpFileSize); +#if defined(OS_ANDROID) + unattended = true; // Android never uploads directly. +#endif + if (unattended) { + g_breakpad = new ExceptionHandler( + minidump_descriptor, + NULL, + CrashDoneNoUpload, + NULL, + true, // Install handlers. + -1); // Server file descriptor. -1 for in-process. + return; + } + +#if !defined(OS_ANDROID) + // Attended mode + g_breakpad = new ExceptionHandler( + minidump_descriptor, + NULL, + CrashDoneUpload, + NULL, + true, // Install handlers. + -1); // Server file descriptor. -1 for in-process. +#endif + LOG(INFO) << "Initialized crash dump in " << dumps_path.value(); +} + +#if defined(OS_ANDROID) +bool CrashDoneInProcessNoUpload( + const google_breakpad::MinidumpDescriptor& descriptor, + void* context, + const bool succeeded) { + // WARNING: this code runs in a compromised context. It may not call into + // libc nor allocate memory normally. + if (!succeeded) { + static const char msg[] = "Crash dump generation failed.\n"; + WriteLog(msg, sizeof(msg) - 1); + return false; + } + + // Start constructing the message to send to the browser. + char guid[kGuidSize + 1] = {0}; + char crash_url[kMaxActiveURLSize + 1] = {0}; + size_t guid_length = 0; + size_t crash_url_length = 0; + BreakpadInfo info = {0}; + info.filename = NULL; + info.fd = descriptor.fd(); + info.process_type = g_process_type; + info.process_type_length = my_strlen(g_process_type); + info.crash_url = crash_url; + info.crash_url_length = crash_url_length; + info.distro = base::g_linux_distro; + info.distro_length = my_strlen(base::g_linux_distro); + info.upload = false; + info.process_start_time = g_process_start_time; + HandleCrashDump(info); + return FinalizeCrashDoneAndroid(); +} + +void EnableNonBrowserCrashDumping(int minidump_fd) { + // This will guarantee that the BuildInfo has been initialized and subsequent + // calls will not require memory allocation. + base::android::BuildInfo::GetInstance(); + SetClientIdFromCommandLine(*CommandLine::ForCurrentProcess()); + + // On Android, the current sandboxing uses process isolation, in which the + // child process runs with a different UID. That breaks the normal crash + // reporting where the browser process generates the minidump by inspecting + // the child process. This is because the browser process now does not have + // the permission to access the states of the child process (as it has a + // different UID). + // TODO(jcivelli): http://b/issue?id=6776356 we should use a watchdog + // process forked from the renderer process that generates the minidump. + if (minidump_fd == -1) { + LOG(ERROR) << "Minidump file descriptor not found, crash reporting will " + " not work."; + return; + } + SetProcessStartTime(); + + g_is_crash_reporter_enabled = true; + // Save the process type (it is leaked). + const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess(); + const std::string process_type = + parsed_command_line.GetSwitchValueASCII(switches::kProcessType); + const size_t process_type_len = process_type.size() + 1; + g_process_type = new char[process_type_len]; + strncpy(g_process_type, process_type.c_str(), process_type_len); + new google_breakpad::ExceptionHandler(MinidumpDescriptor(minidump_fd), + NULL, CrashDoneInProcessNoUpload, NULL, true, -1); +} +#else +// Non-Browser = Extension, Gpu, Plugins, Ppapi and Renderer +bool NonBrowserCrashHandler(const void* crash_context, + size_t crash_context_size, + void* context) { + const int fd = reinterpret_cast(context); + int fds[2] = { -1, -1 }; + if (sys_socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) { + static const char msg[] = "Failed to create socket for crash dumping.\n"; + WriteLog(msg, sizeof(msg) - 1); + return false; + } + + // Start constructing the message to send to the browser. + char distro[kDistroSize + 1] = {0}; + PopulateDistro(distro, NULL); + + char b; // Dummy variable for sys_read below. + const char* b_addr = &b; // Get the address of |b| so we can create the + // expected /proc/[pid]/syscall content in the + // browser to convert namespace tids. + + // The length of the control message: + static const unsigned kControlMsgSize = sizeof(fds); + static const unsigned kControlMsgSpaceSize = CMSG_SPACE(kControlMsgSize); + static const unsigned kControlMsgLenSize = CMSG_LEN(kControlMsgSize); + + struct kernel_msghdr msg; + my_memset(&msg, 0, sizeof(struct kernel_msghdr)); + struct kernel_iovec iov[kCrashIovSize]; + iov[0].iov_base = const_cast(crash_context); + iov[0].iov_len = crash_context_size; + iov[1].iov_base = distro; + iov[1].iov_len = kDistroSize + 1; + iov[2].iov_base = &b_addr; + iov[2].iov_len = sizeof(b_addr); + iov[3].iov_base = &fds[0]; + iov[3].iov_len = sizeof(fds[0]); + iov[4].iov_base = &g_process_start_time; + iov[4].iov_len = sizeof(g_process_start_time); + iov[5].iov_base = &base::g_oom_size; + iov[5].iov_len = sizeof(base::g_oom_size); + google_breakpad::SerializedNonAllocatingMap* serialized_map; + iov[6].iov_len = g_crash_keys->Serialize( + const_cast( + &serialized_map)); + iov[6].iov_base = serialized_map; +#if defined(ADDRESS_SANITIZER) + iov[7].iov_base = const_cast(g_asan_report_str); + iov[7].iov_len = kMaxAsanReportSize + 1; +#endif + + msg.msg_iov = iov; + msg.msg_iovlen = kCrashIovSize; + char cmsg[kControlMsgSpaceSize]; + my_memset(cmsg, 0, kControlMsgSpaceSize); + msg.msg_control = cmsg; + msg.msg_controllen = sizeof(cmsg); + + struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); + hdr->cmsg_level = SOL_SOCKET; + hdr->cmsg_type = SCM_RIGHTS; + hdr->cmsg_len = kControlMsgLenSize; + ((int*) CMSG_DATA(hdr))[0] = fds[0]; + ((int*) CMSG_DATA(hdr))[1] = fds[1]; + + if (HANDLE_EINTR(sys_sendmsg(fd, &msg, 0)) < 0) { + static const char errmsg[] = "Failed to tell parent about crash.\n"; + WriteLog(errmsg, sizeof(errmsg) - 1); + IGNORE_RET(sys_close(fds[1])); + return false; + } + IGNORE_RET(sys_close(fds[1])); + + if (HANDLE_EINTR(sys_read(fds[0], &b, 1)) != 1) { + static const char errmsg[] = "Parent failed to complete crash dump.\n"; + WriteLog(errmsg, sizeof(errmsg) - 1); + } + + return true; +} + +void EnableNonBrowserCrashDumping() { + const int fd = base::GlobalDescriptors::GetInstance()->Get(kCrashDumpSignal); + g_is_crash_reporter_enabled = true; + // We deliberately leak this object. + DCHECK(!g_breakpad); + + g_breakpad = new ExceptionHandler( + MinidumpDescriptor("/tmp"), // Unused but needed or Breakpad will assert. + NULL, + NULL, + reinterpret_cast(fd), // Param passed to the crash handler. + true, + -1); + g_breakpad->set_crash_handler(NonBrowserCrashHandler); +} +#endif // defined(OS_ANDROID) + +void SetCrashKeyValue(const base::StringPiece& key, + const base::StringPiece& value) { + g_crash_keys->SetKeyValue(key.data(), value.data()); +} + +void ClearCrashKey(const base::StringPiece& key) { + g_crash_keys->RemoveKey(key.data()); +} + +} // namespace + +void LoadDataFromFD(google_breakpad::PageAllocator& allocator, + int fd, bool close_fd, uint8_t** file_data, size_t* size) { + STAT_STRUCT st; + if (FSTAT_FUNC(fd, &st) != 0) { + static const char msg[] = "Cannot upload crash dump: stat failed\n"; + WriteLog(msg, sizeof(msg) - 1); + if (close_fd) + IGNORE_RET(sys_close(fd)); + return; + } + + *file_data = reinterpret_cast(allocator.Alloc(st.st_size)); + if (!(*file_data)) { + static const char msg[] = "Cannot upload crash dump: cannot alloc\n"; + WriteLog(msg, sizeof(msg) - 1); + if (close_fd) + IGNORE_RET(sys_close(fd)); + return; + } + my_memset(*file_data, 0xf, st.st_size); + + *size = st.st_size; + int byte_read = sys_read(fd, *file_data, *size); + if (byte_read == -1) { + static const char msg[] = "Cannot upload crash dump: read failed\n"; + WriteLog(msg, sizeof(msg) - 1); + if (close_fd) + IGNORE_RET(sys_close(fd)); + return; + } + + if (close_fd) + IGNORE_RET(sys_close(fd)); +} + +void LoadDataFromFile(google_breakpad::PageAllocator& allocator, + const char* filename, + int* fd, uint8_t** file_data, size_t* size) { + // WARNING: this code runs in a compromised context. It may not call into + // libc nor allocate memory normally. + *fd = sys_open(filename, O_RDONLY, 0); + *size = 0; + + if (*fd < 0) { + static const char msg[] = "Cannot upload crash dump: failed to open\n"; + WriteLog(msg, sizeof(msg) - 1); + return; + } + + LoadDataFromFD(allocator, *fd, true, file_data, size); +} + +// Spawn the appropriate upload process for the current OS: +// - generic Linux invokes wget. +// - ChromeOS invokes crash_reporter. +// |dumpfile| is the path to the dump data file. +// |mime_boundary| is only used on Linux. +// |exe_buf| is only used on CrOS and is the crashing process' name. +void ExecUploadProcessOrTerminate(const BreakpadInfo& info, + const char* dumpfile, + const char* mime_boundary, + const char* exe_buf, + google_breakpad::PageAllocator* allocator) { +#if defined(OS_CHROMEOS) + // CrOS uses crash_reporter instead of wget to report crashes, + // it needs to know where the crash dump lives and the pid and uid of the + // crashing process. + static const char kCrashReporterBinary[] = "/sbin/crash_reporter"; + + char pid_buf[kUint64StringSize]; + uint64_t pid_str_length = my_uint64_len(info.pid); + my_uint64tos(pid_buf, info.pid, pid_str_length); + pid_buf[pid_str_length] = '\0'; + + char uid_buf[kUint64StringSize]; + uid_t uid = geteuid(); + uint64_t uid_str_length = my_uint64_len(uid); + my_uint64tos(uid_buf, uid, uid_str_length); + uid_buf[uid_str_length] = '\0'; + const char* args[] = { + kCrashReporterBinary, + "--chrome", + dumpfile, + "--pid", + pid_buf, + "--uid", + uid_buf, + "--exe", + exe_buf, + NULL, + }; + static const char msg[] = "Cannot upload crash dump: cannot exec " + "/sbin/crash_reporter\n"; +#else + // The --header argument to wget looks like: + // --header=Content-Type: multipart/form-data; boundary=XYZ + // where the boundary has two fewer leading '-' chars + static const char header_msg[] = + "--header=Content-Type: multipart/form-data; boundary="; + char* const header = reinterpret_cast(allocator->Alloc( + sizeof(header_msg) - 1 + strlen(mime_boundary) - 2 + 1)); + memcpy(header, header_msg, sizeof(header_msg) - 1); + memcpy(header + sizeof(header_msg) - 1, mime_boundary + 2, + strlen(mime_boundary) - 2); + // We grab the NUL byte from the end of |mime_boundary|. + + // The --post-file argument to wget looks like: + // --post-file=/tmp/... + static const char post_file_msg[] = "--post-file="; + char* const post_file = reinterpret_cast(allocator->Alloc( + sizeof(post_file_msg) - 1 + strlen(dumpfile) + 1)); + memcpy(post_file, post_file_msg, sizeof(post_file_msg) - 1); + memcpy(post_file + sizeof(post_file_msg) - 1, dumpfile, strlen(dumpfile)); + + static const char kWgetBinary[] = "/usr/bin/wget"; + const char* args[] = { + kWgetBinary, + header, + post_file, + kUploadURL, + "--timeout=10", // Set a timeout so we don't hang forever. + "--tries=1", // Don't retry if the upload fails. + "-O", // output reply to fd 3 + "/dev/fd/3", + NULL, + }; + static const char msg[] = "Cannot upload crash dump: cannot exec " + "/usr/bin/wget\n"; +#endif + execve(args[0], const_cast(args), environ); + WriteLog(msg, sizeof(msg) - 1); + sys__exit(1); +} + +#if defined(OS_CHROMEOS) +const char* GetCrashingProcessName(const BreakpadInfo& info, + google_breakpad::PageAllocator* allocator) { + // Symlink to process binary is at /proc/###/exe. + char linkpath[kUint64StringSize + sizeof("/proc/") + sizeof("/exe")] = + "/proc/"; + uint64_t pid_value_len = my_uint64_len(info.pid); + my_uint64tos(linkpath + sizeof("/proc/") - 1, info.pid, pid_value_len); + linkpath[sizeof("/proc/") - 1 + pid_value_len] = '\0'; + my_strlcat(linkpath, "/exe", sizeof(linkpath)); + + const int kMaxSize = 4096; + char* link = reinterpret_cast(allocator->Alloc(kMaxSize)); + if (link) { + ssize_t size = readlink(linkpath, link, kMaxSize); + if (size < kMaxSize && size > 0) { + // readlink(2) doesn't add a terminating NUL, so do it now. + link[size] = '\0'; + + const char* name = my_strrchr(link, '/'); + if (name) + return name + 1; + return link; + } + } + // Either way too long, or a read error. + return "chrome-crash-unknown-process"; +} +#endif + +void HandleCrashDump(const BreakpadInfo& info) { + int dumpfd; + bool keep_fd = false; + size_t dump_size; + uint8_t* dump_data; + google_breakpad::PageAllocator allocator; + const char* exe_buf = NULL; + +#if defined(OS_CHROMEOS) + // Grab the crashing process' name now, when it should still be available. + // If we try to do this later in our grandchild the crashing process has + // already terminated. + exe_buf = GetCrashingProcessName(info, &allocator); +#endif + + if (info.fd != -1) { + // Dump is provided with an open FD. + keep_fd = true; + dumpfd = info.fd; + + // The FD is pointing to the end of the file. + // Rewind, we'll read the data next. + if (lseek(dumpfd, 0, SEEK_SET) == -1) { + static const char msg[] = "Cannot upload crash dump: failed to " + "reposition minidump FD\n"; + WriteLog(msg, sizeof(msg) - 1); + IGNORE_RET(sys_close(dumpfd)); + return; + } + LoadDataFromFD(allocator, info.fd, false, &dump_data, &dump_size); + } else { + // Dump is provided with a path. + keep_fd = false; + LoadDataFromFile(allocator, info.filename, &dumpfd, &dump_data, &dump_size); + } + + // TODO(jcivelli): make log work when using FDs. +#if defined(ADDRESS_SANITIZER) + int logfd; + size_t log_size; + uint8_t* log_data; + // Load the AddressSanitizer log into log_data. + LoadDataFromFile(allocator, info.log_filename, &logfd, &log_data, &log_size); +#endif + + // We need to build a MIME block for uploading to the server. Since we are + // going to fork and run wget, it needs to be written to a temp file. + const int ufd = sys_open("/dev/urandom", O_RDONLY, 0); + if (ufd < 0) { + static const char msg[] = "Cannot upload crash dump because /dev/urandom" + " is missing\n"; + WriteLog(msg, sizeof(msg) - 1); + return; + } + + static const char temp_file_template[] = + "/tmp/chromium-upload-XXXXXXXXXXXXXXXX"; + char temp_file[sizeof(temp_file_template)]; + int temp_file_fd = -1; + if (keep_fd) { + temp_file_fd = dumpfd; + // Rewind the destination, we are going to overwrite it. + if (lseek(dumpfd, 0, SEEK_SET) == -1) { + static const char msg[] = "Cannot upload crash dump: failed to " + "reposition minidump FD (2)\n"; + WriteLog(msg, sizeof(msg) - 1); + IGNORE_RET(sys_close(dumpfd)); + return; + } + } else { + if (info.upload) { + memcpy(temp_file, temp_file_template, sizeof(temp_file_template)); + + for (unsigned i = 0; i < 10; ++i) { + uint64_t t; + sys_read(ufd, &t, sizeof(t)); + write_uint64_hex(temp_file + sizeof(temp_file) - (16 + 1), t); + + temp_file_fd = sys_open(temp_file, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (temp_file_fd >= 0) + break; + } + + if (temp_file_fd < 0) { + static const char msg[] = "Failed to create temporary file in /tmp: " + "cannot upload crash dump\n"; + WriteLog(msg, sizeof(msg) - 1); + IGNORE_RET(sys_close(ufd)); + return; + } + } else { + temp_file_fd = sys_open(info.filename, O_WRONLY, 0600); + if (temp_file_fd < 0) { + static const char msg[] = "Failed to save crash dump: failed to open\n"; + WriteLog(msg, sizeof(msg) - 1); + IGNORE_RET(sys_close(ufd)); + return; + } + } + } + + // The MIME boundary is 28 hyphens, followed by a 64-bit nonce and a NUL. + char mime_boundary[28 + 16 + 1]; + my_memset(mime_boundary, '-', 28); + uint64_t boundary_rand; + sys_read(ufd, &boundary_rand, sizeof(boundary_rand)); + write_uint64_hex(mime_boundary + 28, boundary_rand); + mime_boundary[28 + 16] = 0; + IGNORE_RET(sys_close(ufd)); + + // The MIME block looks like this: + // BOUNDARY \r\n + // Content-Disposition: form-data; name="prod" \r\n \r\n + // Chrome_Linux \r\n + // BOUNDARY \r\n + // Content-Disposition: form-data; name="ver" \r\n \r\n + // 1.2.3.4 \r\n + // BOUNDARY \r\n + // Content-Disposition: form-data; name="guid" \r\n \r\n + // 1.2.3.4 \r\n + // BOUNDARY \r\n + // + // zero or one: + // Content-Disposition: form-data; name="ptime" \r\n \r\n + // abcdef \r\n + // BOUNDARY \r\n + // + // zero or one: + // Content-Disposition: form-data; name="ptype" \r\n \r\n + // abcdef \r\n + // BOUNDARY \r\n + // + // zero or more gpu entries: + // Content-Disposition: form-data; name="gpu-xxxxx" \r\n \r\n + // \r\n + // BOUNDARY \r\n + // + // zero or one: + // Content-Disposition: form-data; name="lsb-release" \r\n \r\n + // abcdef \r\n + // BOUNDARY \r\n + // + // zero or more: + // Content-Disposition: form-data; name="url-chunk-1" \r\n \r\n + // abcdef \r\n + // BOUNDARY \r\n + // + // zero or one: + // Content-Disposition: form-data; name="channel" \r\n \r\n + // beta \r\n + // BOUNDARY \r\n + // + // zero or one: + // Content-Disposition: form-data; name="num-views" \r\n \r\n + // 3 \r\n + // BOUNDARY \r\n + // + // zero or one: + // Content-Disposition: form-data; name="num-extensions" \r\n \r\n + // 5 \r\n + // BOUNDARY \r\n + // + // zero to 10: + // Content-Disposition: form-data; name="extension-1" \r\n \r\n + // abcdefghijklmnopqrstuvwxyzabcdef \r\n + // BOUNDARY \r\n + // + // zero to 4: + // Content-Disposition: form-data; name="prn-info-1" \r\n \r\n + // abcdefghijklmnopqrstuvwxyzabcdef \r\n + // BOUNDARY \r\n + // + // zero or one: + // Content-Disposition: form-data; name="num-switches" \r\n \r\n + // 5 \r\n + // BOUNDARY \r\n + // + // zero to 15: + // Content-Disposition: form-data; name="switch-1" \r\n \r\n + // --foo \r\n + // BOUNDARY \r\n + // + // zero or one: + // Content-Disposition: form-data; name="oom-size" \r\n \r\n + // 1234567890 \r\n + // BOUNDARY \r\n + // + // zero or more (up to CrashKeyStorage::num_entries = 64): + // Content-Disposition: form-data; name=crash-key-name \r\n + // crash-key-value \r\n + // BOUNDARY \r\n + // + // Content-Disposition: form-data; name="dump"; filename="dump" \r\n + // Content-Type: application/octet-stream \r\n \r\n + // + // \r\n BOUNDARY -- \r\n + +#if defined(OS_CHROMEOS) + CrashReporterWriter writer(temp_file_fd); +#else + MimeWriter writer(temp_file_fd, mime_boundary); +#endif + writer.AddItem(dump_data, dump_size); + writer.Flush(); + + IGNORE_RET(sys_close(temp_file_fd)); + + LOG(ERROR) << "crash dump file written to " << info.filename; + + if (!info.upload) + return; + + const pid_t child = sys_fork(); + if (!child) { + // Spawned helper process. + // + // This code is called both when a browser is crashing (in which case, + // nothing really matters any more) and when a renderer/plugin crashes, in + // which case we need to continue. + // + // Since we are a multithreaded app, if we were just to fork(), we might + // grab file descriptors which have just been created in another thread and + // hold them open for too long. + // + // Thus, we have to loop and try and close everything. + const int fd = sys_open("/proc/self/fd", O_DIRECTORY | O_RDONLY, 0); + if (fd < 0) { + for (unsigned i = 3; i < 8192; ++i) + IGNORE_RET(sys_close(i)); + } else { + google_breakpad::DirectoryReader reader(fd); + const char* name; + while (reader.GetNextEntry(&name)) { + int i; + if (my_strtoui(&i, name) && i > 2 && i != fd) + IGNORE_RET(sys_close(i)); + reader.PopEntry(); + } + + IGNORE_RET(sys_close(fd)); + } + + IGNORE_RET(sys_setsid()); + + // Leave one end of a pipe in the upload process and watch for it getting + // closed by the upload process exiting. + int fds[2]; + if (sys_pipe(fds) >= 0) { + const pid_t upload_child = sys_fork(); + if (!upload_child) { + // Upload process. + IGNORE_RET(sys_close(fds[0])); + IGNORE_RET(sys_dup2(fds[1], 3)); + ExecUploadProcessOrTerminate(info, temp_file, mime_boundary, exe_buf, + &allocator); + } + + // Helper process. + if (upload_child > 0) { + IGNORE_RET(sys_close(fds[1])); + char id_buf[17]; // Crash report IDs are expected to be 16 chars. + ssize_t len = -1; + // Upload should finish in about 10 seconds. Add a few more 500 ms + // internals to account for process startup time. + for (size_t wait_count = 0; wait_count < 24; ++wait_count) { + struct kernel_pollfd poll_fd; + poll_fd.fd = fds[0]; + poll_fd.events = POLLIN | POLLPRI | POLLERR; + int ret = sys_poll(&poll_fd, 1, 500); + if (ret < 0) { + // Error + break; + } else if (ret > 0) { + // There is data to read. + len = HANDLE_EINTR(sys_read(fds[0], id_buf, sizeof(id_buf) - 1)); + break; + } + // ret == 0 -> timed out, continue waiting. + } + if (len > 0) { + // Write crash dump id to stderr. + id_buf[len] = 0; + static const char msg[] = "\nCrash dump id: "; + WriteLog(msg, sizeof(msg) - 1); + WriteLog(id_buf, my_strlen(id_buf)); + WriteLog("\n", 1); + + // Write crash dump id to crash log as: seconds_since_epoch,crash_id + struct kernel_timeval tv; + if (g_crash_log_path && !sys_gettimeofday(&tv, NULL)) { + uint64_t time = kernel_timeval_to_ms(&tv) / 1000; + char time_str[kUint64StringSize]; + const unsigned time_len = my_uint64_len(time); + my_uint64tos(time_str, time, time_len); + + int log_fd = sys_open(g_crash_log_path, + O_CREAT | O_WRONLY | O_APPEND, + 0600); + if (log_fd > 0) { + sys_write(log_fd, time_str, time_len); + sys_write(log_fd, ",", 1); + sys_write(log_fd, id_buf, my_strlen(id_buf)); + sys_write(log_fd, "\n", 1); + IGNORE_RET(sys_close(log_fd)); + } + } + } + if (sys_waitpid(upload_child, NULL, WNOHANG) == 0) { + // Upload process is still around, kill it. + sys_kill(upload_child, SIGKILL); + } + } + } + + // Helper process. + // IGNORE_RET(sys_unlink(info.filename)); +#if defined(ADDRESS_SANITIZER) + IGNORE_RET(sys_unlink(info.log_filename)); +#endif + // IGNORE_RET(sys_unlink(temp_file)); + sys__exit(0); + } + + // Main browser process. + if (child <= 0) + return; + (void) HANDLE_EINTR(sys_waitpid(child, NULL, 0)); +} + +void InitCrashReporter() { +#if defined(OS_ANDROID) + // This will guarantee that the BuildInfo has been initialized and subsequent + // calls will not require memory allocation. + base::android::BuildInfo::GetInstance(); +#endif + // Determine the process type and take appropriate action. + const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess(); + if (parsed_command_line.HasSwitch(switches::kDisableBreakpad)) + return; + + const std::string process_type = + parsed_command_line.GetSwitchValueASCII(switches::kProcessType); + if (process_type.empty()) { + EnableCrashDumping(breakpad::GetBreakpadClient()->IsRunningUnattended()); + } else if (process_type == switches::kRendererProcess || + process_type == switches::kPluginProcess || + process_type == switches::kPpapiPluginProcess || + process_type == switches::kZygoteProcess || + process_type == switches::kGpuProcess) { +#if defined(OS_ANDROID) + NOTREACHED() << "Breakpad initialized with InitCrashReporter() instead of " + "InitNonBrowserCrashReporter in " << process_type << " process."; + return; +#else + // We might be chrooted in a zygote or renderer process so we cannot call + // GetCollectStatsConsent because that needs access the the user's home + // dir. Instead, we set a command line flag for these processes. + // Even though plugins are not chrooted, we share the same code path for + // simplicity. + if (!parsed_command_line.HasSwitch(switches::kEnableCrashReporter)) + return; + SetClientIdFromCommandLine(parsed_command_line); + EnableNonBrowserCrashDumping(); + VLOG(1) << "Non Browser crash dumping enabled for: " << process_type; +#endif // #if defined(OS_ANDROID) + } + + SetProcessStartTime(); + + base::debug::SetDumpWithoutCrashingFunction(&DumpProcess); +#if defined(ADDRESS_SANITIZER) + // Register the callback for AddressSanitizer error reporting. + __asan_set_error_report_callback(AsanLinuxBreakpadCallback); +#endif + + g_crash_keys = new CrashKeyStorage; + breakpad::GetBreakpadClient()->RegisterCrashKeys(); + base::debug::SetCrashKeyReportingFunctions( + &SetCrashKeyValue, &ClearCrashKey); +} + +#if defined(OS_ANDROID) +void InitNonBrowserCrashReporterForAndroid() { + const CommandLine* command_line = CommandLine::ForCurrentProcess(); + if (command_line->HasSwitch(switches::kEnableCrashReporter)) { + // On Android we need to provide a FD to the file where the minidump is + // generated as the renderer and browser run with different UIDs + // (preventing the browser from inspecting the renderer process). + int minidump_fd = base::GlobalDescriptors::GetInstance()-> + MaybeGet(breakpad::GetBreakpadClient()->GetAndroidMinidumpDescriptor()); + if (minidump_fd == base::kInvalidPlatformFileValue) { + NOTREACHED() << "Could not find minidump FD, crash reporting disabled."; + } else { + EnableNonBrowserCrashDumping(minidump_fd); + } + } +} +#endif // OS_ANDROID + +bool IsCrashReporterEnabled() { + return g_is_crash_reporter_enabled; +} + +} // namespace breakpad + +bool SetCrashDumpPath(const char* path) { + if (!breakpad::g_breakpad) + return false; + breakpad::g_breakpad->set_minidump_descriptor(MinidumpDescriptor(path)); + base::FilePath filepath(path); + // for renderer dumps + PathService::Override(chrome::DIR_CRASH_DUMPS, filepath); + return true; +} diff --git a/src/breakpad_linux.h b/src/breakpad_linux.h new file mode 100644 index 0000000000..595d33bdfb --- /dev/null +++ b/src/breakpad_linux.h @@ -0,0 +1,30 @@ +// 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. + +// Public interface for enabling Breakpad on Linux systems. + +#ifndef CHROME_APP_BREAKPAD_LINUX_H_ +#define CHROME_APP_BREAKPAD_LINUX_H_ + +#include "build/build_config.h" + +namespace breakpad { + +// Turns on the crash reporter in any process. +extern void InitCrashReporter(); + +// Enables the crash reporter in child processes. +#if defined(OS_ANDROID) +extern void InitNonBrowserCrashReporterForAndroid(); +#endif + +// Checks if crash reporting is enabled. Note that this is not the same as +// being opted into metrics reporting (and crash reporting), which controls +// whether InitCrashReporter() is called. +bool IsCrashReporterEnabled(); + +} // namespace breakpad + +bool SetCrashDumpPath(const char* path); +#endif // CHROME_APP_BREAKPAD_LINUX_H_ diff --git a/src/breakpad_mac.h b/src/breakpad_mac.h new file mode 100644 index 0000000000..8bb433545f --- /dev/null +++ b/src/breakpad_mac.h @@ -0,0 +1,23 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_APP_BREAKPAD_MAC_H_ +#define CHROME_APP_BREAKPAD_MAC_H_ + +namespace breakpad { +// This header defines the Chrome entry points for Breakpad integration. + +// Initializes Breakpad. +void InitCrashReporter(); + +// Give Breakpad a chance to store information about the current process. +// Extra information requires a parsed command line, so call this after +// CommandLine::Init has been called. +void InitCrashProcessInfo(); + +// Is Breakpad enabled? +bool IsCrashReporterEnabled(); +} + +#endif // CHROME_APP_BREAKPAD_MAC_H_ diff --git a/src/breakpad_mac.mm b/src/breakpad_mac.mm new file mode 100644 index 0000000000..25b0282831 --- /dev/null +++ b/src/breakpad_mac.mm @@ -0,0 +1,302 @@ +// 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. + +#import "components/breakpad/app/breakpad_mac.h" + +#include +#import + +#include "base/auto_reset.h" +#include "base/base_switches.h" +#import "base/basictypes.h" +#include "base/command_line.h" +#include "base/debug/crash_logging.h" +#include "base/debug/dump_without_crashing.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#import "base/logging.h" +#include "base/mac/bundle_locations.h" +#include "base/mac/mac_util.h" +#include "base/mac/scoped_cftyperef.h" +#import "base/mac/scoped_nsautorelease_pool.h" +#include "base/strings/sys_string_conversions.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread_restrictions.h" +#import "breakpad/src/client/mac/Framework/Breakpad.h" +#include "chrome/common/child_process_logging.h" +#include "content/public/common/content_switches.h" +#include "components/breakpad/app/breakpad_client.h" +#include "content/nw/src/paths_mac.h" +//#include "policy/policy_constants.h" + + +namespace { + +BreakpadRef gBreakpadRef = NULL; + +void SetCrashKeyValue(NSString* key, NSString* value) { + // Comment repeated from header to prevent confusion: + // IMPORTANT: On OS X, the key/value pairs are sent to the crash server + // out of bounds and not recorded on disk in the minidump, this means + // that if you look at the minidump file locally you won't see them! + if (gBreakpadRef == NULL) { + return; + } + + BreakpadAddUploadParameter(gBreakpadRef, key, value); +} + +void ClearCrashKeyValue(NSString* key) { + if (gBreakpadRef == NULL) { + return; + } + + BreakpadRemoveUploadParameter(gBreakpadRef, key); +} + +void SetCrashKeyValueImpl(const base::StringPiece& key, + const base::StringPiece& value) { + SetCrashKeyValue(base::SysUTF8ToNSString(key.as_string()), + base::SysUTF8ToNSString(value.as_string())); +} + +void ClearCrashKeyValueImpl(const base::StringPiece& key) { + ClearCrashKeyValue(base::SysUTF8ToNSString(key.as_string())); +} + +bool FatalMessageHandler(int severity, const char* file, int line, + size_t message_start, const std::string& str) { + // Do not handle non-FATAL. + if (severity != logging::LOG_FATAL) + return false; + + // In case of OOM condition, this code could be reentered when + // constructing and storing the key. Using a static is not + // thread-safe, but if multiple threads are in the process of a + // fatal crash at the same time, this should work. + static bool guarded = false; + if (guarded) + return false; + + base::AutoReset guard(&guarded, true); + + // Only log last path component. This matches logging.cc. + if (file) { + const char* slash = strrchr(file, '/'); + if (slash) + file = slash + 1; + } + + NSString* fatal_key = @"LOG_FATAL"; + NSString* fatal_value = + [NSString stringWithFormat:@"%s:%d: %s", + file, line, str.c_str() + message_start]; + SetCrashKeyValue(fatal_key, fatal_value); + + // Rather than including the code to force the crash here, allow the + // caller to do it. + return false; +} + +// BreakpadGenerateAndSendReport() does not report the current +// thread. This class can be used to spin up a thread to run it. +class DumpHelper : public base::PlatformThread::Delegate { + public: + static void DumpWithoutCrashing() { + DumpHelper dumper; + base::PlatformThreadHandle handle; + if (base::PlatformThread::Create(0, &dumper, &handle)) { + // The entire point of this is to block so that the correct + // stack is logged. + base::ThreadRestrictions::ScopedAllowIO allow_io; + base::PlatformThread::Join(handle); + } + } + + private: + DumpHelper() {} + + virtual void ThreadMain() OVERRIDE { + base::PlatformThread::SetName("CrDumpHelper"); + BreakpadGenerateAndSendReport(gBreakpadRef); + } + + DISALLOW_COPY_AND_ASSIGN(DumpHelper); +}; + +void SIGABRTHandler(int signal) { + // The OSX abort() (link below) masks all signals for the process, + // and all except SIGABRT for the thread. SIGABRT will be masked + // when the SIGABRT is sent, which means at this point only SIGKILL + // and SIGSTOP can be delivered. Unmask others so that the code + // below crashes as desired. + // + // http://www.opensource.apple.com/source/Libc/Libc-825.26/stdlib/FreeBSD/abort.c + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, signal); + pthread_sigmask(SIG_SETMASK, &mask, NULL); + + // Most interesting operations are not safe in a signal handler, just crash. + char* volatile death_ptr = NULL; + *death_ptr = '!'; +} + +} // namespace + +bool IsCrashReporterEnabled() { + return gBreakpadRef != NULL; +} + +namespace breakpad { + +// Only called for a branded build of Chrome.app. +void InitCrashReporter() { + DCHECK(!gBreakpadRef); + base::mac::ScopedNSAutoreleasePool autorelease_pool; + + // Check whether crash reporting should be enabled. If enterprise + // configuration management controls crash reporting, it takes precedence. + // Otherwise, check whether the user has consented to stats and crash + // reporting. The browser process can make this determination directly. + // Helper processes may not have access to the disk or to the same data as + // the browser process, so the browser passes the decision to them on the + // command line. + NSBundle* main_bundle = base::mac::MainBundle(); + bool is_browser = !base::mac::IsBackgroundOnlyProcess(); + bool enable_breakpad = false; + CommandLine* command_line = CommandLine::ForCurrentProcess(); + + if (is_browser) { + // Controlled by the user. The crash reporter may be enabled by + // preference or through an environment variable, but the kDisableBreakpad + // switch overrides both. + enable_breakpad = + breakpad::GetBreakpadClient()->GetCollectStatsConsent() || + breakpad::GetBreakpadClient()->IsRunningUnattended(); + enable_breakpad &= !command_line->HasSwitch(switches::kDisableBreakpad); + } else { + // This is a helper process, check the command line switch. + enable_breakpad = command_line->HasSwitch(switches::kEnableCrashReporter); + } + + if (!enable_breakpad) { + VLOG_IF(1, is_browser) << "Breakpad disabled"; + return; + } + + // Tell Breakpad where crash_inspector and crash_report_sender are. + NSString* resource_path = [[NSString alloc] initWithUTF8String:GetFrameworksPath().value().c_str()]; + NSString *inspector_location = + [resource_path stringByAppendingPathComponent:@"crash_inspector"]; +#if 0 + NSString *reporter_bundle_location = + [resource_path stringByAppendingPathComponent:@"crash_report_sender.app"]; + NSString *reporter_location = + [[NSBundle bundleWithPath:reporter_bundle_location] executablePath]; +#endif + + VLOG(1) << "resource_path: " << [resource_path UTF8String]; + VLOG(1) << "inspector_location: " << [inspector_location UTF8String]; + + if (!inspector_location) { + VLOG_IF(1, is_browser && base::mac::AmIBundled()) << "Breakpad disabled"; + return; + } + + NSDictionary* info_dictionary = [main_bundle infoDictionary]; + NSMutableDictionary *breakpad_config = + [[info_dictionary mutableCopy] autorelease]; + [breakpad_config setObject:inspector_location + forKey:@BREAKPAD_INSPECTOR_LOCATION]; +#if 0 + [breakpad_config setObject:reporter_location + forKey:@BREAKPAD_REPORTER_EXE_LOCATION]; +#endif + + // In the main application (the browser process), crashes can be passed to + // the system's Crash Reporter. This allows the system to notify the user + // when the application crashes, and provide the user with the option to + // restart it. + if (is_browser) + [breakpad_config setObject:@"NO" forKey:@BREAKPAD_SEND_AND_EXIT]; + + base::FilePath dir_crash_dumps; + breakpad::GetBreakpadClient()->GetCrashDumpLocation(&dir_crash_dumps); + [breakpad_config setObject:base::SysUTF8ToNSString(dir_crash_dumps.value()) + forKey:@BREAKPAD_DUMP_DIRECTORY]; + + VLOG(1) << "dir_crash_dumps: " << dir_crash_dumps.value(); + if (![breakpad_config objectForKey:@BREAKPAD_URL]) { + [breakpad_config setObject:@"https://clients2.google.com/cr/report" + forKey:@BREAKPAD_URL]; + } + // Initialize Breakpad. + gBreakpadRef = BreakpadCreate(breakpad_config); + if (!gBreakpadRef) { + LOG_IF(ERROR, base::mac::AmIBundled()) << "Breakpad initialization failed"; + return; + } + + // Initialize the scoped crash key system. + base::debug::SetCrashKeyReportingFunctions(&SetCrashKeyValueImpl, + &ClearCrashKeyValueImpl); + breakpad::GetBreakpadClient()->RegisterCrashKeys(); + + // Set Breakpad metadata values. These values are added to Info.plist during + // the branded Google Chrome.app build. + SetCrashKeyValue(@"ver", [info_dictionary objectForKey:@BREAKPAD_VERSION]); + SetCrashKeyValue(@"prod", [info_dictionary objectForKey:@BREAKPAD_PRODUCT]); + SetCrashKeyValue(@"plat", @"OS X"); + + if (!is_browser) { + // Get the guid from the command line switch. + std::string guid = + command_line->GetSwitchValueASCII(switches::kEnableCrashReporter); + breakpad::GetBreakpadClient()->SetClientID(guid); + } + + logging::SetLogMessageHandler(&FatalMessageHandler); + base::debug::SetDumpWithoutCrashingFunction( + &DumpHelper::DumpWithoutCrashing); + + // abort() sends SIGABRT, which breakpad does not intercept. + // Register a signal handler to crash in a way breakpad will + // intercept. + struct sigaction sigact; + memset(&sigact, 0, sizeof(sigact)); + sigact.sa_handler = SIGABRTHandler; + CHECK(0 == sigaction(SIGABRT, &sigact, NULL)); +} + +} //namespace breakpad + +void InitCrashProcessInfo() { + if (gBreakpadRef == NULL) { + return; + } + + // Determine the process type. + NSString* process_type = @"browser"; + std::string process_type_switch = + CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kProcessType); + if (!process_type_switch.empty()) { + process_type = base::SysUTF8ToNSString(process_type_switch); + } + + breakpad::GetBreakpadClient()->InstallAdditionalFilters(gBreakpadRef); + + // Store process type in crash dump. + SetCrashKeyValue(@"ptype", process_type); +} + +bool SetCrashDumpPath(const char* path) { + if (!gBreakpadRef) + return false; + BreakpadSetKeyValue(gBreakpadRef, @BREAKPAD_DUMP_DIRECTORY, base::SysUTF8ToNSString(path)); + return true; +} + + diff --git a/src/breakpad_mac_stubs.mm b/src/breakpad_mac_stubs.mm new file mode 100644 index 0000000000..f8a651ab5b --- /dev/null +++ b/src/breakpad_mac_stubs.mm @@ -0,0 +1,20 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "chrome/app/breakpad_mac.h" + +#import + +// Stubbed out versions of breakpad integration functions so we can compile +// without linking in Breakpad. + +bool IsCrashReporterEnabled() { + return false; +} + +void InitCrashProcessInfo() { +} + +void InitCrashReporter() { +} diff --git a/src/breakpad_win.cc b/src/breakpad_win.cc new file mode 100644 index 0000000000..65ddf4016b --- /dev/null +++ b/src/breakpad_win.cc @@ -0,0 +1,784 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/breakpad/app/breakpad_win.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "base/base_switches.h" +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/debug/crash_logging.h" +#include "base/environment.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/win/metro.h" +#include "base/win/pe_image.h" +#include "base/win/registry.h" +#include "base/win/win_util.h" +#include "breakpad/src/client/windows/handler/exception_handler.h" +#include "components/breakpad/app/breakpad_client.h" +#include "components/breakpad/app/hard_error_handler_win.h" +#include "content/public/common/content_switches.h" +#include "content/public/common/result_codes.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sidestep/preamble_patcher.h" + +// userenv.dll is required for GetProfileType(). +#pragma comment(lib, "userenv.lib") + +#pragma intrinsic(_AddressOfReturnAddress) +#pragma intrinsic(_ReturnAddress) + +namespace breakpad { + +// TODO(raymes): Modify the way custom crash info is stored. g_custom_entries +// is way too too fragile. See +// https://code.google.com/p/chromium/issues/detail?id=137062. +std::vector* g_custom_entries = NULL; +bool g_deferred_crash_uploads = false; +google_breakpad::ExceptionHandler* g_breakpad = NULL; + +namespace { + +// Minidump with stacks, PEB, TEB, and unloaded module list. +const MINIDUMP_TYPE kSmallDumpType = static_cast( + MiniDumpWithProcessThreadData | // Get PEB and TEB. + MiniDumpWithUnloadedModules); // Get unloaded modules when available. + +// Minidump with all of the above, plus memory referenced from stack. +const MINIDUMP_TYPE kLargerDumpType = static_cast( + MiniDumpWithProcessThreadData | // Get PEB and TEB. + MiniDumpWithUnloadedModules | // Get unloaded modules when available. + MiniDumpWithIndirectlyReferencedMemory); // Get memory referenced by stack. + +// Large dump with all process memory. +const MINIDUMP_TYPE kFullDumpType = static_cast( + MiniDumpWithFullMemory | // Full memory from process. + MiniDumpWithProcessThreadData | // Get PEB and TEB. + MiniDumpWithHandleData | // Get all handle information. + MiniDumpWithUnloadedModules); // Get unloaded modules when available. + +const char kPipeNameVar[] = "CHROME_BREAKPAD_PIPE_NAME"; + +const wchar_t kGoogleUpdatePipeName[] = L"\\\\.\\pipe\\GoogleCrashServices\\"; +const wchar_t kChromePipeName[] = L"\\\\.\\pipe\\ChromeCrashServices"; + +// This is the well known SID for the system principal. +const wchar_t kSystemPrincipalSid[] =L"S-1-5-18"; + +google_breakpad::ExceptionHandler* g_dumphandler_no_crash = NULL; + +EXCEPTION_POINTERS g_surrogate_exception_pointers = {0}; +EXCEPTION_RECORD g_surrogate_exception_record = {0}; +CONTEXT g_surrogate_context = {0}; + +typedef NTSTATUS (WINAPI* NtTerminateProcessPtr)(HANDLE ProcessHandle, + NTSTATUS ExitStatus); +char* g_real_terminate_process_stub = NULL; + +static size_t g_dynamic_keys_offset = 0; +typedef std::map + DynamicEntriesMap; +DynamicEntriesMap* g_dynamic_entries = NULL; +// Allow for 128 entries. POSIX uses 64 entries of 256 bytes, so Windows needs +// 256 entries of 64 bytes to match. See CustomInfoEntry::kValueMaxLength in +// Breakpad. +const size_t kMaxDynamicEntries = 256; + +// Maximum length for plugin path to include in plugin crash reports. +const size_t kMaxPluginPathLength = 256; + +// Dumps the current process memory. +extern "C" void __declspec(dllexport) __cdecl DumpProcess() { + if (g_breakpad) { + g_breakpad->WriteMinidump(); + } +} + +// Used for dumping a process state when there is no crash. +extern "C" void __declspec(dllexport) __cdecl DumpProcessWithoutCrash() { + if (g_dumphandler_no_crash) { + g_dumphandler_no_crash->WriteMinidump(); + } +} + +// We need to prevent ICF from folding DumpForHangDebuggingThread() and +// DumpProcessWithoutCrashThread() together, since that makes them +// indistinguishable in crash dumps. We do this by making the function +// bodies unique, and prevent optimization from shuffling things around. +MSVC_DISABLE_OPTIMIZE() +MSVC_PUSH_DISABLE_WARNING(4748) + +DWORD WINAPI DumpProcessWithoutCrashThread(void*) { + DumpProcessWithoutCrash(); + return 0; +} + +// The following two functions do exactly the same thing as the two above. But +// we want the signatures to be different so that we can easily track them in +// crash reports. +// TODO(yzshen): Remove when enough information is collected and the hang rate +// of pepper/renderer processes is reduced. +DWORD WINAPI DumpForHangDebuggingThread(void*) { + DumpProcessWithoutCrash(); + LOG(INFO) << "dumped for hang debugging"; + return 0; +} + +MSVC_POP_WARNING() +MSVC_ENABLE_OPTIMIZE() + +// Injects a thread into a remote process to dump state when there is no crash. +extern "C" HANDLE __declspec(dllexport) __cdecl +InjectDumpProcessWithoutCrash(HANDLE process) { + return CreateRemoteThread(process, NULL, 0, DumpProcessWithoutCrashThread, + 0, 0, NULL); +} + +extern "C" HANDLE __declspec(dllexport) __cdecl +InjectDumpForHangDebugging(HANDLE process) { + return CreateRemoteThread(process, NULL, 0, DumpForHangDebuggingThread, + 0, 0, NULL); +} + +extern "C" void DumpProcessAbnormalSignature() { + if (!g_breakpad) + return; + g_custom_entries->push_back( + google_breakpad::CustomInfoEntry(L"unusual-crash-signature", L"")); + g_breakpad->WriteMinidump(); +} + +// Reduces the size of the string |str| to a max of 64 chars. Required because +// breakpad's CustomInfoEntry raises an invalid_parameter error if the string +// we want to set is longer. +std::wstring TrimToBreakpadMax(const std::wstring& str) { + std::wstring shorter(str); + return shorter.substr(0, + google_breakpad::CustomInfoEntry::kValueMaxLength - 1); +} + +static void SetIntegerValue(size_t offset, int value) { + if (!g_custom_entries) + return; + + base::wcslcpy((*g_custom_entries)[offset].value, + base::StringPrintf(L"%d", value).c_str(), + google_breakpad::CustomInfoEntry::kValueMaxLength); +} + +// Appends the plugin path to |g_custom_entries|. +void SetPluginPath(const std::wstring& path) { + DCHECK(g_custom_entries); + + if (path.size() > kMaxPluginPathLength) { + // If the path is too long, truncate from the start rather than the end, + // since we want to be able to recover the DLL name. + SetPluginPath(path.substr(path.size() - kMaxPluginPathLength)); + return; + } + + // The chunk size without terminator. + const size_t kChunkSize = static_cast( + google_breakpad::CustomInfoEntry::kValueMaxLength - 1); + + int chunk_index = 0; + size_t chunk_start = 0; // Current position inside |path| + + for (chunk_start = 0; chunk_start < path.size(); chunk_index++) { + size_t chunk_length = std::min(kChunkSize, path.size() - chunk_start); + + g_custom_entries->push_back(google_breakpad::CustomInfoEntry( + base::StringPrintf(L"plugin-path-chunk-%i", chunk_index + 1).c_str(), + path.substr(chunk_start, chunk_length).c_str())); + + chunk_start += chunk_length; + } +} + +// Appends the breakpad dump path to |g_custom_entries|. +void SetBreakpadDumpPath() { + DCHECK(g_custom_entries); + base::FilePath crash_dumps_dir_path; + if (GetBreakpadClient()->GetAlternativeCrashDumpLocation( + &crash_dumps_dir_path)) { + g_custom_entries->push_back(google_breakpad::CustomInfoEntry( + L"breakpad-dump-location", crash_dumps_dir_path.value().c_str())); + } +} + +// Returns a string containing a list of all modifiers for the loaded profile. +std::wstring GetProfileType() { + std::wstring profile_type; + DWORD profile_bits = 0; + if (::GetProfileType(&profile_bits)) { + static const struct { + DWORD bit; + const wchar_t* name; + } kBitNames[] = { + { PT_MANDATORY, L"mandatory" }, + { PT_ROAMING, L"roaming" }, + { PT_TEMPORARY, L"temporary" }, + }; + for (size_t i = 0; i < arraysize(kBitNames); ++i) { + const DWORD this_bit = kBitNames[i].bit; + if ((profile_bits & this_bit) != 0) { + profile_type.append(kBitNames[i].name); + profile_bits &= ~this_bit; + if (profile_bits != 0) + profile_type.append(L", "); + } + } + } else { + DWORD last_error = ::GetLastError(); + base::SStringPrintf(&profile_type, L"error %u", last_error); + } + return profile_type; +} + +// Returns the custom info structure based on the dll in parameter and the +// process type. +google_breakpad::CustomClientInfo* GetCustomInfo(const std::wstring& exe_path, + const std::wstring& type) { + base::string16 version, product; + base::string16 special_build; + base::string16 channel_name; + GetBreakpadClient()->GetProductNameAndVersion( + base::FilePath(exe_path), + &product, + &version, + &special_build, + &channel_name); + + // We only expect this method to be called once per process. + DCHECK(!g_custom_entries); + g_custom_entries = new std::vector; + + // Common g_custom_entries. + g_custom_entries->push_back( + google_breakpad::CustomInfoEntry(L"ver", base::UTF16ToWide(version).c_str())); + g_custom_entries->push_back( + google_breakpad::CustomInfoEntry(L"prod", base::UTF16ToWide(product).c_str())); + g_custom_entries->push_back( + google_breakpad::CustomInfoEntry(L"plat", L"Win32")); + g_custom_entries->push_back( + google_breakpad::CustomInfoEntry(L"ptype", type.c_str())); + g_custom_entries->push_back(google_breakpad::CustomInfoEntry( + L"channel", base::UTF16ToWide(channel_name).c_str())); + g_custom_entries->push_back(google_breakpad::CustomInfoEntry( + L"profile-type", GetProfileType().c_str())); + + if (g_deferred_crash_uploads) + g_custom_entries->push_back( + google_breakpad::CustomInfoEntry(L"deferred-upload", L"true")); + + if (!special_build.empty()) + g_custom_entries->push_back(google_breakpad::CustomInfoEntry( + L"special", base::UTF16ToWide(special_build).c_str())); + + if (type == L"plugin" || type == L"ppapi") { + std::wstring plugin_path = + CommandLine::ForCurrentProcess()->GetSwitchValueNative("plugin-path"); + if (!plugin_path.empty()) + SetPluginPath(plugin_path); + } + + // Check whether configuration management controls crash reporting. + bool crash_reporting_enabled = true; + bool controlled_by_policy = GetBreakpadClient()->ReportingIsEnforcedByPolicy( + &crash_reporting_enabled); + const CommandLine& command = *CommandLine::ForCurrentProcess(); + bool use_crash_service = + !controlled_by_policy && (command.HasSwitch(switches::kNoErrorDialogs) || + GetBreakpadClient()->IsRunningUnattended()); + if (use_crash_service) + SetBreakpadDumpPath(); + + // Create space for dynamic ad-hoc keys. The names and values are set using + // the API defined in base/debug/crash_logging.h. + g_dynamic_keys_offset = g_custom_entries->size(); + for (size_t i = 0; i < kMaxDynamicEntries; ++i) { + // The names will be mutated as they are set. Un-numbered since these are + // merely placeholders. The name cannot be empty because Breakpad's + // HTTPUpload will interpret that as an invalid parameter. + g_custom_entries->push_back( + google_breakpad::CustomInfoEntry(L"unspecified-crash-key", L"")); + } + g_dynamic_entries = new DynamicEntriesMap; + + static google_breakpad::CustomClientInfo custom_client_info; + custom_client_info.entries = &g_custom_entries->front(); + custom_client_info.count = g_custom_entries->size(); + + return &custom_client_info; +} + +// This callback is used when we want to get a dump without crashing the +// process. +bool DumpDoneCallbackWhenNoCrash(const wchar_t*, const wchar_t*, void*, + EXCEPTION_POINTERS* ex_info, + MDRawAssertionInfo*, bool) { + return true; +} + +// This callback is executed when the browser process has crashed, after +// the crash dump has been created. We need to minimize the amount of work +// done here since we have potentially corrupted process. Our job is to +// spawn another instance of chrome which will show a 'chrome has crashed' +// dialog. This code needs to live in the exe and thus has no access to +// facilities such as the i18n helpers. +bool DumpDoneCallback(const wchar_t*, const wchar_t*, void*, + EXCEPTION_POINTERS* ex_info, + MDRawAssertionInfo*, bool) { + // Check if the exception is one of the kind which would not be solved + // by simply restarting chrome. In this case we show a message box with + // and exit silently. Remember that chrome is in a crashed state so we + // can't show our own UI from this process. + if (HardErrorHandler(ex_info)) + return true; + + if (!GetBreakpadClient()->AboutToRestart()) + return true; + + // Now we just start chrome browser with the same command line. + STARTUPINFOW si = {sizeof(si)}; + PROCESS_INFORMATION pi; + if (::CreateProcessW(NULL, ::GetCommandLineW(), NULL, NULL, FALSE, + CREATE_UNICODE_ENVIRONMENT, NULL, NULL, &si, &pi)) { + ::CloseHandle(pi.hProcess); + ::CloseHandle(pi.hThread); + } + // After this return we will be terminated. The actual return value is + // not used at all. + return true; +} + +// flag to indicate that we are already handling an exception. +volatile LONG handling_exception = 0; + +// This callback is used when there is no crash. Note: Unlike the +// |FilterCallback| below this does not do dupe detection. It is upto the caller +// to implement it. +bool FilterCallbackWhenNoCrash( + void*, EXCEPTION_POINTERS*, MDRawAssertionInfo*) { + GetBreakpadClient()->RecordCrashDumpAttempt(false); + return true; +} + +// This callback is executed when the Chrome process has crashed and *before* +// the crash dump is created. To prevent duplicate crash reports we +// make every thread calling this method, except the very first one, +// go to sleep. +bool FilterCallback(void*, EXCEPTION_POINTERS*, MDRawAssertionInfo*) { + // Capture every thread except the first one in the sleep. We don't + // want multiple threads to concurrently report exceptions. + if (::InterlockedCompareExchange(&handling_exception, 1, 0) == 1) { + ::Sleep(INFINITE); + } + GetBreakpadClient()->RecordCrashDumpAttempt(true); + return true; +} + +// Previous unhandled filter. Will be called if not null when we +// intercept a crash. +LPTOP_LEVEL_EXCEPTION_FILTER previous_filter = NULL; + +// Exception filter used when breakpad is not enabled. We just display +// the "Do you want to restart" message and then we call the previous filter. +long WINAPI ChromeExceptionFilter(EXCEPTION_POINTERS* info) { + DumpDoneCallback(NULL, NULL, NULL, info, NULL, false); + + if (previous_filter) + return previous_filter(info); + + return EXCEPTION_EXECUTE_HANDLER; +} + +// Exception filter for the service process used when breakpad is not enabled. +// We just display the "Do you want to restart" message and then die +// (without calling the previous filter). +long WINAPI ServiceExceptionFilter(EXCEPTION_POINTERS* info) { + DumpDoneCallback(NULL, NULL, NULL, info, NULL, false); + return EXCEPTION_EXECUTE_HANDLER; +} + +// NOTE: This function is used by SyzyASAN to annotate crash reports. If you +// change the name or signature of this function you will break SyzyASAN +// instrumented releases of Chrome. Please contact syzygy-team@chromium.org +// before doing so! +extern "C" void __declspec(dllexport) __cdecl SetCrashKeyValueImpl( + const wchar_t* key, const wchar_t* value) { + if (!g_dynamic_entries) + return; + + // CustomInfoEntry limits the length of key and value. If they exceed + // their maximum length the underlying string handling functions raise + // an exception and prematurely trigger a crash. Truncate here. + std::wstring safe_key(std::wstring(key).substr( + 0, google_breakpad::CustomInfoEntry::kNameMaxLength - 1)); + std::wstring safe_value(std::wstring(value).substr( + 0, google_breakpad::CustomInfoEntry::kValueMaxLength - 1)); + + // If we already have a value for this key, update it; otherwise, insert + // the new value if we have not exhausted the pre-allocated slots for dynamic + // entries. + DynamicEntriesMap::iterator it = g_dynamic_entries->find(safe_key); + google_breakpad::CustomInfoEntry* entry = NULL; + if (it == g_dynamic_entries->end()) { + if (g_dynamic_entries->size() >= kMaxDynamicEntries) + return; + entry = &(*g_custom_entries)[g_dynamic_keys_offset++]; + g_dynamic_entries->insert(std::make_pair(safe_key, entry)); + } else { + entry = it->second; + } + + entry->set(safe_key.data(), safe_value.data()); +} + +extern "C" void __declspec(dllexport) __cdecl ClearCrashKeyValueImpl( + const wchar_t* key) { + if (!g_dynamic_entries) + return; + + std::wstring key_string(key); + DynamicEntriesMap::iterator it = g_dynamic_entries->find(key_string); + if (it == g_dynamic_entries->end()) + return; + + it->second->set_value(NULL); +} + +} // namespace + +bool WrapMessageBoxWithSEH(const wchar_t* text, const wchar_t* caption, + UINT flags, bool* exit_now) { + // We wrap the call to MessageBoxW with a SEH handler because it some + // machines with CursorXP, PeaDict or with FontExplorer installed it crashes + // uncontrollably here. Being this a best effort deal we better go away. + __try { + *exit_now = (IDOK != ::MessageBoxW(NULL, text, caption, flags)); + } __except(EXCEPTION_EXECUTE_HANDLER) { + // Its not safe to continue executing, exit silently here. + ::TerminateProcess(::GetCurrentProcess(), + GetBreakpadClient()->GetResultCodeRespawnFailed()); + } + + return true; +} + +// This function is executed by the child process that DumpDoneCallback() +// spawned and basically just shows the 'chrome has crashed' dialog if +// the CHROME_CRASHED environment variable is present. +bool ShowRestartDialogIfCrashed(bool* exit_now) { + // If we are being launched in metro mode don't try to show the dialog. + if (base::win::IsMetroProcess()) + return false; + + // Only show this for the browser process. See crbug.com/132119. + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + std::string process_type = + command_line.GetSwitchValueASCII(switches::kProcessType); + if (!process_type.empty()) + return false; + + base::string16 message; + base::string16 title; + bool is_rtl_locale; + if (!GetBreakpadClient()->ShouldShowRestartDialog( + &title, &message, &is_rtl_locale)) { + return false; + } + + // If the UI layout is right-to-left, we need to pass the appropriate MB_XXX + // flags so that an RTL message box is displayed. + UINT flags = MB_OKCANCEL | MB_ICONWARNING; + if (is_rtl_locale) + flags |= MB_RIGHT | MB_RTLREADING; + + return WrapMessageBoxWithSEH(base::UTF16ToWide(message).c_str(), + base::UTF16ToWide(title).c_str(), + flags, + exit_now); +} + +// Crashes the process after generating a dump for the provided exception. Note +// that the crash reporter should be initialized before calling this function +// for it to do anything. +// NOTE: This function is used by SyzyASAN to invoke a crash. If you change the +// the name or signature of this function you will break SyzyASAN instrumented +// releases of Chrome. Please contact syzygy-team@chromium.org before doing so! +extern "C" int __declspec(dllexport) CrashForException( + EXCEPTION_POINTERS* info) { + if (g_breakpad) { + g_breakpad->WriteMinidumpForException(info); + // Patched stub exists based on conditions (See InitCrashReporter). + // As a side note this function also gets called from + // WindowProcExceptionFilter. + if (g_real_terminate_process_stub == NULL) { + ::TerminateProcess(::GetCurrentProcess(), content::RESULT_CODE_KILLED); + } else { + NtTerminateProcessPtr real_terminate_proc = + reinterpret_cast( + static_cast(g_real_terminate_process_stub)); + real_terminate_proc(::GetCurrentProcess(), content::RESULT_CODE_KILLED); + } + } + return EXCEPTION_CONTINUE_SEARCH; +} + +NTSTATUS WINAPI HookNtTerminateProcess(HANDLE ProcessHandle, + NTSTATUS ExitStatus) { + if (g_breakpad && + (ProcessHandle == ::GetCurrentProcess() || ProcessHandle == NULL)) { + NT_TIB* tib = reinterpret_cast(NtCurrentTeb()); + void* address_on_stack = _AddressOfReturnAddress(); + if (address_on_stack < tib->StackLimit || + address_on_stack > tib->StackBase) { + g_surrogate_exception_record.ExceptionAddress = _ReturnAddress(); + g_surrogate_exception_record.ExceptionCode = DBG_TERMINATE_PROCESS; + g_surrogate_exception_record.ExceptionFlags = EXCEPTION_NONCONTINUABLE; + CrashForException(&g_surrogate_exception_pointers); + } + } + + NtTerminateProcessPtr real_proc = + reinterpret_cast( + static_cast(g_real_terminate_process_stub)); + return real_proc(ProcessHandle, ExitStatus); +} + +static void InitTerminateProcessHooks() { + NtTerminateProcessPtr terminate_process_func_address = + reinterpret_cast(::GetProcAddress( + ::GetModuleHandle(L"ntdll.dll"), "NtTerminateProcess")); + if (terminate_process_func_address == NULL) + return; + + DWORD old_protect = 0; + if (!::VirtualProtect(terminate_process_func_address, 5, + PAGE_EXECUTE_READWRITE, &old_protect)) + return; + + g_real_terminate_process_stub = reinterpret_cast(VirtualAllocEx( + ::GetCurrentProcess(), NULL, sidestep::kMaxPreambleStubSize, + MEM_COMMIT, PAGE_EXECUTE_READWRITE)); + if (g_real_terminate_process_stub == NULL) + return; + + g_surrogate_exception_pointers.ContextRecord = &g_surrogate_context; + g_surrogate_exception_pointers.ExceptionRecord = + &g_surrogate_exception_record; + + sidestep::SideStepError patch_result = + sidestep::PreamblePatcher::Patch( + terminate_process_func_address, HookNtTerminateProcess, + g_real_terminate_process_stub, sidestep::kMaxPreambleStubSize); + if (patch_result != sidestep::SIDESTEP_SUCCESS) { + CHECK(::VirtualFreeEx(::GetCurrentProcess(), g_real_terminate_process_stub, + 0, MEM_RELEASE)); + CHECK(::VirtualProtect(terminate_process_func_address, 5, old_protect, + &old_protect)); + return; + } + + DWORD dummy = 0; + CHECK(::VirtualProtect(terminate_process_func_address, + 5, + old_protect, + &dummy)); + CHECK(::VirtualProtect(g_real_terminate_process_stub, + sidestep::kMaxPreambleStubSize, + old_protect, + &old_protect)); +} + +static void InitPipeNameEnvVar(bool is_per_user_install) { + scoped_ptr env(base::Environment::Create()); + if (env->HasVar(kPipeNameVar)) { + // The Breakpad pipe name is already configured: nothing to do. + return; + } + + // Check whether configuration management controls crash reporting. + bool crash_reporting_enabled = true; + bool controlled_by_policy = GetBreakpadClient()->ReportingIsEnforcedByPolicy( + &crash_reporting_enabled); + + const CommandLine& command = *CommandLine::ForCurrentProcess(); + bool use_crash_service = + !controlled_by_policy && (command.HasSwitch(switches::kNoErrorDialogs) || + GetBreakpadClient()->IsRunningUnattended()); + + std::wstring pipe_name; + if (use_crash_service) { + // Crash reporting is done by crash_service.exe. + pipe_name = kChromePipeName; + } else { + // We want to use the Google Update crash reporting. We need to check if the + // user allows it first (in case the administrator didn't already decide + // via policy). + if (!controlled_by_policy) + crash_reporting_enabled = GetBreakpadClient()->GetCollectStatsConsent(); + + if (!crash_reporting_enabled) { + if (!controlled_by_policy && + GetBreakpadClient()->GetDeferredUploadsSupported( + is_per_user_install)) { + g_deferred_crash_uploads = true; + } else { + return; + } + } + + // Build the pipe name. It can be either: + // System-wide install: "NamedPipe\GoogleCrashServices\S-1-5-18" + // Per-user install: "NamedPipe\GoogleCrashServices\" + std::wstring user_sid; + if (is_per_user_install) { + if (!base::win::GetUserSidString(&user_sid)) { + return; + } + } else { + user_sid = kSystemPrincipalSid; + } + + pipe_name = kGoogleUpdatePipeName; + pipe_name += user_sid; + } + env->SetVar(kPipeNameVar, base::UTF16ToASCII(pipe_name)); +} + +void InitDefaultCrashCallback(LPTOP_LEVEL_EXCEPTION_FILTER filter) { + previous_filter = SetUnhandledExceptionFilter(filter); +} + +void InitCrashReporter() { + const CommandLine& command = *CommandLine::ForCurrentProcess(); + if (command.HasSwitch(switches::kDisableBreakpad)) + return; + + // Disable the message box for assertions. + _CrtSetReportMode(_CRT_ASSERT, 0); + + std::wstring process_type = + command.GetSwitchValueNative(switches::kProcessType); + if (process_type.empty()) + process_type = L"browser"; + + wchar_t exe_path[MAX_PATH]; + exe_path[0] = 0; + GetModuleFileNameW(NULL, exe_path, MAX_PATH); + + bool is_per_user_install = + GetBreakpadClient()->GetIsPerUserInstall(base::FilePath(exe_path)); + + google_breakpad::CustomClientInfo* custom_info = + GetCustomInfo(exe_path, process_type); + + google_breakpad::ExceptionHandler::MinidumpCallback callback = NULL; + LPTOP_LEVEL_EXCEPTION_FILTER default_filter = NULL; + // We install the post-dump callback only for the browser and service + // processes. It spawns a new browser/service process. + if (process_type == L"browser") { + callback = &DumpDoneCallback; + default_filter = &ChromeExceptionFilter; + } else if (process_type == L"service") { + callback = &DumpDoneCallback; + default_filter = &ServiceExceptionFilter; + } + + if (process_type == L"browser") { + InitPipeNameEnvVar(is_per_user_install); + GetBreakpadClient()->InitBrowserCrashDumpsRegKey(); + } + + scoped_ptr env(base::Environment::Create()); + std::string pipe_name_ascii; + if (!env->GetVar(kPipeNameVar, &pipe_name_ascii)) { + // Breakpad is not enabled. Configuration is managed or the user + // did not allow Google Update to send crashes. We need to use + // our default crash handler instead, but only for the + // browser/service processes. + if (default_filter) + InitDefaultCrashCallback(default_filter); + return; + } + std::wstring pipe_name = base::ASCIIToWide(pipe_name_ascii); + +#ifdef _WIN64 + // The protocol for connecting to the out-of-process Breakpad crash + // reporter is different for x86-32 and x86-64: the message sizes + // are different because the message struct contains a pointer. As + // a result, there are two different named pipes to connect to. The + // 64-bit one is distinguished with an "-x64" suffix. + pipe_name += L"-x64"; +#endif + + // Get the alternate dump directory. We use the temp path. + wchar_t temp_dir[MAX_PATH] = {0}; + ::GetTempPathW(MAX_PATH, temp_dir); + + MINIDUMP_TYPE dump_type = kSmallDumpType; + // Capture full memory if explicitly instructed to. + if (command.HasSwitch(switches::kFullMemoryCrashReport)) + dump_type = kFullDumpType; + else if (GetBreakpadClient()->GetShouldDumpLargerDumps(is_per_user_install)) + dump_type = kLargerDumpType; + + int handler_types = google_breakpad::ExceptionHandler::HANDLER_EXCEPTION | + google_breakpad::ExceptionHandler::HANDLER_PURECALL; + g_breakpad = new google_breakpad::ExceptionHandler(temp_dir, &FilterCallback, + callback, NULL, + handler_types, + dump_type, pipe_name.c_str(), custom_info); + + // Now initialize the non crash dump handler. + g_dumphandler_no_crash = new google_breakpad::ExceptionHandler(temp_dir, + &FilterCallbackWhenNoCrash, + &DumpDoneCallbackWhenNoCrash, + NULL, + // Set the handler to none so this handler would not be added to + // |handler_stack_| in |ExceptionHandler| which is a list of exception + // handlers. + google_breakpad::ExceptionHandler::HANDLER_NONE, + dump_type, pipe_name.c_str(), custom_info); + + if (g_breakpad->IsOutOfProcess()) { + // Tells breakpad to handle breakpoint and single step exceptions. + // This might break JIT debuggers, but at least it will always + // generate a crashdump for these exceptions. + g_breakpad->set_handle_debug_exceptions(true); + +#ifndef _WIN64 + if (process_type != L"browser" && + !GetBreakpadClient()->IsRunningUnattended()) { + // Initialize the hook TerminateProcess to catch unexpected exits. + InitTerminateProcessHooks(); + } +#endif + } +} + +} // namespace breakpad + +bool SetCrashDumpPath(const char* path) { + if (!breakpad::g_breakpad) + return false; + breakpad::g_breakpad->set_dump_path(base::UTF8ToWide(path)); + return true; +} + diff --git a/src/breakpad_win.h b/src/breakpad_win.h new file mode 100644 index 0000000000..3e81a786a5 --- /dev/null +++ b/src/breakpad_win.h @@ -0,0 +1,22 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_BREAKPAD_APP_BREAKPAD_WIN_H_ +#define COMPONENTS_BREAKPAD_APP_BREAKPAD_WIN_H_ + +#include +#include +#include + +namespace breakpad { + +void InitCrashReporter(); + +// If chrome has been restarted because it crashed, this function will display +// a dialog asking for permission to continue execution or to exit now. +bool ShowRestartDialogIfCrashed(bool* exit_now); + +} // namespace breakpad + +#endif // COMPONENTS_BREAKPAD_APP_BREAKPAD_WIN_H_ diff --git a/src/browser/app_controller_mac.h b/src/browser/app_controller_mac.h index 6199be75b8..f2158498f1 100644 --- a/src/browser/app_controller_mac.h +++ b/src/browser/app_controller_mac.h @@ -24,7 +24,11 @@ #import @interface AppController : NSObject { + BOOL appReady; } + +@property(nonatomic) BOOL appReady; + @end #endif // CONTENT_NW_SRC_BROWSER_APP_CONTROLLER_MAC_H_ diff --git a/src/browser/app_controller_mac.mm b/src/browser/app_controller_mac.mm index 8aac9b7e68..7de1d69375 100644 --- a/src/browser/app_controller_mac.mm +++ b/src/browser/app_controller_mac.mm @@ -27,14 +27,17 @@ #include "content/nw/src/shell_browser_context.h" #include "content/nw/src/shell_browser_main_parts.h" #include "content/nw/src/shell_content_browser_client.h" +#include "base/values.h" @implementation AppController +@synthesize appReady; + - (BOOL)application:(NSApplication*)sender openFile:(NSString*)filename { if (content::Shell::windows().size() == 0) { - CommandLine::ForCurrentProcess()->AppendArg([filename UTF8String]); - CommandLine::ForCurrentProcess()->FixOrigArgv4Finder([filename UTF8String]); + base::CommandLine::ForCurrentProcess()->AppendArg([filename UTF8String]); + base::CommandLine::ForCurrentProcess()->FixOrigArgv4Finder([filename UTF8String]); return TRUE; } @@ -43,7 +46,7 @@ - (BOOL)application:(NSApplication*)sender if (package->self_extract()) { // Let the app deal with the opening event if it's a standalone app. - api::App::EmitOpenEvent([filename UTF8String]); + nwapi::App::EmitOpenEvent([filename UTF8String]); } else { // Or open a new app in the runtime mode. } @@ -51,6 +54,15 @@ - (BOOL)application:(NSApplication*)sender return FALSE; } +- (void) applicationWillFinishLaunching: (NSNotification *) note { + self.appReady = FALSE; + NSAppleEventManager *eventManager = [NSAppleEventManager sharedAppleEventManager]; + [eventManager setEventHandler:self + andSelector:@selector(handleGetURLEvent:withReplyEvent:) + forEventClass:kInternetEventClass + andEventID:kAEGetURL]; +} + - (void) applicationDidFinishLaunching: (NSNotification *) note { // Initlialize everything here content::ShellContentBrowserClient* browser_client = @@ -59,14 +71,54 @@ - (void) applicationDidFinishLaunching: (NSNotification *) note { browser_client->shell_browser_main_parts()->Init(); // And init menu. + bool no_edit_menu = false; + browser_client->shell_browser_main_parts()->package()->root()->GetBoolean("no-edit-menu", &no_edit_menu); + [NSApp setMainMenu:[[[NSMenu alloc] init] autorelease]]; [[NSApp mainMenu] addItem:[[[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""] autorelease]]; + + self.appReady = TRUE; +#if 0 nw::StandardMenusMac standard_menus( browser_client->shell_browser_main_parts()->package()->GetName()); standard_menus.BuildAppleMenu(); - standard_menus.BuildEditMenu(); + if (!no_edit_menu) + standard_menus.BuildEditMenu(); standard_menus.BuildWindowMenu(); +#endif +} + +- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication + hasVisibleWindows:(BOOL)flag { + nwapi::App::EmitReopenEvent(); + return YES; +} + +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)app { + // The termination procedure is completely and gracefully handled by node-webkit + // (triggered by CloseAllWindows, app exits when last window closes) so we + // don't need Cocoa to terminate the application immediately (NSTerminateNow) + // neither run a special event loop (NSTerminateLater) waiting for a termination + // reply + nwapi::App::CloseAllWindows(false, true); + return NSTerminateCancel; +} + +- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent +{ + NSString *urlString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; + if (self.appReady) { + // Immediate handle of get url event + nwapi::App::EmitOpenEvent([urlString UTF8String]); + } else { + // App is not ready yet, add the URL to the command line arguments. + // This happens when the app is started by opening a link with the registered URL. + if (content::Shell::windows().size() == 0) { + base::CommandLine::ForCurrentProcess()->AppendArg([urlString UTF8String]); + base::CommandLine::ForCurrentProcess()->FixOrigArgv4Finder([urlString UTF8String]); + } + } } @end diff --git a/src/browser/autofill_popup_base_view.cc b/src/browser/autofill_popup_base_view.cc new file mode 100644 index 0000000000..69901380ef --- /dev/null +++ b/src/browser/autofill_popup_base_view.cc @@ -0,0 +1,241 @@ +// 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 "content/nw/src/browser/autofill_popup_base_view.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/message_loop/message_loop.h" +#include "chrome/browser/ui/autofill/popup_constants.h" +#include "ui/views/border.h" +#include "ui/views/widget/widget.h" + +namespace autofill { + +const SkColor AutofillPopupBaseView::kBorderColor = + SkColorSetARGB(0xFF, 0xC7, 0xCA, 0xCE); +const SkColor AutofillPopupBaseView::kHoveredBackgroundColor = + SkColorSetARGB(0xFF, 0xCD, 0xCD, 0xCD); +const SkColor AutofillPopupBaseView::kItemTextColor = + SkColorSetARGB(0xFF, 0x7F, 0x7F, 0x7F); +const SkColor AutofillPopupBaseView::kPopupBackground = + SkColorSetARGB(0xFF, 0xFF, 0xFF, 0xFF); +const SkColor AutofillPopupBaseView::kValueTextColor = + SkColorSetARGB(0xFF, 0x00, 0x00, 0x00); +const SkColor AutofillPopupBaseView::kWarningTextColor = + SkColorSetARGB(0xFF, 0x7F, 0x7F, 0x7F); + +AutofillPopupBaseView::AutofillPopupBaseView( + AutofillPopupViewDelegate* delegate, + views::Widget* observing_widget) + : delegate_(delegate), + observing_widget_(observing_widget), + weak_ptr_factory_(this) {} + +AutofillPopupBaseView::~AutofillPopupBaseView() { + if (delegate_) { + delegate_->ViewDestroyed(); + + RemoveObserver(); + } +} + +void AutofillPopupBaseView::DoShow() { + const bool initialize_widget = !GetWidget(); + if (initialize_widget) { + observing_widget_->AddObserver(this); + + views::FocusManager* focus_manager = observing_widget_->GetFocusManager(); + focus_manager->RegisterAccelerator( + ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE), + ui::AcceleratorManager::kNormalPriority, + this); + focus_manager->RegisterAccelerator( + ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE), + ui::AcceleratorManager::kNormalPriority, + this); + + // The widget is destroyed by the corresponding NativeWidget, so we use + // a weak pointer to hold the reference and don't have to worry about + // deletion. + views::Widget* widget = new views::Widget; + views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); + params.delegate = this; + params.parent = container_view(); + widget->Init(params); + widget->SetContentsView(this); + + // No animation for popup appearance (too distracting). + widget->SetVisibilityAnimationTransition(views::Widget::ANIMATE_HIDE); + } + + SetBorder(views::Border::CreateSolidBorder(kPopupBorderThickness, + kBorderColor)); + + DoUpdateBoundsAndRedrawPopup(); + GetWidget()->Show(); + + // Showing the widget can change native focus (which would result in an + // immediate hiding of the popup). Only start observing after shown. + if (initialize_widget) + views::WidgetFocusManager::GetInstance()->AddFocusChangeListener(this); +} + +void AutofillPopupBaseView::DoHide() { + // The controller is no longer valid after it hides us. + delegate_ = NULL; + + RemoveObserver(); + + if (GetWidget()) { + // Don't call CloseNow() because some of the functions higher up the stack + // assume the the widget is still valid after this point. + // http://crbug.com/229224 + // NOTE: This deletes |this|. + GetWidget()->Close(); + } else { + delete this; + } +} + +void AutofillPopupBaseView::RemoveObserver() { + observing_widget_->GetFocusManager()->UnregisterAccelerators(this); + observing_widget_->RemoveObserver(this); + views::WidgetFocusManager::GetInstance()->RemoveFocusChangeListener(this); +} + +void AutofillPopupBaseView::DoUpdateBoundsAndRedrawPopup() { + GetWidget()->SetBounds(delegate_->popup_bounds()); + SchedulePaint(); +} + +void AutofillPopupBaseView::OnNativeFocusChange( + gfx::NativeView focused_before, + gfx::NativeView focused_now) { + if (GetWidget() && GetWidget()->GetNativeView() != focused_now) + HideController(); +} + +void AutofillPopupBaseView::OnWidgetBoundsChanged(views::Widget* widget, + const gfx::Rect& new_bounds) { + DCHECK_EQ(widget, observing_widget_); + HideController(); +} + +void AutofillPopupBaseView::OnMouseCaptureLost() { + ClearSelection(); +} + +bool AutofillPopupBaseView::OnMouseDragged(const ui::MouseEvent& event) { + if (HitTestPoint(event.location())) { + SetSelection(event.location()); + + // We must return true in order to get future OnMouseDragged and + // OnMouseReleased events. + return true; + } + + // If we move off of the popup, we lose the selection. + ClearSelection(); + return false; +} + +void AutofillPopupBaseView::OnMouseExited(const ui::MouseEvent& event) { + // Pressing return causes the cursor to hide, which will generate an + // OnMouseExited event. Pressing return should activate the current selection + // via AcceleratorPressed, so we need to let that run first. + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&AutofillPopupBaseView::ClearSelection, + weak_ptr_factory_.GetWeakPtr())); +} + +void AutofillPopupBaseView::OnMouseMoved(const ui::MouseEvent& event) { + if (HitTestPoint(event.location())) + SetSelection(event.location()); + else + ClearSelection(); +} + +bool AutofillPopupBaseView::OnMousePressed(const ui::MouseEvent& event) { + return event.GetClickCount() == 1; +} + +void AutofillPopupBaseView::OnMouseReleased(const ui::MouseEvent& event) { + // We only care about the left click. + if (event.IsOnlyLeftMouseButton() && HitTestPoint(event.location())) + AcceptSelection(event.location()); +} + +void AutofillPopupBaseView::OnGestureEvent(ui::GestureEvent* event) { + switch (event->type()) { + case ui::ET_GESTURE_TAP_DOWN: + case ui::ET_GESTURE_SCROLL_BEGIN: + case ui::ET_GESTURE_SCROLL_UPDATE: + if (HitTestPoint(event->location())) + SetSelection(event->location()); + else + ClearSelection(); + break; + case ui::ET_GESTURE_TAP: + case ui::ET_GESTURE_SCROLL_END: + if (HitTestPoint(event->location())) + AcceptSelection(event->location()); + else + ClearSelection(); + break; + case ui::ET_GESTURE_TAP_CANCEL: + case ui::ET_SCROLL_FLING_START: + ClearSelection(); + break; + default: + return; + } + event->SetHandled(); +} + +bool AutofillPopupBaseView::AcceleratorPressed( + const ui::Accelerator& accelerator) { + DCHECK_EQ(accelerator.modifiers(), ui::EF_NONE); + + if (accelerator.key_code() == ui::VKEY_ESCAPE) { + HideController(); + return true; + } + + if (accelerator.key_code() == ui::VKEY_RETURN) + return delegate_->AcceptSelectedLine(); + + NOTREACHED(); + return false; +} + +void AutofillPopupBaseView::SetSelection(const gfx::Point& point) { + if (delegate_) + delegate_->SetSelectionAtPoint(point); +} + +void AutofillPopupBaseView::AcceptSelection(const gfx::Point& point) { + if (!delegate_) + return; + + delegate_->SetSelectionAtPoint(point); + delegate_->AcceptSelectedLine(); +} + +void AutofillPopupBaseView::ClearSelection() { + if (delegate_) + delegate_->SelectionCleared(); +} + +void AutofillPopupBaseView::HideController() { + if (delegate_) + delegate_->Hide(); +} + +gfx::NativeView AutofillPopupBaseView::container_view() { + return delegate_->container_view(); +} + +} // namespace autofill diff --git a/src/browser/autofill_popup_base_view.h b/src/browser/autofill_popup_base_view.h new file mode 100644 index 0000000000..ef4ce08abb --- /dev/null +++ b/src/browser/autofill_popup_base_view.h @@ -0,0 +1,99 @@ +// 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_UI_VIEWS_AUTOFILL_AUTOFILL_POPUP_BASE_VIEW_H_ +#define CHROME_BROWSER_UI_VIEWS_AUTOFILL_AUTOFILL_POPUP_BASE_VIEW_H_ + +#include "base/memory/weak_ptr.h" +#include "content/nw/src/browser/autofill_popup_view_delegate.h" +#include "ui/views/focus/widget_focus_manager.h" +#include "ui/views/widget/widget_delegate.h" +#include "ui/views/widget/widget_observer.h" + +namespace content { +class WebContents; +} + +namespace gfx { +class Point; +} + +namespace autofill { + +// Class that deals with the event handling for Autofill-style popups. This +// class should only be instantiated by sub-classes. +class AutofillPopupBaseView : public views::WidgetDelegateView, + public views::WidgetFocusChangeListener, + public views::WidgetObserver { + public: + static const SkColor kBorderColor; + static const SkColor kHoveredBackgroundColor; + static const SkColor kItemTextColor; + static const SkColor kPopupBackground; + static const SkColor kValueTextColor; + static const SkColor kWarningTextColor; + + protected: + explicit AutofillPopupBaseView(AutofillPopupViewDelegate* delegate, + views::Widget* observing_widget); + ~AutofillPopupBaseView() override; + + // Show this popup. Idempotent. + void DoShow(); + + // Hide the widget and delete |this|. + void DoHide(); + + // Update size of popup and paint. + void DoUpdateBoundsAndRedrawPopup(); + + private: + friend class AutofillPopupBaseViewTest; + + // views::Views implementation. + void OnMouseCaptureLost() override; + bool OnMouseDragged(const ui::MouseEvent& event) override; + void OnMouseExited(const ui::MouseEvent& event) override; + void OnMouseMoved(const ui::MouseEvent& event) override; + bool OnMousePressed(const ui::MouseEvent& event) override; + void OnMouseReleased(const ui::MouseEvent& event) override; + void OnGestureEvent(ui::GestureEvent* event) override; + bool AcceleratorPressed(const ui::Accelerator& accelerator) override; + + // views::WidgetFocusChangeListener implementation. + void OnNativeFocusChange(gfx::NativeView focused_before, + gfx::NativeView focused_now) override; + + // views::WidgetObserver implementation. + void OnWidgetBoundsChanged(views::Widget* widget, + const gfx::Rect& new_bounds) override; + + // Stop observing the |observing_widget_|. + void RemoveObserver(); + + void SetSelection(const gfx::Point& point); + void AcceptSelection(const gfx::Point& point); + void ClearSelection(); + + // Hide the controller of this view. This assumes that doing so will + // eventually hide this view in the process. + void HideController(); + + // Must return the container view for this popup. + gfx::NativeView container_view(); + + // Controller for this popup. Weak reference. + AutofillPopupViewDelegate* delegate_; + + // The widget that |this| observes. Weak reference. + views::Widget* observing_widget_; + + base::WeakPtrFactory weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(AutofillPopupBaseView); +}; + +} // namespace autofill + +#endif // CHROME_BROWSER_UI_VIEWS_AUTOFILL_AUTOFILL_POPUP_BASE_VIEW_H_ diff --git a/src/browser/autofill_popup_base_view_cocoa.h b/src/browser/autofill_popup_base_view_cocoa.h new file mode 100644 index 0000000000..24212726c4 --- /dev/null +++ b/src/browser/autofill_popup_base_view_cocoa.h @@ -0,0 +1,48 @@ +// 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_UI_COCOA_AUTOFILL_AUTOFILL_POPUP_BASE_VIEW_COCOA_H_ +#define CHROME_BROWSER_UI_COCOA_AUTOFILL_AUTOFILL_POPUP_BASE_VIEW_COCOA_H_ + +#import + +#import "ui/base/cocoa/base_view.h" + +namespace autofill { +class AutofillPopupViewDelegate; +} + +@interface AutofillPopupBaseViewCocoa : BaseView { + @private + __weak autofill::AutofillPopupViewDelegate* delegate_; +} + +- (NSColor*)backgroundColor; +- (NSColor*)borderColor; +- (NSColor*)highlightColor; +- (NSColor*)nameColor; +- (NSColor*)separatorColor; +- (NSColor*)subtextColor; +- (NSColor*)warningColor; + +- (id)initWithDelegate:(autofill::AutofillPopupViewDelegate*)delegate + frame:(NSRect)frame; + +// Informs the view that its delegate has been (or will imminently be) +// destroyed. +- (void)delegateDestroyed; + +// Draw the popup's background and border. +- (void)drawBackgroundAndBorder; + +// Draws a thin separator in the popup UI. +- (void)drawSeparatorWithBounds:(NSRect)bounds; + +// Messages from AutofillPopupViewBridge: +- (void)updateBoundsAndRedrawPopup; +- (void)showPopup; +- (void)hidePopup; +@end + +#endif // CHROME_BROWSER_UI_COCOA_AUTOFILL_AUTOFILL_POPUP_BASE_VIEW_COCOA_H_ diff --git a/src/browser/autofill_popup_base_view_cocoa.mm b/src/browser/autofill_popup_base_view_cocoa.mm new file mode 100644 index 0000000000..7a8743c802 --- /dev/null +++ b/src/browser/autofill_popup_base_view_cocoa.mm @@ -0,0 +1,175 @@ +// 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. + +#import "chrome/browser/ui/cocoa/autofill/autofill_popup_base_view_cocoa.h" + +#include "chrome/browser/ui/autofill/autofill_popup_view_delegate.h" +#include "chrome/browser/ui/autofill/popup_constants.h" +#include "ui/base/cocoa/window_size_constants.h" + +@implementation AutofillPopupBaseViewCocoa + +#pragma mark - +#pragma mark Colors + +- (NSColor*)backgroundColor { + return [NSColor whiteColor]; +} + +- (NSColor*)borderColor { + return [NSColor colorForControlTint:[NSColor currentControlTint]]; +} + +- (NSColor*)highlightColor { + return [NSColor selectedControlColor]; +} + +- (NSColor*)nameColor { + return [NSColor blackColor]; +} + +- (NSColor*)separatorColor { + return [NSColor colorWithCalibratedWhite:220 / 255.0 alpha:1]; +} + +- (NSColor*)subtextColor { + return [NSColor grayColor]; +} + +- (NSColor*)warningColor { + return [NSColor grayColor]; +} + +#pragma mark - +#pragma mark Public methods + +- (id)initWithDelegate:(autofill::AutofillPopupViewDelegate*)delegate + frame:(NSRect)frame { + self = [super initWithFrame:frame]; + if (self) + delegate_ = delegate; + + return self; +} + +- (void)delegateDestroyed { + delegate_ = NULL; +} + +- (void)drawSeparatorWithBounds:(NSRect)bounds { + [[self separatorColor] set]; + [NSBezierPath fillRect:bounds]; +} + +// A slight optimization for drawing: +// https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaViewsGuide/Optimizing/Optimizing.html +- (BOOL)isOpaque { + return YES; +} + +- (BOOL)isFlipped { + // Flipped so that it's easier to share controller logic with other OSes. + return YES; +} + +- (void)drawBackgroundAndBorder { + // The inset is needed since the border is centered on the |path|. + // TODO(isherman): We should consider using asset-based drawing for the + // border, creating simple bitmaps for the view's border and background, and + // drawing them using NSDrawNinePartImage(). + CGFloat inset = autofill::kPopupBorderThickness / 2.0; + NSRect borderRect = NSInsetRect([self bounds], inset, inset); + NSBezierPath* path = [NSBezierPath bezierPathWithRect:borderRect]; + [[self backgroundColor] setFill]; + [path fill]; + [path setLineWidth:autofill::kPopupBorderThickness]; + [[self borderColor] setStroke]; + [path stroke]; +} + +- (void)mouseUp:(NSEvent*)theEvent { + // If the view is in the process of being destroyed, abort. + if (!delegate_) + return; + + // Only accept single-click. + if ([theEvent clickCount] > 1) + return; + + NSPoint location = [self convertPoint:[theEvent locationInWindow] + fromView:nil]; + + if (NSPointInRect(location, [self bounds])) { + delegate_->SetSelectionAtPoint(gfx::Point(NSPointToCGPoint(location))); + delegate_->AcceptSelectedLine(); + } +} + +- (void)mouseMoved:(NSEvent*)theEvent { + // If the view is in the process of being destroyed, abort. + if (!delegate_) + return; + + NSPoint location = [self convertPoint:[theEvent locationInWindow] + fromView:nil]; + + delegate_->SetSelectionAtPoint(gfx::Point(NSPointToCGPoint(location))); +} + +- (void)mouseDragged:(NSEvent*)theEvent { + [self mouseMoved:theEvent]; +} + +- (void)mouseExited:(NSEvent*)theEvent { + // If the view is in the process of being destroyed, abort. + if (!delegate_) + return; + + delegate_->SelectionCleared(); +} + +#pragma mark - +#pragma mark Messages from AutofillPopupViewBridge: + +- (void)updateBoundsAndRedrawPopup { + NSRect frame = NSRectFromCGRect(delegate_->popup_bounds().ToCGRect()); + + // Flip coordinates back into Cocoa-land. The controller's platform-neutral + // coordinate space places the origin at the top-left of the first screen, + // whereas Cocoa's coordinate space expects the origin to be at the + // bottom-left of this same screen. + NSScreen* screen = [[NSScreen screens] objectAtIndex:0]; + frame.origin.y = NSMaxY([screen frame]) - NSMaxY(frame); + + // TODO(isherman): The view should support scrolling if the popup gets too + // big to fit on the screen. + [[self window] setFrame:frame display:YES]; + [self setNeedsDisplay:YES]; +} + +- (void)showPopup { + NSWindow* window = + [[NSWindow alloc] initWithContentRect:ui::kWindowSizeDeterminedLater + styleMask:NSBorderlessWindowMask + backing:NSBackingStoreBuffered + defer:YES]; + [window setContentView:self]; + + // Telling Cocoa that the window is opaque enables some drawing optimizations. + [window setOpaque:YES]; + + [self updateBoundsAndRedrawPopup]; + [[delegate_->container_view() window] addChildWindow:window + ordered:NSWindowAbove]; +} + +- (void)hidePopup { + // Remove the child window before closing, otherwise it can mess up + // display ordering. + NSWindow* window = [self window]; + [[window parentWindow] removeChildWindow:window]; + [window close]; +} + +@end diff --git a/src/browser/autofill_popup_controller.h b/src/browser/autofill_popup_controller.h new file mode 100644 index 0000000000..a5a723ca83 --- /dev/null +++ b/src/browser/autofill_popup_controller.h @@ -0,0 +1,76 @@ +// 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_UI_AUTOFILL_AUTOFILL_POPUP_CONTROLLER_H_ +#define CHROME_BROWSER_UI_AUTOFILL_AUTOFILL_POPUP_CONTROLLER_H_ + +#include + +#include "base/compiler_specific.h" +#include "base/strings/string16.h" +#include "chrome/browser/ui/autofill/autofill_popup_view_delegate.h" + +namespace gfx { +class FontList; +class Point; +class Rect; +} + +namespace autofill { + +struct Suggestion; + +// This interface provides data to an AutofillPopupView. +class AutofillPopupController : public AutofillPopupViewDelegate { + public: + // Recalculates the height and width of the popup and triggers a redraw. + virtual void UpdateBoundsAndRedrawPopup() = 0; + + // Accepts the suggestion at |index|. + virtual void AcceptSuggestion(size_t index) = 0; + + // Gets the resource value for the given resource, returning -1 if the + // resource isn't recognized. + virtual int GetIconResourceID(const base::string16& resource_name) const = 0; + + // Returns true if the given index refers to an element that can be deleted. + virtual bool CanDelete(size_t index) const = 0; + + // Returns true if the given index refers to an element that is a warning + // rather than an Autofill suggestion. + virtual bool IsWarning(size_t index) const = 0; + + // Updates the bounds of the popup and initiates a redraw. + virtual void SetPopupBounds(const gfx::Rect& bounds) = 0; + + // Returns the bounds of the item at |index| in the popup, relative to + // the top left of the popup. + virtual gfx::Rect GetRowBounds(size_t index) = 0; + + // Returns the number of lines of data that there are. + virtual size_t GetLineCount() const = 0; + + // Returns the suggestion or pre-elided string at the given row index. + virtual const autofill::Suggestion& GetSuggestionAt(size_t row) const = 0; + virtual const base::string16& GetElidedValueAt(size_t row) const = 0; + virtual const base::string16& GetElidedLabelAt(size_t row) const = 0; + +#if !defined(OS_ANDROID) + // The same font can vary based on the type of data it is showing, + // so we need to know the row. + virtual const gfx::FontList& GetValueFontListForRow(size_t index) const = 0; + virtual const gfx::FontList& GetLabelFontList() const = 0; +#endif + + // Returns the index of the selected line. A line is "selected" when it is + // hovered or has keyboard focus. + virtual int selected_line() const = 0; + + protected: + ~AutofillPopupController() override {} +}; + +} // namespace autofill + +#endif // CHROME_BROWSER_UI_AUTOFILL_AUTOFILL_POPUP_CONTROLLER_H_ diff --git a/src/browser/autofill_popup_controller_impl.cc b/src/browser/autofill_popup_controller_impl.cc new file mode 100644 index 0000000000..a8331e7b7e --- /dev/null +++ b/src/browser/autofill_popup_controller_impl.cc @@ -0,0 +1,666 @@ +// 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/ui/autofill/autofill_popup_controller_impl.h" + +#include +#include + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/ui/autofill/autofill_popup_view.h" +#include "chrome/browser/ui/autofill/popup_constants.h" +#include "components/autofill/core/browser/autofill_popup_delegate.h" +#include "components/autofill/core/browser/popup_item_ids.h" +#include "components/autofill/core/browser/suggestion.h" +#include "content/public/browser/native_web_keyboard_event.h" +#include "grit/components_scaled_resources.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/events/event.h" +#include "ui/gfx/geometry/rect_conversions.h" +#include "ui/gfx/geometry/vector2d.h" +#include "ui/gfx/screen.h" +#include "ui/gfx/text_elider.h" +#include "ui/gfx/text_utils.h" + +using base::WeakPtr; + +namespace autofill { +namespace { + +// Used to indicate that no line is currently selected by the user. +const int kNoSelection = -1; + +// The vertical height of each row in pixels. +const size_t kRowHeight = 24; + +// The vertical height of a separator in pixels. +const size_t kSeparatorHeight = 1; + +#if !defined(OS_ANDROID) +// Size difference between name and label in pixels. +const int kLabelFontSizeDelta = -2; + +const size_t kNamePadding = AutofillPopupView::kNamePadding; +const size_t kIconPadding = AutofillPopupView::kIconPadding; +const size_t kEndPadding = AutofillPopupView::kEndPadding; +#endif + +struct DataResource { + const char* name; + int id; +}; + +const DataResource kDataResources[] = { + { "americanExpressCC", IDR_AUTOFILL_CC_AMEX }, + { "dinersCC", IDR_AUTOFILL_CC_GENERIC }, + { "discoverCC", IDR_AUTOFILL_CC_DISCOVER }, + { "genericCC", IDR_AUTOFILL_CC_GENERIC }, + { "jcbCC", IDR_AUTOFILL_CC_GENERIC }, + { "masterCardCC", IDR_AUTOFILL_CC_MASTERCARD }, + { "visaCC", IDR_AUTOFILL_CC_VISA }, + { "scanCreditCardIcon", IDR_AUTOFILL_CC_SCAN_NEW }, +#if defined(OS_MACOSX) && !defined(OS_IOS) + { "macContactsIcon", IDR_AUTOFILL_MAC_CONTACTS_ICON }, +#endif // defined(OS_MACOSX) && !defined(OS_IOS) +}; + +} // namespace + +// static +WeakPtr AutofillPopupControllerImpl::GetOrCreate( + WeakPtr previous, + WeakPtr delegate, + content::WebContents* web_contents, + gfx::NativeView container_view, + const gfx::RectF& element_bounds, + base::i18n::TextDirection text_direction) { + if (previous.get() && previous->web_contents() == web_contents && + previous->delegate_.get() == delegate.get() && + previous->container_view() == container_view && + previous->element_bounds() == element_bounds) { + previous->ClearState(); + return previous; + } + + if (previous.get()) + previous->Hide(); + + AutofillPopupControllerImpl* controller = + new AutofillPopupControllerImpl( + delegate, web_contents, container_view, element_bounds, + text_direction); + return controller->GetWeakPtr(); +} + +AutofillPopupControllerImpl::AutofillPopupControllerImpl( + base::WeakPtr delegate, + content::WebContents* web_contents, + gfx::NativeView container_view, + const gfx::RectF& element_bounds, + base::i18n::TextDirection text_direction) + : controller_common_(new PopupControllerCommon(element_bounds, + container_view, + web_contents)), + view_(NULL), + delegate_(delegate), + text_direction_(text_direction), + weak_ptr_factory_(this) { + ClearState(); + controller_common_->SetKeyPressCallback( + base::Bind(&AutofillPopupControllerImpl::HandleKeyPressEvent, + base::Unretained(this))); +#if !defined(OS_ANDROID) + label_font_list_ = value_font_list_.DeriveWithSizeDelta(kLabelFontSizeDelta); + title_font_list_ = value_font_list_.DeriveWithStyle(gfx::Font::BOLD); +#if defined(OS_MACOSX) + // There is no italic version of the system font. + warning_font_list_ = value_font_list_; +#else + warning_font_list_ = value_font_list_.DeriveWithStyle(gfx::Font::ITALIC); +#endif +#endif +} + +AutofillPopupControllerImpl::~AutofillPopupControllerImpl() {} + +void AutofillPopupControllerImpl::Show( + const std::vector& suggestions) { + SetValues(suggestions); + DCHECK_EQ(suggestions_.size(), elided_values_.size()); + DCHECK_EQ(suggestions_.size(), elided_labels_.size()); + +#if !defined(OS_ANDROID) + // Android displays the long text with ellipsis using the view attributes. + + UpdatePopupBounds(); + int popup_width = popup_bounds().width(); + + // Elide the name and label strings so that the popup fits in the available + // space. + for (size_t i = 0; i < suggestions_.size(); ++i) { + int value_width = + gfx::GetStringWidth(suggestions_[i].value, GetValueFontListForRow(i)); + int label_width = + gfx::GetStringWidth(suggestions_[i].label, GetLabelFontList()); + int total_text_length = value_width + label_width; + + // The line can have no strings if it represents a UI element, such as + // a separator line. + if (total_text_length == 0) + continue; + + int available_width = popup_width - RowWidthWithoutText(i); + + // Each field receives space in proportion to its length. + int value_size = available_width * value_width / total_text_length; + elided_values_[i] = gfx::ElideText(suggestions_[i].value, + GetValueFontListForRow(i), + value_size, gfx::ELIDE_TAIL); + + int label_size = available_width * label_width / total_text_length; + elided_labels_[i] = gfx::ElideText(suggestions_[i].label, + GetLabelFontList(), + label_size, gfx::ELIDE_TAIL); + } +#endif + + if (!view_) { + view_ = AutofillPopupView::Create(this); + + // It is possible to fail to create the popup, in this case + // treat the popup as hiding right away. + if (!view_) { + Hide(); + return; + } + + ShowView(); + } else { + UpdateBoundsAndRedrawPopup(); + } + + controller_common_->RegisterKeyPressCallback(); + delegate_->OnPopupShown(); + + DCHECK_EQ(suggestions_.size(), elided_values_.size()); + DCHECK_EQ(suggestions_.size(), elided_labels_.size()); +} + +void AutofillPopupControllerImpl::UpdateDataListValues( + const std::vector& values, + const std::vector& labels) { + DCHECK_EQ(suggestions_.size(), elided_values_.size()); + DCHECK_EQ(suggestions_.size(), elided_labels_.size()); + + // Remove all the old data list values, which should always be at the top of + // the list if they are present. + while (!suggestions_.empty() && + suggestions_[0].frontend_id == POPUP_ITEM_ID_DATALIST_ENTRY) { + suggestions_.erase(suggestions_.begin()); + elided_values_.erase(elided_values_.begin()); + elided_labels_.erase(elided_labels_.begin()); + } + + // If there are no new data list values, exit (clearing the separator if there + // is one). + if (values.empty()) { + if (!suggestions_.empty() && + suggestions_[0].frontend_id == POPUP_ITEM_ID_SEPARATOR) { + suggestions_.erase(suggestions_.begin()); + elided_values_.erase(elided_values_.begin()); + elided_labels_.erase(elided_labels_.begin()); + } + + // The popup contents have changed, so either update the bounds or hide it. + if (HasSuggestions()) + UpdateBoundsAndRedrawPopup(); + else + Hide(); + + return; + } + + // Add a separator if there are any other values. + if (!suggestions_.empty() && + suggestions_[0].frontend_id != POPUP_ITEM_ID_SEPARATOR) { + suggestions_.insert(suggestions_.begin(), autofill::Suggestion()); + suggestions_[0].frontend_id = POPUP_ITEM_ID_SEPARATOR; + elided_values_.insert(elided_values_.begin(), base::string16()); + elided_labels_.insert(elided_labels_.begin(), base::string16()); + } + + // Prepend the parameters to the suggestions we already have. + suggestions_.insert(suggestions_.begin(), values.size(), Suggestion()); + elided_values_.insert(elided_values_.begin(), values.size(), + base::string16()); + elided_labels_.insert(elided_labels_.begin(), values.size(), + base::string16()); + for (size_t i = 0; i < values.size(); i++) { + suggestions_[i].value = values[i]; + suggestions_[i].label = labels[i]; + suggestions_[i].frontend_id = POPUP_ITEM_ID_DATALIST_ENTRY; + + // TODO(brettw) it looks like these should be elided. + elided_values_[i] = values[i]; + elided_labels_[i] = labels[i]; + } + + UpdateBoundsAndRedrawPopup(); + DCHECK_EQ(suggestions_.size(), elided_values_.size()); + DCHECK_EQ(suggestions_.size(), elided_labels_.size()); +} + +void AutofillPopupControllerImpl::Hide() { + controller_common_->RemoveKeyPressCallback(); + if (delegate_) + delegate_->OnPopupHidden(); + + if (view_) + view_->Hide(); + + delete this; +} + +void AutofillPopupControllerImpl::ViewDestroyed() { + // The view has already been destroyed so clear the reference to it. + view_ = NULL; + + Hide(); +} + +bool AutofillPopupControllerImpl::HandleKeyPressEvent( + const content::NativeWebKeyboardEvent& event) { + switch (event.windowsKeyCode) { + case ui::VKEY_UP: + SelectPreviousLine(); + return true; + case ui::VKEY_DOWN: + SelectNextLine(); + return true; + case ui::VKEY_PRIOR: // Page up. + // Set no line and then select the next line in case the first line is not + // selectable. + SetSelectedLine(kNoSelection); + SelectNextLine(); + return true; + case ui::VKEY_NEXT: // Page down. + SetSelectedLine(GetLineCount() - 1); + return true; + case ui::VKEY_ESCAPE: + Hide(); + return true; + case ui::VKEY_DELETE: + return (event.modifiers & content::NativeWebKeyboardEvent::ShiftKey) && + RemoveSelectedLine(); + case ui::VKEY_TAB: + // A tab press should cause the selected line to be accepted, but still + // return false so the tab key press propagates and changes the cursor + // location. + AcceptSelectedLine(); + return false; + case ui::VKEY_RETURN: + return AcceptSelectedLine(); + default: + return false; + } +} + +void AutofillPopupControllerImpl::UpdateBoundsAndRedrawPopup() { +#if !defined(OS_ANDROID) + // TODO(csharp): Since UpdatePopupBounds can change the position of the popup, + // the popup could end up jumping from above the element to below it. + // It is unclear if it is better to keep the popup where it was, or if it + // should try and move to its desired position. + UpdatePopupBounds(); +#endif + + view_->UpdateBoundsAndRedrawPopup(); +} + +void AutofillPopupControllerImpl::SetSelectionAtPoint(const gfx::Point& point) { + SetSelectedLine(LineFromY(point.y())); +} + +bool AutofillPopupControllerImpl::AcceptSelectedLine() { + if (selected_line_ == kNoSelection) + return false; + + DCHECK_GE(selected_line_, 0); + DCHECK_LT(selected_line_, static_cast(GetLineCount())); + + if (!CanAccept(suggestions_[selected_line_].frontend_id)) + return false; + + AcceptSuggestion(selected_line_); + return true; +} + +void AutofillPopupControllerImpl::SelectionCleared() { + SetSelectedLine(kNoSelection); +} + +void AutofillPopupControllerImpl::AcceptSuggestion(size_t index) { + const autofill::Suggestion& suggestion = suggestions_[index]; + delegate_->DidAcceptSuggestion(suggestion.value, suggestion.frontend_id); +} + +int AutofillPopupControllerImpl::GetIconResourceID( + const base::string16& resource_name) const { + for (size_t i = 0; i < arraysize(kDataResources); ++i) { + if (resource_name == base::ASCIIToUTF16(kDataResources[i].name)) + return kDataResources[i].id; + } + + return -1; +} + +bool AutofillPopupControllerImpl::CanDelete(size_t index) const { + // TODO(isherman): Native AddressBook suggestions on Mac and Android should + // not be considered to be deleteable. + int id = suggestions_[index].frontend_id; + return id > 0 || id == POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY || + id == POPUP_ITEM_ID_PASSWORD_ENTRY; +} + +bool AutofillPopupControllerImpl::IsWarning(size_t index) const { + return suggestions_[index].frontend_id == POPUP_ITEM_ID_WARNING_MESSAGE; +} + +gfx::Rect AutofillPopupControllerImpl::GetRowBounds(size_t index) { + int top = kPopupBorderThickness; + for (size_t i = 0; i < index; ++i) { + top += GetRowHeightFromId(suggestions_[i].frontend_id); + } + + return gfx::Rect( + kPopupBorderThickness, + top, + popup_bounds_.width() - 2 * kPopupBorderThickness, + GetRowHeightFromId(suggestions_[index].frontend_id)); +} + +void AutofillPopupControllerImpl::SetPopupBounds(const gfx::Rect& bounds) { + popup_bounds_ = bounds; + UpdateBoundsAndRedrawPopup(); +} + +const gfx::Rect& AutofillPopupControllerImpl::popup_bounds() const { + return popup_bounds_; +} + +content::WebContents* AutofillPopupControllerImpl::web_contents() { + return controller_common_->web_contents(); +} + +gfx::NativeView AutofillPopupControllerImpl::container_view() { + return controller_common_->container_view(); +} + +const gfx::RectF& AutofillPopupControllerImpl::element_bounds() const { + return controller_common_->element_bounds(); +} + +bool AutofillPopupControllerImpl::IsRTL() const { + return text_direction_ == base::i18n::RIGHT_TO_LEFT; +} + +size_t AutofillPopupControllerImpl::GetLineCount() const { + return suggestions_.size(); +} + +const autofill::Suggestion& AutofillPopupControllerImpl::GetSuggestionAt( + size_t row) const { + return suggestions_[row]; +} + +const base::string16& AutofillPopupControllerImpl::GetElidedValueAt( + size_t row) const { + return elided_values_[row]; +} + +const base::string16& AutofillPopupControllerImpl::GetElidedLabelAt( + size_t row) const { + return elided_labels_[row]; +} + +#if !defined(OS_ANDROID) +const gfx::FontList& AutofillPopupControllerImpl::GetValueFontListForRow( + size_t index) const { + if (suggestions_[index].frontend_id == POPUP_ITEM_ID_WARNING_MESSAGE) + return warning_font_list_; + + if (suggestions_[index].frontend_id == POPUP_ITEM_ID_TITLE) + return title_font_list_; + + return value_font_list_; +} + +const gfx::FontList& AutofillPopupControllerImpl::GetLabelFontList() const { + return label_font_list_; +} +#endif + +int AutofillPopupControllerImpl::selected_line() const { + return selected_line_; +} + +void AutofillPopupControllerImpl::SetSelectedLine(int selected_line) { + if (selected_line_ == selected_line) + return; + + if (selected_line_ != kNoSelection && + static_cast(selected_line_) < suggestions_.size()) + InvalidateRow(selected_line_); + + if (selected_line != kNoSelection) { + InvalidateRow(selected_line); + + if (!CanAccept(suggestions_[selected_line].frontend_id)) + selected_line = kNoSelection; + } + + selected_line_ = selected_line; + + if (selected_line_ != kNoSelection) { + delegate_->DidSelectSuggestion(elided_values_[selected_line_], + suggestions_[selected_line_].frontend_id); + } else { + delegate_->ClearPreviewedForm(); + } +} + +void AutofillPopupControllerImpl::SelectNextLine() { + int new_selected_line = selected_line_ + 1; + + // Skip over any lines that can't be selected. + while (static_cast(new_selected_line) < GetLineCount() && + !CanAccept(suggestions_[new_selected_line].frontend_id)) { + ++new_selected_line; + } + + if (new_selected_line >= static_cast(GetLineCount())) + new_selected_line = 0; + + SetSelectedLine(new_selected_line); +} + +void AutofillPopupControllerImpl::SelectPreviousLine() { + int new_selected_line = selected_line_ - 1; + + // Skip over any lines that can't be selected. + while (new_selected_line > kNoSelection && + !CanAccept(GetSuggestionAt(new_selected_line).frontend_id)) { + --new_selected_line; + } + + if (new_selected_line <= kNoSelection) + new_selected_line = GetLineCount() - 1; + + SetSelectedLine(new_selected_line); +} + +bool AutofillPopupControllerImpl::RemoveSelectedLine() { + if (selected_line_ == kNoSelection) + return false; + + DCHECK_GE(selected_line_, 0); + DCHECK_LT(selected_line_, static_cast(GetLineCount())); + + if (!CanDelete(selected_line_)) + return false; + + delegate_->RemoveSuggestion(suggestions_[selected_line_].value, + suggestions_[selected_line_].frontend_id); + + // Remove the deleted element. + suggestions_.erase(suggestions_.begin() + selected_line_); + elided_values_.erase(elided_values_.begin() + selected_line_); + elided_labels_.erase(elided_labels_.begin() + selected_line_); + + SetSelectedLine(kNoSelection); + + if (HasSuggestions()) { + delegate_->ClearPreviewedForm(); + UpdateBoundsAndRedrawPopup(); + } else { + Hide(); + } + + return true; +} + +int AutofillPopupControllerImpl::LineFromY(int y) { + int current_height = kPopupBorderThickness; + + for (size_t i = 0; i < suggestions_.size(); ++i) { + current_height += GetRowHeightFromId(suggestions_[i].frontend_id); + + if (y <= current_height) + return i; + } + + // The y value goes beyond the popup so stop the selection at the last line. + return GetLineCount() - 1; +} + +int AutofillPopupControllerImpl::GetRowHeightFromId(int identifier) const { + if (identifier == POPUP_ITEM_ID_SEPARATOR) + return kSeparatorHeight; + + return kRowHeight; +} + +bool AutofillPopupControllerImpl::CanAccept(int id) { + return id != POPUP_ITEM_ID_SEPARATOR && id != POPUP_ITEM_ID_WARNING_MESSAGE && + id != POPUP_ITEM_ID_TITLE; +} + +bool AutofillPopupControllerImpl::HasSuggestions() { + if (suggestions_.empty()) + return false; + int id = suggestions_[0].frontend_id; + return id > 0 || + id == POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY || + id == POPUP_ITEM_ID_PASSWORD_ENTRY || + id == POPUP_ITEM_ID_DATALIST_ENTRY || + id == POPUP_ITEM_ID_MAC_ACCESS_CONTACTS || + id == POPUP_ITEM_ID_SCAN_CREDIT_CARD; +} + +void AutofillPopupControllerImpl::SetValues( + const std::vector& suggestions) { + suggestions_ = suggestions; + elided_values_.resize(suggestions.size()); + elided_labels_.resize(suggestions.size()); + for (size_t i = 0; i < suggestions.size(); i++) { + elided_values_[i] = suggestions[i].value; + elided_labels_[i] = suggestions[i].label; + } +} + +void AutofillPopupControllerImpl::ShowView() { + view_->Show(); +} + +void AutofillPopupControllerImpl::InvalidateRow(size_t row) { + DCHECK(0 <= row); + DCHECK(row < suggestions_.size()); + view_->InvalidateRow(row); +} + +#if !defined(OS_ANDROID) +int AutofillPopupControllerImpl::GetDesiredPopupWidth() const { + int popup_width = controller_common_->RoundedElementBounds().width(); + for (size_t i = 0; i < GetLineCount(); ++i) { + int row_size = + gfx::GetStringWidth(GetElidedValueAt(i), value_font_list_) + + gfx::GetStringWidth(GetElidedLabelAt(i), label_font_list_) + + RowWidthWithoutText(i); + + popup_width = std::max(popup_width, row_size); + } + + return popup_width; +} + +int AutofillPopupControllerImpl::GetDesiredPopupHeight() const { + int popup_height = 2 * kPopupBorderThickness; + + for (size_t i = 0; i < suggestions_.size(); ++i) { + popup_height += GetRowHeightFromId(suggestions_[i].frontend_id); + } + + return popup_height; +} + +int AutofillPopupControllerImpl::RowWidthWithoutText(int row) const { + int row_size = kEndPadding; + + if (!elided_labels_[row].empty()) + row_size += kNamePadding; + + // Add the Autofill icon size, if required. + const base::string16& icon = suggestions_[row].icon; + if (!icon.empty()) { + int icon_width = ui::ResourceBundle::GetSharedInstance().GetImageNamed( + GetIconResourceID(icon)).Width(); + row_size += icon_width + kIconPadding; + } + + // Add the padding at the end. + row_size += kEndPadding; + + // Add room for the popup border. + row_size += 2 * kPopupBorderThickness; + + return row_size; +} + +void AutofillPopupControllerImpl::UpdatePopupBounds() { + int popup_width = GetDesiredPopupWidth(); + int popup_height = GetDesiredPopupHeight(); + + popup_bounds_ = controller_common_->GetPopupBounds(popup_width, popup_height); +} +#endif // !defined(OS_ANDROID) + +WeakPtr AutofillPopupControllerImpl::GetWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); +} + +void AutofillPopupControllerImpl::ClearState() { + // Don't clear view_, because otherwise the popup will have to get regenerated + // and this will cause flickering. + + popup_bounds_ = gfx::Rect(); + + suggestions_.clear(); + elided_values_.clear(); + elided_labels_.clear(); + + selected_line_ = kNoSelection; +} + +} // namespace autofill diff --git a/src/browser/autofill_popup_controller_impl.h b/src/browser/autofill_popup_controller_impl.h new file mode 100644 index 0000000000..8e84b6cd6e --- /dev/null +++ b/src/browser/autofill_popup_controller_impl.h @@ -0,0 +1,199 @@ +// 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_UI_AUTOFILL_AUTOFILL_POPUP_CONTROLLER_IMPL_H_ +#define CHROME_BROWSER_UI_AUTOFILL_AUTOFILL_POPUP_CONTROLLER_IMPL_H_ + +#include "base/gtest_prod_util.h" +#include "base/i18n/rtl.h" +#include "base/memory/weak_ptr.h" +#include "base/strings/string16.h" +#include "chrome/browser/ui/autofill/autofill_popup_controller.h" +#include "chrome/browser/ui/autofill/popup_controller_common.h" +#include "ui/gfx/font_list.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_f.h" + +namespace autofill { + +class AutofillPopupDelegate; +class AutofillPopupView; + +// This class is a controller for an AutofillPopupView. It implements +// AutofillPopupController to allow calls from AutofillPopupView. The +// other, public functions are available to its instantiator. +class AutofillPopupControllerImpl : public AutofillPopupController { + public: + // Creates a new |AutofillPopupControllerImpl|, or reuses |previous| if the + // construction arguments are the same. |previous| may be invalidated by this + // call. The controller will listen for keyboard input routed to + // |web_contents| while the popup is showing, unless |web_contents| is NULL. + static base::WeakPtr GetOrCreate( + base::WeakPtr previous, + base::WeakPtr delegate, + content::WebContents* web_contents, + gfx::NativeView container_view, + const gfx::RectF& element_bounds, + base::i18n::TextDirection text_direction); + + // Shows the popup, or updates the existing popup with the given values. + void Show(const std::vector& suggestions); + + // Updates the data list values currently shown with the popup. + void UpdateDataListValues(const std::vector& values, + const std::vector& labels); + + // Hides the popup and destroys the controller. This also invalidates + // |delegate_|. + void Hide() override; + + // Invoked when the view was destroyed by by someone other than this class. + void ViewDestroyed() override; + + bool HandleKeyPressEvent(const content::NativeWebKeyboardEvent& event); + + // Tells the view to capture mouse events. Must be called before |Show()|. + void set_hide_on_outside_click(bool hide_on_outside_click); + + protected: + FRIEND_TEST_ALL_PREFIXES(AutofillExternalDelegateBrowserTest, + CloseWidgetAndNoLeaking); + FRIEND_TEST_ALL_PREFIXES(AutofillPopupControllerUnitTest, + ProperlyResetController); + + AutofillPopupControllerImpl(base::WeakPtr delegate, + content::WebContents* web_contents, + gfx::NativeView container_view, + const gfx::RectF& element_bounds, + base::i18n::TextDirection text_direction); + ~AutofillPopupControllerImpl() override; + + // AutofillPopupController implementation. + void UpdateBoundsAndRedrawPopup() override; + void SetSelectionAtPoint(const gfx::Point& point) override; + bool AcceptSelectedLine() override; + void SelectionCleared() override; + void AcceptSuggestion(size_t index) override; + int GetIconResourceID(const base::string16& resource_name) const override; + bool CanDelete(size_t index) const override; + bool IsWarning(size_t index) const override; + gfx::Rect GetRowBounds(size_t index) override; + void SetPopupBounds(const gfx::Rect& bounds) override; + const gfx::Rect& popup_bounds() const override; + gfx::NativeView container_view() override; + const gfx::RectF& element_bounds() const override; + bool IsRTL() const override; + + size_t GetLineCount() const override; + const autofill::Suggestion& GetSuggestionAt(size_t row) const override; + const base::string16& GetElidedValueAt(size_t row) const override; + const base::string16& GetElidedLabelAt(size_t row) const override; +#if !defined(OS_ANDROID) + const gfx::FontList& GetValueFontListForRow(size_t index) const override; + const gfx::FontList& GetLabelFontList() const override; +#endif + int selected_line() const override; + + content::WebContents* web_contents(); + + // Change which line is currently selected by the user. + void SetSelectedLine(int selected_line); + + // Increase the selected line by 1, properly handling wrapping. + void SelectNextLine(); + + // Decrease the selected line by 1, properly handling wrapping. + void SelectPreviousLine(); + + // The user has removed a suggestion. + bool RemoveSelectedLine(); + + // Convert a y-coordinate to the closest line. + int LineFromY(int y); + + // Returns the height of a row depending on its type. + int GetRowHeightFromId(int identifier) const; + + // Returns true if the given id refers to an element that can be accepted. + bool CanAccept(int id); + + // Returns true if the popup still has non-options entries to show the user. + bool HasSuggestions(); + + // Set the Autofill entry values. Exposed to allow tests to set these values + // without showing the popup. + void SetValues(const std::vector& suggestions); + + AutofillPopupView* view() { return view_; } + + // |view_| pass throughs (virtual for testing). + virtual void ShowView(); + virtual void InvalidateRow(size_t row); + + // Protected so tests can access. +#if !defined(OS_ANDROID) + // Calculates the desired width of the popup based on its contents. + int GetDesiredPopupWidth() const; + + // Calculates the desired height of the popup based on its contents. + int GetDesiredPopupHeight() const; + + // Calculate the width of the row, excluding all the text. This provides + // the size of the row that won't be reducible (since all the text can be + // elided if there isn't enough space). + int RowWidthWithoutText(int row) const; +#endif + + base::WeakPtr GetWeakPtr(); + + // Contains common popup functionality such as popup layout. Protected for + // testing. + scoped_ptr controller_common_; + + private: + // Clear the internal state of the controller. This is needed to ensure that + // when the popup is reused it doesn't leak values between uses. + void ClearState(); + +#if !defined(OS_ANDROID) + // Calculates and sets the bounds of the popup, including placing it properly + // to prevent it from going off the screen. + void UpdatePopupBounds(); +#endif + + AutofillPopupView* view_; // Weak reference. + base::WeakPtr delegate_; + + // The bounds of the Autofill popup. + gfx::Rect popup_bounds_; + + // The text direction of the popup. + base::i18n::TextDirection text_direction_; + + // The current Autofill query values. + std::vector suggestions_; + + // Elided values and labels corresponding to the suggestions_ vector to + // ensure that it fits on the screen. + std::vector elided_values_; + std::vector elided_labels_; + +#if !defined(OS_ANDROID) + // The fonts for the popup text. + gfx::FontList value_font_list_; + gfx::FontList label_font_list_; + gfx::FontList warning_font_list_; + gfx::FontList title_font_list_; +#endif + + // The line that is currently selected by the user. + // |kNoSelection| indicates that no line is currently selected. + int selected_line_; + + base::WeakPtrFactory weak_ptr_factory_; +}; + +} // namespace autofill + +#endif // CHROME_BROWSER_UI_AUTOFILL_AUTOFILL_POPUP_CONTROLLER_IMPL_H_ diff --git a/src/browser/autofill_popup_view.h b/src/browser/autofill_popup_view.h new file mode 100644 index 0000000000..d01f7740a2 --- /dev/null +++ b/src/browser/autofill_popup_view.h @@ -0,0 +1,63 @@ +// 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_UI_AUTOFILL_AUTOFILL_POPUP_VIEW_H_ +#define CHROME_BROWSER_UI_AUTOFILL_AUTOFILL_POPUP_VIEW_H_ + +#include "ui/gfx/native_widget_types.h" + +namespace gfx { +class Rect; +} + +namespace ui { +class KeyEvent; +} + +namespace autofill { + +class AutofillPopupController; + +// The interface for creating and controlling a platform-dependent +// AutofillPopupView. +class AutofillPopupView { + public: + // The minimum amount of padding between the Autofill name and subtext, + // in pixels. + static const size_t kNamePadding = 15; + + // The amount of padding between icons in pixels. + static const int kIconPadding = 5; + + // The amount of padding at the end of the popup in pixels. + static const int kEndPadding = 3; + + // Height of the delete icon in pixels. + static const int kDeleteIconHeight = 16; + + // Width of the delete icon in pixels. + static const int kDeleteIconWidth = 16; + + // Displays the Autofill popup and fills it in with data from the controller. + virtual void Show() = 0; + + // Hides the popup from view. This will cause the popup to be deleted. + virtual void Hide() = 0; + + // Invalidates the given row and redraw it. + virtual void InvalidateRow(size_t row) = 0; + + // Refreshes the position of the popup. + virtual void UpdateBoundsAndRedrawPopup() = 0; + + // Factory function for creating the view. + static AutofillPopupView* Create(AutofillPopupController* controller); + + protected: + virtual ~AutofillPopupView() {} +}; + +} // namespace autofill + +#endif // CHROME_BROWSER_UI_AUTOFILL_AUTOFILL_POPUP_VIEW_H_ diff --git a/src/browser/autofill_popup_view_bridge.h b/src/browser/autofill_popup_view_bridge.h new file mode 100644 index 0000000000..3cf9c36f74 --- /dev/null +++ b/src/browser/autofill_popup_view_bridge.h @@ -0,0 +1,52 @@ +// 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_UI_COCOA_AUTOFILL_AUTOFILL_POPUP_VIEW_BRIDGE_H_ +#define CHROME_BROWSER_UI_COCOA_AUTOFILL_AUTOFILL_POPUP_VIEW_BRIDGE_H_ + +#include + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/mac/scoped_nsobject.h" +#include "chrome/browser/ui/autofill/autofill_popup_view.h" +#include "chrome/browser/ui/cocoa/autofill/autofill_popup_view_cocoa.h" + +@class AutofillPopupViewCocoa; +@class NSWindow; + +namespace autofill { + +class AutofillPopupViewDelegate; + +// Mac implementation of the AutofillPopupView interface. +// Serves as a bridge to an instance of the Objective-C class which actually +// implements the view. +class AutofillPopupViewBridge : public AutofillPopupView { + public: + explicit AutofillPopupViewBridge(AutofillPopupController* controller); + + private: + ~AutofillPopupViewBridge() override; + + // AutofillPopupView implementation. + void Hide() override; + void Show() override; + void InvalidateRow(size_t row) override; + void UpdateBoundsAndRedrawPopup() override; + + // Set the initial bounds of the popup, including its placement. + void SetInitialBounds(); + + // The native Cocoa view. + base::scoped_nsobject view_; + + AutofillPopupController* controller_; // Weak. + + DISALLOW_COPY_AND_ASSIGN(AutofillPopupViewBridge); +}; + +} // namespace autofill + +#endif // CHROME_BROWSER_UI_COCOA_AUTOFILL_AUTOFILL_POPUP_VIEW_BRIDGE_H_ diff --git a/src/browser/autofill_popup_view_bridge.mm b/src/browser/autofill_popup_view_bridge.mm new file mode 100644 index 0000000000..da5d8d927e --- /dev/null +++ b/src/browser/autofill_popup_view_bridge.mm @@ -0,0 +1,50 @@ +// 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. + +#import + +#include "chrome/browser/ui/cocoa/autofill/autofill_popup_view_bridge.h" + +#include "base/logging.h" +#include "chrome/browser/ui/autofill/autofill_popup_controller.h" +#include "chrome/browser/ui/autofill/autofill_popup_view_delegate.h" +#import "chrome/browser/ui/cocoa/autofill/autofill_popup_view_cocoa.h" + +namespace autofill { + +AutofillPopupViewBridge::AutofillPopupViewBridge( + AutofillPopupController* controller) + : controller_(controller) { + view_.reset( + [[AutofillPopupViewCocoa alloc] initWithController:controller + frame:NSZeroRect]); +} + +AutofillPopupViewBridge::~AutofillPopupViewBridge() { + [view_ controllerDestroyed]; + [view_ hidePopup]; +} + +void AutofillPopupViewBridge::Hide() { + delete this; +} + +void AutofillPopupViewBridge::Show() { + [view_ showPopup]; +} + +void AutofillPopupViewBridge::InvalidateRow(size_t row) { + [view_ invalidateRow:row]; +} + +void AutofillPopupViewBridge::UpdateBoundsAndRedrawPopup() { + [view_ updateBoundsAndRedrawPopup]; +} + +AutofillPopupView* AutofillPopupView::Create( + AutofillPopupController* controller) { + return new AutofillPopupViewBridge(controller); +} + +} // namespace autofill diff --git a/src/browser/autofill_popup_view_cocoa.h b/src/browser/autofill_popup_view_cocoa.h new file mode 100644 index 0000000000..b140d05471 --- /dev/null +++ b/src/browser/autofill_popup_view_cocoa.h @@ -0,0 +1,35 @@ +// 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_UI_COCOA_AUTOFILL_AUTOFILL_POPUP_VIEW_COCOA_H_ +#define CHROME_BROWSER_UI_COCOA_AUTOFILL_AUTOFILL_POPUP_VIEW_COCOA_H_ + +#import + +#import "chrome/browser/ui/cocoa/autofill/autofill_popup_base_view_cocoa.h" + +namespace autofill { +class AutofillPopupController; +} // namespace autofill + +// Draws the native Autofill popup view on Mac. +@interface AutofillPopupViewCocoa : AutofillPopupBaseViewCocoa { + @private + // The cross-platform controller for this view. + __weak autofill::AutofillPopupController* controller_; +} + +// Designated initializer. +- (id)initWithController:(autofill::AutofillPopupController*)controller + frame:(NSRect)frame; + +// Informs the view that its controller has been (or will imminently be) +// destroyed. +- (void)controllerDestroyed; + +- (void)invalidateRow:(size_t)row; + +@end + +#endif // CHROME_BROWSER_UI_COCOA_AUTOFILL_AUTOFILL_POPUP_VIEW_COCOA_H_ diff --git a/src/browser/autofill_popup_view_cocoa.mm b/src/browser/autofill_popup_view_cocoa.mm new file mode 100644 index 0000000000..a8b0e9559d --- /dev/null +++ b/src/browser/autofill_popup_view_cocoa.mm @@ -0,0 +1,275 @@ +// 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. + +#import "chrome/browser/ui/cocoa/autofill/autofill_popup_view_cocoa.h" + +#include "base/logging.h" +#include "base/strings/sys_string_conversions.h" +#include "chrome/browser/ui/autofill/autofill_popup_controller.h" +#include "chrome/browser/ui/autofill/popup_constants.h" +#include "chrome/browser/ui/cocoa/autofill/autofill_popup_view_bridge.h" +#include "components/autofill/core/browser/popup_item_ids.h" +#include "components/autofill/core/browser/suggestion.h" +#include "ui/base/cocoa/window_size_constants.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/font_list.h" +#include "ui/gfx/image/image.h" + +using autofill::AutofillPopupView; + +@interface AutofillPopupViewCocoa () + +#pragma mark - +#pragma mark Private methods + +// Draws an Autofill suggestion in the given |bounds|, labeled with the given +// |name| and |subtext| hint. If the suggestion |isSelected|, then it is drawn +// with a highlight. |index| determines the font to use, as well as the icon, +// if the row requires it -- such as for credit cards. |imageFirst| indicates +// whether the image should be drawn before the name, and with the same +// alignment, or whether it should be drawn afterwards, with the opposite +// alignment. +- (void)drawSuggestionWithName:(NSString*)name + subtext:(NSString*)subtext + index:(size_t)index + bounds:(NSRect)bounds + selected:(BOOL)isSelected + imageFirst:(BOOL)imageFirst + textYOffset:(CGFloat)textYOffset; + +// This comment block applies to all three draw* methods that follow. +// If |rightAlign| == YES. +// Draws the widget with right border aligned to |x|. +// Returns the x value of left border of the widget. +// If |rightAlign| == NO. +// Draws the widget with left border aligned to |x|. +// Returns the x value of right border of the widget. +- (CGFloat)drawName:(NSString*)name + atX:(CGFloat)x + index:(size_t)index + rightAlign:(BOOL)rightAlign + bounds:(NSRect)bounds + textYOffset:(CGFloat)textYOffset; +- (CGFloat)drawIconAtIndex:(size_t)index + atX:(CGFloat)x + rightAlign:(BOOL)rightAlign + bounds:(NSRect)bounds; +- (CGFloat)drawSubtext:(NSString*)subtext + atX:(CGFloat)x + rightAlign:(BOOL)rightAlign + bounds:(NSRect)bounds + textYOffset:(CGFloat)textYOffset; + +// Returns the icon for the row with the given |index|, or |nil| if there is +// none. +- (NSImage*)iconAtIndex:(size_t)index; + +@end + +@implementation AutofillPopupViewCocoa + +#pragma mark - +#pragma mark Initialisers + +- (id)initWithFrame:(NSRect)frame { + NOTREACHED(); + return [self initWithController:NULL frame:frame]; +} + +- (id)initWithController:(autofill::AutofillPopupController*)controller + frame:(NSRect)frame { + self = [super initWithDelegate:controller frame:frame]; + if (self) + controller_ = controller; + + return self; +} + +#pragma mark - +#pragma mark NSView implementation: + +- (void)drawRect:(NSRect)dirtyRect { + // If the view is in the process of being destroyed, don't bother drawing. + if (!controller_) + return; + + [self drawBackgroundAndBorder]; + + for (size_t i = 0; i < controller_->GetLineCount(); ++i) { + // Skip rows outside of the dirty rect. + NSRect rowBounds = + NSRectFromCGRect(controller_->GetRowBounds(i).ToCGRect()); + if (!NSIntersectsRect(rowBounds, dirtyRect)) + continue; + + if (controller_->GetSuggestionAt(i).frontend_id == autofill::POPUP_ITEM_ID_SEPARATOR) { + [self drawSeparatorWithBounds:rowBounds]; + continue; + } + + // Additional offset applied to the text in the vertical direction. + CGFloat textYOffset = 0; + BOOL imageFirst = NO; + if (controller_->GetSuggestionAt(i).frontend_id == + autofill::POPUP_ITEM_ID_MAC_ACCESS_CONTACTS) { + // Due to the weighting of the asset used for this autofill entry, the + // text needs to be bumped up by 1 pt to make it look vertically aligned. + textYOffset = -1; + imageFirst = YES; + } + + NSString* name = SysUTF16ToNSString(controller_->GetElidedValueAt(i)); + NSString* subtext = SysUTF16ToNSString(controller_->GetElidedLabelAt(i)); + BOOL isSelected = static_cast(i) == controller_->selected_line(); + [self drawSuggestionWithName:name + subtext:subtext + index:i + bounds:rowBounds + selected:isSelected + imageFirst:imageFirst + textYOffset:textYOffset]; + } +} + +#pragma mark - +#pragma mark Public API: + +- (void)controllerDestroyed { + // Since the |controller_| either already has been destroyed or is about to + // be, about the only thing we can safely do with it is to null it out. + controller_ = NULL; + [super delegateDestroyed]; +} + +- (void)invalidateRow:(size_t)row { + NSRect dirty_rect = + NSRectFromCGRect(controller_->GetRowBounds(row).ToCGRect()); + [self setNeedsDisplayInRect:dirty_rect]; +} + +#pragma mark - +#pragma mark Private API: + +- (void)drawSuggestionWithName:(NSString*)name + subtext:(NSString*)subtext + index:(size_t)index + bounds:(NSRect)bounds + selected:(BOOL)isSelected + imageFirst:(BOOL)imageFirst + textYOffset:(CGFloat)textYOffset { + // If this row is selected, highlight it. + if (isSelected) { + [[self highlightColor] set]; + [NSBezierPath fillRect:bounds]; + } + + BOOL isRTL = controller_->IsRTL(); + + // The X values of the left and right borders of the autofill widget. + CGFloat leftX = NSMinX(bounds) + AutofillPopupView::kEndPadding; + CGFloat rightX = NSMaxX(bounds) - AutofillPopupView::kEndPadding; + + // Draw left side if isRTL == NO, right side if isRTL == YES. + CGFloat x = isRTL ? rightX : leftX; + if (imageFirst) + x = [self drawIconAtIndex:index atX:x rightAlign:isRTL bounds:bounds]; + [self drawName:name + atX:x + index:index + rightAlign:isRTL + bounds:bounds + textYOffset:textYOffset]; + + // Draw right side if isRTL == NO, left side if isRTL == YES. + x = isRTL ? leftX : rightX; + if (!imageFirst) + x = [self drawIconAtIndex:index atX:x rightAlign:!isRTL bounds:bounds]; + [self drawSubtext:subtext + atX:x + rightAlign:!isRTL + bounds:bounds + textYOffset:textYOffset]; +} + +- (CGFloat)drawName:(NSString*)name + atX:(CGFloat)x + index:(size_t)index + rightAlign:(BOOL)rightAlign + bounds:(NSRect)bounds + textYOffset:(CGFloat)textYOffset { + NSColor* nameColor = + controller_->IsWarning(index) ? [self warningColor] : [self nameColor]; + NSDictionary* nameAttributes = + [NSDictionary dictionaryWithObjectsAndKeys: + controller_->GetValueFontListForRow(index).GetPrimaryFont(). + GetNativeFont(), + NSFontAttributeName, nameColor, NSForegroundColorAttributeName, + nil]; + NSSize nameSize = [name sizeWithAttributes:nameAttributes]; + x -= rightAlign ? nameSize.width : 0; + CGFloat y = bounds.origin.y + (bounds.size.height - nameSize.height) / 2; + y += textYOffset; + + [name drawAtPoint:NSMakePoint(x, y) withAttributes:nameAttributes]; + + x += rightAlign ? 0 : nameSize.width; + return x; +} + +- (CGFloat)drawIconAtIndex:(size_t)index + atX:(CGFloat)x + rightAlign:(BOOL)rightAlign + bounds:(NSRect)bounds { + NSImage* icon = [self iconAtIndex:index]; + if (!icon) + return x; + NSSize iconSize = [icon size]; + x -= rightAlign ? iconSize.width : 0; + CGFloat y = bounds.origin.y + (bounds.size.height - iconSize.height) / 2; + [icon drawInRect:NSMakeRect(x, y, iconSize.width, iconSize.height) + fromRect:NSZeroRect + operation:NSCompositeSourceOver + fraction:1.0 + respectFlipped:YES + hints:nil]; + + x += rightAlign ? -AutofillPopupView::kIconPadding + : iconSize.width + AutofillPopupView::kIconPadding; + return x; +} + +- (CGFloat)drawSubtext:(NSString*)subtext + atX:(CGFloat)x + rightAlign:(BOOL)rightAlign + bounds:(NSRect)bounds + textYOffset:(CGFloat)textYOffset { + NSDictionary* subtextAttributes = + [NSDictionary dictionaryWithObjectsAndKeys: + controller_->GetLabelFontList().GetPrimaryFont().GetNativeFont(), + NSFontAttributeName, + [self subtextColor], + NSForegroundColorAttributeName, + nil]; + NSSize subtextSize = [subtext sizeWithAttributes:subtextAttributes]; + x -= rightAlign ? subtextSize.width : 0; + CGFloat y = bounds.origin.y + (bounds.size.height - subtextSize.height) / 2; + y += textYOffset; + + [subtext drawAtPoint:NSMakePoint(x, y) withAttributes:subtextAttributes]; + x += rightAlign ? 0 : subtextSize.width; + return x; +} + +- (NSImage*)iconAtIndex:(size_t)index { + if (controller_->GetSuggestionAt(index).icon.empty()) + return nil; + + int iconId = controller_->GetIconResourceID(controller_->GetSuggestionAt(index).icon); + DCHECK_NE(-1, iconId); + + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + return rb.GetNativeImageNamed(iconId).ToNSImage(); +} + +@end diff --git a/src/browser/autofill_popup_view_delegate.h b/src/browser/autofill_popup_view_delegate.h new file mode 100644 index 0000000000..e4320fd3f6 --- /dev/null +++ b/src/browser/autofill_popup_view_delegate.h @@ -0,0 +1,59 @@ +// 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_UI_AUTOFILL_AUTOFILL_POPUP_VIEW_DELEGATE_H_ +#define CHROME_BROWSER_UI_AUTOFILL_AUTOFILL_POPUP_VIEW_DELEGATE_H_ + +#include "ui/gfx/native_widget_types.h" + +namespace gfx { +class Point; +class Rect; +class RectF; +} + +namespace autofill { + +// Base class for Controllers of Autofill-style popups. This interface is +// used by the relevant views to communicate with the controller. +class AutofillPopupViewDelegate { + public: + // Called when the popup should be hidden. Controller will be deleted after + // the view has been hidden and destroyed. + virtual void Hide() = 0; + + // Called whent the popup view was destroyed. + virtual void ViewDestroyed() = 0; + + // The user has selected |point|, e.g. by hovering the mouse cursor. |point| + // must be in popup coordinates. + virtual void SetSelectionAtPoint(const gfx::Point& point) = 0; + + // The user has accepted the currently selected line. Returns whether there + // was a selection to accept. + virtual bool AcceptSelectedLine() = 0; + + // The user cleared the current selection, e.g. by moving the mouse cursor + // out of the popup bounds. + virtual void SelectionCleared() = 0; + + // The actual bounds of the popup. + virtual const gfx::Rect& popup_bounds() const = 0; + + // The view that the form field element sits in. + virtual gfx::NativeView container_view() = 0; + + // The bounds of the form field element (screen coordinates). + virtual const gfx::RectF& element_bounds() const = 0; + + // If the current popup should be displayed in RTL mode. + virtual bool IsRTL() const = 0; + + protected: + virtual ~AutofillPopupViewDelegate() {} +}; + +} // namespace autofill + +#endif // CHROME_BROWSER_UI_AUTOFILL_AUTOFILL_POPUP_VIEW_DELEGATE_H_ diff --git a/src/browser/autofill_popup_view_gtk.cc b/src/browser/autofill_popup_view_gtk.cc new file mode 100644 index 0000000000..7aaf87e23f --- /dev/null +++ b/src/browser/autofill_popup_view_gtk.cc @@ -0,0 +1,327 @@ +// 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 "content/nw/src/browser/autofill_popup_view_gtk.h" + +#include +#include + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/ui/autofill/autofill_popup_controller.h" +#include "chrome/browser/ui/gtk/gtk_util.h" +#include "components/autofill/core/browser/popup_item_ids.h" +#include "grit/ui_resources.h" +#include "ui/base/gtk/gtk_hig_constants.h" +#include "ui/base/gtk/gtk_windowing.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/gtk_compat.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/pango_util.h" +#include "ui/gfx/text_utils.h" +#include "ui/gfx/image/cairo_cached_surface.h" + + +namespace { + +const GdkColor kBorderColor = GDK_COLOR_RGB(0xc7, 0xca, 0xce); +const GdkColor kHoveredBackgroundColor = GDK_COLOR_RGB(0xcd, 0xcd, 0xcd); +const GdkColor kNameColor = GDK_COLOR_RGB(0x00, 0x00, 0x00); +const GdkColor kWarningColor = GDK_COLOR_RGB(0x7f, 0x7f, 0x7f); +const GdkColor kSubtextColor = GDK_COLOR_RGB(0x7f, 0x7f, 0x7f); + +} // namespace + +namespace gtk_util { + +void SetLayoutText(PangoLayout* layout, const base::string16& text) { + // Pango is really easy to overflow and send into a computational death + // spiral that can corrupt the screen. Assume that we'll never have more than + // 2000 characters, which should be a safe assumption until we all get robot + // eyes. http://crbug.com/66576 + std::string text_utf8 = base::UTF16ToUTF8(text); + if (text_utf8.length() > 2000) + text_utf8 = text_utf8.substr(0, 2000); + + pango_layout_set_text(layout, text_utf8.data(), text_utf8.length()); +} + +void DrawFullImage(cairo_t* cr, + GtkWidget* widget, + const gfx::Image& image, + gint dest_x, + gint dest_y) { + gfx::CairoCachedSurface* surface = image.ToCairo(); + surface->SetSource(cr, widget, dest_x, dest_y); + cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT); + cairo_rectangle(cr, dest_x, dest_y, surface->Width(), surface->Height()); + cairo_fill(cr); +} + +} + +namespace autofill { + +AutofillPopupViewGtk::AutofillPopupViewGtk( + AutofillPopupController* controller) + : controller_(controller), + window_(gtk_window_new(GTK_WINDOW_POPUP)) { + gtk_window_set_resizable(GTK_WINDOW(window_), FALSE); + gtk_widget_set_app_paintable(window_, TRUE); + gtk_widget_set_double_buffered(window_, TRUE); + + // Setup the window to ensure it receives the expose event. + gtk_widget_add_events(window_, GDK_BUTTON_MOTION_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_EXPOSURE_MASK | + GDK_POINTER_MOTION_MASK); + + GtkWidget* toplevel_window = gtk_widget_get_toplevel( + controller->container_view()); + signals_.Connect(toplevel_window, "configure-event", + G_CALLBACK(HandleConfigureThunk), this); + g_signal_connect(window_, "expose-event", + G_CALLBACK(HandleExposeThunk), this); + g_signal_connect(window_, "leave-notify-event", + G_CALLBACK(HandleLeaveThunk), this); + g_signal_connect(window_, "motion-notify-event", + G_CALLBACK(HandleMotionThunk), this); + g_signal_connect(window_, "button-release-event", + G_CALLBACK(HandleButtonReleaseThunk), this); + + // Cache the layout so we don't have to create it for every expose. + layout_ = gtk_widget_create_pango_layout(window_, NULL); +} + +AutofillPopupViewGtk::~AutofillPopupViewGtk() { + g_object_unref(layout_); + gtk_widget_destroy(window_); +} + +void AutofillPopupViewGtk::Hide() { + delete this; +} + +void AutofillPopupViewGtk::Show() { + UpdateBoundsAndRedrawPopup(); + + gtk_widget_show(window_); + + GtkWidget* parent_window = + gtk_widget_get_toplevel(controller_->container_view()); + ui::StackPopupWindow(window_, parent_window); +} + +void AutofillPopupViewGtk::InvalidateRow(size_t row) { + GdkRectangle row_rect = controller_->GetRowBounds(row).ToGdkRectangle(); + GdkWindow* gdk_window = gtk_widget_get_window(window_); + gdk_window_invalidate_rect(gdk_window, &row_rect, FALSE); +} + +void AutofillPopupViewGtk::UpdateBoundsAndRedrawPopup() { + gtk_widget_set_size_request(window_, + controller_->popup_bounds().width(), + controller_->popup_bounds().height()); + gtk_window_move(GTK_WINDOW(window_), + controller_->popup_bounds().x(), + controller_->popup_bounds().y()); + + GdkWindow* gdk_window = gtk_widget_get_window(window_); + GdkRectangle popup_rect = controller_->popup_bounds().ToGdkRectangle(); + if (gdk_window != NULL) + gdk_window_invalidate_rect(gdk_window, &popup_rect, FALSE); +} + +gboolean AutofillPopupViewGtk::HandleConfigure(GtkWidget* widget, + GdkEventConfigure* event) { + controller_->Hide(); + return FALSE; +} + +gboolean AutofillPopupViewGtk::HandleButtonRelease(GtkWidget* widget, + GdkEventButton* event) { + // We only care about the left click. + if (event->button != 1) + return FALSE; + + controller_->SetSelectionAtPoint(gfx::Point(event->x, event->y)); + controller_->AcceptSelectedLine(); + return TRUE; +} + +gboolean AutofillPopupViewGtk::HandleExpose(GtkWidget* widget, + GdkEventExpose* event) { + cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(gtk_widget_get_window(widget))); + gdk_cairo_rectangle(cr, &event->area); + cairo_clip(cr); + + // Draw the 1px border around the entire window. + gdk_cairo_set_source_color(cr, &kBorderColor); + gdk_cairo_rectangle(cr, &widget->allocation); + cairo_stroke(cr); + SetUpLayout(); + + gfx::Rect damage_rect(event->area); + + for (size_t i = 0; i < controller_->names().size(); ++i) { + gfx::Rect line_rect = controller_->GetRowBounds(i); + // Only repaint and layout damaged lines. + if (!line_rect.Intersects(damage_rect)) + continue; + + if (controller_->identifiers()[i] == POPUP_ITEM_ID_SEPARATOR) + DrawSeparator(cr, line_rect); + else + DrawAutofillEntry(cr, i, line_rect); + } + + cairo_destroy(cr); + + return TRUE; +} + +gboolean AutofillPopupViewGtk::HandleLeave(GtkWidget* widget, + GdkEventCrossing* event) { + controller_->SelectionCleared(); + + return FALSE; +} + +gboolean AutofillPopupViewGtk::HandleMotion(GtkWidget* widget, + GdkEventMotion* event) { + controller_->SetSelectionAtPoint(gfx::Point(event->x, event->y)); + + return TRUE; +} + +void AutofillPopupViewGtk::SetUpLayout() { + pango_layout_set_width(layout_, window_->allocation.width * PANGO_SCALE); + pango_layout_set_height(layout_, window_->allocation.height * PANGO_SCALE); +} + +void AutofillPopupViewGtk::SetLayoutText(const base::string16& text, + const gfx::FontList& font_list, + const GdkColor text_color) { + PangoAttrList* attrs = pango_attr_list_new(); + + PangoAttribute* fg_attr = pango_attr_foreground_new(text_color.red, + text_color.green, + text_color.blue); + pango_attr_list_insert(attrs, fg_attr); // Ownership taken. + + pango_layout_set_attributes(layout_, attrs); // Ref taken. + pango_attr_list_unref(attrs); + + gfx::ScopedPangoFontDescription font_description( + pango_font_description_from_string( + font_list.GetFontDescriptionString().c_str())); + pango_layout_set_font_description(layout_, font_description.get()); + + gtk_util::SetLayoutText(layout_, text); + + // The popup is already the correct size for the text, so set the width to -1 + // to prevent additional wrapping or ellipsization. + pango_layout_set_width(layout_, -1); +} + +void AutofillPopupViewGtk::DrawSeparator(cairo_t* cairo_context, + const gfx::Rect& separator_rect) { + cairo_save(cairo_context); + cairo_move_to(cairo_context, 0, separator_rect.y()); + cairo_line_to(cairo_context, + separator_rect.width(), + separator_rect.y() + separator_rect.height()); + cairo_stroke(cairo_context); + cairo_restore(cairo_context); +} + +void AutofillPopupViewGtk::DrawAutofillEntry(cairo_t* cairo_context, + size_t index, + const gfx::Rect& entry_rect) { + if (controller_->selected_line() == static_cast(index)) { + gdk_cairo_set_source_color(cairo_context, &kHoveredBackgroundColor); + cairo_rectangle(cairo_context, entry_rect.x(), entry_rect.y(), + entry_rect.width(), entry_rect.height()); + cairo_fill(cairo_context); + } + + // Draw the value. + SetLayoutText(controller_->names()[index], + controller_->GetNameFontListForRow(index), + controller_->IsWarning(index) ? kWarningColor : kNameColor); + int value_text_width = + gfx::GetStringWidth(controller_->names()[index], + controller_->GetNameFontListForRow(index)); + + // Center the text within the line. + int row_height = entry_rect.height(); + int value_content_y = std::max( + entry_rect.y(), + entry_rect.y() + + (row_height - + controller_->GetNameFontListForRow(index).GetHeight()) / 2); + + bool is_rtl = controller_->IsRTL(); + int value_content_x = is_rtl ? + entry_rect.width() - value_text_width - kEndPadding : kEndPadding; + + cairo_save(cairo_context); + cairo_move_to(cairo_context, value_content_x, value_content_y); + pango_cairo_show_layout(cairo_context, layout_); + cairo_restore(cairo_context); + + // Use this to figure out where all the other Autofill items should be placed. + int x_align_left = is_rtl ? kEndPadding : entry_rect.width() - kEndPadding; + + // Draw the Autofill icon, if one exists + if (!controller_->icons()[index].empty()) { + int icon = controller_->GetIconResourceID(controller_->icons()[index]); + DCHECK_NE(-1, icon); + const gfx::Image& image = + ui::ResourceBundle::GetSharedInstance().GetImageNamed(icon); + int icon_y = entry_rect.y() + (row_height - image.Height()) / 2; + + x_align_left += is_rtl ? 0 : -image.Width(); + + cairo_save(cairo_context); + gtk_util::DrawFullImage(cairo_context, + window_, + image, + x_align_left, + icon_y); + cairo_restore(cairo_context); + + x_align_left += is_rtl ? image.Width() + kIconPadding : -kIconPadding; + } + + // Draw the subtext. + SetLayoutText(controller_->subtexts()[index], + controller_->subtext_font_list(), + kSubtextColor); + if (!is_rtl) { + x_align_left -= gfx::GetStringWidth(controller_->subtexts()[index], + controller_->subtext_font_list()); + } + + // Center the text within the line. + int subtext_content_y = std::max( + entry_rect.y(), + entry_rect.y() + + (row_height - controller_->subtext_font_list().GetHeight()) / 2); + + cairo_save(cairo_context); + cairo_move_to(cairo_context, x_align_left, subtext_content_y); + pango_cairo_show_layout(cairo_context, layout_); + cairo_restore(cairo_context); +} + +AutofillPopupView* AutofillPopupView::Create( + AutofillPopupController* controller) { + return new AutofillPopupViewGtk(controller); +} + +} // namespace autofill diff --git a/src/browser/autofill_popup_view_gtk.h b/src/browser/autofill_popup_view_gtk.h new file mode 100644 index 0000000000..7cc5456d68 --- /dev/null +++ b/src/browser/autofill_popup_view_gtk.h @@ -0,0 +1,100 @@ +// 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_UI_GTK_AUTOFILL_AUTOFILL_POPUP_VIEW_GTK_H_ +#define CHROME_BROWSER_UI_GTK_AUTOFILL_AUTOFILL_POPUP_VIEW_GTK_H_ + +#include +#include + +#include "base/compiler_specific.h" +#include "base/strings/string16.h" +#include "chrome/browser/ui/autofill/autofill_popup_view.h" +#include "ui/base/glib/glib_integers.h" +#include "ui/base/gtk/gtk_signal.h" +#include "ui/base/gtk/gtk_signal_registrar.h" + +class Profile; + +namespace content { +class RenderViewHost; +} + +namespace gfx { +class FontList; +class Rect; +} + +typedef struct _GdkEventButton GdkEventButton; +typedef struct _GdkEventConfigure GdkEventConfigure; +typedef struct _GdkEventCrossing GdkEventCrossing; +typedef struct _GdkEventExpose GdkEventExpose; +typedef struct _GdkEventKey GdkEventKey; +typedef struct _GdkEventMotion GdkEventMotion; +typedef struct _GdkColor GdkColor; +typedef struct _GtkWidget GtkWidget; + +namespace autofill { + +class AutofillPopupController; + +// Gtk implementation for AutofillPopupView interface. +class AutofillPopupViewGtk : public AutofillPopupView { + public: + explicit AutofillPopupViewGtk(AutofillPopupController* controller); + + private: + virtual ~AutofillPopupViewGtk(); + + // AutofillPopupView implementation. + virtual void Hide() OVERRIDE; + virtual void Show() OVERRIDE; + virtual void InvalidateRow(size_t row) OVERRIDE; + virtual void UpdateBoundsAndRedrawPopup() OVERRIDE; + + CHROMEGTK_CALLBACK_1(AutofillPopupViewGtk, gboolean, HandleConfigure, + GdkEventConfigure*); + CHROMEGTK_CALLBACK_1(AutofillPopupViewGtk, gboolean, HandleButtonRelease, + GdkEventButton*); + CHROMEGTK_CALLBACK_1(AutofillPopupViewGtk, gboolean, HandleExpose, + GdkEventExpose*); + CHROMEGTK_CALLBACK_1(AutofillPopupViewGtk, gboolean, HandleLeave, + GdkEventCrossing*) + CHROMEGTK_CALLBACK_1(AutofillPopupViewGtk, gboolean, HandleMotion, + GdkEventMotion*); + + // Set up the pango layout to display the autofill results. + void SetUpLayout(); + + // Set up the pango layout to print the given text and have it's width match + // the text's (this gives us better control when placing the text box). + void SetLayoutText(const base::string16& text, + const gfx::FontList& font_list, + const GdkColor text_color); + + // Draw the separator as the given |separator_rect|. + void DrawSeparator(cairo_t* cairo_context, const gfx::Rect& separator_rect); + + // Draw the given autofill entry in |entry_rect|. + void DrawAutofillEntry(cairo_t* cairo_context, + size_t index, + const gfx::Rect& entry_rect); + + // Set the initial bounds of the popup to show, including the placement + // of it. + void SetInitialBounds(); + + AutofillPopupController* controller_; // Weak reference. + + ui::GtkSignalRegistrar signals_; + + GtkWidget* window_; // Strong reference. + PangoLayout* layout_; // Strong reference. + + DISALLOW_COPY_AND_ASSIGN(AutofillPopupViewGtk); +}; + +} // namespace autofill + +#endif // CHROME_BROWSER_UI_GTK_AUTOFILL_AUTOFILL_POPUP_VIEW_GTK_H_ diff --git a/src/browser/autofill_popup_view_views.cc b/src/browser/autofill_popup_view_views.cc new file mode 100644 index 0000000000..f3cb3507fd --- /dev/null +++ b/src/browser/autofill_popup_view_views.cc @@ -0,0 +1,143 @@ +// 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/ui/views/autofill/autofill_popup_view_views.h" + +#include "chrome/browser/ui/autofill/autofill_popup_controller.h" +#include "components/autofill/core/browser/popup_item_ids.h" +#include "components/autofill/core/browser/suggestion.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/events/keycodes/keyboard_codes.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/text_utils.h" +#include "ui/views/border.h" +#include "ui/views/widget/widget.h" + +namespace autofill { + +AutofillPopupViewViews::AutofillPopupViewViews( + AutofillPopupController* controller, views::Widget* observing_widget) + : AutofillPopupBaseView(controller, observing_widget), + controller_(controller) {} + +AutofillPopupViewViews::~AutofillPopupViewViews() {} + +void AutofillPopupViewViews::Show() { + DoShow(); +} + +void AutofillPopupViewViews::Hide() { + // The controller is no longer valid after it hides us. + controller_ = NULL; + + DoHide(); +} + +void AutofillPopupViewViews::UpdateBoundsAndRedrawPopup() { + DoUpdateBoundsAndRedrawPopup(); +} + +void AutofillPopupViewViews::OnPaint(gfx::Canvas* canvas) { + if (!controller_) + return; + + canvas->DrawColor(kPopupBackground); + OnPaintBorder(canvas); + + for (size_t i = 0; i < controller_->GetLineCount(); ++i) { + gfx::Rect line_rect = controller_->GetRowBounds(i); + + if (controller_->GetSuggestionAt(i).frontend_id == + POPUP_ITEM_ID_SEPARATOR) { + canvas->FillRect(line_rect, kItemTextColor); + } else { + DrawAutofillEntry(canvas, i, line_rect); + } + } +} + +void AutofillPopupViewViews::InvalidateRow(size_t row) { + SchedulePaintInRect(controller_->GetRowBounds(row)); +} + +void AutofillPopupViewViews::DrawAutofillEntry(gfx::Canvas* canvas, + int index, + const gfx::Rect& entry_rect) { + if (controller_->selected_line() == index) + canvas->FillRect(entry_rect, kHoveredBackgroundColor); + + const bool is_rtl = controller_->IsRTL(); + const int value_text_width = + gfx::GetStringWidth(controller_->GetElidedValueAt(index), + controller_->GetValueFontListForRow(index)); + const int value_content_x = is_rtl ? + entry_rect.width() - value_text_width - kEndPadding : kEndPadding; + + canvas->DrawStringRectWithFlags( + controller_->GetElidedValueAt(index), + controller_->GetValueFontListForRow(index), + controller_->IsWarning(index) ? kWarningTextColor : kValueTextColor, + gfx::Rect(value_content_x, + entry_rect.y(), + value_text_width, + entry_rect.height()), + gfx::Canvas::TEXT_ALIGN_CENTER); + + // Use this to figure out where all the other Autofill items should be placed. + int x_align_left = is_rtl ? kEndPadding : entry_rect.width() - kEndPadding; + + // Draw the Autofill icon, if one exists + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + int row_height = controller_->GetRowBounds(index).height(); + if (!controller_->GetSuggestionAt(index).icon.empty()) { + int icon = controller_->GetIconResourceID( + controller_->GetSuggestionAt(index).icon); + DCHECK_NE(-1, icon); + const gfx::ImageSkia* image = rb.GetImageSkiaNamed(icon); + int icon_y = entry_rect.y() + (row_height - image->height()) / 2; + + x_align_left += is_rtl ? 0 : -image->width(); + + canvas->DrawImageInt(*image, x_align_left, icon_y); + + x_align_left += is_rtl ? image->width() + kIconPadding : -kIconPadding; + } + + // Draw the label text. + const int label_width = + gfx::GetStringWidth(controller_->GetElidedLabelAt(index), + controller_->GetLabelFontList()); + if (!is_rtl) + x_align_left -= label_width; + + canvas->DrawStringRectWithFlags( + controller_->GetElidedLabelAt(index), + controller_->GetLabelFontList(), + kItemTextColor, + gfx::Rect(x_align_left, + entry_rect.y(), + label_width, + entry_rect.height()), + gfx::Canvas::TEXT_ALIGN_CENTER); +} + +AutofillPopupView* AutofillPopupView::Create( + AutofillPopupController* controller) { + views::Widget* observing_widget = + views::Widget::GetTopLevelWidgetForNativeView( + controller->container_view()); + + // If the top level widget can't be found, cancel the popup since we can't + // fully set it up. + if (!observing_widget) + return NULL; + + return new AutofillPopupViewViews(controller, observing_widget); +} + +} // namespace autofill diff --git a/src/browser/autofill_popup_view_views.h b/src/browser/autofill_popup_view_views.h new file mode 100644 index 0000000000..cd2d95e35d --- /dev/null +++ b/src/browser/autofill_popup_view_views.h @@ -0,0 +1,49 @@ +// 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_UI_VIEWS_AUTOFILL_AUTOFILL_POPUP_VIEW_VIEWS_H_ +#define CHROME_BROWSER_UI_VIEWS_AUTOFILL_AUTOFILL_POPUP_VIEW_VIEWS_H_ + +#include "chrome/browser/ui/autofill/autofill_popup_view.h" +#include "chrome/browser/ui/views/autofill/autofill_popup_base_view.h" + +class AutofillPopupController; + +namespace autofill { + +// Views toolkit implementation for AutofillPopupView. +class AutofillPopupViewViews : public AutofillPopupBaseView, + public AutofillPopupView { + public: + // The observing widget should be the top level widget for the native + // view, which we need to listen to for several signals that indicate the + // popup should be closed. + AutofillPopupViewViews(AutofillPopupController* controller, + views::Widget* observing_widget); + + private: + ~AutofillPopupViewViews() override; + + // AutofillPopupView implementation. + void Show() override; + void Hide() override; + void InvalidateRow(size_t row) override; + void UpdateBoundsAndRedrawPopup() override; + + // views::Views implementation + void OnPaint(gfx::Canvas* canvas) override; + + // Draw the given autofill entry in |entry_rect|. + void DrawAutofillEntry(gfx::Canvas* canvas, + int index, + const gfx::Rect& entry_rect); + + AutofillPopupController* controller_; // Weak reference. + + DISALLOW_COPY_AND_ASSIGN(AutofillPopupViewViews); +}; + +} // namespace autofill + +#endif // CHROME_BROWSER_UI_VIEWS_AUTOFILL_AUTOFILL_POPUP_VIEW_VIEWS_H_ diff --git a/src/browser/browser_dialogs.h b/src/browser/browser_dialogs.h new file mode 100644 index 0000000000..610a786689 --- /dev/null +++ b/src/browser/browser_dialogs.h @@ -0,0 +1,26 @@ +// 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 NW_BROWSER_UI_BROWSER_DIALOGS_H_ +#define NW_BROWSER_UI_BROWSER_DIALOGS_H_ + +#include "base/callback.h" +#include "ipc/ipc_message.h" // For IPC_MESSAGE_LOG_ENABLED. +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/native_widget_types.h" + +namespace content { +class BrowserContext; +class ColorChooser; +class WebContents; +} + +namespace nw { + +// Shows a color chooser that reports to the given WebContents. +content::ColorChooser* ShowColorChooser(content::WebContents* web_contents, + SkColor initial_color); +} // namespace nw + +#endif // NW_BROWSER_UI_BROWSER_DIALOGS_H_ diff --git a/src/browser/browser_view_layout.cc b/src/browser/browser_view_layout.cc new file mode 100644 index 0000000000..d30d49f22e --- /dev/null +++ b/src/browser/browser_view_layout.cc @@ -0,0 +1,74 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/nw/src/browser/browser_view_layout.h" +#include "content/nw/src/common/shell_switches.h" + +#include "base/logging.h" + +using views::View; + +namespace nw { + +BrowserViewLayout::BrowserViewLayout() + : menu_bar_(NULL), web_view_(NULL), tool_bar_(NULL) +{ +} + +BrowserViewLayout::~BrowserViewLayout() {} + +void BrowserViewLayout::Layout(View* host) { + if (!host->has_children()) + return; + + int y = 0; + gfx::Size host_size = host->GetContentsBounds().size(); + + if (menu_bar_) { + menu_bar_->SetBounds(0, y, host_size.width(), kMenuHeight); + y += kMenuHeight; + } + + if (tool_bar_) { + int height = tool_bar_->GetPreferredSize().height(); + tool_bar_->SetBounds(0, y, host_size.width(), height); + y += height; + } + + web_view_->SetBounds(0, y, host_size.width(), host_size.height() - y); +} + +gfx::Size BrowserViewLayout::GetPreferredSize(const View* host) const { + if (!host->has_children()) + return gfx::Size(); + + gfx::Rect rect(web_view_->GetPreferredSize()); + rect.Inset(-host->GetInsets()); + if (menu_bar_) + rect.Inset(0, 0, 0, -kMenuHeight); + + if (tool_bar_) + rect.Inset(0, 0, 0, -tool_bar_->GetPreferredSize().height()); + + return rect.size(); +} + +int BrowserViewLayout::GetPreferredHeightForWidth(const View* host, int width) const { + if (!host->has_children()) + return 0; + + const gfx::Insets insets = host->GetInsets(); + int ret = web_view_->GetHeightForWidth(width - insets.width()) + + insets.height(); + + if (menu_bar_) + ret += kMenuHeight; + + if (tool_bar_) + ret += tool_bar_->GetHeightForWidth(width - insets.width()); + + return ret; +} + +} // namespace views diff --git a/src/browser/browser_view_layout.h b/src/browser/browser_view_layout.h new file mode 100644 index 0000000000..e70a25cbc0 --- /dev/null +++ b/src/browser/browser_view_layout.h @@ -0,0 +1,49 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NW_BROWSER_VIEW_LAYOUT_H_ +#define NW_BROWSER_VIEW_LAYOUT_H_ + +#include "base/compiler_specific.h" +#include "ui/views/layout/layout_manager.h" +#include "ui/views/view.h" + +namespace nw { + +/////////////////////////////////////////////////////////////////////////////// +// +// copied from ui/views/layout/FillLayout +// +/////////////////////////////////////////////////////////////////////////////// +class BrowserViewLayout : public views::LayoutManager { + public: + BrowserViewLayout(); + ~BrowserViewLayout() override; + + // Overridden from LayoutManager: + void Layout(views::View* host) override; + gfx::Size GetPreferredSize(const views::View* host) const override; + int GetPreferredHeightForWidth(const views::View* host, + int width) const override; + + void set_menu_bar(views::View* menu_bar) { menu_bar_ = menu_bar; } + views::View* menu_bar() { return menu_bar_; } + + void set_web_view(views::View* web_view) { web_view_ = web_view; } + views::View* web_view() { return web_view_; } + + void set_tool_bar(views::View* tool_bar) { tool_bar_ = tool_bar; } + views::View* tool_bar() { return tool_bar_; } + + private: + views::View* menu_bar_; + views::View* web_view_; + views::View* tool_bar_; + + DISALLOW_COPY_AND_ASSIGN(BrowserViewLayout); +}; + +} // namespace nw + +#endif // NW_BROWSER_VIEW_LAYOUT_H_ diff --git a/src/browser/capture_page_helper.cc b/src/browser/capture_page_helper.cc index 0cabfdfab6..97943a84d8 100644 --- a/src/browser/capture_page_helper.cc +++ b/src/browser/capture_page_helper.cc @@ -25,7 +25,7 @@ #include "base/base64.h" #include "base/bind.h" #include "base/stl_util.h" -#include "base/stringprintf.h" +#include "base/strings/stringprintf.h" #include "content/nw/src/api/api_messages.h" #include "content/nw/src/nw_shell.h" #include "content/nw/src/renderer/common/render_messages.h" @@ -35,7 +35,7 @@ #include "skia/ext/platform_canvas.h" #include "ui/gfx/codec/jpeg_codec.h" #include "ui/gfx/codec/png_codec.h" -#include "ui/gfx/rect.h" +#include "ui/gfx/geometry/rect.h" namespace nw { @@ -43,8 +43,6 @@ namespace capture_page_helper_constants { const char kFormatValueJpeg[] = "jpeg"; const char kFormatValuePng[] = "png"; -const char kMimeTypeJpeg[] = "image/jpeg"; -const char kMimeTypePng[] = "image/png"; const int kDefaultQuality = 90; @@ -54,11 +52,11 @@ namespace keys = nw::capture_page_helper_constants; // static scoped_refptr CapturePageHelper::Create( - content::Shell* shell) { + const base::WeakPtr& shell) { return make_scoped_refptr(new CapturePageHelper(shell)); } -CapturePageHelper::CapturePageHelper(content::Shell *shell) +CapturePageHelper::CapturePageHelper(const base::WeakPtr&shell) : content::WebContentsObserver(shell->web_contents()), shell_(shell) { } @@ -90,13 +88,11 @@ void CapturePageHelper::StartCapturePage(const std::string& image_format_str) { gfx::Rect(), view->GetViewBounds().size(), base::Bind(&CapturePageHelper::CopyFromBackingStoreComplete, - this)); + this), kN32_SkColorType); } -void CapturePageHelper::CopyFromBackingStoreComplete( - bool succeeded, - const SkBitmap& bitmap) { - if (succeeded) { +void CapturePageHelper::CopyFromBackingStoreComplete(const SkBitmap& bitmap, content::ReadbackResponse response) { + if (response == content::READBACK_SUCCESS) { // Get image from backing store. SendResultFromBitmap(bitmap); return; @@ -110,7 +106,6 @@ void CapturePageHelper::SendResultFromBitmap(const SkBitmap& screen_capture) { std::vector data; SkAutoLockPixels screen_capture_lock(screen_capture); bool encoded = false; - std::string mime_type; switch (image_format_) { case FORMAT_JPEG: encoded = gfx::JPEGCodec::Encode( @@ -120,15 +115,12 @@ void CapturePageHelper::SendResultFromBitmap(const SkBitmap& screen_capture) { screen_capture.height(), static_cast(screen_capture.rowBytes()), keys::kDefaultQuality, - &data); - mime_type = keys::kMimeTypeJpeg; - break; + &data); break; case FORMAT_PNG: encoded = gfx::PNGCodec::EncodeBGRASkBitmap( screen_capture, true, // Discard transparency. &data); - mime_type = keys::kMimeTypePng; break; default: NOTREACHED() << "Invalid image format."; @@ -144,10 +136,8 @@ void CapturePageHelper::SendResultFromBitmap(const SkBitmap& screen_capture) { reinterpret_cast(vector_as_array(&data)), data.size()); base::Base64Encode(stream_as_string, &base64_result); - base64_result.insert(0, base::StringPrintf("data:%s;base64,", - mime_type.c_str())); - - shell_->SendEvent("capturepagedone", base64_result); + + shell_->SendEvent("__nw_capturepagedone", base64_result ); } void CapturePageHelper::OnSnapshot(const SkBitmap& bitmap) { diff --git a/src/browser/capture_page_helper.h b/src/browser/capture_page_helper.h index 880152b413..7b82b225c0 100644 --- a/src/browser/capture_page_helper.h +++ b/src/browser/capture_page_helper.h @@ -22,7 +22,9 @@ #define CONTENT_NW_SRC_BROWSER_CAPTURE_PAGE_HELPER_H_ #include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" #include "content/public/browser/web_contents_observer.h" +#include "content/public/browser/readback_types.h" namespace content { class Shell; @@ -56,29 +58,27 @@ class CapturePageHelper : public base::RefCountedThreadSafe, FORMAT_PNG }; - static scoped_refptr Create(content::Shell *shell); + static scoped_refptr Create(const base::WeakPtr& shell); // Capture a snapshot of the page. void StartCapturePage(const std::string& image_format_str); private: - CapturePageHelper(content::Shell *shell); - virtual ~CapturePageHelper(); + CapturePageHelper(const base::WeakPtr& shell); + ~CapturePageHelper() override; // Internal helpers ---------------------------------------------------------- // Message handler. void OnSnapshot(const SkBitmap& bitmap); - void CopyFromBackingStoreComplete( - bool succeeded, - const SkBitmap& bitmap); + void CopyFromBackingStoreComplete(const SkBitmap& bitmap, content::ReadbackResponse response); void SendResultFromBitmap(const SkBitmap& screen_capture); // content::WebContentsObserver overrides: - virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + bool OnMessageReceived(const IPC::Message& message) override; - content::Shell* shell_; + base::WeakPtr shell_; // The format (JPEG vs PNG) of the resulting image. Set in StartCapturePage(). ImageFormat image_format_; diff --git a/src/browser/chrome_crash_reporter_client.cc b/src/browser/chrome_crash_reporter_client.cc new file mode 100644 index 0000000000..de5f3ef308 --- /dev/null +++ b/src/browser/chrome_crash_reporter_client.cc @@ -0,0 +1,374 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/app/chrome_crash_reporter_client.h" + +#include "base/atomicops.h" +#include "base/command_line.h" +#include "base/environment.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/path_service.h" +#include "base/strings/safe_sprintf.h" +#include "base/strings/string_split.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_result_codes.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/crash_keys.h" +#include "chrome/common/env_vars.h" +#include "chrome/installer/util/google_update_settings.h" + +#include "content/nw/src/nw_version.h" + +#if defined(OS_WIN) +#include + +#include "base/file_version_info.h" +#include "base/win/registry.h" +#include "chrome/installer/util/google_chrome_sxs_distribution.h" +#include "chrome/installer/util/install_util.h" +#include "chrome/installer/util/util_constants.h" +//#include "policy/policy_constants.h" +#endif + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS) +#include "chrome/browser/crash_upload_list.h" +//#include "chrome/common/chrome_version_info_values.h" +#endif + +#if defined(OS_POSIX) +#include "base/debug/dump_without_crashing.h" +#endif + +#if defined(OS_ANDROID) +#include "chrome/common/descriptors_android.h" +#endif + +#if defined(OS_CHROMEOS) +#include "chrome/common/chrome_version_info.h" +#include "chromeos/chromeos_switches.h" +#endif + +namespace chrome { + +namespace { + +#if defined(OS_WIN) +// This is the minimum version of google update that is required for deferred +// crash uploads to work. +const char kMinUpdateVersion[] = "1.3.21.115"; + +// The value name prefix will be of the form {chrome-version}-{pid}-{timestamp} +// (i.e., "#####.#####.#####.#####-########-########") which easily fits into a +// 63 character buffer. +const char kBrowserCrashDumpPrefixTemplate[] = "%s-%08x-%08x"; +const size_t kBrowserCrashDumpPrefixLength = 63; +char g_browser_crash_dump_prefix[kBrowserCrashDumpPrefixLength + 1] = {}; + +// These registry key to which we'll write a value for each crash dump attempt. +HKEY g_browser_crash_dump_regkey = NULL; + +// A atomic counter to make each crash dump value name unique. +base::subtle::Atomic32 g_browser_crash_dump_count = 0; +#endif + +} // namespace + +ChromeCrashReporterClient::ChromeCrashReporterClient() {} + +ChromeCrashReporterClient::~ChromeCrashReporterClient() {} + +void ChromeCrashReporterClient::SetCrashReporterClientIdFromGUID( + const std::string& client_guid) { + crash_keys::SetCrashClientIdFromGUID(client_guid); +} + +#if defined(OS_WIN) +bool ChromeCrashReporterClient::GetAlternativeCrashDumpLocation( + base::FilePath* crash_dir) { + // By setting the BREAKPAD_DUMP_LOCATION environment variable, an alternate + // location to write breakpad crash dumps can be set. + scoped_ptr env(base::Environment::Create()); + std::string alternate_crash_dump_location; + if (env->GetVar("BREAKPAD_DUMP_LOCATION", &alternate_crash_dump_location)) { + *crash_dir = base::FilePath::FromUTF8Unsafe(alternate_crash_dump_location); + return true; + } + + return false; +} + +void ChromeCrashReporterClient::GetProductNameAndVersion( + const base::FilePath& exe_path, + base::string16* product_name, + base::string16* version, + base::string16* special_build, + base::string16* channel_name) { + DCHECK(product_name); + DCHECK(version); + DCHECK(special_build); + DCHECK(channel_name); + + scoped_ptr version_info( + FileVersionInfo::CreateFileVersionInfo(exe_path)); + + if (version_info.get()) { + // Get the information from the file. + *version = version_info->product_version(); + if (!version_info->is_official_build()) + version->append(base::ASCIIToUTF16("-devel")); + + *product_name = version_info->product_short_name(); + *special_build = version_info->special_build(); + } else { + // No version info found. Make up the values. + *product_name = base::ASCIIToUTF16("Chrome"); + *version = base::ASCIIToUTF16("0.0.0.0-devel"); + } + +#if 0 + GoogleUpdateSettings::GetChromeChannelAndModifiers( + !GetIsPerUserInstall(exe_path), channel_name); +#endif +} + +bool ChromeCrashReporterClient::ShouldShowRestartDialog(base::string16* title, + base::string16* message, + bool* is_rtl_locale) { + scoped_ptr env(base::Environment::Create()); + if (!env->HasVar(env_vars::kShowRestart) || + !env->HasVar(env_vars::kRestartInfo) || + env->HasVar(env_vars::kMetroConnected)) { + return false; + } + + std::string restart_info; + env->GetVar(env_vars::kRestartInfo, &restart_info); + + // The CHROME_RESTART var contains the dialog strings separated by '|'. + // See ChromeBrowserMainPartsWin::PrepareRestartOnCrashEnviroment() + // for details. + std::vector dlg_strings; + base::SplitString(restart_info, '|', &dlg_strings); + + if (dlg_strings.size() < 3) + return false; + + *title = base::UTF8ToUTF16(dlg_strings[0]); + *message = base::UTF8ToUTF16(dlg_strings[1]); + *is_rtl_locale = dlg_strings[2] == env_vars::kRtlLocale; + return true; +} + +bool ChromeCrashReporterClient::AboutToRestart() { + scoped_ptr env(base::Environment::Create()); + if (!env->HasVar(env_vars::kRestartInfo)) + return false; + + env->SetVar(env_vars::kShowRestart, "1"); + return true; +} + +bool ChromeCrashReporterClient::GetDeferredUploadsSupported( + bool is_per_user_install) { +#if 0 + Version update_version = GoogleUpdateSettings::GetGoogleUpdateVersion( + !is_per_user_install); + if (!update_version.IsValid() || + update_version.IsOlderThan(std::string(kMinUpdateVersion))) + return false; +#endif + return true; +} + +bool ChromeCrashReporterClient::GetIsPerUserInstall( + const base::FilePath& exe_path) { + return true; +} + +bool ChromeCrashReporterClient::GetShouldDumpLargerDumps( + bool is_per_user_install) { + return true; +#if 0 + base::string16 channel_name = + GoogleUpdateSettings::GetChromeChannel(!is_per_user_install); + + // Capture more detail in crash dumps for beta and dev channel builds. + return (channel_name == installer::kChromeChannelDev || + channel_name == installer::kChromeChannelBeta || + channel_name == GoogleChromeSxSDistribution::ChannelName()); +#endif +} + +int ChromeCrashReporterClient::GetResultCodeRespawnFailed() { + return chrome::RESULT_CODE_RESPAWN_FAILED; +} + +void ChromeCrashReporterClient::InitBrowserCrashDumpsRegKey() { + DCHECK(g_browser_crash_dump_regkey == NULL); + + base::win::RegKey regkey; + if (regkey.Create(HKEY_CURRENT_USER, + chrome::kBrowserCrashDumpAttemptsRegistryPath, + KEY_ALL_ACCESS) != ERROR_SUCCESS) { + return; + } + + // We use the current process id and the current tick count as a (hopefully) + // unique combination for the crash dump value. There's a small chance that + // across a reboot we might have a crash dump signal written, and the next + // browser process might have the same process id and tick count, but crash + // before consuming the signal (overwriting the signal with an identical one). + // For now, we're willing to live with that risk. + if (base::strings::SafeSPrintf(g_browser_crash_dump_prefix, + kBrowserCrashDumpPrefixTemplate, + "version", + ::GetCurrentProcessId(), + ::GetTickCount()) <= 0) { + NOTREACHED(); + g_browser_crash_dump_prefix[0] = '\0'; + return; + } + + // Hold the registry key in a global for update on crash dump. + g_browser_crash_dump_regkey = regkey.Take(); +} + +void ChromeCrashReporterClient::RecordCrashDumpAttempt(bool is_real_crash) { + // If we're not a browser (or the registry is unavailable to us for some + // reason) then there's nothing to do. + if (g_browser_crash_dump_regkey == NULL) + return; + + // Generate the final value name we'll use (appends the crash number to the + // base value name). + const size_t kMaxValueSize = 2 * kBrowserCrashDumpPrefixLength; + char value_name[kMaxValueSize + 1] = {}; + if (base::strings::SafeSPrintf( + value_name, "%s-%x", g_browser_crash_dump_prefix, + base::subtle::NoBarrier_AtomicIncrement(&g_browser_crash_dump_count, + 1)) > 0) { + DWORD value_dword = is_real_crash ? 1 : 0; + ::RegSetValueExA(g_browser_crash_dump_regkey, value_name, 0, REG_DWORD, + reinterpret_cast(&value_dword), + sizeof(value_dword)); + } +} + +bool ChromeCrashReporterClient::ReportingIsEnforcedByPolicy( + bool* breakpad_enabled) { +// Determine whether configuration management allows loading the crash reporter. +// Since the configuration management infrastructure is not initialized at this +// point, we read the corresponding registry key directly. The return status +// indicates whether policy data was successfully read. If it is true, +// |breakpad_enabled| contains the value set by policy. +#if 0 + base::string16 key_name = + base::UTF8ToUTF16(policy::key::kMetricsReportingEnabled); + DWORD value = 0; + base::win::RegKey hklm_policy_key(HKEY_LOCAL_MACHINE, + policy::kRegistryChromePolicyKey, KEY_READ); + if (hklm_policy_key.ReadValueDW(key_name.c_str(), &value) == ERROR_SUCCESS) { + *breakpad_enabled = value != 0; + return true; + } + + base::win::RegKey hkcu_policy_key(HKEY_CURRENT_USER, + policy::kRegistryChromePolicyKey, KEY_READ); + if (hkcu_policy_key.ReadValueDW(key_name.c_str(), &value) == ERROR_SUCCESS) { + *breakpad_enabled = value != 0; + return true; + } +#endif + return true; +} +#endif // defined(OS_WIN) + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS) +void ChromeCrashReporterClient::GetProductNameAndVersion( + const char** product_name, + const char** version) { + DCHECK(product_name); + DCHECK(version); + *product_name = "NW.JS"; + *version = NW_VERSION_STRING; +} + +base::FilePath ChromeCrashReporterClient::GetReporterLogFilename() { + return base::FilePath(CrashUploadList::kReporterLogFilename); +} +#endif + +bool ChromeCrashReporterClient::GetCrashDumpLocation( + base::FilePath* crash_dir) { + // By setting the BREAKPAD_DUMP_LOCATION environment variable, an alternate + // location to write breakpad crash dumps can be set. + scoped_ptr env(base::Environment::Create()); + std::string alternate_crash_dump_location; + if (env->GetVar("BREAKPAD_DUMP_LOCATION", &alternate_crash_dump_location)) { + base::FilePath crash_dumps_dir_path = + base::FilePath::FromUTF8Unsafe(alternate_crash_dump_location); + PathService::Override(chrome::DIR_CRASH_DUMPS, crash_dumps_dir_path); + } + + return PathService::Get(chrome::DIR_CRASH_DUMPS, crash_dir); +} + +size_t ChromeCrashReporterClient::RegisterCrashKeys() { + // Note: This is not called on Windows because Breakpad is initialized in the + // EXE module, but code that uses crash keys is in the DLL module. + // RegisterChromeCrashKeys() will be called after the DLL is loaded. + return crash_keys::RegisterChromeCrashKeys(); +} + +bool ChromeCrashReporterClient::IsRunningUnattended() { + return true; +} + +bool ChromeCrashReporterClient::GetCollectStatsConsent() { +#if defined(GOOGLE_CHROME_BUILD) + bool is_official_chrome_build = true; +#else + // bool is_official_chrome_build = false; +#endif + +#if defined(OS_CHROMEOS) + bool is_guest_session = CommandLine::ForCurrentProcess()->HasSwitch( + chromeos::switches::kGuestSession); + bool is_stable_channel = + chrome::VersionInfo::GetChannel() == chrome::VersionInfo::CHANNEL_STABLE; + + if (is_guest_session && is_stable_channel) + return false; +#endif // defined(OS_CHROMEOS) + +#if defined(OS_ANDROID) + // TODO(jcivelli): we should not initialize the crash-reporter when it was not + // enabled. Right now if it is disabled we still generate the minidumps but we + // do not upload them. + return is_official_chrome_build; +#else // !defined(OS_ANDROID) + return false; +#endif // defined(OS_ANDROID) +} + +#if defined(OS_ANDROID) +int ChromeCrashReporterClient::GetAndroidMinidumpDescriptor() { + return kAndroidMinidumpDescriptor; +} +#endif + +bool ChromeCrashReporterClient::EnableBreakpadForProcess( + const std::string& process_type) { + return process_type == switches::kRendererProcess || + process_type == switches::kPluginProcess || + process_type == switches::kPpapiPluginProcess || + process_type == switches::kZygoteProcess || + process_type == switches::kGpuProcess; +} + +} // namespace chrome diff --git a/src/browser/chrome_crash_reporter_client.h b/src/browser/chrome_crash_reporter_client.h new file mode 100644 index 0000000000..d3eb42f1f3 --- /dev/null +++ b/src/browser/chrome_crash_reporter_client.h @@ -0,0 +1,76 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_APP_CHROME_CRASH_REPORTER_CLIENT_H_ +#define CHROME_APP_CHROME_CRASH_REPORTER_CLIENT_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "components/crash/app/crash_reporter_client.h" + +namespace chrome { + +class ChromeCrashReporterClient : public crash_reporter::CrashReporterClient { + public: + ChromeCrashReporterClient(); + ~ChromeCrashReporterClient() override; + + // crash_reporter::CrashReporterClient implementation. + void SetCrashReporterClientIdFromGUID( + const std::string& client_guid) override; +#if defined(OS_WIN) + virtual bool GetAlternativeCrashDumpLocation(base::FilePath* crash_dir) + override; + virtual void GetProductNameAndVersion(const base::FilePath& exe_path, + base::string16* product_name, + base::string16* version, + base::string16* special_build, + base::string16* channel_name) override; + virtual bool ShouldShowRestartDialog(base::string16* title, + base::string16* message, + bool* is_rtl_locale) override; + virtual bool AboutToRestart() override; + virtual bool GetDeferredUploadsSupported(bool is_per_user_install) override; + virtual bool GetIsPerUserInstall(const base::FilePath& exe_path) override; + virtual bool GetShouldDumpLargerDumps(bool is_per_user_install) override; + virtual int GetResultCodeRespawnFailed() override; + virtual void InitBrowserCrashDumpsRegKey() override; + virtual void RecordCrashDumpAttempt(bool is_real_crash) override; +#endif + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS) + void GetProductNameAndVersion(const char** product_name, + const char** version) override; + base::FilePath GetReporterLogFilename() override; +#endif + + bool GetCrashDumpLocation(base::FilePath* crash_dir) override; + + size_t RegisterCrashKeys() override; + + bool IsRunningUnattended() override; + + bool GetCollectStatsConsent() override; + +#if defined(OS_WIN) || defined(OS_MACOSX) + bool ReportingIsEnforcedByPolicy(bool* breakpad_enabled) override; +#endif + +#if defined(OS_ANDROID) + virtual int GetAndroidMinidumpDescriptor() override; +#endif + +#if defined(OS_MACOSX) + void InstallAdditionalFilters(BreakpadRef breakpad) override; +#endif + + bool EnableBreakpadForProcess(const std::string& process_type) override; + + private: + DISALLOW_COPY_AND_ASSIGN(ChromeCrashReporterClient); +}; + +} // namespace chrome + +#endif // CHROME_APP_CHROME_CRASH_REPORTER_CLIENT_H_ diff --git a/src/browser/chrome_crash_reporter_client_mac.mm b/src/browser/chrome_crash_reporter_client_mac.mm new file mode 100644 index 0000000000..d7927559ea --- /dev/null +++ b/src/browser/chrome_crash_reporter_client_mac.mm @@ -0,0 +1,62 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/app/chrome_crash_reporter_client.h" + +#include + +#include "base/mac/scoped_cftyperef.h" +#include "base/strings/sys_string_conversions.h" +//#include "policy/policy_constants.h" + +#if !defined(DISABLE_NACL) +#include "base/command_line.h" +#import "breakpad/src/client/mac/Framework/Breakpad.h" +#include "chrome/common/chrome_switches.h" +#include "components/nacl/common/nacl_switches.h" +#include "native_client/src/trusted/service_runtime/osx/crash_filter.h" +#endif + +namespace chrome { + +namespace { + +#if !defined(DISABLE_NACL) +bool NaClBreakpadCrashFilter(int exception_type, + int exception_code, + mach_port_t crashing_thread, + void* context) { + return !NaClMachThreadIsInUntrusted(crashing_thread); +} +#endif + +} // namespace + +void ChromeCrashReporterClient::InstallAdditionalFilters(BreakpadRef breakpad) { +#if !defined(DISABLE_NACL) + if (CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kProcessType) == switches::kNaClLoaderProcess) { + BreakpadSetFilterCallback(breakpad, NaClBreakpadCrashFilter, NULL); + } +#endif +} + +bool ChromeCrashReporterClient::ReportingIsEnforcedByPolicy( + bool* breakpad_enabled) { +#if 0 + base::ScopedCFTypeRef key( + base::SysUTF8ToCFStringRef(policy::key::kMetricsReportingEnabled)); + Boolean key_valid; + Boolean metrics_reporting_enabled = CFPreferencesGetAppBooleanValue(key, + kCFPreferencesCurrentApplication, &key_valid); + if (key_valid && + CFPreferencesAppValueIsForced(key, kCFPreferencesCurrentApplication)) { + *breakpad_enabled = metrics_reporting_enabled; + return true; + } +#endif + return false; +} + +} // namespace chrome diff --git a/src/browser/chrome_event_processing_window.h b/src/browser/chrome_event_processing_window.h index 26d140cb7a..5b5118388b 100644 --- a/src/browser/chrome_event_processing_window.h +++ b/src/browser/chrome_event_processing_window.h @@ -7,7 +7,7 @@ #import -#include "base/memory/scoped_nsobject.h" +#include "base/mac/scoped_nsobject.h" #import "ui/base/cocoa/underlay_opengl_hosting_window.h" // Override NSWindow to access unhandled keyboard events (for command diff --git a/src/browser/color_chooser_aura.cc b/src/browser/color_chooser_aura.cc new file mode 100644 index 0000000000..1c4c5012c3 --- /dev/null +++ b/src/browser/color_chooser_aura.cc @@ -0,0 +1,68 @@ +// 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 "content/nw/src/browser/color_chooser_aura.h" + +#include "content/public/browser/web_contents.h" +#include "ui/views/color_chooser/color_chooser_view.h" +#include "ui/views/widget/widget.h" + +ColorChooserAura::ColorChooserAura(content::WebContents* web_contents, + SkColor initial_color) + : web_contents_(web_contents) { + view_ = new views::ColorChooserView(this, initial_color); + widget_ = views::Widget::CreateWindowWithParent( + view_, web_contents->GetTopLevelNativeWindow()); + widget_->Show(); +} + +void ColorChooserAura::OnColorChosen(SkColor color) { + if (web_contents_) + web_contents_->DidChooseColorInColorChooser(color); +} + +void ColorChooserAura::OnColorChooserDialogClosed() { + view_ = NULL; + widget_ = NULL; + DidEndColorChooser(); +} + +void ColorChooserAura::End() { + if (widget_) { + view_->set_listener(NULL); + widget_->Close(); + view_ = NULL; + widget_ = NULL; + // DidEndColorChooser will invoke Browser::DidEndColorChooser, which deletes + // this. Take care of the call order. + DidEndColorChooser(); + } +} + +void ColorChooserAura::DidEndColorChooser() { + if (web_contents_) + web_contents_->DidEndColorChooser(); +} + +void ColorChooserAura::SetSelectedColor(SkColor color) { + if (view_) + view_->OnColorChanged(color); +} + +// static +ColorChooserAura* ColorChooserAura::Open( + content::WebContents* web_contents, SkColor initial_color) { + return new ColorChooserAura(web_contents, initial_color); +} + +#if !defined(OS_WIN) +namespace nw { + +content::ColorChooser* ShowColorChooser(content::WebContents* web_contents, + SkColor initial_color) { + return ColorChooserAura::Open(web_contents, initial_color); +} + +} // namespace nw +#endif // OS_WIN diff --git a/src/browser/color_chooser_aura.h b/src/browser/color_chooser_aura.h new file mode 100644 index 0000000000..05a2f6f832 --- /dev/null +++ b/src/browser/color_chooser_aura.h @@ -0,0 +1,58 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NW_BROWSER_UI_VIEWS_COLOR_CHOOSER_AURA_H_ +#define NW_BROWSER_UI_VIEWS_COLOR_CHOOSER_AURA_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "content/public/browser/color_chooser.h" +#include "ui/views/color_chooser/color_chooser_listener.h" + +namespace content { +class WebContents; +} + +namespace views { +class ColorChooserView; +class Widget; +} + +// TODO(mukai): rename this as -Ash and move to c/b/ui/ash after Linux-aura +// switches to its native color chooser. +class ColorChooserAura : public content::ColorChooser, + public views::ColorChooserListener { + public: + static ColorChooserAura* Open(content::WebContents* web_contents, + SkColor initial_color); + + private: + ColorChooserAura(content::WebContents* web_contents, SkColor initial_color); + + // content::ColorChooser overrides: + void End() override; + void SetSelectedColor(SkColor color) override; + + // views::ColorChooserListener overrides: + void OnColorChosen(SkColor color) override; + void OnColorChooserDialogClosed() override; + + void DidEndColorChooser(); + + // The actual view of the color chooser. No ownership because its parent + // view will take care of its lifetime. + views::ColorChooserView* view_; + + // The widget for the color chooser. No ownership because it's released + // automatically when closed. + views::Widget* widget_; + + // The web contents invoking the color chooser. No ownership because it will + // outlive this class. + content::WebContents* web_contents_; + + DISALLOW_COPY_AND_ASSIGN(ColorChooserAura); +}; + +#endif // CHROME_BROWSER_UI_VIEWS_COLOR_CHOOSER_AURA_H_ diff --git a/src/browser/color_chooser_dialog.cc b/src/browser/color_chooser_dialog.cc new file mode 100644 index 0000000000..3f27e3da93 --- /dev/null +++ b/src/browser/color_chooser_dialog.cc @@ -0,0 +1,82 @@ +// 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 "content/nw/src/browser/color_chooser_dialog.h" + +#include + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/thread.h" +#include "content/public/browser/browser_thread.h" +#include "skia/ext/skia_utils_win.h" +#include "ui/views/color_chooser/color_chooser_listener.h" + +using content::BrowserThread; + +// static +COLORREF ColorChooserDialog::g_custom_colors[16]; + +ColorChooserDialog::ExecuteOpenParams::ExecuteOpenParams(SkColor color, + RunState run_state, + HWND owner) + : color(color), + run_state(run_state), + owner(owner) { +} + +ColorChooserDialog::ColorChooserDialog(views::ColorChooserListener* listener, + SkColor initial_color, + gfx::NativeWindow owning_window) + : listener_(listener) { + DCHECK(listener_); + CopyCustomColors(g_custom_colors, custom_colors_); + ExecuteOpenParams execute_params(initial_color, BeginRun((HWND)owning_window), + (HWND)owning_window); + execute_params.run_state.dialog_thread->message_loop()->PostTask(FROM_HERE, + base::Bind(&ColorChooserDialog::ExecuteOpen, this, execute_params)); +} + +ColorChooserDialog::~ColorChooserDialog() { +} + +bool ColorChooserDialog::IsRunning(gfx::NativeWindow owning_hwnd) const { + return listener_ && IsRunningDialogForOwner((HWND)owning_hwnd); +} + +void ColorChooserDialog::ListenerDestroyed() { + // Our associated listener has gone away, so we shouldn't call back to it if + // our worker thread returns after the listener is dead. + listener_ = NULL; +} + +void ColorChooserDialog::ExecuteOpen(const ExecuteOpenParams& params) { + CHOOSECOLOR cc; + cc.lStructSize = sizeof(CHOOSECOLOR); + cc.hwndOwner = params.owner; + cc.rgbResult = skia::SkColorToCOLORREF(params.color); + cc.lpCustColors = custom_colors_; + cc.Flags = CC_ANYCOLOR | CC_FULLOPEN | CC_RGBINIT; + bool success = !!ChooseColor(&cc); + DisableOwner(cc.hwndOwner); + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(&ColorChooserDialog::DidCloseDialog, this, success, + skia::COLORREFToSkColor(cc.rgbResult), params.run_state)); +} + +void ColorChooserDialog::DidCloseDialog(bool chose_color, + SkColor color, + RunState run_state) { + if (!listener_) + return; + EndRun(run_state); + CopyCustomColors(custom_colors_, g_custom_colors); + if (chose_color) + listener_->OnColorChosen(color); + listener_->OnColorChooserDialogClosed(); +} + +void ColorChooserDialog::CopyCustomColors(COLORREF* src, COLORREF* dst) { + memcpy(dst, src, sizeof(COLORREF) * arraysize(g_custom_colors)); +} diff --git a/src/browser/color_chooser_dialog.h b/src/browser/color_chooser_dialog.h new file mode 100644 index 0000000000..530c9a3331 --- /dev/null +++ b/src/browser/color_chooser_dialog.h @@ -0,0 +1,73 @@ +// 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_UI_VIEWS_COLOR_CHOOSER_DIALOG_H_ +#define CHROME_BROWSER_UI_VIEWS_COLOR_CHOOSER_DIALOG_H_ + +#include "base/memory/ref_counted.h" +#include "content/nw/src/browser/color_chooser_dialog.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/shell_dialogs/base_shell_dialog.h" +#include "ui/shell_dialogs/base_shell_dialog_win.h" + +namespace views { +class ColorChooserListener; +} + +class ColorChooserDialog + : public base::RefCountedThreadSafe, + public ui::BaseShellDialog, + public ui::BaseShellDialogImpl { + public: + ColorChooserDialog(views::ColorChooserListener* listener, + SkColor initial_color, + gfx::NativeWindow owning_window); + virtual ~ColorChooserDialog(); + + // BaseShellDialog: + virtual bool IsRunning(gfx::NativeWindow owning_window) const override; + virtual void ListenerDestroyed() override; + + private: + struct ExecuteOpenParams { + ExecuteOpenParams(SkColor color, RunState run_state, HWND owner); + SkColor color; + RunState run_state; + HWND owner; + }; + + // Called on the dialog thread to show the actual color chooser. This is + // shown modal to |params.owner|. Once it's closed, calls back to + // DidCloseDialog() on the UI thread. + void ExecuteOpen(const ExecuteOpenParams& params); + + // Called on the UI thread when a color chooser is closed. |chose_color| is + // true if the user actually chose a color, in which case |color| is the + // chosen color. Calls back to the |listener_| (if applicable) to notify it + // of the results, and copies the modified array of |custom_colors_| back to + // |g_custom_colors| so future dialogs will see the changes. + void DidCloseDialog(bool chose_color, SkColor color, RunState run_state); + + // Copies the array of colors in |src| to |dst|. + void CopyCustomColors(COLORREF*, COLORREF*); + + // The user's custom colors. Kept process-wide so that they can be persisted + // from one dialog invocation to the next. + static COLORREF g_custom_colors[16]; + + // A copy of the custom colors for the current dialog to display and modify. + // This allows us to safely access the colors even if multiple windows are + // simultaneously showing color choosers (which would cause thread safety + // problems if we gave them direct handles to |g_custom_colors|). + COLORREF custom_colors_[16]; + + // The listener to notify when the user closes the dialog. This may be set to + // NULL before the color chooser is closed, signalling that the listener no + // longer cares about the outcome. + views::ColorChooserListener* listener_; + + DISALLOW_COPY_AND_ASSIGN(ColorChooserDialog); +}; + +#endif // CHROME_BROWSER_UI_VIEWS_COLOR_CHOOSER_DIALOG_H_ diff --git a/src/browser/color_chooser_mac.mm b/src/browser/color_chooser_mac.mm new file mode 100644 index 0000000000..38b6b2101e --- /dev/null +++ b/src/browser/color_chooser_mac.mm @@ -0,0 +1,161 @@ +// 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. + +#import + +#include "base/logging.h" +#import "base/mac/scoped_nsobject.h" +#include "content/nw/src/browser/browser_dialogs.h" +#include "content/public/browser/color_chooser.h" +#include "content/public/browser/web_contents.h" +#include "skia/ext/skia_utils_mac.h" + +class ColorChooserMac; + +// A Listener class to act as a event target for NSColorPanel and send +// the results to the C++ class, ColorChooserMac. +@interface ColorPanelCocoa : NSObject { + @private + // We don't call DidChooseColor if the change wasn't caused by the user + // interacting with the panel. + BOOL nonUserChange_; + ColorChooserMac* chooser_; // weak, owns this +} + +- (id)initWithChooser:(ColorChooserMac*)chooser; + +// Called from NSColorPanel. +- (void)didChooseColor:(NSColorPanel*)panel; + +// Sets color to the NSColorPanel as a non user change. +- (void)setColor:(NSColor*)color; + +@end + +class ColorChooserMac : public content::ColorChooser { + public: + static ColorChooserMac* Open(content::WebContents* web_contents, + SkColor initial_color); + + ColorChooserMac(content::WebContents* tab, SkColor initial_color); + virtual ~ColorChooserMac(); + + // Called from ColorPanelCocoa. + void DidChooseColorInColorPanel(SkColor color); + void DidCloseColorPabel(); + + virtual void End() override; + virtual void SetSelectedColor(SkColor color) override; + + private: + static ColorChooserMac* current_color_chooser_; + + // The web contents invoking the color chooser. No ownership because it will + // outlive this class. + content::WebContents* web_contents_; + base::scoped_nsobject panel_; +}; + +ColorChooserMac* ColorChooserMac::current_color_chooser_ = NULL; + +// static +ColorChooserMac* ColorChooserMac::Open(content::WebContents* web_contents, + SkColor initial_color) { + if (current_color_chooser_) + current_color_chooser_->End(); + CHECK(!current_color_chooser_); + current_color_chooser_ = + new ColorChooserMac(web_contents, initial_color); + return current_color_chooser_; +} + +ColorChooserMac::ColorChooserMac(content::WebContents* web_contents, + SkColor initial_color) + : web_contents_(web_contents) { + panel_.reset([[ColorPanelCocoa alloc] initWithChooser:this]); + [panel_ setColor:gfx::SkColorToDeviceNSColor(initial_color)]; + [[NSColorPanel sharedColorPanel] makeKeyAndOrderFront:nil]; +} + +ColorChooserMac::~ColorChooserMac() { + // Always call End() before destroying. + CHECK(!panel_); +} + +void ColorChooserMac::DidChooseColorInColorPanel(SkColor color) { + if (web_contents_) + web_contents_->DidChooseColorInColorChooser(color); +} + +void ColorChooserMac::DidCloseColorPabel() { + End(); +} + +void ColorChooserMac::End() { + panel_.reset(); + CHECK(current_color_chooser_ == this); + current_color_chooser_ = NULL; + if (web_contents_) + web_contents_->DidEndColorChooser(); +} + +void ColorChooserMac::SetSelectedColor(SkColor color) { + [panel_ setColor:gfx::SkColorToDeviceNSColor(color)]; +} + +@implementation ColorPanelCocoa + +- (id)initWithChooser:(ColorChooserMac*)chooser { + if ((self = [super init])) { + chooser_ = chooser; + NSColorPanel* panel = [NSColorPanel sharedColorPanel]; + [panel setShowsAlpha:NO]; + [panel setDelegate:self]; + [panel setTarget:self]; + [panel setAction:@selector(didChooseColor:)]; + } + return self; +} + +- (void)dealloc { + NSColorPanel* panel = [NSColorPanel sharedColorPanel]; + if ([panel delegate] == self) { + [panel setDelegate:nil]; + [panel setTarget:nil]; + [panel setAction:nil]; + } + + [super dealloc]; +} + +- (void)windowWillClose:(NSNotification*)notification { + nonUserChange_ = NO; + chooser_->DidCloseColorPabel(); +} + +- (void)didChooseColor:(NSColorPanel*)panel { + if (nonUserChange_) { + nonUserChange_ = NO; + return; + } + chooser_->DidChooseColorInColorPanel(gfx::NSDeviceColorToSkColor( + [[panel color] colorUsingColorSpaceName:NSDeviceRGBColorSpace])); + nonUserChange_ = NO; +} + +- (void)setColor:(NSColor*)color { + nonUserChange_ = YES; + [[NSColorPanel sharedColorPanel] setColor:color]; +} + +namespace nw { + +content::ColorChooser* ShowColorChooser(content::WebContents* web_contents, + SkColor initial_color) { + return ColorChooserMac::Open(web_contents, initial_color); +} + +} // namepace chrome + +@end diff --git a/src/browser/color_chooser_win.cc b/src/browser/color_chooser_win.cc new file mode 100644 index 0000000000..6ebc3be0a0 --- /dev/null +++ b/src/browser/color_chooser_win.cc @@ -0,0 +1,92 @@ +// 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 + +//#include "chrome/browser/platform_util.h" +#include "content/nw/src/browser/browser_dialogs.h" +#include "content/nw/src/browser/color_chooser_dialog.h" +#include "content/public/browser/color_chooser.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host_view.h" +#include "content/public/browser/web_contents.h" +#include "ui/views/color_chooser/color_chooser_listener.h" + +class ColorChooserWin : public content::ColorChooser, + public views::ColorChooserListener { + public: + static ColorChooserWin* Open(content::WebContents* web_contents, + SkColor initial_color); + + ColorChooserWin(content::WebContents* web_contents, + SkColor initial_color); + ~ColorChooserWin(); + + // content::ColorChooser overrides: + virtual void End() override {} + virtual void SetSelectedColor(SkColor color) override {} + + // views::ColorChooserListener overrides: + virtual void OnColorChosen(SkColor color); + virtual void OnColorChooserDialogClosed(); + + private: + static ColorChooserWin* current_color_chooser_; + + // The web contents invoking the color chooser. No ownership. because it will + // outlive this class. + content::WebContents* web_contents_; + + // The color chooser dialog which maintains the native color chooser UI. + scoped_refptr color_chooser_dialog_; +}; + +ColorChooserWin* ColorChooserWin::current_color_chooser_ = NULL; + +ColorChooserWin* ColorChooserWin::Open(content::WebContents* web_contents, + SkColor initial_color) { + if (!current_color_chooser_) + current_color_chooser_ = new ColorChooserWin(web_contents, initial_color); + return current_color_chooser_; +} + +ColorChooserWin::ColorChooserWin(content::WebContents* web_contents, + SkColor initial_color) + : web_contents_(web_contents) { + gfx::NativeWindow owning_window = (gfx::NativeWindow)::GetAncestor( + (HWND)web_contents->GetRenderViewHost()->GetView()->GetNativeView(), GA_ROOT); + color_chooser_dialog_ = new ColorChooserDialog(this, + initial_color, + owning_window); +} + +ColorChooserWin::~ColorChooserWin() { + // Always call End() before destroying. + DCHECK(!color_chooser_dialog_); +} + +void ColorChooserWin::OnColorChosen(SkColor color) { + if (web_contents_) + web_contents_->DidChooseColorInColorChooser(color); +} + +void ColorChooserWin::OnColorChooserDialogClosed() { + if (color_chooser_dialog_.get()) { + color_chooser_dialog_->ListenerDestroyed(); + color_chooser_dialog_ = NULL; + } + DCHECK(current_color_chooser_ == this); + current_color_chooser_ = NULL; + if (web_contents_) + web_contents_->DidEndColorChooser(); +} + +namespace nw { + +content::ColorChooser* ShowColorChooser(content::WebContents* web_contents, + SkColor initial_color) { + return ColorChooserWin::Open(web_contents, initial_color); +} + +} // namespace chrome diff --git a/src/browser/file_select_helper.cc b/src/browser/file_select_helper.cc index a6731ccae4..3f34eca45b 100644 --- a/src/browser/file_select_helper.cc +++ b/src/browser/file_select_helper.cc @@ -1,35 +1,26 @@ -// Copyright (c) 2012 Intel Corp -// Copyright (c) 2012 The Chromium Authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co -// pies of the Software, and to permit persons to whom the Software is furnished -// to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in al -// l copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM -// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES -// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH -// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// 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 "content/nw/src/browser/file_select_helper.h" #include +#include #include "base/bind.h" -#include "base/file_util.h" -#include "base/platform_file.h" -#include "base/logging.h" -#include "base/string_split.h" -#include "base/string_util.h" -#include "base/utf_string_conversions.h" +#include "base/files/file_enumerator.h" +#include "base/files/file_util.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +//#include "chrome/browser/browser_process.h" #include "chrome/browser/platform_util.h" +//#include "chrome/browser/profiles/profile.h" +//#include "chrome/browser/profiles/profile_manager.h" +// #include "chrome/browser/ui/browser.h" +// #include "chrome/browser/ui/browser_list.h" +// #include "chrome/browser/ui/chrome_select_file_policy.h" +#include "grit/nw_resources.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_source.h" @@ -37,18 +28,22 @@ #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/web_contents.h" +#include "content/public/common/file_chooser_file_info.h" #include "content/public/common/file_chooser_params.h" -#include "grit/nw_resources.h" #include "net/base/mime_util.h" -#include "ui/shell_dialogs/selected_file_info.h" #include "ui/base/l10n/l10n_util.h" +#include "ui/shell_dialogs/selected_file_info.h" + +#if defined(OS_CHROMEOS) +#include "chrome/browser/chromeos/file_manager/fileapi_util.h" +#include "content/public/browser/site_instance.h" +#endif using content::BrowserThread; using content::FileChooserParams; using content::RenderViewHost; using content::RenderWidgetHost; using content::WebContents; -using base::FilePath; namespace { @@ -57,34 +52,9 @@ namespace { // the renderer must start at 0 and increase. const int kFileSelectEnumerationId = -1; -void NotifyRenderViewHost(RenderViewHost* render_view_host, - const std::vector& files, - ui::SelectFileDialog::Type dialog_type) { - const int kReadFilePermissions = - base::PLATFORM_FILE_OPEN | - base::PLATFORM_FILE_READ | - base::PLATFORM_FILE_EXCLUSIVE_READ | - base::PLATFORM_FILE_ASYNC; - - const int kWriteFilePermissions = - base::PLATFORM_FILE_CREATE | - base::PLATFORM_FILE_CREATE_ALWAYS | - base::PLATFORM_FILE_OPEN | - base::PLATFORM_FILE_OPEN_ALWAYS | - base::PLATFORM_FILE_OPEN_TRUNCATED | - base::PLATFORM_FILE_WRITE | - base::PLATFORM_FILE_WRITE_ATTRIBUTES | - base::PLATFORM_FILE_ASYNC; - - int permissions = kReadFilePermissions; - if (dialog_type == ui::SelectFileDialog::SELECT_SAVEAS_FILE) - permissions = kWriteFilePermissions; - render_view_host->FilesSelectedInChooser(files, permissions); -} - // Converts a list of FilePaths to a list of ui::SelectedFileInfo. std::vector FilePathListToSelectedFileInfoList( - const std::vector& paths) { + const std::vector& paths) { std::vector selected_files; for (size_t i = 0; i < paths.size(); ++i) { selected_files.push_back( @@ -93,6 +63,11 @@ std::vector FilePathListToSelectedFileInfoList( return selected_files; } +void DeleteFiles(const std::vector& paths) { + for (auto& file_path : paths) + base::DeleteFile(file_path, false); +} + } // namespace struct FileSelectHelper::ActiveDirectoryEnumeration { @@ -101,15 +76,17 @@ struct FileSelectHelper::ActiveDirectoryEnumeration { scoped_ptr delegate_; scoped_ptr lister_; RenderViewHost* rvh_; - std::vector results_; + std::vector results_; }; FileSelectHelper::FileSelectHelper() - : render_view_host_(NULL), + : + render_view_host_(NULL), web_contents_(NULL), select_file_dialog_(), select_file_types_(), - dialog_type_(ui::SelectFileDialog::SELECT_OPEN_FILE) { + dialog_type_(ui::SelectFileDialog::SELECT_OPEN_FILE), + dialog_mode_(FileChooserParams::Open) { } FileSelectHelper::~FileSelectHelper() { @@ -138,7 +115,7 @@ void FileSelectHelper::DirectoryListerDispatchDelegate::OnListDone(int error) { parent_->OnListDone(id_, error); } -void FileSelectHelper::FileSelected(const FilePath& path, +void FileSelectHelper::FileSelected(const base::FilePath& path, int index, void* params) { FileSelectedWithExtraInfo(ui::SelectedFileInfo(path, path), index, params); } @@ -147,11 +124,14 @@ void FileSelectHelper::FileSelectedWithExtraInfo( const ui::SelectedFileInfo& file, int index, void* params) { - if (!render_view_host_) + + if (!render_view_host_) { + RunFileChooserEnd(); return; + } - const FilePath& path = file.local_path; - if (dialog_type_ == ui::SelectFileDialog::SELECT_FOLDER && + const base::FilePath& path = file.local_path; + if (dialog_type_ == ui::SelectFileDialog::SELECT_UPLOAD_FOLDER && extract_directory_) { StartNewEnumeration(path, kFileSelectEnumerationId, render_view_host_); return; @@ -159,14 +139,20 @@ void FileSelectHelper::FileSelectedWithExtraInfo( std::vector files; files.push_back(file); - NotifyRenderViewHost(render_view_host_, files, dialog_type_); - // No members should be accessed from here on. - RunFileChooserEnd(); +#if defined(OS_MACOSX) && !defined(OS_IOS) + content::BrowserThread::PostTask( + content::BrowserThread::FILE_USER_BLOCKING, + FROM_HERE, + base::Bind(&FileSelectHelper::ProcessSelectedFilesMac, this, files)); +#else + NotifyRenderViewHostAndEnd(files); +#endif // defined(OS_MACOSX) && !defined(OS_IOS) } -void FileSelectHelper::MultiFilesSelected(const std::vector& files, - void* params) { +void FileSelectHelper::MultiFilesSelected( + const std::vector& files, + void* params) { std::vector selected_files = FilePathListToSelectedFileInfoList(files); @@ -176,30 +162,22 @@ void FileSelectHelper::MultiFilesSelected(const std::vector& files, void FileSelectHelper::MultiFilesSelectedWithExtraInfo( const std::vector& files, void* params) { - if (!render_view_host_) - return; - - NotifyRenderViewHost(render_view_host_, files, dialog_type_); - // No members should be accessed from here on. - RunFileChooserEnd(); +#if defined(OS_MACOSX) && !defined(OS_IOS) + content::BrowserThread::PostTask( + content::BrowserThread::FILE_USER_BLOCKING, + FROM_HERE, + base::Bind(&FileSelectHelper::ProcessSelectedFilesMac, this, files)); +#else + NotifyRenderViewHostAndEnd(files); +#endif // defined(OS_MACOSX) && !defined(OS_IOS) } void FileSelectHelper::FileSelectionCanceled(void* params) { - if (!render_view_host_) - return; - - // If the user cancels choosing a file to upload we pass back an - // empty vector. - NotifyRenderViewHost( - render_view_host_, std::vector(), - dialog_type_); - - // No members should be accessed from here on. - RunFileChooserEnd(); + NotifyRenderViewHostAndEnd(std::vector()); } -void FileSelectHelper::StartNewEnumeration(const FilePath& path, +void FileSelectHelper::StartNewEnumeration(const base::FilePath& path, int request_id, RenderViewHost* render_view_host) { scoped_ptr entry(new ActiveDirectoryEnumeration); @@ -225,13 +203,11 @@ void FileSelectHelper::OnListFile( const net::DirectoryLister::DirectoryListerData& data) { ActiveDirectoryEnumeration* entry = directory_enumerations_[id]; - // Directory upload returns directories via a "." file, so that - // empty directories are included. This util call just checks - // the flags in the structure; there's no file I/O going on. - if (file_util::FileEnumerator::IsDirectory(data.info)) - entry->results_.push_back(data.path.Append(FILE_PATH_LITERAL("."))); - else - entry->results_.push_back(data.path); + // Directory upload only cares about files. + if (data.info.IsDirectory()) + return; + + entry->results_.push_back(data.path); } void FileSelectHelper::OnListDone(int id, int error) { @@ -248,32 +224,89 @@ void FileSelectHelper::OnListDone(int id, int error) { std::vector selected_files = FilePathListToSelectedFileInfoList(entry->results_); - if (id == kFileSelectEnumerationId) - NotifyRenderViewHost(entry->rvh_, selected_files, dialog_type_); - else + if (id == kFileSelectEnumerationId) { + NotifyRenderViewHostAndEnd(selected_files); + } else { entry->rvh_->DirectoryEnumerationFinished(id, entry->results_); + EnumerateDirectoryEnd(); + } +} + +void FileSelectHelper::NotifyRenderViewHostAndEnd( + const std::vector& files) { + if (!render_view_host_) { + RunFileChooserEnd(); + return; + } + +#if defined(OS_CHROMEOS) + if (!files.empty()) { + if (!IsValidProfile(profile_)) { + RunFileChooserEnd(); + return; + } + // Converts |files| into FileChooserFileInfo with handling of non-native + // files. + file_manager::util::ConvertSelectedFileInfoListToFileChooserFileInfoList( + file_manager::util::GetFileSystemContextForRenderViewHost( + profile_, render_view_host_), + web_contents_->GetSiteInstance()->GetSiteURL(), + files, + base::Bind( + &FileSelectHelper::NotifyRenderViewHostAndEndAfterConversion, + this)); + return; + } +#endif // defined(OS_CHROMEOS) + + std::vector chooser_files; + for (const auto& file : files) { + content::FileChooserFileInfo chooser_file; + chooser_file.file_path = file.local_path; + chooser_file.display_name = file.display_name; + chooser_files.push_back(chooser_file); + } + + NotifyRenderViewHostAndEndAfterConversion(chooser_files); +} + +void FileSelectHelper::NotifyRenderViewHostAndEndAfterConversion( + const std::vector& list) { + if (render_view_host_) + render_view_host_->FilesSelectedInChooser(list, dialog_mode_); + + // No members should be accessed from here on. + RunFileChooserEnd(); +} - EnumerateDirectoryEnd(); +void FileSelectHelper::DeleteTemporaryFiles() { + BrowserThread::PostTask(BrowserThread::FILE, + FROM_HERE, + base::Bind(&DeleteFiles, temporary_files_)); + temporary_files_.clear(); } -ui::SelectFileDialog::FileTypeInfo* +scoped_ptr FileSelectHelper::GetFileTypesFromAcceptType( - const std::vector& accept_types) { + const std::vector& accept_types) { + scoped_ptr base_file_type( + new ui::SelectFileDialog::FileTypeInfo()); if (accept_types.empty()) - return NULL; + return base_file_type.Pass(); // Create FileTypeInfo and pre-allocate for the first extension list. scoped_ptr file_type( - new ui::SelectFileDialog::FileTypeInfo()); + new ui::SelectFileDialog::FileTypeInfo(*base_file_type)); file_type->include_all_files = true; file_type->extensions.resize(1); - std::vector* extensions = &file_type->extensions.back(); + std::vector* extensions = + &file_type->extensions.back(); // Find the corresponding extensions. int valid_type_count = 0; - bool description_known = false; + int description_id = 0; for (size_t i = 0; i < accept_types.size(); ++i) { - std::string ascii_type = UTF16ToASCII(accept_types[i]); + std::string ascii_type = base::UTF16ToASCII(accept_types[i]); if (!IsAcceptTypeValid(ascii_type)) continue; @@ -281,22 +314,15 @@ FileSelectHelper::GetFileTypesFromAcceptType( if (ascii_type[0] == '.') { // If the type starts with a period it is assumed to be a file extension // so we just have to add it to the list. - FilePath::StringType ext(ascii_type.begin(), ascii_type.end()); + base::FilePath::StringType ext(ascii_type.begin(), ascii_type.end()); extensions->push_back(ext.substr(1)); } else { - if (ascii_type == "image/*") { - description_known = true; - file_type->extension_description_overrides.push_back( - ASCIIToUTF16("Images")); - } else if (ascii_type == "audio/*") { - description_known = true; - file_type->extension_description_overrides.push_back( - ASCIIToUTF16("Audios")); - } else if (ascii_type == "video/*") { - description_known = true; - file_type->extension_description_overrides.push_back( - ASCIIToUTF16("Videos")); - } + if (ascii_type == "image/*") + description_id = IDS_IMAGE_FILES; + else if (ascii_type == "audio/*") + description_id = IDS_AUDIO_FILES; + else if (ascii_type == "video/*") + description_id = IDS_VIDEO_FILES; net::GetExtensionsForMimeType(ascii_type, extensions); } @@ -307,7 +333,7 @@ FileSelectHelper::GetFileTypesFromAcceptType( // If no valid extension is added, bail out. if (valid_type_count == 0) - return NULL; + return base_file_type.Pass(); // Use a generic description "Custom Files" if either of the following is // true: @@ -317,11 +343,15 @@ FileSelectHelper::GetFileTypesFromAcceptType( // dialog uses the first extension in the list to form the description, // like "EHTML Files". This is not what we want. if (valid_type_count > 1 || - (valid_type_count == 1 && !description_known && extensions->size() > 1)) + (valid_type_count == 1 && description_id == 0 && extensions->size() > 1)) + description_id = IDS_CUSTOM_FILES; + + if (description_id) { file_type->extension_description_overrides.push_back( - ASCIIToUTF16("Custom Files")); + l10n_util::GetStringUTF16(description_id)); + } - return file_type.release(); + return file_type.Pass(); } // static @@ -337,7 +367,7 @@ void FileSelectHelper::RunFileChooser(content::WebContents* tab, // static void FileSelectHelper::EnumerateDirectory(content::WebContents* tab, int request_id, - const FilePath& path) { + const base::FilePath& path) { // FileSelectHelper will keep itself alive until it sends the result message. scoped_refptr file_select_helper( new FileSelectHelper()); @@ -353,8 +383,12 @@ void FileSelectHelper::RunFileChooser(RenderViewHost* render_view_host, render_view_host_ = render_view_host; web_contents_ = web_contents; notification_registrar_.RemoveAll(); + notification_registrar_.Add(this, + content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED, + content::Source(web_contents_)); notification_registrar_.Add( - this, content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED, + this, + content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED, content::Source(render_view_host_)); notification_registrar_.Add( this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, @@ -374,8 +408,8 @@ void FileSelectHelper::RunFileChooser(RenderViewHost* render_view_host, void FileSelectHelper::RunFileChooserOnFileThread( const FileChooserParams& params) { - select_file_types_.reset( - GetFileTypesFromAcceptType(params.accept_types)); + select_file_types_ = GetFileTypesFromAcceptType(params.accept_types); + select_file_types_->support_drive = !params.need_local_path; BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, @@ -391,8 +425,12 @@ void FileSelectHelper::RunFileChooserOnUIThread( return; } - select_file_dialog_ = ui::SelectFileDialog::Create(this, NULL); + select_file_dialog_ = ui::SelectFileDialog::Create( + this, NULL); + if (!select_file_dialog_.get()) + return; + dialog_mode_ = params.mode; switch (params.mode) { case FileChooserParams::Open: dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE; @@ -400,7 +438,7 @@ void FileSelectHelper::RunFileChooserOnUIThread( case FileChooserParams::OpenMultiple: dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE; break; - case FileChooserParams::OpenFolder: + case FileChooserParams::UploadFolder: dialog_type_ = ui::SelectFileDialog::SELECT_FOLDER; break; case FileChooserParams::Save: @@ -412,22 +450,34 @@ void FileSelectHelper::RunFileChooserOnUIThread( NOTREACHED(); } - FilePath default_file_name = params.default_file_name; - FilePath working_path = params.initial_path; + base::FilePath default_file_name = params.default_file_name; + base::FilePath working_path = params.initial_path; gfx::NativeWindow owning_window = platform_util::GetTopLevel(render_view_host_->GetView()->GetNativeView()); +#if defined(OS_ANDROID) + // Android needs the original MIME types and an additional capture value. + std::pair, bool> accept_types = + std::make_pair(params.accept_types, params.capture); +#endif + select_file_dialog_->SelectFile( dialog_type_, params.title, default_file_name, select_file_types_.get(), - select_file_types_.get() ? 1 : 0, // 1-based index. - FILE_PATH_LITERAL(""), + select_file_types_.get() && !select_file_types_->extensions.empty() + ? 1 + : 0, // 1-based index of default extension to show. + base::FilePath::StringType(), owning_window, - const_cast(¶ms), - working_path); +#if defined(OS_ANDROID) + &accept_types); +#else + NULL, + working_path); +#endif select_file_types_.reset(); } @@ -436,6 +486,12 @@ void FileSelectHelper::RunFileChooserOnUIThread( // chooser dialog. Perform any cleanup and release the reference we added // in RunFileChooser(). void FileSelectHelper::RunFileChooserEnd() { + // If there are temporary files, then this instance needs to stick around + // until web_contents_ is destroyed, so that this instance can delete the + // temporary files. + if (!temporary_files_.empty()) + return; + render_view_host_ = NULL; web_contents_ = NULL; Release(); @@ -443,7 +499,7 @@ void FileSelectHelper::RunFileChooserEnd() { void FileSelectHelper::EnumerateDirectory(int request_id, RenderViewHost* render_view_host, - const FilePath& path) { + const base::FilePath& path) { // Because this class returns notifications to the RenderViewHost, it is // difficult for callers to know how long to keep a reference to this @@ -475,9 +531,20 @@ void FileSelectHelper::Observe(int type, case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: { DCHECK(content::Source(source).ptr() == web_contents_); web_contents_ = NULL; - break; } + // Intentional fall through. + case content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED: + if (!temporary_files_.empty()) { + DeleteTemporaryFiles(); + + // Now that the temporary files have been scheduled for deletion, there + // is no longer any reason to keep this instance around. + Release(); + } + + break; + default: NOTREACHED(); } @@ -490,8 +557,9 @@ bool FileSelectHelper::IsAcceptTypeValid(const std::string& accept_type) { // of an extension or a "/" in the case of a MIME type). std::string unused; if (accept_type.length() <= 1 || - StringToLowerASCII(accept_type) != accept_type || - TrimWhitespaceASCII(accept_type, TRIM_ALL, &unused) != TRIM_NONE) { + base::StringToLowerASCII(accept_type) != accept_type || + base::TrimWhitespaceASCII(accept_type, base::TRIM_ALL, &unused) != + base::TRIM_NONE) { return false; } return true; diff --git a/src/browser/file_select_helper.h b/src/browser/file_select_helper.h index fd8290c9a6..35036d1ff4 100644 --- a/src/browser/file_select_helper.h +++ b/src/browser/file_select_helper.h @@ -1,25 +1,9 @@ -// Copyright (c) 2012 Intel Corp -// Copyright (c) 2012 The Chromium Authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co -// pies of the Software, and to permit persons to whom the Software is furnished -// to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in al -// l copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM -// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES -// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH -// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -#ifndef CONTENT_NW_SRC_BROWSER_FILE_SELECT_HELPER_H_ -#define CONTENT_NW_SRC_BROWSER_FILE_SELECT_HELPER_H_ +// 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_FILE_SELECT_HELPER_H_ +#define CHROME_BROWSER_FILE_SELECT_HELPER_H_ #include #include @@ -28,22 +12,22 @@ #include "base/gtest_prod_util.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" +#include "content/public/common/file_chooser_params.h" #include "net/base/directory_lister.h" #include "ui/shell_dialogs/select_file_dialog.h" +class Profile; + namespace content { +struct FileChooserFileInfo; class RenderViewHost; class WebContents; -struct FileChooserParams; } namespace ui { struct SelectedFileInfo; } -namespace base { -class FilePath; -} // This class handles file-selection requests coming from WebUI elements // (via the extensions::ExtensionHost class). It implements both the // initialisation and listener functions for file-selection dialogs. @@ -65,8 +49,9 @@ class FileSelectHelper private: friend class base::RefCountedThreadSafe; FRIEND_TEST_ALL_PREFIXES(FileSelectHelperTest, IsAcceptTypeValid); + FRIEND_TEST_ALL_PREFIXES(FileSelectHelperTest, ZipPackage); explicit FileSelectHelper(); - virtual ~FileSelectHelper(); + ~FileSelectHelper() override; // Utility class which can listen for directory lister events and relay // them to the main object with the correct tracking id. @@ -76,10 +61,11 @@ class FileSelectHelper DirectoryListerDispatchDelegate(FileSelectHelper* parent, int id) : parent_(parent), id_(id) {} - virtual ~DirectoryListerDispatchDelegate() {} - virtual void OnListFile( - const net::DirectoryLister::DirectoryListerData& data) OVERRIDE; - virtual void OnListDone(int error) OVERRIDE; + ~DirectoryListerDispatchDelegate() override {} + void OnListFile( + const net::DirectoryLister::DirectoryListerData& data) override; + void OnListDone(int error) override; + private: // This FileSelectHelper owns this object. FileSelectHelper* parent_; @@ -89,7 +75,7 @@ class FileSelectHelper }; void RunFileChooser(content::RenderViewHost* render_view_host, - content::WebContents* tab_contents, + content::WebContents* web_contents, const content::FileChooserParams& params); void RunFileChooserOnFileThread( const content::FileChooserParams& params); @@ -101,23 +87,23 @@ class FileSelectHelper void RunFileChooserEnd(); // SelectFileDialog::Listener overrides. - virtual void FileSelected( - const base::FilePath& path, int index, void* params) OVERRIDE; - virtual void FileSelectedWithExtraInfo( - const ui::SelectedFileInfo& file, - int index, - void* params) OVERRIDE; - virtual void MultiFilesSelected(const std::vector& files, - void* params) OVERRIDE; - virtual void MultiFilesSelectedWithExtraInfo( + void FileSelected(const base::FilePath& path, + int index, + void* params) override; + void FileSelectedWithExtraInfo(const ui::SelectedFileInfo& file, + int index, + void* params) override; + void MultiFilesSelected(const std::vector& files, + void* params) override; + void MultiFilesSelectedWithExtraInfo( const std::vector& files, - void* params) OVERRIDE; - virtual void FileSelectionCanceled(void* params) OVERRIDE; + void* params) override; + void FileSelectionCanceled(void* params) override; // content::NotificationObserver overrides. - virtual void Observe(int type, - const content::NotificationSource& source, - const content::NotificationDetails& details) OVERRIDE; + void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) override; void EnumerateDirectory(int request_id, content::RenderViewHost* render_view_host, @@ -138,15 +124,45 @@ class FileSelectHelper // callback is received from the enumeration code. void EnumerateDirectoryEnd(); - bool extract_directory_; +#if defined(OS_MACOSX) && !defined(OS_IOS) + // Must be called on the FILE_USER_BLOCKING thread. Each selected file that is + // a package will be zipped, and the zip will be passed to the render view + // host in place of the package. + void ProcessSelectedFilesMac(const std::vector& files); + + // Saves the paths of |zipped_files| for later deletion. Passes |files| to the + // render view host. + void ProcessSelectedFilesMacOnUIThread( + const std::vector& files, + const std::vector& zipped_files); + + // Zips the package at |path| into a temporary destination. Returns the + // temporary destination, if the zip was successful. Otherwise returns an + // empty path. + static base::FilePath ZipPackage(const base::FilePath& path); +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + + // Utility method that passes |files| to the render view host, and ends the + // file chooser. + void NotifyRenderViewHostAndEnd( + const std::vector& files); + + // Sends the result to the render process, and call |RunFileChooserEnd|. + void NotifyRenderViewHostAndEndAfterConversion( + const std::vector& list); + + // Schedules the deletion of the files in |temporary_files_| and clears the + // vector. + void DeleteTemporaryFiles(); // Helper method to get allowed extensions for select file dialog from // the specified accept types as defined in the spec: // http://whatwg.org/html/number-state.html#attr-input-accept // |accept_types| contains only valid lowercased MIME types or file extensions // beginning with a period (.). - ui::SelectFileDialog::FileTypeInfo* GetFileTypesFromAcceptType( - const std::vector& accept_types); + static scoped_ptr + GetFileTypesFromAcceptType( + const std::vector& accept_types); // Check the accept type is valid. It is expected to be all lower case with // no whitespace. @@ -164,6 +180,9 @@ class FileSelectHelper // The type of file dialog last shown. ui::SelectFileDialog::Type dialog_type_; + // The mode of file dialog last shown. + content::FileChooserParams::Mode dialog_mode_; + // Maintain a list of active directory enumerations. These could come from // the file select dialog or from drag-and-drop of directories, so there could // be more than one going on at a time. @@ -173,7 +192,13 @@ class FileSelectHelper // Registrar for notifications regarding our RenderViewHost. content::NotificationRegistrar notification_registrar_; + // Temporary files only used on OSX. This class is responsible for deleting + // these files when they are no longer needed. + std::vector temporary_files_; + + bool extract_directory_; + DISALLOW_COPY_AND_ASSIGN(FileSelectHelper); }; -#endif // CONTENT_NW_SRC_BROWSER_FILE_SELECT_HELPER_H_ +#endif // CHROME_BROWSER_FILE_SELECT_HELPER_H_ diff --git a/src/browser/file_select_helper_mac.mm b/src/browser/file_select_helper_mac.mm new file mode 100644 index 0000000000..4f0f4aad8c --- /dev/null +++ b/src/browser/file_select_helper_mac.mm @@ -0,0 +1,142 @@ +// 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/file_select_helper.h" + +#include +#include + +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/mac/foundation_util.h" +#include "content/public/browser/browser_thread.h" +#include "third_party/zlib/google/zip.h" +#include "ui/shell_dialogs/selected_file_info.h" + +namespace { + +// Given the |path| of a package, returns the destination that the package +// should be zipped to. Returns an empty path on any errors. +base::FilePath ZipDestination(const base::FilePath& path) { + NSMutableString* dest = + [NSMutableString stringWithString:NSTemporaryDirectory()]; + + // Couldn't get the temporary directory. + if (!dest) + return base::FilePath(); + + [dest appendFormat:@"%@/zip_cache/%@", + [[NSBundle mainBundle] bundleIdentifier], + [[NSProcessInfo processInfo] globallyUniqueString]]; + + return base::mac::NSStringToFilePath(dest); +} + +// Returns the path of the package and its components relative to the package's +// parent directory. +std::vector RelativePathsForPackage( + const base::FilePath& package) { + // Get the base directory. + base::FilePath base_dir = package.DirName(); + + // Add the package as the first relative path. + std::vector relative_paths; + relative_paths.push_back(package.BaseName()); + + // Add the components of the package as relative paths. + base::FileEnumerator file_enumerator( + package, + true /* recursive */, + base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES); + for (base::FilePath path = file_enumerator.Next(); !path.empty(); + path = file_enumerator.Next()) { + base::FilePath relative_path; + bool success = base_dir.AppendRelativePath(path, &relative_path); + if (success) + relative_paths.push_back(relative_path); + } + + return relative_paths; +} + +} // namespace + +base::FilePath FileSelectHelper::ZipPackage(const base::FilePath& path) { + base::FilePath dest(ZipDestination(path)); + if (dest.empty()) + return dest; + + if (!base::CreateDirectory(dest.DirName())) + return base::FilePath(); + + base::File file(dest, base::File::FLAG_CREATE | base::File::FLAG_WRITE); + if (!file.IsValid()) + return base::FilePath(); + + std::vector files_to_zip(RelativePathsForPackage(path)); + base::FilePath base_dir = path.DirName(); + bool success = zip::ZipFiles(base_dir, files_to_zip, file.GetPlatformFile()); + + int result = -1; + if (success) + result = fchmod(file.GetPlatformFile(), S_IRUSR); + + return result >= 0 ? dest : base::FilePath(); +} + +void FileSelectHelper::ProcessSelectedFilesMac( + const std::vector& files) { + DCHECK_CURRENTLY_ON(content::BrowserThread::FILE_USER_BLOCKING); + + // Make a mutable copy of the input files. + std::vector files_out(files); + std::vector temporary_files; + + for (auto& file_info : files_out) { + NSString* filename = base::mac::FilePathToNSString(file_info.local_path); + BOOL isPackage = + [[NSWorkspace sharedWorkspace] isFilePackageAtPath:filename]; + if (isPackage && base::DirectoryExists(file_info.local_path)) { + base::FilePath result = ZipPackage(file_info.local_path); + + if (!result.empty()) { + temporary_files.push_back(result); + file_info.local_path = result; + file_info.file_path = result; + file_info.display_name.append(".zip"); + } + } + } + + content::BrowserThread::PostTask( + content::BrowserThread::UI, + FROM_HERE, + base::Bind(&FileSelectHelper::ProcessSelectedFilesMacOnUIThread, + base::Unretained(this), + files_out, + temporary_files)); +} + +void FileSelectHelper::ProcessSelectedFilesMacOnUIThread( + const std::vector& files, + const std::vector& temporary_files) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + if (!temporary_files.empty()) { + temporary_files_.insert( + temporary_files_.end(), temporary_files.begin(), temporary_files.end()); + + // Typically, |temporary_files| are deleted after |web_contents_| is + // destroyed. If |web_contents_| is already NULL, then the temporary files + // need to be deleted now. + if (!web_contents_) { + DeleteTemporaryFiles(); + RunFileChooserEnd(); + return; + } + } + + NotifyRenderViewHostAndEnd(files); +} diff --git a/src/browser/login_view.cc b/src/browser/login_view.cc new file mode 100644 index 0000000000..75fb4ba1af --- /dev/null +++ b/src/browser/login_view.cc @@ -0,0 +1,89 @@ +// 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 "login_view.h" + +#include "base/strings/utf_string_conversions.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/views/controls/label.h" +#include "ui/views/controls/textfield/textfield.h" +#include "ui/views/layout/grid_layout.h" +#include "ui/views/layout/layout_constants.h" + +static const int kMessageWidth = 320; +static const int kTextfieldStackHorizontalSpacing = 30; + +using views::GridLayout; + +/////////////////////////////////////////////////////////////////////////////// +// LoginView, public: + +LoginView::LoginView(const base::string16& explanation) + : username_field_(new views::Textfield()), + password_field_(new views::Textfield()), + username_label_(new views::Label(base::ASCIIToUTF16("Username"))), + password_label_(new views::Label(base::ASCIIToUTF16("Password"))), + message_label_(new views::Label(explanation)) { + password_field_->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); + message_label_->SetMultiLine(true); + message_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); + message_label_->SetAllowCharacterBreak(true); + + // Initialize the Grid Layout Manager used for this dialog box. + GridLayout* layout = GridLayout::CreatePanel(this); + SetLayoutManager(layout); + + // Add the column set for the information message at the top of the dialog + // box. + const int single_column_view_set_id = 0; + views::ColumnSet* column_set = + layout->AddColumnSet(single_column_view_set_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::FIXED, kMessageWidth, 0); + + // Add the column set for the user name and password fields and labels. + const int labels_column_set_id = 1; + column_set = layout->AddColumnSet(labels_column_set_id); + column_set->AddPaddingColumn(0, kTextfieldStackHorizontalSpacing); + column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kTextfieldStackHorizontalSpacing); + + layout->StartRow(0, single_column_view_set_id); + layout->AddView(message_label_); + + layout->AddPaddingRow(0, views::kUnrelatedControlLargeVerticalSpacing); + + layout->StartRow(0, labels_column_set_id); + layout->AddView(username_label_); + layout->AddView(username_field_); + + layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); + + layout->StartRow(0, labels_column_set_id); + layout->AddView(password_label_); + layout->AddView(password_field_); + + layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing); + +} + +LoginView::~LoginView() { +} + +const base::string16& LoginView::GetUsername() const { + return username_field_->text(); +} + +const base::string16& LoginView::GetPassword() const { + return password_field_->text(); +} + +views::View* LoginView::GetInitiallyFocusedView() { + return username_field_; +} + diff --git a/src/browser/login_view.h b/src/browser/login_view.h new file mode 100644 index 0000000000..2d43e9c9d3 --- /dev/null +++ b/src/browser/login_view.h @@ -0,0 +1,47 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_UI_VIEWS_LOGIN_VIEW_H_ +#define CHROME_BROWSER_UI_VIEWS_LOGIN_VIEW_H_ + +#include "base/compiler_specific.h" +#include "ui/views/view.h" + +namespace views { +class Label; +class Textfield; +} + +// This class is responsible for displaying the contents of a login window +// for HTTP/FTP authentication. +class LoginView : public views::View { + public: + // |model| is observed for the entire lifetime of the LoginView. + // Therefore |model| should not be destroyed before the LoginView object. + LoginView(const base::string16& explanation); + ~LoginView() final; + + // Access the data in the username/password text fields. + const base::string16& GetUsername() const; + const base::string16& GetPassword() const; + + // Used by LoginHandlerWin to set the initial focus. + views::View* GetInitiallyFocusedView(); + + private: + // Non-owning refs to the input text fields. + views::Textfield* username_field_; + views::Textfield* password_field_; + + // Button labels + views::Label* username_label_; + views::Label* password_label_; + + // Authentication message. + views::Label* message_label_; + + DISALLOW_COPY_AND_ASSIGN(LoginView); +}; + +#endif // CHROME_BROWSER_UI_VIEWS_LOGIN_VIEW_H_ diff --git a/src/browser/media_capture_util.cc b/src/browser/media_capture_util.cc new file mode 100644 index 0000000000..30e3fb63c7 --- /dev/null +++ b/src/browser/media_capture_util.cc @@ -0,0 +1,88 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/shell/browser/media_capture_util.h" + +#include + +#include "base/callback.h" +#include "base/logging.h" +#include "content/public/browser/media_capture_devices.h" +#include "extensions/common/permissions/permissions_data.h" + +using content::MediaCaptureDevices; +using content::MediaStreamDevice; +using content::MediaStreamDevices; +using content::MediaStreamUI; + +namespace extensions { + +const MediaStreamDevice* GetRequestedDeviceOrDefault( + const MediaStreamDevices& devices, + const std::string& requested_device_id) { + if (!requested_device_id.empty()) + return devices.FindById(requested_device_id); + + if (!devices.empty()) + return &devices[0]; + + return NULL; +} + +namespace media_capture_util { + +// See also Chrome's MediaCaptureDevicesDispatcher. +void GrantMediaStreamRequest(content::WebContents* web_contents, + const content::MediaStreamRequest& request, + const content::MediaResponseCallback& callback, + const Extension* extension) { + // app_shell only supports audio and video capture, not tab or screen capture. + DCHECK(request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE || + request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE); + + MediaStreamDevices devices; + + if (request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE) { + VerifyMediaAccessPermission(request.audio_type, extension); + const MediaStreamDevice* device = GetRequestedDeviceOrDefault( + MediaCaptureDevices::GetInstance()->GetAudioCaptureDevices(), + request.requested_audio_device_id); + if (device) + devices.push_back(*device); + } + + if (request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE) { + VerifyMediaAccessPermission(request.video_type, extension); + const MediaStreamDevice* device = GetRequestedDeviceOrDefault( + MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices(), + request.requested_video_device_id); + if (device) + devices.push_back(*device); + } + + // TODO(jamescook): Should we show a recording icon somewhere? If so, where? + scoped_ptr ui; + callback.Run(devices, + devices.empty() ? content::MEDIA_DEVICE_INVALID_STATE + : content::MEDIA_DEVICE_OK, + ui.Pass()); +} + +void VerifyMediaAccessPermission(content::MediaStreamType type, + const Extension* extension) { + const PermissionsData* permissions_data = extension->permissions_data(); + if (type == content::MEDIA_DEVICE_AUDIO_CAPTURE) { + // app_shell has no UI surface to show an error, and on an embedded device + // it's better to crash than to have a feature not work. + CHECK(permissions_data->HasAPIPermission(APIPermission::kAudioCapture)) + << "Audio capture request but no audioCapture permission in manifest."; + } else { + DCHECK(type == content::MEDIA_DEVICE_VIDEO_CAPTURE); + CHECK(permissions_data->HasAPIPermission(APIPermission::kVideoCapture)) + << "Video capture request but no videoCapture permission in manifest."; + } +} + +} // namespace media_capture_util +} // namespace extensions diff --git a/src/browser/media_capture_util.h b/src/browser/media_capture_util.h new file mode 100644 index 0000000000..b1b15ba98c --- /dev/null +++ b/src/browser/media_capture_util.h @@ -0,0 +1,38 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_SHELL_BROWSER_MEDIA_CAPTURE_UTIL_H_ +#define EXTENSIONS_SHELL_BROWSER_MEDIA_CAPTURE_UTIL_H_ + +#include "base/macros.h" +#include "content/public/common/media_stream_request.h" + +namespace content { +class WebContents; +} + +namespace extensions { + +class Extension; + +namespace media_capture_util { + +// Grants access to audio and video capture devices. +// * If the caller requests specific device ids, grants access to those. +// * If the caller does not request specific ids, grants access to the first +// available device. +// Usually used as a helper for media capture ProcessMediaAccessRequest(). +void GrantMediaStreamRequest(content::WebContents* web_contents, + const content::MediaStreamRequest& request, + const content::MediaResponseCallback& callback, + const Extension* extension); + +// Verifies that the extension has permission for |type|. If not, crash. +void VerifyMediaAccessPermission(content::MediaStreamType type, + const Extension* extension); + +} // namespace media_capture_util +} // namespace extensions + +#endif // EXTENSIONS_SHELL_BROWSER_MEDIA_CAPTURE_UTIL_H_ diff --git a/src/browser/menubar_controller.cc b/src/browser/menubar_controller.cc new file mode 100644 index 0000000000..b661117872 --- /dev/null +++ b/src/browser/menubar_controller.cc @@ -0,0 +1,76 @@ +#include "content/nw/src/browser/menubar_controller.h" + +#include "base/stl_util.h" +#include "content/nw/src/browser/menubar_view.h" +#include "ui/views/controls/button/menu_button.h" +#include "ui/views/controls/menu/menu_item_view.h" +#include "ui/views/widget/widget.h" + +namespace nw { + +MenuBarController::ModelToMenuMap MenuBarController::model_to_menu_map_; +MenuBarController* MenuBarController::master_; + +MenuBarController::MenuBarController(MenuBarView* menubar, ui::MenuModel* menu_model, MenuBarController* master) + :MenuModelAdapter(menu_model), menubar_(menubar) { + + views::MenuItemView* menu = MenuBarController::CreateMenu(menubar, menu_model, this); + if (!master) { + master_ = this; + menu_runner_.reset(new views::MenuRunner(menu, views::MenuRunner::HAS_MNEMONICS)); + } +} + +MenuBarController::~MenuBarController() { + if (master_ == this) { + STLDeleteElements(&controllers_); + model_to_menu_map_.clear(); + } +} + +views::MenuItemView* MenuBarController::GetSiblingMenu( + views::MenuItemView* menu, + const gfx::Point& screen_point, + views::MenuAnchorPosition* anchor, + bool* has_mnemonics, + views::MenuButton** button) { + if (!menubar_) + return NULL; + gfx::Point menubar_loc(screen_point); + views::View::ConvertPointFromScreen(menubar_, &menubar_loc); + ui::MenuModel* model; + if (!menubar_->GetMenuButtonAtLocation(menubar_loc, &model, button)) + return NULL; + + *has_mnemonics = false; + *anchor = views::MENU_ANCHOR_TOPLEFT; + if (!model_to_menu_map_[model]) { + MenuBarController* controller = new MenuBarController(menubar_, model, master_); + CreateMenu(menubar_, model, controller); + controllers_.push_back(controller); + } + + return model_to_menu_map_[model]; +} + +views::MenuItemView* MenuBarController::CreateMenu(MenuBarView* menubar, + ui::MenuModel* model, + MenuBarController* controller) { + views::MenuItemView* menu = new views::MenuItemView(controller); + controller->BuildMenu(menu); + model_to_menu_map_[model] = menu; + + return menu; +} + +void MenuBarController::RunMenuAt(views::View* view, const gfx::Point& point) { + + ignore_result(menu_runner_->RunMenuAt(view->GetWidget()->GetTopLevelWidget(), + static_cast(view), + gfx::Rect(point, gfx::Size()), + views::MENU_ANCHOR_TOPRIGHT, + ui::MENU_SOURCE_NONE)); + delete this; +} + +} //namespace nw diff --git a/src/browser/menubar_controller.h b/src/browser/menubar_controller.h new file mode 100644 index 0000000000..b7ecf0dcdf --- /dev/null +++ b/src/browser/menubar_controller.h @@ -0,0 +1,45 @@ +#ifndef NW_BROWSER_MENUBAR_CONTROLLER_H +#define NW_BROWSER_MENUBAR_CONTROLLER_H + +#include "ui/views/controls/menu/menu_model_adapter.h" +#include "ui/views/controls/menu/menu_runner.h" +#include "ui/views/view.h" + +#include + +namespace ui { +class MenuModel; +} + +namespace nw { +class MenuBarView; + +class MenuBarController : public views::MenuModelAdapter { + public: + MenuBarController(MenuBarView* menubar, ui::MenuModel* menu_model, MenuBarController* master); + ~MenuBarController() override; + + static views::MenuItemView* CreateMenu(MenuBarView* menubar, ui::MenuModel* model, MenuBarController* controller); + void RunMenuAt(views::View* view, const gfx::Point& point); + + views::MenuItemView* GetSiblingMenu( + views::MenuItemView* menu, + const gfx::Point& screen_point, + views::MenuAnchorPosition* anchor, + bool* has_mnemonics, + views::MenuButton** button) override; + + private: + typedef std::map ModelToMenuMap; + + MenuBarView* menubar_; + scoped_ptr menu_runner_; + std::vector controllers_; + static ModelToMenuMap model_to_menu_map_; + static MenuBarController* master_; + + DISALLOW_COPY_AND_ASSIGN(MenuBarController); +}; + +} //namespace nw +#endif diff --git a/src/browser/menubar_view.cc b/src/browser/menubar_view.cc new file mode 100644 index 0000000000..3592efcb00 --- /dev/null +++ b/src/browser/menubar_view.cc @@ -0,0 +1,128 @@ +// 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 "content/nw/src/browser/menubar_view.h" + + +#include "content/nw/src/browser/menubar_controller.h" +#include "ui/base/models/menu_model.h" +#include "ui/base/window_open_disposition.h" +#include "ui/gfx/text_elider.h" +#include "ui/views/controls/button/menu_button.h" +#include "ui/views/layout/box_layout.h" +#include "ui/views/widget/widget.h" + +using views::MenuRunner; + +#if !defined(OS_WIN) +static const gfx::ElideBehavior kElideBehavior = gfx::FADE_TAIL; +#else +// Windows fade eliding causes text to darken; see http://crbug.com/388084 +static const gfx::ElideBehavior kElideBehavior = gfx::ELIDE_TAIL; +#endif + +namespace nw { + +const char MenuBarView::kViewClassName[] = "BookmarkBarView"; + +// MenuBarButton ------------------------------------------------------- + +// Buttons used on the menu bar. Copied from BookmarkFolderButton + +class MenuBarButton : public views::MenuButton { + public: + MenuBarButton(views::ButtonListener* listener, + const base::string16& title, + views::MenuButtonListener* menu_button_listener, + bool show_menu_marker) + : MenuButton(listener, title, menu_button_listener, show_menu_marker) { + SetElideBehavior(kElideBehavior); + } + + bool GetTooltipText(const gfx::Point& p, + base::string16* tooltip) const override { + if (label()->GetPreferredSize().width() > label()->size().width()) + *tooltip = GetText(); + return !tooltip->empty(); + } + + bool IsTriggerableEvent(const ui::Event& e) override { + // Left clicks and taps should show the menu contents and right clicks + // should show the context menu. They should not trigger the opening of + // underlying urls. + if (e.type() == ui::ET_GESTURE_TAP || + (e.IsMouseEvent() && (e.flags() & + (ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON)))) + return false; + + if (e.IsMouseEvent()) + return ui::DispositionFromEventFlags(e.flags()) != CURRENT_TAB; + return false; + } + + private: + + DISALLOW_COPY_AND_ASSIGN(MenuBarButton); +}; + +MenuBarView::MenuBarView() { + SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0)); +} + +MenuBarView::~MenuBarView() { +} + +void MenuBarView::UpdateMenu(ui::MenuModel* model) { + RemoveAllChildViews(true); + InitView(model); + Layout(); + PreferredSizeChanged(); + SchedulePaint(); +} + +void MenuBarView::InitView(ui::MenuModel* model) { + model_ = model; + for (int i = 0; i < model_->GetItemCount(); i++) { + AddChildView(new MenuBarButton(this, model_->GetLabelAt(i), this, false)); + } +} + +bool MenuBarView::GetMenuButtonAtLocation(const gfx::Point& loc, ui::MenuModel** model, views::MenuButton** button) { + if (!model_) + return false; + if (loc.x() < 0 || loc.x() >= width() || loc.y() < 0 || loc.y() >= height()) + return false; + for (int i = 0; i < model_->GetItemCount(); i++) { + views::View* child = child_at(i); + if (child->bounds().Contains(loc) && + (model_->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU)) { + *model = model_->GetSubmenuModelAt(i); + *button = static_cast(child); + return true; + } + } + return false; +} + +void MenuBarView::OnMenuButtonClicked(views::View* view, + const gfx::Point& point) { + int button_index = GetIndexOf(view); + DCHECK_NE(-1, button_index); + ui::MenuModel::ItemType type = model_->GetTypeAt(button_index); + if (type == ui::MenuModel::TYPE_SUBMENU) { + MenuBarController* controller = new MenuBarController(this, model_->GetSubmenuModelAt(button_index), NULL); + controller->RunMenuAt(view, point); + } +} + +void MenuBarView::ButtonPressed(views::Button* sender, + const ui::Event& event) { +} + +void MenuBarView::OnNativeThemeChanged(const ui::NativeTheme* theme) { + set_background(views::Background::CreateSolidBackground(GetNativeTheme()-> + GetSystemColor(ui::NativeTheme::kColorId_MenuBackgroundColor))); +} + +} //namespace nw diff --git a/src/browser/menubar_view.h b/src/browser/menubar_view.h new file mode 100644 index 0000000000..00318f0170 --- /dev/null +++ b/src/browser/menubar_view.h @@ -0,0 +1,65 @@ +// 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 NW_BROWSER_MENUBAR_VIEWS_H_ +#define NW_BROWSER_MENUBAR_VIEWS_H_ + +#include "base/strings/string16.h" +#include "ui/views/accessible_pane_view.h" +#include "ui/views/controls/button/button.h" +#include "ui/views/controls/button/menu_button_listener.h" + +namespace views { + class MenuRunner; + class MenuButton; +} + +namespace ui { + class MenuModel; +} + +namespace nw { + +/////////////////////////////////////////////////////////////////////////////// +// +// copied from chrome/browser/ui/views/bookmarks/bookmark_bar_view.h +// +/////////////////////////////////////////////////////////////////////////////// + +class MenuBarView : + public views::AccessiblePaneView, + public views::MenuButtonListener, + public views::ButtonListener { + + public: + // The internal view class name. + static const char kViewClassName[]; + + // Maximum size of buttons + static const int kMaxButtonWidth; + + // |browser_view| can be NULL during tests. + MenuBarView(); + ~MenuBarView() override; + + void UpdateMenu(ui::MenuModel* model); + void InitView(ui::MenuModel* model); + + bool GetMenuButtonAtLocation(const gfx::Point& loc, ui::MenuModel** model, views::MenuButton** button); + + // views::MenuButtonListener: + void OnMenuButtonClicked(views::View* view, + const gfx::Point& point) override; + + // views::ButtonListener: + void ButtonPressed(views::Button* sender, + const ui::Event& event) override; + void OnNativeThemeChanged(const ui::NativeTheme* theme) override; + + private: + ui::MenuModel* model_; + DISALLOW_COPY_AND_ASSIGN(MenuBarView); +}; +} //namespace nw +#endif diff --git a/src/browser/native_window.cc b/src/browser/native_window.cc index 1f8fbd88f8..997d513c3b 100644 --- a/src/browser/native_window.cc +++ b/src/browser/native_window.cc @@ -20,27 +20,35 @@ #include "content/nw/src/browser/native_window.h" +#include "base/memory/weak_ptr.h" #include "base/values.h" +#include "base/command_line.h" #include "content/nw/src/browser/capture_page_helper.h" #include "content/nw/src/common/shell_switches.h" #include "content/nw/src/nw_package.h" #include "content/nw/src/nw_shell.h" +#include "content/public/common/content_switches.h" #include "grit/nw_resources.h" #include "ui/base/resource/resource_bundle.h" -#include "ui/gfx/rect.h" +#include "ui/gfx/geometry/rect.h" #if defined(OS_MACOSX) #include "content/nw/src/browser/native_window_helper_mac.h" -#elif defined(TOOLKIT_GTK) -#include "content/nw/src/browser/native_window_gtk.h" +#elif defined(OS_LINUX) +#include "content/nw/src/browser/native_window_aura.h" #elif defined(OS_WIN) -#include "content/nw/src/browser/native_window_win.h" +#include "content/nw/src/browser/native_window_aura.h" #endif +namespace content { + extern bool g_support_transparency; + extern bool g_force_cpu_draw; +} + namespace nw { // static -NativeWindow* NativeWindow::Create(content::Shell* shell, +NativeWindow* NativeWindow::Create(const base::WeakPtr& shell, base::DictionaryValue* manifest) { // Set default width/height. if (!manifest->HasKey(switches::kmWidth)) @@ -49,13 +57,13 @@ NativeWindow* NativeWindow::Create(content::Shell* shell, manifest->SetInteger(switches::kmHeight, 450); // Create window. - NativeWindow* window = -#if defined(TOOLKIT_GTK) - new NativeWindowGtk(shell, manifest); + NativeWindow* window = +#if defined(OS_LINUX) + new NativeWindowAura(shell, manifest); #elif defined(OS_MACOSX) CreateNativeWindowCocoa(shell, manifest); #elif defined(OS_WIN) - new NativeWindowWin(shell, manifest); + new NativeWindowAura(shell, manifest); #else NULL; NOTREACHED() << "Cannot create native window on unsupported platform."; @@ -64,13 +72,24 @@ NativeWindow* NativeWindow::Create(content::Shell* shell, return window; } -NativeWindow::NativeWindow(content::Shell* shell, +NativeWindow::NativeWindow(const base::WeakPtr& shell, base::DictionaryValue* manifest) : shell_(shell), + transparent_(false), has_frame_(true), capture_page_helper_(NULL) { manifest->GetBoolean(switches::kmFrame, &has_frame_); - + content::g_support_transparency = !base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kmDisableTransparency); + if (content::g_support_transparency) { + content::g_force_cpu_draw = base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kForceCpuDraw); + if (content::g_force_cpu_draw) { + if (!base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableGpu)) { + content::g_force_cpu_draw = false; + LOG(WARNING) << "switch " << switches::kForceCpuDraw << " must be used with switch " << switches::kDisableGpu; + } + } + manifest->GetBoolean(switches::kmTransparent, &transparent_); + } LoadAppIconFromPackage(manifest); } @@ -83,7 +102,7 @@ content::WebContents* NativeWindow::web_contents() const { void NativeWindow::InitFromManifest(base::DictionaryValue* manifest) { // Setup window from manifest. - int x, y; + int x, y = 0; std::string position; if (manifest->GetInteger(switches::kmX, &x) && manifest->GetInteger(switches::kmY, &y)) { @@ -115,6 +134,16 @@ void NativeWindow::InitFromManifest(base::DictionaryValue* manifest) { if (manifest->GetBoolean(switches::kmAlwaysOnTop, &top) && top) { SetAlwaysOnTop(true); } + bool showInTaskbar; + if (manifest->GetBoolean(switches::kmShowInTaskbar, &showInTaskbar) && + !showInTaskbar) { + SetShowInTaskbar(false); + } + bool all_workspaces; + if (manifest->GetBoolean(switches::kmVisibleOnAllWorkspaces, &all_workspaces) + && all_workspaces) { + SetVisibleOnAllWorkspaces(true); + } bool fullscreen; if (manifest->GetBoolean(switches::kmFullscreen, &fullscreen) && fullscreen) { SetFullscreen(true); diff --git a/src/browser/native_window.h b/src/browser/native_window.h index 9e7ff5d3d0..578f74fc6c 100644 --- a/src/browser/native_window.h +++ b/src/browser/native_window.h @@ -1,16 +1,16 @@ // Copyright (c) 2012 Intel Corp // Copyright (c) 2012 The Chromium Authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy +// +// Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co // pies of the Software, and to permit persons to whom the Software is furnished // to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in al // l copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM // PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES // S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS @@ -26,13 +26,19 @@ #include "base/basictypes.h" #include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" #include "base/compiler_specific.h" +#include "content/nw/src/nw_shell.h" #include "ui/gfx/image/image.h" #include "ui/gfx/native_widget_types.h" -#include "ui/gfx/point.h" -#include "ui/gfx/size.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/size.h" -namespace api { +#if defined(OS_WIN) || defined(OS_LINUX) +#include "components/web_modal/web_contents_modal_dialog_host.h" +#endif + +namespace nwapi { class Menu; } @@ -58,11 +64,17 @@ namespace nw { class CapturePageHelper; +#if defined(OS_WIN) || defined(OS_LINUX) +class NativeWindow : public web_modal::WebContentsModalDialogHost { +public: + ~NativeWindow() override; +#else class NativeWindow { - public: +public: virtual ~NativeWindow(); +#endif - static NativeWindow* Create(content::Shell* shell, + static NativeWindow* Create(const base::WeakPtr& shell, base::DictionaryValue* manifest); void InitFromManifest(base::DictionaryValue* manifest); @@ -78,20 +90,28 @@ class NativeWindow { virtual void Restore() = 0; virtual void SetFullscreen(bool fullscreen) = 0; virtual bool IsFullscreen() = 0; + virtual void SetTransparent(bool transparent) = 0; virtual void SetSize(const gfx::Size& size) = 0; virtual gfx::Size GetSize() = 0; virtual void SetMinimumSize(int width, int height) = 0; virtual void SetMaximumSize(int width, int height) = 0; virtual void SetResizable(bool resizable) = 0; virtual void SetAlwaysOnTop(bool top) = 0; + virtual void SetShowInTaskbar(bool show = true) = 0; + virtual void SetVisibleOnAllWorkspaces(bool all_workspaces) = 0; virtual void SetPosition(const std::string& position) = 0; virtual void SetPosition(const gfx::Point& position) = 0; virtual gfx::Point GetPosition() = 0; virtual void SetTitle(const std::string& title) = 0; - virtual void FlashFrame(bool flash) = 0; + virtual void FlashFrame(int count) = 0; + virtual void SetBadgeLabel(const std::string& badge) = 0; + virtual void SetProgressBar(double progress) = 0; virtual void SetKiosk(bool kiosk) = 0; virtual bool IsKiosk() = 0; - virtual void SetMenu(api::Menu* menu) = 0; + virtual void SetMenu(nwapi::Menu* menu) = 0; + virtual void ClearMenu() {} + virtual void SetInitialFocus(bool accept_focus) = 0; + virtual bool InitialFocus() = 0; // Toolbar related controls. enum TOOLBAR_BUTTON { @@ -112,18 +132,32 @@ class NativeWindow { virtual void HandleKeyboardEvent( const content::NativeWebKeyboardEvent& event) = 0; - content::Shell* shell() const { return shell_; } +#if defined(OS_WIN) + virtual gfx::NativeView GetHostView() const override = 0; + virtual gfx::Point GetDialogPosition(const gfx::Size& size) override = 0; + virtual void AddObserver(web_modal::ModalDialogHostObserver* observer) override = 0; + virtual void RemoveObserver(web_modal::ModalDialogHostObserver* observer) override = 0; + virtual gfx::Size GetMaximumDialogSize() override = 0; +#endif + content::Shell* shell() const { return shell_.get(); } content::WebContents* web_contents() const; bool has_frame() const { return has_frame_; } const gfx::Image& app_icon() const { return app_icon_; } void CapturePage(const std::string& image_format); + bool IsTransparent() { return transparent_; } protected: - explicit NativeWindow(content::Shell* shell, + void OnNativeWindowDestory() { + if (shell_) + delete shell_.get(); + } + explicit NativeWindow(const base::WeakPtr& shell, base::DictionaryValue* manifest); // Weak reference to parent. - content::Shell* shell_; + base::WeakPtr shell_; + + bool transparent_; bool has_frame_; @@ -131,6 +165,7 @@ class NativeWindow { gfx::Image app_icon_; scoped_refptr capture_page_helper_; + friend class content::Shell; private: void LoadAppIconFromPackage(base::DictionaryValue* manifest); diff --git a/src/browser/native_window_aura.cc b/src/browser/native_window_aura.cc new file mode 100644 index 0000000000..bb3ab1e425 --- /dev/null +++ b/src/browser/native_window_aura.cc @@ -0,0 +1,1208 @@ +// Copyright (c) 2012 Intel Corp +// Copyright (c) 2012 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "content/nw/src/browser/native_window_aura.h" + +#if defined(OS_WIN) +#include +#include +#endif + +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "base/command_line.h" + +#if defined(OS_WIN) +#include "base/win/scoped_comptr.h" +#include "base/win/windows_version.h" +#include "base/win/wrapped_window_proc.h" +#endif + +#include "chrome/browser/platform_util.h" +#ifdef OS_LINUX +#include "chrome/browser/ui/libgtk2ui/gtk2_ui.h" +#endif +#include "content/nw/src/api/menu/menu.h" +#include "content/nw/src/browser/browser_view_layout.h" +#include "content/nw/src/browser/menubar_view.h" +#include "content/nw/src/browser/native_window_toolbar_aura.h" +#include "content/nw/src/common/shell_switches.h" +#include "content/nw/src/nw_shell.h" +#include "content/public/browser/native_web_keyboard_event.h" +#include "content/public/browser/render_view_host.h" +#include "content/browser/renderer_host/render_view_host_impl.h" +#include "content/public/browser/render_widget_host_view.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/content_switches.h" +#include "extensions/common/draggable_region.h" +#include "third_party/skia/include/core/SkPaint.h" +#include "ui/base/hit_test.h" +#include "ui/gfx/native_widget_types.h" +#if defined(OS_WIN) +#include "ui/gfx/win/hwnd_util.h" +#include "ui/gfx/icon_util.h" +#include "ui/views/win/hwnd_util.h" +#endif +#include "ui/gfx/path.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/font_list.h" +#include "ui/gfx/platform_font.h" +#include "ui/gfx/image/image_skia_operations.h" +#include "ui/views/background.h" +#include "ui/views/controls/webview/webview.h" +#include "ui/views/layout/box_layout.h" +#include "ui/views/views_delegate.h" +#include "ui/views/views_switches.h" +#include "ui/views/widget/widget.h" +#include "ui/views/window/native_frame_view.h" +#include "ui/views/widget/native_widget_private.h" +#include "ui/events/event_handler.h" +#include "ui/wm/core/easy_resize_window_targeter.h" +#include "ui/aura/window.h" + +#include "chrome/browser/ui/views/accelerator_table.h" +#include "base/basictypes.h" +#include "chrome/app/chrome_command_ids.h" +#include "ui/base/accelerators/accelerator.h" +#include "ui/events/event_constants.h" +#if defined(USE_ASH) +#include "ash/accelerators/accelerator_table.h" +#endif + +using base::CommandLine; + +namespace content { + extern bool g_support_transparency; + extern bool g_force_cpu_draw; +} + + +namespace nw { + +namespace { + +const int kResizeInsideBoundsSize = 5; +const int kResizeAreaCornerSize = 16; + +// Returns true if |possible_parent| is a parent window of |child|. +bool IsParent(gfx::NativeView child, gfx::NativeView possible_parent) { + if (!child) + return false; +#if !defined(USE_AURA) && defined(OS_WIN) + if (::GetWindow(child, GW_OWNER) == possible_parent) + return true; +#endif + gfx::NativeView parent = child; + while ((parent = (gfx::NativeView)platform_util::GetParent(parent)) != NULL) { + if (possible_parent == parent) + return true; + } + + return false; +} + +#ifdef OS_LINUX +static void SetDeskopEnvironment() { + static bool runOnce = false; + if (runOnce) return; + runOnce = true; + + scoped_ptr env(base::Environment::Create()); + std::string name; + //if (env->GetVar("CHROME_DESKTOP", &name) && !name.empty()) + // return; + + if (!env->GetVar("NW_DESKTOP", &name) || name.empty()) + name = "nw.desktop"; + + env->SetVar("CHROME_DESKTOP", name); +} +#endif + +class NativeWindowClientView : public views::ClientView { + public: + NativeWindowClientView(views::Widget* widget, + views::View* contents_view, + const base::WeakPtr& shell) + : views::ClientView(widget, contents_view), + shell_(shell) { + } + ~NativeWindowClientView() override {} + + bool CanClose() override { + if (shell_) + return shell_->ShouldCloseWindow(); + else + return true; + } + + private: + base::WeakPtr shell_; +}; + +class NativeWindowFrameView : public views::NonClientFrameView { + public: + static const char kViewClassName[]; + + explicit NativeWindowFrameView(NativeWindowAura* window); + ~NativeWindowFrameView() override; + + void Init(views::Widget* frame); + + // views::NonClientFrameView implementation. + gfx::Rect GetBoundsForClientView() const override; + gfx::Rect GetWindowBoundsForClientBounds( + const gfx::Rect& client_bounds) const override; + int NonClientHitTest(const gfx::Point& point) override; + void GetWindowMask(const gfx::Size& size, + gfx::Path* window_mask) override; + void ResetWindowControls() override {} + void UpdateWindowIcon() override {} + void UpdateWindowTitle() override {} + void SizeConstraintsChanged() override {} + + // views::View implementation. + gfx::Size GetPreferredSize() const override; + void Layout() override; + const char* GetClassName() const override; + void OnPaint(gfx::Canvas* canvas) override; + gfx::Size GetMinimumSize() const override; + gfx::Size GetMaximumSize() const override; + + private: + NativeWindowAura* window_; + views::Widget* frame_; + + DISALLOW_COPY_AND_ASSIGN(NativeWindowFrameView); +}; + +const char NativeWindowFrameView::kViewClassName[] = + "content/nw/src/browser/NativeWindowFrameView"; + +NativeWindowFrameView::NativeWindowFrameView(NativeWindowAura* window) + : window_(window), + frame_(NULL) { +} + +NativeWindowFrameView::~NativeWindowFrameView() { +} + +void NativeWindowFrameView::Init(views::Widget* frame) { + frame_ = frame; +} + +gfx::Rect NativeWindowFrameView::GetBoundsForClientView() const { + return bounds(); +} + +gfx::Rect NativeWindowFrameView::GetWindowBoundsForClientBounds( + const gfx::Rect& client_bounds) const { + gfx::Rect window_bounds = client_bounds; + // Enforce minimum size (1, 1) in case that client_bounds is passed with + // empty size. This could occur when the frameless window is being + // initialized. + if (window_bounds.IsEmpty()) { + window_bounds.set_width(1); + window_bounds.set_height(1); + } + return window_bounds; +} + +int NativeWindowFrameView::NonClientHitTest(const gfx::Point& point) { + if (frame_->IsFullscreen()) + return HTCLIENT; + + // Check the frame first, as we allow a small area overlapping the contents + // to be used for resize handles. + bool can_ever_resize = frame_->widget_delegate() ? + frame_->widget_delegate()->CanResize() : + false; + // Don't allow overlapping resize handles when the window is maximized or + // fullscreen, as it can't be resized in those states. + int resize_border = + frame_->IsMaximized() || frame_->IsFullscreen() ? 0 : + kResizeInsideBoundsSize; + int frame_component = GetHTComponentForFrame(point, + resize_border, + resize_border, + kResizeAreaCornerSize, + kResizeAreaCornerSize, + can_ever_resize); + if (frame_component != HTNOWHERE) + return frame_component; + + // Ajust the point if we have a toolbar. + gfx::Point adjusted_point(point); + if (window_->toolbar()) + adjusted_point.set_y(adjusted_point.y() - window_->toolbar()->height()); + + // Check for possible draggable region in the client area for the frameless + // window. + if (window_->draggable_region() && + window_->draggable_region()->contains(adjusted_point.x(), + adjusted_point.y())) + return HTCAPTION; + + int client_component = frame_->client_view()->NonClientHitTest(point); + if (client_component != HTNOWHERE) + return client_component; + + // Caption is a safe default. + return HTCAPTION; +} + +void NativeWindowFrameView::GetWindowMask(const gfx::Size& size, + gfx::Path* window_mask) { + // We got nothing to say about no window mask. +} + +gfx::Size NativeWindowFrameView::GetPreferredSize() const { + gfx::Size pref = frame_->client_view()->GetPreferredSize(); + gfx::Rect bounds(0, 0, pref.width(), pref.height()); + return frame_->non_client_view()->GetWindowBoundsForClientBounds( + bounds).size(); +} + +void NativeWindowFrameView::Layout() { +} + +void NativeWindowFrameView::OnPaint(gfx::Canvas* canvas) { +} + +const char* NativeWindowFrameView::GetClassName() const { + return kViewClassName; +} + +gfx::Size NativeWindowFrameView::GetMinimumSize() const { + return frame_->client_view()->GetMinimumSize(); +} + +gfx::Size NativeWindowFrameView::GetMaximumSize() const { + return frame_->client_view()->GetMaximumSize(); +} + +} // namespace + +NativeWindowAura* NativeWindowAura::GetBrowserViewForNativeWindow( + gfx::NativeWindow window) { + views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window); + return widget ? + reinterpret_cast(widget->GetNativeWindowProperty( + "__BROWSER_VIEW__")) : nullptr; +} + +NativeWindowAura::NativeWindowAura(const base::WeakPtr& shell, + base::DictionaryValue* manifest) + : NativeWindow(shell, manifest), + toolbar_(NULL), + web_view_(NULL), + is_fullscreen_(false), + is_visible_on_all_workspaces_(false), + is_minimized_(false), + is_maximized_(false), + is_focus_(false), + is_blur_(false), + menu_(NULL), + resizable_(true), + minimum_size_(0, 0), + maximum_size_(), + initial_focus_(true), + last_width_(-1), last_height_(-1) { + manifest->GetBoolean("focus", &initial_focus_); + manifest->GetBoolean("fullscreen", &is_fullscreen_); + manifest->GetBoolean("resizable", &resizable_); + manifest->GetBoolean("visible-on-all-workspaces", &is_visible_on_all_workspaces_); + + window_ = new views::Widget; + views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW); + params.delegate = this; + params.remove_standard_frame = !has_frame(); + params.use_system_default_icon = true; + if (content::g_support_transparency && transparent_) + params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; + if (is_fullscreen_) + params.show_state = ui::SHOW_STATE_FULLSCREEN; + params.visible_on_all_workspaces = is_visible_on_all_workspaces_; +#if defined(OS_WIN) + if (has_frame()) + window_->set_frame_type(views::Widget::FRAME_TYPE_FORCE_NATIVE); +#endif + window_->Init(params); + + // WS_CAPTION is needed or the size will be miscalculated on maximizing + // see upstream issue #224924 +#if defined(OS_WIN) + HWND hwnd = views::HWNDForWidget(window_->GetTopLevelWidget()); + int current_style = ::GetWindowLong(hwnd, GWL_STYLE); + ::SetWindowLong(hwnd, GWL_STYLE, current_style | WS_CAPTION); +#endif + + if (!has_frame()) + InstallEasyResizeTargeterOnContainer(); + + views::WidgetFocusManager::GetInstance()->AddFocusChangeListener(this); + + int width, height; + manifest->GetInteger(switches::kmWidth, &width); + manifest->GetInteger(switches::kmHeight, &height); + gfx::Rect window_bounds = + window_->non_client_view()->GetWindowBoundsForClientBounds( + gfx::Rect(width,height)); + last_width_ = width; + last_height_ = height; + window_->AddObserver(this); + + window_->SetSize(window_bounds.size()); + window_->CenterWindow(window_bounds.size()); + + window_->UpdateWindowIcon(); + + OnViewWasResized(); + window_->SetInitialFocus(ui::SHOW_STATE_NORMAL); + + window_->SetNativeWindowProperty("__BROWSER_VIEW__", this); + +#ifdef OS_LINUX + SetDeskopEnvironment(); +#endif +} + +NativeWindowAura::~NativeWindowAura() { +#if defined(OS_WIN) + FOR_EACH_OBSERVER(web_modal::ModalDialogHostObserver, + observer_list_, + OnHostDestroying()); +#endif + views::WidgetFocusManager::GetInstance()->RemoveFocusChangeListener(this); +} + +void NativeWindowAura::Close() { + window_->Close(); +} + +void NativeWindowAura::Move(const gfx::Rect& bounds) { + window_->SetBounds(bounds); +} + +void NativeWindowAura::Focus(bool focus) { + window_->Activate(); +} + +void NativeWindowAura::Show() { + VLOG(1) << "NativeWindowAura::Show(); initial_focus = " << initial_focus_; + if (is_maximized_) + window_->native_widget_private()->ShowWithWindowState(ui::SHOW_STATE_MAXIMIZED); + else if (!initial_focus_) { + window_->set_focus_on_creation(false); + window_->native_widget_private()->ShowWithWindowState(ui::SHOW_STATE_INACTIVE); + } else if (is_fullscreen_) { + window_->native_widget_private()->ShowWithWindowState(ui::SHOW_STATE_FULLSCREEN); + } else + window_->native_widget_private()->Show(); +} + +void NativeWindowAura::Hide() { + window_->Hide(); +} + +void NativeWindowAura::Maximize() { + window_->Maximize(); +} + +void NativeWindowAura::Unmaximize() { + window_->Restore(); +} + +void NativeWindowAura::Minimize() { + window_->Minimize(); +} + +void NativeWindowAura::Restore() { + window_->Restore(); +} + +void NativeWindowAura::SetFullscreen(bool fullscreen) { + is_fullscreen_ = fullscreen; + window_->SetFullscreen(fullscreen); + if (shell()) { + if (fullscreen) + shell()->SendEvent("enter-fullscreen"); + else + shell()->SendEvent("leave-fullscreen"); + } +} + +bool NativeWindowAura::IsFullscreen() { + return is_fullscreen_; +} + +void NativeWindowAura::SetTransparent(bool transparent) { + if (!content::g_support_transparency) + return; +#if defined(OS_WIN) + // Check for Windows Vista or higher, transparency isn't supported in + // anything lower. + if (base::win::GetVersion() < base::win::VERSION_VISTA) { + NOTREACHED() << "The operating system does not support transparency."; + transparent_ = false; + return; + } + + // Check to see if composition is disabled, if so we have to throw an + // error, there's no graceful recovery, yet. TODO: Graceful recovery. + BOOL enabled = FALSE; + HRESULT result = ::DwmIsCompositionEnabled(&enabled); + if (!enabled || !SUCCEEDED(result)) { + NOTREACHED() << "Windows DWM composition is not enabled, transparency is not supported."; + transparent_ = false; + return; + } + + HWND hWnd = views::HWNDForWidget(window_); + + const int marginVal = transparent ? -1 : 0; + MARGINS mgMarInset = { marginVal, marginVal, marginVal, marginVal }; + if (DwmExtendFrameIntoClientArea(hWnd, &mgMarInset) != S_OK) { + NOTREACHED() << "Windows DWM extending to client area failed, transparency is not supported."; + transparent_ = false; + return; + } + + // this is needed, or transparency will fail if it defined on startup + bool change_window_style = false; + const LONG lastExStyle = GetWindowLong(hWnd, GWL_EXSTYLE); + const LONG exStyle = WS_EX_COMPOSITED; + const LONG newExStyle = transparent ? lastExStyle | exStyle : lastExStyle & ~exStyle; + SetWindowLong(hWnd, GWL_EXSTYLE, newExStyle); + change_window_style |= lastExStyle != newExStyle; + + if (change_window_style) { + window_->FrameTypeChanged(); + } + + if (content::g_force_cpu_draw && transparent) { + // Quick FIX where window content is not updated + Minimize(); + Restore(); + } + +#elif defined(USE_X11) && !defined(OS_CHROMEOS) + + static char cachedRes = -1; + if ( cachedRes<0 ) { + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + cachedRes = !command_line.HasSwitch(switches::kDisableGpu) || + !command_line.HasSwitch(views::switches::kEnableTransparentVisuals); + } + + if (cachedRes && transparent) { + LOG(INFO) << "if transparency does not work, try with --enable-transparent-visuals --disable-gpu"; + } + + #endif + + if (toolbar_) { + toolbar_->set_background(transparent ? views::Background::CreateSolidBackground(SK_ColorTRANSPARENT) : + views::Background::CreateStandardPanelBackground()); + toolbar_->SchedulePaint(); + } + + // this is needed to prevent white popup on startup + content::RenderWidgetHostView* rwhv = shell_->web_contents()->GetRenderWidgetHostView(); + if (rwhv) { + if (transparent) + rwhv->SetBackgroundColor(SK_ColorTRANSPARENT); + else + rwhv->SetBackgroundColorToDefault(); + } + + content::RenderViewHostImpl* rvh = static_cast(shell_->web_contents()->GetRenderViewHost()); + if (rvh) { + rvh->SetBackgroundOpaque(!transparent); + } + + transparent_ = transparent; +} + +void NativeWindowAura::SetSize(const gfx::Size& size) { + window_->SetSize(size); +} + +gfx::Size NativeWindowAura::GetSize() { + return window_->GetWindowBoundsInScreen().size(); +} + +void NativeWindowAura::SetMinimumSize(int width, int height) { + minimum_size_.set_width(width); + minimum_size_.set_height(height); +} + +void NativeWindowAura::SetMaximumSize(int width, int height) { + maximum_size_.set_width(width); + maximum_size_.set_height(height); +} + +void NativeWindowAura::SetResizable(bool resizable) { + resizable_ = resizable; + +#if 0 //FIXME + // Show/Hide the maximize button. + DWORD style = ::GetWindowLong(views::HWNDForWidget(window_), GWL_STYLE); + if (resizable) + style |= WS_MAXIMIZEBOX; + else + style &= ~WS_MAXIMIZEBOX; + ::SetWindowLong(views::HWNDForWidget(window_), GWL_STYLE, style); +#endif +} + +void NativeWindowAura::SetShowInTaskbar(bool show) { +#if defined(OS_WIN) + if (show == false && base::win::GetVersion() < base::win::VERSION_VISTA) { + // Change the owner of native window. Only needed on Windows XP. + ::SetWindowLong(views::HWNDForWidget(window_), + GWLP_HWNDPARENT, + (LONG)ui::GetHiddenWindow()); + } + + base::win::ScopedComPtr taskbar; + HRESULT result = taskbar.CreateInstance(CLSID_TaskbarList, NULL, + CLSCTX_INPROC_SERVER); + if (FAILED(result)) { + VLOG(1) << "Failed creating a TaskbarList object: " << result; + return; + } + + result = taskbar->HrInit(); + if (FAILED(result)) { + LOG(ERROR) << "Failed initializing an ITaskbarList interface."; + return; + } + + if (show) + result = taskbar->AddTab(views::HWNDForWidget(window_)); + else + result = taskbar->DeleteTab(views::HWNDForWidget(window_)); + + if (FAILED(result)) { + LOG(ERROR) << "Failed to change the show in taskbar attribute"; + return; + } +#endif +} + +void NativeWindowAura::SetAlwaysOnTop(bool top) { + window_->StackAtTop(); + // SetAlwaysOnTop should be called after StackAtTop because otherwise + // the top-most flag will be removed. + window_->SetAlwaysOnTop(top); +} + +void NativeWindowAura::SetVisibleOnAllWorkspaces(bool all_workspaces) { + is_visible_on_all_workspaces_ = all_workspaces; + window_->SetVisibleOnAllWorkspaces(all_workspaces); +} + +void NativeWindowAura::OnWidgetActivationChanged(views::Widget* widget, bool active) { + if (active) { + if (shell()) + shell()->SendEvent("focus"); + is_focus_ = true; + is_blur_ = false; + }else{ + if (shell()) + shell()->SendEvent("blur"); + is_focus_ = false; + is_blur_ = true; + } +} + +void NativeWindowAura::OnWidgetBoundsChanged(views::Widget* widget, const gfx::Rect& new_bounds) { + int w = new_bounds.width(); + int h = new_bounds.height(); + if (shell() && (w != last_width_ || h != last_height_)) { + base::ListValue args; + args.AppendInteger(w); + args.AppendInteger(h); + shell()->SendEvent("resize", args); + last_width_ = w; + last_height_ = h; + } +} + +void NativeWindowAura::SetPosition(const std::string& position) { + if (position == "center") { + gfx::Rect bounds = window_->GetWindowBoundsInScreen(); + window_->CenterWindow(gfx::Size(bounds.width(), bounds.height())); + } else if (position == "mouse") { +#if defined(OS_WIN) //FIXME + gfx::Rect bounds = window_->GetWindowBoundsInScreen(); + POINT pt; + if (::GetCursorPos(&pt)) { + int x = pt.x - (bounds.width() >> 1); + int y = pt.y - (bounds.height() >> 1); + bounds.set_x(x > 0 ? x : 0); + bounds.set_y(y > 0 ? y : 0); + window_->SetBoundsConstrained(bounds); + } +#endif + } +} + +void NativeWindowAura::SetPosition(const gfx::Point& position) { + gfx::Rect bounds = window_->GetWindowBoundsInScreen(); + window_->SetBounds(gfx::Rect(position, bounds.size())); +} + +gfx::Point NativeWindowAura::GetPosition() { + return window_->GetWindowBoundsInScreen().origin(); +} + +void NativeWindowAura::FlashFrame(int count) { +#if defined(OS_WIN) //FIXME + FLASHWINFO fwi; + fwi.cbSize = sizeof(fwi); + fwi.hwnd = views::HWNDForWidget(window_); + if (count != 0) { + fwi.dwFlags = FLASHW_ALL; + fwi.uCount = count < 0 ? 4 : count; + fwi.dwTimeout = 0; + } + else { + fwi.dwFlags = FLASHW_STOP; + } + FlashWindowEx(&fwi); +#elif defined(OS_LINUX) + window_->FlashFrame(count); +#endif +} + +#if defined(OS_WIN) +HICON createBadgeIcon(const HWND hWnd, const TCHAR *value, const int sizeX, const int sizeY) { + // canvas for the overlay icon + gfx::Canvas canvas(gfx::Size(sizeX, sizeY), 1, false); + + // drawing red circle + SkPaint paint; + paint.setColor(SK_ColorRED); + canvas.DrawCircle(gfx::Point(sizeX / 2, sizeY / 2), sizeX / 2, paint); + + // drawing the text + gfx::PlatformFont *platform_font = gfx::PlatformFont::CreateDefault(); + const int fontSize = sizeY*0.65f; + gfx::Font font(platform_font->GetFontName(), fontSize); + platform_font->Release(); + platform_font = NULL; + const int yMargin = (sizeY - fontSize) / 2; + canvas.DrawStringRectWithFlags(value, gfx::FontList(font), SK_ColorWHITE, gfx::Rect(sizeX, fontSize + yMargin + 1), gfx::Canvas::TEXT_ALIGN_CENTER); + + // return the canvas as windows native icon handle + return IconUtil::CreateHICONFromSkBitmap(canvas.ExtractImageRep().sk_bitmap()); +} +#endif + +void NativeWindowAura::SetBadgeLabel(const std::string& badge) { +#if defined(OS_WIN) + base::win::ScopedComPtr taskbar; + HRESULT result = taskbar.CreateInstance(CLSID_TaskbarList, NULL, + CLSCTX_INPROC_SERVER); + + if (FAILED(result)) { + VLOG(1) << "Failed creating a TaskbarList3 object: " << result; + return; + } + + result = taskbar->HrInit(); + if (FAILED(result)) { + LOG(ERROR) << "Failed initializing an ITaskbarList3 interface."; + return; + } + + HICON icon = NULL; + HWND hWnd = views::HWNDForWidget(window_); + if (badge.size()) + icon = createBadgeIcon(hWnd, base::UTF8ToUTF16(badge).c_str(), 32, 32); + + taskbar->SetOverlayIcon(hWnd, icon, L"Status"); + DestroyIcon(icon); +#elif defined(OS_LINUX) + views::LinuxUI::instance()->SetDownloadCount(atoi(badge.c_str())); +#endif +} + +void NativeWindowAura::SetProgressBar(double progress) { +#if defined(OS_WIN) + base::win::ScopedComPtr taskbar; + HRESULT result = taskbar.CreateInstance(CLSID_TaskbarList, NULL, + CLSCTX_INPROC_SERVER); + + if (FAILED(result)) { + VLOG(1) << "Failed creating a TaskbarList3 object: " << result; + return; + } + + result = taskbar->HrInit(); + if (FAILED(result)) { + LOG(ERROR) << "Failed initializing an ITaskbarList3 interface."; + return; + } + + HWND hWnd = views::HWNDForWidget(window_); + + TBPFLAG tbpFlag = TBPF_NOPROGRESS; + + if (progress > 1) { + tbpFlag = TBPF_INDETERMINATE; + } + else if (progress >= 0) { + tbpFlag = TBPF_NORMAL; + taskbar->SetProgressValue(hWnd, progress * 100, 100); + } + + taskbar->SetProgressState(hWnd, tbpFlag); +#elif defined(OS_LINUX) + views::LinuxUI::instance()->SetProgressFraction(progress); +#endif +} + +void NativeWindowAura::SetKiosk(bool kiosk) { + SetFullscreen(kiosk); +} + +bool NativeWindowAura::IsKiosk() { + return IsFullscreen(); +} + +void NativeWindowAura::SetMenu(nwapi::Menu* menu) { + window_->set_has_menu_bar(true); + menu_ = menu; +#if defined(OS_LINUX) + MenuBarView* menubar = new MenuBarView(); + GetBrowserViewLayout()->set_menu_bar(menubar); + AddChildView(menubar); + menubar->UpdateMenu(menu->model()); + Layout(); + SchedulePaint(); +#endif + // The menu is lazily built. +#if defined(OS_WIN) //FIXME + menu->Rebuild(); + menu->SetWindow(this); + + // menu is nwapi::Menu, menu->menu_ is NativeMenuWin, + ::SetMenu(views::HWNDForWidget(window_), menu->menu_->GetNativeMenu()); + +#endif + menu->UpdateKeys( window_->GetFocusManager() ); +} + +BrowserViewLayout* NativeWindowAura::GetBrowserViewLayout() const { + return static_cast(GetLayoutManager()); +} + +void NativeWindowAura::SetTitle(const std::string& title) { + title_ = title; + window_->UpdateWindowTitle(); +} + +void NativeWindowAura::AddToolbar() { + toolbar_ = new NativeWindowToolbarAura(shell()); + GetBrowserViewLayout()->set_tool_bar(toolbar_); + AddChildViewAt(toolbar_, 0); +} + +void NativeWindowAura::SetToolbarButtonEnabled(TOOLBAR_BUTTON button, + bool enabled) { + if (toolbar_) + toolbar_->SetButtonEnabled(button, enabled); +} + +void NativeWindowAura::SetToolbarUrlEntry(const std::string& url) { + if (toolbar_) + toolbar_->SetUrlEntry(url); +} + +void NativeWindowAura::SetToolbarIsLoading(bool loading) { + if (toolbar_) + toolbar_->SetIsLoading(loading); +} + +views::View* NativeWindowAura::GetContentsView() { + return this; +} + +views::ClientView* NativeWindowAura::CreateClientView(views::Widget* widget) { + return new NativeWindowClientView(widget, GetContentsView(), shell_); +} + +views::NonClientFrameView* NativeWindowAura::CreateNonClientFrameView( + views::Widget* widget) { + if (has_frame()) + return new views::NativeFrameView(GetWidget()); + + NativeWindowFrameView* frame_view = new NativeWindowFrameView(this); + frame_view->Init(window_); + return frame_view; +} + +void NativeWindowAura::OnWidgetMove() { + gfx::Point origin = GetPosition(); + if (shell()) { + base::ListValue args; + args.AppendInteger(origin.x()); + args.AppendInteger(origin.y()); + shell()->SendEvent("move", args); + } +} + +bool NativeWindowAura::CanResize() const { + return resizable_; +} + +bool NativeWindowAura::CanMaximize() const { + return resizable_; +} + +bool NativeWindowAura::CanMinimize() const { + return true; +} + +views::Widget* NativeWindowAura::GetWidget() { + return window_; +} + +const views::Widget* NativeWindowAura::GetWidget() const { + return window_; +} + +base::string16 NativeWindowAura::GetWindowTitle() const { + return base::UTF8ToUTF16(title_); +} + +void NativeWindowAura::DeleteDelegate() { + OnNativeWindowDestory(); +} + +bool NativeWindowAura::ShouldShowWindowTitle() const { + return has_frame(); +} + +bool NativeWindowAura::ShouldHandleOnSize() const { + return true; +} + +void NativeWindowAura::OnNativeFocusChange(gfx::NativeView focused_before, + gfx::NativeView focused_now) { + gfx::NativeView this_window = GetWidget()->GetNativeView(); + if (IsParent(focused_now, this_window) || + IsParent(this_window, focused_now)) + return; + + if (focused_now == this_window) { + if (!is_focus_ && shell()) + shell()->SendEvent("focus"); + is_focus_ = true; + is_blur_ = false; + } else if (focused_before == this_window) { + if (!is_blur_ && shell()) + shell()->SendEvent("blur"); + is_focus_ = false; + is_blur_ = true; + } +} + +gfx::ImageSkia NativeWindowAura::GetWindowAppIcon() { + gfx::Image icon = app_icon(); + if (icon.IsEmpty()) + return gfx::ImageSkia(); + + gfx::ImageSkia icon2 = *icon.ToImageSkia(); +#if defined(OS_WIN) + int size = ::GetSystemMetrics(SM_CXICON); + return gfx::ImageSkiaOperations::CreateResizedImage(icon2, + skia::ImageOperations::RESIZE_BEST, + gfx::Size(size, size)); +#else + return icon2; +#endif +} + +gfx::ImageSkia NativeWindowAura::GetWindowIcon() { + gfx::Image icon = app_icon(); + if (icon.IsEmpty()) + return gfx::ImageSkia(); + + return *icon.ToImageSkia(); +} + +views::View* NativeWindowAura::GetInitiallyFocusedView() { + return web_view_; +} + +void NativeWindowAura::UpdateDraggableRegions( + const std::vector& regions) { + // Draggable region is not supported for non-frameless window. + if (has_frame()) + return; + + SkRegion* draggable_region = new SkRegion; + + // By default, the whole window is non-draggable. We need to explicitly + // include those draggable regions. + for (std::vector::const_iterator iter = + regions.begin(); + iter != regions.end(); ++iter) { + const extensions::DraggableRegion& region = *iter; + draggable_region->op( + region.bounds.x(), + region.bounds.y(), + region.bounds.right(), + region.bounds.bottom(), + region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op); + } + + draggable_region_.reset(draggable_region); + OnViewWasResized(); +} + +void NativeWindowAura::HandleKeyboardEvent( + const content::NativeWebKeyboardEvent& event) { + unhandled_keyboard_event_handler_.HandleKeyboardEvent(event, + GetFocusManager()); + // Any unhandled keyboard/character messages should be defproced. + // This allows stuff like F10, etc to work correctly. + + // DefWindowProc(event.os_event.hwnd, event.os_event.message, + // event.os_event.wParam, event.os_event.lParam); +} + +#if 0 +void NativeWindowAura::Layout() { + DCHECK(web_view_); + if (toolbar_) { + toolbar_->SetBounds(0, 0, width(), 34); + web_view_->SetBounds(0, 34, width(), height() - 34); + } else { + web_view_->SetBounds(0, 0, width(), height()); + } + OnViewWasResized(); +} +#endif + +void NativeWindowAura::ViewHierarchyChanged( + const ViewHierarchyChangedDetails& details) { + if (details.is_add && details.child == this) { + BrowserViewLayout* layout = new BrowserViewLayout(); + SetLayoutManager(layout); + + web_view_ = new views::WebView(NULL); + web_view_->SetWebContents(web_contents()); + layout->set_web_view(web_view_); + AddChildView(web_view_); + } +} + +gfx::Size NativeWindowAura::GetMinimumSize() const { + return minimum_size_; +} + +gfx::Size NativeWindowAura::GetMaximumSize() const { + return maximum_size_; +} + +void NativeWindowAura::OnFocus() { + web_view_->RequestFocus(); +} + +bool NativeWindowAura::InitialFocus() { + return initial_focus_; +} + +bool NativeWindowAura::AcceleratorPressed(const ui::Accelerator& accelerator) { + return true; +} + +bool NativeWindowAura::CanHandleAccelerators() const { + return true; +} + +void NativeWindowAura::SetInitialFocus(bool initial_focus) { + initial_focus_ = initial_focus; +} + +bool NativeWindowAura::ExecuteWindowsCommand(int command_id) { +#if defined(OS_WIN) + // Windows uses the 4 lower order bits of |command_id| for type-specific + // information so we must exclude this when comparing. + static const int sc_mask = 0xFFF0; + + if ((command_id & sc_mask) == SC_MINIMIZE) { + is_minimized_ = true; + if (shell()) + shell()->SendEvent("minimize"); + } else if ((command_id & sc_mask) == SC_RESTORE && is_minimized_) { + is_minimized_ = false; + if (shell()) + shell()->SendEvent("restore"); + } else if ((command_id & sc_mask) == SC_RESTORE && !is_minimized_) { + is_maximized_ = false; + if (shell()) + shell()->SendEvent("unmaximize"); + } +#endif + return false; +} + +void NativeWindowAura::HandleWMStateUpdate() { + if (window_->IsMaximized()) { + if (!is_maximized_ && shell()) + shell()->SendEvent("maximize"); + is_maximized_ = true; + }else if (is_maximized_) { + if (shell()) + shell()->SendEvent("unmaximize"); + is_maximized_ = false; + } + + if (window_->IsMinimized()) { + if (!is_minimized_ && shell()) + shell()->SendEvent("minimize"); + is_minimized_ = true; + }else if (is_minimized_) { + if (shell()) + shell()->SendEvent("restore"); + is_minimized_ = false; + } +} + +bool NativeWindowAura::HandleSize(unsigned int param, const gfx::Size& size) { +#if defined(OS_WIN) + if (param == SIZE_MAXIMIZED) { + is_maximized_ = true; + if (shell()) + shell()->SendEvent("maximize"); + }else if (param == SIZE_RESTORED && is_maximized_) { + is_maximized_ = false; + if (shell()) + shell()->SendEvent("unmaximize"); + } +#endif + return false; +} + +bool NativeWindowAura::ExecuteAppCommand(int command_id) { +#if defined(OS_WIN) + if (menu_) { + menu_->menu_delegate_->ExecuteCommand(command_id, 0); + menu_->menu_->UpdateStates(); + } +#endif + return false; +} + +void NativeWindowAura::SaveWindowPlacement(const gfx::Rect& bounds, + ui::WindowShowState show_state) { + // views::WidgetDelegate::SaveWindowPlacement(bounds, show_state); +} + +void NativeWindowAura::OnViewWasResized() { + // Set the window shape of the RWHV. +#if defined(OS_WIN) + DCHECK(window_); + DCHECK(web_view_); + gfx::Size sz = web_view_->size(); + int height = sz.height(), width = sz.width(); + gfx::Path path; + path.addRect(0, 0, width, height); + SetWindowRgn((HWND)web_contents()->GetNativeView(), + (HRGN)path.CreateNativeRegion(), + 1); + + SkRegion* rgn = new SkRegion; + if (!window_->IsFullscreen()) { + if (draggable_region()) + rgn->op(*draggable_region(), SkRegion::kUnion_Op); + + if (!has_frame() && CanResize() && !window_->IsMaximized()) { + rgn->op(0, 0, width, kResizeInsideBoundsSize, SkRegion::kUnion_Op); + rgn->op(0, 0, kResizeInsideBoundsSize, height, SkRegion::kUnion_Op); + rgn->op(width - kResizeInsideBoundsSize, 0, width, height, + SkRegion::kUnion_Op); + rgn->op(0, height - kResizeInsideBoundsSize, width, height, + SkRegion::kUnion_Op); + } + } +#if 0 //FIXME + if (web_contents()->GetRenderViewHost()->GetView()) + web_contents()->GetRenderViewHost()->GetView()->SetClickthroughRegion(rgn); +#endif +#endif +} + +void NativeWindowAura::InstallEasyResizeTargeterOnContainer() { + aura::Window* window = window_->GetNativeWindow(); + gfx::Insets inset(kResizeInsideBoundsSize, kResizeInsideBoundsSize, + kResizeInsideBoundsSize, kResizeInsideBoundsSize); + // Add the EasyResizeWindowTargeter on the window, not its root window. The + // root window does not have a delegate, which is needed to handle the event + // in Linux. + window->SetEventTargeter(scoped_ptr( + new wm::EasyResizeWindowTargeter(window, inset, inset))); +} + +bool NativeWindowAura::ShouldDescendIntoChildForEventHandling( + gfx::NativeView child, + const gfx::Point& location) { +#if defined(USE_AURA) + if (child->Contains(web_view_->web_contents()->GetNativeView())) { + // App window should claim mouse events that fall within the draggable + // region. + return !draggable_region_.get() || + !draggable_region_->contains(location.x(), location.y()); + } +#endif + + return true; +} + +gfx::NativeView NativeWindowAura::GetHostView() const { + return window_->GetNativeView(); +} + +gfx::Size NativeWindowAura::GetMaximumDialogSize() { + return window_->GetWindowBoundsInScreen().size(); +} + +gfx::Point NativeWindowAura::GetDialogPosition(const gfx::Size& size) { + gfx::Size app_window_size = window_->GetWindowBoundsInScreen().size(); + return gfx::Point(app_window_size.width() / 2 - size.width() / 2, + app_window_size.height() / 2 - size.height() / 2); +} + +void NativeWindowAura::AddObserver(web_modal::ModalDialogHostObserver* observer) { + if (observer && !observer_list_.HasObserver(observer)) + observer_list_.AddObserver(observer); +} + +void NativeWindowAura::RemoveObserver(web_modal::ModalDialogHostObserver* observer) { + observer_list_.RemoveObserver(observer); +} + +} // namespace nw diff --git a/src/browser/native_window_aura.h b/src/browser/native_window_aura.h new file mode 100644 index 0000000000..ea71601df8 --- /dev/null +++ b/src/browser/native_window_aura.h @@ -0,0 +1,217 @@ +// Copyright (c) 2012 Intel Corp +// Copyright (c) 2012 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef CONTENT_NW_SRC_BROWSER_NATIVE_WINDOW_WIN_H_ +#define CONTENT_NW_SRC_BROWSER_NATIVE_WINDOW_WIN_H_ + +#include "content/nw/src/browser/native_window.h" + +#include "third_party/skia/include/core/SkRegion.h" +#if defined(OS_WIN) +#include "ui/base/win/hidden_window.h" +#endif +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/views/focus/widget_focus_manager.h" +#include "ui/views/widget/widget_delegate.h" +#include "ui/views/widget/widget_observer.h" + +#include "ui/base/accelerators/accelerator.h" +#include "ui/base/accelerators/accelerator_manager.h" +#include "ui/views/controls/webview/unhandled_keyboard_event_handler.h" +#include "ui/events/keycodes/keyboard_codes.h" + +namespace views { +class WebView; +} + +namespace nwapi { +class Menu; +} + +namespace nw { + +class BrowserViewLayout; +class NativeWindowToolbarAura; + +class NativeWindowAura : public NativeWindow, + public views::WidgetFocusChangeListener, + public views::WidgetDelegateView, + public views::WidgetObserver { + public: + explicit NativeWindowAura(const base::WeakPtr& shell, + base::DictionaryValue* manifest); + ~NativeWindowAura() final; + static NativeWindowAura* GetBrowserViewForNativeWindow(gfx::NativeWindow window); + + SkRegion* draggable_region() { return draggable_region_.get(); } + NativeWindowToolbarAura* toolbar() { return toolbar_; } + views::Widget* window() { return window_; } + + BrowserViewLayout* GetBrowserViewLayout() const; + + // NativeWindow implementation. + void Close() override; + void Move(const gfx::Rect& pos) override; + void Focus(bool focus) override; + void Show() override; + void Hide() override; + void Maximize() override; + void Unmaximize() override; + void Minimize() override; + void Restore() override; + void SetFullscreen(bool fullscreen) override; + bool IsFullscreen() override; + void SetTransparent(bool transparent) override; + void SetSize(const gfx::Size& size) override; + gfx::Size GetSize() override; + void SetMinimumSize(int width, int height) override; + void SetMaximumSize(int width, int height) override; + void SetResizable(bool resizable) override; + void SetAlwaysOnTop(bool top) override; + void SetShowInTaskbar(bool show = true) override; + void SetVisibleOnAllWorkspaces(bool all_workspaces) override; + void SetPosition(const std::string& position) override; + void SetPosition(const gfx::Point& position) override; + gfx::Point GetPosition() override; + void SetTitle(const std::string& title) override; + void FlashFrame(int count) override; + void SetKiosk(bool kiosk) override; + void SetBadgeLabel(const std::string& badge) override; + void SetProgressBar(double progress) override; + bool IsKiosk() override; + void SetMenu(nwapi::Menu* menu) override; + void SetToolbarButtonEnabled(TOOLBAR_BUTTON button, + bool enabled) override; + void SetToolbarUrlEntry(const std::string& url) override; + void SetToolbarIsLoading(bool loading) override; + void SetInitialFocus(bool initial_focus) override; + bool InitialFocus() override; + + // WidgetDelegate implementation. + void OnWidgetMove() override; + views::View* GetContentsView() override; + views::ClientView* CreateClientView(views::Widget*) override; + views::NonClientFrameView* CreateNonClientFrameView( + views::Widget* widget) override; + bool CanResize() const override; + bool CanMaximize() const override; + bool CanMinimize() const override; + views::Widget* GetWidget() override; + const views::Widget* GetWidget() const override; + base::string16 GetWindowTitle() const override; + void DeleteDelegate() override; + views::View* GetInitiallyFocusedView() override; + gfx::ImageSkia GetWindowAppIcon() override; + gfx::ImageSkia GetWindowIcon() override; + bool ShouldShowWindowTitle() const override; + bool ShouldHandleOnSize() const override; + void HandleWMStateUpdate() override; + + views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_; + + + + // WidgetFocusChangeListener implementation. + void OnNativeFocusChange(gfx::NativeView focused_before, + gfx::NativeView focused_now) override; + + // WidgetObserver implementation + void OnWidgetBoundsChanged(views::Widget* widget, const gfx::Rect& new_bounds) override; + void OnWidgetActivationChanged(views::Widget* widget, + bool active) override; + + bool AcceleratorPressed(const ui::Accelerator& accelerator) override; + + bool CanHandleAccelerators() const override; + + gfx::NativeView GetHostView() const override; + gfx::Point GetDialogPosition(const gfx::Size& size) override; + void AddObserver(web_modal::ModalDialogHostObserver* observer) override; + void RemoveObserver(web_modal::ModalDialogHostObserver* observer) override; + gfx::Size GetMaximumDialogSize() override; + + protected: + // NativeWindow implementation. + void AddToolbar() override; + void UpdateDraggableRegions( + const std::vector& regions) override; + void HandleKeyboardEvent( + const content::NativeWebKeyboardEvent& event) override; + + // views::View implementation. + // virtual void Layout() override; + void ViewHierarchyChanged( + const ViewHierarchyChangedDetails& details) override; + gfx::Size GetMinimumSize() const override; + gfx::Size GetMaximumSize() const override; + void OnFocus() override; + + // views::WidgetDelegate implementation. + bool ExecuteWindowsCommand(int command_id) override; + bool HandleSize(unsigned int param, const gfx::Size& size) override; + bool ExecuteAppCommand(int command_id) override; + void SaveWindowPlacement(const gfx::Rect& bounds, + ui::WindowShowState show_state) override; + bool ShouldDescendIntoChildForEventHandling( + gfx::NativeView child, + const gfx::Point& location) override; + private: + friend class content::Shell; + friend class nwapi::Menu; + + void OnViewWasResized(); + void InstallEasyResizeTargeterOnContainer(); + + NativeWindowToolbarAura* toolbar_; + views::WebView* web_view_; + views::Widget* window_; + bool is_fullscreen_; + bool is_visible_on_all_workspaces_; + + // Flags used to prevent sending extra events. + bool is_minimized_; + bool is_maximized_; + bool is_focus_; + bool is_blur_; + + scoped_ptr draggable_region_; + // The window's menubar. + nwapi::Menu* menu_; + + bool resizable_; + std::string title_; + gfx::Size minimum_size_; + gfx::Size maximum_size_; + + bool initial_focus_; + + int last_width_; + int last_height_; + + bool super_down_; + bool meta_down_; + ObserverList observer_list_; + DISALLOW_COPY_AND_ASSIGN(NativeWindowAura); +}; + +} // namespace nw + +#endif // CONTENT_NW_SRC_BROWSER_NATIVE_WINDOW_WIN_H_ diff --git a/src/browser/native_window_gtk.cc b/src/browser/native_window_gtk.cc index 9e2b2b204e..08e93fa86a 100644 --- a/src/browser/native_window_gtk.cc +++ b/src/browser/native_window_gtk.cc @@ -23,19 +23,28 @@ #include #include "base/values.h" -#include "chrome/browser/ui/gtk/gtk_window_util.h" +#include "base/environment.h" +//#include "chrome/browser/ui/gtk/gtk_window_util.h" +//#include "chrome/browser/ui/gtk/unity_service.h" #include "extensions/common/draggable_region.h" #include "content/nw/src/api/menu/menu.h" #include "content/nw/src/common/shell_switches.h" #include "content/nw/src/nw_shell.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" -#include "content/public/browser/web_contents_view.h" #include "content/public/common/renderer_preferences.h" #include "ui/base/x/x11_util.h" -#include "ui/gfx/gtk_util.h" #include "ui/gfx/rect.h" -#include "ui/gfx/skia_utils_gtk.h" +//#include "ui/gfx/skia_utils_gtk.h" + +namespace ShellIntegrationLinux { +std::string GetDesktopName(base::Environment* env) { + std::string name; + if (env->GetVar("NW_DESKTOP", &name) && !name.empty()) + return name; + return "nw.desktop"; +} +} namespace nw { @@ -48,20 +57,31 @@ const double kGtkCursorBlinkCycleFactor = 2000.0; } // namespace -NativeWindowGtk::NativeWindowGtk(content::Shell* shell, +NativeWindowGtk::NativeWindowGtk(const base::WeakPtr& shell, base::DictionaryValue* manifest) : NativeWindow(shell, manifest), - content_thinks_its_fullscreen_(false) { + state_(GDK_WINDOW_STATE_WITHDRAWN), + content_thinks_its_fullscreen_(false), + frame_cursor_(NULL), + resizable_(true), + last_x_(-1), last_y_(-1), + last_width_(-1), last_height_(-1) +{ window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); + gtk_accel_group = gtk_accel_group_new(); + gtk_window_add_accel_group(window_,gtk_accel_group); + vbox_ = gtk_vbox_new(FALSE, 0); gtk_widget_show(vbox_); gtk_container_add(GTK_CONTAINER(window_), vbox_); +#if 0 //FIXME // Set window icon. gfx::Image icon = app_icon(); if (!icon.IsEmpty()) gtk_window_set_icon(window_, icon.ToGdkPixbuf()); +#endif // Always create toolbar since we need to create a url entry. CreateToolbar(); @@ -73,7 +93,7 @@ NativeWindowGtk::NativeWindowGtk(content::Shell* shell, gtk_box_pack_start(GTK_BOX(vbox_), toolbar_, FALSE, FALSE, 0); gfx::NativeView native_view = - web_contents()->GetView()->GetNativeView(); + web_contents()->GetNativeView(); gtk_widget_show(native_view); gtk_container_add(GTK_CONTAINER(vbox_), native_view); @@ -91,10 +111,17 @@ NativeWindowGtk::NativeWindowGtk(content::Shell* shell, gtk_window_set_default_size(window_, width, height); } + last_width_ = width; + last_height_ = height; + // Hide titlebar when {frame: false} specified. if (!has_frame_) gtk_window_set_decorated(window_, false); + bool initial_focus = true; + manifest->GetBoolean(switches::kmInitialFocus, &initial_focus); + SetInitialFocus(initial_focus); + // Set to desktop bool as_desktop; if (manifest->GetBoolean(switches::kmAsDesktop, &as_desktop) && as_desktop) @@ -117,9 +144,14 @@ NativeWindowGtk::NativeWindowGtk(content::Shell* shell, G_CALLBACK(OnWindowStateThunk), this); g_signal_connect(window_, "delete-event", G_CALLBACK(OnWindowDeleteEventThunk), this); + g_signal_connect(window_, "configure-event", + G_CALLBACK(OnWindowConfigureEventThunk), this); if (!has_frame_) { g_signal_connect(window_, "button-press-event", G_CALLBACK(OnButtonPressThunk), this); + + g_signal_connect(window_, "motion-notify-event", + G_CALLBACK(OnMouseMoveEventThunk), this); } SetWebKitColorStyle(); @@ -130,7 +162,7 @@ NativeWindowGtk::~NativeWindowGtk() { } void NativeWindowGtk::Close() { - if (!shell_->ShouldCloseWindow()) + if (shell_ && !shell_->ShouldCloseWindow()) return; gtk_widget_destroy(GTK_WIDGET(window_)); @@ -189,6 +221,14 @@ bool NativeWindowGtk::IsFullscreen() { return content_thinks_its_fullscreen_; } +bool NativeWindowGtk::IsMaximized() const { + return (state_ & GDK_WINDOW_STATE_MAXIMIZED); +} + +bool NativeWindowGtk::IsMinimized() const { + return (state_ & GDK_WINDOW_STATE_ICONIFIED); +} + void NativeWindowGtk::SetSize(const gfx::Size& size) { gtk_window_util::SetWindowSize(window_, size); } @@ -221,6 +261,7 @@ void NativeWindowGtk::SetMaximumSize(int width, int height) { } void NativeWindowGtk::SetResizable(bool resizable) { + resizable_ = resizable; // Should request widget size after setting unresizable, otherwise the // window will shrink to a very small size. if (resizable == false) { @@ -236,6 +277,10 @@ void NativeWindowGtk::SetAlwaysOnTop(bool top) { gtk_window_set_keep_above(window_, top ? TRUE : FALSE); } +void NativeWindowGtk::SetShowInTaskbar(bool show) { + gtk_window_set_skip_taskbar_hint(window_, show ? FALSE : TRUE); +} + void NativeWindowGtk::SetPosition(const std::string& position) { if (position == "center") gtk_window_set_position(window_, GTK_WIN_POS_CENTER); @@ -260,8 +305,20 @@ void NativeWindowGtk::SetTitle(const std::string& title) { gtk_window_set_title(GTK_WINDOW(window_), title.c_str()); } -void NativeWindowGtk::FlashFrame(bool flash) { - gtk_window_set_urgency_hint(window_, flash); +void NativeWindowGtk::FlashFrame(int count) { + gtk_window_set_urgency_hint(window_, count); +} + +void NativeWindowGtk::SetBadgeLabel(const std::string& badge) { + if (unity::IsRunning()) { + unity::SetDownloadCount(atoi(badge.c_str())); + } +} + +void NativeWindowGtk::SetProgressBar(double progress) { + if (unity::IsRunning()) { + unity::SetProgressFraction(progress); + } } void NativeWindowGtk::SetKiosk(bool kiosk) { @@ -272,11 +329,20 @@ bool NativeWindowGtk::IsKiosk() { return IsFullscreen(); } -void NativeWindowGtk::SetMenu(api::Menu* menu) { +void NativeWindowGtk::SetMenu(nwapi::Menu* menu) { + menu->UpdateKeys(gtk_accel_group); gtk_box_pack_start(GTK_BOX(vbox_), menu->menu_, FALSE, FALSE, 0); gtk_box_reorder_child(GTK_BOX(vbox_), menu->menu_, 0); } +void NativeWindowGtk::SetInitialFocus(bool initial_focus) { + gtk_window_set_focus_on_map(GTK_WINDOW(window_), initial_focus); +} + +bool NativeWindowGtk::InitialFocus() { + return gtk_window_get_focus_on_map(GTK_WINDOW(window_)); +} + void NativeWindowGtk::SetAsDesktop() { gtk_window_set_type_hint(window_, GDK_WINDOW_TYPE_HINT_DESKTOP); GdkScreen* screen = gtk_window_get_screen(window_); @@ -444,19 +510,23 @@ gfx::Rect NativeWindowGtk::GetBounds() { } void NativeWindowGtk::OnBackButtonClicked(GtkWidget* widget) { - shell()->GoBackOrForward(-1); + if (shell()) + shell()->GoBackOrForward(-1); } void NativeWindowGtk::OnForwardButtonClicked(GtkWidget* widget) { - shell()->GoBackOrForward(1); + if (shell()) + shell()->GoBackOrForward(1); } void NativeWindowGtk::OnRefreshStopButtonClicked(GtkWidget* widget) { - shell()->ReloadOrStop(); + if (shell()) + shell()->ReloadOrStop(); } void NativeWindowGtk::OnDevReloadButtonClicked(GtkWidget* widget) { - shell()->Reload(content::Shell::RELOAD_DEV); + if (shell()) + shell()->Reload(content::Shell::RELOAD_DEV); } void NativeWindowGtk::OnURLEntryActivate(GtkWidget* entry) { @@ -464,16 +534,18 @@ void NativeWindowGtk::OnURLEntryActivate(GtkWidget* entry) { GURL url(str); if (!url.has_scheme()) url = GURL(std::string("http://") + std::string(str)); - shell()->LoadURL(GURL(url)); + if (shell()) + shell()->LoadURL(GURL(url)); } void NativeWindowGtk::OnDevtoolsButtonClicked(GtkWidget* entry) { - shell()->ShowDevTools(); + if (shell()) + shell()->ShowDevTools(); } // Callback for when the main window is destroyed. gboolean NativeWindowGtk::OnWindowDestroyed(GtkWidget* window) { - delete shell(); + OnNativeWindowDestory(); return FALSE; } @@ -481,37 +553,47 @@ gboolean NativeWindowGtk::OnWindowDestroyed(GtkWidget* window) { gboolean NativeWindowGtk::OnFocusIn(GtkWidget* window, GdkEventFocus*) { gtk_widget_grab_focus(web_contents()->GetView()->GetContentNativeView()); - shell()->SendEvent("focus"); + if (shell()) + shell()->SendEvent("focus"); return FALSE; } // Window lost focus. gboolean NativeWindowGtk::OnFocusOut(GtkWidget* window, GdkEventFocus*) { - shell()->SendEvent("blur"); + if (shell()) + shell()->SendEvent("blur"); return FALSE; } // Window state has changed. gboolean NativeWindowGtk::OnWindowState(GtkWidget* window, GdkEventWindowState* event) { + state_ = event->new_window_state; + switch (event->changed_mask) { case GDK_WINDOW_STATE_ICONIFIED: - if (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED) - shell()->SendEvent("minimize"); - else - shell()->SendEvent("restore"); + if (shell()) { + if (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED) + shell()->SendEvent("minimize"); + else + shell()->SendEvent("restore"); + } break; case GDK_WINDOW_STATE_MAXIMIZED: - if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) - shell()->SendEvent("maximize"); - else - shell()->SendEvent("unmaximize"); + if (shell()) { + if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) + shell()->SendEvent("maximize"); + else + shell()->SendEvent("unmaximize"); + } break; case GDK_WINDOW_STATE_FULLSCREEN: - if (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) - shell()->SendEvent("enter-fullscreen"); - else - shell()->SendEvent("leave-fullscreen"); + if (shell()) { + if (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) + shell()->SendEvent("enter-fullscreen"); + else + shell()->SendEvent("leave-fullscreen"); + } if (content_thinks_its_fullscreen_ && !(event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN)) { @@ -530,29 +612,145 @@ gboolean NativeWindowGtk::OnWindowState(GtkWidget* window, // Window will be closed. gboolean NativeWindowGtk::OnWindowDeleteEvent(GtkWidget* widget, GdkEvent* event) { - if (!shell()->ShouldCloseWindow()) + if (shell() && !shell()->ShouldCloseWindow()) return TRUE; return FALSE; } +gboolean NativeWindowGtk::OnWindowConfigureEvent(GtkWidget* window, + GdkEvent* event) +{ + int x, y; + int w, h; + + x = event->configure.x; + y = event->configure.y; + if (x != last_x_ || y != last_y_) { + last_x_ = x; + last_y_ = y; + base::ListValue args; + args.AppendInteger(x); + args.AppendInteger(y); + if (shell()) + shell()->SendEvent("move", args); + } + + w = event->configure.width; + h = event->configure.height; + if (w != last_width_ || h != last_height_) { + last_width_ = w; + last_height_ = h; + base::ListValue args; + args.AppendInteger(w); + args.AppendInteger(h); + if (shell()) + shell()->SendEvent("resize", args); + } + return FALSE; +} + +bool NativeWindowGtk::GetWindowEdge(int x, int y, GdkWindowEdge* edge) { + if (has_frame_) + return false; + + if (IsMaximized() || IsFullscreen()) + return false; + + return gtk_window_util::GetWindowEdge(GetBounds().size(), 0, x, y, edge); +} + +gboolean NativeWindowGtk::OnMouseMoveEvent(GtkWidget* widget, + GdkEventMotion* event) { + if (has_frame_) { + // Reset the cursor. + if (frame_cursor_) { + frame_cursor_ = NULL; + gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)), NULL); + } + return FALSE; + } + + if (!resizable_) + return FALSE; + + int win_x, win_y; + GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_)); + gdk_window_get_origin(gdk_window, &win_x, &win_y); + gfx::Point point(static_cast(event->x_root - win_x), + static_cast(event->y_root - win_y)); + + // Update the cursor if we're on the custom frame border. + GdkWindowEdge edge; + bool has_hit_edge = GetWindowEdge(point.x(), point.y(), &edge); + GdkCursorType new_cursor = GDK_LAST_CURSOR; + if (has_hit_edge) + new_cursor = gtk_window_util::GdkWindowEdgeToGdkCursorType(edge); + + GdkCursorType last_cursor = GDK_LAST_CURSOR; + if (frame_cursor_) + last_cursor = frame_cursor_->type; + + if (last_cursor != new_cursor) { + frame_cursor_ = has_hit_edge ? gfx::GetCursor(new_cursor) : NULL; + gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)), + frame_cursor_); + } + return FALSE; +} + // Capture mouse click on window. gboolean NativeWindowGtk::OnButtonPress(GtkWidget* widget, GdkEventButton* event) { - if (!draggable_region_.isEmpty() && - draggable_region_.contains(event->x, event->y)) { - if (event->button == 1 && GDK_BUTTON_PRESS == event->type) { - if (!suppress_window_raise_) + DCHECK(!has_frame_); + // Make the button press coordinate relative to the browser window. + int win_x, win_y; + GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_)); + gdk_window_get_origin(gdk_window, &win_x, &win_y); + + GdkWindowEdge edge; + gfx::Point point(static_cast(event->x_root - win_x), + static_cast(event->y_root - win_y)); + bool has_hit_edge = resizable_ && GetWindowEdge(point.x(), point.y(), &edge); + bool has_hit_titlebar = + !draggable_region_.isEmpty() && draggable_region_.contains(event->x, event->y); + + if (event->button == 1) { + if (GDK_BUTTON_PRESS == event->type) { + // Raise the window after a click on either the titlebar or the border to + // match the behavior of most window managers, unless that behavior has + // been suppressed. + if ((has_hit_titlebar || has_hit_edge) && !suppress_window_raise_) gdk_window_raise(GTK_WIDGET(widget)->window); - return gtk_window_util::HandleTitleBarLeftMousePress( - GTK_WINDOW(widget), GetBounds(), event); - } else if (event->button == 2) { - gdk_window_lower(GTK_WIDGET(widget)->window); - return TRUE; + if (has_hit_edge) { + gtk_window_begin_resize_drag(window_, edge, event->button, + static_cast(event->x_root), + static_cast(event->y_root), + event->time); + return TRUE; + } else if (has_hit_titlebar) { + return gtk_window_util::HandleTitleBarLeftMousePress( + window_, GetBounds(), event); + } + } else if (GDK_2BUTTON_PRESS == event->type) { + if (has_hit_titlebar && resizable_) { + // Maximize/restore on double click. + if (IsMaximized()) { + gtk_window_unmaximize(window_); + //gtk_window_util::UnMaximize(GTK_WINDOW(widget), + // GetBounds(), restored_bounds_); + } else { + gtk_window_maximize(window_); + } + return TRUE; + } } + } else if (event->button == 2) { + if (has_hit_titlebar || has_hit_edge) + gdk_window_lower(gdk_window); + return TRUE; } - return FALSE; } diff --git a/src/browser/native_window_gtk.h b/src/browser/native_window_gtk.h index 70b5819ffb..c0a9111519 100644 --- a/src/browser/native_window_gtk.h +++ b/src/browser/native_window_gtk.h @@ -22,16 +22,17 @@ #define CONTENT_NW_SRC_BROWSER_NATIVE_WINDOW_GTK_H_ #include +#include #include "content/nw/src/browser/native_window.h" #include "third_party/skia/include/core/SkRegion.h" -#include "ui/base/gtk/gtk_signal.h" +#include "chrome/browser/ui/libgtk2ui/gtk2_signal.h" namespace nw { class NativeWindowGtk : public NativeWindow { public: - explicit NativeWindowGtk(content::Shell* shell, + explicit NativeWindowGtk(const base::WeakPtr& shell, base::DictionaryValue* manifest); virtual ~NativeWindowGtk(); @@ -47,20 +48,26 @@ class NativeWindowGtk : public NativeWindow { virtual void Restore() OVERRIDE; virtual void SetFullscreen(bool fullscreen) OVERRIDE; virtual bool IsFullscreen() OVERRIDE; + virtual void SetTransparent(bool transparent) OVERRIDE; virtual void SetSize(const gfx::Size& size) OVERRIDE; virtual gfx::Size GetSize() OVERRIDE; virtual void SetMinimumSize(int width, int height) OVERRIDE; virtual void SetMaximumSize(int width, int height) OVERRIDE; virtual void SetResizable(bool resizable) OVERRIDE; virtual void SetAlwaysOnTop(bool top) OVERRIDE; + virtual void SetShowInTaskbar(bool show = true) OVERRIDE; virtual void SetPosition(const std::string& position) OVERRIDE; virtual void SetPosition(const gfx::Point& position) OVERRIDE; virtual gfx::Point GetPosition() OVERRIDE; virtual void SetTitle(const std::string& title) OVERRIDE; - virtual void FlashFrame(bool flash) OVERRIDE; + virtual void FlashFrame(int count) OVERRIDE; + virtual void SetBadgeLabel(const std::string& badge) OVERRIDE; + virtual void SetProgressBar(double progress) OVERRIDE; virtual void SetKiosk(bool kiosk) OVERRIDE; virtual bool IsKiosk() OVERRIDE; - virtual void SetMenu(api::Menu* menu) OVERRIDE; + virtual void SetMenu(nwapi::Menu* menu) OVERRIDE; + virtual void SetInitialFocus(bool initial_focus) OVERRIDE; + virtual bool InitialFocus() OVERRIDE; virtual void SetToolbarButtonEnabled(TOOLBAR_BUTTON button, bool enabled) OVERRIDE; virtual void SetToolbarUrlEntry(const std::string& url) OVERRIDE; @@ -68,6 +75,8 @@ class NativeWindowGtk : public NativeWindow { GtkWindow* window() const { return window_; } + bool IsMinimized() const; + bool IsMaximized() const; protected: // NativeWindow implementation. virtual void AddToolbar() OVERRIDE; @@ -82,12 +91,20 @@ class NativeWindowGtk : public NativeWindow { // Set WebKit's style from current theme. void SetWebKitColorStyle(); + //Use to bind shortcut + GtkAccelGroup *gtk_accel_group; + // Create toolbar. void CreateToolbar(); // Get the position and size of the current window. gfx::Rect GetBounds(); + // If the point (|x|, |y|) is within the resize border area of the window, + // returns true and sets |edge| to the appropriate GdkWindowEdge value. + // Otherwise, returns false. + bool GetWindowEdge(int x, int y, GdkWindowEdge* edge); + CHROMEGTK_CALLBACK_0(NativeWindowGtk, void, OnBackButtonClicked); CHROMEGTK_CALLBACK_0(NativeWindowGtk, void, OnForwardButtonClicked); CHROMEGTK_CALLBACK_0(NativeWindowGtk, void, OnRefreshStopButtonClicked); @@ -104,6 +121,10 @@ class NativeWindowGtk : public NativeWindow { GdkEvent*); CHROMEGTK_CALLBACK_1(NativeWindowGtk, gboolean, OnButtonPress, GdkEventButton*); + CHROMEGTK_CALLBACK_1(NativeWindowGtk, gboolean, OnMouseMoveEvent, + GdkEventMotion*); + CHROMEGTK_CALLBACK_1(NativeWindowGtk, gboolean, OnWindowConfigureEvent, + GdkEvent*); GtkWindow* window_; GtkWidget* toolbar_; @@ -114,6 +135,7 @@ class NativeWindowGtk : public NativeWindow { GtkToolItem* refresh_stop_button_; GtkToolItem* devtools_button_; GtkToolItem* dev_reload_button_; + GdkWindowState state_; // True if the RVH is in fullscreen mode. The window may not actually be in // fullscreen, however: some WMs don't support fullscreen. @@ -127,6 +149,18 @@ class NativeWindowGtk : public NativeWindow { // bar or window border. This is to work around a compiz bug. bool suppress_window_raise_; + // The current window cursor. We set it to a resize cursor when over the + // custom frame border. We set it to NULL if we want the default cursor. + GdkCursor* frame_cursor_; + + // True if the window should be resizable by the user. + bool resizable_; + + int last_x_; + int last_y_; + int last_width_; + int last_height_; + DISALLOW_COPY_AND_ASSIGN(NativeWindowGtk); }; diff --git a/src/browser/native_window_helper_mac.h b/src/browser/native_window_helper_mac.h index c266fcfe27..a299791c33 100644 --- a/src/browser/native_window_helper_mac.h +++ b/src/browser/native_window_helper_mac.h @@ -30,7 +30,7 @@ namespace nw { class NativeWindow; -NativeWindow* CreateNativeWindowCocoa(content::Shell* shell, +NativeWindow* CreateNativeWindowCocoa(const base::WeakPtr& shell, base::DictionaryValue* manifest); } // namespace nw diff --git a/src/browser/native_window_mac.h b/src/browser/native_window_mac.h index 18bca0fc0a..f91240d7ff 100644 --- a/src/browser/native_window_mac.h +++ b/src/browser/native_window_mac.h @@ -23,52 +23,62 @@ #import +#include "base/mac/scoped_nsobject.h" #include "base/memory/scoped_ptr.h" -#include "base/memory/scoped_nsobject.h" +#include "base/memory/weak_ptr.h" #include "content/nw/src/browser/native_window.h" @class ShellNSWindow; @class ShellToolbarDelegate; +@class NWMenuDelegate; class SkRegion; namespace nw { class NativeWindowCocoa : public NativeWindow { public: - explicit NativeWindowCocoa(content::Shell* shell, + explicit NativeWindowCocoa(const base::WeakPtr& shell, base::DictionaryValue* manifest); virtual ~NativeWindowCocoa(); // NativeWindow implementation. - virtual void Close() OVERRIDE; - virtual void Move(const gfx::Rect& pos) OVERRIDE; - virtual void Focus(bool focus) OVERRIDE; - virtual void Show() OVERRIDE; - virtual void Hide() OVERRIDE; - virtual void Maximize() OVERRIDE; - virtual void Unmaximize() OVERRIDE; - virtual void Minimize() OVERRIDE; - virtual void Restore() OVERRIDE; - virtual void SetFullscreen(bool fullscreen) OVERRIDE; - virtual bool IsFullscreen() OVERRIDE; - virtual void SetSize(const gfx::Size& size) OVERRIDE; - virtual gfx::Size GetSize() OVERRIDE; - virtual void SetMinimumSize(int width, int height) OVERRIDE; - virtual void SetMaximumSize(int width, int height) OVERRIDE; - virtual void SetResizable(bool resizable) OVERRIDE; - virtual void SetAlwaysOnTop(bool top) OVERRIDE; - virtual void SetPosition(const std::string& position) OVERRIDE; - virtual void SetPosition(const gfx::Point& position) OVERRIDE; - virtual gfx::Point GetPosition() OVERRIDE; - virtual void SetTitle(const std::string& title) OVERRIDE; - virtual void FlashFrame(bool flash) OVERRIDE; - virtual void SetKiosk(bool kiosk) OVERRIDE; - virtual bool IsKiosk() OVERRIDE; - virtual void SetMenu(api::Menu* menu) OVERRIDE; + virtual void Close() override; + virtual void Move(const gfx::Rect& pos) override; + virtual void Focus(bool focus) override; + virtual void Show() override; + virtual void Hide() override; + virtual void Maximize() override; + virtual void Unmaximize() override; + virtual void Minimize() override; + virtual void Restore() override; + virtual void SetFullscreen(bool fullscreen) override; + virtual bool IsFullscreen() override; + virtual void SetTransparent(bool transparent) override; + virtual void SetSize(const gfx::Size& size) override; + virtual gfx::Size GetSize() override; + virtual void SetMinimumSize(int width, int height) override; + virtual void SetMaximumSize(int width, int height) override; + virtual void SetResizable(bool resizable) override; + virtual void SetAlwaysOnTop(bool top) override; + virtual void SetShowInTaskbar(bool show = true) override; + virtual void SetVisibleOnAllWorkspaces(bool all_workspaces) override; + virtual void SetPosition(const std::string& position) override; + virtual void SetPosition(const gfx::Point& position) override; + virtual gfx::Point GetPosition() override; + virtual void SetTitle(const std::string& title) override; + virtual void FlashFrame(int count) override; + virtual void SetBadgeLabel(const std::string& badge) override; + virtual void SetProgressBar(double progress) override; + virtual void SetKiosk(bool kiosk) override; + virtual bool IsKiosk() override; + virtual void SetMenu(nwapi::Menu* menu) override; + virtual void ClearMenu() override; virtual void SetToolbarButtonEnabled(TOOLBAR_BUTTON button, - bool enabled) OVERRIDE; - virtual void SetToolbarUrlEntry(const std::string& url) OVERRIDE; - virtual void SetToolbarIsLoading(bool loading) OVERRIDE; + bool enabled) override; + virtual void SetToolbarUrlEntry(const std::string& url) override; + virtual void SetToolbarIsLoading(bool loading) override; + virtual void SetInitialFocus(bool accept_focus) override; + virtual bool InitialFocus() override; // Called to handle a mouse event. void HandleMouseEvent(NSEvent* event); @@ -82,11 +92,11 @@ class NativeWindowCocoa : public NativeWindow { protected: // NativeWindow implementation. - virtual void AddToolbar() OVERRIDE; + virtual void AddToolbar() override; virtual void UpdateDraggableRegions( - const std::vector& regions) OVERRIDE; + const std::vector& regions) override; virtual void HandleKeyboardEvent( - const content::NativeWebKeyboardEvent& event) OVERRIDE; + const content::NativeWebKeyboardEvent& event) override; void SetNonLionFullscreen(bool fullscreen); @@ -103,7 +113,10 @@ class NativeWindowCocoa : public NativeWindow { NSWindow* window_; // Delegate to the toolbar. - scoped_nsobject toolbar_delegate_; + base::scoped_nsobject toolbar_delegate_; + + // Data for transparency + NSColor *opaque_color_; bool is_fullscreen_; bool is_kiosk_; @@ -127,6 +140,9 @@ class NativeWindowCocoa : public NativeWindow { // used in custom drag to compute the window movement. NSPoint last_mouse_location_; + bool initial_focus_; + bool first_show_; + DISALLOW_COPY_AND_ASSIGN(NativeWindowCocoa); }; diff --git a/src/browser/native_window_mac.mm b/src/browser/native_window_mac.mm index 3dc689e7b0..44ca8ac355 100644 --- a/src/browser/native_window_mac.mm +++ b/src/browser/native_window_mac.mm @@ -21,10 +21,12 @@ #include "content/nw/src/browser/native_window_mac.h" #include "base/mac/mac_util.h" -#include "base/sys_string_conversions.h" +#include "base/strings/sys_string_conversions.h" #include "base/values.h" #import "chrome/browser/ui/cocoa/custom_frame_view.h" +#import "ui/base/cocoa/nsview_additions.h" #include "content/nw/src/api/menu/menu.h" +#include "content/nw/src/api/menu/menu_delegate_mac.h" #include "content/nw/src/api/app/app.h" #include "content/nw/src/browser/chrome_event_processing_window.h" #include "content/nw/src/browser/native_window_helper_mac.h" @@ -34,12 +36,18 @@ #include "content/nw/src/nw_package.h" #include "content/nw/src/nw_shell.h" #include "content/public/browser/native_web_keyboard_event.h" +#include "content/browser/renderer_host/render_widget_host_view_mac.h" +#include "content/browser/renderer_host/render_view_host_impl.h" #include "content/public/browser/web_contents.h" -#include "content/public/browser/web_contents_view.h" #include "extensions/common/draggable_region.h" #include "third_party/skia/include/core/SkRegion.h" #import "ui/base/cocoa/underlay_opengl_hosting_window.h" +namespace content { + extern bool g_support_transparency; + extern bool g_force_cpu_draw; +} + @interface NSWindow (NSPrivateApis) - (void)setBottomCornerRounded:(BOOL)rounded; @end @@ -71,14 +79,14 @@ - (void)toggleFullScreen:(id)sender; @interface NativeWindowDelegate : NSObject { @private - content::Shell* shell_; + base::WeakPtr shell_; } -- (id)initWithShell:(content::Shell*)shell; +- (id)initWithShell:(const base::WeakPtr&)shell; @end @implementation NativeWindowDelegate -- (id)initWithShell:(content::Shell*)shell { +- (id)initWithShell:(const base::WeakPtr&)shell { if ((self = [super init])) { shell_ = shell; } @@ -88,7 +96,7 @@ - (id)initWithShell:(content::Shell*)shell { - (BOOL)windowShouldClose:(id)window { // If this window is bound to a js object and is not forced to close, // then send event to renderer to let the user decide. - if (!shell_->ShouldCloseWindow()) + if (shell_ && !shell_->ShouldCloseWindow()) return NO; // Clean ourselves up and do the work after clearing the stack of anything @@ -101,48 +109,83 @@ - (BOOL)windowShouldClose:(id)window { } - (void)windowWillEnterFullScreen:(NSNotification*)notification { - static_cast(shell_->window())-> - set_is_fullscreen(true); - shell_->SendEvent("enter-fullscreen"); + if (shell_) { + static_cast(shell_->window())-> + set_is_fullscreen(true); + shell_->SendEvent("enter-fullscreen"); + } } - (void)windowWillExitFullScreen:(NSNotification*)notification { - static_cast(shell_->window())-> - set_is_fullscreen(false); - shell_->SendEvent("leave-fullscreen"); + if (shell_) { + static_cast(shell_->window())-> + set_is_fullscreen(false); + shell_->SendEvent("leave-fullscreen"); + } } - (void)windowDidBecomeKey:(NSNotification *)notification { - shell_->web_contents()->GetView()->Focus(); - shell_->SendEvent("focus"); + if (shell_) { + shell_->web_contents()->Focus(); + shell_->SendEvent("focus"); + } } - (void)windowDidResignKey:(NSNotification *)notification { - shell_->SendEvent("blur"); + if (shell_) + shell_->SendEvent("blur"); } - (void)windowDidMiniaturize:(NSNotification *)notification{ - shell_->SendEvent("minimize"); + if (shell_) + shell_->SendEvent("minimize"); } - (void)windowDidDeminiaturize:(NSNotification *)notification { - shell_->SendEvent("restore"); + if (shell_) + shell_->SendEvent("restore"); +} + +- (void)windowDidMove:(NSNotification*)notification { + if (shell_) { + gfx::Point origin = + static_cast(shell_->window())->GetPosition(); + base::ListValue args; + args.AppendInteger(origin.x()); + args.AppendInteger(origin.y()); + shell_->SendEvent("move", args); + } +} + +- (void)windowDidResize:(NSNotification*)notification { + if (shell_) { + NSWindow* window = + static_cast(shell_->window())->window(); + NSRect frame = [window frame]; + base::ListValue args; + args.AppendInteger(frame.size.width); + args.AppendInteger(frame.size.height); + shell_->SendEvent("resize", args); + } } - (BOOL)windowShouldZoom:(NSWindow*)window toFrame:(NSRect)newFrame { // Cocoa doen't have concept of maximize/unmaximize, so wee need to emulate // them by calculating size change when zooming. - if (newFrame.size.width < [window frame].size.width || - newFrame.size.height < [window frame].size.height) - shell_->SendEvent("unmaximize"); - else - shell_->SendEvent("maximize"); + if (shell_) { + if (newFrame.size.width < [window frame].size.width || + newFrame.size.height < [window frame].size.height) + shell_->SendEvent("unmaximize"); + else + shell_->SendEvent("maximize"); + } return YES; } - (void)cleanup:(id)window { - delete shell_; + if (shell_) + delete shell_.get(); [self release]; } @@ -195,25 +238,31 @@ - (CGFloat)roundedCornerRadius; @interface ShellNSWindow : ChromeEventProcessingWindow { @private - content::Shell* shell_; + base::WeakPtr shell_; } -- (void)setShell:(content::Shell*)shell; +- (void)setShell:(const base::WeakPtr&)shell; - (void)showDevTools:(id)sender; -- (void)closeAllWindows:(id)sender; +- (void)closeAllWindowsQuit:(id)sender; @end @implementation ShellNSWindow -- (void)setShell:(content::Shell*)shell { +- (void)setShell:(const base::WeakPtr&)shell { shell_ = shell; } - (void)showDevTools:(id)sender { - shell_->ShowDevTools(); + if (shell_) + shell_->ShowDevTools(); } -- (void)closeAllWindows:(id)sender { - api::App::CloseAllWindows(); +- (void)closeAllWindowsQuit:(id)sender { + nwapi::App::CloseAllWindows(false, true); +} + +- (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen +{ + return frameRect; } @end @@ -240,7 +289,11 @@ - (void)drawCustomFrameRect:(NSRect)rect forView:(NSView*)view { [[NSBezierPath bezierPathWithRoundedRect:[view bounds] xRadius:cornerRadius yRadius:cornerRadius] addClip]; - [[NSColor whiteColor] set]; + if ([self isOpaque] || !content::g_support_transparency) + [[NSColor whiteColor] set]; + else + [[NSColor clearColor] set]; + NSRectFill(rect); } @@ -264,19 +317,60 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { @end +@interface NWProgressBar : NSProgressIndicator +@end + +@implementation NWProgressBar + +// override the drawing, so we can give color to the progress bar +- (void)drawRect:(NSRect)dirtyRect { + + [super drawRect:dirtyRect]; + + if(self.style != NSProgressIndicatorBarStyle) + return; + + NSRect sliceRect, remainderRect; + double progressFraction = ([self doubleValue] - [self minValue]) / + ([self maxValue] - [self minValue]); + + NSDivideRect(dirtyRect, &sliceRect, &remainderRect, + NSWidth(dirtyRect) * progressFraction, NSMinXEdge); + + const int kProgressBarCornerRadius = 3; + + if (progressFraction == 0.0) + return; + + NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:sliceRect + xRadius:kProgressBarCornerRadius + yRadius:kProgressBarCornerRadius]; + // blue color with alpha blend 0.5 + [[NSColor colorWithCalibratedRed:0 green:0 blue:1 alpha:0.5] set]; + [path fill]; +} +@end + + namespace nw { NativeWindowCocoa::NativeWindowCocoa( - content::Shell* shell, + const base::WeakPtr& shell, base::DictionaryValue* manifest) : NativeWindow(shell, manifest), is_fullscreen_(false), is_kiosk_(false), attention_request_id_(0), - use_system_drag_(true) { + use_system_drag_(true), + initial_focus_(false), // the initial value is different from other + // platforms since osx will focus the first + // window and we want to distinguish the first + // window opening and Window.open case. See also #497 + first_show_(true) { int width, height; manifest->GetInteger(switches::kmWidth, &width); manifest->GetInteger(switches::kmHeight, &height); + manifest->GetBoolean(switches::kmInitialFocus, &initial_focus_); NSRect main_screen_rect = [[[NSScreen screens] objectAtIndex:0] frame]; NSRect cocoa_bounds = NSMakeRect( @@ -302,9 +396,13 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { defer:NO]; } window_ = shell_window; + opaque_color_ = [window() backgroundColor]; [shell_window setShell:shell]; + [[window() contentView] setWantsLayer:!content::g_force_cpu_draw]; [window() setDelegate:[[NativeWindowDelegate alloc] initWithShell:shell]]; + SetTransparent(transparent_); + // Disable fullscreen button when 'fullscreen' is specified to false. bool fullscreen; if (!(manifest->GetBoolean(switches::kmFullscreen, &fullscreen) && @@ -323,7 +421,7 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { [window() respondsToSelector:@selector(setBottomCornerRounded:)]) [window() setBottomCornerRounded:NO]; - NSView* view = web_contents()->GetView()->GetNativeView(); + NSView* view = web_contents()->GetNativeView(); [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; // By default, the whole frameless window is not draggable. @@ -340,7 +438,7 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { } void NativeWindowCocoa::InstallView() { - NSView* view = web_contents()->GetView()->GetNativeView(); + NSView* view = web_contents()->GetNativeView(); if (has_frame_) { [view setFrame:[[window() contentView] bounds]]; [[window() contentView] addSubview:view]; @@ -365,7 +463,7 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { } void NativeWindowCocoa::UninstallView() { - NSView* view = web_contents()->GetView()->GetNativeView(); + NSView* view = web_contents()->GetNativeView(); [view removeFromSuperview]; } @@ -386,6 +484,8 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { } void NativeWindowCocoa::Focus(bool focus) { + NSApplication *myApp = [NSApplication sharedApplication]; + [myApp activateIgnoringOtherApps:YES]; if (focus && [window() isVisible]) [window() makeKeyAndOrderFront:nil]; else @@ -393,7 +493,26 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { } void NativeWindowCocoa::Show() { - [window() makeKeyAndOrderFront:nil]; + NSApplication *myApp = [NSApplication sharedApplication]; + [myApp activateIgnoringOtherApps:NO]; + + if (first_show_ && initial_focus_) { + [window() makeKeyAndOrderFront:nil]; + // FIXME: the new window through Window.open failed + // the focus-on-load test + } else { + // orderFrontRegardless causes us to become the first responder. The usual + // Chrome assumption is that becoming the first responder = you have focus + // so we use this trick to refuse to become first responder during orderFrontRegardless + + // TODO(roger) + // if (rwhv) + // rwhv->SetTakesFocusOnlyOnMouseDown(true); + [window() orderFrontRegardless]; + // if (rwhv) + // rwhv->SetTakesFocusOnlyOnMouseDown(false); + } + first_show_ = false; } void NativeWindowCocoa::Hide() { @@ -435,6 +554,51 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { return is_fullscreen_; } +//debug function to iterate all the sublayers +void SetTransparent (CALayer* layer, const bool transparent) { + if (layer == NULL) return; + [layer setBackgroundColor:CGColorGetConstantColor(transparent ? kCGColorClear : kCGColorWhite)]; + for (CALayer* l in layer.sublayers) { + SetTransparent(l, transparent); + } +} + +//debug function to iterate all the subviews +void SetTransparent (NSView * view, const bool transparent) { + if (view == NULL) return; + SetTransparent(view.layer, transparent); + for (NSView* v in view.subviews) + SetTransparent(v, transparent); +} + +void NativeWindowCocoa::SetTransparent(bool transparent) { + + if (!content::g_support_transparency) return; + if (!transparent_ && transparent) { + opaque_color_ = [window() backgroundColor]; + } + + [window() setHasShadow:transparent ? NO : YES]; + [window() setOpaque:transparent ? NO : YES]; + [window() setBackgroundColor:transparent ? [NSColor clearColor] : opaque_color_]; + + content::RenderWidgetHostViewMac* rwhv = static_cast(shell_->web_contents()->GetRenderWidgetHostView()); + content::RenderViewHostImpl* rvh = static_cast(shell_->web_contents()->GetRenderViewHost()); + + if (rwhv) { + [rwhv->background_layer_ setBackgroundColor:CGColorGetConstantColor(transparent ? kCGColorClear : kCGColorWhite)]; + } + + if (rvh) { + rvh->SetBackgroundOpaque(!transparent); + } + + //this is for debugging, iterate all the subviews / sublayers + //nw::SetTransparent((NSView*)[window()contentView], transparent); + + transparent_ = transparent; + +} void NativeWindowCocoa::SetNonLionFullscreen(bool fullscreen) { if (fullscreen == is_fullscreen_) return; @@ -521,7 +685,7 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { [window() setStyleMask:window().styleMask | NSResizableWindowMask]; } else { [[window() standardWindowButton:NSWindowZoomButton] setEnabled:NO]; - [window() setStyleMask:window().styleMask ^ NSResizableWindowMask]; + [window() setStyleMask:window().styleMask & ~NSResizableWindowMask]; } } @@ -529,6 +693,32 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { [window() setLevel:(top ? NSFloatingWindowLevel : NSNormalWindowLevel)]; } +void NativeWindowCocoa::SetVisibleOnAllWorkspaces(bool all_workspaces) { + NSUInteger collectionBehavior = [window() collectionBehavior]; + if (all_workspaces) { + collectionBehavior |= NSWindowCollectionBehaviorCanJoinAllSpaces; + } else { + collectionBehavior &= ~NSWindowCollectionBehaviorCanJoinAllSpaces; + } + [window() setCollectionBehavior:collectionBehavior]; +} + +void NativeWindowCocoa::SetShowInTaskbar(bool show) { + ProcessSerialNumber psn = { 0, kCurrentProcess }; + if (!show) { + NSArray* windowList = [[NSArray alloc] init]; + windowList = [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces]; + for (unsigned int i = 0; i < [windowList count]; ++i) { + NSWindow *window = [NSApp windowWithWindowNumber:[[windowList objectAtIndex:i] integerValue]]; + [window setCanHide:NO]; + } + TransformProcessType(&psn, kProcessTransformToUIElementApplication); + } + else { + TransformProcessType(&psn, kProcessTransformToForegroundApplication); + } +} + void NativeWindowCocoa::SetPosition(const std::string& position) { if (position == "center") [window() center]; @@ -550,20 +740,78 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { [window() setTitle:base::SysUTF8ToNSString(title)]; } -void NativeWindowCocoa::FlashFrame(bool flash) { - if (flash) { - attention_request_id_ = [NSApp requestUserAttention:NSInformationalRequest]; +void NativeWindowCocoa::FlashFrame(int count) { + if (count != 0) { + attention_request_id_ = count < 0 ? [NSApp requestUserAttention:NSInformationalRequest] : [NSApp requestUserAttention:NSCriticalRequest]; } else { [NSApp cancelUserAttentionRequest:attention_request_id_]; attention_request_id_ = 0; } } +void NativeWindowCocoa::SetBadgeLabel(const std::string& badge) { + [[NSApp dockTile] setBadgeLabel:base::SysUTF8ToNSString(badge)]; +} + + +void NativeWindowCocoa::SetProgressBar(double progress){ + NSDockTile *dockTile = [NSApp dockTile]; + NWProgressBar *progressIndicator = NULL; + + if (dockTile.contentView == NULL && progress >= 0) { + + // create image view to draw application icon + NSImageView *iv = [[NSImageView alloc] init]; + [iv setImage:[NSApp applicationIconImage]]; + + // set dockTile content view to app icon + [dockTile setContentView:iv]; + + progressIndicator = [[NWProgressBar alloc] + initWithFrame:NSMakeRect(0.0f, 0.0f, dockTile.size.width, 15.)]; + + [progressIndicator setStyle:NSProgressIndicatorBarStyle]; + + [progressIndicator setBezeled:YES]; + [progressIndicator setMinValue:0]; + [progressIndicator setMaxValue:1]; + [progressIndicator setHidden:NO]; + [progressIndicator setUsesThreadedAnimation:false]; + + // add progress indicator to image view + [iv addSubview:progressIndicator]; + } + + progressIndicator = (NWProgressBar*)[dockTile.contentView.subviews objectAtIndex:0]; + + if(progress >= 0) { + [progressIndicator setIndeterminate:progress > 1]; + if(progress > 1) { + // progress Indicator is indeterminate + // [progressIndicator startAnimation:window_]; + [progressIndicator setDoubleValue:1]; + } + else { + //[progressIndicator stopAnimation:window_]; + [progressIndicator setDoubleValue:progress]; + } + } + else { + // progress indicator < 0, destroy it + [[dockTile.contentView.subviews objectAtIndex:0]release]; + [dockTile.contentView release]; + dockTile.contentView = NULL; + } + + [dockTile display]; + +} + void NativeWindowCocoa::SetKiosk(bool kiosk) { if (kiosk) { NSApplicationPresentationOptions options = NSApplicationPresentationHideDock + - NSApplicationPresentationHideMenuBar + + NSApplicationPresentationHideMenuBar + NSApplicationPresentationDisableAppleMenu + NSApplicationPresentationDisableProcessSwitching + NSApplicationPresentationDisableForceQuit + @@ -583,12 +831,26 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { return is_kiosk_; } -void NativeWindowCocoa::SetMenu(api::Menu* menu) { - StandardMenusMac standard_menus(shell_->GetPackage()->GetName()); - [NSApp setMainMenu:menu->menu_]; - standard_menus.BuildAppleMenu(); - standard_menus.BuildEditMenu(); - standard_menus.BuildWindowMenu(); +void NativeWindowCocoa::SetMenu(nwapi::Menu* menu) { + if(menu == nil) { + NSMenu *menu = [[NSMenu alloc] initWithTitle:@""]; + [NSApp setMainMenu:menu]; + } else { + [NSApp setMainMenu:menu->menu_]; + } +} + +void NativeWindowCocoa::ClearMenu() { + NSMenu *menu = [[NSMenu alloc] initWithTitle:@""]; + [NSApp setMainMenu:menu]; +} + +void NativeWindowCocoa::SetInitialFocus(bool accept_focus) { + initial_focus_ = accept_focus; +} + +bool NativeWindowCocoa::InitialFocus() { + return initial_focus_; } void NativeWindowCocoa::HandleMouseEvent(NSEvent* event) { @@ -611,7 +873,7 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { return; // create the toolbar object - scoped_nsobject toolbar( + base::scoped_nsobject toolbar( [[NSToolbar alloc] initWithIdentifier:@"node-webkit toolbar"]); // set initial toolbar properties @@ -638,7 +900,7 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { if (toolbar_delegate_) [toolbar_delegate_ setUrl:base::SysUTF8ToNSString(url)]; } - + void NativeWindowCocoa::SetToolbarIsLoading(bool loading) { if (toolbar_delegate_) [toolbar_delegate_ setIsLoading:loading]; @@ -692,7 +954,7 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { event.type == content::NativeWebKeyboardEvent::Char) return; - + DVLOG(1) << "NativeWindowCocoa::HandleKeyboardEvent - redispatch"; // // The event handling to get this strictly right is a tangle; cheat here a bit @@ -708,7 +970,7 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { void NativeWindowCocoa::UpdateDraggableRegionsForSystemDrag( const std::vector& regions, const extensions::DraggableRegion* draggable_area) { - NSView* web_view = web_contents()->GetView()->GetNativeView(); + NSView* web_view = web_contents()->GetNativeView(); NSInteger web_view_width = NSWidth([web_view bounds]); NSInteger web_view_height = NSHeight([web_view bounds]); @@ -778,7 +1040,7 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { const std::vector& regions) { // We still need one ControlRegionView to cover the whole window such that // mouse events could be captured. - NSView* web_view = web_contents()->GetView()->GetNativeView(); + NSView* web_view = web_contents()->GetNativeView(); gfx::Rect window_bounds( 0, 0, NSWidth([web_view bounds]), NSHeight([web_view bounds])); system_drag_exclude_areas_.clear(); @@ -808,14 +1070,14 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { // All ControlRegionViews should be added as children of the WebContentsView, // because WebContentsView will be removed and re-added when entering and // leaving fullscreen mode. - NSView* webView = web_contents()->GetView()->GetNativeView(); + NSView* webView = web_contents()->GetNativeView(); NSInteger webViewHeight = NSHeight([webView bounds]); // Remove all ControlRegionViews that are added last time. // Note that [webView subviews] returns the view's mutable internal array and // it should be copied to avoid mutating the original array while enumerating // it. - scoped_nsobject subviews([[webView subviews] copy]); + base::scoped_nsobject subviews([[webView subviews] copy]); for (NSView* subview in subviews.get()) if ([subview isKindOfClass:[ControlRegionView class]]) [subview removeFromSuperview]; @@ -826,17 +1088,18 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { system_drag_exclude_areas_.begin(); iter != system_drag_exclude_areas_.end(); ++iter) { - scoped_nsobject controlRegion( + base::scoped_nsobject controlRegion( [[ControlRegionView alloc] initWithShellWindow:this]); [controlRegion setFrame:NSMakeRect(iter->x(), webViewHeight - iter->bottom(), iter->width(), iter->height())]; + [controlRegion setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; [webView addSubview:controlRegion]; } } -NativeWindow* CreateNativeWindowCocoa(content::Shell* shell, +NativeWindow* CreateNativeWindowCocoa(const base::WeakPtr& shell, base::DictionaryValue* manifest) { return new NativeWindowCocoa(shell, manifest); } diff --git a/src/browser/native_window_toolbar_aura.cc b/src/browser/native_window_toolbar_aura.cc new file mode 100644 index 0000000000..cddc19b6f4 --- /dev/null +++ b/src/browser/native_window_toolbar_aura.cc @@ -0,0 +1,254 @@ +// Copyright (c) 2012 Intel Corp +// Copyright (c) 2012 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "content/nw/src/browser/native_window_toolbar_aura.h" + +#include "base/logging.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "content/nw/src/nw_shell.h" +#include "grit/nw_resources.h" +#include "grit/ui_resources.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/views/background.h" +#include "ui/views/controls/button/image_button.h" +#include "ui/views/controls/textfield/textfield.h" +#include "ui/views/layout/box_layout.h" + +namespace nw { + +const int kButtonMargin = 2; + +NativeWindowToolbarAura::NativeWindowToolbarAura(content::Shell* shell) + : shell_(shell) { +} + +NativeWindowToolbarAura::~NativeWindowToolbarAura() { +} + +views::View* NativeWindowToolbarAura::GetContentsView() { + return this; +} + +void NativeWindowToolbarAura::Layout() { + int panel_width = width(); + int x = kButtonMargin; + + // Place three left buttons. + gfx::Size sz = back_button_->GetPreferredSize(); + back_button_->SetBounds(x, (height() - sz.height()) / 2, + sz.width(), sz.height()); + x += sz.width() + kButtonMargin; + + sz = forward_button_->GetPreferredSize(); + forward_button_->SetBounds(x, back_button_->y(), + sz.width(), sz.height()); + x += sz.width() + kButtonMargin; + + sz = stop_or_refresh_button_->GetPreferredSize(); + stop_or_refresh_button_->SetBounds(x, back_button_->y(), + sz.width(), sz.height()); + x += sz.width() + kButtonMargin; + + // And place dev reload button as far as possible. + sz = dev_reload_button_->GetPreferredSize(); + dev_reload_button_->SetBounds(panel_width - sz.width() - kButtonMargin, + back_button_->y(), + sz.width(), + sz.height()); + + sz = devtools_button_->GetPreferredSize(); + devtools_button_->SetBounds(dev_reload_button_->x() - sz.width() - kButtonMargin, + back_button_->y(), + sz.width(), + sz.height()); + + // Stretch url entry. + url_entry_->SetBounds(x, + (height() - 24) / 2, + devtools_button_->x() - kButtonMargin - x, + 24); +} + +void NativeWindowToolbarAura::ViewHierarchyChanged( + const ViewHierarchyChangedDetails& details) { + if (details.is_add && details.child == this) + InitToolbar(); +} + +void NativeWindowToolbarAura::ContentsChanged( + views::Textfield* sender, + const base::string16& new_contents) { +} + +bool NativeWindowToolbarAura::HandleKeyEvent(views::Textfield* sender, + const ui::KeyEvent& key_event) { + if (key_event.key_code() == ui::VKEY_RETURN) { + base::string16 url_string = url_entry_->text(); + if (!url_string.empty()) { + GURL url(url_string); + if (!url.has_scheme()) +#if defined(OS_WIN) + url = GURL(L"http://" + url_string); +#else + url = GURL(base::UTF8ToUTF16("http://") + url_string); +#endif + shell_->LoadURL(url); + } + } + + return false; +} + +void NativeWindowToolbarAura::ButtonPressed(views::Button* sender, + const ui::Event& event) { + if (sender == back_button_) + shell_->GoBackOrForward(-1); + else if (sender == forward_button_) + shell_->GoBackOrForward(1); + else if (sender == stop_or_refresh_button_) + shell_->ReloadOrStop(); + else if (sender == devtools_button_) + shell_->ShowDevTools(); + else if (sender == dev_reload_button_) + shell_->Reload(content::Shell::RELOAD_DEV); + else + NOTREACHED() << "Click on unkown toolbar button."; +} + +void NativeWindowToolbarAura::InitToolbar() { + set_background(views::Background::CreateStandardPanelBackground()); + + views::BoxLayout* layout = new views::BoxLayout( + views::BoxLayout::kHorizontal, 5, 5, 10); + SetLayoutManager(layout); + + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + back_button_ = new views::ImageButton(this); + back_button_->SetImage(views::CustomButton::STATE_NORMAL, + rb.GetNativeImageNamed(IDR_NW_BACK).ToImageSkia()); + back_button_->SetImage(views::CustomButton::STATE_HOVERED, + rb.GetNativeImageNamed(IDR_NW_BACK_H).ToImageSkia()); + back_button_->SetImage(views::CustomButton::STATE_PRESSED, + rb.GetNativeImageNamed(IDR_NW_BACK_P).ToImageSkia()); + back_button_->SetImage(views::CustomButton::STATE_DISABLED, + rb.GetNativeImageNamed(IDR_NW_BACK_D).ToImageSkia()); + back_button_->SetAccessibleName(base::UTF8ToUTF16("Back")); + AddChildView(back_button_); + + forward_button_ = new views::ImageButton(this); + forward_button_->SetImage(views::CustomButton::STATE_NORMAL, + rb.GetNativeImageNamed(IDR_NW_FORWARD).ToImageSkia()); + forward_button_->SetImage(views::CustomButton::STATE_HOVERED, + rb.GetNativeImageNamed(IDR_NW_FORWARD_H).ToImageSkia()); + forward_button_->SetImage(views::CustomButton::STATE_PRESSED, + rb.GetNativeImageNamed(IDR_NW_FORWARD_P).ToImageSkia()); + forward_button_->SetImage(views::CustomButton::STATE_DISABLED, + rb.GetNativeImageNamed(IDR_NW_FORWARD_D).ToImageSkia()); + forward_button_->SetAccessibleName(base::UTF8ToUTF16("Forward")); + AddChildView(forward_button_); + + stop_or_refresh_button_ = new views::ImageButton(this); + SetIsLoading(true); + AddChildView(stop_or_refresh_button_); + + url_entry_ = new views::Textfield(); + url_entry_->set_controller(this); + AddChildView(url_entry_); + + devtools_button_ = new views::ImageButton(this); + devtools_button_->SetImage(views::CustomButton::STATE_NORMAL, + rb.GetNativeImageNamed(IDR_NW_TOOLS).ToImageSkia()); + devtools_button_->SetImage(views::CustomButton::STATE_HOVERED, + rb.GetNativeImageNamed(IDR_NW_TOOLS_H).ToImageSkia()); + devtools_button_->SetImage(views::CustomButton::STATE_PRESSED, + rb.GetNativeImageNamed(IDR_NW_TOOLS_P).ToImageSkia()); + devtools_button_->SetAccessibleName(base::UTF8ToUTF16("Devtools")); + AddChildView(devtools_button_); + + dev_reload_button_ = new views::ImageButton(this); + dev_reload_button_->SetImage(views::CustomButton::STATE_NORMAL, + rb.GetNativeImageNamed(IDR_NW_RELOAD).ToImageSkia()); + dev_reload_button_->SetImage(views::CustomButton::STATE_HOVERED, + rb.GetNativeImageNamed(IDR_NW_RELOAD_H).ToImageSkia()); + dev_reload_button_->SetImage(views::CustomButton::STATE_PRESSED, + rb.GetNativeImageNamed(IDR_NW_RELOAD_P).ToImageSkia()); + dev_reload_button_->SetImage(views::CustomButton::STATE_DISABLED, + rb.GetNativeImageNamed(IDR_NW_RELOAD_D).ToImageSkia()); + dev_reload_button_->SetAccessibleName(base::UTF8ToUTF16("Reload render process")); + AddChildView(dev_reload_button_); +} + +void NativeWindowToolbarAura::SetButtonEnabled( + NativeWindow::TOOLBAR_BUTTON button, + bool enabled) { + switch (button) { + case nw::NativeWindow::BUTTON_BACK: + back_button_->SetEnabled(enabled); + break; + case nw::NativeWindow::BUTTON_FORWARD: + forward_button_->SetEnabled(enabled); + break; + case nw::NativeWindow::BUTTON_REFRESH_OR_STOP: + stop_or_refresh_button_->SetEnabled(enabled); + break; + case nw::NativeWindow::BUTTON_DEVTOOLS: + devtools_button_->SetEnabled(enabled); + break; + case nw::NativeWindow::BUTTON_REFRESH_DEV: + dev_reload_button_->SetEnabled(enabled); + break; + } +} + +void NativeWindowToolbarAura::SetUrlEntry(const std::string& url) { + url_entry_->SetText(base::UTF8ToUTF16(url)); +} + +void NativeWindowToolbarAura::SetIsLoading(bool loading) { + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + + if (loading) { + stop_or_refresh_button_->SetImage(views::CustomButton::STATE_NORMAL, + rb.GetNativeImageNamed(IDR_NW_STOP).ToImageSkia()); + stop_or_refresh_button_->SetImage(views::CustomButton::STATE_HOVERED, + rb.GetNativeImageNamed(IDR_NW_STOP_H).ToImageSkia()); + stop_or_refresh_button_->SetImage(views::CustomButton::STATE_PRESSED, + rb.GetNativeImageNamed(IDR_NW_STOP_P).ToImageSkia()); + stop_or_refresh_button_->SetImage(views::CustomButton::STATE_DISABLED, + rb.GetNativeImageNamed(IDR_NW_STOP_D).ToImageSkia()); + stop_or_refresh_button_->SetAccessibleName(base::UTF8ToUTF16("Stop")); + } else { + stop_or_refresh_button_->SetImage(views::CustomButton::STATE_NORMAL, + rb.GetNativeImageNamed(IDR_NW_RELOAD).ToImageSkia()); + stop_or_refresh_button_->SetImage(views::CustomButton::STATE_HOVERED, + rb.GetNativeImageNamed(IDR_NW_RELOAD_H).ToImageSkia()); + stop_or_refresh_button_->SetImage(views::CustomButton::STATE_PRESSED, + rb.GetNativeImageNamed(IDR_NW_RELOAD_P).ToImageSkia()); + stop_or_refresh_button_->SetImage(views::CustomButton::STATE_DISABLED, + rb.GetNativeImageNamed(IDR_NW_RELOAD_D).ToImageSkia()); + stop_or_refresh_button_->SetAccessibleName(base::UTF8ToUTF16("Reload")); + } + + // Force refresh + stop_or_refresh_button_->SchedulePaint(); +} + +} // namespace nw diff --git a/src/browser/native_window_toolbar_win.h b/src/browser/native_window_toolbar_aura.h similarity index 69% rename from src/browser/native_window_toolbar_win.h rename to src/browser/native_window_toolbar_aura.h index ecd7cd270b..5cec67572d 100644 --- a/src/browser/native_window_toolbar_win.h +++ b/src/browser/native_window_toolbar_aura.h @@ -18,8 +18,8 @@ // ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -#ifndef CONTENT_NW_SRC_BROWSER_NATIVE_WINDOW_TOOLBAR_WIN_H_ -#define CONTENT_NW_SRC_BROWSER_NATIVE_WINDOW_TOOLBAR_WIN_H_ +#ifndef CONTENT_NW_SRC_BROWSER_NATIVE_WINDOW_TOOLBAR_AURA_H_ +#define CONTENT_NW_SRC_BROWSER_NATIVE_WINDOW_TOOLBAR_AURA_H_ #include "base/basictypes.h" #include "content/nw/src/browser/native_window.h" @@ -37,13 +37,13 @@ class Textfield; } namespace nw { - -class NativeWindowToolbarWin : public views::WidgetDelegateView, + +class NativeWindowToolbarAura : public views::WidgetDelegateView, public views::TextfieldController, public views::ButtonListener { public: - explicit NativeWindowToolbarWin(content::Shell* shell); - ~NativeWindowToolbarWin(); + explicit NativeWindowToolbarAura(content::Shell* shell); + ~NativeWindowToolbarAura() override; void SetButtonEnabled(NativeWindow::TOOLBAR_BUTTON button, bool enabled); @@ -52,23 +52,22 @@ class NativeWindowToolbarWin : public views::WidgetDelegateView, protected: // Overridden from WidgetDelegateView: - virtual views::View* GetContentsView() OVERRIDE; + views::View* GetContentsView() override; // Overridden from View: - virtual void Layout() OVERRIDE; - virtual void ViewHierarchyChanged(bool is_add, - views::View* parent, - views::View* child) OVERRIDE; + void Layout() override; + void ViewHierarchyChanged( + const ViewHierarchyChangedDetails& details) override; // Overridden from TextfieldController: - virtual void ContentsChanged(views::Textfield* sender, - const string16& new_contents) OVERRIDE; - virtual bool HandleKeyEvent(views::Textfield* sender, - const ui::KeyEvent& key_event) OVERRIDE; + void ContentsChanged(views::Textfield* sender, + const base::string16& new_contents) override; + bool HandleKeyEvent(views::Textfield* sender, + const ui::KeyEvent& key_event) override; // Overridden from ButtonListener: - virtual void ButtonPressed(views::Button* sender, - const ui::Event& event) OVERRIDE; + void ButtonPressed(views::Button* sender, + const ui::Event& event) override; private: void InitToolbar(); @@ -82,9 +81,9 @@ class NativeWindowToolbarWin : public views::WidgetDelegateView, views::ImageButton* devtools_button_; views::ImageButton* dev_reload_button_; - DISALLOW_COPY_AND_ASSIGN(NativeWindowToolbarWin); + DISALLOW_COPY_AND_ASSIGN(NativeWindowToolbarAura); }; } // namespace nw -#endif // CONTENT_NW_SRC_BROWSER_NATIVE_WINDOW_TOOLBAR_WIN_H_ +#endif // CONTENT_NW_SRC_BROWSER_NATIVE_WINDOW_TOOLBAR_AURA_H_ diff --git a/src/browser/native_window_toolbar_win.cc b/src/browser/native_window_toolbar_win.cc index da5de01058..270f4ef466 100644 --- a/src/browser/native_window_toolbar_win.cc +++ b/src/browser/native_window_toolbar_win.cc @@ -18,11 +18,11 @@ // ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -#include "content/nw/src/browser/native_window_toolbar_win.h" +#include "content/nw/src/browser/native_window_toolbar_aura.h" #include "base/logging.h" -#include "base/string16.h" -#include "base/utf_string_conversions.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" #include "content/nw/src/nw_shell.h" #include "grit/nw_resources.h" #include "grit/ui_resources.h" @@ -36,18 +36,18 @@ namespace nw { const int kButtonMargin = 2; -NativeWindowToolbarWin::NativeWindowToolbarWin(content::Shell* shell) +NativeWindowToolbarAura::NativeWindowToolbarAura(content::Shell* shell) : shell_(shell) { } -NativeWindowToolbarWin::~NativeWindowToolbarWin() { +NativeWindowToolbarAura::~NativeWindowToolbarAura() { } -views::View* NativeWindowToolbarWin::GetContentsView() { +views::View* NativeWindowToolbarAura::GetContentsView() { return this; } -void NativeWindowToolbarWin::Layout() { +void NativeWindowToolbarAura::Layout() { int panel_width = width(); int x = kButtonMargin; @@ -87,22 +87,21 @@ void NativeWindowToolbarWin::Layout() { 24); } -void NativeWindowToolbarWin::ViewHierarchyChanged(bool is_add, - views::View* parent, - views::View* child) { - if (is_add && child == this) +void NativeWindowToolbarAura::ViewHierarchyChanged( + const ViewHierarchyChangedDetails& details) { + if (details.is_add && details.child == this) InitToolbar(); } -void NativeWindowToolbarWin::ContentsChanged( +void NativeWindowToolbarAura::ContentsChanged( views::Textfield* sender, - const string16& new_contents) { + const base::string16& new_contents) { } -bool NativeWindowToolbarWin::HandleKeyEvent(views::Textfield* sender, +bool NativeWindowToolbarAura::HandleKeyEvent(views::Textfield* sender, const ui::KeyEvent& key_event) { if (key_event.key_code() == ui::VKEY_RETURN) { - string16 url_string = url_entry_->text(); + base::string16 url_string = url_entry_->text(); if (!url_string.empty()) { GURL url(url_string); if (!url.has_scheme()) @@ -114,7 +113,7 @@ bool NativeWindowToolbarWin::HandleKeyEvent(views::Textfield* sender, return false; } -void NativeWindowToolbarWin::ButtonPressed(views::Button* sender, +void NativeWindowToolbarAura::ButtonPressed(views::Button* sender, const ui::Event& event) { if (sender == back_button_) shell_->GoBackOrForward(-1); @@ -130,7 +129,7 @@ void NativeWindowToolbarWin::ButtonPressed(views::Button* sender, NOTREACHED() << "Click on unkown toolbar button."; } -void NativeWindowToolbarWin::InitToolbar() { +void NativeWindowToolbarAura::InitToolbar() { set_background(views::Background::CreateStandardPanelBackground()); views::BoxLayout* layout = new views::BoxLayout( @@ -166,8 +165,8 @@ void NativeWindowToolbarWin::InitToolbar() { SetIsLoading(true); AddChildView(stop_or_refresh_button_); - url_entry_ = new views::Textfield(views::Textfield::STYLE_DEFAULT); - url_entry_->SetController(this); + url_entry_ = new views::Textfield(); + url_entry_->set_controller(this); AddChildView(url_entry_); devtools_button_ = new views::ImageButton(this); @@ -193,7 +192,7 @@ void NativeWindowToolbarWin::InitToolbar() { AddChildView(dev_reload_button_); } -void NativeWindowToolbarWin::SetButtonEnabled( +void NativeWindowToolbarAura::SetButtonEnabled( NativeWindow::TOOLBAR_BUTTON button, bool enabled) { switch (button) { @@ -215,11 +214,11 @@ void NativeWindowToolbarWin::SetButtonEnabled( } } -void NativeWindowToolbarWin::SetUrlEntry(const std::string& url) { - url_entry_->SetText(UTF8ToUTF16(url)); +void NativeWindowToolbarAura::SetUrlEntry(const std::string& url) { + url_entry_->SetText(base::UTF8ToUTF16(url)); } -void NativeWindowToolbarWin::SetIsLoading(bool loading) { +void NativeWindowToolbarAura::SetIsLoading(bool loading) { ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); if (loading) { diff --git a/src/browser/native_window_win.cc b/src/browser/native_window_win.cc deleted file mode 100644 index d77476482e..0000000000 --- a/src/browser/native_window_win.cc +++ /dev/null @@ -1,639 +0,0 @@ -// Copyright (c) 2012 Intel Corp -// Copyright (c) 2012 The Chromium Authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co -// pies of the Software, and to permit persons to whom the Software is furnished -// to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in al -// l copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM -// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES -// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH -// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -#include "content/nw/src/browser/native_window_win.h" - -#include "base/utf_string_conversions.h" -#include "base/values.h" -#include "base/win/wrapped_window_proc.h" -#include "chrome/browser/platform_util.h" -#include "content/nw/src/api/menu/menu.h" -#include "content/nw/src/browser/native_window_toolbar_win.h" -#include "content/nw/src/common/shell_switches.h" -#include "content/nw/src/nw_shell.h" -#include "content/public/browser/native_web_keyboard_event.h" -#include "content/public/browser/render_view_host.h" -#include "content/public/browser/render_widget_host_view.h" -#include "content/public/browser/web_contents.h" -#include "content/public/browser/web_contents_view.h" -#include "extensions/common/draggable_region.h" -#include "third_party/skia/include/core/SkPaint.h" -#include "ui/base/hit_test.h" -#include "ui/base/win/hwnd_util.h" -#include "ui/gfx/path.h" -#include "ui/views/controls/webview/webview.h" -#include "ui/views/layout/box_layout.h" -#include "ui/views/views_delegate.h" -#include "ui/views/widget/widget.h" -#include "ui/views/widget/native_widget_win.h" -#include "ui/views/window/native_frame_view.h" - -namespace nw { - -namespace { - -const int kResizeInsideBoundsSize = 5; -const int kResizeAreaCornerSize = 16; - -// Returns true if |possible_parent| is a parent window of |child|. -bool IsParent(gfx::NativeView child, gfx::NativeView possible_parent) { - if (!child) - return false; -#if !defined(USE_AURA) && defined(OS_WIN) - if (::GetWindow(child, GW_OWNER) == possible_parent) - return true; -#endif - gfx::NativeView parent = child; - while ((parent = platform_util::GetParent(parent))) { - if (possible_parent == parent) - return true; - } - - return false; -} - -class NativeWindowClientView : public views::ClientView { - public: - NativeWindowClientView(views::Widget* widget, - views::View* contents_view, - content::Shell* shell) - : views::ClientView(widget, contents_view), - shell_(shell) { - } - virtual ~NativeWindowClientView() {} - - virtual bool CanClose() OVERRIDE { - return shell_->ShouldCloseWindow(); - } - - private: - content::Shell* shell_; -}; - -class NativeWindowFrameView : public views::NonClientFrameView { - public: - static const char kViewClassName[]; - - explicit NativeWindowFrameView(NativeWindowWin* window); - virtual ~NativeWindowFrameView(); - - void Init(views::Widget* frame); - - // views::NonClientFrameView implementation. - virtual gfx::Rect GetBoundsForClientView() const OVERRIDE; - virtual gfx::Rect GetWindowBoundsForClientBounds( - const gfx::Rect& client_bounds) const OVERRIDE; - virtual int NonClientHitTest(const gfx::Point& point) OVERRIDE; - virtual void GetWindowMask(const gfx::Size& size, - gfx::Path* window_mask) OVERRIDE; - virtual void ResetWindowControls() OVERRIDE {} - virtual void UpdateWindowIcon() OVERRIDE {} - virtual void UpdateWindowTitle() OVERRIDE {} - - // views::View implementation. - virtual gfx::Size GetPreferredSize() OVERRIDE; - virtual void Layout() OVERRIDE; - virtual std::string GetClassName() const OVERRIDE; - virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE; - virtual gfx::Size GetMinimumSize() OVERRIDE; - virtual gfx::Size GetMaximumSize() OVERRIDE; - - private: - NativeWindowWin* window_; - views::Widget* frame_; - - DISALLOW_COPY_AND_ASSIGN(NativeWindowFrameView); -}; - -const char NativeWindowFrameView::kViewClassName[] = - "content/nw/src/browser/NativeWindowFrameView"; - -NativeWindowFrameView::NativeWindowFrameView(NativeWindowWin* window) - : window_(window), - frame_(NULL) { -} - -NativeWindowFrameView::~NativeWindowFrameView() { -} - -void NativeWindowFrameView::Init(views::Widget* frame) { - frame_ = frame; -} - -gfx::Rect NativeWindowFrameView::GetBoundsForClientView() const { - return bounds(); -} - -gfx::Rect NativeWindowFrameView::GetWindowBoundsForClientBounds( - const gfx::Rect& client_bounds) const { - gfx::Rect window_bounds = client_bounds; - // Enforce minimum size (1, 1) in case that client_bounds is passed with - // empty size. This could occur when the frameless window is being - // initialized. - if (window_bounds.IsEmpty()) { - window_bounds.set_width(1); - window_bounds.set_height(1); - } - return window_bounds; -} - -int NativeWindowFrameView::NonClientHitTest(const gfx::Point& point) { - if (frame_->IsFullscreen()) - return HTCLIENT; - - // Check the frame first, as we allow a small area overlapping the contents - // to be used for resize handles. - bool can_ever_resize = frame_->widget_delegate() ? - frame_->widget_delegate()->CanResize() : - false; - // Don't allow overlapping resize handles when the window is maximized or - // fullscreen, as it can't be resized in those states. - int resize_border = - frame_->IsMaximized() || frame_->IsFullscreen() ? 0 : - kResizeInsideBoundsSize; - int frame_component = GetHTComponentForFrame(point, - resize_border, - resize_border, - kResizeAreaCornerSize, - kResizeAreaCornerSize, - can_ever_resize); - if (frame_component != HTNOWHERE) - return frame_component; - - // Ajust the point if we have a toolbar. - gfx::Point adjusted_point(point); - if (window_->toolbar()) - adjusted_point.set_y(adjusted_point.y() - window_->toolbar()->height()); - - // Check for possible draggable region in the client area for the frameless - // window. - if (window_->draggable_region() && - window_->draggable_region()->contains(adjusted_point.x(), - adjusted_point.y())) - return HTCAPTION; - - int client_component = frame_->client_view()->NonClientHitTest(point); - if (client_component != HTNOWHERE) - return client_component; - - // Caption is a safe default. - return HTCAPTION; -} - -void NativeWindowFrameView::GetWindowMask(const gfx::Size& size, - gfx::Path* window_mask) { - // We got nothing to say about no window mask. -} - -gfx::Size NativeWindowFrameView::GetPreferredSize() { - gfx::Size pref = frame_->client_view()->GetPreferredSize(); - gfx::Rect bounds(0, 0, pref.width(), pref.height()); - return frame_->non_client_view()->GetWindowBoundsForClientBounds( - bounds).size(); -} - -void NativeWindowFrameView::Layout() { -} - -void NativeWindowFrameView::OnPaint(gfx::Canvas* canvas) { -} - -std::string NativeWindowFrameView::GetClassName() const { - return kViewClassName; -} - -gfx::Size NativeWindowFrameView::GetMinimumSize() { - return frame_->client_view()->GetMinimumSize(); -} - -gfx::Size NativeWindowFrameView::GetMaximumSize() { - return frame_->client_view()->GetMaximumSize(); -} - -} // namespace - -NativeWindowWin::NativeWindowWin(content::Shell* shell, - base::DictionaryValue* manifest) - : NativeWindow(shell, manifest), - web_view_(NULL), - toolbar_(NULL), - is_fullscreen_(false), - is_minimized_(false), - is_focus_(false), - is_blur_(false), - menu_(NULL), - resizable_(true), - minimum_size_(0, 0), - maximum_size_() { - window_ = new views::Widget; - views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW); - params.delegate = this; - params.remove_standard_frame = !has_frame(); - params.use_system_default_icon = true; - window_->Init(params); - - views::WidgetFocusManager::GetInstance()->AddFocusChangeListener(this); - - int width, height; - manifest->GetInteger(switches::kmWidth, &width); - manifest->GetInteger(switches::kmHeight, &height); - gfx::Rect window_bounds = - window_->non_client_view()->GetWindowBoundsForClientBounds( - gfx::Rect(width,height)); - window_->SetSize(window_bounds.size()); - window_->CenterWindow(window_bounds.size()); - - window_->UpdateWindowIcon(); - - OnViewWasResized(); -} - -NativeWindowWin::~NativeWindowWin() { - views::WidgetFocusManager::GetInstance()->RemoveFocusChangeListener(this); -} - -void NativeWindowWin::Close() { - window_->Close(); -} - -void NativeWindowWin::Move(const gfx::Rect& bounds) { - window_->SetBounds(bounds); -} - -void NativeWindowWin::Focus(bool focus) { - window_->Activate(); -} - -void NativeWindowWin::Show() { - window_->Show(); -} - -void NativeWindowWin::Hide() { - window_->Hide(); -} - -void NativeWindowWin::Maximize() { - window_->Maximize(); -} - -void NativeWindowWin::Unmaximize() { - window_->Restore(); -} - -void NativeWindowWin::Minimize() { - window_->Minimize(); -} - -void NativeWindowWin::Restore() { - window_->Restore(); -} - -void NativeWindowWin::SetFullscreen(bool fullscreen) { - is_fullscreen_ = fullscreen; - window_->SetFullscreen(fullscreen); - if (fullscreen) - shell()->SendEvent("enter-fullscreen"); - else - shell()->SendEvent("leave-fullscreen"); -} - -bool NativeWindowWin::IsFullscreen() { - return is_fullscreen_; -} - -void NativeWindowWin::SetSize(const gfx::Size& size) { - window_->SetSize(size); -} - -gfx::Size NativeWindowWin::GetSize() { - return window_->GetWindowBoundsInScreen().size(); -} - -void NativeWindowWin::SetMinimumSize(int width, int height) { - minimum_size_.set_width(width); - minimum_size_.set_height(height); -} - -void NativeWindowWin::SetMaximumSize(int width, int height) { - maximum_size_.set_width(width); - maximum_size_.set_height(height); -} - -void NativeWindowWin::SetResizable(bool resizable) { - resizable_ = resizable; - - // Show/Hide the maximize button. - DWORD style = ::GetWindowLong(window_->GetNativeView(), GWL_STYLE); - if (resizable) - style |= WS_MAXIMIZEBOX; - else - style &= ~WS_MAXIMIZEBOX; - ::SetWindowLong(window_->GetNativeView(), GWL_STYLE, style); -} - -void NativeWindowWin::SetAlwaysOnTop(bool top) { - window_->StackAtTop(); - // SetAlwaysOnTop should be called after StackAtTop because otherwise - // the top-most flag will be removed. - window_->SetAlwaysOnTop(top); -} - -void NativeWindowWin::SetPosition(const std::string& position) { - if (position == "center") { - gfx::Rect bounds = window_->GetWindowBoundsInScreen(); - window_->CenterWindow(gfx::Size(bounds.width(), bounds.height())); - } -} - -void NativeWindowWin::SetPosition(const gfx::Point& position) { - gfx::Rect bounds = window_->GetWindowBoundsInScreen(); - window_->SetBounds(gfx::Rect(position, bounds.size())); -} - -gfx::Point NativeWindowWin::GetPosition() { - return window_->GetWindowBoundsInScreen().origin(); -} - -void NativeWindowWin::FlashFrame(bool flash) { - window_->FlashFrame(flash); -} - -void NativeWindowWin::SetKiosk(bool kiosk) { - SetFullscreen(kiosk); -} - -bool NativeWindowWin::IsKiosk() { - return IsFullscreen(); -} - -void NativeWindowWin::SetMenu(api::Menu* menu) { - window_->set_has_menu_bar(true); - menu_ = menu; - - // The menu is lazily built. - menu->Rebuild(); - - // menu is api::Menu, menu->menu_ is NativeMenuWin, - ::SetMenu(window_->GetNativeWindow(), menu->menu_->GetNativeMenu()); -} - -void NativeWindowWin::SetTitle(const std::string& title) { - title_ = title; - window_->UpdateWindowTitle(); -} - -void NativeWindowWin::AddToolbar() { - toolbar_ = new NativeWindowToolbarWin(shell()); - AddChildViewAt(toolbar_, 0); -} - -void NativeWindowWin::SetToolbarButtonEnabled(TOOLBAR_BUTTON button, - bool enabled) { - if (toolbar_) - toolbar_->SetButtonEnabled(button, enabled); -} - -void NativeWindowWin::SetToolbarUrlEntry(const std::string& url) { - if (toolbar_) - toolbar_->SetUrlEntry(url); -} - -void NativeWindowWin::SetToolbarIsLoading(bool loading) { - if (toolbar_) - toolbar_->SetIsLoading(loading); -} - -views::View* NativeWindowWin::GetContentsView() { - return this; -} - -views::ClientView* NativeWindowWin::CreateClientView(views::Widget* widget) { - return new NativeWindowClientView(widget, GetContentsView(), shell()); -} - -views::NonClientFrameView* NativeWindowWin::CreateNonClientFrameView( - views::Widget* widget) { - if (has_frame()) - return new views::NativeFrameView(GetWidget()); - - NativeWindowFrameView* frame_view = new NativeWindowFrameView(this); - frame_view->Init(window_); - return frame_view; -} - -bool NativeWindowWin::CanResize() const { - return resizable_; -} - -bool NativeWindowWin::CanMaximize() const { - return resizable_; -} - -views::Widget* NativeWindowWin::GetWidget() { - return window_; -} - -const views::Widget* NativeWindowWin::GetWidget() const { - return window_; -} - -string16 NativeWindowWin::GetWindowTitle() const { - return UTF8ToUTF16(title_); -} - -void NativeWindowWin::DeleteDelegate() { - delete shell(); -} - -bool NativeWindowWin::ShouldShowWindowTitle() const { - return has_frame(); -} - -void NativeWindowWin::OnNativeFocusChange(gfx::NativeView focused_before, - gfx::NativeView focused_now) { - gfx::NativeView this_window = GetWidget()->GetNativeView(); - if (IsParent(focused_now, this_window) || - IsParent(this_window, focused_now)) - return; - - if (focused_now == this_window) { - if (!is_focus_) - shell()->SendEvent("focus"); - is_focus_ = true; - is_blur_ = false; - } else if (focused_before == this_window) { - if (!is_blur_) - shell()->SendEvent("blur"); - is_focus_ = false; - is_blur_ = true; - } -} - -gfx::ImageSkia NativeWindowWin::GetWindowAppIcon() { - gfx::Image icon = app_icon(); - if (icon.IsEmpty()) - return gfx::ImageSkia(); - - return *icon.ToImageSkia(); -} - -gfx::ImageSkia NativeWindowWin::GetWindowIcon() { - return GetWindowAppIcon(); -} - -views::View* NativeWindowWin::GetInitiallyFocusedView() { - return web_view_; -} - -void NativeWindowWin::UpdateDraggableRegions( - const std::vector& regions) { - // Draggable region is not supported for non-frameless window. - if (has_frame()) - return; - - SkRegion* draggable_region = new SkRegion; - - // By default, the whole window is non-draggable. We need to explicitly - // include those draggable regions. - for (std::vector::const_iterator iter = - regions.begin(); - iter != regions.end(); ++iter) { - const extensions::DraggableRegion& region = *iter; - draggable_region->op( - region.bounds.x(), - region.bounds.y(), - region.bounds.right(), - region.bounds.bottom(), - region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op); - } - - draggable_region_.reset(draggable_region); - OnViewWasResized(); -} - -void NativeWindowWin::HandleKeyboardEvent( - const content::NativeWebKeyboardEvent& event) { - // Any unhandled keyboard/character messages should be defproced. - // This allows stuff like F10, etc to work correctly. - DefWindowProc(event.os_event.hwnd, event.os_event.message, - event.os_event.wParam, event.os_event.lParam); -} - -void NativeWindowWin::Layout() { - DCHECK(web_view_); - if (toolbar_) { - toolbar_->SetBounds(0, 0, width(), 34); - web_view_->SetBounds(0, 34, width(), height() - 34); - } else { - web_view_->SetBounds(0, 0, width(), height()); - } - OnViewWasResized(); -} - -void NativeWindowWin::ViewHierarchyChanged( - bool is_add, views::View *parent, views::View *child) { - if (is_add && child == this) { - views::BoxLayout* layout = new views::BoxLayout( - views::BoxLayout::kVertical, 0, 0, 0); - SetLayoutManager(layout); - - web_view_ = new views::WebView(NULL); - web_view_->SetWebContents(web_contents()); - AddChildView(web_view_); - } -} - -gfx::Size NativeWindowWin::GetMinimumSize() { - return minimum_size_; -} - -gfx::Size NativeWindowWin::GetMaximumSize() { - return maximum_size_; -} - -void NativeWindowWin::OnFocus() { - web_view_->RequestFocus(); -} - -bool NativeWindowWin::ExecuteWindowsCommand(int command_id) { - // Windows uses the 4 lower order bits of |command_id| for type-specific - // information so we must exclude this when comparing. - static const int sc_mask = 0xFFF0; - - if ((command_id & sc_mask) == SC_MINIMIZE) { - is_minimized_ = true; - shell()->SendEvent("minimize"); - } else if ((command_id & sc_mask) == SC_RESTORE && is_minimized_) { - is_minimized_ = false; - shell()->SendEvent("restore"); - } else if ((command_id & sc_mask) == SC_RESTORE && !is_minimized_) { - shell()->SendEvent("unmaximize"); - } else if ((command_id & sc_mask) == SC_MAXIMIZE) { - shell()->SendEvent("maximize"); - } - - return false; -} - -bool NativeWindowWin::ExecuteAppCommand(int command_id) { - if (menu_) { - menu_->menu_delegate_->ExecuteCommand(command_id); - menu_->menu_->UpdateStates(); - } - - return false; -} - -void NativeWindowWin::SaveWindowPlacement(const gfx::Rect& bounds, - ui::WindowShowState show_state) { - // views::WidgetDelegate::SaveWindowPlacement(bounds, show_state); -} - -void NativeWindowWin::OnViewWasResized() { - // Set the window shape of the RWHV. - DCHECK(window_); - DCHECK(web_view_); - gfx::Size sz = web_view_->size(); - int height = sz.height(), width = sz.width(); - gfx::Path path; - path.addRect(0, 0, width, height); - SetWindowRgn(web_contents()->GetView()->GetNativeView(), - path.CreateNativeRegion(), - 1); - - SkRegion* rgn = new SkRegion; - if (!window_->IsFullscreen() && !window_->IsMaximized()) { - if (draggable_region()) - rgn->op(*draggable_region(), SkRegion::kUnion_Op); - - if (!has_frame() && CanResize()) { - rgn->op(0, 0, width, kResizeInsideBoundsSize, SkRegion::kUnion_Op); - rgn->op(0, 0, kResizeInsideBoundsSize, height, SkRegion::kUnion_Op); - rgn->op(width - kResizeInsideBoundsSize, 0, width, height, - SkRegion::kUnion_Op); - rgn->op(0, height - kResizeInsideBoundsSize, width, height, - SkRegion::kUnion_Op); - } - } - if (web_contents()->GetRenderViewHost()->GetView()) - web_contents()->GetRenderViewHost()->GetView()->SetClickthroughRegion(rgn); -} - -} // namespace nw diff --git a/src/browser/native_window_win.h b/src/browser/native_window_win.h deleted file mode 100644 index 75f7bd2488..0000000000 --- a/src/browser/native_window_win.h +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) 2012 Intel Corp -// Copyright (c) 2012 The Chromium Authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co -// pies of the Software, and to permit persons to whom the Software is furnished -// to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in al -// l copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM -// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES -// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH -// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -#ifndef CONTENT_NW_SRC_BROWSER_NATIVE_WINDOW_WIN_H_ -#define CONTENT_NW_SRC_BROWSER_NATIVE_WINDOW_WIN_H_ - -#include "content/nw/src/browser/native_window.h" - -#include "third_party/skia/include/core/SkRegion.h" -#include "ui/gfx/image/image_skia.h" -#include "ui/gfx/rect.h" -#include "ui/views/focus/widget_focus_manager.h" -#include "ui/views/widget/widget_delegate.h" - -namespace views { -class WebView; -} - -namespace nw { - -class NativeWindowToolbarWin; - -class NativeWindowWin : public NativeWindow, - public views::WidgetFocusChangeListener, - public views::WidgetDelegateView { - public: - explicit NativeWindowWin(content::Shell* shell, - base::DictionaryValue* manifest); - virtual ~NativeWindowWin(); - - SkRegion* draggable_region() { return draggable_region_.get(); } - NativeWindowToolbarWin* toolbar() { return toolbar_; } - views::Widget* window() { return window_; } - - // NativeWindow implementation. - virtual void Close() OVERRIDE; - virtual void Move(const gfx::Rect& pos) OVERRIDE; - virtual void Focus(bool focus) OVERRIDE; - virtual void Show() OVERRIDE; - virtual void Hide() OVERRIDE; - virtual void Maximize() OVERRIDE; - virtual void Unmaximize() OVERRIDE; - virtual void Minimize() OVERRIDE; - virtual void Restore() OVERRIDE; - virtual void SetFullscreen(bool fullscreen) OVERRIDE; - virtual bool IsFullscreen() OVERRIDE; - virtual void SetSize(const gfx::Size& size) OVERRIDE; - virtual gfx::Size GetSize() OVERRIDE; - virtual void SetMinimumSize(int width, int height) OVERRIDE; - virtual void SetMaximumSize(int width, int height) OVERRIDE; - virtual void SetResizable(bool resizable) OVERRIDE; - virtual void SetAlwaysOnTop(bool top) OVERRIDE; - virtual void SetPosition(const std::string& position) OVERRIDE; - virtual void SetPosition(const gfx::Point& position) OVERRIDE; - virtual gfx::Point GetPosition() OVERRIDE; - virtual void SetTitle(const std::string& title) OVERRIDE; - virtual void FlashFrame(bool flash) OVERRIDE; - virtual void SetKiosk(bool kiosk) OVERRIDE; - virtual bool IsKiosk() OVERRIDE; - virtual void SetMenu(api::Menu* menu) OVERRIDE; - virtual void SetToolbarButtonEnabled(TOOLBAR_BUTTON button, - bool enabled) OVERRIDE; - virtual void SetToolbarUrlEntry(const std::string& url) OVERRIDE; - virtual void SetToolbarIsLoading(bool loading) OVERRIDE; - - // WidgetDelegate implementation. - virtual views::View* GetContentsView() OVERRIDE; - virtual views::ClientView* CreateClientView(views::Widget*) OVERRIDE; - virtual views::NonClientFrameView* CreateNonClientFrameView( - views::Widget* widget) OVERRIDE; - virtual bool CanResize() const OVERRIDE; - virtual bool CanMaximize() const OVERRIDE; - virtual views::Widget* GetWidget() OVERRIDE; - virtual const views::Widget* GetWidget() const OVERRIDE; - virtual string16 GetWindowTitle() const OVERRIDE; - virtual void DeleteDelegate() OVERRIDE; - virtual views::View* GetInitiallyFocusedView() OVERRIDE; - virtual gfx::ImageSkia GetWindowAppIcon() OVERRIDE; - virtual gfx::ImageSkia GetWindowIcon() OVERRIDE; - virtual bool ShouldShowWindowTitle() const OVERRIDE; - - // WidgetFocusChangeListener implementation. - virtual void OnNativeFocusChange(gfx::NativeView focused_before, - gfx::NativeView focused_now) OVERRIDE; - - protected: - // NativeWindow implementation. - virtual void AddToolbar() OVERRIDE; - virtual void UpdateDraggableRegions( - const std::vector& regions) OVERRIDE; - virtual void HandleKeyboardEvent( - const content::NativeWebKeyboardEvent& event) OVERRIDE; - - // views::View implementation. - virtual void Layout() OVERRIDE; - virtual void ViewHierarchyChanged( - bool is_add, views::View *parent, views::View *child) OVERRIDE; - virtual gfx::Size GetMinimumSize() OVERRIDE; - virtual gfx::Size GetMaximumSize() OVERRIDE; - virtual void OnFocus() OVERRIDE; - - // views::WidgetDelegate implementation. - virtual bool ExecuteWindowsCommand(int command_id) OVERRIDE; - virtual bool ExecuteAppCommand(int command_id) OVERRIDE; - virtual void SaveWindowPlacement(const gfx::Rect& bounds, - ui::WindowShowState show_state) OVERRIDE; - - private: - void OnViewWasResized(); - - NativeWindowToolbarWin* toolbar_; - views::WebView* web_view_; - views::Widget* window_; - bool is_fullscreen_; - - // Flags used to prevent sending extra events. - bool is_minimized_; - bool is_focus_; - bool is_blur_; - - scoped_ptr draggable_region_; - - // The window's menubar. - api::Menu* menu_; - - bool resizable_; - std::string title_; - gfx::Size minimum_size_; - gfx::Size maximum_size_; - - DISALLOW_COPY_AND_ASSIGN(NativeWindowWin); -}; - -} // namespace nw - -#endif // CONTENT_NW_SRC_BROWSER_NATIVE_WINDOW_WIN_H_ diff --git a/src/browser/net_disk_cache_remover.cc b/src/browser/net_disk_cache_remover.cc new file mode 100644 index 0000000000..c3ee12a64e --- /dev/null +++ b/src/browser/net_disk_cache_remover.cc @@ -0,0 +1,88 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net_disk_cache_remover.h" + +#include "base/bind_helpers.h" +#include "base/synchronization/waitable_event.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/web_contents.h" +#include "net/disk_cache/disk_cache.h" +#include "net/http/http_cache.h" +#include "net/http/http_transaction_factory.h" +#include "net/url_request/url_request_context_getter.h" +#include "net/url_request/url_request_context.h" +#include "net/base/completion_callback.h" + +using content::BrowserThread; +using disk_cache::Backend; +using net::CompletionCallback; +using net::URLRequestContextGetter; + +namespace { +// Everything is called and accessed on the IO thread. + +void Noop(base::WaitableEvent* completion, int rv) { + DCHECK(rv == net::OK); + if (completion) + completion->Signal(); +} + +void CallDoomAllEntries(Backend** backend, base::WaitableEvent* completion, int rv) { + DCHECK(rv == net::OK); + if (backend && *backend) + (*backend)->DoomAllEntries(base::Bind(&Noop, completion)); +} + +void ClearHttpDiskCacheOfContext(URLRequestContextGetter* context_getter, + base::WaitableEvent* completion) { + typedef Backend* BackendPtr; // Make line below easier to understand. + BackendPtr* backend_ptr = new BackendPtr(NULL); + CompletionCallback callback(base::Bind(&CallDoomAllEntries, + base::Owned(backend_ptr), + completion)); + + int rv = context_getter->GetURLRequestContext()-> + http_transaction_factory()->GetCache()->GetBackend(backend_ptr, callback); + + // If not net::ERR_IO_PENDING, then backend pointer is updated but callback + // is not called, so call it explicitly. + if (rv != net::ERR_IO_PENDING) + callback.Run(net::OK); +} + +void ClearHttpDiskCacheOnIoThread( + URLRequestContextGetter* main_context_getter, + URLRequestContextGetter* media_context_getter, + base::WaitableEvent* completion1, + base::WaitableEvent* completion2) { + ClearHttpDiskCacheOfContext(main_context_getter, completion1); + ClearHttpDiskCacheOfContext(media_context_getter, completion2); +} + +} // namespace + +namespace nw { + +void RemoveHttpDiskCache(content::BrowserContext* browser_context, + int renderer_child_id) { + URLRequestContextGetter* main_context_getter = + browser_context->GetRequestContextForRenderProcess(renderer_child_id); + URLRequestContextGetter* media_context_getter = + browser_context->GetMediaRequestContextForRenderProcess( + renderer_child_id); + + base::WaitableEvent comp1(false, false), comp2(false, false); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&ClearHttpDiskCacheOnIoThread, + base::Unretained(main_context_getter), + base::Unretained(media_context_getter), + &comp1, &comp2)); + comp1.Wait(); + comp2.Wait(); +} + +} // namespace nw diff --git a/src/browser/net_disk_cache_remover.h b/src/browser/net_disk_cache_remover.h new file mode 100644 index 0000000000..3568c7cba3 --- /dev/null +++ b/src/browser/net_disk_cache_remover.h @@ -0,0 +1,23 @@ +// 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 NW_BROWSER_NET_DISK_CACHE_REMOVER_H_ +#define NW_BROWSER_NET_DISK_CACHE_REMOVER_H_ + +namespace content { + +class BrowserContext; + +} // namespace content + +namespace nw { + +// Clear all http disk cache for this renderer. This method is asynchronous and +// will noop if a previous call has not finished. +void RemoveHttpDiskCache(content::BrowserContext* browser_context, + int renderer_child_id); + +} // namespace nw + +#endif // NW_BROWSER_NET_DISK_CACHE_REMOVER_H_ diff --git a/src/browser/nw_autofill_client.cc b/src/browser/nw_autofill_client.cc new file mode 100644 index 0000000000..b91a1ec7e5 --- /dev/null +++ b/src/browser/nw_autofill_client.cc @@ -0,0 +1,158 @@ +// 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 "content/nw/src/browser/nw_autofill_client.h" + +#include "base/logging.h" +#include "base/prefs/pref_service.h" +#include "components/autofill/content/browser/content_autofill_driver.h" +#include "components/autofill/content/common/autofill_messages.h" +#include "components/autofill/core/common/autofill_pref_names.h" +#include "content/nw/src/browser/autofill_popup_controller_impl.h" +#include "content/nw/src/browser/nw_form_database_service.h" +#include "content/nw/src/shell_browser_context.h" +#include "content/public/browser/render_view_host.h" +#include "ui/gfx/geometry/rect.h" + +#if defined(OS_ANDROID) +#include "chrome/browser/ui/android/autofill/autofill_logger_android.h" +#endif + +DEFINE_WEB_CONTENTS_USER_DATA_KEY(autofill::NWAutofillClient); + +namespace autofill { + +NWAutofillClient::NWAutofillClient(content::WebContents* web_contents) + : content::WebContentsObserver(web_contents), web_contents_(web_contents) { + DCHECK(web_contents); +#if defined(OS_MACOSX) && !defined(OS_IOS) + RegisterForKeystoneNotifications(); +#endif // defined(OS_MACOSX) && !defined(OS_IOS) +} + +NWAutofillClient::~NWAutofillClient() { + // NOTE: It is too late to clean up the autofill popup; that cleanup process + // requires that the WebContents instance still be valid and it is not at + // this point (in particular, the WebContentsImpl destructor has already + // finished running and we are now in the base class destructor). + DCHECK(!popup_controller_); +#if defined(OS_MACOSX) && !defined(OS_IOS) + UnregisterFromKeystoneNotifications(); +#endif // defined(OS_MACOSX) && !defined(OS_IOS) +} + +void NWAutofillClient::TabActivated() { +} + +PersonalDataManager* NWAutofillClient::GetPersonalDataManager() { + return NULL; +} + +scoped_refptr NWAutofillClient::GetDatabase() { + nw::NwFormDatabaseService* service = + static_cast( + web_contents_->GetBrowserContext())->GetFormDatabaseService(); + return service->get_autofill_webdata_service(); +} + +PrefService* NWAutofillClient::GetPrefs() { + return NULL; +} + +void NWAutofillClient::ShowAutofillSettings() { + NOTIMPLEMENTED(); +} + +void NWAutofillClient::ConfirmSaveCreditCard( + const base::Closure& save_card_callback) { + NOTIMPLEMENTED(); +} + +void NWAutofillClient::ShowRequestAutocompleteDialog( + const FormData& form, + content::RenderFrameHost* render_frame_host, + const ResultCallback& callback) { + NOTIMPLEMENTED(); +} + +void NWAutofillClient::ShowAutofillPopup( + const gfx::RectF& element_bounds, + base::i18n::TextDirection text_direction, + const std::vector& suggestions, + base::WeakPtr delegate) { + // Convert element_bounds to be in screen space. + gfx::Rect client_area = web_contents_->GetContainerBounds(); + gfx::RectF element_bounds_in_screen_space = + element_bounds + client_area.OffsetFromOrigin(); + + // Will delete or reuse the old |popup_controller_|. + popup_controller_ = + AutofillPopupControllerImpl::GetOrCreate(popup_controller_, + delegate, + web_contents(), + web_contents()->GetNativeView(), + element_bounds_in_screen_space, + text_direction); + + popup_controller_->Show(suggestions); +} + +void NWAutofillClient::UpdateAutofillPopupDataListValues( + const std::vector& values, + const std::vector& labels) { + if (popup_controller_.get()) + popup_controller_->UpdateDataListValues(values, labels); +} + +void NWAutofillClient::HideAutofillPopup() { + if (popup_controller_.get()) + popup_controller_->Hide(); +} + +bool NWAutofillClient::IsAutocompleteEnabled() { + return true; +} + +void NWAutofillClient::HideRequestAutocompleteDialog() { + NOTIMPLEMENTED(); +} + +void NWAutofillClient::WebContentsDestroyed() { + HideAutofillPopup(); +} + +void NWAutofillClient::DetectAccountCreationForms( + content::RenderFrameHost* rfh, + const std::vector& forms) { + NOTIMPLEMENTED(); +} + +void NWAutofillClient::DidFillOrPreviewField( + const base::string16& autofilled_value, + const base::string16& profile_full_name) { +} + +bool NWAutofillClient::HasCreditCardScanFeature() { + return false; +} + +void NWAutofillClient::ScanCreditCard(const CreditCardScanCallback& callback) { + NOTIMPLEMENTED(); +} + +void NWAutofillClient::ShowUnmaskPrompt( + const autofill::CreditCard& card, + base::WeakPtr delegate) { + NOTIMPLEMENTED(); +} + +void NWAutofillClient::OnUnmaskVerificationResult(bool success) { + NOTIMPLEMENTED(); +} + +void NWAutofillClient::OnFirstUserGestureObserved() { + //NOTIMPLEMENTED(); +} + +} // namespace autofill diff --git a/src/browser/nw_autofill_client.h b/src/browser/nw_autofill_client.h new file mode 100644 index 0000000000..4a41bc4aa7 --- /dev/null +++ b/src/browser/nw_autofill_client.h @@ -0,0 +1,112 @@ +// 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 NW_BROWSER_UI_AUTOFILL_CHROME_AUTOFILL_CLIENT_H_ +#define NW_BROWSER_UI_AUTOFILL_CHROME_AUTOFILL_CLIENT_H_ + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/i18n/rtl.h" +#include "base/memory/weak_ptr.h" +#include "components/autofill/core/browser/autofill_client.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/browser/web_contents_user_data.h" + +namespace content { +struct FrameNavigateParams; +struct LoadCommittedDetails; +class WebContents; +} + +namespace autofill { + +class AutofillDialogController; +class AutofillKeystoneBridgeWrapper; +class AutofillPopupControllerImpl; +struct FormData; + +// Chrome implementation of AutofillClient. +class NWAutofillClient + : public AutofillClient, + public content::WebContentsUserData, + public content::WebContentsObserver { + public: + ~NWAutofillClient() final; + + // Called when the tab corresponding to |this| instance is activated. + void TabActivated(); + + // AutofillClient: + PersonalDataManager* GetPersonalDataManager() override; + scoped_refptr GetDatabase() override; + PrefService* GetPrefs() override; + void HideRequestAutocompleteDialog() override; + void ShowAutofillSettings() override; + bool HasCreditCardScanFeature() override; + void ScanCreditCard(const CreditCardScanCallback& callback) override; + void ConfirmSaveCreditCard( + const base::Closure& save_card_callback) override; + void ShowRequestAutocompleteDialog( + const FormData& form, + content::RenderFrameHost* render_frame_host, + const ResultCallback& callback) override; + void ShowAutofillPopup( + const gfx::RectF& element_bounds, + base::i18n::TextDirection text_direction, + const std::vector& suggestions, + base::WeakPtr delegate) override; + void UpdateAutofillPopupDataListValues( + const std::vector& values, + const std::vector& labels) override; + void HideAutofillPopup() override; + bool IsAutocompleteEnabled() override; + void DetectAccountCreationForms( + content::RenderFrameHost* rfh, + const std::vector& forms) override; + void DidFillOrPreviewField( + const base::string16& autofilled_value, + const base::string16& profile_full_name) override; + + // content::WebContentsObserver implementation. + void WebContentsDestroyed() override; + void ShowUnmaskPrompt( + const autofill::CreditCard& card, + base::WeakPtr delegate) override; + void OnUnmaskVerificationResult(bool success) override; + void OnFirstUserGestureObserved() override; + + private: +#if defined(OS_MACOSX) && !defined(OS_IOS) + // Creates |bridge_wrapper_|, which is responsible for dealing with Keystone + // notifications. + void RegisterForKeystoneNotifications(); + + // Deletes |bridge_wrapper_|. + void UnregisterFromKeystoneNotifications(); +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + + explicit NWAutofillClient(content::WebContents* web_contents); + friend class content::WebContentsUserData; + + content::WebContents* const web_contents_; + // base::WeakPtr dialog_controller_; + base::WeakPtr popup_controller_; + +#if defined(OS_MACOSX) && !defined(OS_IOS) + // Listens to Keystone notifications and passes relevant ones on to the + // PersonalDataManager. + // + // The class of this member must remain a forward declaration, even in the + // .cc implementation file, since the class is defined in a Mac-only + // implementation file. This means that the pointer cannot be wrapped in a + // scoped_ptr. + // AutofillKeystoneBridgeWrapper* bridge_wrapper_; +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + + DISALLOW_COPY_AND_ASSIGN(NWAutofillClient); +}; + +} // namespace autofill + +#endif // CHROME_BROWSER_UI_AUTOFILL_CHROME_AUTOFILL_CLIENT_H_ diff --git a/src/browser/nw_autofill_client_mac.mm b/src/browser/nw_autofill_client_mac.mm new file mode 100644 index 0000000000..783572495b --- /dev/null +++ b/src/browser/nw_autofill_client_mac.mm @@ -0,0 +1,21 @@ +// 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 "content/nw/src/browser/nw_autofill_client.h" + +#import + +#include "base/logging.h" +#include "base/mac/scoped_nsobject.h" +#include "components/autofill/core/browser/personal_data_manager.h" + +namespace autofill { + +void NWAutofillClient::RegisterForKeystoneNotifications() { +} + +void NWAutofillClient::UnregisterFromKeystoneNotifications() { +} + +} // namespace autofill diff --git a/src/browser/nw_constrained_window_views_client.cc b/src/browser/nw_constrained_window_views_client.cc new file mode 100644 index 0000000000..b332316cdf --- /dev/null +++ b/src/browser/nw_constrained_window_views_client.cc @@ -0,0 +1,139 @@ +// 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 "content/nw/src/browser/nw_constrained_window_views_client.h" + +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "components/constrained_window/constrained_window_views.h" +#include "components/constrained_window/constrained_window_views_client.h" +#include "components/web_modal/web_contents_modal_dialog_host.h" +#include "extensions/browser/guest_view/guest_view_base.h" +#include "ui/aura/window.h" +#include "ui/aura/window_observer.h" +#include "ui/aura/window_property.h" + +DECLARE_WINDOW_PROPERTY_TYPE(web_modal::ModalDialogHost*); + +namespace nw { +namespace { + +// Provides the host environment for web modal dialogs. See +// web_modal::WebContentsModalDialogHost, and ModalDialogHost for more +// details. +class ModalDialogHostImpl : public web_modal::WebContentsModalDialogHost, + public aura::WindowObserver { + public: + // Returns a modal dialog host for |window|. If it doesn't exist it creates + // one and stores it as owned property. + static ModalDialogHost* Get(aura::Window* window); + + private: + explicit ModalDialogHostImpl(aura::Window* host_window) + : host_window_(host_window) { + host_window_->AddObserver(this); + } + ~ModalDialogHostImpl() override {} + + // web_modal::ModalDialogHost: + gfx::NativeView GetHostView() const override { + return host_window_; + } + gfx::Point GetDialogPosition(const gfx::Size& size) override { + gfx::Size app_window_size = host_window_->GetBoundsInScreen().size(); + return gfx::Point(app_window_size.width() / 2 - size.width() / 2, + app_window_size.height() / 2 - size.height() / 2); + } + void AddObserver(web_modal::ModalDialogHostObserver* observer) override { + observer_list_.AddObserver(observer); + } + void RemoveObserver(web_modal::ModalDialogHostObserver* observer) override { + observer_list_.RemoveObserver(observer); + } + + // web_modal::WebContensModalDialogHost: + gfx::Size GetMaximumDialogSize() override { + return host_window_->bounds().size(); + } + + // aura::WindowObserver: + void OnWindowDestroying(aura::Window* window) override { + if (window != host_window_) + return; + host_window_->RemoveObserver(this); + FOR_EACH_OBSERVER(web_modal::ModalDialogHostObserver, + observer_list_, + OnHostDestroying()); + } + void OnWindowBoundsChanged(aura::Window* window, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) override { + if (window != host_window_) + return; + FOR_EACH_OBSERVER(web_modal::ModalDialogHostObserver, + observer_list_, + OnPositionRequiresUpdate()); + } + + aura::Window* host_window_; + ObserverList observer_list_; + + DISALLOW_COPY_AND_ASSIGN(ModalDialogHostImpl); +}; + +// A window property key to store the modal dialog host for +// dialogs created with the window as its parent. +DEFINE_OWNED_WINDOW_PROPERTY_KEY(web_modal::ModalDialogHost, + kModalDialogHostKey, + nullptr); + +// static +web_modal::ModalDialogHost* ModalDialogHostImpl::Get( + aura::Window* window) { + web_modal::ModalDialogHost* host = window->GetProperty(kModalDialogHostKey); + if (!host) { + host = new ModalDialogHostImpl(window); + window->SetProperty(kModalDialogHostKey, host); + } + return host; +} + +class NWConstrainedWindowViewsClient + : public constrained_window::ConstrainedWindowViewsClient { + public: + NWConstrainedWindowViewsClient() {} + ~NWConstrainedWindowViewsClient() override {} + + private: + // ConstrainedWindowViewsClient: + content::WebContents* GetEmbedderWebContents( + content::WebContents* initiator_web_contents) override { + extensions::GuestViewBase* guest_view = + extensions::GuestViewBase::FromWebContents(initiator_web_contents); + return guest_view && guest_view->embedder_web_contents() ? + guest_view->embedder_web_contents() : initiator_web_contents; + } + web_modal::ModalDialogHost* GetModalDialogHost( + gfx::NativeWindow parent) override { + return ModalDialogHostImpl::Get(parent); + } + gfx::NativeView GetDialogHostView(gfx::NativeWindow parent) override { + return parent; + } + + DISALLOW_COPY_AND_ASSIGN(NWConstrainedWindowViewsClient); +}; + +} // namespace + +void InstallConstrainedWindowViewsClient() { + constrained_window::SetConstrainedWindowViewsClient( + make_scoped_ptr(new NWConstrainedWindowViewsClient)); +} + +void UninstallConstrainedWindowViewsClient() { + constrained_window::SetConstrainedWindowViewsClient(nullptr); +} + +} // namespace athena diff --git a/src/browser/nw_constrained_window_views_client.h b/src/browser/nw_constrained_window_views_client.h new file mode 100644 index 0000000000..3c7d37ff01 --- /dev/null +++ b/src/browser/nw_constrained_window_views_client.h @@ -0,0 +1,15 @@ +// 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 NW_CONSTRAINED_WINDOW_VIEWS_CLIENT_H_ +#define NW_CONSTRAINED_WINDOW_VIEWS_CLIENT_H_ + +namespace nw { + +void InstallConstrainedWindowViewsClient(); +void UninstallConstrainedWindowViewsClient(); + +} + +#endif diff --git a/src/browser/nw_form_database_service.cc b/src/browser/nw_form_database_service.cc new file mode 100644 index 0000000000..52d599909c --- /dev/null +++ b/src/browser/nw_form_database_service.cc @@ -0,0 +1,127 @@ +// 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 "content/nw/src/browser/nw_form_database_service.h" +#include "base/logging.h" +#include "base/synchronization/waitable_event.h" +#include "components/autofill/core/browser/webdata/autofill_table.h" +#include "components/webdata/common/webdata_constants.h" +#include "content/public/browser/browser_thread.h" +//#include "ui/base/l10n/l10n_util.h" + +using base::WaitableEvent; +using content::BrowserThread; + +namespace { + +// Callback to handle database error. It seems chrome uses this to +// display an error dialog box only. +void DatabaseErrorCallback(sql::InitStatus status) { + LOG(WARNING) << "initializing autocomplete database failed"; +} + +} // namespace + +namespace nw { + +NwFormDatabaseService::NwFormDatabaseService(const base::FilePath path) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + web_database_ = new WebDatabaseService(path.Append(kWebDataFilename), + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI), + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB)); + web_database_->AddTable( + scoped_ptr(new autofill::AutofillTable( + ""))); + web_database_->LoadDatabase(); + + autofill_data_ = new autofill::AutofillWebDataService( + web_database_, + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI), + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB), + base::Bind(&DatabaseErrorCallback)); + autofill_data_->Init(); +} + +NwFormDatabaseService::~NwFormDatabaseService() { + Shutdown(); +} + +void NwFormDatabaseService::Shutdown() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(result_map_.empty()); + // TODO(sgurun) we don't run into this logic right now, + // but if we do, then we need to implement cancellation + // of pending queries. + autofill_data_->ShutdownOnUIThread(); + web_database_->ShutdownDatabase(); +} + +scoped_refptr +NwFormDatabaseService::get_autofill_webdata_service() { + return autofill_data_; +} + +void NwFormDatabaseService::ClearFormData() { + BrowserThread::PostTask( + BrowserThread::DB, + FROM_HERE, + base::Bind(&NwFormDatabaseService::ClearFormDataImpl, + base::Unretained(this))); +} + +void NwFormDatabaseService::ClearFormDataImpl() { + base::Time begin; + base::Time end = base::Time::Max(); + autofill_data_->RemoveFormElementsAddedBetween(begin, end); + autofill_data_->RemoveAutofillDataModifiedBetween(begin, end); +} + +bool NwFormDatabaseService::HasFormData() { + WaitableEvent completion(false, false); + bool result = false; + BrowserThread::PostTask( + BrowserThread::DB, + FROM_HERE, + base::Bind(&NwFormDatabaseService::HasFormDataImpl, + base::Unretained(this), + &completion, + &result)); + completion.Wait(); + return result; +} + +void NwFormDatabaseService::HasFormDataImpl( + WaitableEvent* completion, + bool* result) { + WebDataServiceBase::Handle pending_query_handle = + autofill_data_->HasFormElements(this); + PendingQuery query; + query.result = result; + query.completion = completion; + result_map_[pending_query_handle] = query; +} + +void NwFormDatabaseService::OnWebDataServiceRequestDone( + WebDataServiceBase::Handle h, + const WDTypedResult* result) { + + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + bool has_form_data = false; + if (result) { + DCHECK_EQ(AUTOFILL_VALUE_RESULT, result->GetType()); + const WDResult* autofill_result = + static_cast*>(result); + has_form_data = autofill_result->GetValue(); + } + QueryMap::const_iterator it = result_map_.find(h); + if (it == result_map_.end()) { + LOG(WARNING) << "Received unexpected callback from web data service"; + return; + } + *(it->second.result) = has_form_data; + it->second.completion->Signal(); + result_map_.erase(h); +} + +} // namespace android_webview diff --git a/src/browser/nw_form_database_service.h b/src/browser/nw_form_database_service.h new file mode 100644 index 0000000000..62a9338d18 --- /dev/null +++ b/src/browser/nw_form_database_service.h @@ -0,0 +1,67 @@ +// 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 NW_BROWSER_AW_FORM_DATABASE_SERVICE_H_ +#define NW_BROWSER_AW_FORM_DATABASE_SERVICE_H_ + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "components/autofill/core/browser/webdata/autofill_webdata_service.h" +#include "components/webdata/common/web_data_service_consumer.h" +#include "components/webdata/common/web_database_service.h" + +namespace base { +class WaitableEvent; +}; + +namespace nw { + +// Handles the database operations necessary to implement the autocomplete +// functionality. This includes creating and initializing the components that +// handle the database backend, and providing a synchronous interface when +// needed (the chromium database components have an async. interface). +class NwFormDatabaseService : public WebDataServiceConsumer { + public: + NwFormDatabaseService(const base::FilePath path); + + ~NwFormDatabaseService() final; + + void Shutdown(); + + // Returns whether the database has any data stored. May do + // IO access and block. + bool HasFormData(); + + // Clear any saved form data. Executes asynchronously. + void ClearFormData(); + + scoped_refptr + get_autofill_webdata_service(); + + // WebDataServiceConsumer implementation. + void OnWebDataServiceRequestDone( + WebDataServiceBase::Handle h, + const WDTypedResult* result) override; + + private: + struct PendingQuery { + bool* result; + base::WaitableEvent* completion; + }; + typedef std::map QueryMap; + + void ClearFormDataImpl(); + void HasFormDataImpl(base::WaitableEvent* completion, bool* result); + + QueryMap result_map_; + + scoped_refptr autofill_data_; + scoped_refptr web_database_; + + DISALLOW_COPY_AND_ASSIGN(NwFormDatabaseService); +}; + +} // namespace android_webview + +#endif // ANDROID_WEBVIEW_BROWSER_AW_FORM_DATABASE_SERVICE_H_ diff --git a/src/browser/pepper/chrome_browser_pepper_host_factory.cc b/src/browser/pepper/chrome_browser_pepper_host_factory.cc new file mode 100644 index 0000000000..bc7bd41f73 --- /dev/null +++ b/src/browser/pepper/chrome_browser_pepper_host_factory.cc @@ -0,0 +1,94 @@ +// 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 "content/nw/src/browser/pepper/chrome_browser_pepper_host_factory.h" + +#include "build/build_config.h" +#include "content/nw/src/browser/pepper/pepper_broker_message_filter.h" +#include "content/nw/src/browser/pepper/pepper_flash_browser_host.h" +#include "content/nw/src/browser/pepper/pepper_flash_clipboard_message_filter.h" +#include "content/nw/src/browser/pepper/pepper_isolated_file_system_message_filter.h" +#include "content/public/browser/browser_ppapi_host.h" +#include "ppapi/host/message_filter_host.h" +#include "ppapi/host/ppapi_host.h" +#include "ppapi/host/resource_host.h" +#include "ppapi/proxy/ppapi_messages.h" +#include "ppapi/shared_impl/ppapi_permissions.h" + +using ppapi::host::MessageFilterHost; +using ppapi::host::ResourceHost; +using ppapi::host::ResourceMessageFilter; + +namespace chrome { + +ChromeBrowserPepperHostFactory::ChromeBrowserPepperHostFactory( + content::BrowserPpapiHost* host) + : host_(host) {} + +ChromeBrowserPepperHostFactory::~ChromeBrowserPepperHostFactory() {} + +scoped_ptr ChromeBrowserPepperHostFactory::CreateResourceHost( + ppapi::host::PpapiHost* host, + PP_Resource resource, + PP_Instance instance, + const IPC::Message& message) { + DCHECK(host == host_->GetPpapiHost()); + + // Make sure the plugin is giving us a valid instance for this resource. + if (!host_->IsValidInstance(instance)) + return scoped_ptr(); + + // Private interfaces. + if (host_->GetPpapiHost()->permissions().HasPermission( + ppapi::PERMISSION_PRIVATE)) { + switch (message.type()) { + case PpapiHostMsg_Broker_Create::ID: { + scoped_refptr broker_filter( + new PepperBrokerMessageFilter(instance, host_)); + return scoped_ptr(new MessageFilterHost( + host_->GetPpapiHost(), instance, resource, broker_filter)); + } + } + } + + // Flash interfaces. + if (host_->GetPpapiHost()->permissions().HasPermission( + ppapi::PERMISSION_FLASH)) { + switch (message.type()) { + case PpapiHostMsg_Flash_Create::ID: + return scoped_ptr( + new PepperFlashBrowserHost(host_, instance, resource)); + case PpapiHostMsg_FlashClipboard_Create::ID: { + scoped_refptr clipboard_filter( + new PepperFlashClipboardMessageFilter); + return scoped_ptr(new MessageFilterHost( + host_->GetPpapiHost(), instance, resource, clipboard_filter)); + } +#if 0 + case PpapiHostMsg_FlashDRM_Create::ID: + return scoped_ptr( + new PepperFlashDRMHost(host_, instance, resource)); +#endif + } + } + + // Permissions for the following interfaces will be checked at the + // time of the corresponding instance's methods calls (because + // permission check can be performed only on the UI + // thread). Currently these interfaces are available only for + // whitelisted apps which may not have access to the other private + // interfaces. + if (message.type() == PpapiHostMsg_IsolatedFileSystem_Create::ID) { + PepperIsolatedFileSystemMessageFilter* isolated_fs_filter = + PepperIsolatedFileSystemMessageFilter::Create(instance, host_); + if (!isolated_fs_filter) + return scoped_ptr(); + return scoped_ptr( + new MessageFilterHost(host, instance, resource, isolated_fs_filter)); + } + + return scoped_ptr(); +} + +} // namespace chrome diff --git a/src/browser/pepper/chrome_browser_pepper_host_factory.h b/src/browser/pepper/chrome_browser_pepper_host_factory.h new file mode 100644 index 0000000000..b817953b54 --- /dev/null +++ b/src/browser/pepper/chrome_browser_pepper_host_factory.h @@ -0,0 +1,38 @@ +// 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_RENDERER_HOST_PEPPER_CHROME_BROWSER_PEPPER_HOST_FACTORY_H_ +#define CHROME_BROWSER_RENDERER_HOST_PEPPER_CHROME_BROWSER_PEPPER_HOST_FACTORY_H_ + +#include "base/compiler_specific.h" +#include "ppapi/host/host_factory.h" + +namespace content { +class BrowserPpapiHost; +} // namespace content + +namespace chrome { + +class ChromeBrowserPepperHostFactory : public ppapi::host::HostFactory { + public: + // Non-owning pointer to the filter must outlive this class. + explicit ChromeBrowserPepperHostFactory(content::BrowserPpapiHost* host); + ~ChromeBrowserPepperHostFactory() override; + + scoped_ptr CreateResourceHost( + ppapi::host::PpapiHost* host, + PP_Resource resource, + PP_Instance instance, + const IPC::Message& message) override; + + private: + // Non-owning pointer. + content::BrowserPpapiHost* host_; + + DISALLOW_COPY_AND_ASSIGN(ChromeBrowserPepperHostFactory); +}; + +} // namespace chrome + +#endif // CHROME_BROWSER_RENDERER_HOST_PEPPER_CHROME_BROWSER_PEPPER_HOST_FACTORY_H_ diff --git a/src/browser/pepper/pepper_broker_message_filter.cc b/src/browser/pepper/pepper_broker_message_filter.cc new file mode 100644 index 0000000000..79f7106b09 --- /dev/null +++ b/src/browser/pepper/pepper_broker_message_filter.cc @@ -0,0 +1,56 @@ +// 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 "content/nw/src/browser/pepper/pepper_broker_message_filter.h" + +#include + +#include "components/content_settings/core/browser/host_content_settings_map.h" +#include "components/content_settings/core/common/content_settings.h" +#include "content/public/browser/browser_ppapi_host.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_process_host.h" +#include "ipc/ipc_message_macros.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/host/dispatch_host_message.h" +#include "ppapi/proxy/ppapi_messages.h" +#include "url/gurl.h" + +using content::BrowserPpapiHost; +using content::BrowserThread; +using content::RenderProcessHost; + +namespace chrome { + +PepperBrokerMessageFilter::PepperBrokerMessageFilter(PP_Instance instance, + BrowserPpapiHost* host) + : document_url_(host->GetDocumentURLForInstance(instance)) { + int unused; + host->GetRenderFrameIDsForInstance(instance, &render_process_id_, &unused); +} + +PepperBrokerMessageFilter::~PepperBrokerMessageFilter() {} + +scoped_refptr +PepperBrokerMessageFilter::OverrideTaskRunnerForMessage( + const IPC::Message& message) { + return BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI); +} + +int32_t PepperBrokerMessageFilter::OnResourceMessageReceived( + const IPC::Message& msg, + ppapi::host::HostMessageContext* context) { + PPAPI_BEGIN_MESSAGE_MAP(PepperBrokerMessageFilter, msg) + PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_Broker_IsAllowed, + OnIsAllowed) + PPAPI_END_MESSAGE_MAP() + return PP_ERROR_FAILED; +} + +int32_t PepperBrokerMessageFilter::OnIsAllowed( + ppapi::host::HostMessageContext* context) { + return PP_OK; +} + +} // namespace chrome diff --git a/src/browser/pepper/pepper_broker_message_filter.h b/src/browser/pepper/pepper_broker_message_filter.h new file mode 100644 index 0000000000..44627c6a35 --- /dev/null +++ b/src/browser/pepper/pepper_broker_message_filter.h @@ -0,0 +1,51 @@ +// 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_RENDERER_HOST_PEPPER_PEPPER_BROKER_MESSAGE_FILTER_H_ +#define CHROME_BROWSER_RENDERER_HOST_PEPPER_PEPPER_BROKER_MESSAGE_FILTER_H_ + +#include "base/compiler_specific.h" +#include "ppapi/c/pp_instance.h" +#include "ppapi/host/resource_message_filter.h" +#include "url/gurl.h" + +namespace content { +class BrowserPpapiHost; +} + +namespace ppapi { +namespace host { +struct HostMessageContext; +} +} + +namespace chrome { + +// This filter handles messages for the PepperBrokerHost on the UI thread. +class PepperBrokerMessageFilter : public ppapi::host::ResourceMessageFilter { + public: + PepperBrokerMessageFilter(PP_Instance instance, + content::BrowserPpapiHost* host); + + private: + ~PepperBrokerMessageFilter() override; + + // ppapi::host::ResourceMessageFilter overrides. + scoped_refptr OverrideTaskRunnerForMessage( + const IPC::Message& message) override; + int32_t OnResourceMessageReceived( + const IPC::Message& msg, + ppapi::host::HostMessageContext* context) override; + + int32_t OnIsAllowed(ppapi::host::HostMessageContext* context); + + int render_process_id_; + GURL document_url_; + + DISALLOW_COPY_AND_ASSIGN(PepperBrokerMessageFilter); +}; + +} // namespace chrome + +#endif // CHROME_BROWSER_RENDERER_HOST_PEPPER_PEPPER_BROKER_MESSAGE_FILTER_H_ diff --git a/src/browser/pepper/pepper_flash_browser_host.cc b/src/browser/pepper/pepper_flash_browser_host.cc new file mode 100644 index 0000000000..ddd4305951 --- /dev/null +++ b/src/browser/pepper/pepper_flash_browser_host.cc @@ -0,0 +1,133 @@ +// 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 "content/nw/src/browser/pepper/pepper_flash_browser_host.h" + +#include "base/time/time.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_ppapi_host.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_process_host.h" +#include "ipc/ipc_message_macros.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/c/private/ppb_flash.h" +#include "ppapi/host/dispatch_host_message.h" +#include "ppapi/proxy/ppapi_messages.h" +#include "ppapi/proxy/resource_message_params.h" +#include "ppapi/shared_impl/time_conversion.h" +#include "url/gurl.h" + +#if defined(OS_WIN) +#include +#elif defined(OS_MACOSX) +#include +#endif + +using content::BrowserPpapiHost; +using content::BrowserThread; +using content::RenderProcessHost; + +namespace chrome { + +#if 0 +namespace { + +// Get the CookieSettings on the UI thread for the given render process ID. +scoped_refptr GetCookieSettings(int render_process_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + RenderProcessHost* render_process_host = + RenderProcessHost::FromID(render_process_id); + if (render_process_host && render_process_host->GetBrowserContext()) { + Profile* profile = + Profile::FromBrowserContext(render_process_host->GetBrowserContext()); + return CookieSettings::Factory::GetForProfile(profile); + } + return NULL; +} + +} // namespace +#endif + +PepperFlashBrowserHost::PepperFlashBrowserHost(BrowserPpapiHost* host, + PP_Instance instance, + PP_Resource resource) + : ResourceHost(host->GetPpapiHost(), instance, resource), + host_(host), + weak_factory_(this) { + int unused; + host->GetRenderFrameIDsForInstance(instance, &render_process_id_, &unused); +} + +PepperFlashBrowserHost::~PepperFlashBrowserHost() {} + +int32_t PepperFlashBrowserHost::OnResourceMessageReceived( + const IPC::Message& msg, + ppapi::host::HostMessageContext* context) { + PPAPI_BEGIN_MESSAGE_MAP(PepperFlashBrowserHost, msg) + PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_Flash_UpdateActivity, + OnUpdateActivity) + PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_Flash_GetLocalTimeZoneOffset, + OnGetLocalTimeZoneOffset) + PPAPI_DISPATCH_HOST_RESOURCE_CALL_0( + PpapiHostMsg_Flash_GetLocalDataRestrictions, OnGetLocalDataRestrictions) + PPAPI_END_MESSAGE_MAP() + return PP_ERROR_FAILED; +} + +int32_t PepperFlashBrowserHost::OnUpdateActivity( + ppapi::host::HostMessageContext* host_context) { +#if defined(OS_WIN) + // Reading then writing back the same value to the screensaver timeout system + // setting resets the countdown which prevents the screensaver from turning + // on "for a while". As long as the plugin pings us with this message faster + // than the screensaver timeout, it won't go on. + int value = 0; + if (SystemParametersInfo(SPI_GETSCREENSAVETIMEOUT, 0, &value, 0)) + SystemParametersInfo(SPI_SETSCREENSAVETIMEOUT, value, NULL, 0); +#elif defined(OS_MACOSX) + UpdateSystemActivity(OverallAct); +#else +// TODO(brettw) implement this for other platforms. +#endif + return PP_OK; +} + +int32_t PepperFlashBrowserHost::OnGetLocalTimeZoneOffset( + ppapi::host::HostMessageContext* host_context, + const base::Time& t) { + // The reason for this processing being in the browser process is that on + // Linux, the localtime calls require filesystem access prohibited by the + // sandbox. + host_context->reply_msg = PpapiPluginMsg_Flash_GetLocalTimeZoneOffsetReply( + ppapi::PPGetLocalTimeZoneOffset(t)); + return PP_OK; +} + +int32_t PepperFlashBrowserHost::OnGetLocalDataRestrictions( + ppapi::host::HostMessageContext* context) { + // Getting the Flash LSO settings requires using the CookieSettings which + // belong to the profile which lives on the UI thread. We lazily initialize + // |cookie_settings_| by grabbing the reference from the UI thread and then + // call |GetLocalDataRestrictions| with it. + GURL document_url = host_->GetDocumentURLForInstance(pp_instance()); + GURL plugin_url = host_->GetPluginURLForInstance(pp_instance()); + GetLocalDataRestrictions(context->MakeReplyMessageContext(), + document_url, + plugin_url); + return PP_OK_COMPLETIONPENDING; +} + +void PepperFlashBrowserHost::GetLocalDataRestrictions( + ppapi::host::ReplyMessageContext reply_context, + const GURL& document_url, + const GURL& plugin_url) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + PP_FlashLSORestrictions restrictions = PP_FLASHLSORESTRICTIONS_NONE; + SendReply(reply_context, + PpapiPluginMsg_Flash_GetLocalDataRestrictionsReply( + static_cast(restrictions))); +} + +} // namespace chrome diff --git a/src/browser/pepper/pepper_flash_browser_host.h b/src/browser/pepper/pepper_flash_browser_host.h new file mode 100644 index 0000000000..6fb4aced18 --- /dev/null +++ b/src/browser/pepper/pepper_flash_browser_host.h @@ -0,0 +1,60 @@ +// 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_RENDERER_HOST_PEPPER_PEPPER_FLASH_BROWSER_HOST_H_ +#define CHROME_BROWSER_RENDERER_HOST_PEPPER_PEPPER_FLASH_BROWSER_HOST_H_ + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "ppapi/host/host_message_context.h" +#include "ppapi/host/resource_host.h" + +namespace base { +class Time; +} + +namespace content { +class BrowserPpapiHost; +class ResourceContext; +} + +class GURL; + +namespace chrome { + +class PepperFlashBrowserHost : public ppapi::host::ResourceHost { + public: + PepperFlashBrowserHost(content::BrowserPpapiHost* host, + PP_Instance instance, + PP_Resource resource); + ~PepperFlashBrowserHost() override; + + // ppapi::host::ResourceHost override. + int32_t OnResourceMessageReceived( + const IPC::Message& msg, + ppapi::host::HostMessageContext* context) override; + + private: + int32_t OnUpdateActivity(ppapi::host::HostMessageContext* host_context); + int32_t OnGetLocalTimeZoneOffset( + ppapi::host::HostMessageContext* host_context, + const base::Time& t); + int32_t OnGetLocalDataRestrictions(ppapi::host::HostMessageContext* context); + + void GetLocalDataRestrictions(ppapi::host::ReplyMessageContext reply_context, + const GURL& document_url, + const GURL& plugin_url); + + content::BrowserPpapiHost* host_; + int render_process_id_; + // For fetching the Flash LSO settings. + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(PepperFlashBrowserHost); +}; + +} // namespace chrome + +#endif // CHROME_BROWSER_RENDERER_HOST_PEPPER_PEPPER_FLASH_BROWSER_HOST_H_ diff --git a/src/browser/pepper/pepper_flash_clipboard_message_filter.cc b/src/browser/pepper/pepper_flash_clipboard_message_filter.cc new file mode 100644 index 0000000000..dc0cb3a341 --- /dev/null +++ b/src/browser/pepper/pepper_flash_clipboard_message_filter.cc @@ -0,0 +1,376 @@ +// 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 "content/nw/src/browser/pepper/pepper_flash_clipboard_message_filter.h" + +#include "base/pickle.h" +#include "base/strings/utf_string_conversions.h" +#include "content/public/browser/browser_thread.h" +#include "ipc/ipc_message.h" +#include "ipc/ipc_message_macros.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/c/private/ppb_flash_clipboard.h" +#include "ppapi/host/dispatch_host_message.h" +#include "ppapi/host/host_message_context.h" +#include "ppapi/host/ppapi_host.h" +#include "ppapi/proxy/ppapi_messages.h" +#include "ppapi/proxy/resource_message_params.h" +#include "ui/base/clipboard/scoped_clipboard_writer.h" + +using content::BrowserThread; + +namespace chrome { + +namespace { + +const size_t kMaxClipboardWriteSize = 1000000; + +ui::ClipboardType ConvertClipboardType(uint32_t type) { + switch (type) { + case PP_FLASH_CLIPBOARD_TYPE_STANDARD: + return ui::CLIPBOARD_TYPE_COPY_PASTE; + case PP_FLASH_CLIPBOARD_TYPE_SELECTION: + return ui::CLIPBOARD_TYPE_SELECTION; + } + NOTREACHED(); + return ui::CLIPBOARD_TYPE_COPY_PASTE; +} + +// Functions to pack/unpack custom data from a pickle. See the header file for +// more detail on custom formats in Pepper. +// TODO(raymes): Currently pepper custom formats are stored in their own +// native format type. However we should be able to store them in the same way +// as "Web Custom" formats are. This would allow clipboard data to be shared +// between pepper applications and web applications. However currently web apps +// assume all data that is placed on the clipboard is UTF16 and pepper allows +// arbitrary data so this change would require some reworking of the chrome +// clipboard interface for custom data. +bool JumpToFormatInPickle(const base::string16& format, PickleIterator* iter) { + size_t size = 0; + if (!iter->ReadSizeT(&size)) + return false; + for (size_t i = 0; i < size; ++i) { + base::string16 stored_format; + if (!iter->ReadString16(&stored_format)) + return false; + if (stored_format == format) + return true; + int skip_length; + if (!iter->ReadLength(&skip_length)) + return false; + if (!iter->SkipBytes(skip_length)) + return false; + } + return false; +} + +bool IsFormatAvailableInPickle(const base::string16& format, + const Pickle& pickle) { + PickleIterator iter(pickle); + return JumpToFormatInPickle(format, &iter); +} + +std::string ReadDataFromPickle(const base::string16& format, + const Pickle& pickle) { + std::string result; + PickleIterator iter(pickle); + if (!JumpToFormatInPickle(format, &iter) || !iter.ReadString(&result)) + return std::string(); + return result; +} + +bool WriteDataToPickle(const std::map& data, + Pickle* pickle) { + pickle->WriteSizeT(data.size()); + for (std::map::const_iterator it = data.begin(); + it != data.end(); + ++it) { + if (!pickle->WriteString16(it->first)) + return false; + if (!pickle->WriteString(it->second)) + return false; + } + return true; +} + +} // namespace + +PepperFlashClipboardMessageFilter::PepperFlashClipboardMessageFilter() {} + +PepperFlashClipboardMessageFilter::~PepperFlashClipboardMessageFilter() {} + +scoped_refptr +PepperFlashClipboardMessageFilter::OverrideTaskRunnerForMessage( + const IPC::Message& msg) { + // Clipboard writes should always occur on the UI thread due to the + // restrictions of various platform APIs. In general, the clipboard is not + // thread-safe, so all clipboard calls should be serviced from the UI thread. + if (msg.type() == PpapiHostMsg_FlashClipboard_WriteData::ID) + return BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI); + +// Windows needs clipboard reads to be serviced from the IO thread because +// these are sync IPCs which can result in deadlocks with plugins if serviced +// from the UI thread. Note that Windows clipboard calls ARE thread-safe so it +// is ok for reads and writes to be serviced from different threads. +#if !defined(OS_WIN) + return BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI); +#else + return BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO); +#endif +} + +int32_t PepperFlashClipboardMessageFilter::OnResourceMessageReceived( + const IPC::Message& msg, + ppapi::host::HostMessageContext* context) { + PPAPI_BEGIN_MESSAGE_MAP(PepperFlashClipboardMessageFilter, msg) + PPAPI_DISPATCH_HOST_RESOURCE_CALL( + PpapiHostMsg_FlashClipboard_RegisterCustomFormat, + OnMsgRegisterCustomFormat) + PPAPI_DISPATCH_HOST_RESOURCE_CALL( + PpapiHostMsg_FlashClipboard_IsFormatAvailable, OnMsgIsFormatAvailable) + PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FlashClipboard_ReadData, + OnMsgReadData) + PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FlashClipboard_WriteData, + OnMsgWriteData) + PPAPI_DISPATCH_HOST_RESOURCE_CALL( + PpapiHostMsg_FlashClipboard_GetSequenceNumber, OnMsgGetSequenceNumber) + PPAPI_END_MESSAGE_MAP() + return PP_ERROR_FAILED; +} + +int32_t PepperFlashClipboardMessageFilter::OnMsgRegisterCustomFormat( + ppapi::host::HostMessageContext* host_context, + const std::string& format_name) { + uint32_t format = custom_formats_.RegisterFormat(format_name); + if (format == PP_FLASH_CLIPBOARD_FORMAT_INVALID) + return PP_ERROR_FAILED; + host_context->reply_msg = + PpapiPluginMsg_FlashClipboard_RegisterCustomFormatReply(format); + return PP_OK; +} + +int32_t PepperFlashClipboardMessageFilter::OnMsgIsFormatAvailable( + ppapi::host::HostMessageContext* host_context, + uint32_t clipboard_type, + uint32_t format) { + if (clipboard_type != PP_FLASH_CLIPBOARD_TYPE_STANDARD) { + NOTIMPLEMENTED(); + return PP_ERROR_FAILED; + } + + ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); + ui::ClipboardType type = ConvertClipboardType(clipboard_type); + bool available = false; + switch (format) { + case PP_FLASH_CLIPBOARD_FORMAT_PLAINTEXT: { + bool plain = clipboard->IsFormatAvailable( + ui::Clipboard::GetPlainTextFormatType(), type); + bool plainw = clipboard->IsFormatAvailable( + ui::Clipboard::GetPlainTextWFormatType(), type); + available = plain || plainw; + break; + } + case PP_FLASH_CLIPBOARD_FORMAT_HTML: + available = clipboard->IsFormatAvailable( + ui::Clipboard::GetHtmlFormatType(), type); + break; + case PP_FLASH_CLIPBOARD_FORMAT_RTF: + available = + clipboard->IsFormatAvailable(ui::Clipboard::GetRtfFormatType(), type); + break; + case PP_FLASH_CLIPBOARD_FORMAT_INVALID: + break; + default: + if (custom_formats_.IsFormatRegistered(format)) { + std::string format_name = custom_formats_.GetFormatName(format); + std::string clipboard_data; + clipboard->ReadData(ui::Clipboard::GetPepperCustomDataFormatType(), + &clipboard_data); + Pickle pickle(clipboard_data.data(), clipboard_data.size()); + available = + IsFormatAvailableInPickle(base::UTF8ToUTF16(format_name), pickle); + } + break; + } + + return available ? PP_OK : PP_ERROR_FAILED; +} + +int32_t PepperFlashClipboardMessageFilter::OnMsgReadData( + ppapi::host::HostMessageContext* host_context, + uint32_t clipboard_type, + uint32_t format) { + if (clipboard_type != PP_FLASH_CLIPBOARD_TYPE_STANDARD) { + NOTIMPLEMENTED(); + return PP_ERROR_FAILED; + } + + ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); + ui::ClipboardType type = ConvertClipboardType(clipboard_type); + std::string clipboard_string; + int32_t result = PP_ERROR_FAILED; + switch (format) { + case PP_FLASH_CLIPBOARD_FORMAT_PLAINTEXT: { + if (clipboard->IsFormatAvailable(ui::Clipboard::GetPlainTextWFormatType(), + type)) { + base::string16 text; + clipboard->ReadText(type, &text); + if (!text.empty()) { + result = PP_OK; + clipboard_string = base::UTF16ToUTF8(text); + break; + } + } + // If the PlainTextW format isn't available or is empty, take the + // ASCII text format. + if (clipboard->IsFormatAvailable(ui::Clipboard::GetPlainTextFormatType(), + type)) { + result = PP_OK; + clipboard->ReadAsciiText(type, &clipboard_string); + } + break; + } + case PP_FLASH_CLIPBOARD_FORMAT_HTML: { + if (!clipboard->IsFormatAvailable(ui::Clipboard::GetHtmlFormatType(), + type)) { + break; + } + + base::string16 html; + std::string url; + uint32 fragment_start; + uint32 fragment_end; + clipboard->ReadHTML(type, &html, &url, &fragment_start, &fragment_end); + result = PP_OK; + clipboard_string = base::UTF16ToUTF8( + html.substr(fragment_start, fragment_end - fragment_start)); + break; + } + case PP_FLASH_CLIPBOARD_FORMAT_RTF: { + if (!clipboard->IsFormatAvailable(ui::Clipboard::GetRtfFormatType(), + type)) { + break; + } + result = PP_OK; + clipboard->ReadRTF(type, &clipboard_string); + break; + } + case PP_FLASH_CLIPBOARD_FORMAT_INVALID: + break; + default: { + if (custom_formats_.IsFormatRegistered(format)) { + base::string16 format_name = + base::UTF8ToUTF16(custom_formats_.GetFormatName(format)); + std::string clipboard_data; + clipboard->ReadData(ui::Clipboard::GetPepperCustomDataFormatType(), + &clipboard_data); + Pickle pickle(clipboard_data.data(), clipboard_data.size()); + if (IsFormatAvailableInPickle(format_name, pickle)) { + result = PP_OK; + clipboard_string = ReadDataFromPickle(format_name, pickle); + } + } + break; + } + } + + if (result == PP_OK) { + host_context->reply_msg = + PpapiPluginMsg_FlashClipboard_ReadDataReply(clipboard_string); + } + return result; +} + +int32_t PepperFlashClipboardMessageFilter::OnMsgWriteData( + ppapi::host::HostMessageContext* host_context, + uint32_t clipboard_type, + const std::vector& formats, + const std::vector& data) { + if (clipboard_type != PP_FLASH_CLIPBOARD_TYPE_STANDARD) { + NOTIMPLEMENTED(); + return PP_ERROR_FAILED; + } + if (formats.size() != data.size()) + return PP_ERROR_FAILED; + + ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); + ui::ClipboardType type = ConvertClipboardType(clipboard_type); + // If no formats are passed in clear the clipboard. + if (formats.size() == 0) { + clipboard->Clear(type); + return PP_OK; + } + + ui::ScopedClipboardWriter scw(type); + std::map custom_data_map; + int32_t res = PP_OK; + for (uint32_t i = 0; i < formats.size(); ++i) { + if (data[i].length() > kMaxClipboardWriteSize) { + res = PP_ERROR_NOSPACE; + break; + } + + switch (formats[i]) { + case PP_FLASH_CLIPBOARD_FORMAT_PLAINTEXT: + scw.WriteText(base::UTF8ToUTF16(data[i])); + break; + case PP_FLASH_CLIPBOARD_FORMAT_HTML: + scw.WriteHTML(base::UTF8ToUTF16(data[i]), std::string()); + break; + case PP_FLASH_CLIPBOARD_FORMAT_RTF: + scw.WriteRTF(data[i]); + break; + case PP_FLASH_CLIPBOARD_FORMAT_INVALID: + res = PP_ERROR_BADARGUMENT; + break; + default: + if (custom_formats_.IsFormatRegistered(formats[i])) { + std::string format_name = custom_formats_.GetFormatName(formats[i]); + custom_data_map[base::UTF8ToUTF16(format_name)] = data[i]; + } else { + // Invalid format. + res = PP_ERROR_BADARGUMENT; + break; + } + } + + if (res != PP_OK) + break; + } + + if (custom_data_map.size() > 0) { + Pickle pickle; + if (WriteDataToPickle(custom_data_map, &pickle)) { + scw.WritePickledData(pickle, + ui::Clipboard::GetPepperCustomDataFormatType()); + } else { + res = PP_ERROR_BADARGUMENT; + } + } + + if (res != PP_OK) { + // Need to clear the objects so nothing is written. + scw.Reset(); + } + + return res; +} + +int32_t PepperFlashClipboardMessageFilter::OnMsgGetSequenceNumber( + ppapi::host::HostMessageContext* host_context, + uint32_t clipboard_type) { + if (clipboard_type != PP_FLASH_CLIPBOARD_TYPE_STANDARD) { + NOTIMPLEMENTED(); + return PP_ERROR_FAILED; + } + + ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); + ui::ClipboardType type = ConvertClipboardType(clipboard_type); + int64_t sequence_number = clipboard->GetSequenceNumber(type); + host_context->reply_msg = + PpapiPluginMsg_FlashClipboard_GetSequenceNumberReply(sequence_number); + return PP_OK; +} + +} // namespace chrome diff --git a/src/browser/pepper/pepper_flash_clipboard_message_filter.h b/src/browser/pepper/pepper_flash_clipboard_message_filter.h new file mode 100644 index 0000000000..ff07eb7375 --- /dev/null +++ b/src/browser/pepper/pepper_flash_clipboard_message_filter.h @@ -0,0 +1,78 @@ +// 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_RENDERER_HOST_PEPPER_PEPPER_FLASH_CLIPBOARD_MESSAGE_FILTER_H_ +#define CHROME_BROWSER_RENDERER_HOST_PEPPER_PEPPER_FLASH_CLIPBOARD_MESSAGE_FILTER_H_ + +#include +#include + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "ppapi/host/resource_message_filter.h" +#include "ppapi/shared_impl/flash_clipboard_format_registry.h" + +namespace ppapi { +namespace host { +struct HostMessageContext; +} +} + +namespace ui { +class ScopedClipboardWriter; +} + +namespace chrome { + +// Resource message filter for accessing the clipboard in Pepper. Pepper +// supports reading/writing custom formats from the clipboard. Currently, all +// custom formats that are read/written from the clipboard through pepper are +// stored in a single real clipboard format (in the same way the "web custom" +// clipboard formats are). This is done so that we don't have to have use real +// clipboard types for each custom clipboard format which may be a limited +// resource on a particular platform. +class PepperFlashClipboardMessageFilter + : public ppapi::host::ResourceMessageFilter { + public: + PepperFlashClipboardMessageFilter(); + + protected: + // ppapi::host::ResourceMessageFilter overrides. + scoped_refptr OverrideTaskRunnerForMessage( + const IPC::Message& msg) override; + int32_t OnResourceMessageReceived( + const IPC::Message& msg, + ppapi::host::HostMessageContext* context) override; + + private: + ~PepperFlashClipboardMessageFilter() override; + + int32_t OnMsgRegisterCustomFormat( + ppapi::host::HostMessageContext* host_context, + const std::string& format_name); + int32_t OnMsgIsFormatAvailable(ppapi::host::HostMessageContext* host_context, + uint32_t clipboard_type, + uint32_t format); + int32_t OnMsgReadData(ppapi::host::HostMessageContext* host_context, + uint32_t clipoard_type, + uint32_t format); + int32_t OnMsgWriteData(ppapi::host::HostMessageContext* host_context, + uint32_t clipboard_type, + const std::vector& formats, + const std::vector& data); + int32_t OnMsgGetSequenceNumber(ppapi::host::HostMessageContext* host_context, + uint32_t clipboard_type); + + int32_t WriteClipboardDataItem(uint32_t format, + const std::string& data, + ui::ScopedClipboardWriter* scw); + + ppapi::FlashClipboardFormatRegistry custom_formats_; + + DISALLOW_COPY_AND_ASSIGN(PepperFlashClipboardMessageFilter); +}; + +} // namespace chrome + +#endif // CHROME_BROWSER_RENDERER_HOST_PEPPER_PEPPER_FLASH_CLIPBOARD_MESSAGE_FILTER_H_ diff --git a/src/browser/pepper/pepper_isolated_file_system_message_filter.cc b/src/browser/pepper/pepper_isolated_file_system_message_filter.cc new file mode 100644 index 0000000000..7cf809148f --- /dev/null +++ b/src/browser/pepper/pepper_isolated_file_system_message_filter.cc @@ -0,0 +1,202 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/nw/src/browser/pepper/pepper_isolated_file_system_message_filter.h" + +// #include "chrome/browser/browser_process.h" +// #include "chrome/browser/profiles/profile.h" +// #include "chrome/browser/profiles/profile_manager.h" +// #include "chrome/common/chrome_switches.h" +// #include "chrome/common/pepper_permission_util.h" +#include "content/public/browser/browser_ppapi_host.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/child_process_security_policy.h" +#include "content/public/browser/render_view_host.h" +#if defined(ENABLE_EXTENSIONS) +#include "extensions/browser/extension_registry.h" +#include "extensions/common/constants.h" +#include "extensions/common/extension.h" +#include "extensions/common/extension_set.h" +#endif +#include "ppapi/c/pp_errors.h" +#include "ppapi/host/dispatch_host_message.h" +#include "ppapi/host/host_message_context.h" +#include "ppapi/host/ppapi_host.h" +#include "ppapi/proxy/ppapi_messages.h" +#include "ppapi/shared_impl/file_system_util.h" +#include "storage/browser/fileapi/isolated_context.h" + +namespace chrome { + +namespace { + +const char* kPredefinedAllowedCrxFsOrigins[] = { + "6EAED1924DB611B6EEF2A664BD077BE7EAD33B8F", // see crbug.com/234789 + "4EB74897CB187C7633357C2FE832E0AD6A44883A" // see crbug.com/234789 +}; + +} // namespace + +// static +PepperIsolatedFileSystemMessageFilter* +PepperIsolatedFileSystemMessageFilter::Create(PP_Instance instance, + content::BrowserPpapiHost* host) { + int render_process_id; + int unused_render_frame_id; + if (!host->GetRenderFrameIDsForInstance( + instance, &render_process_id, &unused_render_frame_id)) { + return NULL; + } + return new PepperIsolatedFileSystemMessageFilter( + render_process_id, + host->GetProfileDataDirectory(), + host->GetDocumentURLForInstance(instance), + host->GetPpapiHost()); +} + +PepperIsolatedFileSystemMessageFilter::PepperIsolatedFileSystemMessageFilter( + int render_process_id, + const base::FilePath& profile_directory, + const GURL& document_url, + ppapi::host::PpapiHost* ppapi_host) + : render_process_id_(render_process_id), + profile_directory_(profile_directory), + document_url_(document_url), + ppapi_host_(ppapi_host) { + for (size_t i = 0; i < arraysize(kPredefinedAllowedCrxFsOrigins); ++i) + allowed_crxfs_origins_.insert(kPredefinedAllowedCrxFsOrigins[i]); +} + +PepperIsolatedFileSystemMessageFilter:: + ~PepperIsolatedFileSystemMessageFilter() {} + +scoped_refptr +PepperIsolatedFileSystemMessageFilter::OverrideTaskRunnerForMessage( + const IPC::Message& msg) { + // In order to reach ExtensionSystem, we need to get ProfileManager first. + // ProfileManager lives in UI thread, so we need to do this in UI thread. + return content::BrowserThread::GetMessageLoopProxyForThread( + content::BrowserThread::UI); +} + +int32_t PepperIsolatedFileSystemMessageFilter::OnResourceMessageReceived( + const IPC::Message& msg, + ppapi::host::HostMessageContext* context) { + PPAPI_BEGIN_MESSAGE_MAP(PepperIsolatedFileSystemMessageFilter, msg) + PPAPI_DISPATCH_HOST_RESOURCE_CALL( + PpapiHostMsg_IsolatedFileSystem_BrowserOpen, + OnOpenFileSystem) + PPAPI_END_MESSAGE_MAP() + return PP_ERROR_FAILED; +} + +#if 0 +Profile* PepperIsolatedFileSystemMessageFilter::GetProfile() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + ProfileManager* profile_manager = g_browser_process->profile_manager(); + return profile_manager->GetProfile(profile_directory_); +} + +std::string PepperIsolatedFileSystemMessageFilter::CreateCrxFileSystem( + Profile* profile) { +#if defined(ENABLE_EXTENSIONS) + const extensions::Extension* extension = + extensions::ExtensionRegistry::Get(profile)->enabled_extensions().GetByID( + document_url_.host()); + if (!extension) + return std::string(); + + // First level directory for isolated filesystem to lookup. + std::string kFirstLevelDirectory("crxfs"); + return storage::IsolatedContext::GetInstance()->RegisterFileSystemForPath( + storage::kFileSystemTypeNativeLocal, + std::string(), + extension->path(), + &kFirstLevelDirectory); +#else + return std::string(); +#endif +} +#endif + +int32_t PepperIsolatedFileSystemMessageFilter::OnOpenFileSystem( + ppapi::host::HostMessageContext* context, + PP_IsolatedFileSystemType_Private type) { + switch (type) { + case PP_ISOLATEDFILESYSTEMTYPE_PRIVATE_INVALID: + case PP_ISOLATEDFILESYSTEMTYPE_PRIVATE_CRX: + break; + case PP_ISOLATEDFILESYSTEMTYPE_PRIVATE_PLUGINPRIVATE: + return OpenPluginPrivateFileSystem(context); + } + NOTREACHED(); + context->reply_msg = + PpapiPluginMsg_IsolatedFileSystem_BrowserOpenReply(std::string()); + return PP_ERROR_FAILED; +} + +#if 0 +int32_t PepperIsolatedFileSystemMessageFilter::OpenCrxFileSystem( + ppapi::host::HostMessageContext* context) { +#if defined(ENABLE_EXTENSIONS) + Profile* profile = GetProfile(); + const extensions::ExtensionSet* extension_set = NULL; + if (profile) { + extension_set = + &extensions::ExtensionRegistry::Get(profile)->enabled_extensions(); + } + if (!IsExtensionOrSharedModuleWhitelisted( + document_url_, extension_set, allowed_crxfs_origins_) && + !IsHostAllowedByCommandLine( + document_url_, extension_set, switches::kAllowNaClCrxFsAPI)) { + LOG(ERROR) << "Host " << document_url_.host() << " cannot use CrxFs API."; + return PP_ERROR_NOACCESS; + } + + // TODO(raymes): When we remove FileSystem from the renderer, we should create + // a pending PepperFileSystemBrowserHost here with the fsid and send the + // pending host ID back to the plugin. + const std::string fsid = CreateCrxFileSystem(profile); + if (fsid.empty()) { + context->reply_msg = + PpapiPluginMsg_IsolatedFileSystem_BrowserOpenReply(std::string()); + return PP_ERROR_NOTSUPPORTED; + } + + // Grant readonly access of isolated filesystem to renderer process. + content::ChildProcessSecurityPolicy* policy = + content::ChildProcessSecurityPolicy::GetInstance(); + policy->GrantReadFileSystem(render_process_id_, fsid); + + context->reply_msg = PpapiPluginMsg_IsolatedFileSystem_BrowserOpenReply(fsid); + return PP_OK; +#else + return PP_ERROR_NOTSUPPORTED; +#endif +} +#endif + +int32_t PepperIsolatedFileSystemMessageFilter::OpenPluginPrivateFileSystem( + ppapi::host::HostMessageContext* context) { + DCHECK(ppapi_host_); + // Only plugins with private permission can open the filesystem. + if (!ppapi_host_->permissions().HasPermission(ppapi::PERMISSION_PRIVATE)) + return PP_ERROR_NOACCESS; + + const std::string& root_name = ppapi::IsolatedFileSystemTypeToRootName( + PP_ISOLATEDFILESYSTEMTYPE_PRIVATE_PLUGINPRIVATE); + const std::string& fsid = + storage::IsolatedContext::GetInstance()->RegisterFileSystemForVirtualPath( + storage::kFileSystemTypePluginPrivate, root_name, base::FilePath()); + + // Grant full access of isolated filesystem to renderer process. + content::ChildProcessSecurityPolicy* policy = + content::ChildProcessSecurityPolicy::GetInstance(); + policy->GrantCreateReadWriteFileSystem(render_process_id_, fsid); + + context->reply_msg = PpapiPluginMsg_IsolatedFileSystem_BrowserOpenReply(fsid); + return PP_OK; +} + +} // namespace chrome diff --git a/src/browser/pepper/pepper_isolated_file_system_message_filter.h b/src/browser/pepper/pepper_isolated_file_system_message_filter.h new file mode 100644 index 0000000000..bc2996b983 --- /dev/null +++ b/src/browser/pepper/pepper_isolated_file_system_message_filter.h @@ -0,0 +1,81 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_RENDERER_HOST_PEPPER_PEPPER_ISOLATED_FILE_SYSTEM_MESSAGE_FILTER_H_ +#define CHROME_BROWSER_RENDERER_HOST_PEPPER_PEPPER_ISOLATED_FILE_SYSTEM_MESSAGE_FILTER_H_ + +#include +#include + +#include "base/files/file_path.h" +#include "ppapi/c/pp_instance.h" +#include "ppapi/c/pp_resource.h" +#include "ppapi/c/private/ppb_isolated_file_system_private.h" +#include "ppapi/host/resource_host.h" +#include "ppapi/host/resource_message_filter.h" +#include "url/gurl.h" + +class Profile; + +namespace content { +class BrowserPpapiHost; +} + +namespace ppapi { +namespace host { +struct HostMessageContext; +} // namespace host +} // namespace ppapi + +namespace chrome { + +class PepperIsolatedFileSystemMessageFilter + : public ppapi::host::ResourceMessageFilter { + public: + static PepperIsolatedFileSystemMessageFilter* Create( + PP_Instance instance, + content::BrowserPpapiHost* host); + + // ppapi::host::ResourceMessageFilter implementation. + scoped_refptr OverrideTaskRunnerForMessage( + const IPC::Message& msg) override; + int32_t OnResourceMessageReceived( + const IPC::Message& msg, + ppapi::host::HostMessageContext* context) override; + + private: + PepperIsolatedFileSystemMessageFilter(int render_process_id, + const base::FilePath& profile_directory, + const GURL& document_url, + ppapi::host::PpapiHost* ppapi_host_); + + ~PepperIsolatedFileSystemMessageFilter() override; + + Profile* GetProfile(); + + // Returns filesystem id of isolated filesystem if valid, or empty string + // otherwise. This must run on the UI thread because ProfileManager only + // allows access on that thread. + + int32_t OnOpenFileSystem(ppapi::host::HostMessageContext* context, + PP_IsolatedFileSystemType_Private type); + int32_t OpenPluginPrivateFileSystem(ppapi::host::HostMessageContext* context); + + const int render_process_id_; + // Keep a copy from original thread. + const base::FilePath profile_directory_; + const GURL document_url_; + + // Not owned by this object. + ppapi::host::PpapiHost* ppapi_host_; + + // Set of origins that can use CrxFs private APIs from NaCl. + std::set allowed_crxfs_origins_; + + DISALLOW_COPY_AND_ASSIGN(PepperIsolatedFileSystemMessageFilter); +}; + +} // namespace chrome + +#endif // CHROME_BROWSER_RENDERER_HOST_PEPPER_PEPPER_ISOLATED_FILE_SYSTEM_MESSAGE_FILTER_H_ diff --git a/src/browser/popup_controller_common.cc b/src/browser/popup_controller_common.cc new file mode 100644 index 0000000000..e8bfa2029a --- /dev/null +++ b/src/browser/popup_controller_common.cc @@ -0,0 +1,161 @@ +// 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 "content/nw/src/browser/popup_controller_common.h" + +#include +#include + +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "ui/gfx/display.h" +#include "ui/gfx/geometry/rect_conversions.h" +#include "ui/gfx/screen.h" +#include "ui/gfx/geometry/vector2d.h" + +namespace autofill { + +PopupControllerCommon::PopupControllerCommon( + const gfx::RectF& element_bounds, + const gfx::NativeView container_view, + content::WebContents* web_contents) + : element_bounds_(element_bounds), + container_view_(container_view), + web_contents_(web_contents), + key_press_event_target_(NULL) {} +PopupControllerCommon::~PopupControllerCommon() {} + +void PopupControllerCommon::SetKeyPressCallback( + content::RenderWidgetHost::KeyPressEventCallback callback) { + DCHECK(key_press_event_callback_.is_null()); + key_press_event_callback_ = callback; +} + +void PopupControllerCommon::RegisterKeyPressCallback() { + if (web_contents_ && !key_press_event_target_) { + key_press_event_target_ = web_contents_->GetRenderViewHost(); + key_press_event_target_->AddKeyPressEventCallback( + key_press_event_callback_); + } +} + +void PopupControllerCommon::RemoveKeyPressCallback() { + if (web_contents_ && (!web_contents_->IsBeingDestroyed()) && + key_press_event_target_ == web_contents_->GetRenderViewHost()) { + web_contents_->GetRenderViewHost()->RemoveKeyPressEventCallback( + key_press_event_callback_); + } + key_press_event_target_ = NULL; +} + +gfx::Display PopupControllerCommon::GetDisplayNearestPoint( + const gfx::Point& point) const { + return gfx::Screen::GetScreenFor(container_view_)->GetDisplayNearestPoint( + point); +} + +const gfx::Rect PopupControllerCommon::RoundedElementBounds() const { + return gfx::ToEnclosingRect(element_bounds_); +} + +std::pair PopupControllerCommon::CalculatePopupXAndWidth( + const gfx::Display& left_display, + const gfx::Display& right_display, + int popup_required_width) const { + int leftmost_display_x = left_display.bounds().x(); + int rightmost_display_x = + right_display.GetSizeInPixel().width() + right_display.bounds().x(); + + // Calculate the start coordinates for the popup if it is growing right or + // the end position if it is growing to the left, capped to screen space. + int right_growth_start = std::max(leftmost_display_x, + std::min(rightmost_display_x, + RoundedElementBounds().x())); + int left_growth_end = std::max(leftmost_display_x, + std::min(rightmost_display_x, + RoundedElementBounds().right())); + + int right_available = rightmost_display_x - right_growth_start; + int left_available = left_growth_end - leftmost_display_x; + + int popup_width = std::min(popup_required_width, + std::max(right_available, left_available)); + + // If there is enough space for the popup on the right, show it there, + // otherwise choose the larger size. + if (right_available >= popup_width || right_available >= left_available) + return std::make_pair(right_growth_start, popup_width); + else + return std::make_pair(left_growth_end - popup_width, popup_width); +} + +std::pair PopupControllerCommon::CalculatePopupYAndHeight( + const gfx::Display& top_display, + const gfx::Display& bottom_display, + int popup_required_height) const { + int topmost_display_y = top_display.bounds().y(); + int bottommost_display_y = + bottom_display.GetSizeInPixel().height() + bottom_display.bounds().y(); + + // Calculate the start coordinates for the popup if it is growing down or + // the end position if it is growing up, capped to screen space. + int top_growth_end = std::max(topmost_display_y, + std::min(bottommost_display_y, + RoundedElementBounds().y())); + int bottom_growth_start = std::max(topmost_display_y, + std::min(bottommost_display_y, + RoundedElementBounds().bottom())); + + int top_available = bottom_growth_start - topmost_display_y; + int bottom_available = bottommost_display_y - top_growth_end; + + // TODO(csharp): Restrict the popup height to what is available. + if (bottom_available >= popup_required_height || + bottom_available >= top_available) { + // The popup can appear below the field. + return std::make_pair(bottom_growth_start, popup_required_height); + } else { + // The popup must appear above the field. + return std::make_pair(top_growth_end - popup_required_height, + popup_required_height); + } +} + +gfx::Rect PopupControllerCommon::GetPopupBounds(int desired_width, + int desired_height) const { + // This is the top left point of the popup if the popup is above the element + // and grows to the left (since that is the highest and furthest left the + // popup go could). + gfx::Point top_left_corner_of_popup = RoundedElementBounds().origin() + + gfx::Vector2d(RoundedElementBounds().width() - desired_width, + -desired_height); + + // This is the bottom right point of the popup if the popup is below the + // element and grows to the right (since the is the lowest and furthest right + // the popup could go). + gfx::Point bottom_right_corner_of_popup = RoundedElementBounds().origin() + + gfx::Vector2d(desired_width, + RoundedElementBounds().height() + desired_height); + + gfx::Display top_left_display = GetDisplayNearestPoint( + top_left_corner_of_popup); + gfx::Display bottom_right_display = GetDisplayNearestPoint( + bottom_right_corner_of_popup); + + std::pair popup_x_and_width = + CalculatePopupXAndWidth(top_left_display, + bottom_right_display, + desired_width); + std::pair popup_y_and_height = + CalculatePopupYAndHeight(top_left_display, + bottom_right_display, + desired_height); + + return gfx::Rect(popup_x_and_width.first, + popup_y_and_height.first, + popup_x_and_width.second, + popup_y_and_height.second); +} + +} // namespace autofill diff --git a/src/browser/popup_controller_common.h b/src/browser/popup_controller_common.h new file mode 100644 index 0000000000..c111670010 --- /dev/null +++ b/src/browser/popup_controller_common.h @@ -0,0 +1,105 @@ +// 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_UI_AUTOFILL_POPUP_CONTROLLER_COMMON_H_ +#define CHROME_BROWSER_UI_AUTOFILL_POPUP_CONTROLLER_COMMON_H_ + +#include "content/public/browser/render_widget_host.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_f.h" + +namespace content { +struct NativeWebKeyboardEvent; +class RenderViewHost; +class WebContents; +} + +namespace gfx { +class Display; +} + +namespace autofill { + +// Class that controls common functionality for Autofill style popups. Can +// determine the correct location of a popup of a desired size and can register +// a handler to key press events. +class PopupControllerCommon { + public: + PopupControllerCommon(const gfx::RectF& element_bounds, + gfx::NativeView container_view, + content::WebContents* web_contents); + virtual ~PopupControllerCommon(); + + const gfx::RectF& element_bounds() const { return element_bounds_; } + gfx::NativeView container_view() { return container_view_; } + content::WebContents* web_contents() { return web_contents_; } + + // Returns the enclosing rectangle for |element_bounds_|. + const gfx::Rect RoundedElementBounds() const; + + // Returns the bounds that the popup should be placed at, given the desired + // width and height. By default this places the popup below |element_bounds| + // but it will be placed above if there isn't enough space. + gfx::Rect GetPopupBounds(int desired_width, int desired_height) const; + + // Callback used to register with RenderViewHost. This can only be set once, + // or else a callback may be registered that will not be removed + // (crbug.com/338070). Call will crash if callback is already set. + void SetKeyPressCallback(content::RenderWidgetHost::KeyPressEventCallback); + + // Register listener for key press events with the current RenderViewHost + // associated with |web_contents_|. If callback has already been registered, + // this has no effect. + void RegisterKeyPressCallback(); + + // Remove previously registered callback, assuming that the current + // RenderViewHost is the same as when it was originally registered. Safe to + // call even if the callback is not currently registered. + void RemoveKeyPressCallback(); + + protected: + // A helper function to get the display closest to the given point (virtual + // for testing). + virtual gfx::Display GetDisplayNearestPoint(const gfx::Point& point) const; + + private: + // Calculates the width of the popup and the x position of it. These values + // will stay on the screen. + std::pair CalculatePopupXAndWidth( + const gfx::Display& left_display, + const gfx::Display& right_display, + int popup_required_width) const; + + // Calculates the height of the popup and the y position of it. These values + // will stay on the screen. + std::pair CalculatePopupYAndHeight( + const gfx::Display& top_display, + const gfx::Display& bottom_display, + int popup_required_height) const; + + // The bounds of the text element that is the focus of the popup. + // These coordinates are in screen space. + gfx::RectF element_bounds_; + + // Weak reference + gfx::NativeView container_view_; + + // The WebContents in which this object should listen for keyboard events + // while showing the popup. Can be NULL, in which case this object will not + // listen for keyboard events. + content::WebContents* web_contents_; + + // The RenderViewHost that this object has registered its keyboard press + // callback with. + content::RenderViewHost* key_press_event_target_; + + content::RenderWidgetHost::KeyPressEventCallback key_press_event_callback_; + + DISALLOW_COPY_AND_ASSIGN(PopupControllerCommon); +}; + +} // namespace autofill + +#endif // CHROME_BROWSER_UI_AUTOFILL_POPUP_CONTROLLER_COMMON_H_ diff --git a/src/browser/printing/print_dialog_gtk.cc b/src/browser/printing/print_dialog_gtk.cc index d8cb419c49..2d6cdf7c25 100644 --- a/src/browser/printing/print_dialog_gtk.cc +++ b/src/browser/printing/print_dialog_gtk.cc @@ -15,9 +15,10 @@ #include "base/bind.h" #include "base/file_util.h" #include "base/files/file_util_proxy.h" +#include "base/lazy_instance.h" #include "base/logging.h" -#include "base/message_loop_proxy.h" -#include "base/utf_string_conversions.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "printing/metafile.h" #include "printing/print_job_constants.h" @@ -36,6 +37,33 @@ const char kDuplexNone[] = "None"; const char kDuplexTumble[] = "DuplexTumble"; const char kDuplexNoTumble[] = "DuplexNoTumble"; +class StickyPrintSettingGtk { + public: + StickyPrintSettingGtk() : last_used_settings_(gtk_print_settings_new()) { + } + ~StickyPrintSettingGtk() { + NOTREACHED(); // Intended to be used with a Leaky LazyInstance. + } + + GtkPrintSettings* settings() { + return last_used_settings_; + } + + void SetLastUsedSettings(GtkPrintSettings* settings) { + DCHECK(last_used_settings_); + g_object_unref(last_used_settings_); + last_used_settings_ = gtk_print_settings_copy(settings); + } + + private: + GtkPrintSettings* last_used_settings_; + + DISALLOW_COPY_AND_ASSIGN(StickyPrintSettingGtk); +}; + +base::LazyInstance::Leaky g_last_used_settings = + LAZY_INSTANCE_INITIALIZER; + // Helper class to track GTK printers. class GtkPrinterList { public: @@ -59,13 +87,13 @@ class GtkPrinterList { // Can return NULL if the printer cannot be found due to: // - Printer list out of sync with printer dialog UI. // - Querying for non-existant printers like 'Print to PDF'. - GtkPrinter* GetPrinterWithName(const char* name) { - if (!name || !*name) + GtkPrinter* GetPrinterWithName(const std::string& name) { + if (name.empty()) return NULL; for (std::vector::iterator it = printers_.begin(); it < printers_.end(); ++it) { - if (strcmp(name, gtk_printer_get_name(*it)) == 0) { + if (gtk_printer_get_name(*it) == name) { return *it; } } @@ -76,7 +104,7 @@ class GtkPrinterList { private: // Callback function used by gtk_enumerate_printers() to get all printer. static gboolean SetPrinter(GtkPrinter* printer, gpointer data) { - GtkPrinterList *printer_list = (GtkPrinterList*)data; + GtkPrinterList* printer_list = reinterpret_cast(data); if (gtk_printer_is_default(printer)) printer_list->default_printer_ = printer; @@ -94,12 +122,12 @@ class GtkPrinterList { // static printing::PrintDialogGtkInterface* PrintDialogGtk::CreatePrintDialog( - PrintingContextGtk* context) { + PrintingContextLinux* context) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); return new PrintDialogGtk(context); } -PrintDialogGtk::PrintDialogGtk(PrintingContextGtk* context) +PrintDialogGtk::PrintDialogGtk(PrintingContextLinux* context) : context_(context), dialog_(NULL), gtk_settings_(NULL), @@ -137,95 +165,74 @@ void PrintDialogGtk::UseDefaultSettings() { page_setup_ = gtk_page_setup_new(); // No page range to initialize for default settings. - PageRanges ranges_vector; PrintSettings settings; - InitPrintSettings(ranges_vector, &settings); + InitPrintSettings(&settings); } -bool PrintDialogGtk::UpdateSettings(const base::DictionaryValue& job_settings, - const printing::PageRanges& ranges, - printing::PrintSettings* settings) { - bool collate; - int color; - bool landscape; - bool print_to_pdf; - int copies; - int duplex_mode; - std::string device_name; - - if (!job_settings.GetBoolean(printing::kSettingLandscape, &landscape) || - !job_settings.GetBoolean(printing::kSettingCollate, &collate) || - !job_settings.GetInteger(printing::kSettingColor, &color) || - !job_settings.GetBoolean(printing::kSettingPrintToPDF, &print_to_pdf) || - !job_settings.GetInteger(printing::kSettingDuplexMode, &duplex_mode) || - !job_settings.GetInteger(printing::kSettingCopies, &copies) || - !job_settings.GetString(printing::kSettingDeviceName, &device_name)) { - return false; +bool PrintDialogGtk::UpdateSettings(printing::PrintSettings* settings) { + if (!gtk_settings_) { + gtk_settings_ = + gtk_print_settings_copy(g_last_used_settings.Get().settings()); } - bool is_cloud_print = job_settings.HasKey(printing::kSettingCloudPrintId); - - if (!gtk_settings_) - gtk_settings_ = gtk_print_settings_new(); - - if (!print_to_pdf && !is_cloud_print) { - scoped_ptr printer_list(new GtkPrinterList); - printer_ = printer_list->GetPrinterWithName(device_name.c_str()); - if (printer_) { - g_object_ref(printer_); - gtk_print_settings_set_printer(gtk_settings_, - gtk_printer_get_name(printer_)); - if (!page_setup_) { - page_setup_ = gtk_printer_get_default_page_size(printer_); - } + scoped_ptr printer_list(new GtkPrinterList); + printer_ = printer_list->GetPrinterWithName( + UTF16ToUTF8(settings->device_name())); + if (printer_) { + g_object_ref(printer_); + gtk_print_settings_set_printer(gtk_settings_, + gtk_printer_get_name(printer_)); + if (!page_setup_) { + page_setup_ = gtk_printer_get_default_page_size(printer_); } + } - gtk_print_settings_set_n_copies(gtk_settings_, copies); - gtk_print_settings_set_collate(gtk_settings_, collate); + gtk_print_settings_set_n_copies(gtk_settings_, settings->copies()); + gtk_print_settings_set_collate(gtk_settings_, settings->collate()); #if defined(USE_CUPS) - std::string color_value; - std::string color_setting_name; - printing::GetColorModelForMode(color, &color_setting_name, &color_value); - gtk_print_settings_set(gtk_settings_, color_setting_name.c_str(), - color_value.c_str()); - - if (duplex_mode != printing::UNKNOWN_DUPLEX_MODE) { - const char* cups_duplex_mode = NULL; - switch (duplex_mode) { - case printing::LONG_EDGE: - cups_duplex_mode = kDuplexNoTumble; - break; - case printing::SHORT_EDGE: - cups_duplex_mode = kDuplexTumble; - break; - case printing::SIMPLEX: - cups_duplex_mode = kDuplexNone; - break; - default: // UNKNOWN_DUPLEX_MODE - NOTREACHED(); - break; - } - gtk_print_settings_set(gtk_settings_, kCUPSDuplex, cups_duplex_mode); + std::string color_value; + std::string color_setting_name; + printing::GetColorModelForMode(settings->color(), &color_setting_name, + &color_value); + gtk_print_settings_set(gtk_settings_, color_setting_name.c_str(), + color_value.c_str()); + + if (settings->duplex_mode() != printing::UNKNOWN_DUPLEX_MODE) { + const char* cups_duplex_mode = NULL; + switch (settings->duplex_mode()) { + case printing::LONG_EDGE: + cups_duplex_mode = kDuplexNoTumble; + break; + case printing::SHORT_EDGE: + cups_duplex_mode = kDuplexTumble; + break; + case printing::SIMPLEX: + cups_duplex_mode = kDuplexNone; + break; + default: // UNKNOWN_DUPLEX_MODE + NOTREACHED(); + break; } -#endif + gtk_print_settings_set(gtk_settings_, kCUPSDuplex, cups_duplex_mode); } +#endif if (!page_setup_) page_setup_ = gtk_page_setup_new(); gtk_print_settings_set_orientation( gtk_settings_, - landscape ? GTK_PAGE_ORIENTATION_LANDSCAPE : - GTK_PAGE_ORIENTATION_PORTRAIT); + settings->landscape() ? GTK_PAGE_ORIENTATION_LANDSCAPE : + GTK_PAGE_ORIENTATION_PORTRAIT); - InitPrintSettings(ranges, settings); + InitPrintSettings(settings); return true; } void PrintDialogGtk::ShowDialog( gfx::NativeView parent_view, bool has_selection, - const PrintingContextGtk::PrintSettingsCallback& callback) { + const PrintingContextLinux::PrintSettingsCallback& callback) { callback_ = callback; GtkWindow* parent = GTK_WINDOW(gtk_widget_get_toplevel(parent_view)); @@ -259,7 +266,7 @@ void PrintDialogGtk::ShowDialog( } void PrintDialogGtk::PrintDocument(const printing::Metafile* metafile, - const string16& document_name) { + const base::string16& document_name) { // This runs on the print worker thread, does not block the UI thread. DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); @@ -268,14 +275,14 @@ void PrintDialogGtk::PrintDocument(const printing::Metafile* metafile, AddRef(); bool error = false; - if (!file_util::CreateTemporaryFile(&path_to_pdf_)) { + if (!base::CreateTemporaryFile(&path_to_pdf_)) { LOG(ERROR) << "Creating temporary file failed"; error = true; } if (!error && !metafile->SaveTo(path_to_pdf_)) { LOG(ERROR) << "Saving metafile failed"; - file_util::Delete(path_to_pdf_, false); + base::DeleteFile(path_to_pdf_, false); error = true; } @@ -353,17 +360,18 @@ void PrintDialogGtk::OnResponse(GtkWidget* dialog, int response_id) { } PrintSettings settings; + settings.set_ranges(ranges_vector); + settings.set_selection_only(print_selection_only); printing::PrintSettingsInitializerGtk::InitPrintSettings( - gtk_settings_, page_setup_, ranges_vector, print_selection_only, - &settings); + gtk_settings_, page_setup_, &settings); context_->InitWithSettings(settings); - callback_.Run(PrintingContextGtk::OK); + callback_.Run(PrintingContextLinux::OK); callback_.Reset(); return; } case GTK_RESPONSE_DELETE_EVENT: // Fall through. case GTK_RESPONSE_CANCEL: { - callback_.Run(PrintingContextGtk::CANCEL); + callback_.Run(PrintingContextLinux::CANCEL); callback_.Reset(); return; } @@ -374,7 +382,7 @@ void PrintDialogGtk::OnResponse(GtkWidget* dialog, int response_id) { } } -void PrintDialogGtk::SendDocumentToPrinter(const string16& document_name) { +void PrintDialogGtk::SendDocumentToPrinter(const base::string16& document_name) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); // If |printer_| is NULL then somehow the GTK printer list changed out under @@ -406,16 +414,15 @@ void PrintDialogGtk::OnJobCompleted(GtkPrintJob* print_job, GError* error) { LOG(ERROR) << "Printing failed: " << error->message; if (print_job) g_object_unref(print_job); - base::FileUtilProxy::Delete( - BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE), + base::FileUtilProxy::DeleteFile( + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE).get(), path_to_pdf_, false, base::FileUtilProxy::StatusCallback()); // Printing finished. Matches AddRef() in PrintDocument(); Release(); } -void PrintDialogGtk::InitPrintSettings(const PageRanges& page_ranges, - PrintSettings* settings) { +void PrintDialogGtk::InitPrintSettings(PrintSettings* settings) { printing::PrintSettingsInitializerGtk::InitPrintSettings( - gtk_settings_, page_setup_, page_ranges, false, settings); + gtk_settings_, page_setup_, settings); context_->InitWithSettings(*settings); } diff --git a/src/browser/printing/print_dialog_gtk.h b/src/browser/printing/print_dialog_gtk.h index 06a14c79be..e6fe453e41 100644 --- a/src/browser/printing/print_dialog_gtk.h +++ b/src/browser/printing/print_dialog_gtk.h @@ -14,7 +14,7 @@ #include "base/sequenced_task_runner_helpers.h" #include "content/public/browser/browser_thread.h" #include "printing/print_dialog_gtk_interface.h" -#include "printing/printing_context_gtk.h" +#include "printing/printing_context_linux.h" #include "ui/base/gtk/gtk_signal.h" namespace printing { @@ -22,7 +22,7 @@ class Metafile; class PrintSettings; } -using printing::PrintingContextGtk; +using printing::PrintingContextLinux; // Needs to be freed on the UI thread to clean up its GTK members variables. class PrintDialogGtk @@ -32,19 +32,19 @@ class PrintDialogGtk public: // Creates and returns a print dialog. static printing::PrintDialogGtkInterface* CreatePrintDialog( - PrintingContextGtk* context); + PrintingContextLinux* context); // printing::PrintDialogGtkInterface implementation. virtual void UseDefaultSettings() OVERRIDE; virtual bool UpdateSettings(const base::DictionaryValue& job_settings, const printing::PageRanges& ranges, - printing::PrintSettings* settings) OVERRIDE; + printing::PrintSettings* settings) ; //FIXME: override virtual void ShowDialog( gfx::NativeView parent_view, bool has_selection, - const PrintingContextGtk::PrintSettingsCallback& callback) OVERRIDE; + const PrintingContextLinux::PrintSettingsCallback& callback) OVERRIDE; virtual void PrintDocument(const printing::Metafile* metafile, - const string16& document_name) OVERRIDE; + const base::string16& document_name) OVERRIDE; virtual void AddRefToDialog() OVERRIDE; virtual void ReleaseDialog() OVERRIDE; @@ -53,14 +53,14 @@ class PrintDialogGtk content::BrowserThread::UI>; friend class base::DeleteHelper; - explicit PrintDialogGtk(PrintingContextGtk* context); + explicit PrintDialogGtk(PrintingContextLinux* context); virtual ~PrintDialogGtk(); // Handles dialog response. CHROMEGTK_CALLBACK_1(PrintDialogGtk, void, OnResponse, int); // Prints document named |document_name|. - void SendDocumentToPrinter(const string16& document_name); + void SendDocumentToPrinter(const base::string16& document_name); // Handles print job response. static void OnJobCompletedThunk(GtkPrintJob* print_job, @@ -74,8 +74,8 @@ class PrintDialogGtk printing::PrintSettings* settings); // Printing dialog callback. - PrintingContextGtk::PrintSettingsCallback callback_; - PrintingContextGtk* context_; + PrintingContextLinux::PrintSettingsCallback callback_; + PrintingContextLinux* context_; // Print dialog settings. PrintDialogGtk owns |dialog_| and holds references // to the other objects. diff --git a/src/browser/printing/print_job.cc b/src/browser/printing/print_job.cc index 818836cba1..7485983945 100644 --- a/src/browser/printing/print_job.cc +++ b/src/browser/printing/print_job.cc @@ -6,16 +6,19 @@ #include "base/bind.h" #include "base/bind_helpers.h" -#include "base/message_loop.h" +#include "base/message_loop/message_loop.h" #include "base/threading/thread_restrictions.h" -#include "base/timer.h" +#include "base/threading/worker_pool.h" +#include "base/timer/timer.h" +#include "content/public/browser/browser_thread.h" #include "content/nw/src/browser/printing/print_job_worker.h" -#include "content/public/browser/notification_service.h" #include "content/public/browser/notification_types.h" +#include "content/public/browser/notification_service.h" #include "printing/printed_document.h" #include "printing/printed_page.h" using base::TimeDelta; +using base::MessageLoop; namespace { @@ -36,7 +39,7 @@ PrintJob::PrintJob() settings_(), is_job_pending_(false), is_canceling_(false), - ALLOW_THIS_IN_INITIALIZER_LIST(quit_factory_(this)) { + quit_factory_(this) { DCHECK(ui_message_loop_); // This is normally a UI message loop, but in unit tests, the message loop is // of the 'default' type. @@ -68,7 +71,8 @@ void PrintJob::Initialize(PrintJobWorkerOwner* job, settings_ = job->settings(); PrintedDocument* new_doc = - new PrintedDocument(settings_, source_, job->cookie()); + new PrintedDocument(settings_, source_, job->cookie(), + content::BrowserThread::GetBlockingPool()); new_doc->set_page_count(page_count); UpdatePrintedDocument(new_doc); @@ -159,16 +163,12 @@ void PrintJob::Stop() { // Be sure to live long enough. scoped_refptr handle(this); - MessageLoop* worker_loop = worker_->message_loop(); - if (worker_loop) { + if (worker_->message_loop()) { ControlledWorkerShutdown(); - - is_job_pending_ = false; - registrar_.Remove(this, content::NOTIFICATION_PRINT_JOB_EVENT, - content::Source(this)); + } else { + // Flush the cached document. + UpdatePrintedDocument(NULL); } - // Flush the cached document. - UpdatePrintedDocument(NULL); } void PrintJob::Cancel() { @@ -313,41 +313,32 @@ void PrintJob::ControlledWorkerShutdown() { // deadlock is eliminated. worker_->StopSoon(); - // Run a tight message loop until the worker terminates. It may seems like a - // hack but I see no other way to get it to work flawlessly. The issues here - // are: - // - We don't want to run tasks while the thread is quitting. - // - We want this code path to wait on the thread to quit before continuing. - MSG msg; - HANDLE thread_handle = worker_->thread_handle(); - for (; thread_handle;) { - // Note that we don't do any kind of message prioritization since we don't - // execute any pending task or timer. - DWORD result = MsgWaitForMultipleObjects(1, &thread_handle, - FALSE, INFINITE, QS_ALLINPUT); - if (result == WAIT_OBJECT_0 + 1) { - while (PeekMessage(&msg, NULL, 0, 0, TRUE) > 0) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - // Continue looping. - } else if (result == WAIT_OBJECT_0) { - // The thread quit. - break; - } else { - // An error occurred. Assume the thread quit. - NOTREACHED(); - break; - } + // Delay shutdown until the worker terminates. We want this code path + // to wait on the thread to quit before continuing. + if (worker_->IsRunning()) { + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&PrintJob::ControlledWorkerShutdown, this), + base::TimeDelta::FromMilliseconds(100)); + return; } #endif - // Temporarily allow it until we fix - // http://code.google.com/p/chromium/issues/detail?id=67044 - base::ThreadRestrictions::ScopedAllowIO allow_io; - // Now make sure the thread object is cleaned up. - worker_->Stop(); + // Now make sure the thread object is cleaned up. Do this on a worker + // thread because it may block. + base::WorkerPool::PostTaskAndReply( + FROM_HERE, + base::Bind(&PrintJobWorker::Stop, base::Unretained(worker_.get())), + base::Bind(&PrintJob::HoldUntilStopIsCalled, this), + false); + + is_job_pending_ = false; + registrar_.RemoveAll(); + UpdatePrintedDocument(NULL); +} + +void PrintJob::HoldUntilStopIsCalled() { } void PrintJob::Quit() { @@ -366,12 +357,8 @@ JobEventDetails::JobEventDetails(Type type, JobEventDetails::~JobEventDetails() { } -PrintedDocument* JobEventDetails::document() const { - return document_; -} +PrintedDocument* JobEventDetails::document() const { return document_.get(); } -PrintedPage* JobEventDetails::page() const { - return page_; -} +PrintedPage* JobEventDetails::page() const { return page_.get(); } } // namespace printing diff --git a/src/browser/printing/print_job.h b/src/browser/printing/print_job.h index 1a34ec4625..6b20bf40ba 100644 --- a/src/browser/printing/print_job.h +++ b/src/browser/printing/print_job.h @@ -8,8 +8,8 @@ #include "base/basictypes.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" -#include "base/message_loop.h" -#include "content/nw/src/browser/printing/print_job_worker_owner.h" +#include "base/message_loop/message_loop.h" +#include "chrome/browser/printing/print_job_worker_owner.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" @@ -34,7 +34,7 @@ class PrinterQuery; // runs in the UI thread. class PrintJob : public PrintJobWorkerOwner, public content::NotificationObserver, - public MessageLoop::DestructionObserver { + public base::MessageLoop::DestructionObserver { public: // Create a empty PrintJob. When initializing with this constructor, // post-constructor initialization must be done with Initialize(). @@ -54,7 +54,7 @@ class PrintJob : public PrintJobWorkerOwner, virtual void GetSettingsDone(const PrintSettings& new_settings, PrintingContext::Result result) OVERRIDE; virtual PrintJobWorker* DetachWorker(PrintJobWorkerOwner* new_owner) OVERRIDE; - virtual MessageLoop* message_loop() OVERRIDE; + virtual base::MessageLoop* message_loop() OVERRIDE; virtual const PrintSettings& settings() const OVERRIDE; virtual int cookie() const OVERRIDE; @@ -65,10 +65,11 @@ class PrintJob : public PrintJobWorkerOwner, // spool as soon as data is available. void StartPrinting(); - // Waits for the worker thread to finish its queued tasks and disconnects the - // delegate object. The PrintJobManager will remove it reference. This may + // Asks for the worker thread to finish its queued tasks and disconnects the + // delegate object. The PrintJobManager will remove its reference. This may // have the side-effect of destroying the object if the caller doesn't have a - // handle to the object. + // handle to the object. Use PrintJob::is_stopped() to check whether the + // worker thread has actually stopped. void Stop(); // Cancels printing job and stops the worker thread. Takes effect immediately. @@ -111,11 +112,13 @@ class PrintJob : public PrintJobWorkerOwner, // Called at shutdown when running a nested message loop. void Quit(); + void HoldUntilStopIsCalled(); + content::NotificationRegistrar registrar_; // Main message loop reference. Used to send notifications in the right // thread. - MessageLoop* const ui_message_loop_; + base::MessageLoop* const ui_message_loop_; // Source that generates the PrintedPage's (i.e. a WebContents). It will be // set back to NULL if the source is deleted before this object. diff --git a/src/browser/printing/print_job_worker.cc b/src/browser/printing/print_job_worker.cc index 1a3b3abddf..7da21ca269 100644 --- a/src/browser/printing/print_job_worker.cc +++ b/src/browser/printing/print_job_worker.cc @@ -8,22 +8,25 @@ #include "base/bind_helpers.h" #include "base/callback.h" #include "base/compiler_specific.h" -#include "base/message_loop.h" +#include "base/message_loop/message_loop.h" #include "base/values.h" -//#include "chrome/browser/browser_process.h" #include "content/nw/src/browser/printing/print_job.h" +#include "chrome/browser/printing/printing_ui_web_contents_observer.h" +#include "content/public/browser/notification_types.h" #include "content/public/browser/browser_thread.h" -#include "content/public/browser/content_browser_client.h" #include "content/public/browser/notification_service.h" -#include "content/public/browser/notification_types.h" +#include "content/public/browser/content_browser_client.h" +#include "content/public/common/content_client.h" #include "grit/nw_resources.h" #include "printing/backend/print_backend.h" #include "printing/print_job_constants.h" #include "printing/printed_document.h" #include "printing/printed_page.h" +#include "printing/printing_utils.h" #include "ui/base/l10n/l10n_util.h" using content::BrowserThread; +using base::MessageLoop; namespace { @@ -52,12 +55,12 @@ void NotificationCallback(PrintJobWorkerOwner* print_job, PrintJobWorker::PrintJobWorker(PrintJobWorkerOwner* owner) : Thread("Printing_Worker"), owner_(owner), - ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { + weak_factory_(this) { // The object is created in the IO thread. DCHECK_EQ(owner_->message_loop(), MessageLoop::current()); printing_context_.reset(PrintingContext::Create( - content::GetContentClient()->browser()->GetApplicationLocale())); + content::GetContentClient()->browser()->GetApplicationLocale())); } PrintJobWorker::~PrintJobWorker() { @@ -78,11 +81,12 @@ void PrintJobWorker::SetPrintDestination( destination_ = destination; } -void PrintJobWorker::GetSettings(bool ask_user_for_settings, - gfx::NativeView parent_view, - int document_page_count, - bool has_selection, - MarginType margin_type) { +void PrintJobWorker::GetSettings( + bool ask_user_for_settings, + scoped_ptr web_contents_observer, + int document_page_count, + bool has_selection, + MarginType margin_type) { DCHECK_EQ(message_loop(), MessageLoop::current()); DCHECK_EQ(page_number_, PageNumber::npos()); @@ -101,8 +105,10 @@ void PrintJobWorker::GetSettings(bool ask_user_for_settings, BrowserThread::UI, FROM_HERE, base::Bind(&HoldRefCallback, make_scoped_refptr(owner_), base::Bind(&PrintJobWorker::GetSettingsWithUI, - base::Unretained(this), parent_view, - document_page_count, has_selection))); + base::Unretained(this), + base::Passed(&web_contents_observer), + document_page_count, + has_selection))); } else { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, @@ -112,7 +118,7 @@ void PrintJobWorker::GetSettings(bool ask_user_for_settings, } } -void PrintJobWorker::SetSettings(const DictionaryValue* const new_settings) { +void PrintJobWorker::SetSettings(const base::DictionaryValue* const new_settings) { DCHECK_EQ(message_loop(), MessageLoop::current()); BrowserThread::PostTask( @@ -123,32 +129,9 @@ void PrintJobWorker::SetSettings(const DictionaryValue* const new_settings) { } void PrintJobWorker::UpdatePrintSettings( - const DictionaryValue* const new_settings) { - // Create new PageRanges based on |new_settings|. - PageRanges new_ranges; - const ListValue* page_range_array; - if (new_settings->GetList(kSettingPageRange, &page_range_array)) { - for (size_t index = 0; index < page_range_array->GetSize(); ++index) { - const DictionaryValue* dict; - if (!page_range_array->GetDictionary(index, &dict)) - continue; - - PageRange range; - if (!dict->GetInteger(kSettingPageRangeFrom, &range.from) || - !dict->GetInteger(kSettingPageRangeTo, &range.to)) { - continue; - } - - // Page numbers are 1-based in the dictionary. - // Page numbers are 0-based for the printing context. - range.from--; - range.to--; - new_ranges.push_back(range); - } - } + const base::DictionaryValue* const new_settings) { PrintingContext::Result result = - printing_context_->UpdatePrintSettings(*new_settings, new_ranges); - delete new_settings; + printing_context_->UpdatePrintSettings(*new_settings); GetSettingsDone(result); } @@ -170,11 +153,17 @@ void PrintJobWorker::GetSettingsDone(PrintingContext::Result result) { result)); } -void PrintJobWorker::GetSettingsWithUI(gfx::NativeView parent_view, - int document_page_count, - bool has_selection) { +void PrintJobWorker::GetSettingsWithUI( + scoped_ptr web_contents_observer, + int document_page_count, + bool has_selection) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + gfx::NativeView parent_view = web_contents_observer->GetParentView(); + if (!parent_view) { + GetSettingsWithUIDone(printing::PrintingContext::FAILED); + return; + } printing_context_->AskUserForSettings( parent_view, document_page_count, has_selection, base::Bind(&PrintJobWorker::GetSettingsWithUIDone, @@ -199,17 +188,16 @@ void PrintJobWorker::StartPrinting(PrintedDocument* new_document) { DCHECK_EQ(page_number_, PageNumber::npos()); DCHECK_EQ(document_, new_document); DCHECK(document_.get()); - DCHECK(new_document->settings().Equals(printing_context_->settings())); if (!document_.get() || page_number_ != PageNumber::npos() || document_ != new_document) { return; } - string16 document_name = - printing::PrintBackend::SimplifyDocumentTitle(document_->name()); + base::string16 document_name = + printing::SimplifyDocumentTitle(document_->name()); if (document_name.empty()) { - document_name = printing::PrintBackend::SimplifyDocumentTitle( + document_name = printing::SimplifyDocumentTitle( l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE)); } PrintingContext::Result result = @@ -231,8 +219,6 @@ void PrintJobWorker::StartPrinting(PrintedDocument* new_document) { void PrintJobWorker::OnDocumentChanged(PrintedDocument* new_document) { DCHECK_EQ(message_loop(), MessageLoop::current()); DCHECK_EQ(page_number_, PageNumber::npos()); - DCHECK(!new_document || - new_document->settings().Equals(printing_context_->settings())); if (page_number_ != PageNumber::npos()) return; @@ -265,8 +251,8 @@ void PrintJobWorker::OnNewPage() { while (true) { // Is the page available? - scoped_refptr page; - if (!document_->GetPage(page_number_.ToInt(), &page)) { + scoped_refptr page = document_->GetPage(page_number_.ToInt()); + if (!page) { // We need to wait for the page to be available. MessageLoop::current()->PostDelayedTask( FROM_HERE, diff --git a/src/browser/printing/print_job_worker.h b/src/browser/printing/print_job_worker.h index 2b639fafd3..b31fef4ea8 100644 --- a/src/browser/printing/print_job_worker.h +++ b/src/browser/printing/print_job_worker.h @@ -15,6 +15,8 @@ #include "printing/print_job_constants.h" #include "ui/gfx/native_widget_types.h" +class PrintingUIWebContentsObserver; + namespace base { class DictionaryValue; } @@ -45,11 +47,12 @@ class PrintJobWorker : public base::Thread { // Initializes the print settings. If |ask_user_for_settings| is true, a // Print... dialog box will be shown to ask the user his preference. - void GetSettings(bool ask_user_for_settings, - gfx::NativeView parent_view, - int document_page_count, - bool has_selection, - MarginType margin_type); + void GetSettings( + bool ask_user_for_settings, + scoped_ptr web_contents_observer, + int document_page_count, + bool has_selection, + MarginType margin_type); // Set the new print settings. This function takes ownership of // |new_settings|. diff --git a/src/browser/printing/print_job_worker_owner.h b/src/browser/printing/print_job_worker_owner.h index 7224fd33bf..00a561a39b 100644 --- a/src/browser/printing/print_job_worker_owner.h +++ b/src/browser/printing/print_job_worker_owner.h @@ -8,7 +8,14 @@ #include "base/memory/ref_counted.h" #include "printing/printing_context.h" +namespace base { class MessageLoop; +class SequencedTaskRunner; +} + +namespace tracked_objects { +class Location; +} namespace printing { @@ -18,6 +25,8 @@ class PrintSettings; class PrintJobWorkerOwner : public base::RefCountedThreadSafe { public: + PrintJobWorkerOwner(); + // Finishes the initialization began by PrintJobWorker::GetSettings(). // Creates a new PrintedDocument if necessary. Solely meant to be called by // PrintJobWorker. @@ -27,19 +36,29 @@ class PrintJobWorkerOwner // Detach the PrintJobWorker associated to this object. virtual PrintJobWorker* DetachWorker(PrintJobWorkerOwner* new_owner) = 0; - // Retrieves the message loop that is expected to process GetSettingsDone. - virtual MessageLoop* message_loop() = 0; - // Access the current settings. virtual const PrintSettings& settings() const = 0; // Cookie uniquely identifying the PrintedDocument and/or loaded settings. virtual int cookie() const = 0; + // Returns true if the current thread is a thread on which a task + // may be run, and false if no task will be run on the current + // thread. + bool RunsTasksOnCurrentThread() const; + + // Posts the given task to be run. + bool PostTask(const tracked_objects::Location& from_here, + const base::Closure& task); + protected: friend class base::RefCountedThreadSafe; - virtual ~PrintJobWorkerOwner() {} + virtual ~PrintJobWorkerOwner(); + + // Task runner reference. Used to send notifications in the right + // thread. + scoped_refptr task_runner_; }; } // namespace printing diff --git a/src/browser/printing/print_view_manager.cc b/src/browser/printing/print_view_manager.cc index e377f4bd30..60a7c451fb 100644 --- a/src/browser/printing/print_view_manager.cc +++ b/src/browser/printing/print_view_manager.cc @@ -12,10 +12,10 @@ #include "base/memory/scoped_ptr.h" #include "base/metrics/histogram.h" #include "base/prefs/pref_service.h" -#include "base/timer.h" -#include "base/utf_string_conversions.h" -#include "chrome/browser/browser_process.h" -//#include "content/nw/src/browser/printing/print_error_dialog.h" +#include "base/timer/timer.h" +#include "base/strings/utf_string_conversions.h" +//#include "chrome/browser/browser_process.h" +//#include "chrome/browser/printing/print_error_dialog.h" #include "content/nw/src/browser/printing/print_job.h" #include "content/nw/src/browser/printing/print_job_manager.h" //#include "content/nw/src/browser/printing/print_preview_dialog_controller.h" @@ -23,18 +23,18 @@ #include "content/nw/src/browser/printing/printer_query.h" //#include "chrome/browser/profiles/profile.h" //#include "chrome/browser/ui/webui/print_preview/print_preview_ui.h" +#include "content/nw/src/shell_content_browser_client.h" +#include "content/public/browser/notification_types.h" //#include "chrome/common/chrome_switches.h" //#include "chrome/common/pref_names.h" -#include "content/nw/src/shell_content_browser_client.h" #include "content/nw/src/common/print_messages.h" +#include "content/nw/src/common/shell_switches.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_source.h" -#include "content/public/browser/notification_types.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" -#include "content/public/browser/web_contents_view.h" #include "grit/nw_resources.h" #include "printing/metafile.h" #include "printing/metafile_impl.h" @@ -42,11 +42,8 @@ #include "printing/printed_document.h" #include "ui/base/l10n/l10n_util.h" -#if defined(OS_WIN) -#include "content/nw/src/common/shell_switches.h" -#endif - using base::TimeDelta; +using base::MessageLoop; using content::BrowserThread; using content::WebContents; @@ -54,13 +51,10 @@ DEFINE_WEB_CONTENTS_USER_DATA_KEY(printing::PrintViewManager); namespace { -// Keeps track of pending scripted print preview closures. -// No locking, only access on the UI thread. -typedef std::map - ScriptedPrintPreviewClosureMap; - +#if defined(OS_WIN) && !defined(WIN_PDF_METAFILE_FOR_PRINTING) // Limits memory usage by raster to 64 MiB. const int kMaxRasterSizeInPixels = 16*1024*1024; +#endif } // namespace @@ -96,6 +90,10 @@ bool PrintViewManager::PrintForSystemDialogNow() { return PrintNowInternal(new PrintMsg_PrintForSystemDialog(routing_id())); } +// bool PrintViewManager::AdvancedPrintNow() { +// return PrintNow(); +// } + bool PrintViewManager::PrintToDestination() { // TODO(mad): Remove this once we can send user metrics from the metro driver. // crbug.com/142330 @@ -103,7 +101,7 @@ bool PrintViewManager::PrintToDestination() { // TODO(mad): Use a passed in destination interface instead. content::ShellContentBrowserClient* browser_client = static_cast(content::GetContentClient()->browser()); - browser_client->print_job_manager()->SetPrintDestination( + browser_client->print_job_manager()->SetPrintDestination( printing::CreatePrintDestination()); return PrintNowInternal(new PrintMsg_PrintPages(routing_id())); } @@ -119,12 +117,12 @@ void PrintViewManager::set_observer(PrintViewManagerObserver* observer) { observer_ = observer; } -void PrintViewManager::StopNavigation() { +void PrintViewManager::NavigationStopped() { // Cancel the current job, wait for the worker to finish. TerminatePrintJob(true); } -void PrintViewManager::RenderViewGone(base::TerminationStatus status) { +void PrintViewManager::RenderProcessGone(base::TerminationStatus status) { ReleasePrinterQuery(); if (!print_job_.get()) @@ -139,8 +137,8 @@ void PrintViewManager::RenderViewGone(base::TerminationStatus status) { } } -string16 PrintViewManager::RenderSourceName() { - string16 name(web_contents()->GetTitle()); +base::string16 PrintViewManager::RenderSourceName() { + base::string16 name(web_contents()->GetTitle()); if (name.empty()) name = l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE); return name; @@ -199,12 +197,12 @@ void PrintViewManager::OnDidPrintPage( } } -#if defined(OS_WIN) +#if defined(OS_WIN) && !defined(WIN_PDF_METAFILE_FOR_PRINTING) bool big_emf = (params.data_size && params.data_size >= kMetafileMaxSize); const CommandLine* cmdline = CommandLine::ForCurrentProcess(); int raster_size = std::min(params.page_size.GetArea(), kMaxRasterSizeInPixels); - if (big_emf || (cmdline && cmdline->HasSwitch(switches::kPrintRaster))) { + if (big_emf) { scoped_ptr raster_metafile( metafile->RasterizeMetafile(raster_size)); if (raster_metafile.get()) { @@ -222,7 +220,9 @@ void PrintViewManager::OnDidPrintPage( // Update the rendered document. It will send notifications to the listener. document->SetPage(params.page_number, metafile.release(), +#if defined(OS_WIN) params.actual_shrink, +#endif params.page_size, params.content_area); @@ -235,10 +235,6 @@ void PrintViewManager::OnPrintingFailed(int cookie) { return; } - // FIXME - // chrome::ShowPrintErrorDialog( - // web_contents()->GetView()->GetTopLevelNativeWindow()); - ReleasePrinterQuery(); content::NotificationService::current()->Notify( @@ -271,8 +267,6 @@ bool PrintViewManager::OnMessageReceived(const IPC::Message& message) { IPC_MESSAGE_HANDLER(PrintHostMsg_DidShowPrintDialog, OnDidShowPrintDialog) IPC_MESSAGE_HANDLER(PrintHostMsg_DidPrintPage, OnDidPrintPage) IPC_MESSAGE_HANDLER(PrintHostMsg_PrintingFailed, OnPrintingFailed) - IPC_MESSAGE_HANDLER_DELAY_REPLY(PrintHostMsg_ScriptedPrintPreview, - OnScriptedPrintPreview) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; @@ -534,8 +528,7 @@ bool PrintViewManager::OpportunisticallyCreatePrintJob(int cookie) { scoped_refptr queued_query; content::ShellContentBrowserClient* browser_client = static_cast(content::GetContentClient()->browser()); - browser_client->print_job_manager()->PopPrinterQuery(cookie, - &queued_query); + browser_client->print_job_manager()->PopPrinterQuery(cookie, &queued_query); DCHECK(queued_query.get()); if (!queued_query.get()) return false; @@ -566,13 +559,13 @@ void PrintViewManager::ReleasePrinterQuery() { int cookie = cookie_; cookie_ = 0; - content::ShellContentBrowserClient* browser_client = static_cast(content::GetContentClient()->browser()); browser_client->print_job_manager()->SetPrintDestination(NULL); + printing::PrintJobManager* print_job_manager = - browser_client->print_job_manager(); + browser_client->print_job_manager(); // May be NULL in tests. if (!print_job_manager) return; diff --git a/src/browser/printing/print_view_manager.h b/src/browser/printing/print_view_manager.h index 0c412d5886..f4640bcb95 100644 --- a/src/browser/printing/print_view_manager.h +++ b/src/browser/printing/print_view_manager.h @@ -6,8 +6,8 @@ #define CHROME_BROWSER_PRINTING_PRINT_VIEW_MANAGER_H_ #include "base/memory/ref_counted.h" -#include "base/prefs/public/pref_member.h" -#include "base/string16.h" +#include "base/prefs/pref_member.h" +#include "base/strings/string16.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" #include "content/public/browser/web_contents_observer.h" @@ -59,25 +59,25 @@ class PrintViewManager : public content::NotificationObserver, void set_observer(PrintViewManagerObserver* observer); // PrintedPagesSource implementation. - virtual string16 RenderSourceName() OVERRIDE; + virtual base::string16 RenderSourceName() override; // content::NotificationObserver implementation. virtual void Observe(int type, const content::NotificationSource& source, - const content::NotificationDetails& details) OVERRIDE; + const content::NotificationDetails& details) override; // content::WebContentsObserver implementation. virtual void DidStartLoading( - content::RenderViewHost* render_view_host) OVERRIDE; + content::RenderViewHost* render_view_host) override; // content::WebContentsObserver implementation. - virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + virtual bool OnMessageReceived(const IPC::Message& message) override; // Terminates or cancels the print job if one was pending. - virtual void RenderViewGone(base::TerminationStatus status) OVERRIDE; + virtual void RenderProcessGone(base::TerminationStatus status) override; // Cancels the print job. - virtual void StopNavigation() OVERRIDE; + virtual void NavigationStopped() override; private: explicit PrintViewManager(content::WebContents* web_contents); @@ -182,8 +182,6 @@ class PrintViewManager : public content::NotificationObserver, // The document cookie of the current PrinterQuery. int cookie_; - /* // Current state of print preview for this view. */ - /* PrintPreviewState print_preview_state_; */ // Keeps track of the pending callback during scripted print preview. content::RenderProcessHost* scripted_print_preview_rph_; diff --git a/src/browser/printing/printer_query.cc b/src/browser/printing/printer_query.cc index 12012c46bf..3bcfb8eb84 100644 --- a/src/browser/printing/printer_query.cc +++ b/src/browser/printing/printer_query.cc @@ -6,16 +6,18 @@ #include "base/bind.h" #include "base/bind_helpers.h" -#include "base/message_loop.h" +#include "base/message_loop/message_loop.h" #include "base/threading/thread_restrictions.h" #include "base/values.h" #include "content/nw/src/browser/printing/print_job_worker.h" +using base::MessageLoop; + namespace printing { PrinterQuery::PrinterQuery() : io_message_loop_(MessageLoop::current()), - ALLOW_THIS_IN_INITIALIZER_LIST(worker_(new PrintJobWorker(this))), + worker_(new PrintJobWorker(this)), is_print_dialog_box_shown_(false), cookie_(PrintSettings::NewCookie()), last_status_(PrintingContext::FAILED) { @@ -68,12 +70,13 @@ int PrinterQuery::cookie() const { return cookie_; } -void PrinterQuery::GetSettings(GetSettingsAskParam ask_user_for_settings, - gfx::NativeView parent_view, - int expected_page_count, - bool has_selection, - MarginType margin_type, - const base::Closure& callback) { +void PrinterQuery::GetSettings( + GetSettingsAskParam ask_user_for_settings, + scoped_ptr web_contents_observer, + int expected_page_count, + bool has_selection, + MarginType margin_type, + const base::Closure& callback) { DCHECK_EQ(io_message_loop_, MessageLoop::current()); DCHECK(!is_print_dialog_box_shown_); @@ -85,11 +88,14 @@ void PrinterQuery::GetSettings(GetSettingsAskParam ask_user_for_settings, FROM_HERE, base::Bind(&PrintJobWorker::GetSettings, base::Unretained(worker_.get()), - is_print_dialog_box_shown_, parent_view, - expected_page_count, has_selection, margin_type)); + is_print_dialog_box_shown_, + base::Passed(&web_contents_observer), + expected_page_count, + has_selection, + margin_type)); } -void PrinterQuery::SetSettings(const DictionaryValue& new_settings, +void PrinterQuery::SetSettings(const base::DictionaryValue& new_settings, const base::Closure& callback) { StartWorker(callback); diff --git a/src/browser/printing/printer_query.h b/src/browser/printing/printer_query.h index 4852fd36ec..f63a1ddca1 100644 --- a/src/browser/printing/printer_query.h +++ b/src/browser/printing/printer_query.h @@ -9,13 +9,13 @@ #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" #include "content/nw/src/browser/printing/print_job_worker_owner.h" +#include "content/nw/src/browser/printing/printing_ui_web_contents_observer.h" #include "printing/print_job_constants.h" #include "ui/gfx/native_widget_types.h" -class MessageLoop; - namespace base { class DictionaryValue; +class MessageLoop; } namespace printing { @@ -38,7 +38,7 @@ class PrinterQuery : public PrintJobWorkerOwner { virtual void GetSettingsDone(const PrintSettings& new_settings, PrintingContext::Result result) OVERRIDE; virtual PrintJobWorker* DetachWorker(PrintJobWorkerOwner* new_owner) OVERRIDE; - virtual MessageLoop* message_loop() OVERRIDE; + virtual base::MessageLoop* message_loop() OVERRIDE; virtual const PrintSettings& settings() const OVERRIDE; virtual int cookie() const OVERRIDE; @@ -46,12 +46,13 @@ class PrinterQuery : public PrintJobWorkerOwner { // times to reinitialize the settings. |parent_view| parameter's window will // be the owner of the print setting dialog box. It is unused when // |ask_for_user_settings| is DEFAULTS. - void GetSettings(GetSettingsAskParam ask_user_for_settings, - gfx::NativeView parent_view, - int expected_page_count, - bool has_selection, - MarginType margin_type, - const base::Closure& callback); + void GetSettings( + GetSettingsAskParam ask_user_for_settings, + scoped_ptr web_contents_observer, + int expected_page_count, + bool has_selection, + MarginType margin_type, + const base::Closure& callback); // Updates the current settings with |new_settings| dictionary values. void SetSettings(const base::DictionaryValue& new_settings, @@ -79,7 +80,7 @@ class PrinterQuery : public PrintJobWorkerOwner { // Main message loop reference. Used to send notifications in the right // thread. - MessageLoop* const io_message_loop_; + base::MessageLoop* const io_message_loop_; // All the UI is done in a worker thread because many Win32 print functions // are blocking and enters a message loop without your consent. There is one diff --git a/src/browser/printing/printing_message_filter.cc b/src/browser/printing/printing_message_filter.cc index 5abdda06a1..fae9f34a7f 100644 --- a/src/browser/printing/printing_message_filter.cc +++ b/src/browser/printing/printing_message_filter.cc @@ -2,37 +2,46 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "content/nw/src/browser/printing/printing_message_filter.h" +#include "chrome/browser/printing/printing_message_filter.h" #include #include "base/bind.h" -#include "base/process_util.h" -//#include "chrome/browser/browser_process.h" -#include "content/nw/src/browser/printing/printer_query.h" -#include "content/nw/src/browser/printing/print_job_manager.h" -//#include "chrome/browser/profiles/profile.h" -//#include "chrome/browser/profiles/profile_io_data.h" -//#include "chrome/browser/ui/webui/print_preview/print_preview_ui.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/printing/print_job_manager.h" +#include "chrome/browser/printing/printer_query.h" #include "content/nw/src/common/print_messages.h" -#include "content/nw/src/shell_content_browser_client.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" -#include "content/public/browser/web_contents_view.h" +#include "content/public/common/child_process_host.h" +#include "content/nw/src/shell_content_browser_client.h" + +#if defined(ENABLE_PRINT_PREVIEW) +#include "chrome/browser/ui/webui/print_preview/print_preview_ui.h" +#endif #if defined(OS_CHROMEOS) #include #include -#include "base/file_util.h" +#include "base/files/file_util.h" #include "base/lazy_instance.h" #include "chrome/browser/printing/print_dialog_cloud.h" #endif +#if defined(OS_ANDROID) +#include "base/strings/string_number_conversions.h" +#include "chrome/browser/android/tab_android.h" +#include "chrome/browser/printing/print_view_manager_basic.h" +#include "printing/printing_context_android.h" +#endif + using content::BrowserThread; +namespace printing { + namespace { #if defined(OS_CHROMEOS) @@ -48,7 +57,7 @@ static base::LazyInstance g_printing_file_descriptor_map = LAZY_INSTANCE_INITIALIZER; #endif -void RenderParamsFromPrintSettings(const printing::PrintSettings& settings, +void RenderParamsFromPrintSettings(const PrintSettings& settings, PrintMsg_Print_Params* params) { params->page_size = settings.page_setup_device_units().physical_size(); params->content_size.SetSize( @@ -63,30 +72,31 @@ void RenderParamsFromPrintSettings(const printing::PrintSettings& settings, params->margin_left = settings.page_setup_device_units().content_area().x(); params->dpi = settings.dpi(); // Currently hardcoded at 1.25. See PrintSettings' constructor. - params->min_shrink = settings.min_shrink; + params->min_shrink = settings.min_shrink(); // Currently hardcoded at 2.0. See PrintSettings' constructor. - params->max_shrink = settings.max_shrink; + params->max_shrink = settings.max_shrink(); // Currently hardcoded at 72dpi. See PrintSettings' constructor. - params->desired_dpi = settings.desired_dpi; + params->desired_dpi = settings.desired_dpi(); // Always use an invalid cookie. params->document_cookie = 0; - params->selection_only = settings.selection_only; + params->selection_only = settings.selection_only(); params->supports_alpha_blend = settings.supports_alpha_blend(); - params->should_print_backgrounds = settings.should_print_backgrounds; - params->display_header_footer = settings.display_header_footer; - params->date = settings.date; - params->title = settings.title; - params->url = settings.url; + params->should_print_backgrounds = settings.should_print_backgrounds(); + params->display_header_footer = settings.display_header_footer(); + params->title = settings.title(); + params->url = settings.url(); } } // namespace PrintingMessageFilter::PrintingMessageFilter(int render_process_id) - : print_job_manager_(NULL), + : BrowserMessageFilter(PrintMsgStart), render_process_id_(render_process_id) { content::ShellContentBrowserClient* browser_client = static_cast(content::GetContentClient()->browser()); - print_job_manager_ = browser_client->print_job_manager(); + queue_ = browser_client->print_job_manager()->queue(); + + DCHECK(queue_.get()); } PrintingMessageFilter::~PrintingMessageFilter() { @@ -99,28 +109,35 @@ void PrintingMessageFilter::OverrideThreadForMessage( message.type() == PrintHostMsg_TempFileForPrintingWritten::ID) { *thread = BrowserThread::FILE; } +#elif defined(OS_ANDROID) + if (message.type() == PrintHostMsg_AllocateTempFileForPrinting::ID || + message.type() == PrintHostMsg_TempFileForPrintingWritten::ID) { + *thread = BrowserThread::UI; + } #endif } -bool PrintingMessageFilter::OnMessageReceived(const IPC::Message& message, - bool* message_was_ok) { +bool PrintingMessageFilter::OnMessageReceived(const IPC::Message& message) { bool handled = true; - IPC_BEGIN_MESSAGE_MAP_EX(PrintingMessageFilter, message, *message_was_ok) + IPC_BEGIN_MESSAGE_MAP(PrintingMessageFilter, message) #if defined(OS_WIN) IPC_MESSAGE_HANDLER(PrintHostMsg_DuplicateSection, OnDuplicateSection) #endif -#if defined(OS_CHROMEOS) +#if defined(OS_CHROMEOS) || defined(OS_ANDROID) IPC_MESSAGE_HANDLER(PrintHostMsg_AllocateTempFileForPrinting, OnAllocateTempFileForPrinting) IPC_MESSAGE_HANDLER(PrintHostMsg_TempFileForPrintingWritten, OnTempFileForPrintingWritten) #endif + IPC_MESSAGE_HANDLER(PrintHostMsg_IsPrintingEnabled, OnIsPrintingEnabled) IPC_MESSAGE_HANDLER_DELAY_REPLY(PrintHostMsg_GetDefaultPrintSettings, OnGetDefaultPrintSettings) IPC_MESSAGE_HANDLER_DELAY_REPLY(PrintHostMsg_ScriptedPrint, OnScriptedPrint) IPC_MESSAGE_HANDLER_DELAY_REPLY(PrintHostMsg_UpdatePrintSettings, OnUpdatePrintSettings) +#if defined(ENABLE_PRINT_PREVIEW) IPC_MESSAGE_HANDLER(PrintHostMsg_CheckForCancel, OnCheckForCancel) +#endif IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; @@ -132,15 +149,19 @@ void PrintingMessageFilter::OnDuplicateSection( base::SharedMemoryHandle* browser_handle) { // Duplicate the handle in this process right now so the memory is kept alive // (even if it is not mapped) - base::SharedMemory shared_buf(renderer_handle, true, peer_handle()); + base::SharedMemory shared_buf(renderer_handle, true, PeerHandle()); shared_buf.GiveToProcess(base::GetCurrentProcessHandle(), browser_handle); } #endif -#if defined(OS_CHROMEOS) +#if defined(OS_CHROMEOS) || defined(OS_ANDROID) void PrintingMessageFilter::OnAllocateTempFileForPrinting( - base::FileDescriptor* temp_file_fd, int* sequence_number) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + int render_view_id, + base::FileDescriptor* temp_file_fd, + int* sequence_number) { +#if defined(OS_CHROMEOS) + // TODO(thestig): Use |render_view_id| for Chrome OS. + DCHECK_CURRENTLY_ON(BrowserThread::FILE); temp_file_fd->fd = *sequence_number = -1; temp_file_fd->auto_close = false; @@ -148,7 +169,7 @@ void PrintingMessageFilter::OnAllocateTempFileForPrinting( *sequence_number = g_printing_file_descriptor_map.Get().sequence++; base::FilePath path; - if (file_util::CreateTemporaryFile(&path)) { + if (base::CreateTemporaryFile(&path)) { int fd = open(path.value().c_str(), O_WRONLY); if (fd >= 0) { SequenceToPathMap::iterator it = map->find(*sequence_number); @@ -162,11 +183,26 @@ void PrintingMessageFilter::OnAllocateTempFileForPrinting( } } } +#elif defined(OS_ANDROID) + DCHECK_CURRENTLY_ON(BrowserThread::UI); + content::WebContents* wc = GetWebContentsForRenderView(render_view_id); + if (!wc) + return; + PrintViewManagerBasic* print_view_manager = + PrintViewManagerBasic::FromWebContents(wc); + // The file descriptor is originally created in & passed from the Android + // side, and it will handle the closing. + const base::FileDescriptor& file_descriptor = + print_view_manager->file_descriptor(); + temp_file_fd->fd = file_descriptor.fd; + temp_file_fd->auto_close = false; +#endif } void PrintingMessageFilter::OnTempFileForPrintingWritten(int render_view_id, int sequence_number) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); +#if defined(OS_CHROMEOS) + DCHECK_CURRENTLY_ON(BrowserThread::FILE); SequenceToPathMap* map = &g_printing_file_descriptor_map.Get().map; SequenceToPathMap::iterator it = map->find(sequence_number); if (it == map->end()) { @@ -181,92 +217,81 @@ void PrintingMessageFilter::OnTempFileForPrintingWritten(int render_view_id, // Erase the entry in the map. map->erase(it); +#elif defined(OS_ANDROID) + DCHECK_CURRENTLY_ON(BrowserThread::UI); + content::WebContents* wc = GetWebContentsForRenderView(render_view_id); + if (!wc) + return; + PrintViewManagerBasic* print_view_manager = + PrintViewManagerBasic::FromWebContents(wc); + const base::FileDescriptor& file_descriptor = + print_view_manager->file_descriptor(); + PrintingContextAndroid::PdfWritingDone(file_descriptor.fd, true); + // Invalidate the file descriptor so it doesn't accidentally get reused. + print_view_manager->set_file_descriptor(base::FileDescriptor(-1, false)); +#endif } +#endif // defined(OS_CHROMEOS) || defined(OS_ANDROID) +#if defined(OS_CHROMEOS) void PrintingMessageFilter::CreatePrintDialogForFile( int render_view_id, const base::FilePath& path) { content::WebContents* wc = GetWebContentsForRenderView(render_view_id); + if (!wc) + return; print_dialog_cloud::CreatePrintDialogForFile( wc->GetBrowserContext(), - wc->GetView()->GetTopLevelNativeWindow(), + wc->GetTopLevelNativeWindow(), path, - string16(), - string16(), - std::string("application/pdf"), - false); + wc->GetTitle(), + base::string16(), + std::string("application/pdf")); } #endif // defined(OS_CHROMEOS) content::WebContents* PrintingMessageFilter::GetWebContentsForRenderView( int render_view_id) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK_CURRENTLY_ON(BrowserThread::UI); content::RenderViewHost* view = content::RenderViewHost::FromID( render_process_id_, render_view_id); - return content::WebContents::FromRenderViewHost(view); + return view ? content::WebContents::FromRenderViewHost(view) : NULL; } -struct PrintingMessageFilter::GetPrintSettingsForRenderViewParams { - printing::PrinterQuery::GetSettingsAskParam ask_user_for_settings; - int expected_page_count; - bool has_selection; - printing::MarginType margin_type; -}; - -void PrintingMessageFilter::GetPrintSettingsForRenderView( - int render_view_id, - GetPrintSettingsForRenderViewParams params, - const base::Closure& callback, - scoped_refptr printer_query) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - content::WebContents* wc = GetWebContentsForRenderView(render_view_id); - - BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - base::Bind(&printing::PrinterQuery::GetSettings, printer_query, - params.ask_user_for_settings, wc->GetView()->GetNativeView(), - params.expected_page_count, params.has_selection, - params.margin_type, callback)); +void PrintingMessageFilter::OnIsPrintingEnabled(bool* is_enabled) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + *is_enabled = true; } void PrintingMessageFilter::OnGetDefaultPrintSettings(IPC::Message* reply_msg) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - scoped_refptr printer_query; -#if 0 - if (!profile_io_data_->printing_enabled()->GetValue()) { - // Reply with NULL query. - OnGetDefaultPrintSettingsReply(printer_query, reply_msg); - return; - } -#endif - print_job_manager_->PopPrinterQuery(0, &printer_query); + DCHECK_CURRENTLY_ON(BrowserThread::IO); + scoped_refptr printer_query; + printer_query = queue_->PopPrinterQuery(0); if (!printer_query.get()) { - printer_query = new printing::PrinterQuery; - printer_query->SetWorkerDestination(print_job_manager_->destination()); + printer_query = + queue_->CreatePrinterQuery(render_process_id_, reply_msg->routing_id()); } // Loads default settings. This is asynchronous, only the IPC message sender // will hang until the settings are retrieved. - GetPrintSettingsForRenderViewParams params; - params.ask_user_for_settings = printing::PrinterQuery::DEFAULTS; - params.expected_page_count = 0; - params.has_selection = false; - params.margin_type = printing::DEFAULT_MARGINS; - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(&PrintingMessageFilter::GetPrintSettingsForRenderView, this, - reply_msg->routing_id(), params, - base::Bind(&PrintingMessageFilter::OnGetDefaultPrintSettingsReply, - this, printer_query, reply_msg), - printer_query)); + printer_query->GetSettings( + PrinterQuery::GetSettingsAskParam::DEFAULTS, + 0, + false, + DEFAULT_MARGINS, + false, + base::Bind(&PrintingMessageFilter::OnGetDefaultPrintSettingsReply, + this, + printer_query, + reply_msg)); } void PrintingMessageFilter::OnGetDefaultPrintSettingsReply( - scoped_refptr printer_query, + scoped_refptr printer_query, IPC::Message* reply_msg) { PrintMsg_Print_Params params; if (!printer_query.get() || - printer_query->last_status() != printing::PrintingContext::OK) { + printer_query->last_status() != PrintingContext::OK) { params.Reset(); } else { RenderParamsFromPrintSettings(printer_query->settings(), ¶ms); @@ -278,7 +303,7 @@ void PrintingMessageFilter::OnGetDefaultPrintSettingsReply( if (printer_query.get()) { // If user hasn't cancelled. if (printer_query->cookie() && printer_query->settings().dpi()) { - print_job_manager_->QueuePrinterQuery(printer_query.get()); + queue_->QueuePrinterQuery(printer_query.get()); } else { printer_query->StopWorker(); } @@ -288,96 +313,134 @@ void PrintingMessageFilter::OnGetDefaultPrintSettingsReply( void PrintingMessageFilter::OnScriptedPrint( const PrintHostMsg_ScriptedPrint_Params& params, IPC::Message* reply_msg) { - scoped_refptr printer_query; - print_job_manager_->PopPrinterQuery(params.cookie, &printer_query); + scoped_refptr printer_query = + queue_->PopPrinterQuery(params.cookie); if (!printer_query.get()) { - printer_query = new printing::PrinterQuery; - printer_query->SetWorkerDestination(print_job_manager_->destination()); + printer_query = + queue_->CreatePrinterQuery(render_process_id_, reply_msg->routing_id()); } - GetPrintSettingsForRenderViewParams settings_params; - settings_params.ask_user_for_settings = printing::PrinterQuery::ASK_USER; - settings_params.expected_page_count = params.expected_pages_count; - settings_params.has_selection = params.has_selection; - settings_params.margin_type = params.margin_type; - - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(&PrintingMessageFilter::GetPrintSettingsForRenderView, this, - reply_msg->routing_id(), settings_params, - base::Bind(&PrintingMessageFilter::OnScriptedPrintReply, this, - printer_query, reply_msg), - printer_query)); + printer_query->GetSettings( + PrinterQuery::GetSettingsAskParam::ASK_USER, + params.expected_pages_count, + params.has_selection, + params.margin_type, + params.is_scripted, + base::Bind(&PrintingMessageFilter::OnScriptedPrintReply, + this, + printer_query, + reply_msg)); } void PrintingMessageFilter::OnScriptedPrintReply( - scoped_refptr printer_query, + scoped_refptr printer_query, IPC::Message* reply_msg) { PrintMsg_PrintPages_Params params; - if (printer_query->last_status() != printing::PrintingContext::OK || +#if defined(OS_ANDROID) + // We need to save the routing ID here because Send method below deletes the + // |reply_msg| before we can get the routing ID for the Android code. + int routing_id = reply_msg->routing_id(); +#endif + if (printer_query->last_status() != PrintingContext::OK || !printer_query->settings().dpi()) { params.Reset(); } else { RenderParamsFromPrintSettings(printer_query->settings(), ¶ms.params); params.params.document_cookie = printer_query->cookie(); - params.pages = - printing::PageRange::GetPages(printer_query->settings().ranges); + params.pages = PageRange::GetPages(printer_query->settings().ranges()); } PrintHostMsg_ScriptedPrint::WriteReplyParams(reply_msg, params); Send(reply_msg); if (params.params.dpi && params.params.document_cookie) { - print_job_manager_->QueuePrinterQuery(printer_query.get()); +#if defined(OS_ANDROID) + int file_descriptor; + const base::string16& device_name = printer_query->settings().device_name(); + if (base::StringToInt(device_name, &file_descriptor)) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&PrintingMessageFilter::UpdateFileDescriptor, this, + routing_id, file_descriptor)); + } +#endif + queue_->QueuePrinterQuery(printer_query.get()); } else { printer_query->StopWorker(); } } -void PrintingMessageFilter::OnUpdatePrintSettings( - int document_cookie, const DictionaryValue& job_settings, - IPC::Message* reply_msg) { - scoped_refptr printer_query; -#if 0 - if (!profile_io_data_->printing_enabled()->GetValue()) { - // Reply with NULL query. - OnUpdatePrintSettingsReply(printer_query, reply_msg); +#if defined(OS_ANDROID) +void PrintingMessageFilter::UpdateFileDescriptor(int render_view_id, int fd) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + content::WebContents* wc = GetWebContentsForRenderView(render_view_id); + if (!wc) return; - } + PrintViewManagerBasic* print_view_manager = + PrintViewManagerBasic::FromWebContents(wc); + print_view_manager->set_file_descriptor(base::FileDescriptor(fd, false)); +} #endif - print_job_manager_->PopPrinterQuery(document_cookie, &printer_query); + +void PrintingMessageFilter::OnUpdatePrintSettings( + int document_cookie, const base::DictionaryValue& job_settings, + IPC::Message* reply_msg) { + scoped_ptr new_settings(job_settings.DeepCopy()); + + scoped_refptr printer_query; + + printer_query = queue_->PopPrinterQuery(document_cookie); if (!printer_query.get()) { - printer_query = new printing::PrinterQuery; - printer_query->SetWorkerDestination(print_job_manager_->destination()); + int host_id = render_process_id_; + int routing_id = reply_msg->routing_id(); + if (!new_settings->GetInteger(printing::kPreviewInitiatorHostId, + &host_id) || + !new_settings->GetInteger(printing::kPreviewInitiatorRoutingId, + &routing_id)) { + host_id = content::ChildProcessHost::kInvalidUniqueID; + routing_id = content::ChildProcessHost::kInvalidUniqueID; + } + printer_query = queue_->CreatePrinterQuery(host_id, routing_id); } printer_query->SetSettings( - job_settings, + new_settings.Pass(), base::Bind(&PrintingMessageFilter::OnUpdatePrintSettingsReply, this, printer_query, reply_msg)); } void PrintingMessageFilter::OnUpdatePrintSettingsReply( - scoped_refptr printer_query, + scoped_refptr printer_query, IPC::Message* reply_msg) { PrintMsg_PrintPages_Params params; if (!printer_query.get() || - printer_query->last_status() != printing::PrintingContext::OK) { + printer_query->last_status() != PrintingContext::OK) { params.Reset(); } else { RenderParamsFromPrintSettings(printer_query->settings(), ¶ms.params); params.params.document_cookie = printer_query->cookie(); - params.pages = - printing::PageRange::GetPages(printer_query->settings().ranges); + params.pages = PageRange::GetPages(printer_query->settings().ranges()); } - PrintHostMsg_UpdatePrintSettings::WriteReplyParams(reply_msg, params); + PrintHostMsg_UpdatePrintSettings::WriteReplyParams( + reply_msg, + params, + printer_query.get() && + (printer_query->last_status() == printing::PrintingContext::CANCEL)); Send(reply_msg); // If user hasn't cancelled. if (printer_query.get()) { - if (printer_query->cookie() && printer_query->settings().dpi()) - print_job_manager_->QueuePrinterQuery(printer_query.get()); - else + if (printer_query->cookie() && printer_query->settings().dpi()) { + queue_->QueuePrinterQuery(printer_query.get()); + } else { printer_query->StopWorker(); + } } } +#if defined(ENABLE_PRINT_PREVIEW) void PrintingMessageFilter::OnCheckForCancel(int32 preview_ui_id, int preview_request_id, bool* cancel) { + PrintPreviewUI::GetCurrentPrintPreviewStatus(preview_ui_id, + preview_request_id, + cancel); } +#endif + +} // namespace printing diff --git a/src/browser/printing/printing_message_filter.h b/src/browser/printing/printing_message_filter.h index 0a4f32facb..0a12d59a07 100644 --- a/src/browser/printing/printing_message_filter.h +++ b/src/browser/printing/printing_message_filter.h @@ -2,19 +2,22 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef NW_BROWSER_PRINTING_PRINTING_MESSAGE_FILTER_H_ -#define NW_BROWSER_PRINTING_PRINTING_MESSAGE_FILTER_H_ +#ifndef CHROME_BROWSER_PRINTING_PRINTING_MESSAGE_FILTER_H_ +#define CHROME_BROWSER_PRINTING_PRINTING_MESSAGE_FILTER_H_ #include #include "base/compiler_specific.h" +#include "base/prefs/pref_member.h" #include "content/public/browser/browser_message_filter.h" #if defined(OS_WIN) -#include "base/shared_memory.h" +#include "base/memory/shared_memory.h" #endif struct PrintHostMsg_ScriptedPrint_Params; +class Profile; +class ProfileIOData; namespace base { class DictionaryValue; @@ -26,9 +29,10 @@ class WebContents; } namespace printing { -class PrinterQuery; + class PrintJobManager; -} +class PrintQueriesQueue; +class PrinterQuery; // This class filters out incoming printing related IPC messages for the // renderer process on the IPC thread. @@ -37,14 +41,12 @@ class PrintingMessageFilter : public content::BrowserMessageFilter { PrintingMessageFilter(int render_process_id); // content::BrowserMessageFilter methods. - virtual void OverrideThreadForMessage( - const IPC::Message& message, - content::BrowserThread::ID* thread) OVERRIDE; - virtual bool OnMessageReceived(const IPC::Message& message, - bool* message_was_ok) OVERRIDE; + void OverrideThreadForMessage(const IPC::Message& message, + content::BrowserThread::ID* thread) override; + bool OnMessageReceived(const IPC::Message& message) override; private: - virtual ~PrintingMessageFilter(); + ~PrintingMessageFilter() override; #if defined(OS_WIN) // Used to pass resulting EMF from renderer to browser in printing. @@ -52,15 +54,25 @@ class PrintingMessageFilter : public content::BrowserMessageFilter { base::SharedMemoryHandle* browser_handle); #endif -#if defined(OS_CHROMEOS) +#if defined(OS_CHROMEOS) || defined(OS_ANDROID) // Used to ask the browser allocate a temporary file for the renderer // to fill in resulting PDF in renderer. - void OnAllocateTempFileForPrinting(base::FileDescriptor* temp_file_fd, + void OnAllocateTempFileForPrinting(int render_view_id, + base::FileDescriptor* temp_file_fd, int* sequence_number); void OnTempFileForPrintingWritten(int render_view_id, int sequence_number); +#endif + +#if defined(OS_CHROMEOS) void CreatePrintDialogForFile(int render_view_id, const base::FilePath& path); #endif +#if defined(OS_ANDROID) + // Updates the file descriptor for the PrintViewManagerBasic of a given + // render_view_id. + void UpdateFileDescriptor(int render_view_id, int fd); +#endif + // Given a render_view_id get the corresponding WebContents. // Must be called on the UI thread. content::WebContents* GetWebContentsForRenderView(int render_view_id); @@ -71,28 +83,21 @@ class PrintingMessageFilter : public content::BrowserMessageFilter { // to base::Bind. struct GetPrintSettingsForRenderViewParams; - // Retrieve print settings. Uses |render_view_id| to get a parent - // for any UI created if needed. - void GetPrintSettingsForRenderView( - int render_view_id, - GetPrintSettingsForRenderViewParams params, - const base::Closure& callback, - scoped_refptr printer_query); + // Checks if printing is enabled. + void OnIsPrintingEnabled(bool* is_enabled); // Get the default print setting. void OnGetDefaultPrintSettings(IPC::Message* reply_msg); - void OnGetDefaultPrintSettingsReply( - scoped_refptr printer_query, - IPC::Message* reply_msg); + void OnGetDefaultPrintSettingsReply(scoped_refptr printer_query, + IPC::Message* reply_msg); // The renderer host have to show to the user the print dialog and returns // the selected print settings. The task is handled by the print worker // thread and the UI thread. The reply occurs on the IO thread. void OnScriptedPrint(const PrintHostMsg_ScriptedPrint_Params& params, IPC::Message* reply_msg); - void OnScriptedPrintReply( - scoped_refptr printer_query, - IPC::Message* reply_msg); + void OnScriptedPrintReply(scoped_refptr printer_query, + IPC::Message* reply_msg); // Modify the current print settings based on |job_settings|. The task is // handled by the print worker thread and the UI thread. The reply occurs on @@ -100,20 +105,23 @@ class PrintingMessageFilter : public content::BrowserMessageFilter { void OnUpdatePrintSettings(int document_cookie, const base::DictionaryValue& job_settings, IPC::Message* reply_msg); - void OnUpdatePrintSettingsReply( - scoped_refptr printer_query, - IPC::Message* reply_msg); + void OnUpdatePrintSettingsReply(scoped_refptr printer_query, + IPC::Message* reply_msg); +#if defined(ENABLE_PRINT_PREVIEW) // Check to see if print preview has been cancelled. void OnCheckForCancel(int32 preview_ui_id, int preview_request_id, bool* cancel); +#endif - printing::PrintJobManager* print_job_manager_; + const int render_process_id_; - int render_process_id_; + scoped_refptr queue_; DISALLOW_COPY_AND_ASSIGN(PrintingMessageFilter); }; +} // namespace printing + #endif // CHROME_BROWSER_PRINTING_PRINTING_MESSAGE_FILTER_H_ diff --git a/src/browser/printing/printing_ui_web_contents_observer.cc b/src/browser/printing/printing_ui_web_contents_observer.cc new file mode 100644 index 0000000000..23be554307 --- /dev/null +++ b/src/browser/printing/printing_ui_web_contents_observer.cc @@ -0,0 +1,19 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/nw/src/browser/printing/printing_ui_web_contents_observer.h" + +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/web_contents.h" + +PrintingUIWebContentsObserver::PrintingUIWebContentsObserver( + content::WebContents* web_contents) + : content::WebContentsObserver(web_contents) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); +} + +gfx::NativeView PrintingUIWebContentsObserver::GetParentView() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + return web_contents() ? web_contents()->GetNativeView() : NULL; +} diff --git a/src/browser/printing/printing_ui_web_contents_observer.h b/src/browser/printing/printing_ui_web_contents_observer.h new file mode 100644 index 0000000000..4257469acd --- /dev/null +++ b/src/browser/printing/printing_ui_web_contents_observer.h @@ -0,0 +1,25 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NW_BROWSER_PRINTING_PRINTING_UI_WEB_CONTENTS_OBSERVER_H_ +#define NW_BROWSER_PRINTING_PRINTING_UI_WEB_CONTENTS_OBSERVER_H_ + +#include "base/basictypes.h" +#include "content/public/browser/web_contents_observer.h" +#include "ui/gfx/native_widget_types.h" + +// Wrapper used to keep track of the lifetime of a WebContents. +// Lives on the UI thread. +class PrintingUIWebContentsObserver : public content::WebContentsObserver { + public: + explicit PrintingUIWebContentsObserver(content::WebContents* web_contents); + + // Return the parent NativeView of the observed WebContents. + gfx::NativeView GetParentView(); + + private: + DISALLOW_COPY_AND_ASSIGN(PrintingUIWebContentsObserver); +}; + +#endif // CHROME_BROWSER_PRINTING_PRINTING_UI_WEB_CONTENTS_OBSERVER_H_ diff --git a/src/browser/printing_handler.cc b/src/browser/printing_handler.cc new file mode 100644 index 0000000000..9d354d4aa5 --- /dev/null +++ b/src/browser/printing_handler.cc @@ -0,0 +1,540 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/utility/printing_handler.h" + +#include "base/files/file_util.h" +#include "base/lazy_instance.h" +#include "base/path_service.h" +#include "base/scoped_native_library.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_utility_printing_messages.h" +#include "chrome/utility/cloud_print/bitmap_image.h" +#include "chrome/utility/cloud_print/pwg_encoder.h" +#include "content/public/utility/utility_thread.h" +#include "printing/page_range.h" +#include "printing/pdf_render_settings.h" + +#if defined(OS_WIN) +#include "base/win/iat_patch_function.h" +#include "printing/emf_win.h" +#include "ui/gfx/gdi_util.h" +#endif + +#if defined(ENABLE_PRINT_PREVIEW) +#include "chrome/common/crash_keys.h" +#include "printing/backend/print_backend.h" +#endif + +namespace { + // File name of the internal PDF plugin on different platforms. +const base::FilePath::CharType kInternalPDFPluginFileName[] = +#if defined(OS_WIN) + FILE_PATH_LITERAL("pdf.dll"); +#elif defined(OS_MACOSX) + FILE_PATH_LITERAL("PDF.plugin"); +#else // Linux and Chrome OS + FILE_PATH_LITERAL("libpdf.so"); +#endif + +bool Send(IPC::Message* message) { + return content::UtilityThread::Get()->Send(message); +} + +void ReleaseProcessIfNeeded() { + content::UtilityThread::Get()->ReleaseProcessIfNeeded(); +} + +class PdfFunctionsBase { + public: + PdfFunctionsBase() : render_pdf_to_bitmap_func_(NULL), + get_pdf_doc_info_func_(NULL) {} + + bool Init() { + base::FilePath pdf_module_path; + if (!PathService::Get(base::DIR_MODULE, &pdf_module_path)) + return false; + pdf_module_path = pdf_module_path.Append(kInternalPDFPluginFileName); + if (!base::PathExists(pdf_module_path)) { + return false; + } + + pdf_lib_.Reset(base::LoadNativeLibrary(pdf_module_path, NULL)); + if (!pdf_lib_.is_valid()) { + LOG(WARNING) << "Couldn't load PDF plugin"; + return false; + } + + render_pdf_to_bitmap_func_ = + reinterpret_cast( + pdf_lib_.GetFunctionPointer("RenderPDFPageToBitmap")); + LOG_IF(WARNING, !render_pdf_to_bitmap_func_) << + "Missing RenderPDFPageToBitmap"; + + get_pdf_doc_info_func_ = + reinterpret_cast( + pdf_lib_.GetFunctionPointer("GetPDFDocInfo")); + LOG_IF(WARNING, !get_pdf_doc_info_func_) << "Missing GetPDFDocInfo"; + + if (!render_pdf_to_bitmap_func_ || !get_pdf_doc_info_func_ || + !PlatformInit(pdf_module_path, pdf_lib_)) { + Reset(); + } + + return IsValid(); + } + + bool IsValid() const { + return pdf_lib_.is_valid(); + } + + void Reset() { + pdf_lib_.Reset(NULL); + } + + bool RenderPDFPageToBitmap(const void* pdf_buffer, + int pdf_buffer_size, + int page_number, + void* bitmap_buffer, + int bitmap_width, + int bitmap_height, + int dpi_x, + int dpi_y, + bool autorotate) { + if (!render_pdf_to_bitmap_func_) + return false; + return render_pdf_to_bitmap_func_(pdf_buffer, pdf_buffer_size, page_number, + bitmap_buffer, bitmap_width, + bitmap_height, dpi_x, dpi_y, autorotate); + } + + bool GetPDFDocInfo(const void* pdf_buffer, + int buffer_size, + int* page_count, + double* max_page_width) { + if (!get_pdf_doc_info_func_) + return false; + return get_pdf_doc_info_func_(pdf_buffer, buffer_size, page_count, + max_page_width); + } + + protected: + virtual bool PlatformInit( + const base::FilePath& pdf_module_path, + const base::ScopedNativeLibrary& pdf_lib) { + return true; + } + + private: + // Exported by PDF plugin. + typedef bool (*RenderPDFPageToBitmapProc)(const void* pdf_buffer, + int pdf_buffer_size, + int page_number, + void* bitmap_buffer, + int bitmap_width, + int bitmap_height, + int dpi_x, + int dpi_y, + bool autorotate); + typedef bool (*GetPDFDocInfoProc)(const void* pdf_buffer, + int buffer_size, int* page_count, + double* max_page_width); + + RenderPDFPageToBitmapProc render_pdf_to_bitmap_func_; + GetPDFDocInfoProc get_pdf_doc_info_func_; + + base::ScopedNativeLibrary pdf_lib_; + DISALLOW_COPY_AND_ASSIGN(PdfFunctionsBase); +}; + +#if defined(OS_WIN) +// The 2 below IAT patch functions are almost identical to the code in +// render_process_impl.cc. This is needed to work around specific Windows APIs +// used by the Chrome PDF plugin that will fail in the sandbox. +static base::win::IATPatchFunction g_iat_patch_createdca; +HDC WINAPI UtilityProcess_CreateDCAPatch(LPCSTR driver_name, + LPCSTR device_name, + LPCSTR output, + const DEVMODEA* init_data) { + if (driver_name && (std::string("DISPLAY") == driver_name)) { + // CreateDC fails behind the sandbox, but not CreateCompatibleDC. + return CreateCompatibleDC(NULL); + } + + NOTREACHED(); + return CreateDCA(driver_name, device_name, output, init_data); +} + +static base::win::IATPatchFunction g_iat_patch_get_font_data; +DWORD WINAPI UtilityProcess_GetFontDataPatch( + HDC hdc, DWORD table, DWORD offset, LPVOID buffer, DWORD length) { + int rv = GetFontData(hdc, table, offset, buffer, length); + if (rv == GDI_ERROR && hdc) { + HFONT font = static_cast(GetCurrentObject(hdc, OBJ_FONT)); + + LOGFONT logfont; + if (GetObject(font, sizeof(LOGFONT), &logfont)) { + content::UtilityThread::Get()->PreCacheFont(logfont); + rv = GetFontData(hdc, table, offset, buffer, length); + content::UtilityThread::Get()->ReleaseCachedFonts(); + } + } + return rv; +} + +class PdfFunctionsWin : public PdfFunctionsBase { + public: + PdfFunctionsWin() : render_pdf_to_dc_func_(NULL) { + } + + bool PlatformInit( + const base::FilePath& pdf_module_path, + const base::ScopedNativeLibrary& pdf_lib) override { + // Patch the IAT for handling specific APIs known to fail in the sandbox. + if (!g_iat_patch_createdca.is_patched()) { + g_iat_patch_createdca.Patch(pdf_module_path.value().c_str(), + "gdi32.dll", "CreateDCA", + UtilityProcess_CreateDCAPatch); + } + + if (!g_iat_patch_get_font_data.is_patched()) { + g_iat_patch_get_font_data.Patch(pdf_module_path.value().c_str(), + "gdi32.dll", "GetFontData", + UtilityProcess_GetFontDataPatch); + } + render_pdf_to_dc_func_ = + reinterpret_cast( + pdf_lib.GetFunctionPointer("RenderPDFPageToDC")); + LOG_IF(WARNING, !render_pdf_to_dc_func_) << "Missing RenderPDFPageToDC"; + + return render_pdf_to_dc_func_ != NULL; + } + + bool RenderPDFPageToDC(const void* pdf_buffer, + int buffer_size, + int page_number, + HDC dc, + int dpi_x, + int dpi_y, + int bounds_origin_x, + int bounds_origin_y, + int bounds_width, + int bounds_height, + bool fit_to_bounds, + bool stretch_to_bounds, + bool keep_aspect_ratio, + bool center_in_bounds, + bool autorotate) { + if (!render_pdf_to_dc_func_) + return false; + return render_pdf_to_dc_func_(pdf_buffer, buffer_size, page_number, + dc, dpi_x, dpi_y, bounds_origin_x, + bounds_origin_y, bounds_width, bounds_height, + fit_to_bounds, stretch_to_bounds, + keep_aspect_ratio, center_in_bounds, + autorotate); + } + + private: + // Exported by PDF plugin. + typedef bool (*RenderPDFPageToDCProc)( + const void* pdf_buffer, int buffer_size, int page_number, HDC dc, + int dpi_x, int dpi_y, int bounds_origin_x, int bounds_origin_y, + int bounds_width, int bounds_height, bool fit_to_bounds, + bool stretch_to_bounds, bool keep_aspect_ratio, bool center_in_bounds, + bool autorotate); + RenderPDFPageToDCProc render_pdf_to_dc_func_; + + DISALLOW_COPY_AND_ASSIGN(PdfFunctionsWin); +}; + +typedef PdfFunctionsWin PdfFunctions; +#else // OS_WIN +typedef PdfFunctionsBase PdfFunctions; +#endif // OS_WIN + +base::LazyInstance g_pdf_lib = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +PrintingHandler::PrintingHandler() {} + +PrintingHandler::~PrintingHandler() {} + +// static +void PrintingHandler::PreSandboxStartup() { + g_pdf_lib.Get().Init(); +} + +bool PrintingHandler::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(PrintingHandler, message) +#if defined(OS_WIN) + IPC_MESSAGE_HANDLER(ChromeUtilityMsg_RenderPDFPagesToMetafiles, + OnRenderPDFPagesToMetafile) + IPC_MESSAGE_HANDLER(ChromeUtilityMsg_RenderPDFPagesToMetafiles_GetPage, + OnRenderPDFPagesToMetafileGetPage) + IPC_MESSAGE_HANDLER(ChromeUtilityMsg_RenderPDFPagesToMetafiles_Stop, + OnRenderPDFPagesToMetafileStop) +#endif // OS_WIN +#if defined(ENABLE_PRINT_PREVIEW) + IPC_MESSAGE_HANDLER(ChromeUtilityMsg_RenderPDFPagesToPWGRaster, + OnRenderPDFPagesToPWGRaster) + IPC_MESSAGE_HANDLER(ChromeUtilityMsg_GetPrinterCapsAndDefaults, + OnGetPrinterCapsAndDefaults) + IPC_MESSAGE_HANDLER(ChromeUtilityMsg_GetPrinterSemanticCapsAndDefaults, + OnGetPrinterSemanticCapsAndDefaults) +#endif // ENABLE_PRINT_PREVIEW + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +#if defined(OS_WIN) +void PrintingHandler::OnRenderPDFPagesToMetafile( + IPC::PlatformFileForTransit pdf_transit, + const printing::PdfRenderSettings& settings) { + pdf_rendering_settings_ = settings; + base::File pdf_file = IPC::PlatformFileForTransitToFile(pdf_transit); + int page_count = LoadPDF(pdf_file.Pass()); + Send( + new ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageCount(page_count)); +} + +void PrintingHandler::OnRenderPDFPagesToMetafileGetPage( + int page_number, + IPC::PlatformFileForTransit output_file) { + base::File emf_file = IPC::PlatformFileForTransitToFile(output_file); + float scale_factor = 1.0f; + bool success = + RenderPdfPageToMetafile(page_number, emf_file.Pass(), &scale_factor); + Send(new ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageDone( + success, scale_factor)); +} + +void PrintingHandler::OnRenderPDFPagesToMetafileStop() { + ReleaseProcessIfNeeded(); +} + +#endif // OS_WIN + +#if defined(ENABLE_PRINT_PREVIEW) +void PrintingHandler::OnRenderPDFPagesToPWGRaster( + IPC::PlatformFileForTransit pdf_transit, + const printing::PdfRenderSettings& settings, + const printing::PwgRasterSettings& bitmap_settings, + IPC::PlatformFileForTransit bitmap_transit) { + base::File pdf = IPC::PlatformFileForTransitToFile(pdf_transit); + base::File bitmap = IPC::PlatformFileForTransitToFile(bitmap_transit); + if (RenderPDFPagesToPWGRaster(pdf.Pass(), settings, bitmap_settings, + bitmap.Pass())) { + Send(new ChromeUtilityHostMsg_RenderPDFPagesToPWGRaster_Succeeded()); + } else { + Send(new ChromeUtilityHostMsg_RenderPDFPagesToPWGRaster_Failed()); + } + ReleaseProcessIfNeeded(); +} +#endif // ENABLE_PRINT_PREVIEW + +#if defined(OS_WIN) +int PrintingHandler::LoadPDF(base::File pdf_file) { + if (!g_pdf_lib.Get().IsValid()) + return 0; + + int64 length64 = pdf_file.GetLength(); + if (length64 <= 0 || length64 > std::numeric_limits::max()) + return 0; + int length = static_cast(length64); + + pdf_data_.resize(length); + if (length != pdf_file.Read(0, pdf_data_.data(), pdf_data_.size())) + return 0; + + int total_page_count = 0; + if (!g_pdf_lib.Get().GetPDFDocInfo( + &pdf_data_.front(), pdf_data_.size(), &total_page_count, NULL)) { + return 0; + } + return total_page_count; +} + +bool PrintingHandler::RenderPdfPageToMetafile(int page_number, + base::File output_file, + float* scale_factor) { + printing::Emf metafile; + metafile.Init(); + + // We need to scale down DC to fit an entire page into DC available area. + // Current metafile is based on screen DC and have current screen size. + // Writing outside of those boundaries will result in the cut-off output. + // On metafiles (this is the case here), scaling down will still record + // original coordinates and we'll be able to print in full resolution. + // Before playback we'll need to counter the scaling up that will happen + // in the service (print_system_win.cc). + *scale_factor = + gfx::CalculatePageScale(metafile.context(), + pdf_rendering_settings_.area().right(), + pdf_rendering_settings_.area().bottom()); + gfx::ScaleDC(metafile.context(), *scale_factor); + + // The underlying metafile is of type Emf and ignores the arguments passed + // to StartPage. + metafile.StartPage(gfx::Size(), gfx::Rect(), 1); + if (!g_pdf_lib.Get().RenderPDFPageToDC( + &pdf_data_.front(), + pdf_data_.size(), + page_number, + metafile.context(), + pdf_rendering_settings_.dpi(), + pdf_rendering_settings_.dpi(), + pdf_rendering_settings_.area().x(), + pdf_rendering_settings_.area().y(), + pdf_rendering_settings_.area().width(), + pdf_rendering_settings_.area().height(), + true, + false, + true, + true, + pdf_rendering_settings_.autorotate())) { + return false; + } + metafile.FinishPage(); + metafile.FinishDocument(); + return metafile.SaveTo(&output_file); +} + +#endif // OS_WIN + +#if defined(ENABLE_PRINT_PREVIEW) +bool PrintingHandler::RenderPDFPagesToPWGRaster( + base::File pdf_file, + const printing::PdfRenderSettings& settings, + const printing::PwgRasterSettings& bitmap_settings, + base::File bitmap_file) { + bool autoupdate = true; + if (!g_pdf_lib.Get().IsValid()) + return false; + + base::File::Info info; + if (!pdf_file.GetInfo(&info) || info.size <= 0 || + info.size > std::numeric_limits::max()) + return false; + int data_size = static_cast(info.size); + + std::string data(data_size, 0); + if (pdf_file.Read(0, &data[0], data_size) != data_size) + return false; + + int total_page_count = 0; + if (!g_pdf_lib.Get().GetPDFDocInfo(data.data(), data_size, + &total_page_count, NULL)) { + return false; + } + + cloud_print::PwgEncoder encoder; + std::string pwg_header; + encoder.EncodeDocumentHeader(&pwg_header); + int bytes_written = bitmap_file.WriteAtCurrentPos(pwg_header.data(), + pwg_header.size()); + if (bytes_written != static_cast(pwg_header.size())) + return false; + + cloud_print::BitmapImage image(settings.area().size(), + cloud_print::BitmapImage::BGRA); + for (int i = 0; i < total_page_count; ++i) { + int page_number = i; + + if (bitmap_settings.reverse_page_order) { + page_number = total_page_count - 1 - page_number; + } + + if (!g_pdf_lib.Get().RenderPDFPageToBitmap(data.data(), + data_size, + page_number, + image.pixel_data(), + image.size().width(), + image.size().height(), + settings.dpi(), + settings.dpi(), + autoupdate)) { + return false; + } + + cloud_print::PwgHeaderInfo header_info; + header_info.dpi = settings.dpi(); + header_info.total_pages = total_page_count; + + // Transform odd pages. + if (page_number % 2) { + switch (bitmap_settings.odd_page_transform) { + case printing::TRANSFORM_NORMAL: + break; + case printing::TRANSFORM_ROTATE_180: + header_info.flipx = true; + header_info.flipy = true; + break; + case printing::TRANSFORM_FLIP_HORIZONTAL: + header_info.flipx = true; + break; + case printing::TRANSFORM_FLIP_VERTICAL: + header_info.flipy = true; + break; + } + } + + if (bitmap_settings.rotate_all_pages) { + header_info.flipx = !header_info.flipx; + header_info.flipy = !header_info.flipy; + } + + std::string pwg_page; + if (!encoder.EncodePage(image, header_info, &pwg_page)) + return false; + bytes_written = bitmap_file.WriteAtCurrentPos(pwg_page.data(), + pwg_page.size()); + if (bytes_written != static_cast(pwg_page.size())) + return false; + } + return true; +} + +void PrintingHandler::OnGetPrinterCapsAndDefaults( + const std::string& printer_name) { + scoped_refptr print_backend = + printing::PrintBackend::CreateInstance(NULL); + printing::PrinterCapsAndDefaults printer_info; + + crash_keys::ScopedPrinterInfo crash_key( + print_backend->GetPrinterDriverInfo(printer_name)); + + if (print_backend->GetPrinterCapsAndDefaults(printer_name, &printer_info)) { + Send(new ChromeUtilityHostMsg_GetPrinterCapsAndDefaults_Succeeded( + printer_name, printer_info)); + } else { + Send(new ChromeUtilityHostMsg_GetPrinterCapsAndDefaults_Failed( + printer_name)); + } + ReleaseProcessIfNeeded(); +} + +void PrintingHandler::OnGetPrinterSemanticCapsAndDefaults( + const std::string& printer_name) { + scoped_refptr print_backend = + printing::PrintBackend::CreateInstance(NULL); + printing::PrinterSemanticCapsAndDefaults printer_info; + + crash_keys::ScopedPrinterInfo crash_key( + print_backend->GetPrinterDriverInfo(printer_name)); + + if (print_backend->GetPrinterSemanticCapsAndDefaults(printer_name, + &printer_info)) { + Send(new ChromeUtilityHostMsg_GetPrinterSemanticCapsAndDefaults_Succeeded( + printer_name, printer_info)); + } else { + Send(new ChromeUtilityHostMsg_GetPrinterSemanticCapsAndDefaults_Failed( + printer_name)); + } + ReleaseProcessIfNeeded(); +} +#endif // ENABLE_PRINT_PREVIEW diff --git a/src/browser/printing_handler.h b/src/browser/printing_handler.h new file mode 100644 index 0000000000..a42c9bb65c --- /dev/null +++ b/src/browser/printing_handler.h @@ -0,0 +1,78 @@ +// 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 NW_UTILITY_PRINTING_HANDLER_H_ +#define NW_UTILITY_PRINTING_HANDLER_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "chrome/utility/utility_message_handler.h" +#include "ipc/ipc_platform_file.h" +#include "printing/pdf_render_settings.h" + +#if !defined(ENABLE_PRINT_PREVIEW) && !defined(OS_WIN) +#error "Windows or full printing must be enabled" +#endif + +namespace printing { +class PdfRenderSettings; +struct PwgRasterSettings; +struct PageRange; +} + +// Dispatches IPCs for printing. +class PrintingHandler : public UtilityMessageHandler { + public: + PrintingHandler(); + ~PrintingHandler() override; + + static void PreSandboxStartup(); + + // IPC::Listener: + bool OnMessageReceived(const IPC::Message& message) override; + + private: + // IPC message handlers. +#if defined(OS_WIN) + void OnRenderPDFPagesToMetafile(IPC::PlatformFileForTransit pdf_transit, + const printing::PdfRenderSettings& settings); + void OnRenderPDFPagesToMetafileGetPage( + int page_number, + IPC::PlatformFileForTransit output_file); + void OnRenderPDFPagesToMetafileStop(); +#endif // OS_WIN +#if defined(ENABLE_PRINT_PREVIEW) + void OnRenderPDFPagesToPWGRaster( + IPC::PlatformFileForTransit pdf_transit, + const printing::PdfRenderSettings& settings, + const printing::PwgRasterSettings& bitmap_settings, + IPC::PlatformFileForTransit bitmap_transit); +#endif // ENABLE_PRINT_PREVIEW + +#if defined(OS_WIN) + int LoadPDF(base::File pdf_file); + bool RenderPdfPageToMetafile(int page_number, + base::File output_file, + float* scale_factor); +#endif // OS_WIN +#if defined(ENABLE_PRINT_PREVIEW) + bool RenderPDFPagesToPWGRaster( + base::File pdf_file, + const printing::PdfRenderSettings& settings, + const printing::PwgRasterSettings& bitmap_settings, + base::File bitmap_file); + + void OnGetPrinterCapsAndDefaults(const std::string& printer_name); + void OnGetPrinterSemanticCapsAndDefaults(const std::string& printer_name); +#endif // ENABLE_PRINT_PREVIEW + +#if defined(OS_WIN) + std::vector pdf_data_; + printing::PdfRenderSettings pdf_rendering_settings_; +#endif + + DISALLOW_COPY_AND_ASSIGN(PrintingHandler); +}; + +#endif // CHROME_UTILITY_PRINTING_HANDLER_H_ diff --git a/src/browser/shell_application_mac.h b/src/browser/shell_application_mac.h index ac5655518e..14c3c73fb8 100644 --- a/src/browser/shell_application_mac.h +++ b/src/browser/shell_application_mac.h @@ -5,7 +5,7 @@ #ifndef CONTENT_NW_SRC_BROWSER_SHELL_APPLICATION_MAC_H_ #define CONTENT_NW_SRC_BROWSER_SHELL_APPLICATION_MAC_H_ -#include "base/message_pump_mac.h" +#include "base/message_loop/message_pump_mac.h" #include "base/mac/scoped_sending_event.h" @interface ShellCrApplication : NSApplication::const_iterator entry = + path_to_resource_id_.find(relative_path); + if (entry != path_to_resource_id_.end()) + *resource_id = entry->second; + + return entry != path_to_resource_id_.end(); +} + +void ShellComponentExtensionResourceManager::AddComponentResourceEntries( + const GritResourceMap* entries, + size_t size) { + for (size_t i = 0; i < size; ++i) { + base::FilePath resource_path = base::FilePath().AppendASCII( + entries[i].name); + resource_path = resource_path.NormalizePathSeparators(); + + DCHECK(path_to_resource_id_.find(resource_path) == + path_to_resource_id_.end()); + path_to_resource_id_[resource_path] = entries[i].value; + } +} + +} // namespace extensions diff --git a/src/browser/shell_component_extension_resource_manager.h b/src/browser/shell_component_extension_resource_manager.h new file mode 100644 index 0000000000..2b3614b578 --- /dev/null +++ b/src/browser/shell_component_extension_resource_manager.h @@ -0,0 +1,41 @@ +// 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 NW_BROWSER_EXTENSIONS_CHROME_COMPONENT_EXTENSION_RESOURCE_MANAGER_H_ +#define NW_BROWSER_EXTENSIONS_CHROME_COMPONENT_EXTENSION_RESOURCE_MANAGER_H_ + +#include + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "extensions/browser/component_extension_resource_manager.h" + +struct GritResourceMap; + +namespace extensions { + +class ShellComponentExtensionResourceManager + : public ComponentExtensionResourceManager { + public: + ShellComponentExtensionResourceManager(); + ~ShellComponentExtensionResourceManager() override; + + // Overridden from ComponentExtensionResourceManager: + bool IsComponentExtensionResource(const base::FilePath& extension_path, + const base::FilePath& resource_path, + int* resource_id) const override; + + private: + void AddComponentResourceEntries(const GritResourceMap* entries, size_t size); + + // A map from a resource path to the resource ID. Used by + // IsComponentExtensionResource. + std::map path_to_resource_id_; + + DISALLOW_COPY_AND_ASSIGN(ShellComponentExtensionResourceManager); +}; + +} // namespace extensions + +#endif // NW_BROWSER_EXTENSIONS_CHROME_COMPONENT_EXTENSION_RESOURCE_MANAGER_H_ diff --git a/src/browser/shell_content_utility_client.cc b/src/browser/shell_content_utility_client.cc new file mode 100644 index 0000000000..c9c158670f --- /dev/null +++ b/src/browser/shell_content_utility_client.cc @@ -0,0 +1,99 @@ +// 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 "content/nw/src/browser/shell_content_utility_client.h" + +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/time/time.h" +#include "chrome/common/chrome_utility_messages.h" +#include "chrome/utility/chrome_content_utility_ipc_whitelist.h" +#include "chrome/utility/utility_message_handler.h" +//#include "chrome/utility/web_resource_unpacker.h" +#include "content/public/child/image_decoder_utils.h" +#include "content/public/common/content_switches.h" +#include "content/public/utility/utility_thread.h" +#include "courgette/courgette.h" +#include "courgette/third_party/bsdiff.h" +#include "ipc/ipc_channel.h" +#include "skia/ext/image_operations.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/zlib/google/zip.h" +#include "ui/gfx/codec/jpeg_codec.h" +#include "ui/gfx/geometry/size.h" + +#include "base/debug/debugger.h" + +#if defined(ENABLE_PRINT_PREVIEW) || defined(OS_WIN) +#include "printing_handler.h" +#endif + +namespace { + +bool Send(IPC::Message* message) { + return content::UtilityThread::Get()->Send(message); +} + +#if 0 +void ReleaseProcessIfNeeded() { + content::UtilityThread::Get()->ReleaseProcessIfNeeded(); +} +#endif + +} // namespace + +int64_t ShellContentUtilityClient::max_ipc_message_size_ = + IPC::Channel::kMaximumMessageSize; + +ShellContentUtilityClient::ShellContentUtilityClient() + : filter_messages_(false) { + +#if defined(ENABLE_PRINT_PREVIEW) || defined(OS_WIN) + handlers_.push_back(new PrintingHandler()); +#endif + +} + +ShellContentUtilityClient::~ShellContentUtilityClient() { +} + +void ShellContentUtilityClient::UtilityThreadStarted() { + +} + +bool ShellContentUtilityClient::OnMessageReceived( + const IPC::Message& message) { + if (filter_messages_ && !ContainsKey(message_id_whitelist_, message.type())) + return false; + + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(ShellContentUtilityClient, message) + IPC_MESSAGE_HANDLER(ChromeUtilityMsg_StartupPing, OnStartupPing) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + + for (Handlers::iterator it = handlers_.begin(); + !handled && it != handlers_.end(); ++it) { + handled = (*it)->OnMessageReceived(message); + } + + return handled; +} + +// static +void ShellContentUtilityClient::PreSandboxStartup() { + //base::debug::WaitForDebugger(120, false); +#if defined(ENABLE_PRINT_PREVIEW) || defined(OS_WIN) + PrintingHandler::PreSandboxStartup(); +#endif + +} + +void ShellContentUtilityClient::OnStartupPing() { + Send(new ChromeUtilityHostMsg_ProcessStarted); + // Don't release the process, we assume further messages are on the way. +} + + diff --git a/src/browser/shell_content_utility_client.h b/src/browser/shell_content_utility_client.h new file mode 100644 index 0000000000..3f18659888 --- /dev/null +++ b/src/browser/shell_content_utility_client.h @@ -0,0 +1,55 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NW_UTILITY_CHROME_CONTENT_UTILITY_CLIENT_H_ +#define NW_UTILITY_CHROME_CONTENT_UTILITY_CLIENT_H_ + +#include +#include +#include + +#include "base/compiler_specific.h" +#include "base/memory/scoped_vector.h" +#include "content/public/utility/content_utility_client.h" +#include "ipc/ipc_platform_file.h" + +namespace base { +class FilePath; +struct FileDescriptor; +} + +class UtilityMessageHandler; + +class ShellContentUtilityClient : public content::ContentUtilityClient { + public: + ShellContentUtilityClient(); + virtual ~ShellContentUtilityClient(); + + void UtilityThreadStarted() override; + bool OnMessageReceived(const IPC::Message& message) override; + + static void PreSandboxStartup(); + + static void set_max_ipc_message_size_for_test(int64_t max_message_size) { + max_ipc_message_size_ = max_message_size; + } + + private: + // IPC message handlers. + void OnStartupPing(); + + typedef ScopedVector Handlers; + Handlers handlers_; + + // Flag to enable whitelisting. + bool filter_messages_; + // A list of message_ids to filter. + std::set message_id_whitelist_; + // Maximum IPC msg size (default to kMaximumMessageSize; override for testing) + static int64_t max_ipc_message_size_; + + DISALLOW_COPY_AND_ASSIGN(ShellContentUtilityClient); +}; + +#endif // CHROME_UTILITY_CHROME_CONTENT_UTILITY_CLIENT_H_ diff --git a/src/browser/shell_devtools_delegate.cc b/src/browser/shell_devtools_delegate.cc index dd4fb643a1..4f08cc67b6 100644 --- a/src/browser/shell_devtools_delegate.cc +++ b/src/browser/shell_devtools_delegate.cc @@ -21,11 +21,13 @@ #include "content/nw/src/browser/shell_devtools_delegate.h" #include "base/files/file_path.h" +#include "base/strings/utf_string_conversions.h" #include "content/nw/src/nw_shell.h" #include "content/public/browser/devtools_http_handler.h" +#include "content/public/browser/devtools_target.h" #include "content/public/browser/web_contents.h" #include "grit/nw_resources.h" -#include "net/base/tcp_listen_socket.h" +#include "net/socket/tcp_listen_socket.h" #include "net/url_request/url_request_context_getter.h" #include "ui/base/layout.h" #include "ui/base/resource/resource_bundle.h" @@ -38,7 +40,7 @@ ShellDevToolsDelegate::ShellDevToolsDelegate(BrowserContext* browser_context, devtools_http_handler_ = DevToolsHttpHandler::Start( new net::TCPListenSocketFactory("127.0.0.1", port), "", - this); + this, base::FilePath()); } ShellDevToolsDelegate::~ShellDevToolsDelegate() { @@ -66,18 +68,91 @@ std::string ShellDevToolsDelegate::GetPageThumbnailData(const GURL& url) { return ""; } -RenderViewHost* ShellDevToolsDelegate::CreateNewTarget() { +scoped_ptr +ShellDevToolsDelegate::CreateSocketForTethering( + net::StreamListenSocket::Delegate* delegate, + std::string* name) { + return scoped_ptr(); +} + +const char kTargetTypePage[] = "page"; + +class Target : public content::DevToolsTarget { + public: + explicit Target(WebContents* web_contents); + + virtual std::string GetId() const OVERRIDE { return id_; } + virtual std::string GetParentId() const OVERRIDE { return std::string(); } + virtual std::string GetType() const OVERRIDE { return kTargetTypePage; } + virtual std::string GetTitle() const OVERRIDE { return title_; } + virtual std::string GetDescription() const OVERRIDE { return description_; } + virtual GURL GetURL() const OVERRIDE { return url_; } + virtual GURL GetFaviconURL() const OVERRIDE { return GURL(); } + virtual base::TimeTicks GetLastActivityTime() const OVERRIDE { + return last_activity_time_; + } + virtual bool IsAttached() const OVERRIDE { + return agent_host_->IsAttached(); + } + virtual scoped_refptr GetAgentHost() const OVERRIDE { + return agent_host_; + } + virtual bool Activate() const OVERRIDE; + virtual bool Close() const OVERRIDE; + + private: + scoped_refptr agent_host_; + std::string id_; + std::string title_; + std::string description_; + GURL url_; + base::TimeTicks last_activity_time_; +}; + +Target::Target(WebContents* web_contents) { + agent_host_ = + DevToolsAgentHost::GetOrCreateFor(web_contents); + id_ = agent_host_->GetId(); + title_ = base::UTF16ToUTF8(web_contents->GetTitle()); + url_ = web_contents->GetURL(); + last_activity_time_ = web_contents->GetLastActiveTime(); +} + +bool Target::Activate() const { + WebContents* web_contents = agent_host_->GetWebContents(); + if (!web_contents) + return false; + web_contents->GetDelegate()->ActivateContents(web_contents); + return true; +} + +bool Target::Close() const { + WebContents* web_contents = agent_host_->GetWebContents(); + if (!web_contents) + return false; + web_contents->GetRenderViewHost()->ClosePage(); + return true; +} + +void ShellDevToolsDelegate::EnumerateTargets(TargetCallback callback) { + TargetList targets; + std::vector wc_list = + content::DevToolsAgentHost::GetInspectableWebContents(); + for (std::vector::iterator it = wc_list.begin(); + it != wc_list.end(); + ++it) { + targets.push_back(new Target(*it)); + } + callback.Run(targets); +} + +scoped_ptr ShellDevToolsDelegate::CreateNewTarget(const GURL& url) { Shell* shell = Shell::Create(browser_context_, GURL("nw:blank"), NULL, MSG_ROUTING_NONE, NULL); - return shell->web_contents()->GetRenderViewHost(); -} - -DevToolsHttpHandlerDelegate::TargetType -ShellDevToolsDelegate::GetTargetType(RenderViewHost*) { - return kTargetTypeTab; + return scoped_ptr(new Target(shell->web_contents())); } } // namespace content diff --git a/src/browser/shell_devtools_delegate.h b/src/browser/shell_devtools_delegate.h index c4ad918b7a..cdde5b7f7f 100644 --- a/src/browser/shell_devtools_delegate.h +++ b/src/browser/shell_devtools_delegate.h @@ -25,7 +25,10 @@ #include "base/basictypes.h" #include "base/compiler_specific.h" +#include "content/public/browser/devtools_agent_host.h" #include "content/public/browser/devtools_http_handler_delegate.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" namespace base { class FilePath; @@ -45,12 +48,15 @@ class ShellDevToolsDelegate : public DevToolsHttpHandlerDelegate { void Stop(); // DevToolsHttpProtocolHandler::Delegate overrides. - virtual std::string GetDiscoveryPageHTML() OVERRIDE; - virtual bool BundlesFrontendResources() OVERRIDE; - virtual base::FilePath GetDebugFrontendDir() OVERRIDE; - virtual std::string GetPageThumbnailData(const GURL& url) OVERRIDE; - virtual RenderViewHost* CreateNewTarget() OVERRIDE; - virtual TargetType GetTargetType(RenderViewHost*) OVERRIDE; + virtual std::string GetDiscoveryPageHTML() override; + virtual bool BundlesFrontendResources() override; + virtual base::FilePath GetDebugFrontendDir() override; + virtual std::string GetPageThumbnailData(const GURL& url) override; + virtual scoped_ptr CreateNewTarget(const GURL& url) override; + virtual void EnumerateTargets(TargetCallback callback) override; + virtual scoped_ptr CreateSocketForTethering( + net::StreamListenSocket::Delegate* delegate, + std::string* name) override; DevToolsHttpHandler* devtools_http_handler() { return devtools_http_handler_; diff --git a/src/browser/shell_devtools_manager_delegate.cc b/src/browser/shell_devtools_manager_delegate.cc new file mode 100644 index 0000000000..967bd0e410 --- /dev/null +++ b/src/browser/shell_devtools_manager_delegate.cc @@ -0,0 +1,282 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/shell/browser/shell_devtools_manager_delegate.h" + +#include + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "content/public/browser/devtools_agent_host.h" +#include "content/public/browser/devtools_http_handler.h" +#include "content/public/browser/devtools_target.h" +#include "content/public/browser/favicon_status.h" +#include "content/public/browser/navigation_entry.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/content_switches.h" +#include "content/public/common/url_constants.h" +#include "content/public/common/user_agent.h" +#include "content/nw/src/nw_shell.h" +#include "grit/nw_resources.h" +#include "net/socket/tcp_server_socket.h" +#include "ui/base/resource/resource_bundle.h" + +#if defined(OS_ANDROID) +#include "content/public/browser/android/devtools_auth.h" +#include "net/socket/unix_domain_server_socket_posix.h" +#endif + +using base::CommandLine; + +namespace content { + +namespace { + +#if defined(OS_ANDROID) +const char kFrontEndURL[] = + "http://chrome-devtools-frontend.appspot.com/serve_rev/%s/inspector.html"; +#endif +const char kTargetTypePage[] = "page"; +const char kTargetTypeServiceWorker[] = "service_worker"; +const char kTargetTypeOther[] = "other"; + +#if defined(OS_ANDROID) +class UnixDomainServerSocketFactory + : public DevToolsHttpHandler::ServerSocketFactory { + public: + explicit UnixDomainServerSocketFactory(const std::string& socket_name) + : DevToolsHttpHandler::ServerSocketFactory(socket_name, 0, 1) {} + + private: + // DevToolsHttpHandler::ServerSocketFactory. + virtual scoped_ptr Create() const override { + return scoped_ptr( + new net::UnixDomainServerSocket( + base::Bind(&CanUserConnectToDevTools), + true /* use_abstract_namespace */)); + } + + DISALLOW_COPY_AND_ASSIGN(UnixDomainServerSocketFactory); +}; +#else +class TCPServerSocketFactory + : public DevToolsHttpHandler::ServerSocketFactory { + public: + TCPServerSocketFactory(const std::string& address, uint16 port, int backlog) + : DevToolsHttpHandler::ServerSocketFactory( + address, port, backlog) {} + + private: + // DevToolsHttpHandler::ServerSocketFactory. + scoped_ptr Create() const override { + return scoped_ptr( + new net::TCPServerSocket(NULL, net::NetLog::Source())); + } + + DISALLOW_COPY_AND_ASSIGN(TCPServerSocketFactory); +}; +#endif + +scoped_ptr +CreateSocketFactory() { + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); +#if defined(OS_ANDROID) + std::string socket_name = "content_shell_devtools_remote"; + if (command_line.HasSwitch(switches::kRemoteDebuggingSocketName)) { + socket_name = command_line.GetSwitchValueASCII( + switches::kRemoteDebuggingSocketName); + } + return scoped_ptr( + new UnixDomainServerSocketFactory(socket_name)); +#else + // See if the user specified a port on the command line (useful for + // automation). If not, use an ephemeral port by specifying 0. + uint16 port = 0; + if (command_line.HasSwitch(switches::kRemoteDebuggingPort)) { + int temp_port; + std::string port_str = + command_line.GetSwitchValueASCII(switches::kRemoteDebuggingPort); + if (base::StringToInt(port_str, &temp_port) && + temp_port > 0 && temp_port < 65535) { + port = static_cast(temp_port); + } else { + DLOG(WARNING) << "Invalid http debugger port number " << temp_port; + } + } + return scoped_ptr( + new TCPServerSocketFactory("127.0.0.1", port, 1)); +#endif +} + +class Target : public DevToolsTarget { + public: + explicit Target(scoped_refptr agent_host); + + std::string GetId() const override { return agent_host_->GetId(); } + std::string GetParentId() const override { return std::string(); } + std::string GetType() const override { + switch (agent_host_->GetType()) { + case DevToolsAgentHost::TYPE_WEB_CONTENTS: + return kTargetTypePage; + case DevToolsAgentHost::TYPE_SERVICE_WORKER: + return kTargetTypeServiceWorker; + default: + break; + } + return kTargetTypeOther; + } + std::string GetTitle() const override { return agent_host_->GetTitle(); } + std::string GetDescription() const override { return std::string(); } + GURL GetURL() const override { return agent_host_->GetURL(); } + GURL GetFaviconURL() const override { return favicon_url_; } + base::TimeTicks GetLastActivityTime() const override { + return last_activity_time_; + } + bool IsAttached() const override { return agent_host_->IsAttached(); } + scoped_refptr GetAgentHost() const override { + return agent_host_; + } + bool Activate() const override; + bool Close() const override; + + private: + scoped_refptr agent_host_; + GURL favicon_url_; + base::TimeTicks last_activity_time_; +}; + +Target::Target(scoped_refptr agent_host) + : agent_host_(agent_host) { + if (WebContents* web_contents = agent_host_->GetWebContents()) { + NavigationController& controller = web_contents->GetController(); + NavigationEntry* entry = controller.GetActiveEntry(); + if (entry != NULL && entry->GetURL().is_valid()) + favicon_url_ = entry->GetFavicon().url; + last_activity_time_ = web_contents->GetLastActiveTime(); + } +} + +bool Target::Activate() const { + return agent_host_->Activate(); +} + +bool Target::Close() const { + return agent_host_->Close(); +} + +// ShellDevToolsDelegate ---------------------------------------------------- + +class ShellDevToolsDelegate : public DevToolsHttpHandlerDelegate { + public: + explicit ShellDevToolsDelegate(BrowserContext* browser_context); + ~ShellDevToolsDelegate() override; + + // DevToolsHttpHandlerDelegate implementation. + std::string GetDiscoveryPageHTML() override; + bool BundlesFrontendResources() override; + base::FilePath GetDebugFrontendDir() override; + scoped_ptr CreateSocketForTethering( + std::string* name) override; + + private: + BrowserContext* browser_context_; + + DISALLOW_COPY_AND_ASSIGN(ShellDevToolsDelegate); +}; + +ShellDevToolsDelegate::ShellDevToolsDelegate(BrowserContext* browser_context) + : browser_context_(browser_context) { +} + +ShellDevToolsDelegate::~ShellDevToolsDelegate() { +} + +std::string ShellDevToolsDelegate::GetDiscoveryPageHTML() { +#if defined(OS_ANDROID) + return std::string(); +#else + return ResourceBundle::GetSharedInstance().GetRawDataResource( + IDR_NW_DEVTOOLS_DISCOVERY_PAGE).as_string(); +#endif +} + +bool ShellDevToolsDelegate::BundlesFrontendResources() { +#if defined(OS_ANDROID) + return false; +#else + return true; +#endif +} + +base::FilePath ShellDevToolsDelegate::GetDebugFrontendDir() { + return base::FilePath(); +} + +scoped_ptr +ShellDevToolsDelegate::CreateSocketForTethering(std::string* name) { + return scoped_ptr(); +} + +} // namespace + +// ShellDevToolsManagerDelegate ---------------------------------------------- + +// static +DevToolsHttpHandler* +ShellDevToolsManagerDelegate::CreateHttpHandler( + BrowserContext* browser_context) { + std::string frontend_url; +#if defined(OS_ANDROID) + frontend_url = base::StringPrintf(kFrontEndURL, GetWebKitRevision().c_str()); +#endif + return DevToolsHttpHandler::Start(CreateSocketFactory(), + frontend_url, + new ShellDevToolsDelegate(browser_context), + base::FilePath()); +} + +ShellDevToolsManagerDelegate::ShellDevToolsManagerDelegate( + BrowserContext* browser_context) + : browser_context_(browser_context) { +} + +ShellDevToolsManagerDelegate::~ShellDevToolsManagerDelegate() { +} + +base::DictionaryValue* ShellDevToolsManagerDelegate::HandleCommand( + DevToolsAgentHost* agent_host, + base::DictionaryValue* command) { + return NULL; +} + +std::string ShellDevToolsManagerDelegate::GetPageThumbnailData( + const GURL& url) { + return std::string(); +} + +scoped_ptr +ShellDevToolsManagerDelegate::CreateNewTarget(const GURL& url) { + Shell* shell = Shell::Create(browser_context_, + url, + NULL, + MSG_ROUTING_NONE, + NULL); + return scoped_ptr( + new Target(DevToolsAgentHost::GetOrCreateFor(shell->web_contents()))); +} + +void ShellDevToolsManagerDelegate::EnumerateTargets(TargetCallback callback) { + TargetList targets; + for (const auto& agent_host : DevToolsAgentHost::GetOrCreateAll()) { + targets.push_back(new Target(agent_host)); + } + callback.Run(targets); +} + +} // namespace content diff --git a/src/browser/shell_devtools_manager_delegate.h b/src/browser/shell_devtools_manager_delegate.h new file mode 100644 index 0000000000..061562d9de --- /dev/null +++ b/src/browser/shell_devtools_manager_delegate.h @@ -0,0 +1,45 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_SHELL_BROWSER_SHELL_DEVTOOLS_MANAGER_DELEGATE_H_ +#define CONTENT_SHELL_BROWSER_SHELL_DEVTOOLS_MANAGER_DELEGATE_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "content/public/browser/devtools_http_handler_delegate.h" +#include "content/public/browser/devtools_manager_delegate.h" + +namespace content { + +class BrowserContext; +class DevToolsHttpHandler; + +class ShellDevToolsManagerDelegate : public DevToolsManagerDelegate { + public: + static DevToolsHttpHandler* CreateHttpHandler( + BrowserContext* browser_context); + + explicit ShellDevToolsManagerDelegate(BrowserContext* browser_context); + ~ShellDevToolsManagerDelegate() override; + + // DevToolsManagerDelegate implementation. + void Inspect(BrowserContext* browser_context, + DevToolsAgentHost* agent_host) override {} + void DevToolsAgentStateChanged(DevToolsAgentHost* agent_host, + bool attached) override {} + base::DictionaryValue* HandleCommand(DevToolsAgentHost* agent_host, + base::DictionaryValue* command) override; + scoped_ptr CreateNewTarget(const GURL& url) override; + void EnumerateTargets(TargetCallback callback) override; + std::string GetPageThumbnailData(const GURL& url) override; + + private: + BrowserContext* browser_context_; + + DISALLOW_COPY_AND_ASSIGN(ShellDevToolsManagerDelegate); +}; + +} // namespace content + +#endif // CONTENT_SHELL_BROWSER_SHELL_DEVTOOLS_MANAGER_DELEGATE_H_ diff --git a/src/browser/shell_display_info_provider.cc b/src/browser/shell_display_info_provider.cc new file mode 100644 index 0000000000..a86173fa93 --- /dev/null +++ b/src/browser/shell_display_info_provider.cc @@ -0,0 +1,38 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/shell/browser/shell_display_info_provider.h" + +namespace extensions { + +ShellDisplayInfoProvider::ShellDisplayInfoProvider() { +} + +ShellDisplayInfoProvider::~ShellDisplayInfoProvider() { +} + +bool ShellDisplayInfoProvider::SetInfo( + const std::string& display_id, + const core_api::system_display::DisplayProperties& info, + std::string* error) { + *error = "Not implemented"; + return false; +} + +void ShellDisplayInfoProvider::UpdateDisplayUnitInfoForPlatform( + const gfx::Display& display, + extensions::core_api::system_display::DisplayUnitInfo* unit) { + NOTIMPLEMENTED(); +} + +gfx::Screen* ShellDisplayInfoProvider::GetActiveScreen() { + return NULL; +} + +// static +DisplayInfoProvider* DisplayInfoProvider::Create() { + return new ShellDisplayInfoProvider(); +} + +} // namespace extensions diff --git a/src/browser/shell_display_info_provider.h b/src/browser/shell_display_info_provider.h new file mode 100644 index 0000000000..61cea1cc7f --- /dev/null +++ b/src/browser/shell_display_info_provider.h @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_DISPLAY_INFO_PROVIDER_H_ +#define EXTENSIONS_SHELL_BROWSER_SHELL_DISPLAY_INFO_PROVIDER_H_ + +#include "extensions/browser/api/system_display/display_info_provider.h" + +namespace extensions { + +class ShellDisplayInfoProvider : public DisplayInfoProvider { + public: + ShellDisplayInfoProvider(); + ~ShellDisplayInfoProvider() override; + + // DisplayInfoProvider implementation. + bool SetInfo(const std::string& display_id, + const core_api::system_display::DisplayProperties& info, + std::string* error) override; + void UpdateDisplayUnitInfoForPlatform( + const gfx::Display& display, + extensions::core_api::system_display::DisplayUnitInfo* unit) override; + gfx::Screen* GetActiveScreen() override; + + private: + DISALLOW_COPY_AND_ASSIGN(ShellDisplayInfoProvider); +}; + +} // namespace extensions + +#endif // EXTENSIONS_SHELL_BROWSER_SHELL_DISPLAY_INFO_PROVIDER_H_ diff --git a/src/browser/shell_download_manager_delegate.cc b/src/browser/shell_download_manager_delegate.cc index 039c0c71c2..f5dd1b73d0 100644 --- a/src/browser/shell_download_manager_delegate.cc +++ b/src/browser/shell_download_manager_delegate.cc @@ -30,15 +30,15 @@ #endif #include "base/bind.h" -#include "base/file_util.h" +#include "base/files/file_util.h" #include "base/logging.h" -#include "base/string_util.h" -#include "base/utf_string_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/download_manager.h" #include "content/public/browser/web_contents.h" -#include "content/public/browser/web_contents_view.h" +#include "net/base/filename_util.h" #include "net/base/net_util.h" using base::FilePath; @@ -87,7 +87,7 @@ bool ShellDownloadManagerDelegate::DetermineDownloadTarget( FilePath generated_name = net::GenerateFileName( download->GetURL(), download->GetContentDisposition(), - EmptyString(), + base::EmptyString(), download->GetSuggestedFilename(), download->GetMimeType(), "download"); @@ -102,14 +102,20 @@ bool ShellDownloadManagerDelegate::DetermineDownloadTarget( return true; } +bool ShellDownloadManagerDelegate::ShouldOpenDownload( + DownloadItem* item, + const DownloadOpenDelayedCallback& callback) { + return true; +} + void ShellDownloadManagerDelegate::GenerateFilename( int32 download_id, const DownloadTargetCallback& callback, const FilePath& generated_name, const FilePath& suggested_directory) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); - if (!file_util::PathExists(suggested_directory)) - file_util::CreateDirectory(suggested_directory); + if (!base::PathExists(suggested_directory)) + base::CreateDirectory(suggested_directory); FilePath suggested_path(suggested_directory.Append(generated_name)); BrowserThread::PostTask( @@ -142,4 +148,10 @@ void ShellDownloadManagerDelegate::SetDownloadBehaviorForTesting( suppress_prompting_ = true; } +void ShellDownloadManagerDelegate::GetNextId( + const DownloadIdCallback& callback) { + static uint32 next_id = DownloadItem::kInvalidId + 1; + callback.Run(next_id++); +} + } // namespace content diff --git a/src/browser/shell_download_manager_delegate.h b/src/browser/shell_download_manager_delegate.h index f9649e0e0f..c1b98c1886 100644 --- a/src/browser/shell_download_manager_delegate.h +++ b/src/browser/shell_download_manager_delegate.h @@ -25,34 +25,52 @@ #include "base/memory/ref_counted.h" #include "content/public/browser/download_manager_delegate.h" +#if defined(OS_WIN) +#include "ui/shell_dialogs/select_file_dialog.h" +#endif + namespace content { class DownloadManager; class ShellDownloadManagerDelegate : public DownloadManagerDelegate, +#if defined(OS_WIN) + public ui::SelectFileDialog::Listener, +#endif public base::RefCountedThreadSafe { public: ShellDownloadManagerDelegate(); void SetDownloadManager(DownloadManager* manager); - virtual void Shutdown() OVERRIDE; - virtual bool DetermineDownloadTarget( + void Shutdown() override; + bool DetermineDownloadTarget( DownloadItem* download, - const DownloadTargetCallback& callback) OVERRIDE; + const DownloadTargetCallback& callback) override; + bool ShouldOpenDownload( + DownloadItem* item, + const DownloadOpenDelayedCallback& callback) override; + void GetNextId(const DownloadIdCallback& callback) override; // Inhibits prompting and sets the default download path. void SetDownloadBehaviorForTesting( const base::FilePath& default_download_path); +#if defined(OS_WIN) + virtual void FileSelected( + const base::FilePath& path, int index, void* params) override; + virtual void FileSelectionCanceled(void* params) override; +#endif protected: // To allow subclasses for testing. - virtual ~ShellDownloadManagerDelegate(); + ~ShellDownloadManagerDelegate() final; private: friend class base::RefCountedThreadSafe; + typedef base::Callback + FilenameDeterminedCallback; void GenerateFilename(int32 download_id, const DownloadTargetCallback& callback, @@ -64,10 +82,15 @@ class ShellDownloadManagerDelegate void ChooseDownloadPath(int32 download_id, const DownloadTargetCallback& callback, const base::FilePath& suggested_path); + void OnFileSelected(const base::FilePath& path); DownloadManager* download_manager_; base::FilePath default_download_path_; bool suppress_prompting_; +#if defined(OS_WIN) + DownloadTargetCallback callback_; + scoped_refptr select_file_dialog_; +#endif DISALLOW_COPY_AND_ASSIGN(ShellDownloadManagerDelegate); }; diff --git a/src/browser/shell_download_manager_delegate_gtk.cc b/src/browser/shell_download_manager_delegate_gtk.cc index b2cb393ff2..b782e30546 100644 --- a/src/browser/shell_download_manager_delegate_gtk.cc +++ b/src/browser/shell_download_manager_delegate_gtk.cc @@ -20,20 +20,19 @@ #include "content/nw/src/browser/shell_download_manager_delegate.h" -#if defined(TOOLKIT_GTK) +#if defined(OS_LINUX) #include #endif #include "base/bind.h" -#include "base/file_util.h" +#include "base/files/file_util.h" #include "base/logging.h" -#include "base/string_util.h" -#include "base/utf_string_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/download_manager.h" #include "content/public/browser/web_contents.h" -#include "content/public/browser/web_contents_view.h" #include "net/base/net_util.h" using base::FilePath; @@ -51,12 +50,10 @@ void ShellDownloadManagerDelegate::ChooseDownloadPath( FilePath result; GtkWidget *dialog; - gfx::NativeWindow parent_window; std::string base_name = FilePath(suggested_path).BaseName().value(); - parent_window = item->GetWebContents()->GetView()->GetTopLevelNativeWindow(); dialog = gtk_file_chooser_dialog_new("Save File", - parent_window, + NULL, GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, diff --git a/src/browser/shell_download_manager_delegate_mac.mm b/src/browser/shell_download_manager_delegate_mac.mm index da4b4f9470..18d21a258a 100644 --- a/src/browser/shell_download_manager_delegate_mac.mm +++ b/src/browser/shell_download_manager_delegate_mac.mm @@ -24,16 +24,15 @@ #include #include "base/bind.h" -#include "base/file_util.h" +#include "base/files/file_util.h" #include "base/files/file_path.h" #include "base/logging.h" -#include "base/string_util.h" -#include "base/utf_string_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/download_manager.h" #include "content/public/browser/web_contents.h" -#include "content/public/browser/web_contents_view.h" #include "net/base/net_util.h" using base::FilePath; diff --git a/src/browser/shell_download_manager_delegate_win.cc b/src/browser/shell_download_manager_delegate_win.cc index 5fd716fe83..9d15e6e4fa 100644 --- a/src/browser/shell_download_manager_delegate_win.cc +++ b/src/browser/shell_download_manager_delegate_win.cc @@ -26,17 +26,20 @@ #endif #include "base/bind.h" -#include "base/file_util.h" +#include "base/files/file_util.h" #include "base/logging.h" -#include "base/string_util.h" -#include "base/utf_string_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/platform_util.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/download_manager.h" #include "content/public/browser/web_contents.h" -#include "content/public/browser/web_contents_view.h" #include "net/base/net_util.h" +#include "ui/aura/window.h" +#include "ui/aura/window_tree_host.h" + namespace content { void ShellDownloadManagerDelegate::ChooseDownloadPath( @@ -48,30 +51,47 @@ void ShellDownloadManagerDelegate::ChooseDownloadPath( if (!item || (item->GetState() != DownloadItem::IN_PROGRESS)) return; - base::FilePath result; - - std::wstring file_part = base::FilePath(suggested_path).BaseName().value(); - wchar_t file_name[MAX_PATH]; - base::wcslcpy(file_name, file_part.c_str(), arraysize(file_name)); - OPENFILENAME save_as; - ZeroMemory(&save_as, sizeof(save_as)); - save_as.lStructSize = sizeof(OPENFILENAME); - save_as.hwndOwner = item->GetWebContents()->GetView()->GetNativeView(); - save_as.lpstrFile = file_name; - save_as.nMaxFile = arraysize(file_name); + WebContents* web_contents = item->GetWebContents(); + select_file_dialog_ = ui::SelectFileDialog::Create(this, NULL); + ui::SelectFileDialog::FileTypeInfo file_type_info; + // Platform file pickers, notably on Mac and Windows, tend to break + // with double extensions like .tar.gz, so only pass in normal ones. + base::FilePath::StringType extension = suggested_path.FinalExtension(); + if (!extension.empty()) { + extension.erase(extension.begin()); // drop the . + file_type_info.extensions.resize(1); + file_type_info.extensions[0].push_back(extension); + } + file_type_info.include_all_files = true; + file_type_info.support_drive = true; + gfx::NativeWindow owning_window = web_contents ? + platform_util::GetTopLevel(web_contents->GetNativeView()) : NULL; - std::wstring directory; - if (!suggested_path.empty()) - directory = suggested_path.DirName().value(); + callback_ = callback; + base::FilePath working_path; + select_file_dialog_->SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE, + base::string16(), + suggested_path, + &file_type_info, + 0, + base::FilePath::StringType(), + owning_window, + NULL, working_path); +} - save_as.lpstrInitialDir = directory.c_str(); - save_as.Flags = OFN_OVERWRITEPROMPT | OFN_EXPLORER | OFN_ENABLESIZING | - OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST; +void ShellDownloadManagerDelegate::OnFileSelected(const base::FilePath& path) { + callback_.Run(path, DownloadItem::TARGET_DISPOSITION_PROMPT, + DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, path); +} - if (GetSaveFileName(&save_as)) - result = base::FilePath(std::wstring(save_as.lpstrFile)); +void ShellDownloadManagerDelegate::FileSelected(const base::FilePath& path, + int index, + void* params) { + OnFileSelected(path); +} - callback.Run(result, DownloadItem::TARGET_DISPOSITION_PROMPT, - DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, result); +void ShellDownloadManagerDelegate::FileSelectionCanceled(void* params) { + OnFileSelected(base::FilePath()); } + } // namespace content diff --git a/src/browser/shell_extension_host_delegate.cc b/src/browser/shell_extension_host_delegate.cc new file mode 100644 index 0000000000..c8df9cee7c --- /dev/null +++ b/src/browser/shell_extension_host_delegate.cc @@ -0,0 +1,67 @@ +// 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 "content/nw/src/browser/shell_extension_host_delegate.h" + +#include "base/logging.h" +#include "content/nw/src/api/dispatcher_host.h" +#include "extensions/browser/extension_host.h" +#include "extensions/shell/browser/media_capture_util.h" +#include "extensions/shell/browser/shell_extension_web_contents_observer.h" + +namespace extensions { + +ShellExtensionHostDelegate::ShellExtensionHostDelegate() { +} + +ShellExtensionHostDelegate::~ShellExtensionHostDelegate() { +} + +void ShellExtensionHostDelegate::OnExtensionHostCreated( + content::WebContents* web_contents) { + ShellExtensionWebContentsObserver::CreateForWebContents(web_contents); +} + +void ShellExtensionHostDelegate::OnRenderViewCreatedForBackgroundPage( + ExtensionHost* host) { + new nwapi::DispatcherHost(host->render_view_host()); +} + +content::JavaScriptDialogManager* +ShellExtensionHostDelegate::GetJavaScriptDialogManager() { + // TODO(jamescook): Create a JavaScriptDialogManager or reuse the one from + // content_shell. + NOTREACHED(); + return NULL; +} + +void ShellExtensionHostDelegate::CreateTab(content::WebContents* web_contents, + const std::string& extension_id, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture) { + // TODO(jamescook): Should app_shell support opening popup windows? + NOTREACHED(); +} + +void ShellExtensionHostDelegate::ProcessMediaAccessRequest( + content::WebContents* web_contents, + const content::MediaStreamRequest& request, + const content::MediaResponseCallback& callback, + const Extension* extension) { + // Allow access to the microphone and/or camera. + media_capture_util::GrantMediaStreamRequest( + web_contents, request, callback, extension); +} + +bool ShellExtensionHostDelegate::CheckMediaAccessPermission( + content::WebContents* web_contents, + const GURL& security_origin, + content::MediaStreamType type, + const Extension* extension) { + media_capture_util::VerifyMediaAccessPermission(type, extension); + return true; +} + +} // namespace extensions diff --git a/src/browser/shell_extension_host_delegate.h b/src/browser/shell_extension_host_delegate.h new file mode 100644 index 0000000000..4f7960163e --- /dev/null +++ b/src/browser/shell_extension_host_delegate.h @@ -0,0 +1,43 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_HOST_DELEGATE_H_ +#define EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_HOST_DELEGATE_H_ + +#include "base/macros.h" +#include "extensions/browser/extension_host_delegate.h" + +namespace extensions { + +// A minimal ExtensionHostDelegate. +class ShellExtensionHostDelegate : public ExtensionHostDelegate { + public: + ShellExtensionHostDelegate(); + ~ShellExtensionHostDelegate() override; + + // ExtensionHostDelegate implementation. + void OnExtensionHostCreated(content::WebContents* web_contents) override; + void OnRenderViewCreatedForBackgroundPage(ExtensionHost* host) override; + content::JavaScriptDialogManager* GetJavaScriptDialogManager() override; + void CreateTab(content::WebContents* web_contents, + const std::string& extension_id, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture) override; + void ProcessMediaAccessRequest(content::WebContents* web_contents, + const content::MediaStreamRequest& request, + const content::MediaResponseCallback& callback, + const Extension* extension) override; + bool CheckMediaAccessPermission(content::WebContents* web_contents, + const GURL& security_origin, + content::MediaStreamType type, + const Extension* extension) override; + + private: + DISALLOW_COPY_AND_ASSIGN(ShellExtensionHostDelegate); +}; + +} // namespace extensions + +#endif // EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_HOST_DELEGATE_H_ diff --git a/src/browser/shell_extension_system.cc b/src/browser/shell_extension_system.cc new file mode 100644 index 0000000000..99c428fb9a --- /dev/null +++ b/src/browser/shell_extension_system.cc @@ -0,0 +1,254 @@ +// 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 "content/nw/src/browser/shell_extension_system.h" + +#include + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/path_service.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_source.h" +#include "extensions/browser/api/app_runtime/app_runtime_api.h" +#include "extensions/browser/event_router.h" +#include "extensions/browser/extension_prefs.h" +#include "extensions/browser/extension_registry.h" +#include "extensions/browser/info_map.h" +#include "extensions/browser/lazy_background_task_queue.h" +#include "extensions/browser/notification_types.h" +#include "extensions/browser/quota_service.h" +#include "extensions/browser/runtime_data.h" +#include "extensions/common/constants.h" +#include "extensions/common/file_util.h" +#include "extensions/common/manifest_constants.h" + +#include "components/crx_file/id_util.h" + +#include "content/nw/src/nw_shell.h" +#include "content/nw/src/nw_package.h" + +using content::BrowserContext; +using content::BrowserThread; + +namespace extensions { + +ShellExtensionSystem::ShellExtensionSystem(BrowserContext* browser_context) + : browser_context_(browser_context) { +} + +ShellExtensionSystem::~ShellExtensionSystem() { +} + +const Extension* ShellExtensionSystem::LoadInternalApp() { + base::DictionaryValue manifest; + manifest.SetString("name", "node-webkit"); + manifest.SetString("version", "1"); + manifest.SetInteger("manifest_version", 2); + base::ListValue* list = new base::ListValue(); + list->Append(new base::StringValue("nw:*")); + list->Append(new base::StringValue("file://*")); + list->Append(new base::StringValue("app://*")); + + manifest.Set(extensions::manifest_keys::kWebURLs, list); + + scoped_ptr scripts(new base::ListValue); + scripts->AppendString("nwapp/background.js"); + nw::Package* package = content::Shell::GetPackage(); + std::string bg_script; + if (package->root()->GetString("bg-script", &bg_script)) + scripts->AppendString(bg_script); + + manifest.Set(extensions::manifest_keys::kPlatformAppBackgroundScripts, scripts.release()); + + base::ListValue* permission_list = new base::ListValue; + permission_list->Append(new base::StringValue("webview")); + permission_list->Append(new base::StringValue("webRequest")); + permission_list->Append(new base::StringValue("webRequestBlocking")); + permission_list->Append(new base::StringValue("")); + manifest.Set(extensions::manifest_keys::kPermissions, permission_list); + + std::string error; + base::FilePath path = package->path(); + //PathService::Get(base::FILE_EXE, &path); + scoped_refptr extension(Extension::Create( + path, Manifest::INTERNAL, manifest, Extension::NO_FLAGS, + // crx_file::id_util::GenerateId("io-blink"), + &error)); + + if (!extension.get()) { + LOG(ERROR) << "Loading internal extension " + << " failed with: " << error; + return nullptr; + } + + ExtensionRegistry::Get(browser_context_)->AddEnabled(extension.get()); + + RegisterExtensionWithRequestContexts(extension.get()); + + content::NotificationService::current()->Notify( + extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED, + content::Source(browser_context_), + content::Details(extension.get())); + + return extension.get(); +} + +const Extension* ShellExtensionSystem::LoadApp(const base::FilePath& app_dir) { + // app_shell only supports unpacked extensions. + // NOTE: If you add packed extension support consider removing the flag + // FOLLOW_SYMLINKS_ANYWHERE below. Packed extensions should not have symlinks. + CHECK(base::DirectoryExists(app_dir)) << app_dir.AsUTF8Unsafe(); + int load_flags = Extension::FOLLOW_SYMLINKS_ANYWHERE; + std::string load_error; + scoped_refptr extension = file_util::LoadExtension( + app_dir, Manifest::COMMAND_LINE, load_flags, &load_error); + if (!extension.get()) { + LOG(ERROR) << "Loading extension at " << app_dir.value() + << " failed with: " << load_error; + return nullptr; + } + + // TODO(jamescook): We may want to do some of these things here: + // * Create a PermissionsUpdater. + // * Call PermissionsUpdater::GrantActivePermissions(). + // * Call ExtensionService::SatisfyImports(). + // * Call ExtensionPrefs::OnExtensionInstalled(). + // * Send NOTIFICATION_EXTENSION_WILL_BE_INSTALLED_DEPRECATED. + + ExtensionRegistry::Get(browser_context_)->AddEnabled(extension.get()); + + RegisterExtensionWithRequestContexts(extension.get()); + + content::NotificationService::current()->Notify( + extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED, + content::Source(browser_context_), + content::Details(extension.get())); + + return extension.get(); +} + +void ShellExtensionSystem::Init() { + // Inform the rest of the extensions system to start. + ready_.Signal(); + content::NotificationService::current()->Notify( + extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED, + content::Source(browser_context_), + content::NotificationService::NoDetails()); +} + +void ShellExtensionSystem::LaunchApp(const ExtensionId& extension_id) { + // Send the onLaunched event. + DCHECK(ExtensionRegistry::Get(browser_context_) + ->enabled_extensions() + .Contains(extension_id)); + const Extension* extension = ExtensionRegistry::Get(browser_context_) + ->enabled_extensions() + .GetByID(extension_id); + AppRuntimeEventRouter::DispatchOnLaunchedEvent( + browser_context_, extension, extensions::SOURCE_UNTRACKED); +} + +void ShellExtensionSystem::Shutdown() { +} + +void ShellExtensionSystem::InitForRegularProfile(bool extensions_enabled) { + runtime_data_.reset( + new RuntimeData(ExtensionRegistry::Get(browser_context_))); + lazy_background_task_queue_.reset( + new LazyBackgroundTaskQueue(browser_context_)); + event_router_.reset( + new EventRouter(browser_context_, ExtensionPrefs::Get(browser_context_))); + quota_service_.reset(new QuotaService); +} + +ExtensionService* ShellExtensionSystem::extension_service() { + return NULL; +} + +RuntimeData* ShellExtensionSystem::runtime_data() { + return runtime_data_.get(); +} + +ManagementPolicy* ShellExtensionSystem::management_policy() { + return NULL; +} + +SharedUserScriptMaster* ShellExtensionSystem::shared_user_script_master() { + return NULL; +} + +DeclarativeUserScriptManager* +ShellExtensionSystem::declarative_user_script_manager() { + return nullptr; +} + +StateStore* ShellExtensionSystem::state_store() { + return NULL; +} + +StateStore* ShellExtensionSystem::rules_store() { + return NULL; +} + +InfoMap* ShellExtensionSystem::info_map() { + if (!info_map_.get()) + info_map_ = new InfoMap; + return info_map_.get(); +} + +LazyBackgroundTaskQueue* ShellExtensionSystem::lazy_background_task_queue() { + return lazy_background_task_queue_.get(); +} + +EventRouter* ShellExtensionSystem::event_router() { + return event_router_.get(); +} + +ErrorConsole* ShellExtensionSystem::error_console() { + return NULL; +} + +InstallVerifier* ShellExtensionSystem::install_verifier() { + return NULL; +} + +QuotaService* ShellExtensionSystem::quota_service() { + return quota_service_.get(); +} + +void ShellExtensionSystem::RegisterExtensionWithRequestContexts( + const Extension* extension) { + BrowserThread::PostTask(BrowserThread::IO, + FROM_HERE, + base::Bind(&InfoMap::AddExtension, + info_map(), + make_scoped_refptr(extension), + base::Time::Now(), + false, + false)); +} + +void ShellExtensionSystem::UnregisterExtensionWithRequestContexts( + const std::string& extension_id, + const UnloadedExtensionInfo::Reason reason) { +} + +const OneShotEvent& ShellExtensionSystem::ready() const { + return ready_; +} + +ContentVerifier* ShellExtensionSystem::content_verifier() { + return NULL; +} + +scoped_ptr ShellExtensionSystem::GetDependentExtensions( + const Extension* extension) { + return make_scoped_ptr(new ExtensionSet()); +} + +} // namespace extensions diff --git a/src/browser/shell_extension_system.h b/src/browser/shell_extension_system.h new file mode 100644 index 0000000000..8395576975 --- /dev/null +++ b/src/browser/shell_extension_system.h @@ -0,0 +1,99 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_SYSTEM_H_ +#define EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_SYSTEM_H_ + +#include + +#include "base/compiler_specific.h" +#include "extensions/browser/extension_system.h" +#include "extensions/common/one_shot_event.h" + +class BrowserContextKeyedServiceFactory; + +namespace base { +class FilePath; +} + +namespace content { +class BrowserContext; +} + +namespace extensions { + +class DeclarativeUserScriptMaster; +class EventRouter; +class InfoMap; +class LazyBackgroundTaskQueue; +class ProcessManager; +class RendererStartupHelper; +class SharedUserScriptMaster; + +// A simplified version of ExtensionSystem for app_shell. Allows +// app_shell to skip initialization of services it doesn't need. +class ShellExtensionSystem : public ExtensionSystem { + public: + ShellExtensionSystem(content::BrowserContext* browser_context); + ~ShellExtensionSystem() override; + + // Loads an unpacked application from a directory. Returns the extension on + // success, or null otherwise. + const Extension* LoadApp(const base::FilePath& app_dir); + const Extension* LoadInternalApp(); + + // Initializes the extension system. + void Init(); + + // Launch the app with id |extension_id|. + void LaunchApp(const std::string& extension_id); + + // KeyedService implementation: + void Shutdown() override; + + // ExtensionSystem implementation: + void InitForRegularProfile(bool extensions_enabled) override; + ExtensionService* extension_service() override; + RuntimeData* runtime_data() override; + ManagementPolicy* management_policy() override; + SharedUserScriptMaster* shared_user_script_master() override; + DeclarativeUserScriptManager* declarative_user_script_manager() override; + StateStore* state_store() override; + StateStore* rules_store() override; + InfoMap* info_map() override; + LazyBackgroundTaskQueue* lazy_background_task_queue() override; + EventRouter* event_router() override; + ErrorConsole* error_console() override; + InstallVerifier* install_verifier() override; + QuotaService* quota_service() override; + void RegisterExtensionWithRequestContexts( + const Extension* extension) override; + void UnregisterExtensionWithRequestContexts( + const std::string& extension_id, + const UnloadedExtensionInfo::Reason reason) override; + const OneShotEvent& ready() const override; + ContentVerifier* content_verifier() override; + scoped_ptr GetDependentExtensions( + const Extension* extension) override; + + private: + content::BrowserContext* browser_context_; // Not owned. + + // Data to be accessed on the IO thread. Must outlive process_manager_. + scoped_refptr info_map_; + + scoped_ptr runtime_data_; + scoped_ptr lazy_background_task_queue_; + scoped_ptr event_router_; + scoped_ptr quota_service_; + + // Signaled when the extension system has completed its startup tasks. + OneShotEvent ready_; + + DISALLOW_COPY_AND_ASSIGN(ShellExtensionSystem); +}; + +} // namespace extensions + +#endif // EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_SYSTEM_H_ diff --git a/src/browser/shell_extension_system_factory.cc b/src/browser/shell_extension_system_factory.cc new file mode 100644 index 0000000000..68bdc377a1 --- /dev/null +++ b/src/browser/shell_extension_system_factory.cc @@ -0,0 +1,52 @@ +// 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 "content/nw/src/browser/shell_extension_system_factory.h" + +#include "components/keyed_service/content/browser_context_dependency_manager.h" +#include "extensions/browser/extension_prefs_factory.h" +#include "extensions/browser/extension_registry_factory.h" +#include "content/nw/src/browser/shell_extension_system.h" + +using content::BrowserContext; + +namespace extensions { + +ExtensionSystem* ShellExtensionSystemFactory::GetForBrowserContext( + BrowserContext* context) { + return static_cast( + GetInstance()->GetServiceForBrowserContext(context, true)); +} + +// static +ShellExtensionSystemFactory* ShellExtensionSystemFactory::GetInstance() { + return Singleton::get(); +} + +ShellExtensionSystemFactory::ShellExtensionSystemFactory() + : ExtensionSystemProvider("ShellExtensionSystem", + BrowserContextDependencyManager::GetInstance()) { + DependsOn(ExtensionPrefsFactory::GetInstance()); + DependsOn(ExtensionRegistryFactory::GetInstance()); +} + +ShellExtensionSystemFactory::~ShellExtensionSystemFactory() { +} + +KeyedService* ShellExtensionSystemFactory::BuildServiceInstanceFor( + BrowserContext* context) const { + return new ShellExtensionSystem(context); +} + +BrowserContext* ShellExtensionSystemFactory::GetBrowserContextToUse( + BrowserContext* context) const { + // Use a separate instance for incognito. + return context; +} + +bool ShellExtensionSystemFactory::ServiceIsCreatedWithBrowserContext() const { + return true; +} + +} // namespace extensions diff --git a/src/browser/shell_extension_system_factory.h b/src/browser/shell_extension_system_factory.h new file mode 100644 index 0000000000..5478d81628 --- /dev/null +++ b/src/browser/shell_extension_system_factory.h @@ -0,0 +1,40 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_SYSTEM_FACTORY_H_ +#define EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_SYSTEM_FACTORY_H_ + +#include "base/memory/singleton.h" +#include "extensions/browser/extension_system_provider.h" + +namespace extensions { + +// A factory that provides ShellExtensionSystem for app_shell. +class ShellExtensionSystemFactory : public ExtensionSystemProvider { + public: + // ExtensionSystemProvider implementation: + ExtensionSystem* GetForBrowserContext( + content::BrowserContext* context) override; + + static ShellExtensionSystemFactory* GetInstance(); + + private: + friend struct DefaultSingletonTraits; + + ShellExtensionSystemFactory(); + ~ShellExtensionSystemFactory() override; + + // BrowserContextKeyedServiceFactory implementation: + KeyedService* BuildServiceInstanceFor( + content::BrowserContext* context) const override; + content::BrowserContext* GetBrowserContextToUse( + content::BrowserContext* context) const override; + bool ServiceIsCreatedWithBrowserContext() const override; + + DISALLOW_COPY_AND_ASSIGN(ShellExtensionSystemFactory); +}; + +} // namespace extensions + +#endif // EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_SYSTEM_FACTORY_H_ diff --git a/src/browser/shell_extension_web_contents_observer.cc b/src/browser/shell_extension_web_contents_observer.cc new file mode 100644 index 0000000000..9dc369103d --- /dev/null +++ b/src/browser/shell_extension_web_contents_observer.cc @@ -0,0 +1,20 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/shell/browser/shell_extension_web_contents_observer.h" + +DEFINE_WEB_CONTENTS_USER_DATA_KEY( + extensions::ShellExtensionWebContentsObserver); + +namespace extensions { + +ShellExtensionWebContentsObserver::ShellExtensionWebContentsObserver( + content::WebContents* web_contents) + : ExtensionWebContentsObserver(web_contents) { +} + +ShellExtensionWebContentsObserver::~ShellExtensionWebContentsObserver() { +} + +} // namespace extensions diff --git a/src/browser/shell_extension_web_contents_observer.h b/src/browser/shell_extension_web_contents_observer.h new file mode 100644 index 0000000000..a2a187fdae --- /dev/null +++ b/src/browser/shell_extension_web_contents_observer.h @@ -0,0 +1,29 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_WEB_CONTENTS_OBSERVER_H_ +#define EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_WEB_CONTENTS_OBSERVER_H_ + +#include "content/public/browser/web_contents_user_data.h" +#include "extensions/browser/extension_web_contents_observer.h" + +namespace extensions { + +// The app_shell version of ExtensionWebContentsObserver. +class ShellExtensionWebContentsObserver + : public ExtensionWebContentsObserver, + public content::WebContentsUserData { + private: + friend class content::WebContentsUserData; + + explicit ShellExtensionWebContentsObserver( + content::WebContents* web_contents); + ~ShellExtensionWebContentsObserver() override; + + DISALLOW_COPY_AND_ASSIGN(ShellExtensionWebContentsObserver); +}; + +} // namespace extensions + +#endif // EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_WEB_CONTENTS_OBSERVER_H_ diff --git a/src/browser/shell_extensions_api_client.cc b/src/browser/shell_extensions_api_client.cc new file mode 100644 index 0000000000..8be9f9e3c1 --- /dev/null +++ b/src/browser/shell_extensions_api_client.cc @@ -0,0 +1,97 @@ +// 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 "content/nw/src/browser/shell_extensions_api_client.h" + +#include "base/files/file_path.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "content/nw/src/browser/shell_web_view_guest_delegate.h" +#include "extensions/browser/api/device_permissions_prompt.h" +#include "extensions/browser/api/virtual_keyboard_private/virtual_keyboard_delegate.h" +#include "extensions/browser/api/web_request/web_request_event_router_delegate.h" +#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest_delegate.h" +#include "extensions/browser/guest_view/web_view/web_view_permission_helper_delegate.h" +#include "extensions/browser/guest_view/web_view/web_view_guest.h" + +namespace extensions { + +ShellExtensionsAPIClient::ShellExtensionsAPIClient() {} + +ShellExtensionsAPIClient::~ShellExtensionsAPIClient() {} + +void ShellExtensionsAPIClient::AddAdditionalValueStoreCaches( + content::BrowserContext* context, + const scoped_refptr& factory, + const scoped_refptr >& observers, + std::map* caches) { +} + +AppViewGuestDelegate* ShellExtensionsAPIClient::CreateAppViewGuestDelegate() + const { + return NULL; +} + +void ShellWebViewGuestDelegate::OnShowContextMenu( + int request_id, + const MenuItemVector* items) { +} + +bool ShellWebViewGuestDelegate::HandleContextMenu( + const content::ContextMenuParams& params) { + return false; +} + +ExtensionOptionsGuestDelegate* +ShellExtensionsAPIClient::CreateExtensionOptionsGuestDelegate( + ExtensionOptionsGuest* guest) const { + return NULL; +} + +scoped_ptr +ShellExtensionsAPIClient::CreateMimeHandlerViewGuestDelegate( + MimeHandlerViewGuest* guest) const { + return scoped_ptr(); +} + +WebViewGuestDelegate* ShellExtensionsAPIClient::CreateWebViewGuestDelegate( + WebViewGuest* web_view_guest) const { + return new ShellWebViewGuestDelegate(web_view_guest); +} + +WebViewPermissionHelperDelegate* ShellExtensionsAPIClient:: + CreateWebViewPermissionHelperDelegate( + WebViewPermissionHelper* web_view_permission_helper) const { + return new WebViewPermissionHelperDelegate(web_view_permission_helper); +} + +WebRequestEventRouterDelegate* +ShellExtensionsAPIClient::CreateWebRequestEventRouterDelegate() const { + return new WebRequestEventRouterDelegate(); +} + +scoped_refptr +ShellExtensionsAPIClient::CreateContentRulesRegistry( + content::BrowserContext* browser_context, + RulesCacheDelegate* cache_delegate) const { + return scoped_refptr(); +} + +scoped_ptr +ShellExtensionsAPIClient::CreateDevicePermissionsPrompt( + content::WebContents* web_contents) const { + return nullptr; +} + +scoped_ptr +ShellExtensionsAPIClient::CreateVirtualKeyboardDelegate() const { + return nullptr; +} + +ManagementAPIDelegate* ShellExtensionsAPIClient::CreateManagementAPIDelegate() + const { + return nullptr; +} + +} // namespace extensions diff --git a/src/browser/shell_extensions_api_client.h b/src/browser/shell_extensions_api_client.h new file mode 100644 index 0000000000..d30432b7e3 --- /dev/null +++ b/src/browser/shell_extensions_api_client.h @@ -0,0 +1,52 @@ +// 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 NW_BROWSER_EXTENSIONS_API_CHROME_EXTENSIONS_API_CLIENT_H_ +#define NW_BROWSER_EXTENSIONS_API_CHROME_EXTENSIONS_API_CLIENT_H_ + +#include "base/compiler_specific.h" +#include "extensions/browser/api/extensions_api_client.h" + +namespace extensions { + +// Extra support for extensions APIs in Chrome. +class ShellExtensionsAPIClient : public ExtensionsAPIClient { + public: + ShellExtensionsAPIClient(); + ~ShellExtensionsAPIClient() override; + + // ExtensionsApiClient implementation. + void AddAdditionalValueStoreCaches( + content::BrowserContext* context, + const scoped_refptr& factory, + const scoped_refptr>& observers, + std::map* caches) + override; + AppViewGuestDelegate* CreateAppViewGuestDelegate() const override; + ExtensionOptionsGuestDelegate* CreateExtensionOptionsGuestDelegate( + ExtensionOptionsGuest* guest) const override; + scoped_ptr CreateMimeHandlerViewGuestDelegate( + MimeHandlerViewGuest* guest) const override; + WebViewGuestDelegate* CreateWebViewGuestDelegate( + WebViewGuest* web_view_guest) const override; + WebViewPermissionHelperDelegate* CreateWebViewPermissionHelperDelegate( + WebViewPermissionHelper* web_view_permission_helper) const override; + WebRequestEventRouterDelegate* CreateWebRequestEventRouterDelegate() + const override; + scoped_refptr CreateContentRulesRegistry( + content::BrowserContext* browser_context, + RulesCacheDelegate* cache_delegate) const override; + scoped_ptr CreateDevicePermissionsPrompt( + content::WebContents* web_contents) const override; + scoped_ptr CreateVirtualKeyboardDelegate() + const override; + ManagementAPIDelegate* CreateManagementAPIDelegate() const override; + + private: + DISALLOW_COPY_AND_ASSIGN(ShellExtensionsAPIClient); +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_CHROME_EXTENSIONS_API_CLIENT_H_ diff --git a/src/browser/shell_extensions_browser_client.cc b/src/browser/shell_extensions_browser_client.cc new file mode 100644 index 0000000000..034db57253 --- /dev/null +++ b/src/browser/shell_extensions_browser_client.cc @@ -0,0 +1,363 @@ +// 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 "content/nw/src/browser/shell_extensions_browser_client.h" + +#include "base/prefs/pref_service.h" +#include "base/prefs/pref_service_factory.h" +#include "base/prefs/testing_pref_store.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "components/pref_registry/pref_registry_syncable.h" +#include "components/user_prefs/user_prefs.h" +#include "content/public/browser/browser_thread.h" +#include "extensions/browser/api/extensions_api_client.h" +#include "extensions/browser/api/generated_api_registration.h" +#include "extensions/browser/app_sorting.h" +#include "extensions/browser/event_router.h" +#include "extensions/browser/extension_function_registry.h" +#include "extensions/browser/extension_prefs.h" +#include "extensions/browser/extension_protocols.h" +#include "extensions/browser/null_app_sorting.h" +#include "extensions/browser/updater/null_extension_cache.h" +#include "extensions/browser/url_request_util.h" +//#include "extensions/shell/browser/api/generated_api_registration.h" +#include "extensions/common/file_util.h" +#include "extensions/shell/browser/shell_extension_host_delegate.h" +#include "extensions/shell/browser/shell_extension_system_factory.h" +#include "extensions/shell/browser/shell_runtime_api_delegate.h" + +#include "content/nw/src/browser/shell_component_extension_resource_manager.h" +#include "content/nw/src/browser/shell_extensions_api_client.h" + +#include "net/base/mime_util.h" +#include "net/base/net_errors.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_response_info.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_simple_job.h" +#include "ui/base/resource/resource_bundle.h" + +using content::BrowserContext; +using content::BrowserThread; + +namespace { + +// A request for an extension resource in a Chrome .pak file. These are used +// by component extensions. +class URLRequestResourceBundleJob : public net::URLRequestSimpleJob { + public: + URLRequestResourceBundleJob(net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const base::FilePath& filename, + int resource_id, + const std::string& content_security_policy, + bool send_cors_header) + : net::URLRequestSimpleJob(request, network_delegate), + filename_(filename), + resource_id_(resource_id), + weak_factory_(this) { + // Leave cache headers out of resource bundle requests. + response_info_.headers = extensions::BuildHttpHeaders( + content_security_policy, send_cors_header, base::Time()); + } + + // Overridden from URLRequestSimpleJob: + int GetRefCountedData( + std::string* mime_type, + std::string* charset, + scoped_refptr* data, + const net::CompletionCallback& callback) const override { + const ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + *data = rb.LoadDataResourceBytes(resource_id_); + + // Add the Content-Length header now that we know the resource length. + response_info_.headers->AddHeader( + base::StringPrintf("%s: %s", net::HttpRequestHeaders::kContentLength, + base::UintToString((*data)->size()).c_str())); + + std::string* read_mime_type = new std::string; + bool posted = base::PostTaskAndReplyWithResult( + BrowserThread::GetBlockingPool(), FROM_HERE, + base::Bind(&net::GetMimeTypeFromFile, filename_, + base::Unretained(read_mime_type)), + base::Bind(&URLRequestResourceBundleJob::OnMimeTypeRead, + weak_factory_.GetWeakPtr(), mime_type, charset, *data, + base::Owned(read_mime_type), callback)); + DCHECK(posted); + + return net::ERR_IO_PENDING; + } + + void GetResponseInfo(net::HttpResponseInfo* info) override { + *info = response_info_; + } + + private: + ~URLRequestResourceBundleJob() override {} + + void OnMimeTypeRead(std::string* out_mime_type, + std::string* charset, + scoped_refptr data, + std::string* read_mime_type, + const net::CompletionCallback& callback, + bool read_result) { + *out_mime_type = *read_mime_type; + if (StartsWithASCII(*read_mime_type, "text/", false)) { + // All of our HTML files should be UTF-8 and for other resource types + // (like images), charset doesn't matter. + DCHECK(base::IsStringUTF8(base::StringPiece( + reinterpret_cast(data->front()), data->size()))); + *charset = "utf-8"; + } + int result = read_result ? net::OK : net::ERR_INVALID_URL; + callback.Run(result); + } + + // We need the filename of the resource to determine the mime type. + base::FilePath filename_; + + // The resource bundle id to load. + int resource_id_; + + net::HttpResponseInfo response_info_; + + mutable base::WeakPtrFactory weak_factory_; +}; + +} // namespace + + +namespace extensions { +namespace { + +// See chrome::RegisterProfilePrefs() in chrome/browser/prefs/browser_prefs.cc +void RegisterPrefs(user_prefs::PrefRegistrySyncable* registry) { + ExtensionPrefs::RegisterProfilePrefs(registry); +} + +} // namespace + +ShellExtensionsBrowserClient::ShellExtensionsBrowserClient( + BrowserContext* context) + : browser_context_(context), + api_client_(new ShellExtensionsAPIClient), + extension_cache_(new NullExtensionCache()) { + // Set up the preferences service. + base::PrefServiceFactory factory; + factory.set_user_prefs(new TestingPrefStore); + factory.set_extension_prefs(new TestingPrefStore); + + // TODO(jamescook): Convert this to PrefRegistrySimple. + user_prefs::PrefRegistrySyncable* pref_registry = + new user_prefs::PrefRegistrySyncable; + // Prefs should be registered before the PrefService is created. + RegisterPrefs(pref_registry); + prefs_ = factory.Create(pref_registry).Pass(); + user_prefs::UserPrefs::Set(browser_context_, prefs_.get()); +} + +ShellExtensionsBrowserClient::~ShellExtensionsBrowserClient() { +} + +bool ShellExtensionsBrowserClient::IsShuttingDown() { + return false; +} + +bool ShellExtensionsBrowserClient::AreExtensionsDisabled( + const base::CommandLine& command_line, + BrowserContext* context) { + return false; +} + +bool ShellExtensionsBrowserClient::IsValidContext(BrowserContext* context) { + return context == browser_context_; +} + +bool ShellExtensionsBrowserClient::IsSameContext(BrowserContext* first, + BrowserContext* second) { + return first == second; +} + +bool ShellExtensionsBrowserClient::HasOffTheRecordContext( + BrowserContext* context) { + return false; +} + +BrowserContext* ShellExtensionsBrowserClient::GetOffTheRecordContext( + BrowserContext* context) { + // app_shell only supports a single context. + return NULL; +} + +BrowserContext* ShellExtensionsBrowserClient::GetOriginalContext( + BrowserContext* context) { + return context; +} + +bool ShellExtensionsBrowserClient::IsGuestSession( + BrowserContext* context) const { + return false; +} + +bool ShellExtensionsBrowserClient::IsExtensionIncognitoEnabled( + const std::string& extension_id, + content::BrowserContext* context) const { + return false; +} + +bool ShellExtensionsBrowserClient::CanExtensionCrossIncognito( + const Extension* extension, + content::BrowserContext* context) const { + return false; +} + +net::URLRequestJob* +ShellExtensionsBrowserClient::MaybeCreateResourceBundleRequestJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const base::FilePath& directory_path, + const std::string& content_security_policy, + bool send_cors_header) { + base::FilePath relative_path; + base::FilePath request_path = + extensions::file_util::ExtensionURLToRelativeFilePath(request->url()); + int resource_id = 0; + if (ExtensionsBrowserClient::Get() + ->GetComponentExtensionResourceManager() + ->IsComponentExtensionResource( + directory_path, request_path, &resource_id)) { + relative_path = relative_path.Append(request_path); + relative_path = relative_path.NormalizePathSeparators(); + return new URLRequestResourceBundleJob(request, + network_delegate, + relative_path, + resource_id, + content_security_policy, + send_cors_header); + } + return NULL; +} + +bool ShellExtensionsBrowserClient::AllowCrossRendererResourceLoad( + net::URLRequest* request, + bool is_incognito, + const Extension* extension, + InfoMap* extension_info_map) { + bool allowed = false; + if (url_request_util::AllowCrossRendererResourceLoad( + request, is_incognito, extension, extension_info_map, &allowed)) { + return allowed; + } + + // Couldn't determine if resource is allowed. Block the load. + return false; +} + +PrefService* ShellExtensionsBrowserClient::GetPrefServiceForContext( + BrowserContext* context) { + return prefs_.get(); +} + +void ShellExtensionsBrowserClient::GetEarlyExtensionPrefsObservers( + content::BrowserContext* context, + std::vector* observers) const { +} + +ProcessManagerDelegate* +ShellExtensionsBrowserClient::GetProcessManagerDelegate() const { + return NULL; +} + +scoped_ptr +ShellExtensionsBrowserClient::CreateExtensionHostDelegate() { + return scoped_ptr(new ShellExtensionHostDelegate); +} + +bool ShellExtensionsBrowserClient::DidVersionUpdate(BrowserContext* context) { + // TODO(jamescook): We might want to tell extensions when app_shell updates. + return false; +} + +void ShellExtensionsBrowserClient::PermitExternalProtocolHandler() { +} + +scoped_ptr ShellExtensionsBrowserClient::CreateAppSorting() { + return scoped_ptr(new NullAppSorting); +} + +bool ShellExtensionsBrowserClient::IsRunningInForcedAppMode() { + return false; +} + +ApiActivityMonitor* ShellExtensionsBrowserClient::GetApiActivityMonitor( + BrowserContext* context) { + // app_shell doesn't monitor API function calls or events. + return NULL; +} + +ExtensionSystemProvider* +ShellExtensionsBrowserClient::GetExtensionSystemFactory() { + return ShellExtensionSystemFactory::GetInstance(); +} + +void ShellExtensionsBrowserClient::RegisterExtensionFunctions( + ExtensionFunctionRegistry* registry) const { + // Register core extension-system APIs. + core_api::GeneratedFunctionRegistry::RegisterAll(registry); + + //shell::api::GeneratedFunctionRegistry::RegisterAll(registry); +} + +scoped_ptr +ShellExtensionsBrowserClient::CreateRuntimeAPIDelegate( + content::BrowserContext* context) const { + return scoped_ptr(new ShellRuntimeAPIDelegate()); +} + +const ComponentExtensionResourceManager* +ShellExtensionsBrowserClient::GetComponentExtensionResourceManager() { + if (!resource_manager_) + resource_manager_.reset(new ShellComponentExtensionResourceManager()); + return resource_manager_.get(); +} + +void ShellExtensionsBrowserClient::BroadcastEventToRenderers( + const std::string& event_name, + scoped_ptr args) { + if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&ShellExtensionsBrowserClient::BroadcastEventToRenderers, + base::Unretained(this), + event_name, + base::Passed(&args))); + return; + } + + scoped_ptr event(new Event(event_name, args.Pass())); + EventRouter::Get(browser_context_)->BroadcastEvent(event.Pass()); +} + +net::NetLog* ShellExtensionsBrowserClient::GetNetLog() { + return NULL; +} + +ExtensionCache* ShellExtensionsBrowserClient::GetExtensionCache() { + return extension_cache_.get(); +} + +bool ShellExtensionsBrowserClient::IsBackgroundUpdateAllowed() { + return true; +} + +bool ShellExtensionsBrowserClient::IsMinBrowserVersionSupported( + const std::string& min_version) { + return true; +} + +} // namespace extensions diff --git a/src/browser/shell_extensions_browser_client.h b/src/browser/shell_extensions_browser_client.h new file mode 100644 index 0000000000..12fd6d398d --- /dev/null +++ b/src/browser/shell_extensions_browser_client.h @@ -0,0 +1,101 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSIONS_BROWSER_CLIENT_H_ +#define EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSIONS_BROWSER_CLIENT_H_ + +#include "base/compiler_specific.h" +#include "extensions/browser/extensions_browser_client.h" + +class PrefService; + +namespace extensions { + +class ExtensionsAPIClient; +class ShellComponentExtensionResourceManager; +// An ExtensionsBrowserClient that supports a single content::BrowserContent +// with no related incognito context. +class ShellExtensionsBrowserClient : public ExtensionsBrowserClient { + public: + // |context| is the single BrowserContext used for IsValidContext() below. + explicit ShellExtensionsBrowserClient(content::BrowserContext* context); + ~ShellExtensionsBrowserClient() override; + + // ExtensionsBrowserClient overrides: + bool IsShuttingDown() override; + bool AreExtensionsDisabled(const base::CommandLine& command_line, + content::BrowserContext* context) override; + bool IsValidContext(content::BrowserContext* context) override; + bool IsSameContext(content::BrowserContext* first, + content::BrowserContext* second) override; + bool HasOffTheRecordContext(content::BrowserContext* context) override; + content::BrowserContext* GetOffTheRecordContext( + content::BrowserContext* context) override; + content::BrowserContext* GetOriginalContext( + content::BrowserContext* context) override; + bool IsGuestSession(content::BrowserContext* context) const override; + bool IsExtensionIncognitoEnabled( + const std::string& extension_id, + content::BrowserContext* context) const override; + bool CanExtensionCrossIncognito( + const Extension* extension, + content::BrowserContext* context) const override; + net::URLRequestJob* MaybeCreateResourceBundleRequestJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const base::FilePath& directory_path, + const std::string& content_security_policy, + bool send_cors_header) override; + bool AllowCrossRendererResourceLoad(net::URLRequest* request, + bool is_incognito, + const Extension* extension, + InfoMap* extension_info_map) override; + PrefService* GetPrefServiceForContext( + content::BrowserContext* context) override; + void GetEarlyExtensionPrefsObservers( + content::BrowserContext* context, + std::vector* observers) const override; + ProcessManagerDelegate* GetProcessManagerDelegate() const override; + scoped_ptr CreateExtensionHostDelegate() override; + bool DidVersionUpdate(content::BrowserContext* context) override; + void PermitExternalProtocolHandler() override; + scoped_ptr CreateAppSorting() override; + bool IsRunningInForcedAppMode() override; + ApiActivityMonitor* GetApiActivityMonitor( + content::BrowserContext* context) override; + ExtensionSystemProvider* GetExtensionSystemFactory() override; + void RegisterExtensionFunctions( + ExtensionFunctionRegistry* registry) const override; + scoped_ptr CreateRuntimeAPIDelegate( + content::BrowserContext* context) const override; + const ComponentExtensionResourceManager* + GetComponentExtensionResourceManager() override; + void BroadcastEventToRenderers(const std::string& event_name, + scoped_ptr args) override; + net::NetLog* GetNetLog() override; + ExtensionCache* GetExtensionCache() override; + bool IsBackgroundUpdateAllowed() override; + bool IsMinBrowserVersionSupported(const std::string& min_version) override; + + private: + // The single BrowserContext for app_shell. Not owned. + content::BrowserContext* browser_context_; + + // Support for extension APIs. + scoped_ptr api_client_; + + // The PrefService for |browser_context_|. + scoped_ptr prefs_; + + // The extension cache used for download and installation. + scoped_ptr extension_cache_; + + scoped_ptr resource_manager_; + + DISALLOW_COPY_AND_ASSIGN(ShellExtensionsBrowserClient); +}; + +} // namespace extensions + +#endif // EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSIONS_BROWSER_CLIENT_H_ diff --git a/src/browser/shell_javascript_dialog.h b/src/browser/shell_javascript_dialog.h index 2ed17fb235..1057f6526b 100644 --- a/src/browser/shell_javascript_dialog.h +++ b/src/browser/shell_javascript_dialog.h @@ -45,8 +45,8 @@ class ShellJavaScriptDialog { ShellJavaScriptDialogCreator* creator, gfx::NativeWindow parent_window, JavaScriptMessageType message_type, - const string16& message_text, - const string16& default_prompt_text, + const base::string16& message_text, + const base::string16& default_prompt_text, const JavaScriptDialogManager::DialogClosedCallback& callback); ~ShellJavaScriptDialog(); @@ -62,8 +62,8 @@ class ShellJavaScriptDialog { #elif defined(OS_WIN) JavaScriptMessageType message_type_; HWND dialog_win_; - string16 message_text_; - string16 default_prompt_text_; + base::string16 message_text_; + base::string16 default_prompt_text_; static INT_PTR CALLBACK DialogProc(HWND dialog, UINT message, WPARAM wparam, LPARAM lparam); #elif defined(TOOLKIT_GTK) diff --git a/src/browser/shell_javascript_dialog_creator.cc b/src/browser/shell_javascript_dialog_creator.cc index 36bb864a16..8b4b8a649c 100644 --- a/src/browser/shell_javascript_dialog_creator.cc +++ b/src/browser/shell_javascript_dialog_creator.cc @@ -22,9 +22,8 @@ #include "base/command_line.h" #include "base/logging.h" -#include "base/utf_string_conversions.h" +#include "base/strings/utf_string_conversions.h" #include "content/public/browser/web_contents.h" -#include "content/public/browser/web_contents_view.h" #include "content/nw/src/browser/shell_javascript_dialog.h" #include "content/nw/src/common/shell_switches.h" #include "net/base/net_util.h" @@ -42,13 +41,13 @@ void ShellJavaScriptDialogCreator::RunJavaScriptDialog( const GURL& origin_url, const std::string& accept_lang, JavaScriptMessageType javascript_message_type, - const string16& message_text, - const string16& default_prompt_text, + const base::string16& message_text, + const base::string16& default_prompt_text, const DialogClosedCallback& callback, bool* did_suppress_message) { if (!dialog_request_callback_.is_null()) { dialog_request_callback_.Run(); - callback.Run(true, string16()); + callback.Run(true, base::string16()); dialog_request_callback_.Reset(); return; } @@ -63,7 +62,7 @@ void ShellJavaScriptDialogCreator::RunJavaScriptDialog( } gfx::NativeWindow parent_window = - web_contents->GetView()->GetTopLevelNativeWindow(); + web_contents->GetTopLevelNativeWindow(); dialog_.reset(new ShellJavaScriptDialog(this, parent_window, @@ -80,12 +79,12 @@ void ShellJavaScriptDialogCreator::RunJavaScriptDialog( void ShellJavaScriptDialogCreator::RunBeforeUnloadDialog( WebContents* web_contents, - const string16& message_text, + const base::string16& message_text, bool is_reload, const DialogClosedCallback& callback) { if (!dialog_request_callback_.is_null()) { dialog_request_callback_.Run(); - callback.Run(true, string16()); + callback.Run(true, base::string16()); dialog_request_callback_.Reset(); return; } @@ -93,34 +92,38 @@ void ShellJavaScriptDialogCreator::RunBeforeUnloadDialog( #if defined(OS_MACOSX) || defined(OS_WIN) || defined(TOOLKIT_GTK) if (dialog_.get()) { // Seriously!? - callback.Run(true, string16()); + callback.Run(true, base::string16()); return; } - string16 new_message_text = + base::string16 new_message_text = message_text + - ASCIIToUTF16("\n\nIs it OK to leave/reload this page?"); + base::ASCIIToUTF16("\n\nIs it OK to leave/reload this page?"); gfx::NativeWindow parent_window = - web_contents->GetView()->GetTopLevelNativeWindow(); + web_contents->GetTopLevelNativeWindow(); dialog_.reset(new ShellJavaScriptDialog(this, parent_window, JAVASCRIPT_MESSAGE_TYPE_CONFIRM, new_message_text, - string16(), // default_prompt_text + base::string16(), // default_prompt_text callback)); #else // TODO: implement ShellJavaScriptDialog for other platforms, drop this #if - callback.Run(true, string16()); + callback.Run(true, base::string16()); return; #endif } -void ShellJavaScriptDialogCreator::ResetJavaScriptState( +void ShellJavaScriptDialogCreator::WebContentsDestroyed( + WebContents* web_contents) { +} + +void ShellJavaScriptDialogCreator::CancelActiveAndPendingDialogs( WebContents* web_contents) { #if defined(OS_MACOSX) || defined(OS_WIN) || defined(TOOLKIT_GTK) - if (dialog_.get()) { + if (dialog_) { dialog_->Cancel(); dialog_.reset(); } diff --git a/src/browser/shell_javascript_dialog_creator.h b/src/browser/shell_javascript_dialog_creator.h index 328c72635f..81c32e5a8c 100644 --- a/src/browser/shell_javascript_dialog_creator.h +++ b/src/browser/shell_javascript_dialog_creator.h @@ -17,29 +17,31 @@ class ShellJavaScriptDialog; class ShellJavaScriptDialogCreator : public JavaScriptDialogManager { public: ShellJavaScriptDialogCreator(); - virtual ~ShellJavaScriptDialogCreator(); + ~ShellJavaScriptDialogCreator() final; // JavaScriptDialogCreator: - virtual void RunJavaScriptDialog( + void RunJavaScriptDialog( WebContents* web_contents, const GURL& origin_url, const std::string& accept_lang, JavaScriptMessageType javascript_message_type, - const string16& message_text, - const string16& default_prompt_text, + const base::string16& message_text, + const base::string16& default_prompt_text, const DialogClosedCallback& callback, - bool* did_suppress_message) OVERRIDE; + bool* did_suppress_message) override; - virtual void RunBeforeUnloadDialog( + void RunBeforeUnloadDialog( WebContents* web_contents, - const string16& message_text, + const base::string16& message_text, bool is_reload, - const DialogClosedCallback& callback) OVERRIDE; + const DialogClosedCallback& callback) override; - virtual void ResetJavaScriptState(WebContents* web_contents) OVERRIDE; + void CancelActiveAndPendingDialogs( + WebContents* web_contents) override; // Called by the ShellJavaScriptDialog when it closes. void DialogClosed(ShellJavaScriptDialog* dialog); + void WebContentsDestroyed(WebContents* web_contents) override; // Used for content_browsertests. void set_dialog_request_callback( diff --git a/src/browser/shell_javascript_dialog_gtk.cc b/src/browser/shell_javascript_dialog_gtk.cc index c9f19a77c8..133fc98e0d 100644 --- a/src/browser/shell_javascript_dialog_gtk.cc +++ b/src/browser/shell_javascript_dialog_gtk.cc @@ -23,8 +23,8 @@ #include #include "base/logging.h" -#include "base/string_util.h" -#include "base/utf_string_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" #include "content/nw/src/browser/shell_javascript_dialog_creator.h" #include "content/nw/src/resource.h" #include "content/nw/src/nw_shell.h" @@ -35,12 +35,12 @@ const char kPromptTextId[] = "content_shell_prompt_text"; // If there's a text entry in the dialog, get the text from the first one and // return it. -string16 GetPromptText(GtkDialog* dialog) { +base::string16 GetPromptText(GtkDialog* dialog) { GtkWidget* widget = static_cast( g_object_get_data(G_OBJECT(dialog), kPromptTextId)); if (widget) - return UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(widget))); - return string16(); + return base::UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(widget))); + return base::string16(); } } // namespace @@ -52,8 +52,8 @@ ShellJavaScriptDialog::ShellJavaScriptDialog( ShellJavaScriptDialogCreator* creator, gfx::NativeWindow parent_window, JavaScriptMessageType message_type, - const string16& message_text, - const string16& default_prompt_text, + const base::string16& message_text, + const base::string16& default_prompt_text, const JavaScriptDialogManager::DialogClosedCallback& callback) : creator_(creator), callback_(callback), @@ -129,7 +129,7 @@ void ShellJavaScriptDialog::OnResponse(GtkWidget* dialog, int response_id) { break; case GTK_RESPONSE_CANCEL: case GTK_RESPONSE_DELETE_EVENT: - callback_.Run(false, string16()); + callback_.Run(false, base::string16()); break; default: NOTREACHED(); diff --git a/src/browser/shell_javascript_dialog_mac.mm b/src/browser/shell_javascript_dialog_mac.mm index 6004faf4b0..a35c621b5e 100644 --- a/src/browser/shell_javascript_dialog_mac.mm +++ b/src/browser/shell_javascript_dialog_mac.mm @@ -22,15 +22,15 @@ #import -#import "base/memory/scoped_nsobject.h" -#include "base/sys_string_conversions.h" +#import "base/mac/scoped_nsobject.h" +#include "base/strings/sys_string_conversions.h" #include "content/nw/src/browser/shell_javascript_dialog_creator.h" // Helper object that receives the notification that the dialog/sheet is // going away. Is responsible for cleaning itself up. @interface ShellJavaScriptDialogHelper : NSObject { @private - scoped_nsobject alert_; + base::scoped_nsobject alert_; NSTextField* textField_; // WEAK; owned by alert_ // Copies of the fields in ShellJavaScriptDialog because they're private. @@ -82,7 +82,7 @@ - (void)alertDidEnd:(NSAlert*)alert return; bool success = returnCode == NSAlertFirstButtonReturn; - string16 input; + base::string16 input; if (textField_) input = base::SysNSStringToUTF16([textField_ stringValue]); @@ -105,8 +105,8 @@ - (void)cancel { ShellJavaScriptDialogCreator* creator, gfx::NativeWindow parent_window, JavaScriptMessageType message_type, - const string16& message_text, - const string16& default_prompt_text, + const base::string16& message_text, + const base::string16& default_prompt_text, const JavaScriptDialogManager::DialogClosedCallback& callback) : creator_(creator), callback_(callback) { @@ -126,7 +126,7 @@ - (void)cancel { } [alert setDelegate:helper_]; [alert setInformativeText:base::SysUTF16ToNSString(message_text)]; - [alert setMessageText:@"Javascript alert"]; + [alert setMessageText:@""]; [alert addButtonWithTitle:@"OK"]; if (!one_button) { NSButton* other = [alert addButtonWithTitle:@"Cancel"]; diff --git a/src/browser/shell_javascript_dialog_win.cc b/src/browser/shell_javascript_dialog_win.cc index c1c4eea735..636bf3028e 100644 --- a/src/browser/shell_javascript_dialog_win.cc +++ b/src/browser/shell_javascript_dialog_win.cc @@ -20,7 +20,7 @@ #include "content/nw/src/browser/shell_javascript_dialog.h" -#include "base/string_util.h" +#include "base/strings/string_util.h" #include "content/nw/src/browser/shell_javascript_dialog_creator.h" #include "content/nw/src/resource.h" #include "content/nw/src/nw_shell.h" @@ -35,7 +35,7 @@ INT_PTR CALLBACK ShellJavaScriptDialog::DialogProc(HWND dialog, LPARAM lparam) { switch (message) { case WM_INITDIALOG: { - SetWindowLongPtr(dialog, DWL_USER, static_cast(lparam)); + SetWindowLongPtr(dialog, DWLP_USER, static_cast(lparam)); ShellJavaScriptDialog* owner = reinterpret_cast(lparam); owner->dialog_win_ = dialog; @@ -47,18 +47,18 @@ INT_PTR CALLBACK ShellJavaScriptDialog::DialogProc(HWND dialog, } case WM_DESTROY: { ShellJavaScriptDialog* owner = reinterpret_cast( - GetWindowLongPtr(dialog, DWL_USER)); + GetWindowLongPtr(dialog, DWLP_USER)); if (owner->dialog_win_) { owner->dialog_win_ = 0; - owner->callback_.Run(false, string16()); + owner->callback_.Run(false, base::string16()); owner->creator_->DialogClosed(owner); } break; } case WM_COMMAND: { ShellJavaScriptDialog* owner = reinterpret_cast( - GetWindowLongPtr(dialog, DWL_USER)); - string16 user_input; + GetWindowLongPtr(dialog, DWLP_USER)); + base::string16 user_input; bool finish = false; bool result; switch (LOWORD(wparam)) { @@ -95,8 +95,8 @@ ShellJavaScriptDialog::ShellJavaScriptDialog( ShellJavaScriptDialogCreator* creator, gfx::NativeWindow parent_window, JavaScriptMessageType message_type, - const string16& message_text, - const string16& default_prompt_text, + const base::string16& message_text, + const base::string16& default_prompt_text, const JavaScriptDialogManager::DialogClosedCallback& callback) : creator_(creator), callback_(callback), diff --git a/src/browser/shell_login_dialog.cc b/src/browser/shell_login_dialog.cc index b7bdcd5374..1dfb826d1b 100644 --- a/src/browser/shell_login_dialog.cc +++ b/src/browser/shell_login_dialog.cc @@ -1,33 +1,18 @@ -// Copyright (c) 2012 Intel Corp -// Copyright (c) 2012 The Chromium Authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co -// pies of the Software, and to permit persons to whom the Software is furnished -// to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in al -// l copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM -// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES -// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH -// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. #include "content/nw/src/browser/shell_login_dialog.h" #include "base/bind.h" #include "base/logging.h" -#include "base/utf_string_conversions.h" +#include "base/strings/utf_string_conversions.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/resource_dispatcher_host.h" +#include "content/public/browser/resource_request_info.h" #include "net/base/auth.h" #include "net/url_request/url_request.h" -#include "ui/base/text/text_elider.h" +#include "ui/gfx/text_elider.h" namespace content { @@ -35,12 +20,24 @@ ShellLoginDialog::ShellLoginDialog( net::AuthChallengeInfo* auth_info, net::URLRequest* request) : auth_info_(auth_info), request_(request) { +#if !defined(OS_MACOSX) + AddRef(); +#endif + +#if defined(OS_WIN) + dialog_ = NULL; +#endif + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + if (!ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderFrame( + &render_process_id_, &render_frame_id_)) { + NOTREACHED(); + } BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&ShellLoginDialog::PrepDialog, this, - ASCIIToUTF16(auth_info->challenger.ToString()), - UTF8ToUTF16(auth_info->realm))); + base::ASCIIToUTF16(auth_info->challenger.ToString()), + base::UTF8ToUTF16(auth_info->realm))); } void ShellLoginDialog::OnRequestCancelled() { @@ -50,8 +47,8 @@ void ShellLoginDialog::OnRequestCancelled() { base::Bind(&ShellLoginDialog::PlatformRequestCancelled, this)); } -void ShellLoginDialog::UserAcceptedAuth(const string16& username, - const string16& password) { +void ShellLoginDialog::UserAcceptedAuth(const base::string16& username, + const base::string16& password) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, @@ -64,7 +61,7 @@ void ShellLoginDialog::UserCancelledAuth() { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&ShellLoginDialog::SendAuthToRequester, this, - false, string16(), string16())); + false, base::string16(), base::string16())); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&ShellLoginDialog::PlatformCleanUp, this)); @@ -75,30 +72,30 @@ ShellLoginDialog::~ShellLoginDialog() { // referenced/dereferenced. } -void ShellLoginDialog::PrepDialog(const string16& host, - const string16& realm) { +void ShellLoginDialog::PrepDialog(const base::string16& host, + const base::string16& realm) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); // The realm is controlled by the remote server, so there is no reason to // believe it is of a reasonable length. - string16 elided_realm; - ui::ElideString(realm, 120, &elided_realm); + base::string16 elided_realm; + gfx::ElideString(realm, 120, &elided_realm); - string16 explanation = - ASCIIToUTF16("The server ") + host + - ASCIIToUTF16(" requires a username and password."); + base::string16 explanation = + base::ASCIIToUTF16("The server ") + host + + base::ASCIIToUTF16(" requires a username and password."); if (!elided_realm.empty()) { - explanation += ASCIIToUTF16(" The server says: "); + explanation += base::ASCIIToUTF16(" The server says: "); explanation += elided_realm; - explanation += ASCIIToUTF16("."); + explanation += base::ASCIIToUTF16("."); } PlatformCreateDialog(explanation); } void ShellLoginDialog::SendAuthToRequester(bool success, - const string16& username, - const string16& password) { + const base::string16& username, + const base::string16& password) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); if (success) request_->SetAuth(net::AuthCredentials(username, password)); @@ -111,4 +108,8 @@ void ShellLoginDialog::SendAuthToRequester(bool success, base::Bind(&ShellLoginDialog::PlatformCleanUp, this)); } +void ShellLoginDialog::ReleaseSoon() { + BrowserThread::ReleaseSoon(BrowserThread::IO, FROM_HERE, this); +} + } // namespace content diff --git a/src/browser/shell_login_dialog.h b/src/browser/shell_login_dialog.h index 2226133930..b4c8d1a3db 100644 --- a/src/browser/shell_login_dialog.h +++ b/src/browser/shell_login_dialog.h @@ -1,34 +1,23 @@ -// Copyright (c) 2012 Intel Corp -// Copyright (c) 2012 The Chromium Authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co -// pies of the Software, and to permit persons to whom the Software is furnished -// to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in al -// l copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM -// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES -// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH -// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -#ifndef CONTENT_NW_SRC_BROWSER_SHELL_LOGIN_DIALOG_H_ -#define CONTENT_NW_SRC_BROWSER_SHELL_LOGIN_DIALOG_H_ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_SHELL_BROWSER_SHELL_LOGIN_DIALOG_H_ +#define CONTENT_SHELL_BROWSER_SHELL_LOGIN_DIALOG_H_ #include "base/compiler_specific.h" -#include "base/string16.h" +#include "base/strings/string16.h" #include "content/public/browser/resource_dispatcher_host_login_delegate.h" #if defined(TOOLKIT_GTK) #include "ui/base/gtk/gtk_signal.h" #endif +#if defined(OS_WIN) || defined(OS_LINUX) +#include "ui/views/window/dialog_delegate.h" +#include "login_view.h" +#endif + #if defined(OS_MACOSX) #if __OBJC__ @class ShellLoginDialogHelper; @@ -46,32 +35,56 @@ namespace content { // This class provides a dialog box to ask the user for credentials. Useful in // ResourceDispatcherHostDelegate::CreateLoginDelegate. +#if defined(OS_WIN) || defined(OS_LINUX) +class ShellLoginDialog : public ResourceDispatcherHostLoginDelegate, public views::DialogDelegate { +#else class ShellLoginDialog : public ResourceDispatcherHostLoginDelegate { +#endif public: // Threading: IO thread. ShellLoginDialog(net::AuthChallengeInfo* auth_info, net::URLRequest* request); // ResourceDispatcherHostLoginDelegate implementation: // Threading: IO thread. - virtual void OnRequestCancelled() OVERRIDE; + void OnRequestCancelled() override; // Called by the platform specific code when the user responds. Public because // the aforementioned platform specific code may not have access to private // members. Not to be called from client code. // Threading: UI thread. - void UserAcceptedAuth(const string16& username, const string16& password); + void UserAcceptedAuth(const base::string16& username, + const base::string16& password); void UserCancelledAuth(); +#if defined(OS_WIN) || defined(OS_LINUX) + // views::DialogDelegate methods: + base::string16 GetDialogButtonLabel(ui::DialogButton button) const override; + base::string16 GetWindowTitle() const override; + void WindowClosing() override; + void DeleteDelegate() override; + ui::ModalType GetModalType() const override; + bool Cancel() override; + bool Accept() override; + views::View* GetInitiallyFocusedView() override; + views::View* GetContentsView() override; + views::Widget* GetWidget() override; + const views::Widget* GetWidget() const override; +#endif + protected: // Threading: any - virtual ~ShellLoginDialog(); + ~ShellLoginDialog() final; + void ReleaseSoon(); + + int render_process_id_; + int render_frame_id_; private: // All the methods that begin with Platform need to be implemented by the // platform specific LoginDialog implementation. // Creates the dialog. // Threading: UI thread. - void PlatformCreateDialog(const string16& message); + void PlatformCreateDialog(const base::string16& message); // Called from the destructor to let each platform do any necessary cleanup. // Threading: UI thread. void PlatformCleanUp(); @@ -81,13 +94,13 @@ class ShellLoginDialog : public ResourceDispatcherHostLoginDelegate { // Sets up dialog creation. // Threading: UI thread. - void PrepDialog(const string16& host, const string16& realm); + void PrepDialog(const base::string16& host, const base::string16& realm); // Sends the authentication to the requester. // Threading: IO thread. void SendAuthToRequester(bool success, - const string16& username, - const string16& password); + const base::string16& username, + const base::string16& password); // Who/where/what asked for the authentication. // Threading: IO thread. @@ -100,19 +113,19 @@ class ShellLoginDialog : public ResourceDispatcherHostLoginDelegate { #if defined(OS_MACOSX) // Threading: UI thread. ShellLoginDialogHelper* helper_; // owned -#elif defined(OS_WIN) - HWND dialog_win_; - string16 message_text_; - static INT_PTR CALLBACK DialogProc(HWND dialog, UINT message, WPARAM wparam, - LPARAM lparam); #elif defined(TOOLKIT_GTK) GtkWidget* username_entry_; GtkWidget* password_entry_; GtkWidget* root_; CHROMEGTK_CALLBACK_1(ShellLoginDialog, void, OnResponse, int); + CHROMEGTK_CALLBACK_0(ShellLoginDialog, void, OnDestroy); +#elif defined(OS_WIN) || defined(OS_LINUX) + LoginView* login_view_; + + views::Widget* dialog_; #endif }; } // namespace content -#endif // CONTENT_NW_SRC_BROWSER_SHELL_LOGIN_DIALOG_H_ +#endif // CONTENT_SHELL_BROWSER_SHELL_LOGIN_DIALOG_H_ diff --git a/src/browser/shell_login_dialog_gtk.cc b/src/browser/shell_login_dialog_gtk.cc index ac0ea40b31..d8239351ff 100644 --- a/src/browser/shell_login_dialog_gtk.cc +++ b/src/browser/shell_login_dialog_gtk.cc @@ -1,55 +1,30 @@ -// Copyright (c) 2012 Intel Corp -// Copyright (c) 2012 The Chromium Authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co -// pies of the Software, and to permit persons to whom the Software is furnished -// to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in al -// l copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM -// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES -// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH -// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. #include "content/nw/src/browser/shell_login_dialog.h" #include #include "base/logging.h" -#include "base/string16.h" -#include "base/utf_string_conversions.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" #include "content/public/browser/browser_thread.h" -#include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_frame_host.h" #include "content/public/browser/resource_dispatcher_host.h" -#include "content/public/browser/resource_request_info.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_view.h" #include "ui/base/gtk/gtk_hig_constants.h" namespace content { -void ShellLoginDialog::PlatformCreateDialog(const string16& message) { +void ShellLoginDialog::PlatformCreateDialog(const base::string16& message) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - int render_process_id; - int render_view_id; - if (!ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderView( - &render_process_id, &render_view_id)) { - NOTREACHED(); - } - WebContents* web_contents = NULL; - RenderViewHost* render_view_host = - RenderViewHost::FromID(render_process_id, render_view_id); - if (render_view_host) - web_contents = WebContents::FromRenderViewHost(render_view_host); + RenderFrameHost* render_frame_host = + RenderFrameHost::FromID(render_process_id_, render_frame_id_); + web_contents = WebContents::FromRenderFrameHost(render_frame_host); DCHECK(web_contents); gfx::NativeWindow parent_window = @@ -62,7 +37,7 @@ void ShellLoginDialog::PlatformCreateDialog(const string16& message) { "Please log in."); GtkWidget* content_area = gtk_dialog_get_content_area(GTK_DIALOG(root_)); - GtkWidget* label = gtk_label_new(UTF16ToUTF8(message).c_str()); + GtkWidget* label = gtk_label_new(base::UTF16ToUTF8(message).c_str()); gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); gtk_box_pack_start(GTK_BOX(content_area), label, FALSE, FALSE, 0); @@ -94,6 +69,8 @@ void ShellLoginDialog::PlatformCreateDialog(const string16& message) { gtk_box_pack_start(GTK_BOX(content_area), table, FALSE, FALSE, 0); g_signal_connect(root_, "response", G_CALLBACK(OnResponseThunk), this); + g_signal_connect(root_, "destroy", G_CALLBACK(OnDestroyThunk), this); + gtk_widget_grab_focus(username_entry_); gtk_widget_show_all(GTK_WIDGET(root_)); } @@ -110,8 +87,8 @@ void ShellLoginDialog::OnResponse(GtkWidget* sender, int response_id) { switch (response_id) { case GTK_RESPONSE_OK: UserAcceptedAuth( - UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(username_entry_))), - UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(password_entry_)))); + base::UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(username_entry_))), + base::UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(password_entry_)))); break; case GTK_RESPONSE_CANCEL: case GTK_RESPONSE_DELETE_EVENT: @@ -124,4 +101,12 @@ void ShellLoginDialog::OnResponse(GtkWidget* sender, int response_id) { gtk_widget_destroy(root_); } +void ShellLoginDialog::OnDestroy(GtkWidget* widget) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + root_ = NULL; + + ReleaseSoon(); +} + } // namespace content diff --git a/src/browser/shell_login_dialog_mac.mm b/src/browser/shell_login_dialog_mac.mm index 11c7aa67e8..3e014e8791 100644 --- a/src/browser/shell_login_dialog_mac.mm +++ b/src/browser/shell_login_dialog_mac.mm @@ -1,31 +1,15 @@ -// Copyright (c) 2012 Intel Corp -// Copyright (c) 2012 The Chromium Authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co -// pies of the Software, and to permit persons to whom the Software is furnished -// to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in al -// l copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM -// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES -// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH -// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -#include "content/nw/src/browser/shell_login_dialog.h" +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/shell/browser/shell_login_dialog.h" #import #include "base/logging.h" #include "base/mac/bundle_locations.h" -#import "base/memory/scoped_nsobject.h" -#include "base/sys_string_conversions.h" +#import "base/mac/scoped_nsobject.h" +#include "base/strings/sys_string_conversions.h" #include "content/public/browser/browser_thread.h" #import "ui/base/cocoa/nib_loading.h" @@ -40,7 +24,7 @@ // going away. @interface ShellLoginDialogHelper : NSObject { @private - scoped_nsobject alert_; + base::scoped_nsobject alert_; NSTextField* usernameField_; // WEAK; owned by alert_ NSSecureTextField* passwordField_; // WEAK; owned by alert_ } @@ -103,7 +87,7 @@ - (void)cancel { namespace content { -void ShellLoginDialog::PlatformCreateDialog(const string16& message) { +void ShellLoginDialog::PlatformCreateDialog(const base::string16& message) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); helper_ = [[ShellLoginDialogHelper alloc] init]; diff --git a/src/browser/shell_login_dialog_views.cc b/src/browser/shell_login_dialog_views.cc new file mode 100644 index 0000000000..6b3f355cec --- /dev/null +++ b/src/browser/shell_login_dialog_views.cc @@ -0,0 +1,185 @@ +// Copyright (c) 2012 Intel Corp +// Copyright (c) 2012 The Chromium Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "content/nw/src/browser/shell_login_dialog.h" + +#include "base/logging.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "components/web_modal/web_contents_modal_dialog_host.h" +#include "components/web_modal/web_contents_modal_dialog_manager.h" +#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h" +#include "content/nw/src/resource.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/resource_dispatcher_host.h" +#include "content/public/browser/resource_request_info.h" +#include "content/public/browser/web_contents.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/views/widget/widget.h" + +using web_modal::WebContentsModalDialogManager; +using web_modal::WebContentsModalDialogManagerDelegate; + +#if 0 +namespace { + +// The captive portal dialog is system-modal, but uses the web-content-modal +// dialog manager (odd) and requires this atypical dialog widget initialization. +views::Widget* CreateWindowAsFramelessChild(views::WidgetDelegate* delegate, + gfx::NativeView parent) { + views::Widget* widget = new views::Widget; + + views::Widget::InitParams params; + params.delegate = delegate; + params.child = true; + params.parent = parent; + params.remove_standard_frame = true; + params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; + + widget->Init(params); + return widget; +} + +} // namespace +#endif + +namespace content { + +void ShellLoginDialog::PlatformCreateDialog(const base::string16& message) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + // Scary thread safety note: This can potentially be called *after* SetAuth + // or CancelAuth (say, if the request was cancelled before the UI thread got + // control). However, that's OK since any UI interaction in those functions + // will occur via an InvokeLater on the UI thread, which is guaranteed + // to happen after this is called (since this was InvokeLater'd first). + content::RenderFrameHost* rfh = content::RenderFrameHost::FromID( + render_process_id_, render_frame_id_); + + WebContents* requesting_contents = WebContents::FromRenderFrameHost(rfh); + WebContentsModalDialogManager* web_contents_modal_dialog_manager = + WebContentsModalDialogManager::FromWebContents(requesting_contents); + + if (!web_contents_modal_dialog_manager) { + UserCancelledAuth(); + DeleteDelegate(); + return; + } + + login_view_ = new LoginView(message); + + WebContentsModalDialogManagerDelegate* modal_delegate = + web_contents_modal_dialog_manager->delegate(); + CHECK(modal_delegate); + dialog_ = views::DialogDelegate::CreateDialogWidget( + this, NULL, modal_delegate->GetWebContentsModalDialogHost()->GetHostView()); + web_modal::WebContentsModalDialogManager::FromWebContents(requesting_contents)-> + ShowModalDialog(dialog_->GetNativeWindow()); +} + +#if 0 +void ShellLoginDialog::PlatformShowDialog() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); +} +#endif + +void ShellLoginDialog::PlatformCleanUp() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (dialog_) + dialog_->Close(); +} + +void ShellLoginDialog::PlatformRequestCancelled() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); +} + + +base::string16 ShellLoginDialog::GetDialogButtonLabel( + ui::DialogButton button) const { + if (button == ui::DIALOG_BUTTON_OK) + return base::ASCIIToUTF16("Log In"); + return DialogDelegate::GetDialogButtonLabel(button); +} + +base::string16 ShellLoginDialog::GetWindowTitle() const { + return base::ASCIIToUTF16("Authentication Required"); +} + +void ShellLoginDialog::WindowClosing() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + content::RenderFrameHost* rfh = content::RenderFrameHost::FromID( + render_process_id_, render_frame_id_); + + WebContents* tab = WebContents::FromRenderFrameHost(rfh); + + if (tab) + tab->GetRenderViewHost()->SetIgnoreInputEvents(false); + + // Reference is no longer valid. + dialog_ = NULL; + + // UserCancelledAuth(); +} + +void ShellLoginDialog::DeleteDelegate() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + // The widget is going to delete itself; clear our pointer. + dialog_ = NULL; + + ReleaseSoon(); +} + +ui::ModalType ShellLoginDialog::GetModalType() const { + return ui::MODAL_TYPE_CHILD; +} + +bool ShellLoginDialog::Cancel() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + UserCancelledAuth(); + return true; +} + +bool ShellLoginDialog::Accept() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + UserAcceptedAuth(login_view_->GetUsername(), login_view_->GetPassword()); + return true; +} + +views::View* ShellLoginDialog::GetInitiallyFocusedView() { + return login_view_->GetInitiallyFocusedView(); +} + +views::View* ShellLoginDialog::GetContentsView() { + return login_view_; +} +views::Widget* ShellLoginDialog::GetWidget() { + return login_view_->GetWidget(); +} +const views::Widget* ShellLoginDialog::GetWidget() const { + return login_view_->GetWidget(); +} + +} // namespace content diff --git a/src/browser/shell_login_dialog_win.cc b/src/browser/shell_login_dialog_win.cc deleted file mode 100644 index d8b5347e9f..0000000000 --- a/src/browser/shell_login_dialog_win.cc +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) 2012 Intel Corp -// Copyright (c) 2012 The Chromium Authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co -// pies of the Software, and to permit persons to whom the Software is furnished -// to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in al -// l copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM -// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES -// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH -// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -#include "content/nw/src/browser/shell_login_dialog.h" - -#include "base/logging.h" -#include "base/string16.h" -#include "base/utf_string_conversions.h" -#include "content/nw/src/resource.h" -#include "content/public/browser/browser_thread.h" -#include "content/public/browser/render_view_host.h" -#include "content/public/browser/resource_dispatcher_host.h" -#include "content/public/browser/resource_request_info.h" -#include "content/public/browser/web_contents.h" -#include "content/public/browser/web_contents_view.h" - -namespace content { - -INT_PTR CALLBACK ShellLoginDialog::DialogProc(HWND dialog, - UINT message, - WPARAM wparam, - LPARAM lparam) { - switch (message) { - case WM_INITDIALOG: { - SetWindowLongPtr(dialog, DWL_USER, static_cast(lparam)); - ShellLoginDialog* owner = reinterpret_cast(lparam); - owner->dialog_win_ = dialog; - SetDlgItemText(dialog, - IDC_DIALOG_MESSAGETEXT, - owner->message_text_.c_str()); - break; - } - case WM_CLOSE: { - ShellLoginDialog* owner = reinterpret_cast( - GetWindowLongPtr(dialog, DWL_USER)); - owner->UserCancelledAuth(); - DestroyWindow(owner->dialog_win_); - owner->dialog_win_ = NULL; - break; - } - case WM_COMMAND: { - ShellLoginDialog* owner = reinterpret_cast( - GetWindowLongPtr(dialog, DWL_USER)); - if (LOWORD(wparam) == IDOK) { - string16 username; - string16 password; - size_t length = - GetWindowTextLength(GetDlgItem(dialog, IDC_USERNAMEEDIT)) + 1; - GetDlgItemText(dialog, IDC_USERNAMEEDIT, - WriteInto(&username, length), length); - length = GetWindowTextLength(GetDlgItem(dialog, IDC_PASSWORDEDIT)) + 1; - GetDlgItemText(dialog, IDC_PASSWORDEDIT, - WriteInto(&password, length), length); - owner->UserAcceptedAuth(username, password); - } else if (LOWORD(wparam) == IDCANCEL) { - owner->UserCancelledAuth(); - } else { - NOTREACHED(); - } - - break; - } - default: - return DefWindowProc(dialog, message, wparam, lparam); - } - - return 0; -} - -void ShellLoginDialog::PlatformCreateDialog(const string16& message) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - int render_process_id; - int render_view_id; - if (!ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderView( - &render_process_id, &render_view_id)) { - NOTREACHED(); - } - - WebContents* web_contents = NULL; - RenderViewHost* render_view_host = - RenderViewHost::FromID(render_process_id, render_view_id); - if (render_view_host) - web_contents = WebContents::FromRenderViewHost(render_view_host); - DCHECK(web_contents); - - gfx::NativeWindow parent_window = - web_contents->GetView()->GetTopLevelNativeWindow(); - message_text_ = message; - dialog_win_ = CreateDialogParam(GetModuleHandle(0), - MAKEINTRESOURCE(IDD_LOGIN), parent_window, - DialogProc, reinterpret_cast(this)); - ShowWindow(dialog_win_, SW_SHOWNORMAL); -} - -void ShellLoginDialog::PlatformCleanUp() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - if (this->dialog_win_) - DestroyWindow(this->dialog_win_); -} - -void ShellLoginDialog::PlatformRequestCancelled() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); -} - -} // namespace content diff --git a/src/browser/shell_resource_dispatcher_host_delegate.cc b/src/browser/shell_resource_dispatcher_host_delegate.cc index 2bc22885aa..2de9d40be1 100644 --- a/src/browser/shell_resource_dispatcher_host_delegate.cc +++ b/src/browser/shell_resource_dispatcher_host_delegate.cc @@ -23,6 +23,7 @@ #include "chrome/browser/platform_util.h" #include "content/nw/src/browser/shell_login_dialog.h" #include "content/public/browser/browser_thread.h" +#include "url/gurl.h" namespace content { @@ -33,13 +34,6 @@ ShellResourceDispatcherHostDelegate::ShellResourceDispatcherHostDelegate() { ShellResourceDispatcherHostDelegate::~ShellResourceDispatcherHostDelegate() { } -bool ShellResourceDispatcherHostDelegate::AcceptAuthRequest( - net::URLRequest* request, - net::AuthChallengeInfo* auth_info) { - // Why not give it a try? - return true; -} - ResourceDispatcherHostLoginDelegate* ShellResourceDispatcherHostDelegate::CreateLoginDelegate( net::AuthChallengeInfo* auth_info, net::URLRequest* request) { @@ -56,14 +50,14 @@ bool ShellResourceDispatcherHostDelegate::HandleExternalProtocol( const GURL& url, int child_id, int route_id) { #if defined(OS_MACOSX) // This must run on the UI thread on OS X. - platform_util::OpenExternal(url); + platform_util::OpenExternal2(url); #else // Otherwise put this work on the file thread. On Windows ShellExecute may // block for a significant amount of time, and it shouldn't hurt on Linux. BrowserThread::PostTask( - BrowserThread::FILE, + BrowserThread::UI, FROM_HERE, - base::Bind(&platform_util::OpenExternal, url)); + base::Bind(&platform_util::OpenExternal2, url)); #endif return true; diff --git a/src/browser/shell_resource_dispatcher_host_delegate.h b/src/browser/shell_resource_dispatcher_host_delegate.h index d3a3386219..26ab6fc734 100644 --- a/src/browser/shell_resource_dispatcher_host_delegate.h +++ b/src/browser/shell_resource_dispatcher_host_delegate.h @@ -15,16 +15,14 @@ class ShellResourceDispatcherHostDelegate : public ResourceDispatcherHostDelegate { public: ShellResourceDispatcherHostDelegate(); - virtual ~ShellResourceDispatcherHostDelegate(); + ~ShellResourceDispatcherHostDelegate() final; // ResourceDispatcherHostDelegate implementation. - virtual bool AcceptAuthRequest(net::URLRequest* request, - net::AuthChallengeInfo* auth_info) OVERRIDE; - virtual ResourceDispatcherHostLoginDelegate* CreateLoginDelegate( - net::AuthChallengeInfo* auth_info, net::URLRequest* request) OVERRIDE; - virtual bool HandleExternalProtocol(const GURL& url, - int child_id, - int route_id) OVERRIDE; + ResourceDispatcherHostLoginDelegate* CreateLoginDelegate( + net::AuthChallengeInfo* auth_info, net::URLRequest* request) override; + bool HandleExternalProtocol(const GURL& url, + int child_id, + int route_id) override; // Used for content_browsertests. void set_login_request_callback( diff --git a/src/browser/shell_runtime_api_delegate.cc b/src/browser/shell_runtime_api_delegate.cc new file mode 100644 index 0000000000..3c63da7b43 --- /dev/null +++ b/src/browser/shell_runtime_api_delegate.cc @@ -0,0 +1,66 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/shell/browser/shell_runtime_api_delegate.h" + +#include "extensions/common/api/runtime.h" + +#if defined(OS_CHROMEOS) +#include "chromeos/dbus/dbus_thread_manager.h" +#include "chromeos/dbus/power_manager_client.h" +#endif + +using extensions::core_api::runtime::PlatformInfo; + +namespace extensions { + +ShellRuntimeAPIDelegate::ShellRuntimeAPIDelegate() { +} + +ShellRuntimeAPIDelegate::~ShellRuntimeAPIDelegate() { +} + +void ShellRuntimeAPIDelegate::AddUpdateObserver(UpdateObserver* observer) { +} + +void ShellRuntimeAPIDelegate::RemoveUpdateObserver(UpdateObserver* observer) { +} + +base::Version ShellRuntimeAPIDelegate::GetPreviousExtensionVersion( + const Extension* extension) { + return base::Version(); +} + +void ShellRuntimeAPIDelegate::ReloadExtension(const std::string& extension_id) { +} + +bool ShellRuntimeAPIDelegate::CheckForUpdates( + const std::string& extension_id, + const UpdateCheckCallback& callback) { + return false; +} + +void ShellRuntimeAPIDelegate::OpenURL(const GURL& uninstall_url) { +} + +bool ShellRuntimeAPIDelegate::GetPlatformInfo(PlatformInfo* info) { +#if defined(OS_CHROMEOS) + info->os = PlatformInfo::OS_CROS_; +#elif defined(OS_LINUX) + info->os = PlatformInfo::OS_LINUX_; +#endif + return true; +} + +bool ShellRuntimeAPIDelegate::RestartDevice(std::string* error_message) { +// We allow chrome.runtime.restart() to request a device restart on ChromeOS. +#if defined(OS_CHROMEOS) + chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->RequestRestart(); + return true; +#endif + *error_message = "Restart is only supported on ChromeOS."; + return false; +} + +} // namespace extensions diff --git a/src/browser/shell_runtime_api_delegate.h b/src/browser/shell_runtime_api_delegate.h new file mode 100644 index 0000000000..816423b097 --- /dev/null +++ b/src/browser/shell_runtime_api_delegate.h @@ -0,0 +1,36 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_RUNTIME_API_DELEGATE_H_ +#define EXTENSIONS_SHELL_BROWSER_SHELL_RUNTIME_API_DELEGATE_H_ + +#include "base/macros.h" +#include "extensions/browser/api/runtime/runtime_api_delegate.h" + +namespace extensions { + +class ShellRuntimeAPIDelegate : public RuntimeAPIDelegate { + public: + ShellRuntimeAPIDelegate(); + ~ShellRuntimeAPIDelegate() override; + + // RuntimeAPIDelegate implementation. + void AddUpdateObserver(UpdateObserver* observer) override; + void RemoveUpdateObserver(UpdateObserver* observer) override; + base::Version GetPreviousExtensionVersion( + const Extension* extension) override; + void ReloadExtension(const std::string& extension_id) override; + bool CheckForUpdates(const std::string& extension_id, + const UpdateCheckCallback& callback) override; + void OpenURL(const GURL& uninstall_url) override; + bool GetPlatformInfo(core_api::runtime::PlatformInfo* info) override; + bool RestartDevice(std::string* error_message) override; + + private: + DISALLOW_COPY_AND_ASSIGN(ShellRuntimeAPIDelegate); +}; + +} // namespace extensions + +#endif // EXTENSIONS_SHELL_BROWSER_SHELL_RUNTIME_API_DELEGATE_H_ diff --git a/src/browser/shell_speech_recognition_manager_delegate.cc b/src/browser/shell_speech_recognition_manager_delegate.cc new file mode 100644 index 0000000000..1cfe565dd9 --- /dev/null +++ b/src/browser/shell_speech_recognition_manager_delegate.cc @@ -0,0 +1,115 @@ +// 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 "content/nw/src/browser/shell_speech_recognition_manager_delegate.h" + +#include "base/bind.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/speech_recognition_manager.h" +#include "content/public/browser/speech_recognition_session_context.h" +#include "content/public/browser/web_contents.h" + +using content::BrowserThread; +using content::SpeechRecognitionManager; +using content::WebContents; + +namespace content { +namespace speech { + +ShellSpeechRecognitionManagerDelegate::ShellSpeechRecognitionManagerDelegate() { +} + +ShellSpeechRecognitionManagerDelegate:: +~ShellSpeechRecognitionManagerDelegate() { +} + +void ShellSpeechRecognitionManagerDelegate::OnRecognitionStart(int session_id) { +} + +void ShellSpeechRecognitionManagerDelegate::OnAudioStart(int session_id) { +} + +void ShellSpeechRecognitionManagerDelegate::OnEnvironmentEstimationComplete( + int session_id) { +} + +void ShellSpeechRecognitionManagerDelegate::OnSoundStart(int session_id) { +} + +void ShellSpeechRecognitionManagerDelegate::OnSoundEnd(int session_id) { +} + +void ShellSpeechRecognitionManagerDelegate::OnAudioEnd(int session_id) { +} + +void ShellSpeechRecognitionManagerDelegate::OnRecognitionEnd(int session_id) { +} + +void ShellSpeechRecognitionManagerDelegate::OnRecognitionResults( + int session_id, + const content::SpeechRecognitionResults& result) { +} + +void ShellSpeechRecognitionManagerDelegate::OnRecognitionError( + int session_id, + const content::SpeechRecognitionError& error) { +} + +void ShellSpeechRecognitionManagerDelegate::OnAudioLevelsChange( + int session_id, + float volume, + float noise_volume) { +} + +void ShellSpeechRecognitionManagerDelegate::GetDiagnosticInformation( + bool* can_report_metrics, + std::string* hardware_info) { +} + +void ShellSpeechRecognitionManagerDelegate::CheckRecognitionIsAllowed( + int session_id, + base::Callback callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + const content::SpeechRecognitionSessionContext& context = + SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id); + + // Make sure that initiators (extensions/web pages) properly set the + // |render_process_id| field, which is needed later to retrieve the profile. + DCHECK_NE(context.render_process_id, 0); + + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(&CheckRenderViewType, + callback, + context.render_process_id, + context.render_view_id)); +} + +content::SpeechRecognitionEventListener* +ShellSpeechRecognitionManagerDelegate::GetEventListener() { + return this; +} + +bool ShellSpeechRecognitionManagerDelegate::FilterProfanities( + int render_process_id) { + // TODO(zork): Determine where this preference should come from. + return true; +} + +// static +void ShellSpeechRecognitionManagerDelegate::CheckRenderViewType( + base::Callback callback, + int render_process_id, + int render_view_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + bool allowed = true; + bool check_permission = false; + + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(callback, check_permission, allowed)); +} + +} // namespace speech +} // namespace extensions diff --git a/src/browser/shell_speech_recognition_manager_delegate.h b/src/browser/shell_speech_recognition_manager_delegate.h new file mode 100644 index 0000000000..47622e124c --- /dev/null +++ b/src/browser/shell_speech_recognition_manager_delegate.h @@ -0,0 +1,60 @@ +// 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 NW_SHELL_BROWSER_SHELL_SPEECH_RECOGNITION_MANAGER_DELEGATE_H_ +#define NW_SHELL_BROWSER_SHELL_SPEECH_RECOGNITION_MANAGER_DELEGATE_H_ + +#include "content/public/browser/speech_recognition_event_listener.h" +#include "content/public/browser/speech_recognition_manager_delegate.h" + +namespace content { +namespace speech { + +class ShellSpeechRecognitionManagerDelegate + : public content::SpeechRecognitionManagerDelegate, + public content::SpeechRecognitionEventListener { + public: + ShellSpeechRecognitionManagerDelegate(); + ~ShellSpeechRecognitionManagerDelegate() override; + + private: + // SpeechRecognitionEventListener methods. + void OnRecognitionStart(int session_id) override; + void OnAudioStart(int session_id) override; + void OnEnvironmentEstimationComplete(int session_id) override; + void OnSoundStart(int session_id) override; + void OnSoundEnd(int session_id) override; + void OnAudioEnd(int session_id) override; + void OnRecognitionEnd(int session_id) override; + void OnRecognitionResults( + int session_id, + const content::SpeechRecognitionResults& result) override; + void OnRecognitionError( + int session_id, + const content::SpeechRecognitionError& error) override; + void OnAudioLevelsChange(int session_id, + float volume, + float noise_volume) override; + + // SpeechRecognitionManagerDelegate methods. + void GetDiagnosticInformation(bool* can_report_metrics, + std::string* hardware_info) override; + void CheckRecognitionIsAllowed( + int session_id, + base::Callback callback) override; + content::SpeechRecognitionEventListener* GetEventListener() override; + bool FilterProfanities(int render_process_id) override; + + static void CheckRenderViewType( + base::Callback callback, + int render_process_id, + int render_view_id); + + DISALLOW_COPY_AND_ASSIGN(ShellSpeechRecognitionManagerDelegate); +}; + +} // namespace speech +} // namespace extensions + +#endif // NW_SHELL_BROWSER_SHELL_CONTENT_BROWSER_CLIENT_H_ diff --git a/src/browser/shell_toolbar_delegate_mac.mm b/src/browser/shell_toolbar_delegate_mac.mm index c38155b689..ac263af873 100644 --- a/src/browser/shell_toolbar_delegate_mac.mm +++ b/src/browser/shell_toolbar_delegate_mac.mm @@ -22,11 +22,11 @@ #include -#import "base/memory/scoped_nsobject.h" -#include "base/sys_string_conversions.h" +#import "base/mac/scoped_nsobject.h" +#include "base/strings/sys_string_conversions.h" #include "content/nw/src/browser/native_window.h" #include "content/nw/src/nw_shell.h" -#include "googleurl/src/gurl.h" +#include "url/gurl.h" static NSString *BackToolbarItemIdentifier = @"Back"; static NSString *ForwardToolbarItemIdentifier = @"Forward"; @@ -115,7 +115,7 @@ - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar [[NSToolbarItem alloc] initWithItemIdentifier:identifier]; if ([identifier isEqualTo:EntryToolbarItemIdentifier]) { - scoped_nsobject entry( + base::scoped_nsobject entry( [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 100, 24)]); entry_ = entry; [entry setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)]; @@ -128,7 +128,7 @@ - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar [item setMinSize:NSMakeSize(100, 20)]; [item setMaxSize:NSMakeSize(2000, 20)]; } else { - scoped_nsobject button([[NSButton alloc] init]); + base::scoped_nsobject button([[NSButton alloc] init]); [button setBezelStyle:NSTexturedRoundedBezelStyle]; [button setTarget:self]; [button setAction:@selector(buttonPressed:)]; diff --git a/src/browser/shell_web_contents_modal_dialog_manager.cc b/src/browser/shell_web_contents_modal_dialog_manager.cc new file mode 100644 index 0000000000..32d44fb670 --- /dev/null +++ b/src/browser/shell_web_contents_modal_dialog_manager.cc @@ -0,0 +1,18 @@ +// 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 "components/web_modal/web_contents_modal_dialog_manager.h" + +namespace web_modal { + +SingleWebContentsDialogManager* +WebContentsModalDialogManager::CreateNativeWebModalManager( + NativeWebContentsModalDialog dialog, + SingleWebContentsDialogManagerDelegate* native_delegate) { + // TODO(oshima): Investigate if we need to implement this. + NOTREACHED(); + return NULL; +} + +} // namespace web_modal diff --git a/src/browser/shell_web_view_guest_delegate.cc b/src/browser/shell_web_view_guest_delegate.cc new file mode 100644 index 0000000000..49c19e4e92 --- /dev/null +++ b/src/browser/shell_web_view_guest_delegate.cc @@ -0,0 +1,54 @@ +// 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 "content/nw/src/browser/shell_web_view_guest_delegate.h" + +#include "content/public/browser/child_process_security_policy.h" +#include "content/public/browser/render_process_host.h" +#include "extensions/browser/api/web_request/web_request_api.h" +#include "extensions/browser/guest_view/web_view/web_view_constants.h" + +namespace extensions { + +ShellWebViewGuestDelegate::ShellWebViewGuestDelegate( + WebViewGuest* web_view_guest) + : web_view_guest_(web_view_guest), + nw_disabled_(false), + weak_ptr_factory_(this) { +} + +ShellWebViewGuestDelegate::~ShellWebViewGuestDelegate() { +} + +void ShellWebViewGuestDelegate::OnAttachWebViewHelpers( + content::WebContents* contents) { +} + +void ShellWebViewGuestDelegate::OnDidAttachToEmbedder() { + web_view_guest_->attach_params()->GetBoolean("nwdisable", &nw_disabled_); + if (nw_disabled_) + return; + content::RenderProcessHost* host = + web_view_guest_->web_contents()->GetRenderProcessHost(); + content::ChildProcessSecurityPolicy::GetInstance()->GrantScheme( + host->GetID(), url::kFileScheme); +} + +void ShellWebViewGuestDelegate::OnDidCommitProvisionalLoadForFrame( + bool is_main_frame) { +} + +void ShellWebViewGuestDelegate::OnDidInitialize() { +} + +void ShellWebViewGuestDelegate::OnDocumentLoadedInFrame( + content::RenderFrameHost* render_frame_host) { +} + + +void ShellWebViewGuestDelegate::OnGuestDestroyed() { +} + +} // namespace extensions diff --git a/src/browser/shell_web_view_guest_delegate.h b/src/browser/shell_web_view_guest_delegate.h new file mode 100644 index 0000000000..efbfec1ae8 --- /dev/null +++ b/src/browser/shell_web_view_guest_delegate.h @@ -0,0 +1,50 @@ +// 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 NW_BROWSER_GUEST_VIEW_WEB_VIEW_CHROME_WEB_VIEW_GUEST_DELEGATE_H_ +#define NW_BROWSER_GUEST_VIEW_WEB_VIEW_CHROME_WEB_VIEW_GUEST_DELEGATE_H_ + +#include "extensions/browser/guest_view/web_view/web_view_guest.h" +#include "extensions/browser/guest_view/web_view/web_view_guest_delegate.h" + + +namespace extensions { + +class ShellWebViewGuestDelegate : public WebViewGuestDelegate { + public : + explicit ShellWebViewGuestDelegate(WebViewGuest* web_view_guest); + ~ShellWebViewGuestDelegate() override; + + // WebViewGuestDelegate implementation. + bool HandleContextMenu(const content::ContextMenuParams& params) override; + void OnAttachWebViewHelpers(content::WebContents* contents) override; + void OnDidAttachToEmbedder() override; + void OnDidCommitProvisionalLoadForFrame(bool is_main_frame) override; + void OnDidInitialize() override; + void OnDocumentLoadedInFrame( + content::RenderFrameHost* render_frame_host) override; + void OnGuestDestroyed() override; + void OnShowContextMenu(int request_id, const MenuItemVector* items) override; + + WebViewGuest* web_view_guest() const { return web_view_guest_; } + + private: + content::WebContents* guest_web_contents() const { + return web_view_guest()->web_contents(); + } + + WebViewGuest* const web_view_guest_; + + bool nw_disabled_; + // This is used to ensure pending tasks will not fire after this object is + // destroyed. + base::WeakPtrFactory weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(ShellWebViewGuestDelegate); +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_GUEST_VIEW_WEB_VIEW_CHROME_WEB_VIEW_GUEST_DELEGATE_H_ + diff --git a/src/browser/standard_menus_mac.mm b/src/browser/standard_menus_mac.mm index 0599d5b44a..80ac54c770 100644 --- a/src/browser/standard_menus_mac.mm +++ b/src/browser/standard_menus_mac.mm @@ -20,7 +20,12 @@ #include "content/nw/src/browser/standard_menus_mac.h" -#include "base/sys_string_conversions.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "grit/nw_strings.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/l10n/l10n_util_mac.h" + #import // For some reaon, Apple removed setAppleMenu from the headers in 10.4, @@ -42,31 +47,31 @@ - (void)setAppleMenu:(NSMenu *)menu; void StandardMenusMac::BuildAppleMenu() { NSMenu* appleMenu = [[NSMenu alloc] initWithTitle:@""]; - NSString* name = base::SysUTF8ToNSString(app_name_); - [appleMenu addItemWithTitle:[@"About " stringByAppendingString:name] + base::string16 name = base::UTF8ToUTF16(app_name_); + [appleMenu addItemWithTitle:l10n_util::GetNSStringFWithFixup(IDS_ABOUT_MAC, name) action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; [appleMenu addItem:[NSMenuItem separatorItem]]; - [appleMenu addItemWithTitle:[@"Hide " stringByAppendingString:name] + [appleMenu addItemWithTitle:l10n_util::GetNSStringFWithFixup(IDS_HIDE_APP_MAC, name) action:@selector(hide:) keyEquivalent:@"h"]; NSMenuItem* menuItem = (NSMenuItem *)[appleMenu - addItemWithTitle:@"Hide Others" + addItemWithTitle:l10n_util::GetNSStringWithFixup(IDS_HIDE_OTHERS_MAC) action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; - [appleMenu addItemWithTitle:@"Show All" + [appleMenu addItemWithTitle:l10n_util::GetNSStringWithFixup(IDS_SHOW_ALL_MAC) action:@selector(unhideAllApplications:) keyEquivalent:@""]; [appleMenu addItem:[NSMenuItem separatorItem]]; - [appleMenu addItemWithTitle:[@"Quit " stringByAppendingString:name] - action:@selector(closeAllWindows:) + [appleMenu addItemWithTitle:l10n_util::GetNSStringFWithFixup(IDS_EXIT_MAC, name) + action:@selector(closeAllWindowsQuit:) keyEquivalent:@"q"]; // Add to menubar. @@ -77,36 +82,36 @@ - (void)setAppleMenu:(NSMenu *)menu; } void StandardMenusMac::BuildEditMenu() { - NSMenu* editMenu = [[NSMenu alloc] initWithTitle:@"Edit"]; + NSMenu* editMenu = [[NSMenu alloc] initWithTitle:l10n_util::GetNSStringWithFixup(IDS_EDIT_MENU_MAC)]; - [editMenu addItemWithTitle:@"Undo" + [editMenu addItemWithTitle:l10n_util::GetNSStringWithFixup(IDS_EDIT_UNDO_MAC) action:@selector(undo:) keyEquivalent:@"z"]; NSMenuItem* menuItem = (NSMenuItem *)[editMenu - addItemWithTitle:@"Redo" + addItemWithTitle:l10n_util::GetNSStringWithFixup(IDS_EDIT_REDO_MAC) action:@selector(redo:) keyEquivalent:@"z"]; [menuItem setKeyEquivalentModifierMask:(NSShiftKeyMask|NSCommandKeyMask)]; [editMenu addItem:[NSMenuItem separatorItem]]; - [editMenu addItemWithTitle:@"Cut" + [editMenu addItemWithTitle:l10n_util::GetNSStringWithFixup(IDS_CUT_MAC) action:@selector(cut:) keyEquivalent:@"x"]; - [editMenu addItemWithTitle:@"Copy" + [editMenu addItemWithTitle:l10n_util::GetNSStringWithFixup(IDS_COPY_MAC) action:@selector(copy:) keyEquivalent:@"c"]; - [editMenu addItemWithTitle:@"Paste" + [editMenu addItemWithTitle:l10n_util::GetNSStringWithFixup(IDS_PASTE_MAC) action:@selector(paste:) keyEquivalent:@"v"]; - [editMenu addItemWithTitle:@"Delete" + [editMenu addItemWithTitle:l10n_util::GetNSStringWithFixup(IDS_EDIT_DELETE_MAC) action:@selector(delete:) keyEquivalent:@""]; - [editMenu addItemWithTitle:@"Select All" + [editMenu addItemWithTitle:l10n_util::GetNSStringWithFixup(IDS_EDIT_SELECT_ALL_MAC) action:@selector(selectAll:) keyEquivalent:@"a"]; - menuItem = [[NSMenuItem alloc] initWithTitle:@"Edit" + menuItem = [[NSMenuItem alloc] initWithTitle:l10n_util::GetNSStringWithFixup(IDS_EDIT_MENU_MAC) action:nil keyEquivalent:@""]; [menuItem setSubmenu:editMenu]; @@ -117,23 +122,24 @@ - (void)setAppleMenu:(NSMenu *)menu; } void StandardMenusMac::BuildWindowMenu() { - NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; + NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:l10n_util::GetNSStringWithFixup(IDS_WINDOW_MENU_MAC)]; - [windowMenu addItemWithTitle:@"Minimize" + [windowMenu addItemWithTitle:l10n_util::GetNSStringWithFixup(IDS_MINIMIZE_WINDOW_MAC) action:@selector(performMiniaturize:) keyEquivalent:@"m"]; - [windowMenu addItemWithTitle:@"Close" + + [windowMenu addItemWithTitle:l10n_util::GetNSStringWithFixup(IDS_CLOSE_WINDOW_MAC) action:@selector(performClose:) keyEquivalent:@"w"]; [windowMenu addItem:[NSMenuItem separatorItem]]; - [windowMenu addItemWithTitle:@"Bring All to Front" + [windowMenu addItemWithTitle:l10n_util::GetNSStringWithFixup(IDS_ALL_WINDOWS_FRONT_MAC) action:@selector(arrangeInFront:) keyEquivalent:@""]; NSMenuItem* windowMenuItem = [[NSMenuItem alloc] - initWithTitle:@"Window" + initWithTitle:l10n_util::GetNSStringWithFixup(IDS_WINDOW_MENU_MAC) action:nil keyEquivalent:@""]; [windowMenuItem setSubmenu:windowMenu]; diff --git a/src/chrome_breakpad_client.cc b/src/chrome_breakpad_client.cc new file mode 100644 index 0000000000..d20083d32f --- /dev/null +++ b/src/chrome_breakpad_client.cc @@ -0,0 +1,325 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/app/chrome_breakpad_client.h" + +#include "base/atomicops.h" +#include "base/command_line.h" +#include "base/environment.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/path_service.h" +#include "base/strings/safe_sprintf.h" +#include "base/strings/string_split.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_result_codes.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/crash_keys.h" +#include "chrome/common/env_vars.h" + +#include "id/commit.h" + +#if defined(OS_WIN) +#include + +#include "base/file_version_info.h" +#include "base/win/registry.h" +#include "chrome/installer/util/google_chrome_sxs_distribution.h" +#include "chrome/installer/util/install_util.h" +#endif + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS) +#include "chrome/browser/crash_upload_list.h" +//#include "chrome/common/chrome_version_info_posix.h" +#include "content/nw/src/nw_version.h" +#endif + +#if defined(OS_POSIX) +#include "base/debug/dump_without_crashing.h" +#endif + +#if defined(OS_WIN) || defined(OS_MACOSX) +#include "chrome/installer/util/google_update_settings.h" +#endif + +#if defined(OS_ANDROID) +#include "chrome/common/descriptors_android.h" +#endif + +namespace chrome { + +namespace { + +#if defined(OS_WIN) +// This is the minimum version of google update that is required for deferred +// crash uploads to work. +const char kMinUpdateVersion[] = "1.3.21.115"; + +// The value name prefix will be of the form {chrome-version}-{pid}-{timestamp} +// (i.e., "#####.#####.#####.#####-########-########") which easily fits into a +// 63 character buffer. +const char kBrowserCrashDumpPrefixTemplate[] = "%s-%08x-%08x"; +const size_t kBrowserCrashDumpPrefixLength = 63; +char g_browser_crash_dump_prefix[kBrowserCrashDumpPrefixLength + 1] = {}; + +// These registry key to which we'll write a value for each crash dump attempt. +HKEY g_browser_crash_dump_regkey = NULL; + +// A atomic counter to make each crash dump value name unique. +base::subtle::Atomic32 g_browser_crash_dump_count = 0; +#endif + +} // namespace + +ChromeBreakpadClient::ChromeBreakpadClient() {} + +ChromeBreakpadClient::~ChromeBreakpadClient() {} + +void ChromeBreakpadClient::SetBreakpadClientIdFromGUID( + const std::string& client_guid) { + crash_keys::SetCrashClientIdFromGUID(client_guid); +} +#if defined(OS_WIN) +bool ChromeBreakpadClient::GetAlternativeCrashDumpLocation( + base::FilePath* crash_dir) { + // By setting the BREAKPAD_DUMP_LOCATION environment variable, an alternate + // location to write breakpad crash dumps can be set. + scoped_ptr env(base::Environment::Create()); + std::string alternate_crash_dump_location; + if (env->GetVar("BREAKPAD_DUMP_LOCATION", &alternate_crash_dump_location)) { + *crash_dir = base::FilePath::FromUTF8Unsafe(alternate_crash_dump_location); + return true; + } + + return false; +} + +void ChromeBreakpadClient::GetProductNameAndVersion( + const base::FilePath& exe_path, + base::string16* product_name, + base::string16* version, + base::string16* special_build, + base::string16* channel_name) { + DCHECK(product_name); + DCHECK(version); + DCHECK(special_build); + DCHECK(channel_name); + + scoped_ptr version_info( + FileVersionInfo::CreateFileVersionInfo(exe_path)); + + if (version_info.get()) { + // Get the information from the file. + *version = version_info->product_version(); + if (!version_info->is_official_build()) + version->append(base::ASCIIToUTF16("-devel")); + + *product_name = version_info->product_short_name(); + *special_build = version_info->special_build(); + } else { + // No version info found. Make up the values. + *product_name = base::ASCIIToUTF16("Chrome"); + *version = base::ASCIIToUTF16("0.0.0.0-devel"); + } +} + +bool ChromeBreakpadClient::ShouldShowRestartDialog(base::string16* title, + base::string16* message, + bool* is_rtl_locale) { + scoped_ptr env(base::Environment::Create()); + if (!env->HasVar(env_vars::kShowRestart) || + !env->HasVar(env_vars::kRestartInfo)) { + return false; + } + + std::string restart_info; + env->GetVar(env_vars::kRestartInfo, &restart_info); + + // The CHROME_RESTART var contains the dialog strings separated by '|'. + // See ChromeBrowserMainPartsWin::PrepareRestartOnCrashEnviroment() + // for details. + std::vector dlg_strings; + base::SplitString(restart_info, '|', &dlg_strings); + + if (dlg_strings.size() < 3) + return false; + + *title = base::ASCIIToUTF16(dlg_strings[0]); + *message = base::ASCIIToUTF16(dlg_strings[1]); + *is_rtl_locale = dlg_strings[2] == env_vars::kRtlLocale; + return true; +} + +bool ChromeBreakpadClient::AboutToRestart() { + scoped_ptr env(base::Environment::Create()); + if (!env->HasVar(env_vars::kRestartInfo)) + return false; + + env->SetVar(env_vars::kShowRestart, "1"); + return true; +} + +bool ChromeBreakpadClient::GetDeferredUploadsSupported(bool) { + return false; +} + +bool ChromeBreakpadClient::GetIsPerUserInstall(const base::FilePath& exe_path) { + return false; +} + +bool ChromeBreakpadClient::GetShouldDumpLargerDumps(bool is_per_user_install) { + return true; +} + +int ChromeBreakpadClient::GetResultCodeRespawnFailed() { + return chrome::RESULT_CODE_RESPAWN_FAILED; +} + +void ChromeBreakpadClient::InitBrowserCrashDumpsRegKey() { + DCHECK(g_browser_crash_dump_regkey == NULL); + + base::win::RegKey regkey; + if (regkey.Create(HKEY_CURRENT_USER, + chrome::kBrowserCrashDumpAttemptsRegistryPath, + KEY_ALL_ACCESS) != ERROR_SUCCESS) { + return; + } + + // We use the current process id and the current tick count as a (hopefully) + // unique combination for the crash dump value. There's a small chance that + // across a reboot we might have a crash dump signal written, and the next + // browser process might have the same process id and tick count, but crash + // before consuming the signal (overwriting the signal with an identical one). + // For now, we're willing to live with that risk. + int length = base::strings::SafeSPrintf(g_browser_crash_dump_prefix, + kBrowserCrashDumpPrefixTemplate, + "chrome-version", + ::GetCurrentProcessId(), + ::GetTickCount()); + if (length <= 0) { + NOTREACHED(); + g_browser_crash_dump_prefix[0] = '\0'; + return; + } + + // Hold the registry key in a global for update on crash dump. + g_browser_crash_dump_regkey = regkey.Take(); +} + +void ChromeBreakpadClient::RecordCrashDumpAttempt(bool is_real_crash) { + // If we're not a browser (or the registry is unavailable to us for some + // reason) then there's nothing to do. + if (g_browser_crash_dump_regkey == NULL) + return; + + // Generate the final value name we'll use (appends the crash number to the + // base value name). + const size_t kMaxValueSize = 2 * kBrowserCrashDumpPrefixLength; + char value_name[kMaxValueSize + 1] = {}; + int length = base::strings::SafeSPrintf( + value_name, + "%s-%x", + g_browser_crash_dump_prefix, + base::subtle::NoBarrier_AtomicIncrement(&g_browser_crash_dump_count, 1)); + + if (length > 0) { + DWORD value_dword = is_real_crash ? 1 : 0; + ::RegSetValueExA(g_browser_crash_dump_regkey, value_name, 0, REG_DWORD, + reinterpret_cast(&value_dword), + sizeof(value_dword)); + } +} + +bool ChromeBreakpadClient::ReportingIsEnforcedByPolicy(bool* breakpad_enabled) { +#if 0 +// Determine whether configuration management allows loading the crash reporter. +// Since the configuration management infrastructure is not initialized at this +// point, we read the corresponding registry key directly. The return status +// indicates whether policy data was successfully read. If it is true, +// |breakpad_enabled| contains the value set by policy. + string16 key_name = UTF8ToUTF16(policy::key::kMetricsReportingEnabled); + DWORD value = 0; + base::win::RegKey hklm_policy_key(HKEY_LOCAL_MACHINE, + policy::kRegistryChromePolicyKey, KEY_READ); + if (hklm_policy_key.ReadValueDW(key_name.c_str(), &value) == ERROR_SUCCESS) { + *breakpad_enabled = value != 0; + return true; + } + + base::win::RegKey hkcu_policy_key(HKEY_CURRENT_USER, + policy::kRegistryChromePolicyKey, KEY_READ); + if (hkcu_policy_key.ReadValueDW(key_name.c_str(), &value) == ERROR_SUCCESS) { + *breakpad_enabled = value != 0; + return true; + } + + return false; +#endif + return true; +} +#endif + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS) +void ChromeBreakpadClient::GetProductNameAndVersion(std::string* product_name, + std::string* version) { + DCHECK(product_name); + DCHECK(version); + + *product_name = "node-webkit"; + + *version = NW_VERSION_STRING " " NW_COMMIT_HASH; +} + +base::FilePath ChromeBreakpadClient::GetReporterLogFilename() { + return base::FilePath(CrashUploadList::kReporterLogFilename); +} +#endif + +bool ChromeBreakpadClient::GetCrashDumpLocation(base::FilePath* crash_dir) { + // By setting the BREAKPAD_DUMP_LOCATION environment variable, an alternate + // location to write breakpad crash dumps can be set. + scoped_ptr env(base::Environment::Create()); + std::string alternate_crash_dump_location; + if (env->GetVar("BREAKPAD_DUMP_LOCATION", &alternate_crash_dump_location)) { + base::FilePath crash_dumps_dir_path = + base::FilePath::FromUTF8Unsafe(alternate_crash_dump_location); + PathService::Override(chrome::DIR_CRASH_DUMPS, crash_dumps_dir_path); + } + + return PathService::Get(chrome::DIR_CRASH_DUMPS, crash_dir); +} + +size_t ChromeBreakpadClient::RegisterCrashKeys() { + return crash_keys::RegisterChromeCrashKeys(); +} + +bool ChromeBreakpadClient::IsRunningUnattended() { + // scoped_ptr env(base::Environment::Create()); + // return env->HasVar(env_vars::kHeadless); + return true; +} + +bool ChromeBreakpadClient::GetCollectStatsConsent() { + return false; +} + +#if defined(OS_ANDROID) +int ChromeBreakpadClient::GetAndroidMinidumpDescriptor() { + return kAndroidMinidumpDescriptor; +} +#endif + +bool ChromeBreakpadClient::EnableBreakpadForProcess( + const std::string& process_type) { + return process_type == switches::kRendererProcess || + process_type == switches::kPluginProcess || + process_type == switches::kPpapiPluginProcess || + process_type == switches::kZygoteProcess || + process_type == switches::kGpuProcess; +} + +} // namespace chrome diff --git a/src/chrome_breakpad_client.h b/src/chrome_breakpad_client.h new file mode 100644 index 0000000000..d056f23f95 --- /dev/null +++ b/src/chrome_breakpad_client.h @@ -0,0 +1,75 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_APP_CHROME_BREAKPAD_CLIENT_H_ +#define CHROME_APP_CHROME_BREAKPAD_CLIENT_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "components/breakpad/app/breakpad_client.h" + +namespace chrome { + +class ChromeBreakpadClient : public breakpad::BreakpadClient { + public: + ChromeBreakpadClient(); + virtual ~ChromeBreakpadClient(); + + // breakpad::BreakpadClient implementation. +#if defined(OS_WIN) + virtual bool GetAlternativeCrashDumpLocation(base::FilePath* crash_dir) + OVERRIDE; + virtual void GetProductNameAndVersion(const base::FilePath& exe_path, + base::string16* product_name, + base::string16* version, + base::string16* special_build, + base::string16* channel_name) OVERRIDE; + virtual bool ShouldShowRestartDialog(base::string16* title, + base::string16* message, + bool* is_rtl_locale) OVERRIDE; + virtual bool AboutToRestart() OVERRIDE; + virtual bool GetDeferredUploadsSupported(bool is_per_user_install) OVERRIDE; + virtual bool GetIsPerUserInstall(const base::FilePath& exe_path) OVERRIDE; + virtual bool GetShouldDumpLargerDumps(bool is_per_user_install) OVERRIDE; + virtual int GetResultCodeRespawnFailed() OVERRIDE; + virtual void InitBrowserCrashDumpsRegKey() OVERRIDE; + virtual void RecordCrashDumpAttempt(bool is_real_crash) OVERRIDE; +#endif + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS) + virtual void GetProductNameAndVersion(std::string* product_name, + std::string* version) OVERRIDE; + virtual base::FilePath GetReporterLogFilename() OVERRIDE; +#endif + + virtual bool GetCrashDumpLocation(base::FilePath* crash_dir) OVERRIDE; + + virtual size_t RegisterCrashKeys() OVERRIDE; + + virtual bool IsRunningUnattended() OVERRIDE; + + virtual bool GetCollectStatsConsent() OVERRIDE; + +#if defined(OS_WIN) || defined(OS_MACOSX) + virtual bool ReportingIsEnforcedByPolicy(bool* breakpad_enabled) OVERRIDE; +#endif + +#if defined(OS_ANDROID) + virtual int GetAndroidMinidumpDescriptor() OVERRIDE; +#endif + +#if defined(OS_MACOSX) + virtual void InstallAdditionalFilters(BreakpadRef breakpad) OVERRIDE; +#endif + + virtual bool EnableBreakpadForProcess( + const std::string& process_type) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(ChromeBreakpadClient); +}; + +} // namespace chrome + +#endif // CHROME_APP_CHROME_BREAKPAD_CLIENT_H_ diff --git a/src/chrome_breakpad_client_mac.mm b/src/chrome_breakpad_client_mac.mm new file mode 100644 index 0000000000..9e7a966c1d --- /dev/null +++ b/src/chrome_breakpad_client_mac.mm @@ -0,0 +1,25 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/app/chrome_breakpad_client.h" + +#include + +#include "base/command_line.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/strings/sys_string_conversions.h" +#include "chrome/common/chrome_switches.h" +//#include "policy/policy_constants.h" + + +namespace chrome { + +void ChromeBreakpadClient::InstallAdditionalFilters(BreakpadRef breakpad) { +} + +bool ChromeBreakpadClient::ReportingIsEnforcedByPolicy(bool* breakpad_enabled) { + return false; +} + +} // namespace chrome diff --git a/src/common/common_message_generator.h b/src/common/common_message_generator.h index 1879cedc93..5b837918aa 100644 --- a/src/common/common_message_generator.h +++ b/src/common/common_message_generator.h @@ -1,2 +1,5 @@ +#include "extensions/common/extension_messages.h" #include "content/nw/src/api/api_messages.h" #include "content/nw/src/common/print_messages.h" +#include "chrome/common/chrome_utility_messages.h" +#include "chrome/common/chrome_utility_printing_messages.h" diff --git a/src/common/gpu_internals.cc b/src/common/gpu_internals.cc deleted file mode 100644 index bf67a32d91..0000000000 --- a/src/common/gpu_internals.cc +++ /dev/null @@ -1,446 +0,0 @@ -// Copyright (c) 2012 Intel Corp -// Copyright (c) 2012 The Chromium Authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co -// pies of the Software, and to permit persons to whom the Software is furnished -// to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in al -// l copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM -// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES -// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH -// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -#include "content/nw/src/common/gpu_internals.h" - -#include "base/callback.h" -#include "base/command_line.h" -#include "base/logging.h" -#include "base/memory/scoped_ptr.h" -#include "base/string_number_conversions.h" -#include "base/stringprintf.h" -#include "base/sys_info.h" -#include "base/values.h" -#include "cc/switches.h" -#include "content/public/browser/compositor_util.h" -#include "content/public/browser/gpu_data_manager.h" -#include "content/public/browser/gpu_data_manager_observer.h" -#include "content/public/common/content_switches.h" -#include "content/public/common/gpu_info.h" -#include "third_party/angle/src/common/version.h" - -using content::GpuDataManager; -using content::GpuFeatureType; - -namespace { - -struct GpuFeatureInfo { - std::string name; - uint32 blocked; - bool disabled; - std::string disabled_description; - bool fallback_to_software; -}; - -DictionaryValue* NewDescriptionValuePair(const std::string& desc, - const std::string& value) { - DictionaryValue* dict = new DictionaryValue(); - dict->SetString("description", desc); - dict->SetString("value", value); - return dict; -} - -DictionaryValue* NewDescriptionValuePair(const std::string& desc, - Value* value) { - DictionaryValue* dict = new DictionaryValue(); - dict->SetString("description", desc); - dict->Set("value", value); - return dict; -} - -Value* NewStatusValue(const char* name, const char* status) { - DictionaryValue* value = new DictionaryValue(); - value->SetString("name", name); - value->SetString("status", status); - return value; -} - -#if defined(OS_WIN) -// Output DxDiagNode tree as nested array of {description,value} pairs -ListValue* DxDiagNodeToList(const content::DxDiagNode& node) { - ListValue* list = new ListValue(); - for (std::map::const_iterator it = - node.values.begin(); - it != node.values.end(); - ++it) { - list->Append(NewDescriptionValuePair(it->first, it->second)); - } - - for (std::map::const_iterator it = - node.children.begin(); - it != node.children.end(); - ++it) { - ListValue* sublist = DxDiagNodeToList(it->second); - list->Append(NewDescriptionValuePair(it->first, sublist)); - } - return list; -} -#endif - -std::string GPUDeviceToString(const content::GPUInfo::GPUDevice& gpu) { - std::string vendor = base::StringPrintf("0x%04x", gpu.vendor_id); - if (!gpu.vendor_string.empty()) - vendor += " [" + gpu.vendor_string + "]"; - std::string device = base::StringPrintf("0x%04x", gpu.device_id); - if (!gpu.device_string.empty()) - device += " [" + gpu.device_string + "]"; - return base::StringPrintf( - "VENDOR = %s, DEVICE= %s", vendor.c_str(), device.c_str()); -} - -DictionaryValue* GpuInfoAsDictionaryValue() { - content::GPUInfo gpu_info = GpuDataManager::GetInstance()->GetGPUInfo(); - ListValue* basic_info = new ListValue(); - basic_info->Append(NewDescriptionValuePair( - "Initialization time", - base::Int64ToString(gpu_info.initialization_time.InMilliseconds()))); - basic_info->Append(NewDescriptionValuePair( - "Sandboxed", - Value::CreateBooleanValue(gpu_info.sandboxed))); - basic_info->Append(NewDescriptionValuePair( - "GPU0", GPUDeviceToString(gpu_info.gpu))); - for (size_t i = 0; i < gpu_info.secondary_gpus.size(); ++i) { - basic_info->Append(NewDescriptionValuePair( - base::StringPrintf("GPU%d", static_cast(i + 1)), - GPUDeviceToString(gpu_info.secondary_gpus[i]))); - } - basic_info->Append(NewDescriptionValuePair( - "Optimus", Value::CreateBooleanValue(gpu_info.optimus))); - basic_info->Append(NewDescriptionValuePair( - "AMD switchable", Value::CreateBooleanValue(gpu_info.amd_switchable))); - basic_info->Append(NewDescriptionValuePair("Driver vendor", - gpu_info.driver_vendor)); - basic_info->Append(NewDescriptionValuePair("Driver version", - gpu_info.driver_version)); - basic_info->Append(NewDescriptionValuePair("Driver date", - gpu_info.driver_date)); - basic_info->Append(NewDescriptionValuePair("Pixel shader version", - gpu_info.pixel_shader_version)); - basic_info->Append(NewDescriptionValuePair("Vertex shader version", - gpu_info.vertex_shader_version)); - basic_info->Append(NewDescriptionValuePair("Machine model", - gpu_info.machine_model)); - basic_info->Append(NewDescriptionValuePair("GL version", - gpu_info.gl_version)); - basic_info->Append(NewDescriptionValuePair("GL_VENDOR", - gpu_info.gl_vendor)); - basic_info->Append(NewDescriptionValuePair("GL_RENDERER", - gpu_info.gl_renderer)); - basic_info->Append(NewDescriptionValuePair("GL_VERSION", - gpu_info.gl_version_string)); - basic_info->Append(NewDescriptionValuePair("GL_EXTENSIONS", - gpu_info.gl_extensions)); - - DictionaryValue* info = new DictionaryValue(); - info->Set("basic_info", basic_info); - -#if defined(OS_WIN) - ListValue* perf_info = new ListValue(); - perf_info->Append(NewDescriptionValuePair( - "Graphics", - base::StringPrintf("%.1f", gpu_info.performance_stats.graphics))); - perf_info->Append(NewDescriptionValuePair( - "Gaming", - base::StringPrintf("%.1f", gpu_info.performance_stats.gaming))); - perf_info->Append(NewDescriptionValuePair( - "Overall", - base::StringPrintf("%.1f", gpu_info.performance_stats.overall))); - info->Set("performance_info", perf_info); - - Value* dx_info; - if (gpu_info.dx_diagnostics.children.size()) - dx_info = DxDiagNodeToList(gpu_info.dx_diagnostics); - else - dx_info = Value::CreateNullValue(); - info->Set("diagnostics", dx_info); -#endif - - return info; -} - -// Determine if accelerated-2d-canvas is supported, which depends on whether -// lose_context could happen and whether skia is the backend. -bool SupportsAccelerated2dCanvas() { - if (GpuDataManager::GetInstance()->GetGPUInfo().can_lose_context) - return false; -#if defined(USE_SKIA) - return true; -#else - return false; -#endif -} - -Value* GetFeatureStatus() { - const CommandLine& command_line = *CommandLine::ForCurrentProcess(); - bool gpu_access_blocked = !GpuDataManager::GetInstance()->GpuAccessAllowed(); - - uint32 flags = GpuDataManager::GetInstance()->GetBlacklistedFeatures(); - DictionaryValue* status = new DictionaryValue(); - - const GpuFeatureInfo kGpuFeatureInfo[] = { - { - "2d_canvas", - flags & content::GPU_FEATURE_TYPE_ACCELERATED_2D_CANVAS, - command_line.HasSwitch(switches::kDisableAccelerated2dCanvas) || - !SupportsAccelerated2dCanvas(), - "Accelerated 2D canvas is unavailable: either disabled at the command" - " line or not supported by the current system.", - true - }, - { - "compositing", - flags & content::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING, - command_line.HasSwitch(switches::kDisableAcceleratedCompositing), - "Accelerated compositing has been disabled, either via about:flags or" - " command line. This adversely affects performance of all hardware" - " accelerated features.", - true - }, - { - "3d_css", - flags & (content::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING | - content::GPU_FEATURE_TYPE_3D_CSS), - command_line.HasSwitch(switches::kDisableAcceleratedLayers), - "Accelerated layers have been disabled at the command line.", - false - }, - { - "css_animation", - flags & (content::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING | - content::GPU_FEATURE_TYPE_3D_CSS), - command_line.HasSwitch(cc::switches::kDisableThreadedAnimation) || - command_line.HasSwitch(switches::kDisableAcceleratedCompositing) || - command_line.HasSwitch(switches::kDisableAcceleratedLayers), - "Accelerated CSS animation has been disabled at the command line.", - true - }, - { - "webgl", - flags & content::GPU_FEATURE_TYPE_WEBGL, -#if defined(OS_ANDROID) - !command_line.HasSwitch(switches::kEnableExperimentalWebGL), -#else - command_line.HasSwitch(switches::kDisableExperimentalWebGL), -#endif - "WebGL has been disabled, either via about:flags or command line.", - false - }, - { - "multisampling", - flags & content::GPU_FEATURE_TYPE_MULTISAMPLING, - command_line.HasSwitch(switches::kDisableGLMultisampling), - "Multisampling has been disabled, either via about:flags or command" - " line.", - false - }, - { - "flash_3d", - flags & content::GPU_FEATURE_TYPE_FLASH3D, - command_line.HasSwitch(switches::kDisableFlash3d), - "Using 3d in flash has been disabled, either via about:flags or" - " command line.", - false - }, - { - "flash_stage3d", - flags & content::GPU_FEATURE_TYPE_FLASH_STAGE3D, - command_line.HasSwitch(switches::kDisableFlashStage3d), - "Using Stage3d in Flash has been disabled, either via about:flags or" - " command line.", - false - }, - { - "texture_sharing", - flags & content::GPU_FEATURE_TYPE_TEXTURE_SHARING, - command_line.HasSwitch(switches::kDisableImageTransportSurface), - "Sharing textures between processes has been disabled, either via" - " about:flags or command line.", - false - }, - { - "video_decode", - flags & content::GPU_FEATURE_TYPE_ACCELERATED_VIDEO_DECODE, - command_line.HasSwitch(switches::kDisableAcceleratedVideoDecode), - "Accelerated video decode has been disabled, either via about:flags" - " or command line.", - true - }, - { - "video", - flags & content::GPU_FEATURE_TYPE_ACCELERATED_VIDEO, - command_line.HasSwitch(switches::kDisableAcceleratedVideo) || - command_line.HasSwitch(switches::kDisableAcceleratedCompositing), - "Accelerated video presentation has been disabled, either via" - " about:flags or command line.", - true - }, - { - "panel_fitting", - flags & content::GPU_FEATURE_TYPE_PANEL_FITTING, -#if defined(OS_CHROMEOS) - command_line.HasSwitch(ash::switches::kAshDisablePanelFitting), -#else - true, -#endif - "Panel fitting is unavailable, either disabled at the command" - " line or not supported by the current system.", - false - } - }; - const size_t kNumFeatures = sizeof(kGpuFeatureInfo) / sizeof(GpuFeatureInfo); - - // Build the feature_status field. - { - ListValue* feature_status_list = new ListValue(); - - for (size_t i = 0; i < kNumFeatures; ++i) { - std::string status; - if (kGpuFeatureInfo[i].disabled) { - status = "disabled"; - if (kGpuFeatureInfo[i].name == "css_animation") { - status += "_software_animated"; - } else { - if (kGpuFeatureInfo[i].fallback_to_software) - status += "_software"; - else - status += "_off"; - } - } else if (GpuDataManager::GetInstance()->ShouldUseSoftwareRendering()) { - status = "unavailable_software"; - } else if (kGpuFeatureInfo[i].blocked || - gpu_access_blocked) { - status = "unavailable"; - if (kGpuFeatureInfo[i].fallback_to_software) - status += "_software"; - else - status += "_off"; - } else { - status = "enabled"; - if (kGpuFeatureInfo[i].name == "webgl" && - (command_line.HasSwitch(switches::kDisableAcceleratedCompositing) || - (flags & content::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING))) - status += "_readback"; - bool has_thread = content::IsThreadedCompositingEnabled(); - if (kGpuFeatureInfo[i].name == "compositing") { - bool force_compositing = - content::IsForceCompositingModeEnabled(); - if (force_compositing) - status += "_force"; - if (has_thread) - status += "_threaded"; - } - if (kGpuFeatureInfo[i].name == "css_animation") { - if (has_thread) - status = "accelerated_threaded"; - else - status = "accelerated"; - } - } - feature_status_list->Append( - NewStatusValue(kGpuFeatureInfo[i].name.c_str(), status.c_str())); - } - content::GPUInfo gpu_info = GpuDataManager::GetInstance()->GetGPUInfo(); - if (gpu_info.secondary_gpus.size() > 0 || - gpu_info.optimus || gpu_info.amd_switchable) { - std::string gpu_switching; - switch (GpuDataManager::GetInstance()->GetGpuSwitchingOption()) { - case content::GPU_SWITCHING_OPTION_AUTOMATIC: - gpu_switching = "gpu_switching_automatic"; - break; - case content::GPU_SWITCHING_OPTION_FORCE_DISCRETE: - gpu_switching = "gpu_switching_force_discrete"; - break; - case content::GPU_SWITCHING_OPTION_FORCE_INTEGRATED: - gpu_switching = "gpu_switching_force_integrated"; - break; - default: - break; - } - feature_status_list->Append( - NewStatusValue("gpu_switching", gpu_switching.c_str())); - } - status->Set("featureStatus", feature_status_list); - } - - // Build the problems list. - { - ListValue* problem_list = - GpuDataManager::GetInstance()->GetBlacklistReasons(); - - if (gpu_access_blocked) { - DictionaryValue* problem = new DictionaryValue(); - problem->SetString("description", - "GPU process was unable to boot. Access to GPU disallowed."); - problem->Set("crBugs", new ListValue()); - problem->Set("webkitBugs", new ListValue()); - problem_list->Append(problem); - } - - for (size_t i = 0; i < kNumFeatures; ++i) { - if (kGpuFeatureInfo[i].disabled) { - DictionaryValue* problem = new DictionaryValue(); - problem->SetString( - "description", kGpuFeatureInfo[i].disabled_description); - problem->Set("crBugs", new ListValue()); - problem->Set("webkitBugs", new ListValue()); - problem_list->Append(problem); - } - } - - status->Set("problems", problem_list); - } - - return status; -} - -} // namespace - -void PrintGpuInfo() { - // Get GPU Info. - scoped_ptr gpu_info_val( - GpuInfoAsDictionaryValue()); - - // Add in blacklisting features - Value* feature_status = GetFeatureStatus(); - if (feature_status) - gpu_info_val->Set("featureStatus", feature_status); - - LOG(ERROR) << *gpu_info_val; -} - -void PrintClientInfo() { - DictionaryValue* dict = new DictionaryValue(); - - dict->SetString("operating_system", - base::SysInfo::OperatingSystemName() + " " + - base::SysInfo::OperatingSystemVersion()); - dict->SetString("angle_revision", base::UintToString(BUILD_REVISION)); -#if defined(USE_SKIA) - dict->SetString("graphics_backend", "Skia"); -#else - dict->SetString("graphics_backend", "Core Graphics"); -#endif - dict->SetString("blacklist_version", - GpuDataManager::GetInstance()->GetBlacklistVersion()); - - LOG(ERROR) << *dict; - - delete dict; -} diff --git a/src/common/print_messages.cc b/src/common/print_messages.cc index 1ecd992542..33420bcfd8 100644 --- a/src/common/print_messages.cc +++ b/src/common/print_messages.cc @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "content/nw/src/common/print_messages.h" +#include "chrome/common/print_messages.h" #include "base/basictypes.h" -#include "base/string16.h" -#include "ui/gfx/size.h" +#include "base/strings/string16.h" +#include "ui/gfx/geometry/size.h" PrintMsg_Print_Params::PrintMsg_Print_Params() : page_size(), @@ -24,10 +24,9 @@ PrintMsg_Print_Params::PrintMsg_Print_Params() preview_ui_id(-1), preview_request_id(0), is_first_request(false), - print_scaling_option(WebKit::WebPrintScalingOptionSourceSize), + print_scaling_option(blink::WebPrintScalingOptionSourceSize), print_to_pdf(false), display_header_footer(false), - date(), title(), url(), should_print_backgrounds(false) { @@ -51,12 +50,11 @@ void PrintMsg_Print_Params::Reset() { preview_ui_id = -1; preview_request_id = 0; is_first_request = false; - print_scaling_option = WebKit::WebPrintScalingOptionSourceSize; + print_scaling_option = blink::WebPrintScalingOptionSourceSize; print_to_pdf = false; display_header_footer = false; - date = string16(); - title = string16(); - url = string16(); + title = base::string16(); + url = base::string16(); should_print_backgrounds = false; } @@ -81,3 +79,14 @@ PrintHostMsg_RequestPrintPreview_Params:: PrintHostMsg_RequestPrintPreview_Params:: ~PrintHostMsg_RequestPrintPreview_Params() {} + +PrintHostMsg_SetOptionsFromDocument_Params:: + PrintHostMsg_SetOptionsFromDocument_Params() + : is_scaling_disabled(false), + copies(0), + duplex(printing::UNKNOWN_DUPLEX_MODE) { +} + +PrintHostMsg_SetOptionsFromDocument_Params:: + ~PrintHostMsg_SetOptionsFromDocument_Params() { +} diff --git a/src/common/print_messages.h b/src/common/print_messages.h index 591299055e..bffa44c4fa 100644 --- a/src/common/print_messages.h +++ b/src/common/print_messages.h @@ -8,17 +8,19 @@ #include #include +#include "base/memory/shared_memory.h" #include "base/values.h" -#include "base/shared_memory.h" #include "ipc/ipc_message_macros.h" +#include "printing/page_range.h" #include "printing/page_size_margins.h" #include "printing/print_job_constants.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebPrintScalingOption.h" +#include "third_party/WebKit/public/web/WebPrintScalingOption.h" +#include "ui/gfx/ipc/gfx_param_traits.h" #include "ui/gfx/native_widget_types.h" -#include "ui/gfx/rect.h" +#include "ui/gfx/geometry/rect.h" -#ifndef NW_COMMON_PRINT_MESSAGES_H_ -#define NW_COMMON_PRINT_MESSAGES_H_ +#ifndef CHROME_COMMON_PRINT_MESSAGES_H_ +#define CHROME_COMMON_PRINT_MESSAGES_H_ struct PrintMsg_Print_Params { PrintMsg_Print_Params(); @@ -42,12 +44,11 @@ struct PrintMsg_Print_Params { int32 preview_ui_id; int preview_request_id; bool is_first_request; - WebKit::WebPrintScalingOption print_scaling_option; + blink::WebPrintScalingOption print_scaling_option; bool print_to_pdf; bool display_header_footer; - string16 date; - string16 title; - string16 url; + base::string16 title; + base::string16 url; bool should_print_backgrounds; }; @@ -71,12 +72,27 @@ struct PrintHostMsg_RequestPrintPreview_Params { bool selection_only; }; +struct PrintHostMsg_SetOptionsFromDocument_Params { + PrintHostMsg_SetOptionsFromDocument_Params(); + ~PrintHostMsg_SetOptionsFromDocument_Params(); + + bool is_scaling_disabled; + int copies; + printing::DuplexMode duplex; + printing::PageRanges page_ranges; +}; + #endif // CHROME_COMMON_PRINT_MESSAGES_H_ #define IPC_MESSAGE_START PrintMsgStart -IPC_ENUM_TRAITS(printing::MarginType) -IPC_ENUM_TRAITS(WebKit::WebPrintScalingOption) +IPC_ENUM_TRAITS_MAX_VALUE(printing::MarginType, + printing::MARGIN_TYPE_LAST) +IPC_ENUM_TRAITS_MAX_VALUE(blink::WebPrintScalingOption, + blink::WebPrintScalingOptionLast) +IPC_ENUM_TRAITS_MIN_MAX_VALUE(printing::DuplexMode, + printing::UNKNOWN_DUPLEX_MODE, + printing::SHORT_EDGE) // Parameters for a render request. IPC_STRUCT_TRAITS_BEGIN(PrintMsg_Print_Params) @@ -137,9 +153,6 @@ IPC_STRUCT_TRAITS_BEGIN(PrintMsg_Print_Params) // Specifies if the header and footer should be rendered. IPC_STRUCT_TRAITS_MEMBER(display_header_footer) - // Date string to be printed as header if requested by the user. - IPC_STRUCT_TRAITS_MEMBER(date) - // Title string to be printed as header if requested by the user. IPC_STRUCT_TRAITS_MEMBER(title) @@ -167,6 +180,25 @@ IPC_STRUCT_TRAITS_BEGIN(PrintHostMsg_RequestPrintPreview_Params) IPC_STRUCT_TRAITS_MEMBER(selection_only) IPC_STRUCT_TRAITS_END() +IPC_STRUCT_TRAITS_BEGIN(printing::PageRange) + IPC_STRUCT_TRAITS_MEMBER(from) + IPC_STRUCT_TRAITS_MEMBER(to) +IPC_STRUCT_TRAITS_END() + +IPC_STRUCT_TRAITS_BEGIN(PrintHostMsg_SetOptionsFromDocument_Params) + // Specifies whether print scaling is enabled or not. + IPC_STRUCT_TRAITS_MEMBER(is_scaling_disabled) + + // Specifies number of copies to be printed. + IPC_STRUCT_TRAITS_MEMBER(copies) + + // Specifies paper handling option. + IPC_STRUCT_TRAITS_MEMBER(duplex) + + // Specifies page range to be printed. + IPC_STRUCT_TRAITS_MEMBER(page_ranges) +IPC_STRUCT_TRAITS_END() + IPC_STRUCT_TRAITS_BEGIN(printing::PageSizeMargins) IPC_STRUCT_TRAITS_MEMBER(content_width) IPC_STRUCT_TRAITS_MEMBER(content_height) @@ -187,10 +219,6 @@ IPC_STRUCT_TRAITS_END() // Parameters to describe a rendered document. IPC_STRUCT_BEGIN(PrintHostMsg_DidPreviewDocument_Params) - // True when we can reuse existing preview data. |metafile_data_handle| and - // |data_size| should not be used when this is true. - IPC_STRUCT_MEMBER(bool, reuse_existing_data) - // A shared memory handle to metafile data. IPC_STRUCT_MEMBER(base::SharedMemoryHandle, metafile_data_handle) @@ -259,9 +287,6 @@ IPC_STRUCT_BEGIN(PrintHostMsg_DidPrintPage_Params) // Page number. IPC_STRUCT_MEMBER(int, page_number) - // Shrink factor used to render this page. - IPC_STRUCT_MEMBER(double, actual_shrink) - // The size of the page the page author specified. IPC_STRUCT_MEMBER(gfx::Size, page_size) @@ -271,10 +296,11 @@ IPC_STRUCT_END() // Parameters for the IPC message ViewHostMsg_ScriptedPrint IPC_STRUCT_BEGIN(PrintHostMsg_ScriptedPrint_Params) - IPC_STRUCT_MEMBER(int, cookie) - IPC_STRUCT_MEMBER(int, expected_pages_count) - IPC_STRUCT_MEMBER(bool, has_selection) - IPC_STRUCT_MEMBER(printing::MarginType, margin_type) +IPC_STRUCT_MEMBER(int, cookie) +IPC_STRUCT_MEMBER(int, expected_pages_count) +IPC_STRUCT_MEMBER(bool, has_selection) +IPC_STRUCT_MEMBER(bool, is_scripted) +IPC_STRUCT_MEMBER(printing::MarginType, margin_type) IPC_STRUCT_END() @@ -283,20 +309,25 @@ IPC_STRUCT_END() // Tells the render view to initiate print preview for the entire document. IPC_MESSAGE_ROUTED1(PrintMsg_InitiatePrintPreview, bool /* selection_only */) -// Tells the render view to initiate printing or print preview for a particular -// node, depending on which mode the render view is in. +// Tells the render frame to initiate printing or print preview for a particular +// node, depending on which mode the render frame is in. IPC_MESSAGE_ROUTED0(PrintMsg_PrintNodeUnderContextMenu) // Tells the renderer to print the print preview tab's PDF plugin without // showing the print dialog. (This is the final step in the print preview // workflow.) IPC_MESSAGE_ROUTED1(PrintMsg_PrintForPrintPreview, - DictionaryValue /* settings */) + base::DictionaryValue /* settings */) +#if defined(ENABLE_BASIC_PRINTING) // Tells the render view to switch the CSS to print media type, renders every // requested pages and switch back the CSS to display media type. IPC_MESSAGE_ROUTED0(PrintMsg_PrintPages) +// Like PrintMsg_PrintPages, but using the print preview document's frame/node. +IPC_MESSAGE_ROUTED0(PrintMsg_PrintForSystemDialog) +#endif // ENABLE_BASIC_PRINTING + // Tells the render view that printing is done so it can clean up. IPC_MESSAGE_ROUTED1(PrintMsg_PrintingDone, bool /* success */) @@ -309,13 +340,7 @@ IPC_MESSAGE_ROUTED1(PrintMsg_SetScriptedPrintingBlocked, // requested pages for print preview using the given |settings|. This gets // called multiple times as the user updates settings. IPC_MESSAGE_ROUTED1(PrintMsg_PrintPreview, - DictionaryValue /* settings */) - -// Like PrintMsg_PrintPages, but using the print preview document's frame/node. -IPC_MESSAGE_ROUTED0(PrintMsg_PrintForSystemDialog) - -// Tells a renderer to stop blocking script initiated printing. -IPC_MESSAGE_ROUTED0(PrintMsg_ResetScriptedPrintCount) + base::DictionaryValue /* settings */) // Messages sent from the renderer to the browser. @@ -327,6 +352,10 @@ IPC_SYNC_MESSAGE_ROUTED1_1(PrintHostMsg_DuplicateSection, base::SharedMemoryHandle /* browser handle */) #endif +// Check if printing is enabled. +IPC_SYNC_MESSAGE_ROUTED0_1(PrintHostMsg_IsPrintingEnabled, + bool /* is_enabled */) + // Tells the browser that the renderer is done calculating the number of // rendered pages according to the specified settings. IPC_MESSAGE_ROUTED2(PrintHostMsg_DidGetPrintedPagesCount, @@ -352,10 +381,11 @@ IPC_SYNC_MESSAGE_ROUTED0_1(PrintHostMsg_GetDefaultPrintSettings, // The renderer wants to update the current print settings with new // |job_settings|. -IPC_SYNC_MESSAGE_ROUTED2_1(PrintHostMsg_UpdatePrintSettings, +IPC_SYNC_MESSAGE_ROUTED2_2(PrintHostMsg_UpdatePrintSettings, int /* document_cookie */, - DictionaryValue /* job_settings */, - PrintMsg_PrintPages_Params /* current_settings */) + base::DictionaryValue /* job_settings */, + PrintMsg_PrintPages_Params /* current_settings */, + bool /* canceled */) // It's the renderer that controls the printing process when it is generated // by javascript. This step is about showing UI to the user to select the @@ -366,17 +396,6 @@ IPC_SYNC_MESSAGE_ROUTED1_1(PrintHostMsg_ScriptedPrint, PrintMsg_PrintPages_Params /* settings chosen by the user*/) -#if defined(USE_X11) -// Asks the browser to create a temporary file for the renderer to fill -// in resulting NativeMetafile in printing. -IPC_SYNC_MESSAGE_CONTROL0_2(PrintHostMsg_AllocateTempFileForPrinting, - base::FileDescriptor /* temp file fd */, - int /* fd in browser*/) -IPC_MESSAGE_CONTROL2(PrintHostMsg_TempFileForPrintingWritten, - int /* render_view_id */, - int /* fd in browser */) -#endif - // Asks the browser to do print preview. IPC_MESSAGE_ROUTED1(PrintHostMsg_RequestPrintPreview, PrintHostMsg_RequestPrintPreview_Params /* params */) @@ -405,6 +424,9 @@ IPC_SYNC_MESSAGE_ROUTED2_1(PrintHostMsg_CheckForCancel, int /* request id */, bool /* print preview cancelled */) +// This is sent when there are invalid printer settings. +IPC_MESSAGE_ROUTED0(PrintHostMsg_ShowInvalidPrinterSettingsError) + // Sends back to the browser the complete rendered document (non-draft mode, // used for printing) that was requested by a PrintMsg_PrintPreview message. // The memory handle in this message is already valid in the browser process. @@ -431,9 +453,13 @@ IPC_MESSAGE_ROUTED1(PrintHostMsg_PrintPreviewInvalidPrinterSettings, // Run a nested message loop in the renderer until print preview for // window.print() finishes. -IPC_SYNC_MESSAGE_ROUTED1_0(PrintHostMsg_ScriptedPrintPreview, - bool /* is_modifiable */) +IPC_SYNC_MESSAGE_ROUTED0_0(PrintHostMsg_SetupScriptedPrintPreview) + +// Tell the browser to show the print preview, when the document is sufficiently +// loaded such that the renderer can determine whether it is modifiable or not. +IPC_MESSAGE_ROUTED1(PrintHostMsg_ShowScriptedPrintPreview, + bool /* is_modifiable */) -// Notify the browser that the PDF in the initiator renderer has disabled print -// scaling option. -IPC_MESSAGE_ROUTED0(PrintHostMsg_PrintPreviewScalingDisabled) +// Notify the browser to set print presets based on source PDF document. +IPC_MESSAGE_ROUTED1(PrintHostMsg_SetOptionsFromDocument, + PrintHostMsg_SetOptionsFromDocument_Params /* params */) diff --git a/src/common/shell_extensions_client.cc b/src/common/shell_extensions_client.cc new file mode 100644 index 0000000000..8701a2b803 --- /dev/null +++ b/src/common/shell_extensions_client.cc @@ -0,0 +1,228 @@ +// 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 "content/nw/src/common/shell_extensions_client.h" + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "extensions/common/api/generated_schemas.h" +#include "extensions/common/common_manifest_handlers.h" +#include "extensions/common/extension_urls.h" +#include "extensions/common/features/api_feature.h" +#include "extensions/common/features/base_feature_provider.h" +#include "extensions/common/features/behavior_feature.h" +#include "extensions/common/features/json_feature_provider_source.h" +#include "extensions/common/features/manifest_feature.h" +#include "extensions/common/features/permission_feature.h" +#include "extensions/common/features/simple_feature.h" +#include "extensions/common/manifest_handler.h" +#include "extensions/common/permissions/permission_message_provider.h" +#include "extensions/common/permissions/permissions_info.h" +#include "extensions/common/permissions/permissions_provider.h" +#include "extensions/common/url_pattern_set.h" +//#include "extensions/shell/common/api/generated_schemas.h" +//#include "grit/app_shell_resources.h" +#include "grit/extensions_resources.h" + +namespace extensions { + +namespace { + +template +SimpleFeature* CreateFeature() { + return new FeatureClass; +} + +// TODO(jamescook): Refactor ChromePermissionsMessageProvider so we can share +// code. For now, this implementation does nothing. +class ShellPermissionMessageProvider : public PermissionMessageProvider { + public: + ShellPermissionMessageProvider() {} + ~ShellPermissionMessageProvider() override {} + + // PermissionMessageProvider implementation. + PermissionMessages GetPermissionMessages( + const PermissionSet* permissions, + Manifest::Type extension_type) const override { + return PermissionMessages(); + } + + std::vector GetWarningMessages( + const PermissionSet* permissions, + Manifest::Type extension_type) const override { + return std::vector(); + } + + std::vector GetWarningMessagesDetails( + const PermissionSet* permissions, + Manifest::Type extension_type) const override { + return std::vector(); + } + + bool IsPrivilegeIncrease(const PermissionSet* old_permissions, + const PermissionSet* new_permissions, + Manifest::Type extension_type) const override { + // Ensure we implement this before shipping. + CHECK(false); + return false; + } + + private: + DISALLOW_COPY_AND_ASSIGN(ShellPermissionMessageProvider); +}; + +base::LazyInstance + g_permission_message_provider = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +ShellExtensionsClient::ShellExtensionsClient() + : extensions_api_permissions_(ExtensionsAPIPermissions()) { +} + +ShellExtensionsClient::~ShellExtensionsClient() { +} + +void ShellExtensionsClient::Initialize() { + RegisterCommonManifestHandlers(); + ManifestHandler::FinalizeRegistration(); + // TODO(jamescook): Do we need to whitelist any extensions? + + PermissionsInfo::GetInstance()->AddProvider(extensions_api_permissions_); +} + +const PermissionMessageProvider& +ShellExtensionsClient::GetPermissionMessageProvider() const { + NOTIMPLEMENTED(); + return g_permission_message_provider.Get(); +} + +const std::string ShellExtensionsClient::GetProductName() { + return "app_shell"; +} + +scoped_ptr ShellExtensionsClient::CreateFeatureProvider( + const std::string& name) const { + scoped_ptr provider; + scoped_ptr source( + CreateFeatureProviderSource(name)); + if (name == "api") { + provider.reset(new BaseFeatureProvider(source->dictionary(), + CreateFeature)); + } else if (name == "manifest") { + provider.reset(new BaseFeatureProvider(source->dictionary(), + CreateFeature)); + } else if (name == "permission") { + provider.reset(new BaseFeatureProvider(source->dictionary(), + CreateFeature)); + } else if (name == "behavior") { + provider.reset(new BaseFeatureProvider(source->dictionary(), + CreateFeature)); + } else { + NOTREACHED(); + } + return provider.Pass(); +} + +scoped_ptr +ShellExtensionsClient::CreateFeatureProviderSource( + const std::string& name) const { + scoped_ptr source( + new JSONFeatureProviderSource(name)); + if (name == "api") { + source->LoadJSON(IDR_EXTENSION_API_FEATURES); + //source->LoadJSON(IDR_SHELL_EXTENSION_API_FEATURES); + } else if (name == "manifest") { + source->LoadJSON(IDR_EXTENSION_MANIFEST_FEATURES); + } else if (name == "permission") { + source->LoadJSON(IDR_EXTENSION_PERMISSION_FEATURES); + } else if (name == "behavior") { + source->LoadJSON(IDR_EXTENSION_BEHAVIOR_FEATURES); + } else { + NOTREACHED(); + source.reset(); + } + return source.Pass(); +} + +void ShellExtensionsClient::FilterHostPermissions( + const URLPatternSet& hosts, + URLPatternSet* new_hosts, + std::set* messages) const { + NOTIMPLEMENTED(); +} + +void ShellExtensionsClient::FilterHostPermissions( + const URLPatternSet& hosts, + URLPatternSet* new_hosts, + PermissionIDSet* permissions) const { + NOTIMPLEMENTED(); +} + +void ShellExtensionsClient::SetScriptingWhitelist( + const ScriptingWhitelist& whitelist) { + scripting_whitelist_ = whitelist; +} + +const ExtensionsClient::ScriptingWhitelist& +ShellExtensionsClient::GetScriptingWhitelist() const { + // TODO(jamescook): Real whitelist. + return scripting_whitelist_; +} + +URLPatternSet ShellExtensionsClient::GetPermittedChromeSchemeHosts( + const Extension* extension, + const APIPermissionSet& api_permissions) const { + // NOTIMPLEMENTED(); + return URLPatternSet(); +} + +bool ShellExtensionsClient::IsScriptableURL(const GURL& url, + std::string* error) const { + NOTIMPLEMENTED(); + return true; +} + +bool ShellExtensionsClient::IsAPISchemaGenerated( + const std::string& name) const { + return core_api::GeneratedSchemas::IsGenerated(name); // || + // shell::api::GeneratedSchemas::IsGenerated(name); +} + +base::StringPiece ShellExtensionsClient::GetAPISchema( + const std::string& name) const { + // Schema for app_shell-only APIs. + // if (shell::api::GeneratedSchemas::IsGenerated(name)) + // return shell::api::GeneratedSchemas::Get(name); + + // Core extensions APIs. + return core_api::GeneratedSchemas::Get(name); +} + +void ShellExtensionsClient::RegisterAPISchemaResources( + ExtensionAPI* api) const { +} + +bool ShellExtensionsClient::ShouldSuppressFatalErrors() const { + return true; +} + +std::string ShellExtensionsClient::GetWebstoreBaseURL() const { + return extension_urls::kChromeWebstoreBaseURL; +} + +std::string ShellExtensionsClient::GetWebstoreUpdateURL() const { + return extension_urls::kChromeWebstoreUpdateURL; +} + +bool ShellExtensionsClient::IsBlacklistUpdateURL(const GURL& url) const { + return false; +} + +std::set ShellExtensionsClient::GetBrowserImagePaths( + const Extension* extension) { + return std::set(); +} + +} // namespace extensions diff --git a/src/common/shell_extensions_client.h b/src/common/shell_extensions_client.h new file mode 100644 index 0000000000..bb6fd5535d --- /dev/null +++ b/src/common/shell_extensions_client.h @@ -0,0 +1,63 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_SHELL_COMMON_SHELL_EXTENSIONS_CLIENT_H_ +#define EXTENSIONS_SHELL_COMMON_SHELL_EXTENSIONS_CLIENT_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "extensions/common/extensions_client.h" +#include "extensions/common/permissions/extensions_api_permissions.h" + +namespace extensions { + +// The app_shell implementation of ExtensionsClient. +class ShellExtensionsClient : public ExtensionsClient { + public: + ShellExtensionsClient(); + ~ShellExtensionsClient() override; + + // ExtensionsClient overrides: + void Initialize() override; + const PermissionMessageProvider& GetPermissionMessageProvider() + const override; + const std::string GetProductName() override; + scoped_ptr CreateFeatureProvider( + const std::string& name) const override; + scoped_ptr CreateFeatureProviderSource( + const std::string& name) const override; + void FilterHostPermissions( + const URLPatternSet& hosts, + URLPatternSet* new_hosts, + std::set* messages) const override; + void FilterHostPermissions(const URLPatternSet& hosts, + URLPatternSet* new_hosts, + PermissionIDSet* permissions) const override; + void SetScriptingWhitelist(const ScriptingWhitelist& whitelist) override; + const ScriptingWhitelist& GetScriptingWhitelist() const override; + URLPatternSet GetPermittedChromeSchemeHosts( + const Extension* extension, + const APIPermissionSet& api_permissions) const override; + bool IsScriptableURL(const GURL& url, std::string* error) const override; + bool IsAPISchemaGenerated(const std::string& name) const override; + base::StringPiece GetAPISchema(const std::string& name) const override; + void RegisterAPISchemaResources(ExtensionAPI* api) const override; + bool ShouldSuppressFatalErrors() const override; + std::string GetWebstoreBaseURL() const override; + std::string GetWebstoreUpdateURL() const override; + bool IsBlacklistUpdateURL(const GURL& url) const override; + std::set GetBrowserImagePaths( + const Extension* extension) override; + + private: + const ExtensionsAPIPermissions extensions_api_permissions_; + + ScriptingWhitelist scripting_whitelist_; + + DISALLOW_COPY_AND_ASSIGN(ShellExtensionsClient); +}; + +} // namespace extensions + +#endif // EXTENSIONS_SHELL_COMMON_SHELL_EXTENSIONS_CLIENT_H_ diff --git a/src/common/shell_switches.cc b/src/common/shell_switches.cc index 5f056165c1..9ffbdef8e8 100644 --- a/src/common/shell_switches.cc +++ b/src/common/shell_switches.cc @@ -42,6 +42,7 @@ const char kNodeMain[] = "node-main"; // snapshot file path const char kSnapshot[] = "snapshot"; +const char kDomStorageQuota[] = "ds-quota"; const char kmMain[] = "main"; const char kmName[] = "name"; @@ -70,6 +71,12 @@ const char kmMaxHeight[] = "max_height"; const char kmResizable[] = "resizable"; const char kmAsDesktop[] = "as_desktop"; const char kmFullscreen[] = "fullscreen"; +const char kmInitialFocus[] = "focus"; +const char kmTransparent[] = "transparent"; +const char kmDisableTransparency[] = "disable-transparency"; + +// Make windows icon hide show or hide in taskbar. +const char kmShowInTaskbar[] = "show_in_taskbar"; // Start with the kiosk mode, see Opera's page for description: // http://www.opera.com/support/mastering/kiosk/ @@ -78,6 +85,9 @@ const char kmKiosk[] = "kiosk"; // Make windows stays on the top of all other windows. const char kmAlwaysOnTop[] = "always-on-top"; +// Make window visible on all workspaces. +const char kmVisibleOnAllWorkspaces[] = "visible-on-all-workspaces"; + // Whether we should support WebGL. const char kmWebgl[] = "webgl"; @@ -96,10 +106,14 @@ const char kmUserAgent[] = "user-agent"; const char kmRemotePages[] = "node-remote"; const char kmNewInstance[] = "new-instance"; +const char kmInjectJSDocStart[] = "inject-js-start"; +const char kmInjectJSDocEnd[] = "inject-js-end"; +const char kmInjectCSS[] = "inject-css"; #if defined(OS_WIN) // Enable conversion from vector to raster for any page. const char kPrintRaster[] = "print-raster"; #endif +const char kCrashDumpsDir[] = "crash-dumps-dir"; } // namespace switches diff --git a/src/common/shell_switches.h b/src/common/shell_switches.h index 1e133b3e59..4e0b532cbc 100644 --- a/src/common/shell_switches.h +++ b/src/common/shell_switches.h @@ -7,6 +7,11 @@ #ifndef CONTENT_NW_SRC_SHELL_SWITCHES_H_ #define CONTENT_NW_SRC_SHELL_SWITCHES_H_ +namespace nw { + const int kMenuHeight = 25; + const int kToolbarHeight = 34; +} + namespace switches { extern const char kContentShellDataPath[]; @@ -16,6 +21,7 @@ extern const char kUrl[]; extern const char kWorkingDirectory[]; extern const char kNodeMain[]; extern const char kSnapshot[]; +extern const char kDomStorageQuota[]; // Manifest settings extern const char kmMain[]; @@ -44,8 +50,13 @@ extern const char kmMaxHeight[]; extern const char kmResizable[]; extern const char kmAsDesktop[]; extern const char kmFullscreen[]; +extern const char kmShowInTaskbar[]; extern const char kmKiosk[]; extern const char kmAlwaysOnTop[]; +extern const char kmVisibleOnAllWorkspaces[]; +extern const char kmInitialFocus[]; +extern const char kmTransparent[]; +extern const char kmDisableTransparency[]; extern const char kmWebgl[]; extern const char kmJava[]; @@ -54,10 +65,16 @@ extern const char kmPageCache[]; extern const char kmUserAgent[]; extern const char kmRemotePages[]; extern const char kmNewInstance[]; +extern const char kmInjectJSDocStart[]; +extern const char kmInjectJSDocEnd[]; +extern const char kmInjectCSS[]; #if defined(OS_WIN) extern const char kPrintRaster[]; #endif + +extern const char kCrashDumpsDir[]; + } // namespace switches #endif // CONTENT_NW_SRC_SHELL_SWITCHES_H_ diff --git a/src/crash_handler_host_linux.cc b/src/crash_handler_host_linux.cc new file mode 100644 index 0000000000..2e20231c60 --- /dev/null +++ b/src/crash_handler_host_linux.cc @@ -0,0 +1,530 @@ +// 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 "content/nw/src/crash_handler_host_linux.h" + +#include +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/files/file_path.h" +#include "base/format_macros.h" +#include "base/linux_util.h" +#include "base/logging.h" +#include "base/memory/singleton.h" +#include "base/message_loop/message_loop.h" +#include "base/path_service.h" +#include "base/posix/eintr_wrapper.h" +#include "base/rand_util.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread.h" +#include "breakpad/src/client/linux/handler/exception_handler.h" +#include "breakpad/src/client/linux/minidump_writer/linux_dumper.h" +#include "breakpad/src/client/linux/minidump_writer/minidump_writer.h" +#include "components/breakpad/app/breakpad_linux_impl.h" + +#include "chrome/common/chrome_paths.h" +#include "chrome/common/env_vars.h" +#include "content/public/browser/browser_thread.h" + +#if defined(OS_ANDROID) +#include + +#define SYS_read __NR_read +#endif + +using content::BrowserThread; +using google_breakpad::ExceptionHandler; + +using namespace breakpad; + +namespace { + +// The length of the control message: +const unsigned kControlMsgSize = + CMSG_SPACE(2*sizeof(int)) + CMSG_SPACE(sizeof(struct ucred)); +// The length of the regular payload: +const unsigned kCrashContextSize = sizeof(ExceptionHandler::CrashContext); + +// Handles the crash dump and frees the allocated BreakpadInfo struct. +void CrashDumpTask(CrashHandlerHostLinux* handler, BreakpadInfo* info) { + if (handler->IsShuttingDown()) + return; + + HandleCrashDump(*info); + delete[] info->filename; + delete[] info->process_type; + delete[] info->distro; + delete info->crash_keys; + delete info; +} + +} // namespace + +// Since classes derived from CrashHandlerHostLinux are singletons, it's only +// destroyed at the end of the processes lifetime, which is greater in span than +// the lifetime of the IO message loop. Thus, all calls to base::Bind() use +// non-refcounted pointers. + +CrashHandlerHostLinux::CrashHandlerHostLinux() + : shutting_down_(false) { + int fds[2]; + // We use SOCK_SEQPACKET rather than SOCK_DGRAM to prevent the process from + // sending datagrams to other sockets on the system. The sandbox may prevent + // the process from calling socket() to create new sockets, but it'll still + // inherit some sockets. With PF_UNIX+SOCK_DGRAM, it can call sendmsg to send + // a datagram to any (abstract) socket on the same system. With + // SOCK_SEQPACKET, this is prevented. + CHECK_EQ(socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds), 0); + static const int on = 1; + + // Enable passcred on the server end of the socket + CHECK_EQ(setsockopt(fds[1], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)), 0); + + process_socket_ = fds[0]; + browser_socket_ = fds[1]; + + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&CrashHandlerHostLinux::Init, base::Unretained(this))); +} + +CrashHandlerHostLinux::~CrashHandlerHostLinux() { + (void) HANDLE_EINTR(close(process_socket_)); + (void) HANDLE_EINTR(close(browser_socket_)); +} + +void CrashHandlerHostLinux::Init() { + base::MessageLoopForIO* ml = base::MessageLoopForIO::current(); + CHECK(ml->WatchFileDescriptor( + browser_socket_, true /* persistent */, + base::MessageLoopForIO::WATCH_READ, + &file_descriptor_watcher_, this)); + ml->AddDestructionObserver(this); +} + +void CrashHandlerHostLinux::InitCrashUploaderThread() { + SetProcessType(); + uploader_thread_.reset( + new base::Thread(std::string(process_type_ + "_crash_uploader").c_str())); + uploader_thread_->Start(); +} + +void CrashHandlerHostLinux::OnFileCanWriteWithoutBlocking(int fd) { + NOTREACHED(); +} + +void CrashHandlerHostLinux::OnFileCanReadWithoutBlocking(int fd) { + DCHECK_EQ(fd, browser_socket_); + + // A process has crashed and has signaled us by writing a datagram + // to the death signal socket. The datagram contains the crash context needed + // for writing the minidump as well as a file descriptor and a credentials + // block so that they can't lie about their pid. + // + // The message sender is in chrome/app/breakpad_linux.cc. + + struct msghdr msg = {0}; + struct iovec iov[kCrashIovSize]; + + // Freed in WriteDumpFile(); + char* crash_context = new char[kCrashContextSize]; + // Freed in CrashDumpTask(); + char* distro = new char[kDistroSize + 1]; +#if defined(ADDRESS_SANITIZER) + asan_report_str_ = new char[kMaxAsanReportSize + 1]; +#endif + + // Freed in CrashDumpTask(). + CrashKeyStorage* crash_keys = new CrashKeyStorage; + google_breakpad::SerializedNonAllocatingMap* serialized_crash_keys; + size_t crash_keys_size = crash_keys->Serialize( + const_cast( + &serialized_crash_keys)); + + char* tid_buf_addr = NULL; + int tid_fd = -1; + uint64_t uptime; + size_t oom_size; + char control[kControlMsgSize]; + const ssize_t expected_msg_size = + kCrashContextSize + + kDistroSize + 1 + + sizeof(tid_buf_addr) + sizeof(tid_fd) + + sizeof(uptime) + +#if defined(ADDRESS_SANITIZER) + kMaxAsanReportSize + 1 + +#endif + sizeof(oom_size) + + crash_keys_size; + iov[0].iov_base = crash_context; + iov[0].iov_len = kCrashContextSize; + iov[1].iov_base = distro; + iov[1].iov_len = kDistroSize + 1; + iov[2].iov_base = &tid_buf_addr; + iov[2].iov_len = sizeof(tid_buf_addr); + iov[3].iov_base = &tid_fd; + iov[3].iov_len = sizeof(tid_fd); + iov[4].iov_base = &uptime; + iov[4].iov_len = sizeof(uptime); + iov[5].iov_base = &oom_size; + iov[5].iov_len = sizeof(oom_size); + iov[6].iov_base = serialized_crash_keys; + iov[6].iov_len = crash_keys_size; +#if defined(ADDRESS_SANITIZER) + iov[7].iov_base = asan_report_str_; + iov[7].iov_len = kMaxAsanReportSize + 1; +#endif + msg.msg_iov = iov; + msg.msg_iovlen = kCrashIovSize; + msg.msg_control = control; + msg.msg_controllen = kControlMsgSize; + + const ssize_t msg_size = HANDLE_EINTR(recvmsg(browser_socket_, &msg, 0)); + if (msg_size != expected_msg_size) { + LOG(ERROR) << "Error reading from death signal socket. Crash dumping" + << " is disabled." + << " msg_size:" << msg_size + << " errno:" << errno; + file_descriptor_watcher_.StopWatchingFileDescriptor(); + return; + } + + if (msg.msg_controllen != kControlMsgSize || + msg.msg_flags & ~MSG_TRUNC) { + LOG(ERROR) << "Received death signal message with the wrong size;" + << " msg.msg_controllen:" << msg.msg_controllen + << " msg.msg_flags:" << msg.msg_flags + << " kCrashContextSize:" << kCrashContextSize + << " kControlMsgSize:" << kControlMsgSize; + return; + } + + // Walk the control payload an extract the file descriptor and validated pid. + pid_t crashing_pid = -1; + int partner_fd = -1; + int signal_fd = -1; + for (struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); hdr; + hdr = CMSG_NXTHDR(&msg, hdr)) { + if (hdr->cmsg_level != SOL_SOCKET) + continue; + if (hdr->cmsg_type == SCM_RIGHTS) { + const unsigned len = hdr->cmsg_len - + (((uint8_t*)CMSG_DATA(hdr)) - (uint8_t*)hdr); + DCHECK_EQ(len % sizeof(int), 0u); + const unsigned num_fds = len / sizeof(int); + if (num_fds != 2) { + // A nasty process could try and send us too many descriptors and + // force a leak. + LOG(ERROR) << "Death signal contained wrong number of descriptors;" + << " num_fds:" << num_fds; + for (unsigned i = 0; i < num_fds; ++i) + (void) HANDLE_EINTR(close(reinterpret_cast(CMSG_DATA(hdr))[i])); + return; + } else { + partner_fd = reinterpret_cast(CMSG_DATA(hdr))[0]; + signal_fd = reinterpret_cast(CMSG_DATA(hdr))[1]; + } + } else if (hdr->cmsg_type == SCM_CREDENTIALS) { + const struct ucred *cred = + reinterpret_cast(CMSG_DATA(hdr)); + crashing_pid = cred->pid; + } + } + + if (crashing_pid == -1 || partner_fd == -1 || signal_fd == -1) { + LOG(ERROR) << "Death signal message didn't contain all expected control" + << " messages"; + if (partner_fd >= 0) + (void) HANDLE_EINTR(close(partner_fd)); + if (signal_fd >= 0) + (void) HANDLE_EINTR(close(signal_fd)); + return; + } + + // Kernel bug workaround (broken in 2.6.30 and 2.6.32, working in 2.6.38). + // The kernel doesn't translate PIDs in SCM_CREDENTIALS across PID + // namespaces. Thus |crashing_pid| might be garbage from our point of view. + // In the future we can remove this workaround, but we have to wait a couple + // of years to be sure that it's worked its way out into the world. + // TODO(thestig) Remove the workaround when Ubuntu Lucid is deprecated. + + // The crashing process closes its copy of the signal_fd immediately after + // calling sendmsg(). We can thus not reliably look for with with + // FindProcessHoldingSocket(). But by necessity, it has to keep the + // partner_fd open until the crashdump is complete. + ino_t inode_number; + if (!base::FileDescriptorGetInode(&inode_number, partner_fd)) { + LOG(WARNING) << "Failed to get inode number for passed socket"; + (void) HANDLE_EINTR(close(partner_fd)); + (void) HANDLE_EINTR(close(signal_fd)); + return; + } + (void) HANDLE_EINTR(close(partner_fd)); + + pid_t actual_crashing_pid = -1; + if (!base::FindProcessHoldingSocket(&actual_crashing_pid, inode_number)) { + LOG(WARNING) << "Failed to find process holding other end of crash reply " + "socket"; + (void) HANDLE_EINTR(close(signal_fd)); + return; + } + + crashing_pid = actual_crashing_pid; + + // The crashing TID set inside the compromised context via + // sys_gettid() in ExceptionHandler::HandleSignal might be wrong (if + // the kernel supports PID namespacing) and may need to be + // translated. + // + // We expect the crashing thread to be in sys_read(), waiting for us to + // write to |signal_fd|. Most newer kernels where we have the different pid + // namespaces also have /proc/[pid]/syscall, so we can look through + // |actual_crashing_pid|'s thread group and find the thread that's in the + // read syscall with the right arguments. + + std::string expected_syscall_data; + // /proc/[pid]/syscall is formatted as follows: + // syscall_number arg1 ... arg6 sp pc + // but we just check syscall_number through arg3. + base::StringAppendF(&expected_syscall_data, "%d 0x%x %p 0x1 ", + SYS_read, tid_fd, tid_buf_addr); + bool syscall_supported = false; + pid_t crashing_tid = + base::FindThreadIDWithSyscall(crashing_pid, + expected_syscall_data, + &syscall_supported); + if (crashing_tid == -1) { + // We didn't find the thread we want. Maybe it didn't reach + // sys_read() yet or the thread went away. We'll just take a + // guess here and assume the crashing thread is the thread group + // leader. If procfs syscall is not supported by the kernel, then + // we assume the kernel also does not support TID namespacing and + // trust the TID passed by the crashing process. + LOG(WARNING) << "Could not translate tid - assuming crashing thread is " + "thread group leader; syscall_supported=" << syscall_supported; + crashing_tid = crashing_pid; + } + + ExceptionHandler::CrashContext* bad_context = + reinterpret_cast(crash_context); + bad_context->tid = crashing_tid; + + // Freed in CrashDumpTask(); + BreakpadInfo* info = new BreakpadInfo; + + info->fd = -1; + info->process_type_length = process_type_.length(); + char* process_type_str = new char[info->process_type_length + 1]; + process_type_.copy(process_type_str, info->process_type_length); + process_type_str[info->process_type_length] = '\0'; + info->process_type = process_type_str; + + info->distro_length = strlen(distro); + info->distro = distro; +#if defined(OS_ANDROID) + // Nothing gets uploaded in android. + info->upload = false; +#else + info->upload = false; +#endif + + info->crash_keys = crash_keys; + +#if defined(ADDRESS_SANITIZER) + info->asan_report_str = asan_report_str_; + info->asan_report_length = strlen(asan_report_str_); +#endif + info->process_start_time = uptime; + info->oom_size = oom_size; + + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&CrashHandlerHostLinux::WriteDumpFile, + base::Unretained(this), + info, + crashing_pid, + crash_context, + signal_fd)); +} + +void CrashHandlerHostLinux::WriteDumpFile(BreakpadInfo* info, + pid_t crashing_pid, + char* crash_context, + int signal_fd) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + + base::FilePath dumps_path("/tmp"); + PathService::Get(base::DIR_TEMP, &dumps_path); + if (!info->upload) + PathService::Get(chrome::DIR_CRASH_DUMPS, &dumps_path); + const uint64 rand = base::RandUint64(); + const std::string minidump_filename = + base::StringPrintf("%s/chromium-%s-minidump-%016" PRIx64 ".dmp", + dumps_path.value().c_str(), + process_type_.c_str(), + rand); + + if (!google_breakpad::WriteMinidump(minidump_filename.c_str(), + kMaxMinidumpFileSize, + crashing_pid, crash_context, + kCrashContextSize, + google_breakpad::MappingList(), + google_breakpad::AppMemoryList())) { + LOG(ERROR) << "Failed to write crash dump for pid " << crashing_pid; + } +#if defined(ADDRESS_SANITIZER) + // Create a temporary file holding the AddressSanitizer report. + const std::string log_filename = + base::StringPrintf("%s/chromium-%s-minidump-%016" PRIx64 ".log", + dumps_path.value().c_str(), + process_type_.c_str(), + rand); + FILE* logfile = fopen(log_filename.c_str(), "w"); + CHECK(logfile); + fprintf(logfile, "%s", asan_report_str_); + fclose(logfile); +#endif + + delete[] crash_context; + + // Freed in CrashDumpTask(); + char* minidump_filename_str = new char[minidump_filename.length() + 1]; + minidump_filename.copy(minidump_filename_str, minidump_filename.length()); + minidump_filename_str[minidump_filename.length()] = '\0'; + info->filename = minidump_filename_str; +#if defined(ADDRESS_SANITIZER) + char* minidump_log_filename_str = new char[minidump_filename.length() + 1]; + minidump_filename.copy(minidump_log_filename_str, minidump_filename.length()); + memcpy(minidump_log_filename_str + minidump_filename.length() - 3, "log", 3); + minidump_log_filename_str[minidump_filename.length()] = '\0'; + info->log_filename = minidump_log_filename_str; +#endif + info->pid = crashing_pid; + + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&CrashHandlerHostLinux::QueueCrashDumpTask, + base::Unretained(this), + info, + signal_fd)); +} + +void CrashHandlerHostLinux::QueueCrashDumpTask(BreakpadInfo* info, + int signal_fd) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + // Send the done signal to the process: it can exit now. + struct msghdr msg = {0}; + struct iovec done_iov; + done_iov.iov_base = const_cast("\x42"); + done_iov.iov_len = 1; + msg.msg_iov = &done_iov; + msg.msg_iovlen = 1; + + (void) HANDLE_EINTR(sendmsg(signal_fd, &msg, MSG_DONTWAIT | MSG_NOSIGNAL)); + (void) HANDLE_EINTR(close(signal_fd)); + + uploader_thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&CrashDumpTask, base::Unretained(this), info)); +} + +void CrashHandlerHostLinux::WillDestroyCurrentMessageLoop() { + file_descriptor_watcher_.StopWatchingFileDescriptor(); + + // If we are quitting and there are crash dumps in the queue, turn them into + // no-ops. + shutting_down_ = true; + uploader_thread_->Stop(); +} + +bool CrashHandlerHostLinux::IsShuttingDown() const { + return shutting_down_; +} + +ExtensionCrashHandlerHostLinux::ExtensionCrashHandlerHostLinux() { + InitCrashUploaderThread(); +} + +ExtensionCrashHandlerHostLinux::~ExtensionCrashHandlerHostLinux() { +} + +void ExtensionCrashHandlerHostLinux::SetProcessType() { + process_type_ = "extension"; +} + +// static +ExtensionCrashHandlerHostLinux* ExtensionCrashHandlerHostLinux::GetInstance() { + return Singleton::get(); +} + +GpuCrashHandlerHostLinux::GpuCrashHandlerHostLinux() { + InitCrashUploaderThread(); +} + +GpuCrashHandlerHostLinux::~GpuCrashHandlerHostLinux() { +} + +void GpuCrashHandlerHostLinux::SetProcessType() { + process_type_ = "gpu-process"; +} + +// static +GpuCrashHandlerHostLinux* GpuCrashHandlerHostLinux::GetInstance() { + return Singleton::get(); +} + +PluginCrashHandlerHostLinux::PluginCrashHandlerHostLinux() { + InitCrashUploaderThread(); +} + +PluginCrashHandlerHostLinux::~PluginCrashHandlerHostLinux() { +} + +void PluginCrashHandlerHostLinux::SetProcessType() { + process_type_ = "plugin"; +} + +// static +PluginCrashHandlerHostLinux* PluginCrashHandlerHostLinux::GetInstance() { + return Singleton::get(); +} + +PpapiCrashHandlerHostLinux::PpapiCrashHandlerHostLinux() { + InitCrashUploaderThread(); +} + +PpapiCrashHandlerHostLinux::~PpapiCrashHandlerHostLinux() { +} + +void PpapiCrashHandlerHostLinux::SetProcessType() { + process_type_ = "ppapi"; +} + +// static +PpapiCrashHandlerHostLinux* PpapiCrashHandlerHostLinux::GetInstance() { + return Singleton::get(); +} + +RendererCrashHandlerHostLinux::RendererCrashHandlerHostLinux() { + InitCrashUploaderThread(); +} + +RendererCrashHandlerHostLinux::~RendererCrashHandlerHostLinux() { +} + +void RendererCrashHandlerHostLinux::SetProcessType() { + process_type_ = "renderer"; +} + +// static +RendererCrashHandlerHostLinux* RendererCrashHandlerHostLinux::GetInstance() { + return Singleton::get(); +} diff --git a/src/crash_handler_host_linux.h b/src/crash_handler_host_linux.h new file mode 100644 index 0000000000..7ea69407ed --- /dev/null +++ b/src/crash_handler_host_linux.h @@ -0,0 +1,168 @@ +// 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_CRASH_HANDLER_HOST_LINUX_H_ +#define CHROME_BROWSER_CRASH_HANDLER_HOST_LINUX_H_ + +#include + +#include + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" + +namespace breakpad { +struct BreakpadInfo; +} + +namespace base { +class Thread; +} + +template struct DefaultSingletonTraits; + +// This is the base class for singleton objects which crash dump renderers and +// plugins on Linux or Android. We perform the crash dump from the browser +// because it allows us to be outside the sandbox. +// +// PluginCrashHandlerHostLinux and RendererCrashHandlerHostLinux are +// singletons that handle plugin and renderer crashes, respectively. +// +// Processes signal that they need to be dumped by sending a datagram over a +// UNIX domain socket. All processes of the same type share the client end of +// this socket which is installed in their descriptor table before exec. +class CrashHandlerHostLinux : public base::MessageLoopForIO::Watcher, + public base::MessageLoop::DestructionObserver { + public: + // Get the file descriptor which processes should be given in order to signal + // crashes to the browser. + int GetDeathSignalSocket() const { + return process_socket_; + } + + // MessagePumbLibevent::Watcher impl: + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; + + // MessageLoop::DestructionObserver impl: + virtual void WillDestroyCurrentMessageLoop() OVERRIDE; + + // Whether we are shutting down or not. + bool IsShuttingDown() const; + + protected: + CrashHandlerHostLinux(); + virtual ~CrashHandlerHostLinux(); + + // Only called in concrete subclasses. + void InitCrashUploaderThread(); + + std::string process_type_; + + private: + void Init(); + + // This is here on purpose to make CrashHandlerHostLinux abstract. + virtual void SetProcessType() = 0; + + // Do work on the FILE thread for OnFileCanReadWithoutBlocking(). + void WriteDumpFile(breakpad::BreakpadInfo* info, + pid_t crashing_pid, + char* crash_context, + int signal_fd); + + // Continue OnFileCanReadWithoutBlocking()'s work on the IO thread. + void QueueCrashDumpTask(breakpad::BreakpadInfo* info, int signal_fd); + + int process_socket_; + int browser_socket_; + + base::MessageLoopForIO::FileDescriptorWatcher file_descriptor_watcher_; + scoped_ptr uploader_thread_; + bool shutting_down_; + +#if defined(ADDRESS_SANITIZER) + char* asan_report_str_; +#endif + + DISALLOW_COPY_AND_ASSIGN(CrashHandlerHostLinux); +}; + +class ExtensionCrashHandlerHostLinux : public CrashHandlerHostLinux { + public: + // Returns the singleton instance. + static ExtensionCrashHandlerHostLinux* GetInstance(); + + private: + friend struct DefaultSingletonTraits; + ExtensionCrashHandlerHostLinux(); + virtual ~ExtensionCrashHandlerHostLinux(); + + virtual void SetProcessType() OVERRIDE; + + DISALLOW_COPY_AND_ASSIGN(ExtensionCrashHandlerHostLinux); +}; + +class GpuCrashHandlerHostLinux : public CrashHandlerHostLinux { + public: + // Returns the singleton instance. + static GpuCrashHandlerHostLinux* GetInstance(); + + private: + friend struct DefaultSingletonTraits; + GpuCrashHandlerHostLinux(); + virtual ~GpuCrashHandlerHostLinux(); + + virtual void SetProcessType() OVERRIDE; + + DISALLOW_COPY_AND_ASSIGN(GpuCrashHandlerHostLinux); +}; + +class PluginCrashHandlerHostLinux : public CrashHandlerHostLinux { + public: + // Returns the singleton instance. + static PluginCrashHandlerHostLinux* GetInstance(); + + private: + friend struct DefaultSingletonTraits; + PluginCrashHandlerHostLinux(); + virtual ~PluginCrashHandlerHostLinux(); + + virtual void SetProcessType() OVERRIDE; + + DISALLOW_COPY_AND_ASSIGN(PluginCrashHandlerHostLinux); +}; + +class PpapiCrashHandlerHostLinux : public CrashHandlerHostLinux { + public: + // Returns the singleton instance. + static PpapiCrashHandlerHostLinux* GetInstance(); + + private: + friend struct DefaultSingletonTraits; + PpapiCrashHandlerHostLinux(); + virtual ~PpapiCrashHandlerHostLinux(); + + virtual void SetProcessType() OVERRIDE; + + DISALLOW_COPY_AND_ASSIGN(PpapiCrashHandlerHostLinux); +}; + +class RendererCrashHandlerHostLinux : public CrashHandlerHostLinux { + public: + // Returns the singleton instance. + static RendererCrashHandlerHostLinux* GetInstance(); + + private: + friend struct DefaultSingletonTraits; + RendererCrashHandlerHostLinux(); + virtual ~RendererCrashHandlerHostLinux(); + + virtual void SetProcessType() OVERRIDE; + + DISALLOW_COPY_AND_ASSIGN(RendererCrashHandlerHostLinux); +}; + +#endif // CHROME_BROWSER_CRASH_HANDLER_HOST_LINUX_H_ diff --git a/src/geolocation/shell_access_token_store.cc b/src/geolocation/shell_access_token_store.cc index 587057c89e..d1c53dc8b7 100644 --- a/src/geolocation/shell_access_token_store.cc +++ b/src/geolocation/shell_access_token_store.cc @@ -2,15 +2,20 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "content/shell/geolocation/shell_access_token_store.h" +#include "content/nw/src/geolocation/shell_access_token_store.h" #include "base/bind.h" -#include "base/message_loop.h" -#include "base/utf_string_conversions.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/utf_string_conversions.h" +#include "content/public/browser/browser_thread.h" +#include "content/nw/src/shell_browser_context.h" + +namespace content { ShellAccessTokenStore::ShellAccessTokenStore( - net::URLRequestContextGetter* request_context) - : request_context_(request_context) { + content::ShellBrowserContext* shell_browser_context) + : shell_browser_context_(shell_browser_context), + system_request_context_(NULL) { } ShellAccessTokenStore::~ShellAccessTokenStore() { @@ -18,22 +23,33 @@ ShellAccessTokenStore::~ShellAccessTokenStore() { void ShellAccessTokenStore::LoadAccessTokens( const LoadAccessTokensCallbackType& callback) { - MessageLoop::current()->PostTask( + BrowserThread::PostTaskAndReply( + BrowserThread::UI, FROM_HERE, - base::Bind(&ShellAccessTokenStore::DidLoadAccessTokens, - request_context_, callback)); + base::Bind(&ShellAccessTokenStore::GetRequestContextOnUIThread, + this, + shell_browser_context_), + base::Bind(&ShellAccessTokenStore::RespondOnOriginatingThread, + this, + callback)); +} + +void ShellAccessTokenStore::GetRequestContextOnUIThread( + content::ShellBrowserContext* shell_browser_context) { + system_request_context_ = shell_browser_context->GetRequestContext(); } -void ShellAccessTokenStore::DidLoadAccessTokens( - net::URLRequestContextGetter* request_context, +void ShellAccessTokenStore::RespondOnOriginatingThread( const LoadAccessTokensCallbackType& callback) { // Since content_shell is a test executable, rather than an end user program, // we provide a dummy access_token set to avoid hitting the server. AccessTokenSet access_token_set; - access_token_set[GURL()] = ASCIIToUTF16("chromium_content_shell"); - callback.Run(access_token_set, request_context); + access_token_set[GURL()] = base::ASCIIToUTF16("chromium_content_shell"); + callback.Run(access_token_set, system_request_context_); } void ShellAccessTokenStore::SaveAccessToken( - const GURL& server_url, const string16& access_token) { + const GURL& server_url, const base::string16& access_token) { } + +} // namespace content diff --git a/src/geolocation/shell_access_token_store.h b/src/geolocation/shell_access_token_store.h index 1384ac5b1e..7cc5d33fcb 100644 --- a/src/geolocation/shell_access_token_store.h +++ b/src/geolocation/shell_access_token_store.h @@ -7,28 +7,35 @@ #include "content/public/browser/access_token_store.h" +namespace content { +class ShellBrowserContext; + // Dummy access token store used to initialise the network location provider. class ShellAccessTokenStore : public content::AccessTokenStore { public: - explicit ShellAccessTokenStore(net::URLRequestContextGetter* request_context); + explicit ShellAccessTokenStore( + content::ShellBrowserContext* shell_browser_context); private: - virtual ~ShellAccessTokenStore(); + ~ShellAccessTokenStore() final; - // AccessTokenStore - virtual void LoadAccessTokens( - const LoadAccessTokensCallbackType& callback) OVERRIDE; + void GetRequestContextOnUIThread( + content::ShellBrowserContext* shell_browser_context); + void RespondOnOriginatingThread(const LoadAccessTokensCallbackType& callback); - virtual void SaveAccessToken( - const GURL& server_url, const string16& access_token) OVERRIDE; + // AccessTokenStore + void LoadAccessTokens( + const LoadAccessTokensCallbackType& callback) override; - static void DidLoadAccessTokens( - net::URLRequestContextGetter* request_context, - const LoadAccessTokensCallbackType& callback); + void SaveAccessToken( + const GURL& server_url, const base::string16& access_token) override; - net::URLRequestContextGetter* request_context_; + content::ShellBrowserContext* shell_browser_context_; + net::URLRequestContextGetter* system_request_context_; DISALLOW_COPY_AND_ASSIGN(ShellAccessTokenStore); }; +} // namespace content + #endif // CONTENT_SHELL_GEOLOCATION_SHELL_ACCESS_TOKEN_STORE_H_ diff --git a/src/hard_error_handler_win.cc b/src/hard_error_handler_win.cc new file mode 100644 index 0000000000..0cd46d5461 --- /dev/null +++ b/src/hard_error_handler_win.cc @@ -0,0 +1,118 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/crash/app/hard_error_handler_win.h" + +#if defined(_WIN32_WINNT_WIN8) && _MSC_VER < 1700 +// The Windows 8 SDK defines FACILITY_VISUALCPP in winerror.h, and in +// delayimp.h previous to VS2012. +#undef FACILITY_VISUALCPP +#endif +#include +#include + +#include "base/basictypes.h" +#include "base/strings/string_util.h" +#include "components/breakpad/app/breakpad_client.h" + +namespace breakpad { + +namespace { +const DWORD kExceptionModuleNotFound = VcppException(ERROR_SEVERITY_ERROR, + ERROR_MOD_NOT_FOUND); +const DWORD kExceptionEntryPtNotFound = VcppException(ERROR_SEVERITY_ERROR, + ERROR_PROC_NOT_FOUND); +// This is defined in but we can't include this file here. +const DWORD FACILITY_GRAPHICS_KERNEL = 0x1E; +const DWORD NT_STATUS_ENTRYPOINT_NOT_FOUND = 0xC0000139; +const DWORD NT_STATUS_DLL_NOT_FOUND = 0xC0000135; + +// We assume that exception codes are NT_STATUS codes. +DWORD FacilityFromException(DWORD exception_code) { + return (exception_code >> 16) & 0x0FFF; +} + +// This is not a generic function. It only works with some |nt_status| values. +// Check the strings here http://msdn.microsoft.com/en-us/library/cc704588.aspx +// before attempting to use this function. +void RaiseHardErrorMsg(long nt_status, const std::string& p1, + const std::string& p2) { + // If headless just exit silently. + if (GetBreakpadClient()->IsRunningUnattended()) + return; + + HMODULE ntdll = ::GetModuleHandleA("NTDLL.DLL"); + wchar_t* msg_template = NULL; + size_t count = ::FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_FROM_HMODULE, + ntdll, + nt_status, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&msg_template), + 0, + NULL); + + if (!count) + return; + count += p1.size() + p2.size() + 1; + base::string16 message; + ::wsprintf(WriteInto(&message, count), msg_template, p1.c_str(), p2.c_str()); + // The MB_SERVICE_NOTIFICATION causes this message to be displayed by + // csrss. This means that we are not creating windows or pumping WM messages + // in this process. + ::MessageBox(NULL, message.c_str(), + L"chrome.exe", + MB_OK | MB_SERVICE_NOTIFICATION); + ::LocalFree(msg_template); +} + +void ModuleNotFoundHardError(const EXCEPTION_RECORD* ex_record) { + DelayLoadInfo* dli = reinterpret_cast( + ex_record->ExceptionInformation[0]); + if (!dli->szDll) + return; + RaiseHardErrorMsg(NT_STATUS_DLL_NOT_FOUND, dli->szDll, std::string()); +} + +void EntryPointNotFoundHardError(const EXCEPTION_RECORD* ex_record) { + DelayLoadInfo* dli = reinterpret_cast( + ex_record->ExceptionInformation[0]); + if (!dli->dlp.fImportByName) + return; + if (!dli->dlp.szProcName) + return; + if (!dli->szDll) + return; + RaiseHardErrorMsg(NT_STATUS_ENTRYPOINT_NOT_FOUND, + dli->dlp.szProcName, dli->szDll); +} + +} // namespace + +bool HardErrorHandler(EXCEPTION_POINTERS* ex_info) { + if (!ex_info) + return false; + if (!ex_info->ExceptionRecord) + return false; + + long exception = ex_info->ExceptionRecord->ExceptionCode; + if (exception == kExceptionModuleNotFound) { + ModuleNotFoundHardError(ex_info->ExceptionRecord); + return true; + } else if (exception == kExceptionEntryPtNotFound) { + EntryPointNotFoundHardError(ex_info->ExceptionRecord); + return true; + } else if (FacilityFromException(exception) == FACILITY_GRAPHICS_KERNEL) { +#if defined(USE_AURA) + RaiseHardErrorMsg(exception, std::string(), std::string()); + return true; +#else + return false; +#endif + } + return false; +} + +} // namespace breakpad diff --git a/src/hard_error_handler_win.h b/src/hard_error_handler_win.h new file mode 100644 index 0000000000..5795d19fed --- /dev/null +++ b/src/hard_error_handler_win.h @@ -0,0 +1,31 @@ +// Copyright (c) 2009 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_APP_HARD_ERROR_HANDLER_WIN_H_ +#define CHROME_APP_HARD_ERROR_HANDLER_WIN_H_ + +#include + +// This function is in charge of displaying a dialog box that informs the +// user of a fatal condition in chrome. It is meant to be called from +// breakpad's unhandled exception handler after the crash dump has been +// created. The return value will be true if we are to retry launching +// chrome (and show the 'chrome has crashed' dialog) or to silently exit. +// +// This function only handles a few known exceptions, currently: +// - Failure to load a delayload dll. +// - Failure to bind to a delayloaded import. +// - Fatal Graphics card failure (aura build only). +// +// If any of these conditions are encountered, a message box shown by +// the operating system CSRSS process via NtRaiseHardError is invoked. +// The wording and localization is up to the operating system. +// +// Do not call this function for memory related errors like heap corruption +// or stack exahustion. This function assumes that memory allocations are +// possible. +bool HardErrorHandler(EXCEPTION_POINTERS* ex_info); + +#endif // CHROME_APP_HARD_ERROR_HANDLER_WIN_H_ + diff --git a/src/mac/app-Info.plist b/src/mac/app-Info.plist index f3653998d6..f521c701d6 100644 --- a/src/mac/app-Info.plist +++ b/src/mac/app-Info.plist @@ -11,7 +11,7 @@ CFBundleIconFile nw.icns CFBundleIdentifier - com.intel.nw + io.nwjs.nw CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -19,13 +19,13 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.5.1 + 0.12.3 NSPrincipalClass NSApplication LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET}.0 LSFileQuarantineEnabled - + NSSupportsAutomaticGraphicsSwitching CFBundleDocumentTypes @@ -34,14 +34,14 @@ CFBundleTypeIconFile nw.icns CFBundleTypeName - node-webkit App + nwjs App CFBundleTypeRole Viewer LSHandlerRank Owner LSItemContentTypes - com.intel.nw.app + io.nwjs.nw.app @@ -65,23 +65,23 @@ com.pkware.zip-archive UTTypeDescription - node-webkit App + nwjs App UTTypeIconFile nw.icns UTTypeIdentifier - com.intel.nw.app + io.nwjs.nw.app UTTypeReferenceURL https://github.com/rogerwang/node-webkit/wiki/How-to-package-and-distribute-your-apps UTTypeTagSpecification com.apple.ostype - node-webkit + nwjs public.filename-extension nw public.mime-type - application/x-node-webkit-app + application/x-nwjs-app diff --git a/src/mac/helper-Info.plist b/src/mac/helper-Info.plist index a9e9ee3b23..258f659c0e 100644 --- a/src/mac/helper-Info.plist +++ b/src/mac/helper-Info.plist @@ -9,7 +9,7 @@ CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier - com.intel.nw.helper + io.nwjs.nw.helper CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -19,7 +19,7 @@ CFBundleSignature ???? LSFileQuarantineEnabled - + LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET}.0 LSUIElement diff --git a/src/media/media_capture_devices_dispatcher.cc b/src/media/media_capture_devices_dispatcher.cc index 758ed7971e..5e697f6732 100644 --- a/src/media/media_capture_devices_dispatcher.cc +++ b/src/media/media_capture_devices_dispatcher.cc @@ -2,38 +2,57 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/memory/singleton.h" + +#include "content/public/browser/media_observer.h" #include "content/nw/src/media/media_capture_devices_dispatcher.h" #include "content/public/browser/browser_thread.h" -#include "content/public/browser/media_devices_monitor.h" +#include "content/public/browser/media_capture_devices.h" #include "content/public/common/media_stream_request.h" using content::BrowserThread; using content::MediaStreamDevices; +using content::MediaCaptureDevices; + +namespace { + +// Finds a device in |devices| that has |device_id|, or NULL if not found. +const content::MediaStreamDevice* FindDeviceWithId( + const content::MediaStreamDevices& devices, + const std::string& device_id) { + content::MediaStreamDevices::const_iterator iter = devices.begin(); + for (; iter != devices.end(); ++iter) { + if (iter->id == device_id) { + return &(*iter); + } + } + return NULL; +}; + +} MediaCaptureDevicesDispatcher::MediaCaptureDevicesDispatcher() - : devices_enumerated_(false) {} +{} MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() {} -void MediaCaptureDevicesDispatcher::AudioCaptureDevicesChanged( - const MediaStreamDevices& devices) { +void MediaCaptureDevicesDispatcher::OnAudioCaptureDevicesChanged() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, - base::Bind(&MediaCaptureDevicesDispatcher::UpdateAudioDevicesOnUIThread, - this, devices)); + base::Bind( + &MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread, + base::Unretained(this))); } -void MediaCaptureDevicesDispatcher::VideoCaptureDevicesChanged( - const MediaStreamDevices& devices) { +void MediaCaptureDevicesDispatcher::OnVideoCaptureDevicesChanged() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, - base::Bind(&MediaCaptureDevicesDispatcher::UpdateVideoDevicesOnUIThread, - this, devices)); + base::Bind( + &MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread, + base::Unretained(this))); } void MediaCaptureDevicesDispatcher::AddObserver(Observer* observer) { @@ -50,41 +69,87 @@ void MediaCaptureDevicesDispatcher::RemoveObserver(Observer* observer) { const MediaStreamDevices& MediaCaptureDevicesDispatcher::GetAudioCaptureDevices() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - if (!devices_enumerated_) { - BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - base::Bind(&content::EnsureMonitorCaptureDevices)); - devices_enumerated_ = true; - } - return audio_devices_; + return MediaCaptureDevices::GetInstance()->GetAudioCaptureDevices(); } const MediaStreamDevices& MediaCaptureDevicesDispatcher::GetVideoCaptureDevices() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - if (!devices_enumerated_) { - BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - base::Bind(&content::EnsureMonitorCaptureDevices)); - devices_enumerated_ = true; - } - return video_devices_; + return MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices(); } -void MediaCaptureDevicesDispatcher::UpdateAudioDevicesOnUIThread( - const content::MediaStreamDevices& devices) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - devices_enumerated_ = true; - audio_devices_ = devices; +void MediaCaptureDevicesDispatcher::OnCreatingAudioStream( + int render_process_id, + int render_view_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind( + &MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread, + base::Unretained(this), render_process_id, render_view_id)); +} + +void MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread() { + MediaStreamDevices devices = GetAudioCaptureDevices(); + FOR_EACH_OBSERVER(Observer, observers_, + OnUpdateAudioDevices(devices)); +} + +void MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread() { + MediaStreamDevices devices = GetVideoCaptureDevices(); FOR_EACH_OBSERVER(Observer, observers_, - OnUpdateAudioDevices(audio_devices_)); + OnUpdateVideoDevices(devices)); } -void MediaCaptureDevicesDispatcher::UpdateVideoDevicesOnUIThread( - const content::MediaStreamDevices& devices){ +void MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread( + int render_process_id, + int render_view_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - devices_enumerated_ = true; - video_devices_ = devices; FOR_EACH_OBSERVER(Observer, observers_, - OnUpdateVideoDevices(video_devices_)); + OnCreatingAudioStream(render_process_id, render_view_id)); +} + +const content::MediaStreamDevice* +MediaCaptureDevicesDispatcher::GetRequestedAudioDevice( + const std::string& requested_audio_device_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + const content::MediaStreamDevices& audio_devices = GetAudioCaptureDevices(); + const content::MediaStreamDevice* const device = + FindDeviceWithId(audio_devices, requested_audio_device_id); + return device; +} + +const content::MediaStreamDevice* +MediaCaptureDevicesDispatcher::GetFirstAvailableAudioDevice() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + const content::MediaStreamDevices& audio_devices = GetAudioCaptureDevices(); + if (audio_devices.empty()) + return NULL; + return &(*audio_devices.begin()); +} + +const content::MediaStreamDevice* +MediaCaptureDevicesDispatcher::GetRequestedVideoDevice( + const std::string& requested_video_device_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + const content::MediaStreamDevices& video_devices = GetVideoCaptureDevices(); + const content::MediaStreamDevice* const device = + FindDeviceWithId(video_devices, requested_video_device_id); + return device; +} + +const content::MediaStreamDevice* +MediaCaptureDevicesDispatcher::GetFirstAvailableVideoDevice() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + const content::MediaStreamDevices& video_devices = GetVideoCaptureDevices(); + if (video_devices.empty()) + return NULL; + return &(*video_devices.begin()); +} + +void MediaCaptureDevicesDispatcher::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); } diff --git a/src/media/media_capture_devices_dispatcher.h b/src/media/media_capture_devices_dispatcher.h index 8191ee1f1d..9e834961c4 100644 --- a/src/media/media_capture_devices_dispatcher.h +++ b/src/media/media_capture_devices_dispatcher.h @@ -8,12 +8,16 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/observer_list.h" +#include "content/public/browser/media_observer.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "content/public/browser/web_contents_delegate.h" #include "content/public/common/media_stream_request.h" // This observer is owned by MediaInternals and deleted when MediaInternals // is deleted. -class MediaCaptureDevicesDispatcher - : public base::RefCountedThreadSafe { +class MediaCaptureDevicesDispatcher : public content::MediaObserver, + public content::NotificationObserver { public: class Observer { public: @@ -27,17 +31,45 @@ class MediaCaptureDevicesDispatcher virtual void OnUpdateVideoDevices( const content::MediaStreamDevices& devices) {} + // Handle an information update related to a media stream request. + virtual void OnRequestUpdate( + int render_process_id, + int render_view_id, + const content::MediaStreamDevice& device, + const content::MediaRequestState state) {} + + virtual void OnCreatingAudioStream(int render_process_id, + int render_view_id) {} + virtual ~Observer() {} }; MediaCaptureDevicesDispatcher(); + virtual ~MediaCaptureDevicesDispatcher(); - // Called on IO thread when one audio device is plugged in or unplugged. - void AudioCaptureDevicesChanged(const content::MediaStreamDevices& devices); - - // Called on IO thread when one video device is plugged in or unplugged. - void VideoCaptureDevicesChanged(const content::MediaStreamDevices& devices); - + void NotifyAudioDevicesChangedOnUIThread(); + void NotifyVideoDevicesChangedOnUIThread(); + // Overridden from content::MediaObserver: + virtual void OnAudioCaptureDevicesChanged() OVERRIDE; + virtual void OnVideoCaptureDevicesChanged() OVERRIDE; + virtual void OnCreatingAudioStream(int render_process_id, + int render_view_id) OVERRIDE; + + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + + virtual void OnAudioStreamPlaying( + int render_process_id, + int render_frame_id, + int stream_id, + const ReadPowerAndClipCallback& power_read_callback) OVERRIDE {} + virtual void OnAudioStreamStopped( + int render_process_id, + int render_frame_id, + int stream_id) OVERRIDE {} // Methods for observers. Called on UI thread. // Observers should add themselves on construction and remove themselves // on destruction. @@ -46,14 +78,36 @@ class MediaCaptureDevicesDispatcher const content::MediaStreamDevices& GetAudioCaptureDevices(); const content::MediaStreamDevices& GetVideoCaptureDevices(); + // Helpers for picking particular requested devices, identified by raw id. + // If the device requested is not available it will return NULL. + const content::MediaStreamDevice* + GetRequestedAudioDevice(const std::string& requested_audio_device_id); + const content::MediaStreamDevice* + GetRequestedVideoDevice(const std::string& requested_video_device_id); + + + virtual void OnMediaRequestStateChanged( + int render_process_id, + int render_frame_id, + int page_request_id, + const GURL& security_origin, + content::MediaStreamType stream_type, + content::MediaRequestState state) OVERRIDE {} + + // Returns the first available audio or video device, or NULL if no devices + // are available. + const content::MediaStreamDevice* GetFirstAvailableAudioDevice(); + const content::MediaStreamDevice* GetFirstAvailableVideoDevice(); + private: friend class base::RefCountedThreadSafe; - virtual ~MediaCaptureDevicesDispatcher(); // Called by the public Audio/VideoCaptureDevicesChanged() functions, // executed on UI thread. void UpdateAudioDevicesOnUIThread(const content::MediaStreamDevices& devices); void UpdateVideoDevicesOnUIThread(const content::MediaStreamDevices& devices); + void OnCreatingAudioStreamOnUIThread(int render_process_id, + int render_view_id); // A list of cached audio capture devices. content::MediaStreamDevices audio_devices_; diff --git a/src/media/media_internals.cc b/src/media/media_internals.cc index 754e10963e..22ddf30191 100644 --- a/src/media/media_internals.cc +++ b/src/media/media_internals.cc @@ -21,8 +21,8 @@ #include "content/nw/src/media/media_internals.h" #include "base/memory/scoped_ptr.h" -#include "base/string16.h" -#include "base/stringprintf.h" +#include "base/strings/string16.h" +#include "base/strings/stringprintf.h" #include "content/nw/src/media/media_capture_devices_dispatcher.h" #include "content/public/browser/browser_thread.h" #include "media/base/media_log.h" @@ -89,46 +89,36 @@ MediaInternals* MediaInternals::GetInstance() { MediaInternals::~MediaInternals() {} -void MediaInternals::OnCaptureDevicesOpened( - int render_process_id, - int render_view_id, - const content::MediaStreamDevices& devices) { -} - -void MediaInternals::OnCaptureDevicesClosed( - int render_process_id, - int render_view_id, - const content::MediaStreamDevices& devices) { -} - -void MediaInternals::OnAudioCaptureDevicesChanged( - const content::MediaStreamDevices& devices) { +void MediaInternals::OnAudioCaptureDevicesChanged() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - media_devices_dispatcher_->AudioCaptureDevicesChanged(devices); + media_devices_dispatcher_->OnAudioCaptureDevicesChanged(); } -void MediaInternals::OnVideoCaptureDevicesChanged( - const content::MediaStreamDevices& devices) { +void MediaInternals::OnVideoCaptureDevicesChanged() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - media_devices_dispatcher_->VideoCaptureDevicesChanged(devices); + media_devices_dispatcher_->OnVideoCaptureDevicesChanged(); } void MediaInternals::OnMediaRequestStateChanged( - int render_process_id, - int render_view_id, - const content::MediaStreamDevice& device, - content::MediaRequestState state) { + int render_process_id, + int render_frame_id, + int page_request_id, + const GURL& security_origin, + content::MediaStreamType stream_type, + content::MediaRequestState state) { } -void MediaInternals::OnAudioStreamPlayingChanged( - int render_process_id, int render_view_id, int stream_id, bool playing) { +void MediaInternals::OnCreatingAudioStream( + int render_process_id, + int render_view_id) { + media_devices_dispatcher_->OnCreatingAudioStream(render_process_id, render_view_id); } -scoped_refptr +MediaCaptureDevicesDispatcher* MediaInternals::GetMediaCaptureDevicesDispatcher() { return media_devices_dispatcher_; } MediaInternals::MediaInternals() - : media_devices_dispatcher_(new MediaCaptureDevicesDispatcher()) { + : media_devices_dispatcher_(new MediaCaptureDevicesDispatcher()) { } diff --git a/src/media/media_internals.h b/src/media/media_internals.h index cb037d0ce5..cef21cb575 100644 --- a/src/media/media_internals.h +++ b/src/media/media_internals.h @@ -42,29 +42,28 @@ class MediaInternals : public content::MediaObserver { static MediaInternals* GetInstance(); // Overridden from content::MediaObserver: - virtual void OnCaptureDevicesOpened( - int render_process_id, - int render_view_id, - const content::MediaStreamDevices& devices) OVERRIDE; - virtual void OnCaptureDevicesClosed( - int render_process_id, - int render_view_id, - const content::MediaStreamDevices& devices) OVERRIDE; - virtual void OnAudioCaptureDevicesChanged( - const content::MediaStreamDevices& devices) OVERRIDE; - virtual void OnVideoCaptureDevicesChanged( - const content::MediaStreamDevices& devices) OVERRIDE; + virtual void OnAudioCaptureDevicesChanged() override; + virtual void OnVideoCaptureDevicesChanged() override; virtual void OnMediaRequestStateChanged( int render_process_id, - int render_view_id, - const content::MediaStreamDevice& device, - content::MediaRequestState state) OVERRIDE; - virtual void OnAudioStreamPlayingChanged( + int render_frame_id, + int page_request_id, + const GURL& security_origin, + content::MediaStreamType stream_type, + content::MediaRequestState state) override; + + virtual void OnCreatingAudioStream(int render_process_id, + int render_view_id) override; + + virtual void OnAudioStreamPlaying( int render_process_id, - int render_view_id, + int render_frame_id, int stream_id, - bool playing) OVERRIDE; - + const ReadPowerAndClipCallback& power_read_callback) override {} + virtual void OnAudioStreamStopped( + int render_process_id, + int render_frame_id, + int stream_id) override {} // Methods for observers. // Observers should add themselves on construction and remove themselves // on destruction. @@ -73,8 +72,7 @@ class MediaInternals : public content::MediaObserver { void SendEverything(); scoped_refptr GetMediaStreamCaptureIndicator(); - scoped_refptr - GetMediaCaptureDevicesDispatcher(); + MediaCaptureDevicesDispatcher* GetMediaCaptureDevicesDispatcher(); private: friend class MediaInternalsTest; @@ -86,7 +84,7 @@ class MediaInternals : public content::MediaObserver { // (host, stream_id) is a unique id for the audio stream. // |host| will never be dereferenced. void UpdateAudioStream(void* host, int stream_id, - const std::string& property, Value* value); + const std::string& property, base::Value* value); // Removes |item| from |data_|. void DeleteItem(const std::string& item); @@ -94,14 +92,14 @@ class MediaInternals : public content::MediaObserver { // Sets data_.id.property = value and notifies attached UIs using update_fn. // id may be any depth, e.g. "video.decoders.1.2.3" void UpdateItem(const std::string& update_fn, const std::string& id, - const std::string& property, Value* value); + const std::string& property, base::Value* value); // Calls javascript |function|(|value|) on each attached UI. - void SendUpdate(const std::string& function, Value* value); + void SendUpdate(const std::string& function, base::Value* value); - DictionaryValue data_; + base::DictionaryValue data_; ObserverList observers_; - scoped_refptr media_devices_dispatcher_; + MediaCaptureDevicesDispatcher* media_devices_dispatcher_; DISALLOW_COPY_AND_ASSIGN(MediaInternals); }; diff --git a/src/media/media_stream_devices_controller.cc b/src/media/media_stream_devices_controller.cc index 989ca8822b..1522f034df 100644 --- a/src/media/media_stream_devices_controller.cc +++ b/src/media/media_stream_devices_controller.cc @@ -4,12 +4,17 @@ #include "content/nw/src/media/media_stream_devices_controller.h" +#include "base/command_line.h" #include "base/values.h" +#include "chrome/common/chrome_switches.h" #include "content/nw/src/media/media_capture_devices_dispatcher.h" #include "content/nw/src/media/media_internals.h" #include "content/public/browser/browser_thread.h" +#include "content/public/browser/desktop_media_id.h" #include "content/public/common/media_stream_request.h" +#include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h" + using content::BrowserThread; namespace { @@ -25,8 +30,8 @@ bool HasAnyAvailableDevice() { return !audio_devices.empty() || !video_devices.empty(); }; -const char kAudioKey[] = "audio"; -const char kVideoKey[] = "video"; +//const char kAudioKey[] = "audio"; +//const char kVideoKey[] = "video"; } // namespace @@ -36,7 +41,7 @@ MediaStreamDevicesController::MediaStreamDevicesController( : request_(request), callback_(callback), - has_audio_(content::IsAudioMediaType(request.audio_type) && + has_audio_(content::IsAudioInputMediaType(request.audio_type) && !IsAudioDeviceBlockedByPolicy()), has_video_(content::IsVideoMediaType(request.video_type) && !IsVideoDeviceBlockedByPolicy()) { @@ -66,7 +71,8 @@ bool MediaStreamDevicesController::DismissInfoBarAndTakeActionOnSettings() { } if (request_.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE || - request_.video_type == content::MEDIA_TAB_VIDEO_CAPTURE) { + request_.video_type == content::MEDIA_TAB_VIDEO_CAPTURE || + request_.video_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE) { HandleTapMediaRequest(); return true; } @@ -114,16 +120,83 @@ const std::string& MediaStreamDevicesController::GetSecurityOriginSpec() const { void MediaStreamDevicesController::Accept(bool update_content_setting) { // Get the default devices for the request. content::MediaStreamDevices devices; - media::GetDefaultDevicesForProfile( - has_audio_, - has_video_, - &devices); + MediaCaptureDevicesDispatcher* dispatcher = + MediaInternals::GetInstance()->GetMediaCaptureDevicesDispatcher(); + switch (request_.request_type) { + case content::MEDIA_OPEN_DEVICE: { + const content::MediaStreamDevice* device = NULL; + // For open device request pick the desired device or fall back to the + // first available of the given type. + if (request_.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE) { + device = dispatcher-> + GetRequestedAudioDevice(request_.requested_audio_device_id); + // TODO(wjia): Confirm this is the intended behavior. + if (!device) { + device = dispatcher->GetFirstAvailableAudioDevice(); + } + } else if (request_.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE) { + // Pepper API opens only one device at a time. + device = dispatcher->GetRequestedVideoDevice(request_.requested_video_device_id); + // TODO(wjia): Confirm this is the intended behavior. + if (!device) { + device = dispatcher->GetFirstAvailableVideoDevice(); + } + } + if (device) + devices.push_back(*device); + break; + } case content::MEDIA_GENERATE_STREAM: { + bool needs_audio_device = has_audio_; + bool needs_video_device = has_video_; - callback_.Run(devices); + // Get the exact audio or video device if an id is specified. + if (!request_.requested_audio_device_id.empty()) { + const content::MediaStreamDevice* audio_device = + dispatcher->GetRequestedAudioDevice(request_.requested_audio_device_id); + if (audio_device) { + devices.push_back(*audio_device); + needs_audio_device = false; + } + } + if (!request_.requested_video_device_id.empty()) { + const content::MediaStreamDevice* video_device = + dispatcher->GetRequestedVideoDevice(request_.requested_video_device_id); + if (video_device) { + devices.push_back(*video_device); + needs_video_device = false; + } + } + + // If either or both audio and video devices were requested but not + // specified by id, get the default devices. + if (needs_audio_device || needs_video_device) { + media::GetDefaultDevicesForProfile( + needs_audio_device, + needs_video_device, + &devices); + } + break; + } case content::MEDIA_DEVICE_ACCESS: + // Get the default devices for the request. + media::GetDefaultDevicesForProfile( + has_audio_, + has_video_, + &devices); + break; + case content::MEDIA_ENUMERATE_DEVICES: + // Do nothing. + NOTREACHED(); + break; + } + + callback_.Run(devices, + devices.empty() ? + content::MEDIA_DEVICE_NO_HARDWARE : content::MEDIA_DEVICE_OK, + scoped_ptr()); } void MediaStreamDevicesController::Deny(bool update_content_setting) { - callback_.Run(content::MediaStreamDevices()); + callback_.Run(content::MediaStreamDevices(), content::MEDIA_DEVICE_NO_HARDWARE, scoped_ptr()); } bool MediaStreamDevicesController::IsAudioDeviceBlockedByPolicy() const { @@ -160,8 +233,33 @@ void MediaStreamDevicesController::HandleTapMediaRequest() { devices.push_back(content::MediaStreamDevice( content::MEDIA_TAB_AUDIO_CAPTURE, "", "")); } + if (request_.video_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE) { + const bool screen_capture_enabled = + CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableUserMediaScreenCapturing); + + if (screen_capture_enabled) { + content::DesktopMediaID media_id; + // If the device id wasn't specified then this is a screen capture request + // (i.e. chooseDesktopMedia() API wasn't used to generate device id). + if (request_.requested_video_device_id.empty()) { + media_id = + content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, + webrtc::kFullDesktopScreenId); + } else { + media_id = + content::DesktopMediaID::Parse(request_.requested_video_device_id); + } + + devices.push_back(content::MediaStreamDevice( + content::MEDIA_DESKTOP_VIDEO_CAPTURE, media_id.ToString(), "Screen")); + } + } - callback_.Run(devices); + callback_.Run(devices, + devices.empty() ? + content::MEDIA_DEVICE_NO_HARDWARE : content::MEDIA_DEVICE_OK, + scoped_ptr()); } bool MediaStreamDevicesController::IsSchemeSecure() const { diff --git a/src/net/app_protocol_handler.cc b/src/net/app_protocol_handler.cc new file mode 100644 index 0000000000..b3060f2b4d --- /dev/null +++ b/src/net/app_protocol_handler.cc @@ -0,0 +1,185 @@ +// 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 "content/nw/src/net/app_protocol_handler.h" + +#include "base/base64.h" +#include "base/files/file_util.h" +#include "base/files/file_path.h" +#include "base/format_macros.h" +#include "base/logging.h" +#include "base/sha1.h" +#include "base/strings/stringprintf.h" +#include "base/threading/sequenced_worker_pool.h" +#include "content/public/browser/browser_thread.h" +#include "net/base/filename_util.h" +#include "net/base/mime_util.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_response_info.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_error_job.h" +#include "net/url_request/url_request_file_dir_job.h" +#include "net/url_request/url_request_file_job.h" + +namespace net { + +namespace { + +net::HttpResponseHeaders* BuildHttpHeaders( + const std::string& content_security_policy, bool send_cors_header, + const base::Time& last_modified_time) { + std::string raw_headers; + raw_headers.append("HTTP/1.1 200 OK"); + if (!content_security_policy.empty()) { + raw_headers.append(1, '\0'); + raw_headers.append("Content-Security-Policy: "); + raw_headers.append(content_security_policy); + } + + if (send_cors_header) { + raw_headers.append(1, '\0'); + raw_headers.append("Access-Control-Allow-Origin: *"); + } + + if (!last_modified_time.is_null()) { + // Hash the time and make an etag to avoid exposing the exact + // user installation time of the extension. + std::string hash = base::StringPrintf("%" PRId64, + last_modified_time.ToInternalValue()); + hash = base::SHA1HashString(hash); + std::string etag; + base::Base64Encode(hash, &etag); + raw_headers.append(1, '\0'); + raw_headers.append("ETag: \""); + raw_headers.append(etag); + raw_headers.append("\""); + // Also force revalidation. + raw_headers.append(1, '\0'); + raw_headers.append("cache-control: no-cache"); + } + + raw_headers.append(2, '\0'); + return new net::HttpResponseHeaders(raw_headers); +} + +base::Time GetFileLastModifiedTime(const base::FilePath& filename) { + if (base::PathExists(filename)) { + base::File::Info info; + if (base::GetFileInfo(filename, &info)) + return info.last_modified; + } + return base::Time(); +} + +void ReadResourceFilePathAndLastModifiedTime( + const base::FilePath& file_path, + base::Time* last_modified_time) { + *last_modified_time = GetFileLastModifiedTime(file_path); +} + +class URLRequestNWAppJob : public net::URLRequestFileJob { + public: + URLRequestNWAppJob(net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const base::FilePath& file_path, + const std::string& content_security_policy, + bool send_cors_header) + : net::URLRequestFileJob( + request, network_delegate, base::FilePath(), + content::BrowserThread::GetBlockingPool()-> + GetTaskRunnerWithShutdownBehavior( + base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)), + content_security_policy_(content_security_policy), + send_cors_header_(send_cors_header), + weak_factory_(this) { + file_path_ = file_path; + // response_info_.headers = BuildHttpHeaders(content_security_policy, + // send_cors_header, + // base::Time()); + } + + void GetResponseInfo(net::HttpResponseInfo* info) override { + *info = response_info_; + } + + void Start() override { + base::Time* last_modified_time = new base::Time(); + bool posted = content::BrowserThread::PostBlockingPoolTaskAndReply( + FROM_HERE, + base::Bind(&ReadResourceFilePathAndLastModifiedTime, + file_path_, + base::Unretained(last_modified_time)), + base::Bind(&URLRequestNWAppJob::OnFilePathAndLastModifiedTimeRead, + weak_factory_.GetWeakPtr(), + base::Owned(last_modified_time))); + DCHECK(posted); + } + + private: + ~URLRequestNWAppJob() override {} + + void OnFilePathAndLastModifiedTimeRead(base::Time* last_modified_time) { + response_info_.headers = BuildHttpHeaders( + content_security_policy_, + send_cors_header_, + *last_modified_time); + URLRequestFileJob::Start(); + } + + net::HttpResponseInfo response_info_; + std::string content_security_policy_; + bool send_cors_header_; + base::WeakPtrFactory weak_factory_; +}; + +} // namespace + +AppProtocolHandler::AppProtocolHandler(const base::FilePath& root) + :root_path_(root) +{ +} + +URLRequestJob* AppProtocolHandler::MaybeCreateJob( + URLRequest* request, NetworkDelegate* network_delegate) const { + base::FilePath file_path; + GURL url(request->url()); + url::Replacements replacements; + replacements.SetScheme("file", url::Component(0, 4)); + replacements.ClearHost(); + url = url.ReplaceComponents(replacements); + + const bool is_file = net::FileURLToFilePath(url, &file_path); + + file_path = root_path_.Append(file_path); + // Check file access permissions. + if (!network_delegate || + !network_delegate->CanAccessFile(*request, file_path)) { + return new URLRequestErrorJob(request, network_delegate, ERR_ACCESS_DENIED); + } + + // We need to decide whether to create URLRequestFileJob for file access or + // URLRequestFileDirJob for directory access. To avoid accessing the + // filesystem, we only look at the path string here. + // The code in the URLRequestFileJob::Start() method discovers that a path, + // which doesn't end with a slash, should really be treated as a directory, + // and it then redirects to the URLRequestFileDirJob. + if (is_file && + file_path.EndsWithSeparator() && + file_path.IsAbsolute()) { + return new URLRequestFileDirJob(request, network_delegate, file_path); + } + + // Use a regular file request job for all non-directories (including invalid + // file names). + return new URLRequestNWAppJob(request, network_delegate, file_path, "", false); +} + +bool AppProtocolHandler::IsSafeRedirectTarget(const GURL& location) const { + return false; +} + +} // namespace net diff --git a/src/net/app_protocol_handler.h b/src/net/app_protocol_handler.h new file mode 100644 index 0000000000..76b7ed9006 --- /dev/null +++ b/src/net/app_protocol_handler.h @@ -0,0 +1,37 @@ +// 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 NW_APP_PROTOCOL_HANDLER_H_ +#define NW_APP_PROTOCOL_HANDLER_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "net/url_request/url_request_job_factory.h" + +class GURL; + +namespace net { + +class NetworkDelegate; +class URLRequestJob; + +// Implements a ProtocolHandler for File jobs. If |network_delegate_| is NULL, +// then all file requests will fail with ERR_ACCESS_DENIED. +class AppProtocolHandler : + public URLRequestJobFactory::ProtocolHandler { + public: + AppProtocolHandler(const base::FilePath& root); + URLRequestJob* MaybeCreateJob( + URLRequest* request, NetworkDelegate* network_delegate) const override; + bool IsSafeRedirectTarget(const GURL& location) const override; + + private: + base::FilePath root_path_; + DISALLOW_COPY_AND_ASSIGN(AppProtocolHandler); +}; + +} // namespace net + +#endif diff --git a/src/net/clear_on_exit_policy.cc b/src/net/clear_on_exit_policy.cc index 748d07ea6c..0457b4ab22 100644 --- a/src/net/clear_on_exit_policy.cc +++ b/src/net/clear_on_exit_policy.cc @@ -5,8 +5,8 @@ #include "content/nw/src/net/clear_on_exit_policy.h" #include "content/public/common/url_constants.h" -#include "googleurl/src/gurl.h" -#include "webkit/quota/special_storage_policy.h" +#include "url/gurl.h" +#include "webkit/browser/quota/special_storage_policy.h" ClearOnExitPolicy::ClearOnExitPolicy( quota::SpecialStoragePolicy* special_storage_policy) @@ -27,9 +27,9 @@ bool ClearOnExitPolicy::ShouldClearOriginOnExit(const std::string& domain, return false; std::string scheme = - scheme_is_secure ? chrome::kHttpsScheme : chrome::kHttpScheme; + scheme_is_secure ? url::kHttpsScheme : url::kHttpScheme; std::string host = domain[0] == '.' ? domain.substr(1) : domain; - GURL url(scheme + content::kStandardSchemeSeparator + host); + GURL url(scheme + url::kStandardSchemeSeparator + host); return special_storage_policy_->IsStorageSessionOnly(url); } diff --git a/src/net/resource_request_job.cc b/src/net/resource_request_job.cc index f0f3eef8d7..20ce91a4c6 100644 --- a/src/net/resource_request_job.cc +++ b/src/net/resource_request_job.cc @@ -24,9 +24,9 @@ #include "base/compiler_specific.h" #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" -#include "base/message_loop.h" +#include "base/message_loop/message_loop.h" #include "base/logging.h" -#include "googleurl/src/gurl.h" +#include "url/gurl.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/http/http_response_headers.h" @@ -35,6 +35,8 @@ #include "grit/nw_resources.h" #include "ui/base/resource/resource_bundle.h" +using base::MessageLoop; + namespace nw { ResourceRequestJob::ResourceRequestJob( @@ -47,7 +49,7 @@ ResourceRequestJob::ResourceRequestJob( pending_buf_size_(0), mime_type_(mime_type), resource_id_(resource_id), - ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { + weak_factory_(this) { } // static @@ -88,7 +90,7 @@ void ResourceRequestJob::DataAvailable(base::RefCountedMemory* bytes) { int bytes_read; if (pending_buf_.get()) { CHECK(pending_buf_->data()); - CompleteRead(pending_buf_, pending_buf_size_, &bytes_read); + CompleteRead(pending_buf_.get(), pending_buf_size_, &bytes_read); pending_buf_ = NULL; NotifyReadComplete(bytes_read); } diff --git a/src/net/resource_request_job.h b/src/net/resource_request_job.h index 5726a52e00..200265fcee 100644 --- a/src/net/resource_request_job.h +++ b/src/net/resource_request_job.h @@ -39,11 +39,11 @@ class ResourceRequestJob : public net::URLRequestJob { int resource_id); // net::URLRequestJob methods. - virtual void Start() OVERRIDE; - virtual bool ReadRawData(net::IOBuffer* dest, int dest_size, int* bytes_read) - OVERRIDE; - virtual bool GetMimeType(std::string* mime_type) const OVERRIDE; - virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE; + void Start() override; + bool ReadRawData(net::IOBuffer* dest, int dest_size, int* bytes_read) + override; + bool GetMimeType(std::string* mime_type) const override; + void GetResponseInfo(net::HttpResponseInfo* info) override; static ResourceRequestJob* Factory(net::URLRequest* request, net::NetworkDelegate* network_delegate); @@ -53,7 +53,7 @@ class ResourceRequestJob : public net::URLRequestJob { void DataAvailable(base::RefCountedMemory* bytes); private: - virtual ~ResourceRequestJob(); + ~ResourceRequestJob() override; // Helper for Start(), to let us start asynchronously. // (This pattern is shared by most net::URLRequestJob implementations.) diff --git a/src/net/shell_network_delegate.cc b/src/net/shell_network_delegate.cc index 3485e276c1..bb80384f43 100644 --- a/src/net/shell_network_delegate.cc +++ b/src/net/shell_network_delegate.cc @@ -1,67 +1,82 @@ -// Copyright (c) 2012 Intel Corp -// Copyright (c) 2012 The Chromium Authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co -// pies of the Software, and to permit persons to whom the Software is furnished -// to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in al -// l copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM -// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES -// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH -// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. #include "content/nw/src/net/shell_network_delegate.h" +#include "extensions/browser/info_map.h" +#include "extensions/browser/api/web_request/web_request_api.h" #include "net/base/net_errors.h" +#include "net/base/static_cookie_policy.h" +#include "net/url_request/url_request.h" namespace content { -ShellNetworkDelegate::ShellNetworkDelegate() { +namespace { +bool g_accept_all_cookies = true; +} + +ShellNetworkDelegate::ShellNetworkDelegate( + void* browser_context, extensions::InfoMap* extension_info_map) { + browser_context_ = browser_context; + extension_info_map_ = extension_info_map; } ShellNetworkDelegate::~ShellNetworkDelegate() { } +void ShellNetworkDelegate::SetAcceptAllCookies(bool accept) { + g_accept_all_cookies = accept; +} + int ShellNetworkDelegate::OnBeforeURLRequest( net::URLRequest* request, const net::CompletionCallback& callback, GURL* new_url) { - return net::OK; + return ExtensionWebRequestEventRouter::GetInstance()->OnBeforeRequest( + browser_context_, extension_info_map_.get(), request, callback, new_url); } int ShellNetworkDelegate::OnBeforeSendHeaders( net::URLRequest* request, const net::CompletionCallback& callback, net::HttpRequestHeaders* headers) { - return net::OK; + return ExtensionWebRequestEventRouter::GetInstance()->OnBeforeSendHeaders( + browser_context_, extension_info_map_.get(), request, callback, headers); } void ShellNetworkDelegate::OnSendHeaders( net::URLRequest* request, const net::HttpRequestHeaders& headers) { + ExtensionWebRequestEventRouter::GetInstance()->OnSendHeaders( + browser_context_, extension_info_map_.get(), request, headers); } int ShellNetworkDelegate::OnHeadersReceived( net::URLRequest* request, const net::CompletionCallback& callback, const net::HttpResponseHeaders* original_response_headers, - scoped_refptr* override_response_headers) { - return net::OK; + scoped_refptr* override_response_headers, + GURL* allowed_unsafe_redirect_url) { + return ExtensionWebRequestEventRouter::GetInstance()->OnHeadersReceived( + browser_context_, + extension_info_map_.get(), + request, + callback, + original_response_headers, + override_response_headers, + allowed_unsafe_redirect_url); } void ShellNetworkDelegate::OnBeforeRedirect(net::URLRequest* request, const GURL& new_location) { + ExtensionWebRequestEventRouter::GetInstance()->OnBeforeRedirect( + browser_context_, extension_info_map_.get(), request, new_location); } void ShellNetworkDelegate::OnResponseStarted(net::URLRequest* request) { + ExtensionWebRequestEventRouter::GetInstance()->OnResponseStarted( + browser_context_, extension_info_map_.get(), request); } void ShellNetworkDelegate::OnRawBytesRead(const net::URLRequest& request, @@ -69,13 +84,34 @@ void ShellNetworkDelegate::OnRawBytesRead(const net::URLRequest& request, } void ShellNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { + if (request->status().status() == net::URLRequestStatus::SUCCESS) { + bool is_redirect = request->response_headers() && + net::HttpResponseHeaders::IsRedirectResponseCode( + request->response_headers()->response_code()); + if (!is_redirect) { + ExtensionWebRequestEventRouter::GetInstance()->OnCompleted( + browser_context_, extension_info_map_.get(), request); + } + return; + } + + if (request->status().status() == net::URLRequestStatus::FAILED || + request->status().status() == net::URLRequestStatus::CANCELED) { + ExtensionWebRequestEventRouter::GetInstance()->OnErrorOccurred( + browser_context_, extension_info_map_.get(), request, started); + return; + } + + NOTREACHED(); } void ShellNetworkDelegate::OnURLRequestDestroyed(net::URLRequest* request) { + ExtensionWebRequestEventRouter::GetInstance()->OnURLRequestDestroyed( + browser_context_, request); } void ShellNetworkDelegate::OnPACScriptError(int line_number, - const string16& error) { + const base::string16& error) { } ShellNetworkDelegate::AuthRequiredResponse ShellNetworkDelegate::OnAuthRequired( @@ -83,18 +119,32 @@ ShellNetworkDelegate::AuthRequiredResponse ShellNetworkDelegate::OnAuthRequired( const net::AuthChallengeInfo& auth_info, const AuthCallback& callback, net::AuthCredentials* credentials) { - return AUTH_REQUIRED_RESPONSE_NO_ACTION; + return ExtensionWebRequestEventRouter::GetInstance()->OnAuthRequired( + browser_context_, extension_info_map_.get(), request, auth_info, callback, + credentials); } bool ShellNetworkDelegate::OnCanGetCookies(const net::URLRequest& request, const net::CookieList& cookie_list) { - return true; + net::StaticCookiePolicy::Type policy_type = g_accept_all_cookies ? + net::StaticCookiePolicy::ALLOW_ALL_COOKIES : + net::StaticCookiePolicy::BLOCK_ALL_THIRD_PARTY_COOKIES; + net::StaticCookiePolicy policy(policy_type); + int rv = policy.CanGetCookies( + request.url(), request.first_party_for_cookies()); + return rv == net::OK; } bool ShellNetworkDelegate::OnCanSetCookie(const net::URLRequest& request, const std::string& cookie_line, net::CookieOptions* options) { - return true; + net::StaticCookiePolicy::Type policy_type = g_accept_all_cookies ? + net::StaticCookiePolicy::ALLOW_ALL_COOKIES : + net::StaticCookiePolicy::BLOCK_ALL_THIRD_PARTY_COOKIES; + net::StaticCookiePolicy policy(policy_type); + int rv = policy.CanSetCookie( + request.url(), request.first_party_for_cookies()); + return rv == net::OK; } bool ShellNetworkDelegate::OnCanAccessFile(const net::URLRequest& request, @@ -107,15 +157,4 @@ bool ShellNetworkDelegate::OnCanThrottleRequest( return false; } -int ShellNetworkDelegate::OnBeforeSocketStreamConnect( - net::SocketStream* socket, - const net::CompletionCallback& callback) { - return net::OK; -} - -void ShellNetworkDelegate::OnRequestWaitStateChange( - const net::URLRequest& request, - RequestWaitState waiting) { -} - } // namespace content diff --git a/src/net/shell_network_delegate.h b/src/net/shell_network_delegate.h index 14c0bf08b7..61a551d5a0 100644 --- a/src/net/shell_network_delegate.h +++ b/src/net/shell_network_delegate.h @@ -1,86 +1,72 @@ -// Copyright (c) 2012 Intel Corp -// Copyright (c) 2012 The Chromium Authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co -// pies of the Software, and to permit persons to whom the Software is furnished -// to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in al -// l copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM -// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES -// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH -// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. -#ifndef CONTENT_NW_SRC_NET_SHELL_NETWORK_DELEGATE_H_ -#define CONTENT_NW_SRC_NET_SHELL_NETWORK_DELEGATE_H_ +#ifndef CONTENT_SHELL_BROWSER_SHELL_NETWORK_DELEGATE_H_ +#define CONTENT_SHELL_BROWSER_SHELL_NETWORK_DELEGATE_H_ #include "base/basictypes.h" #include "base/compiler_specific.h" -#include "base/files/file_path.h" -#include "net/base/network_delegate.h" +#include "extensions/browser/info_map.h" +#include "net/base/network_delegate_impl.h" +namespace extensions { + +class InfoMap; + +} namespace content { -class ShellNetworkDelegate : public net::NetworkDelegate { +class ShellNetworkDelegate : public net::NetworkDelegateImpl { public: - ShellNetworkDelegate(); - virtual ~ShellNetworkDelegate(); + ShellNetworkDelegate(void* browser_context, extensions::InfoMap* extension_info_map); + ~ShellNetworkDelegate() override; + + static void SetAcceptAllCookies(bool accept); private: // net::NetworkDelegate implementation. - virtual int OnBeforeURLRequest(net::URLRequest* request, - const net::CompletionCallback& callback, - GURL* new_url) OVERRIDE; - virtual int OnBeforeSendHeaders(net::URLRequest* request, - const net::CompletionCallback& callback, - net::HttpRequestHeaders* headers) OVERRIDE; - virtual void OnSendHeaders(net::URLRequest* request, - const net::HttpRequestHeaders& headers) OVERRIDE; - virtual int OnHeadersReceived( + int OnBeforeURLRequest(net::URLRequest* request, + const net::CompletionCallback& callback, + GURL* new_url) override; + int OnBeforeSendHeaders(net::URLRequest* request, + const net::CompletionCallback& callback, + net::HttpRequestHeaders* headers) override; + void OnSendHeaders(net::URLRequest* request, + const net::HttpRequestHeaders& headers) override; + int OnHeadersReceived( net::URLRequest* request, const net::CompletionCallback& callback, const net::HttpResponseHeaders* original_response_headers, - scoped_refptr* - override_response_headers) OVERRIDE; - virtual void OnBeforeRedirect(net::URLRequest* request, - const GURL& new_location) OVERRIDE; - virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE; - virtual void OnRawBytesRead(const net::URLRequest& request, - int bytes_read) OVERRIDE; - virtual void OnCompleted(net::URLRequest* request, bool started) OVERRIDE; - virtual void OnURLRequestDestroyed(net::URLRequest* request) OVERRIDE; - virtual void OnPACScriptError(int line_number, - const string16& error) OVERRIDE; - virtual AuthRequiredResponse OnAuthRequired( + scoped_refptr* override_response_headers, + GURL* allowed_unsafe_redirect_url) override; + void OnBeforeRedirect(net::URLRequest* request, + const GURL& new_location) override; + void OnResponseStarted(net::URLRequest* request) override; + void OnRawBytesRead(const net::URLRequest& request, int bytes_read) override; + void OnCompleted(net::URLRequest* request, bool started) override; + void OnURLRequestDestroyed(net::URLRequest* request) override; + void OnPACScriptError(int line_number, const base::string16& error) override; + AuthRequiredResponse OnAuthRequired( net::URLRequest* request, const net::AuthChallengeInfo& auth_info, const AuthCallback& callback, - net::AuthCredentials* credentials) OVERRIDE; - virtual bool OnCanGetCookies(const net::URLRequest& request, - const net::CookieList& cookie_list) OVERRIDE; - virtual bool OnCanSetCookie(const net::URLRequest& request, - const std::string& cookie_line, - net::CookieOptions* options) OVERRIDE; - virtual bool OnCanAccessFile(const net::URLRequest& request, - const base::FilePath& path) const OVERRIDE; - virtual bool OnCanThrottleRequest( - const net::URLRequest& request) const OVERRIDE; - virtual int OnBeforeSocketStreamConnect( - net::SocketStream* stream, - const net::CompletionCallback& callback) OVERRIDE; - virtual void OnRequestWaitStateChange(const net::URLRequest& request, - RequestWaitState state) OVERRIDE; + net::AuthCredentials* credentials) override; + bool OnCanGetCookies(const net::URLRequest& request, + const net::CookieList& cookie_list) override; + bool OnCanSetCookie(const net::URLRequest& request, + const std::string& cookie_line, + net::CookieOptions* options) override; + bool OnCanAccessFile(const net::URLRequest& request, + const base::FilePath& path) const override; + bool OnCanThrottleRequest(const net::URLRequest& request) const override; + + void* browser_context_; + scoped_refptr extension_info_map_; DISALLOW_COPY_AND_ASSIGN(ShellNetworkDelegate); }; } // namespace content -#endif // CONTENT_NW_SRC_NET_SHELL_NETWORK_DELEGATE_H_ +#endif // CONTENT_SHELL_BROWSER_SHELL_NETWORK_DELEGATE_H_ diff --git a/src/net/shell_url_request_context_getter.cc b/src/net/shell_url_request_context_getter.cc index 6dfac58a04..9c9ea85eb4 100644 --- a/src/net/shell_url_request_context_getter.cc +++ b/src/net/shell_url_request_context_getter.cc @@ -21,22 +21,30 @@ #include "content/nw/src/net/shell_url_request_context_getter.h" #include "base/logging.h" -#include "base/string_split.h" -#include "base/string_util.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/threading/sequenced_worker_pool.h" #include "base/threading/worker_pool.h" +#include "chrome/browser/chrome_notification_types.h" +#include "chrome/browser/net/chrome_cookie_notification_details.h" #include "content/public/browser/browser_thread.h" +#include "content/public/browser/notification_service.h" #include "content/public/common/url_constants.h" #include "content/nw/src/net/shell_network_delegate.h" -#include "content/nw/src/net/sqlite_persistent_cookie_store.h" +#include "content/public/browser/cookie_store_factory.h" +#include "content/nw/src/net/app_protocol_handler.h" #include "content/nw/src/nw_protocol_handler.h" #include "content/nw/src/nw_shell.h" -#include "net/base/cert_verifier.h" -#include "net/base/default_server_bound_cert_store.h" -#include "net/base/host_resolver.h" -#include "net/base/mapped_host_resolver.h" -#include "net/base/server_bound_cert_service.h" -#include "net/base/ssl_config_service_defaults.h" +#include "content/nw/src/shell_content_browser_client.h" +#include "extensions/browser/info_map.h" +#include "net/cert/cert_verifier.h" +#include "net/cert/cert_verify_proc.h" +#include "net/cert/multi_threaded_cert_verifier.h" +#include "net/dns/host_resolver.h" +#include "net/dns/mapped_host_resolver.h" +#include "net/ssl/ssl_config_service_defaults.h" #include "net/cookies/cookie_monster.h" +#include "net/http/http_auth_filter.h" #include "net/http/http_auth_handler_factory.h" #include "net/http/http_cache.h" #include "net/http/http_network_session.h" @@ -46,48 +54,116 @@ #include "net/proxy/proxy_script_fetcher_impl.h" #include "net/proxy/proxy_service.h" #include "net/proxy/proxy_service_v8.h" -#include "net/url_request/protocol_intercept_job_factory.h" +#include "net/ssl/channel_id_service.h" +#include "net/ssl/default_channel_id_store.h" +#include "net/url_request/file_protocol_handler.h" #include "net/url_request/static_http_user_agent_settings.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_storage.h" +#include "net/url_request/url_request_intercepting_job_factory.h" #include "net/url_request/url_request_job_factory_impl.h" +#include "ui/base/l10n/l10n_util.h" + +using base::MessageLoop; namespace content { +namespace { + +void InstallProtocolHandlers(net::URLRequestJobFactoryImpl* job_factory, + ProtocolHandlerMap* protocol_handlers) { + for (ProtocolHandlerMap::iterator it = + protocol_handlers->begin(); + it != protocol_handlers->end(); + ++it) { + bool set_protocol = job_factory->SetProtocolHandler( + it->first, it->second.release()); + DCHECK(set_protocol); + } + protocol_handlers->clear(); +} + +// ---------------------------------------------------------------------------- +// CookieMonster::Delegate implementation +// ---------------------------------------------------------------------------- +class NWCookieMonsterDelegate : public net::CookieMonster::Delegate { + public: + explicit NWCookieMonsterDelegate(ShellBrowserContext* browser_context) + : browser_context_(browser_context) { + } + + // net::CookieMonster::Delegate implementation. + void OnCookieChanged( + const net::CanonicalCookie& cookie, + bool removed, + net::CookieMonster::Delegate::ChangeCause cause) override { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&NWCookieMonsterDelegate::OnCookieChangedAsyncHelper, + this, cookie, removed, cause)); + } + void OnLoaded() override { + } + + private: + ~NWCookieMonsterDelegate() final {} + + void OnCookieChangedAsyncHelper( + const net::CanonicalCookie& cookie, + bool removed, + net::CookieMonster::Delegate::ChangeCause cause) { + ChromeCookieDetails cookie_details(&cookie, removed, cause); + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_COOKIE_CHANGED, + content::Source(browser_context_), + content::Details(&cookie_details)); + } + + ShellBrowserContext* browser_context_; +}; + +} // namespace + + ShellURLRequestContextGetter::ShellURLRequestContextGetter( bool ignore_certificate_errors, - const FilePath& base_path, + const FilePath& data_path, + const FilePath& root_path, MessageLoop* io_loop, MessageLoop* file_loop, - scoped_ptr - blob_protocol_handler, - scoped_ptr - file_system_protocol_handler, - scoped_ptr - developer_protocol_handler, - scoped_ptr - chrome_protocol_handler, - scoped_ptr - chrome_devtools_protocol_handler) + ProtocolHandlerMap* protocol_handlers, + ShellBrowserContext* browser_context, + URLRequestInterceptorScopedVector request_interceptors, + const std::string& auth_schemes, + const std::string& auth_server_whitelist, + const std::string& auth_delegate_whitelist, + const std::string& gssapi_library_name, + extensions::InfoMap* extension_info_map) : ignore_certificate_errors_(ignore_certificate_errors), - base_path_(base_path), + data_path_(data_path), + root_path_(root_path), + auth_schemes_(auth_schemes), + negotiate_disable_cname_lookup_(false), + negotiate_enable_port_(false), + auth_server_whitelist_(auth_server_whitelist), + auth_delegate_whitelist_(auth_delegate_whitelist), + gssapi_library_name_(gssapi_library_name), io_loop_(io_loop), file_loop_(file_loop), - blob_protocol_handler_(blob_protocol_handler.Pass()), - file_system_protocol_handler_(file_system_protocol_handler.Pass()), - developer_protocol_handler_(developer_protocol_handler.Pass()), - chrome_protocol_handler_(chrome_protocol_handler.Pass()), - chrome_devtools_protocol_handler_( - chrome_devtools_protocol_handler.Pass()) { + browser_context_(browser_context), + request_interceptors_(request_interceptors.Pass()), + extension_info_map_(extension_info_map){ // Must first be created on the UI thread. DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + std::swap(protocol_handlers_, *protocol_handlers); + // We must create the proxy config service on the UI loop on Linux because it // must synchronously run on the glib message loop. This will be passed to // the URLRequestContextStorage on the IO thread in GetURLRequestContext(). proxy_config_service_.reset( net::ProxyService::CreateSystemProxyConfigService( - io_loop_->message_loop_proxy(), file_loop_)); + io_loop_->message_loop_proxy(), file_loop_->message_loop_proxy())); } ShellURLRequestContextGetter::~ShellURLRequestContextGetter() { @@ -97,30 +173,55 @@ net::URLRequestContext* ShellURLRequestContextGetter::GetURLRequestContext() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); if (!url_request_context_.get()) { - url_request_context_.reset(new net::URLRequestContext()); - network_delegate_.reset(new ShellNetworkDelegate); - url_request_context_->set_network_delegate(network_delegate_.get()); - storage_.reset( + ShellContentBrowserClient* browser_client = + static_cast( + GetContentClient()->browser()); + + url_request_context_.reset(new net::URLRequestContext()); + network_delegate_.reset(new ShellNetworkDelegate(browser_context_, extension_info_map_)); + url_request_context_->set_network_delegate(network_delegate_.get()); + storage_.reset( new net::URLRequestContextStorage(url_request_context_.get())); - FilePath cookie_path = base_path_.Append(FILE_PATH_LITERAL("cookies")); - scoped_refptr cookie_db = - new SQLitePersistentCookieStore(cookie_path, true, NULL); - net::CookieMonster* cookie_store = new net::CookieMonster(cookie_db.get(), NULL); + FilePath cookie_path = data_path_.Append(FILE_PATH_LITERAL("cookies")); + scoped_refptr cookie_store = NULL; + + content::CookieStoreConfig cookie_config( + cookie_path, content::CookieStoreConfig::PERSISTANT_SESSION_COOKIES, + NULL, new NWCookieMonsterDelegate(browser_context_)); + cookie_store = content::CreateCookieStore(cookie_config); cookie_store->GetCookieMonster()->SetPersistSessionCookies(true); - storage_->set_cookie_store(cookie_store); + storage_->set_cookie_store(cookie_store.get()); + + const char* schemes[] = {"http", "https", "ws", "wss", "app", "file"}; + cookie_store->GetCookieMonster()->SetCookieableSchemes(schemes, 6); - storage_->set_server_bound_cert_service(new net::ServerBoundCertService( - new net::DefaultServerBoundCertStore(NULL), + storage_->set_channel_id_service(new net::ChannelIDService( + new net::DefaultChannelIDStore(NULL), base::WorkerPool::GetTaskRunner(true))); + + std::string accept_lang = browser_client->GetApplicationLocale(); + if (accept_lang.empty()) + accept_lang = "en-us,en"; + else + accept_lang.append(",en-us,en"); storage_->set_http_user_agent_settings( - new net::StaticHttpUserAgentSettings( - "en-us,en", "iso-8859-1,*,utf-8", EmptyString())); + new net::StaticHttpUserAgentSettings( + net::HttpUtil::GenerateAcceptLanguageHeader(accept_lang), + base::EmptyString())); scoped_ptr host_resolver( net::HostResolver::CreateDefaultResolver(NULL)); - storage_->set_cert_verifier(net::CertVerifier::CreateDefault()); + net::CertVerifyProc *verify_proc = net::CertVerifyProc::CreateDefault(); + if (!verify_proc->SupportsAdditionalTrustAnchors()) { + LOG(WARNING) + << "Additional trust anchors not supported on the current platform!"; + } + net::MultiThreadedCertVerifier *verifier = new net::MultiThreadedCertVerifier(verify_proc); + verifier->SetCertTrustAnchorProvider(this); + storage_->set_cert_verifier(verifier); + storage_->set_transport_security_state(new net::TransportSecurityState); net::ProxyService* proxy_service; net::DhcpProxyScriptFetcherFactory dhcp_factory; @@ -136,23 +237,29 @@ net::URLRequestContext* ShellURLRequestContextGetter::GetURLRequestContext() { storage_->set_ssl_config_service(new net::SSLConfigServiceDefaults); storage_->set_http_auth_handler_factory( - net::HttpAuthHandlerFactory::CreateDefault(host_resolver.get())); - storage_->set_http_server_properties(new net::HttpServerPropertiesImpl); + CreateDefaultAuthHandlerFactory(host_resolver.get())); - FilePath cache_path = base_path_.Append(FILE_PATH_LITERAL("Cache")); + storage_->set_http_server_properties( + scoped_ptr( + new net::HttpServerPropertiesImpl())); + + FilePath cache_path = data_path_.Append(FILE_PATH_LITERAL("Cache")); net::HttpCache::DefaultBackend* main_backend = new net::HttpCache::DefaultBackend( net::DISK_CACHE, + net::CACHE_BACKEND_BLOCKFILE, cache_path, - 0, + 10 * 1024 * 1024, // 10M BrowserThread::GetMessageLoopProxyForThread( BrowserThread::CACHE)); net::HttpNetworkSession::Params network_session_params; network_session_params.cert_verifier = url_request_context_->cert_verifier(); - network_session_params.server_bound_cert_service = - url_request_context_->server_bound_cert_service(); + network_session_params.transport_security_state = + url_request_context_->transport_security_state(); + network_session_params.channel_id_service = + url_request_context_->channel_id_service(); network_session_params.proxy_service = url_request_context_->proxy_service(); network_session_params.ssl_config_service = @@ -177,24 +284,30 @@ net::URLRequestContext* ShellURLRequestContextGetter::GetURLRequestContext() { scoped_ptr job_factory( new net::URLRequestJobFactoryImpl()); - bool set_protocol = job_factory->SetProtocolHandler( - chrome::kBlobScheme, blob_protocol_handler_.release()); - DCHECK(set_protocol); - set_protocol = job_factory->SetProtocolHandler( - chrome::kFileSystemScheme, file_system_protocol_handler_.release()); - DCHECK(set_protocol); - set_protocol = job_factory->SetProtocolHandler( - chrome::kChromeUIScheme, chrome_protocol_handler_.release()); - DCHECK(set_protocol); - set_protocol = job_factory->SetProtocolHandler( - chrome::kChromeDevToolsScheme, - chrome_devtools_protocol_handler_.release()); - DCHECK(set_protocol); + InstallProtocolHandlers(job_factory.get(), &protocol_handlers_); + job_factory->SetProtocolHandler( + url::kFileScheme, + new net::FileProtocolHandler( + content::BrowserThread::GetBlockingPool()-> + GetTaskRunnerWithShutdownBehavior( + base::SequencedWorkerPool::SKIP_ON_SHUTDOWN))); + job_factory->SetProtocolHandler("app", + new net::AppProtocolHandler(root_path_)); job_factory->SetProtocolHandler("nw", new nw::NwProtocolHandler()); - storage_->set_job_factory(new net::ProtocolInterceptJobFactory( - job_factory.PassAs(), - developer_protocol_handler_.Pass())); + // Set up interceptors in the reverse order. + scoped_ptr top_job_factory = + job_factory.Pass(); + for (URLRequestInterceptorScopedVector::reverse_iterator i = + request_interceptors_.rbegin(); + i != request_interceptors_.rend(); + ++i) { + top_job_factory.reset(new net::URLRequestInterceptingJobFactory( + top_job_factory.Pass(), make_scoped_ptr(*i))); + } + request_interceptors_.weak_clear(); + + storage_->set_job_factory(top_job_factory.release()); } return url_request_context_.get(); @@ -209,4 +322,42 @@ net::HostResolver* ShellURLRequestContextGetter::host_resolver() { return url_request_context_->host_resolver(); } +void ShellURLRequestContextGetter::SetAdditionalTrustAnchors(const net::CertificateList& trust_anchors) +{ + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); + trust_anchors_ = trust_anchors; +} + +const net::CertificateList& ShellURLRequestContextGetter::GetAdditionalTrustAnchors() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); + return trust_anchors_; +} + +net::HttpAuthHandlerFactory* ShellURLRequestContextGetter::CreateDefaultAuthHandlerFactory( + net::HostResolver* resolver) { + net::HttpAuthFilterWhitelist* auth_filter_default_credentials = NULL; + if (!auth_server_whitelist_.empty()) { + auth_filter_default_credentials = + new net::HttpAuthFilterWhitelist(auth_server_whitelist_); + } + net::HttpAuthFilterWhitelist* auth_filter_delegate = NULL; + if (!auth_delegate_whitelist_.empty()) { + auth_filter_delegate = + new net::HttpAuthFilterWhitelist(auth_delegate_whitelist_); + } + url_security_manager_.reset( + net::URLSecurityManager::Create(auth_filter_default_credentials, + auth_filter_delegate)); + std::vector supported_schemes; + base::SplitString(auth_schemes_, ',', &supported_schemes); + + scoped_ptr registry_factory( + net::HttpAuthHandlerRegistryFactory::Create( + supported_schemes, url_security_manager_.get(), + resolver, gssapi_library_name_, negotiate_disable_cname_lookup_, + negotiate_enable_port_)); + + return registry_factory.release(); +} + } // namespace content diff --git a/src/net/shell_url_request_context_getter.h b/src/net/shell_url_request_context_getter.h index 1d4fceb596..130000da3f 100644 --- a/src/net/shell_url_request_context_getter.h +++ b/src/net/shell_url_request_context_getter.h @@ -25,68 +25,92 @@ #include "base/files/file_path.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" +#include "content/public/browser/content_browser_client.h" +#include "net/cert/cert_trust_anchor_provider.h" #include "net/url_request/url_request_context_getter.h" #include "net/url_request/url_request_job_factory.h" -class MessageLoop; namespace net { +class HttpAuthHandlerFactory; class HostResolver; class NetworkDelegate; class ProxyConfigService; class URLRequestContextStorage; +class URLSecurityManager; +} + +namespace base{ +class MessageLoop; +} + +namespace extensions { + class InfoMap; } namespace content { -class ShellURLRequestContextGetter : public net::URLRequestContextGetter { +class ShellBrowserContext; + + class ShellURLRequestContextGetter : public net::URLRequestContextGetter, public net::CertTrustAnchorProvider { public: ShellURLRequestContextGetter( bool ignore_certificate_errors, - const base::FilePath& base_path, - MessageLoop* io_loop, - MessageLoop* file_loop, - scoped_ptr - blob_protocol_handler, - scoped_ptr - file_system_protocol_handler, - scoped_ptr - developer_protocol_handler, - scoped_ptr - chrome_protocol_handler, - scoped_ptr - chrome_devtools_protocol_handler); + const base::FilePath& data_path, + const base::FilePath& root_path, + base::MessageLoop* io_loop, + base::MessageLoop* file_loop, + ProtocolHandlerMap* protocol_handlers, + ShellBrowserContext*, + URLRequestInterceptorScopedVector request_interceptors, + const std::string& auth_schemes, + const std::string& auth_server_whitelist, + const std::string& auth_delegate_whitelist, + const std::string& gssapi_library_name, + extensions::InfoMap* extension_info_map); // net::URLRequestContextGetter implementation. - virtual net::URLRequestContext* GetURLRequestContext() OVERRIDE; - virtual scoped_refptr - GetNetworkTaskRunner() const OVERRIDE; + net::URLRequestContext* GetURLRequestContext() override; + scoped_refptr + GetNetworkTaskRunner() const override; net::HostResolver* host_resolver(); + void SetAdditionalTrustAnchors(const net::CertificateList& trust_anchors); + + // net::CertTrustAnchorProvider implementation. + const net::CertificateList& GetAdditionalTrustAnchors() override; + protected: - virtual ~ShellURLRequestContextGetter(); + ~ShellURLRequestContextGetter() final; + net::HttpAuthHandlerFactory* CreateDefaultAuthHandlerFactory(net::HostResolver* resolver); private: bool ignore_certificate_errors_; - base::FilePath base_path_; - MessageLoop* io_loop_; - MessageLoop* file_loop_; + base::FilePath data_path_; + base::FilePath root_path_; + + std::string auth_schemes_; + bool negotiate_disable_cname_lookup_; + bool negotiate_enable_port_; + std::string auth_server_whitelist_; + std::string auth_delegate_whitelist_; + std::string gssapi_library_name_; + // std::vector spdyproxy_auth_origins_; + net::CertificateList trust_anchors_; + + base::MessageLoop* io_loop_; + base::MessageLoop* file_loop_; scoped_ptr proxy_config_service_; scoped_ptr network_delegate_; scoped_ptr storage_; scoped_ptr url_request_context_; - scoped_ptr - blob_protocol_handler_; - scoped_ptr - file_system_protocol_handler_; - scoped_ptr - developer_protocol_handler_; - scoped_ptr - chrome_protocol_handler_; - scoped_ptr - chrome_devtools_protocol_handler_; + scoped_ptr url_security_manager_; + ProtocolHandlerMap protocol_handlers_; + ShellBrowserContext* browser_context_; + URLRequestInterceptorScopedVector request_interceptors_; + extensions::InfoMap* extension_info_map_; DISALLOW_COPY_AND_ASSIGN(ShellURLRequestContextGetter); }; diff --git a/src/net/sqlite_persistent_cookie_store.cc b/src/net/sqlite_persistent_cookie_store.cc index a76d8931cf..bb511ace7b 100644 --- a/src/net/sqlite_persistent_cookie_store.cc +++ b/src/net/sqlite_persistent_cookie_store.cc @@ -25,7 +25,7 @@ #include "base/time.h" #include "content/nw/src/net/clear_on_exit_policy.h" #include "content/public/browser/browser_thread.h" -#include "googleurl/src/gurl.h" +#include "url/gurl.h" #include "net/base/registry_controlled_domains/registry_controlled_domain.h" #include "net/cookies/canonical_cookie.h" #include "sql/error_delegate_util.h" diff --git a/src/nw.exe.manifest b/src/nw.exe.manifest new file mode 100644 index 0000000000..72a5886248 --- /dev/null +++ b/src/nw.exe.manifest @@ -0,0 +1,22 @@ + + + +node-webkit + + + + + + \ No newline at end of file diff --git a/src/nw_notification_manager.cc b/src/nw_notification_manager.cc new file mode 100644 index 0000000000..c5aa4bde43 --- /dev/null +++ b/src/nw_notification_manager.cc @@ -0,0 +1,142 @@ +// Copyright (c) 2014 Jefry Tedjokusumo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "ui/gfx/image/image.h" +#include "content/public/browser/desktop_notification_delegate.h" +#include "content/public/browser/render_process_host.h" +#include "content/common/platform_notification_messages.h" + + +#include "content/nw/src/nw_notification_manager.h" +#if defined(OS_MACOSX) +#include "content/nw/src/nw_notification_manager_mac.h" +#elif defined(OS_WIN) +#include "content/nw/src/nw_notification_manager_win.h" +#include "content/nw/src/nw_notification_manager_toast_win.h" +#elif defined(OS_LINUX) +#include "content/nw/src/nw_notification_manager_linux.h" +#endif + +namespace nw +{ +NotificationManager* NotificationManager::singleton_ = NULL; + +NotificationManager::NotificationManager() { +} + +NotificationManager::~NotificationManager() { + singleton_ = NULL; +} + +NotificationManager* NotificationManager::getSingleton() { + if (singleton_ == NULL) { +#if defined(OS_MACOSX) + singleton_ = new NotificationManagerMac(); +#elif defined(OS_WIN) + if (NotificationManagerToastWin::IsSupported()) + singleton_ = new NotificationManagerToastWin(); + else + singleton_ = new NotificationManagerWin(); +#elif defined(OS_LINUX) + singleton_ = new NotificationManagerLinux(); +#endif + } + return singleton_; +} + +bool NotificationManager::DesktopNotificationPostClick(int render_process_id, int notification_id) { + content::RenderProcessHost* rfh = content::RenderProcessHost::FromID(render_process_id); + if (!rfh) + return false; + + rfh->Send(new PlatformNotificationMsg_DidClick(notification_id)); + return true; +} + +bool NotificationManager::DesktopNotificationPostClose(int render_process_id, int notification_id, bool by_user) { + content::RenderProcessHost* rfh = content::RenderProcessHost::FromID(render_process_id); + if (!rfh) + return false; + + rfh->Send(new PlatformNotificationMsg_DidClose(notification_id)); + return true; +} + +bool NotificationManager::DesktopNotificationPostDisplay(int render_process_id, int notification_id) { + content::RenderProcessHost* rfh = content::RenderProcessHost::FromID(render_process_id); + if (!rfh) + return false; + + rfh->Send(new PlatformNotificationMsg_DidShow(notification_id)); + return true; +} + +bool NotificationManager::DesktopNotificationPostError(int render_process_id, int notification_id, const base::string16& message) { +#if 0 //FIXME + content::RenderProcessHost* rfh = content::RenderProcessHost::FromID(render_process_id); + if (!rfh) + return false; + + // Google remove the error notification messaging !! + rfh->Send(new PlatformNotificationMsg_DidError(rfh->GetRoutingID(), notification_id)); +#endif + return true; +} + +blink::WebNotificationPermission NotificationManager::CheckPermission(ResourceContext* resource_context, + const GURL& origin, + int render_process_id) { + return blink::WebNotificationPermissionAllowed; +} + +void CancelDesktopNotification(int notification_id) { + nw::NotificationManager *notificationManager = nw::NotificationManager::getSingleton(); + if (notificationManager == NULL) { + NOTIMPLEMENTED(); + return; + } + notificationManager->CancelDesktopNotification(notification_id); +} + +void NotificationManager::DisplayNotification(BrowserContext* browser_context, + const GURL& origin, + const SkBitmap& icon, + const PlatformNotificationData& notification_data, + scoped_ptr delegate, + int render_process_id, + base::Closure* cancel_callback) { + if (AddDesktopNotification(notification_data, render_process_id, delegate->notification_id(), icon)) + *cancel_callback = base::Bind(&nw::CancelDesktopNotification, delegate->notification_id()); +} + +void NotificationManager::DisplayPersistentNotification(BrowserContext* browser_context, + int64 service_worker_registration_id, + const GURL& origin, + const SkBitmap& icon, + const PlatformNotificationData& notification_data, + int render_process_id) { + NOTIMPLEMENTED(); +} + +void NotificationManager::ClosePersistentNotification(BrowserContext* browser_context, + const std::string& persistent_notification_id) { + NOTIMPLEMENTED(); +} + +} // namespace nw diff --git a/src/nw_notification_manager.h b/src/nw_notification_manager.h new file mode 100644 index 0000000000..d4afc93427 --- /dev/null +++ b/src/nw_notification_manager.h @@ -0,0 +1,81 @@ +// Copyright (c) 2014 Jefry Tedjokusumo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef CONTENT_NW_NOTIFICATION_MANAGER_H_ +#define CONTENT_NW_NOTIFICATION_MANAGER_H_ + +#include "base/basictypes.h" +#include "content/public/browser/platform_notification_service.h" + +namespace nw { + +using namespace content; + +class NotificationManager : public content::PlatformNotificationService{ +private: + static NotificationManager *singleton_; + +protected: + explicit NotificationManager(); + +public: + ~NotificationManager() override; + static NotificationManager* getSingleton(); + + virtual bool AddDesktopNotification(const content::PlatformNotificationData& params, + const int render_process_id, + const int notification_id, + const SkBitmap& icon) = 0; + + virtual bool CancelDesktopNotification(int notification_id) = 0; + + bool DesktopNotificationPostClick(int render_process_id, int notification_id); + bool DesktopNotificationPostClose(int render_process_id, int notification_id, bool by_user); + bool DesktopNotificationPostDisplay(int render_process_id, int notification_id); + bool DesktopNotificationPostError(int render_process_id, int notification_id, const base::string16& message); + + // PlatformNotificationService functions + blink::WebNotificationPermission CheckPermission(ResourceContext* resource_context, + const GURL& origin, + int render_process_id) override; + + void DisplayNotification(BrowserContext* browser_context, + const GURL& origin, + const SkBitmap& icon, + const PlatformNotificationData& notification_data, + scoped_ptr delegate, + int render_process_id, + base::Closure* cancel_callback) override; + + void DisplayPersistentNotification(BrowserContext* browser_context, + int64 service_worker_registration_id, + const GURL& origin, + const SkBitmap& icon, + const PlatformNotificationData& notification_data, + int render_process_id) override; + + void ClosePersistentNotification(BrowserContext* browser_context, + const std::string& persistent_notification_id) override; + + +}; + +} // namespace nw + +#endif // CONTENT_NW_NOTIFICATION_MANAGER_H_ diff --git a/src/nw_notification_manager_linux.cc b/src/nw_notification_manager_linux.cc new file mode 100644 index 0000000000..21860a20d2 --- /dev/null +++ b/src/nw_notification_manager_linux.cc @@ -0,0 +1,148 @@ +// Copyright (c) 2014 Jefry Tedjokusumo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "chrome/browser/status_icons/status_icon.h" +#include "chrome/browser/ui/libgtk2ui/skia_utils_gtk2.h" + +#include "content/public/browser/web_contents.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/common/platform_notification_data.h" +#include "content/nw/src/browser/native_window.h" +#include "content/nw/src/nw_package.h" +#include "content/nw/src/nw_shell.h" + +#include "ui/gfx/image/image.h" +#include "base/strings/utf_string_conversions.h" + +#include "content/nw/src/nw_notification_manager_linux.h" + + +namespace nw { + +NotificationManagerLinux::NotificationManagerLinux() { + notify_init (content::Shell::GetPackage()->GetName().c_str()); + char* info[4]; + if (notify_get_server_info(&info[0], &info[1], &info[2], &info[3])) + //Ubuntu notify-osd can only show 1 notification, this is the "hack" to do that + mForceOneNotification = strcmp(info[0], "notify-osd") == 0; +} + +NotificationManagerLinux::~NotificationManagerLinux() { + notify_uninit(); +} + +NotificationManagerLinux::NotificationMap::iterator NotificationManagerLinux::getNotification(int id) { + if (mForceOneNotification) { + return mNotificationIDmap.begin(); + } + return mNotificationIDmap.find(id); +} + +void NotificationManagerLinux::onClose(NotifyNotification *notif) +{ + NotificationManagerLinux* singleton = static_cast(NotificationManagerLinux::getSingleton()); + NotificationMap::iterator i; + for (i = singleton->mNotificationIDmap.begin(); i!=singleton->mNotificationIDmap.end(); i++) { + if (i->second.mNotification == notif) + break; + } + //int close_reason = notify_notification_get_closed_reason(notif); + //printf("close reason %d\n", close_reason); + singleton->DesktopNotificationPostClose(i->second.mRenderProcessId, i->first, false); + singleton->mNotificationIDmap.erase(i); + g_object_unref(G_OBJECT(notif)); +}; + +bool NotificationManagerLinux::AddDesktopNotification(const content::PlatformNotificationData& params, + const int render_process_id, + const int notification_id, + const SkBitmap& icon) { + content::Shell* shell = content::Shell::windows()[0]; + SkBitmap bitmap; + if(icon.getSize()) { + bitmap = icon; + } else { + bitmap = shell->window()->app_icon().AsBitmap(); + } + + NotifyNotification * notif; + NotificationMap::iterator i = getNotification(notification_id); + if (i==mNotificationIDmap.end()) { +#ifdef NOTIFY_CHECK_VERSION +#if NOTIFY_CHECK_VERSION(0,7,0) + notif = notify_notification_new ( + base::UTF16ToUTF8(params.title).c_str(), base::UTF16ToUTF8(params.body).c_str(), NULL); +#else + notif = notify_notification_new ( + base::UTF16ToUTF8(params.title).c_str(), base::UTF16ToUTF8(params.body).c_str(), NULL, NULL); +#endif +#else + notif = notify_notification_new ( + base::UTF16ToUTF8(params.title).c_str(), base::UTF16ToUTF8(params.body).c_str(), NULL, NULL); +#endif + NotificationData data; + data.mNotification = notif; + data.mRenderProcessId = render_process_id; + mNotificationIDmap[notification_id] = data; + } + else { + notif = i->second.mNotification; + notify_notification_update(notif, base::UTF16ToUTF8(params.title).c_str(), + base::UTF16ToUTF8(params.body).c_str(), NULL); + + // means this is the notify-osd hack + if (i->first != notification_id) { + NotificationData data; + data.mNotification = notif; + g_object_ref(G_OBJECT(notif)); + onClose(notif); + data.mRenderProcessId = render_process_id; + mNotificationIDmap[notification_id] = data; + } + } + + GdkPixbuf* pixbuf = libgtk2ui::GdkPixbufFromSkBitmap(bitmap); + if (pixbuf) { + notify_notification_set_image_from_pixbuf(notif, pixbuf); + g_object_unref(pixbuf); + } + + NOTIFY_NOTIFICATION_GET_CLASS(notif)->closed = onClose; + + GError* error = NULL; + if (notify_notification_show (notif, &error)) { + DesktopNotificationPostDisplay(render_process_id, notification_id); + } + else { + base::string16 errorMsg = base::UTF8ToUTF16(error->message); + DesktopNotificationPostError(render_process_id, notification_id, errorMsg); + } + return error==NULL; +} + +bool NotificationManagerLinux::CancelDesktopNotification(int notification_id) { + NotificationMap::const_iterator i = getNotification(notification_id); + if (i!=mNotificationIDmap.end()) { + return notify_notification_close(i->second.mNotification, NULL); + } + return false; +} + +} // namespace nw diff --git a/src/nw_notification_manager_linux.h b/src/nw_notification_manager_linux.h new file mode 100644 index 0000000000..830afe8546 --- /dev/null +++ b/src/nw_notification_manager_linux.h @@ -0,0 +1,54 @@ +// Copyright (c) 2014 Jefry Tedjokusumo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef CONTENT_NW_NOTIFICATION_MANAGER_LINUX_H_ +#define CONTENT_NW_NOTIFICATION_MANAGER_LINUX_H_ + +#include "content/nw/src/nw_notification_manager.h" +#include + +namespace nw { +class NotificationManagerLinux : public NotificationManager { + + struct NotificationData { + NotifyNotification* mNotification; + int mRenderProcessId; + }; + + typedef std::map NotificationMap; + NotificationMap mNotificationIDmap; + + static void onClose(NotifyNotification *notif); + bool mForceOneNotification; + + NotificationMap::iterator getNotification(int id); + +public: + explicit NotificationManagerLinux(); + ~NotificationManagerLinux() override; + bool AddDesktopNotification(const content::PlatformNotificationData& params, + const int render_process_id, + const int notification_id, + const SkBitmap& icon) override; + bool CancelDesktopNotification(int notification_id) override; +}; + +} // namespace nw + +#endif // CONTENT_NW_NOTIFICATION_MANAGER_LINUX_H_ diff --git a/src/nw_notification_manager_mac.h b/src/nw_notification_manager_mac.h new file mode 100644 index 0000000000..fd1c91eb12 --- /dev/null +++ b/src/nw_notification_manager_mac.h @@ -0,0 +1,41 @@ +// Copyright (c) 2014 Jefry Tedjokusumo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef CONTENT_NW_NOTIFICATION_MANAGER_MAC_H_ +#define CONTENT_NW_NOTIFICATION_MANAGER_MAC_H_ + +#include "content/nw/src/nw_notification_manager.h" + +namespace nw { + +class NotificationManagerMac : public NotificationManager { +public: + explicit NotificationManagerMac(); + virtual ~NotificationManagerMac(){} + + virtual bool AddDesktopNotification(const content::PlatformNotificationData& params, + const int render_process_id, const int notification_id, const SkBitmap& icon) override; + + virtual bool CancelDesktopNotification(int notification_id) override; + +}; + +} // namespace nw + +#endif // CONTENT_NW_NOTIFICATION_MANAGER_MAC_H_ diff --git a/src/nw_notification_manager_mac.mm b/src/nw_notification_manager_mac.mm new file mode 100644 index 0000000000..726173417f --- /dev/null +++ b/src/nw_notification_manager_mac.mm @@ -0,0 +1,123 @@ +// Copyright (c) 2014 Jefry Tedjokusumo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import +#include "base/mac/mac_util.h" +#include "base/strings/sys_string_conversions.h" + +#include "content/public/browser/web_contents.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/common/platform_notification_data.h" +#include "content/nw/src/browser/native_window.h" +#include "content/nw/src/nw_package.h" +#include "content/nw/src/nw_shell.h" + +#include "content/nw/src/nw_notification_manager_mac.h" + +#if !defined(MAC_OS_X_VERSION_10_8) || \ + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8 +@interface NSUserNotificationCenter : NSObject +@end +@interface NSUserNotification : NSObject +@end +@implementation NSUserNotification +@end +#endif + +@interface NWUserNotificationCenterDelegate : NSObject { +} +@end +@implementation NWUserNotificationCenterDelegate + +static NWUserNotificationCenterDelegate *singleton_ = nil; + ++(NWUserNotificationCenterDelegate *)defaultNWUserNotificationCenterDelegate{ + @synchronized(self) { + if (singleton_ == nil) + singleton_ = [[self alloc] init]; + } + return singleton_; +} + +-(BOOL)userNotificationCenter:(NSUserNotificationCenter *)center + shouldPresentNotification : (NSUserNotification *)notification { + + NSNumber *render_process_id = [notification.userInfo objectForKey : @"render_process_id"]; + NSNumber *notification_id = [notification.userInfo objectForKey : @"notification_id"]; + + + nw::NotificationManager::getSingleton()->DesktopNotificationPostDisplay(render_process_id.intValue, + notification_id.intValue); + return YES; +} + +-(void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification : (NSUserNotification *)notification { + NSNumber *render_process_id = [notification.userInfo objectForKey : @"render_process_id"]; + NSNumber *notification_id = [notification.userInfo objectForKey : @"notification_id"]; + + + nw::NotificationManager::getSingleton()->DesktopNotificationPostClick(render_process_id.intValue, + notification_id.intValue); +} +@end + +namespace nw { +NotificationManagerMac::NotificationManagerMac() { +} + +bool NotificationManagerMac::AddDesktopNotification(const content::PlatformNotificationData ¶ms, + const int render_process_id, const int notification_id, const SkBitmap& icon) { + + NSUserNotification *notification = [[NSUserNotification alloc] init]; + [notification setTitle : base::SysUTF16ToNSString(params.title)]; + [notification setInformativeText : base::SysUTF16ToNSString(params.body)]; + notification.hasActionButton = YES; + + if (base::mac::IsOSMavericksOrLater() && icon.getSize()) { + gfx::Image image = gfx::Image::CreateFrom1xBitmap(icon); + // this function only runs on Mavericks or later + [notification setContentImage : image.ToNSImage()]; + } + + notification.userInfo = @{ @"render_process_id" :[NSNumber numberWithInt : render_process_id], + @"notification_id" :[NSNumber numberWithInt : notification_id], + }; + + + [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:[NWUserNotificationCenterDelegate defaultNWUserNotificationCenterDelegate]]; + + [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; + + [notification release]; + + return true; +} + +bool NotificationManagerMac::CancelDesktopNotification(int notification_id){ + for (NSUserNotification *notification in[[NSUserNotificationCenter defaultUserNotificationCenter] deliveredNotifications]) { + NSNumber *current_notification_id = [notification.userInfo objectForKey : @"notification_id"]; + if (current_notification_id.intValue == notification_id){ + [[NSUserNotificationCenter defaultUserNotificationCenter] removeDeliveredNotification:notification]; + return true; + } + } + return false; +} +} // namespace nw diff --git a/src/nw_notification_manager_toast_win.cc b/src/nw_notification_manager_toast_win.cc new file mode 100644 index 0000000000..683f809397 --- /dev/null +++ b/src/nw_notification_manager_toast_win.cc @@ -0,0 +1,436 @@ +// Copyright (c) 2014 Jefry Tedjokusumo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +#include "config.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/common/platform_notification_data.h" +#include "content/nw/src/browser/native_window.h" +#include "content/nw/src/nw_package.h" +#include "content/nw/src/nw_shell.h" + +#include "ui/gfx/image/image.h" +#include "base/strings/utf_string_conversions.h" +#include "content/nw/src/common/shell_switches.h" +#include "content/nw/src/nw_notification_manager_toast_win.h" +#include "platform/image-encoders/skia/PNGImageEncoder.h" + +#include +#include +#include +#include + +#include +#include +#include + +using namespace Windows::Foundation; + +namespace nw { + +class StringReferenceWrapper { +public: + // Constructor which takes an existing string buffer and its length as the parameters. + // It fills an HSTRING_HEADER struct with the parameter. + // Warning: The caller must ensure the lifetime of the buffer outlives this + // object as it does not make a copy of the wide string memory. + + static bool isSupported() { + static char cachedRes = -1; + if (cachedRes > -1) return cachedRes; + cachedRes = ::LoadLibrary(L"API-MS-WIN-CORE-WINRT-STRING-L1-1-0.DLL") != 0; + return cachedRes; + } + + StringReferenceWrapper(_In_reads_(length) PCWSTR stringRef, _In_ UINT32 length) throw() { + HRESULT hr = WindowsCreateStringReference(stringRef, length, &_header, &_hstring); + if (FAILED(hr)) { + RaiseException(static_cast(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); + } + } + + ~StringReferenceWrapper() { + WindowsDeleteString(_hstring); + } + + template + StringReferenceWrapper(_In_reads_(N) wchar_t const (&stringRef)[N]) throw() { + UINT32 length = N - 1; + HRESULT hr = WindowsCreateStringReference(stringRef, length, &_header, &_hstring); + if (FAILED(hr)) { + RaiseException(static_cast(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); + } + } + + template + StringReferenceWrapper(_In_reads_(_) wchar_t(&stringRef)[_]) throw() { + UINT32 length; + HRESULT hr = SizeTToUInt32(wcslen(stringRef), &length); + if (FAILED(hr)) { + RaiseException(static_cast(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); + } + WindowsCreateStringReference(stringRef, length, &_header, &_hstring); + } + + HSTRING Get() const throw() { + return _hstring; + } + +private: + HSTRING _hstring; + HSTRING_HEADER _header; +}; + +typedef ABI::Windows::Foundation::ITypedEventHandler DesktopToastActivatedEventHandler; +typedef ABI::Windows::Foundation::ITypedEventHandler DesktopToastDismissedEventHandler; +typedef ABI::Windows::Foundation::ITypedEventHandler DesktopToastFailedEventHandler; + +class ToastEventHandler : + public Microsoft::WRL::Implements { +public: + ToastEventHandler::ToastEventHandler(const int render_process_id, const int notification_id, const content::PlatformNotificationData& params, const SkBitmap& icon); + ~ToastEventHandler(); + + // DesktopToastActivatedEventHandler + IFACEMETHODIMP Invoke(_In_ ABI::Windows::UI::Notifications::IToastNotification *sender, _In_ IInspectable* args); + + // DesktopToastDismissedEventHandler + IFACEMETHODIMP Invoke(_In_ ABI::Windows::UI::Notifications::IToastNotification *sender, _In_ ABI::Windows::UI::Notifications::IToastDismissedEventArgs *e); + + // DesktopToastFailedEventHandler + IFACEMETHODIMP Invoke(_In_ ABI::Windows::UI::Notifications::IToastNotification *sender, _In_ ABI::Windows::UI::Notifications::IToastFailedEventArgs *e); + + // IUnknown + IFACEMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&_ref); } + + IFACEMETHODIMP_(ULONG) Release() { + ULONG l = InterlockedDecrement(&_ref); + if (l == 0) + delete this; + return l; + } + + IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _COM_Outptr_ void **ppv) { + if (IsEqualIID(riid, IID_IUnknown)) + *ppv = static_cast(static_cast(this)); + else if (IsEqualIID(riid, __uuidof(DesktopToastActivatedEventHandler))) + *ppv = static_cast(this); + else if (IsEqualIID(riid, __uuidof(DesktopToastDismissedEventHandler))) + *ppv = static_cast(this); + else if (IsEqualIID(riid, __uuidof(DesktopToastFailedEventHandler))) + *ppv = static_cast(this); + else *ppv = nullptr; + + if (*ppv) { + reinterpret_cast(*ppv)->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; + } + +private: + ULONG _ref; + const int _render_process_id, _notification_id; + // _params and _icon is stored for fallback to bubble notification + const content::PlatformNotificationData _params; + const SkBitmap _icon; +}; + +// ============= ToastEventHandler Implementation ============= + +ToastEventHandler::ToastEventHandler(const int render_process_id, const int notification_id, const content::PlatformNotificationData& params, const SkBitmap& icon) : +_ref(0), _render_process_id(render_process_id), _notification_id(notification_id), _params(params), _icon(icon) { +} + +ToastEventHandler::~ToastEventHandler() { +} + +// DesktopToastActivatedEventHandler +IFACEMETHODIMP ToastEventHandler::Invoke(_In_ IToastNotification* /* sender */, _In_ IInspectable* /* args */) { + BOOL succeeded = nw::NotificationManager::getSingleton()->DesktopNotificationPostClick(_render_process_id, _notification_id); + return succeeded ? S_OK : E_FAIL; +} + +// DesktopToastDismissedEventHandler +IFACEMETHODIMP ToastEventHandler::Invoke(_In_ IToastNotification* /* sender */, _In_ IToastDismissedEventArgs* e) { + ToastDismissalReason tdr; + HRESULT hr = e->get_Reason(&tdr); + if (SUCCEEDED(hr)) { + BOOL succeeded = nw::NotificationManager::getSingleton()->DesktopNotificationPostClose(_render_process_id, _notification_id, tdr == ToastDismissalReason_UserCanceled); + hr = succeeded ? S_OK : E_FAIL; + } + nw::NotificationManager::getSingleton()->CancelDesktopNotification(_notification_id); + return hr; +} + +// DesktopToastFailedEventHandler +IFACEMETHODIMP ToastEventHandler::Invoke(_In_ IToastNotification* /* sender */, _In_ IToastFailedEventArgs* e) { + HRESULT errCode; + e->get_ErrorCode(&errCode); + nw::NotificationManagerToastWin* nmtw = static_cast(nw::NotificationManager::getSingleton()); + std::wstringstream errMsg; errMsg << L"The toast encountered an error code (0x" << std::hex << errCode <<")."; + const bool fallBack = errCode == 0x80070490; + if (fallBack) + errMsg << " Fallback to balloon notification!"; + + BOOL succeeded = nmtw->DesktopNotificationPostError(_render_process_id, _notification_id, errMsg.str().c_str()); + nmtw->notification_map_.erase(_notification_id); + + if (fallBack) { + NotificationManagerToastWin::ForceDisable = true; + delete nmtw; + NotificationManager::getSingleton()->AddDesktopNotification(_params, _render_process_id, _notification_id, _icon); + } + return succeeded ? S_OK : E_FAIL; +} + +// ============= NotificationManagerToastWin Implementation ============= +bool NotificationManagerToastWin::ForceDisable = false; + +HRESULT NotificationManagerToastWin::SetNodeValueString(_In_ HSTRING inputString, _In_ IXmlNode *node, _In_ IXmlDocument *xml) { + ComPtr inputText; + + HRESULT hr = xml->CreateTextNode(inputString, &inputText); + if (SUCCEEDED(hr)) { + ComPtr inputTextNode; + hr = inputText.As(&inputTextNode); + if (SUCCEEDED(hr)) { + ComPtr pAppendedChild; + hr = node->AppendChild(inputTextNode.Get(), &pAppendedChild); + } + } + + return hr; +} + +HRESULT NotificationManagerToastWin::SetTextValues(_In_reads_(textValuesCount) const wchar_t **textValues, _In_ UINT32 textValuesCount, + _In_reads_(textValuesCount) UINT32 *textValuesLengths, _In_ IXmlDocument *toastXml) { + HRESULT hr = textValues != nullptr && textValuesCount > 0 ? S_OK : E_INVALIDARG; + if (SUCCEEDED(hr)) { + ComPtr nodeList; + hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"text").Get(), &nodeList); + if (SUCCEEDED(hr)) { + UINT32 nodeListLength; + hr = nodeList->get_Length(&nodeListLength); + if (SUCCEEDED(hr)) { + hr = textValuesCount <= nodeListLength ? S_OK : E_INVALIDARG; + if (SUCCEEDED(hr)) { + for (UINT32 i = 0; i < textValuesCount; i++) { + ComPtr textNode; + hr = nodeList->Item(i, &textNode); + if (SUCCEEDED(hr)) { + hr = SetNodeValueString(StringReferenceWrapper(textValues[i], textValuesLengths[i]).Get(), textNode.Get(), toastXml); + } + } + } + } + } + } + return hr; +} + +HRESULT NotificationManagerToastWin::SilentAudio(_In_ IXmlDocument *toastXml) { + ComPtr nodeList; + HRESULT hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"toast").Get(), &nodeList); + if (SUCCEEDED(hr)) { + ComPtr toastNode; + hr = nodeList->Item(0, &toastNode); + if (SUCCEEDED(hr)) { + ComPtr soundElement; + hr = toastXml->CreateElement(StringReferenceWrapper(L"audio").Get(), &soundElement); + if (SUCCEEDED(hr)) { + hr = soundElement->SetAttribute(StringReferenceWrapper(L"silent").Get(), StringReferenceWrapper(L"true").Get()); + if (SUCCEEDED(hr)) { + ComPtr soundNode, appendedSoundNode; + hr = soundElement.As(&soundNode); + if (SUCCEEDED(hr)) { + hr = toastNode->AppendChild(soundNode.Get(), &appendedSoundNode); + } + } + } + } + } + return hr; +} + +HRESULT NotificationManagerToastWin::SetImageSrc(_In_z_ const wchar_t *imagePath, _In_ IXmlDocument *toastXml) { + wchar_t imageSrc[MAX_PATH] = L""; + HRESULT hr = StringCchCat(imageSrc, ARRAYSIZE(imageSrc), imagePath); + if (SUCCEEDED(hr)) { + ComPtr nodeList; + hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"image").Get(), &nodeList); + if (SUCCEEDED(hr)) { + ComPtr imageNode; + hr = nodeList->Item(0, &imageNode); + if (SUCCEEDED(hr)) { + ComPtr attributes; + hr = imageNode->get_Attributes(&attributes); + if (SUCCEEDED(hr)) { + ComPtr srcAttribute; + hr = attributes->GetNamedItem(StringReferenceWrapper(L"src").Get(), &srcAttribute); + if (SUCCEEDED(hr)) { + hr = SetNodeValueString(StringReferenceWrapper(imageSrc).Get(), srcAttribute.Get(), toastXml); + } + } + } + } + } + return hr; +} + +HRESULT NotificationManagerToastWin::CreateToastXml(_In_ IToastNotificationManagerStatics *toastManager, + const content::PlatformNotificationData& params, const SkBitmap& icon, _Outptr_ IXmlDocument** inputXml) { + bool bImage = icon.getSize() > 0; + char tempFileName[MAX_PATH]; + + if (params.icon.SchemeIsFile()) { + strcpy_s(tempFileName, params.icon.spec().data()); + } + else if (bImage) { + + char temp[MAX_PATH]; + GetTempPathA(MAX_PATH, tempFileName); + GetTempFileNameA(tempFileName, "NTF", 0, temp); + + Vector encodedImage; + bImage = blink::PNGImageEncoder::encode(icon, reinterpret_cast*>(&encodedImage)); + + FILE *f = fopen(temp, "wb"); + fwrite(encodedImage.data(), sizeof(char), encodedImage.size(), f); + fclose(f); + + sprintf_s(tempFileName, "file:///%s", temp); + } + + // Retrieve the template XML + HRESULT hr = toastManager->GetTemplateContent(bImage ? ToastTemplateType_ToastImageAndText03 : ToastTemplateType_ToastText03, inputXml); + if (SUCCEEDED(hr)) { + hr = bImage ? SetImageSrc(base::UTF8ToWide(tempFileName).c_str(), *inputXml) : S_OK; + if (SUCCEEDED(hr)) { + const wchar_t* textValues[] = { + params.title.c_str(), + params.body.c_str() + }; + UINT32 textLengths[] = { params.title.length(), params.body.length() }; + hr = SetTextValues(textValues, 2, textLengths, *inputXml); + if (SUCCEEDED(hr)) { + hr = SilentAudio(*inputXml); + } + } + } + return hr; +} + +HRESULT NotificationManagerToastWin::CreateToast(_In_ IToastNotificationManagerStatics *toastManager, _In_ IXmlDocument *xml, + const int render_process_id, const int notification_id, const content::PlatformNotificationData& params, const SkBitmap& icon) { + ComPtr factory; + HRESULT hr = GetActivationFactory(StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), &factory); + if (SUCCEEDED(hr)) { + ComPtr& toast = notification_map_[notification_id]; + hr = factory->CreateToastNotification(xml, &toast); + if (SUCCEEDED(hr)) { + // Register the event handlers + EventRegistrationToken activatedToken, dismissedToken, failedToken; + ComPtr eventHandler = new ToastEventHandler(render_process_id, notification_id, params, icon); + + hr = toast->add_Activated(eventHandler.Get(), &activatedToken); + if (SUCCEEDED(hr)) { + hr = toast->add_Dismissed(eventHandler.Get(), &dismissedToken); + if (SUCCEEDED(hr)) { + hr = toast->add_Failed(eventHandler.Get(), &failedToken); + if (SUCCEEDED(hr)) { + hr = notifier_->Show(toast.Get()); + } + } + } + } + } + return hr; +} + +bool NotificationManagerToastWin::IsSupported() { + static char cachedRes = -1; + if (ForceDisable) return false; + if (cachedRes > -1) return cachedRes; + cachedRes = 0; + + if (StringReferenceWrapper::isSupported()) { + ComPtr toastStatics; + HRESULT hr = GetActivationFactory(StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), &toastStatics); + cachedRes = SUCCEEDED(hr); + } + + return cachedRes; +} + +NotificationManagerToastWin::NotificationManagerToastWin() { + Init(); +} + +bool NotificationManagerToastWin::Init() { + if (!content::BrowserThread::CurrentlyOn(BrowserThread::UI)) + return false; + + DCHECK(toastStatics_.Get() == NULL); + + HRESULT hr = GetActivationFactory(StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), &toastStatics_); + if (SUCCEEDED(hr)) { + base::string16 appID; + if (content::Shell::GetPackage()->root()->GetString("app-id", &appID) == false) + content::Shell::GetPackage()->root()->GetString(switches::kmName, &appID); + + HRESULT hr = toastStatics_->CreateToastNotifierWithId(StringReferenceWrapper(appID.c_str(), appID.length()).Get(), ¬ifier_); + } + return SUCCEEDED(hr); +} + +NotificationManagerToastWin::~NotificationManagerToastWin() { +} + +bool NotificationManagerToastWin::AddDesktopNotification(const content::PlatformNotificationData& params, + const int render_process_id, const int notification_id, const SkBitmap& icon) { + + if (toastStatics_ == NULL) + if (!Init()) return false; + + ComPtr toastXml; + HRESULT hr = CreateToastXml(toastStatics_.Get(), params, icon, &toastXml); + if (SUCCEEDED(hr)) { + hr = CreateToast(toastStatics_.Get(), toastXml.Get(), render_process_id, notification_id, params, icon); + if (SUCCEEDED(hr)) + DesktopNotificationPostDisplay(render_process_id, notification_id); + } + + return SUCCEEDED(hr); +} + +bool NotificationManagerToastWin::CancelDesktopNotification(int notification_id) { + std::map>::iterator i = notification_map_.find(notification_id); + if (i == notification_map_.end()) + return false; + + ComPtr toast = i->second; + notification_map_.erase(i); + + return SUCCEEDED(notifier_->Hide(toast.Get())); +} +} // namespace nw diff --git a/src/nw_notification_manager_toast_win.h b/src/nw_notification_manager_toast_win.h new file mode 100644 index 0000000000..3aeebeff80 --- /dev/null +++ b/src/nw_notification_manager_toast_win.h @@ -0,0 +1,73 @@ +// Copyright (c) 2014 Jefry Tedjokusumo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef CONTENT_NW_NOTIFICATION_MANAGER_TOAST_WIN_H_ +#define CONTENT_NW_NOTIFICATION_MANAGER_TOAST_WIN_H_ + +#include "content/nw/src/nw_notification_manager.h" +#include +#include + + +namespace nw { + using namespace Microsoft::WRL; + using namespace ABI::Windows::UI::Notifications; + using namespace ABI::Windows::Data::Xml::Dom; + +class NotificationManagerToastWin : public NotificationManager { + friend class ToastEventHandler; + + ComPtr toastStatics_; + ComPtr notifier_; + std::map> notification_map_; + static bool ForceDisable; + + HRESULT CreateToast(_In_ IToastNotificationManagerStatics *toastManager, _In_ IXmlDocument *xml, + const int render_process_id, const int notification_id, const content::PlatformNotificationData& params, const SkBitmap& icon); + + // Create the toast XML from a template + HRESULT CreateToastXml(_In_ IToastNotificationManagerStatics *toastManager, + const content::PlatformNotificationData& params, const SkBitmap& icon, _Outptr_ IXmlDocument** inputXml); + + // Set the value of the "src" attribute of the "image" node + HRESULT SetImageSrc(_In_z_ const wchar_t *imagePath, _In_ IXmlDocument *toastXml); + + // Set the value of the "silent" attribute of the "audio" node + HRESULT SilentAudio( _In_ IXmlDocument *toastXml); + + // Set the values of each of the text nodes + HRESULT SetTextValues(_In_reads_(textValuesCount) const wchar_t **textValues, _In_ UINT32 textValuesCount, + _In_reads_(textValuesCount) UINT32 *textValuesLengths, _In_ IXmlDocument *toastXml); + + HRESULT SetNodeValueString(_In_ HSTRING inputString, _In_ IXmlNode *node, _In_ IXmlDocument *xml); + + bool Init(); + +public: + static bool IsSupported(); + explicit NotificationManagerToastWin(); + virtual ~NotificationManagerToastWin(); + virtual bool AddDesktopNotification(const content::PlatformNotificationData& params, + const int render_process_id, const int notification_id, const SkBitmap& icon) override; + virtual bool CancelDesktopNotification(int notification_id) override; +}; + +} // namespace nw + +#endif // CONTENT_NW_NOTIFICATION_MANAGER_WIN_H_ diff --git a/src/nw_notification_manager_win.cc b/src/nw_notification_manager_win.cc new file mode 100644 index 0000000000..5ad90c2356 --- /dev/null +++ b/src/nw_notification_manager_win.cc @@ -0,0 +1,184 @@ +// Copyright (c) 2014 Jefry Tedjokusumo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "chrome/browser/status_icons/status_icon.h" +#include "chrome/browser/status_icons/status_icon_observer.h" +#include "chrome/browser/ui/views/status_icons/status_tray_win.h" + +#include "content/public/browser/web_contents.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/common/platform_notification_data.h" +#include "content/nw/src/browser/native_window.h" +#include "content/nw/src/nw_package.h" +#include "content/nw/src/nw_shell.h" + +#include "ui/gfx/image/image.h" +#include "base/strings/utf_string_conversions.h" + +#include "content/nw/src/nw_notification_manager_win.h" + +#include + +namespace nw { +class TrayObserver : public StatusIconObserver { +public: + TrayObserver(NotificationManagerWin* tray) + : tray_(tray) { + } + + virtual ~TrayObserver() { + } + + virtual void OnStatusIconClicked() override { + } + + virtual void OnBalloonEvent(int event) override { + switch (event) { + case NIN_BALLOONHIDE: + tray_->DesktopNotificationPostClose(true); + tray_->ReleaseNotification(); + break; + case NIN_BALLOONTIMEOUT: + tray_->DesktopNotificationPostClose(false); + tray_->ReleaseNotification(); + break; + case NIN_BALLOONSHOW: + tray_->DesktopNotificationPostDisplay(); + break; + } + } + + virtual void OnBalloonClicked() override { + tray_->DesktopNotificationPostClick(); + tray_->ReleaseNotification(); + } +private: + NotificationManagerWin* tray_; +}; + +NotificationManagerWin::NotificationManagerWin() : status_icon_(NULL), status_tray_(NULL) { + Init(); +} + +bool NotificationManagerWin::Init() { + if (!content::BrowserThread::CurrentlyOn(BrowserThread::UI)) + return false; + + status_tray_ = static_cast(StatusTray::GetSingleton()); + + // check if status icon is already created + StatusIcon* status_icon = status_tray_->GetStatusIcon(); + + // if status icon already created, set the notification count to 1 and add the observer + notification_count_ = status_icon ? 1 : 0; + if (status_icon) { + status_observer_ = new TrayObserver(this); + status_icon->AddObserver(status_observer_); + } + return true; +} + +bool NotificationManagerWin::ReleaseNotification() { + if (notification_count_ > 0) { + if (notification_count_ == 1) { + // if I create the status_icon_ I am responsible to delete it + if (status_icon_) { + status_icon_->RemoveObserver(status_observer_); + status_tray_->RemoveStatusIcon(status_icon_); + status_icon_ = NULL; + } + + delete status_observer_; + status_observer_ = NULL; + } + notification_count_--; + return true; + } + return false; +} + + +NotificationManagerWin::~NotificationManagerWin() { + ReleaseNotification(); + + // this is to clean up status_observer_ if it is created by the constructor + if (status_observer_) { + StatusIcon* status_icon = status_tray_->GetStatusIcon(); + if (status_icon) + status_icon->RemoveObserver(status_observer_); + + delete status_observer_; + status_observer_ = NULL; + } +} + +bool NotificationManagerWin::AddDesktopNotification(const content::PlatformNotificationData& params, + const int render_process_id, const int notification_id, const SkBitmap& bitmap_icon) { + + if (status_tray_ == NULL) + if(!Init()) return false; + + content::Shell* shell = content::Shell::windows()[0]; + + // if we reach here, it means the function is called from image download callback + render_process_id_ = render_process_id; + notification_id_ = notification_id; + + // set the default notification icon as the app icon + gfx::Image icon = shell->window()->app_icon(); + + // always check if status icon is exist or not + StatusIcon* status_icon = status_tray_->GetStatusIcon(); + + // status_icon_ is null, it means we need to create and adds it to the tray + if (status_icon == NULL) { + nw::Package* package = shell->GetPackage(); + status_icon_ = status_tray_->CreateStatusIcon(StatusTray::NOTIFICATION_TRAY_ICON, + icon.IsEmpty() ? gfx::ImageSkia() : *icon.ToImageSkia(), base::UTF8ToUTF16(package->GetName())); + status_icon = status_icon_; + status_observer_ = new TrayObserver(this); + status_icon->AddObserver(status_observer_); + } + + // add the counter + notification_count_++; + // try to get the notification icon image given by image download callback + if (bitmap_icon.getSize()) + icon = gfx::Image::CreateFrom1xBitmap(bitmap_icon); + + //if body is empty string, the baloon won't shown + base::string16 body = params.body; + if (body.empty()) body = L" "; + + //show the baloon, this only works if iconsize >= 32x32 + bool result = status_icon->DisplayBalloon(icon.Width() < 32 || icon.Height() < 32 ? gfx::ImageSkia() : *icon.ToImageSkia(), params.title, body); + if (!result) { + DesktopNotificationPostError(L"DisplayBalloon fail"); + ReleaseNotification(); + } + + return result; +} + +bool NotificationManagerWin::CancelDesktopNotification(int notification_id) { + //windows can only have 1 notification, cannot delete existing notification + return true; +} +} // namespace nw diff --git a/src/nw_notification_manager_win.h b/src/nw_notification_manager_win.h new file mode 100644 index 0000000000..a067585dfa --- /dev/null +++ b/src/nw_notification_manager_win.h @@ -0,0 +1,74 @@ +// Copyright (c) 2014 Jefry Tedjokusumo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef CONTENT_NW_NOTIFICATION_MANAGER_WIN_H_ +#define CONTENT_NW_NOTIFICATION_MANAGER_WIN_H_ + +#include "content/nw/src/nw_notification_manager.h" +class StatusTrayWin; +class StatusIcon; + +namespace nw { +class NotificationManagerWin : public NotificationManager { + // The global presentation of system tray. + StatusTrayWin* status_tray_; + + // StatusIcon pointer created by ME + StatusIcon* status_icon_; + + // number of notification in the queue + int notification_count_; + + // decrement the status_icon_count_, if the value is 0 remove the status_icon_ from the tray + bool ReleaseNotification(); + + // Click observer. + friend class TrayObserver; + TrayObserver* status_observer_; + + // variable to store the latest notification data, windows can only show 1 notification + int render_process_id_, notification_id_; + + bool Init(); + + // dispatch the events from the latest notification + bool DesktopNotificationPostClick() { + return NotificationManager::DesktopNotificationPostClick(render_process_id_, notification_id_); + } + bool DesktopNotificationPostClose(bool by_user) { + return NotificationManager::DesktopNotificationPostClose(render_process_id_, notification_id_, by_user); + } + bool DesktopNotificationPostDisplay() { + return NotificationManager::DesktopNotificationPostDisplay(render_process_id_, notification_id_); + } + bool DesktopNotificationPostError(const base::string16& message) { + return NotificationManager::DesktopNotificationPostError(render_process_id_, notification_id_, message); + } + +public: + explicit NotificationManagerWin(); + virtual ~NotificationManagerWin(); + virtual bool AddDesktopNotification(const content::PlatformNotificationData& params, + const int render_process_id, const int notification_id, const SkBitmap& icon) override; + virtual bool CancelDesktopNotification(int notification_id) override; +}; + +} // namespace nw + +#endif // CONTENT_NW_NOTIFICATION_MANAGER_WIN_H_ diff --git a/src/nw_package.cc b/src/nw_package.cc index 8ad1f40f4f..aad9c948d7 100644 --- a/src/nw_package.cc +++ b/src/nw_package.cc @@ -23,28 +23,34 @@ #include #include "base/command_line.h" -#include "base/file_util.h" +#include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/json/json_file_value_serializer.h" +#include "base/json/json_string_value_serializer.h" +#include "base/strings/string_number_conversions.h" #include "base/strings/string_tokenizer.h" -#include "base/string_util.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" #include "base/threading/thread_restrictions.h" #include "base/values.h" -#include "chrome/common/zip.h" +#include "third_party/zlib/google/zip.h" #include "content/nw/src/common/shell_switches.h" #include "content/public/common/content_switches.h" -#include "googleurl/src/gurl.h" +#include "url/gurl.h" #include "grit/nw_resources.h" +#include "media/base/media_switches.h" #include "net/base/escape.h" #include "third_party/node/deps/uv/include/uv.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_skia_rep.h" -#include "webkit/glue/image_decoder.h" +#include "ui/gfx/codec/png_codec.h" +namespace base { bool IsSwitch(const CommandLine::StringType& string, CommandLine::StringType* switch_string, CommandLine::StringType* switch_value); +} namespace nw { @@ -61,14 +67,16 @@ bool MakePathAbsolute(FilePath* file_path) { DCHECK(file_path); FilePath current_directory; - if (!file_util::GetCurrentDirectory(¤t_directory)) + if (!base::GetCurrentDirectory(¤t_directory)) return false; if (file_path->IsAbsolute()) return true; - if (current_directory.empty()) - return file_util::AbsolutePath(file_path); + if (current_directory.empty()) { + *file_path = MakeAbsoluteFilePath(*file_path); + return true; + } if (!current_directory.IsAbsolute()) return false; @@ -78,7 +86,7 @@ bool MakePathAbsolute(FilePath* file_path) { } FilePath GetSelfPath() { - CommandLine* command_line = CommandLine::ForCurrentProcess(); + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); FilePath path; @@ -112,10 +120,12 @@ void RelativePathToURI(FilePath root, base::DictionaryValue* manifest) { std::string("file://") + main_path.AsUTF8Unsafe()); } +#if defined(OS_WIN) std::wstring ASCIIToWide(const std::string& ascii) { - DCHECK(IsStringASCII(ascii)) << ascii; + DCHECK(base::IsStringASCII(ascii)) << ascii; return std::wstring(ascii.begin(), ascii.end()); } +#endif } // namespace @@ -126,20 +136,26 @@ Package::Package() if (InitFromPath()) return; + // Try to load from the folder where the exe resides. + // Note: self_extract_ is true here, otherwise a 'Invalid Package' error + // would be triggered. + path_ = GetSelfPath().DirName(); +#if defined(OS_MACOSX) + path_ = path_.DirName().DirName().DirName(); +#endif + if (InitFromPath()) + return; + + path_ = path_.AppendASCII("package.nw"); + if (InitFromPath()) + return; + // Then see if we have arguments and extract it. - CommandLine* command_line = CommandLine::ForCurrentProcess(); - const CommandLine::StringVector& args = command_line->GetArgs(); + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + const base::CommandLine::StringVector& args = command_line->GetArgs(); if (args.size() > 0) { self_extract_ = false; path_ = FilePath(args[0]); - } else { - // Try to load from the folder where the exe resides. - // Note: self_extract_ is true here, otherwise a 'Invalid Package' error - // would be triggered. - path_ = GetSelfPath().DirName(); -#if defined(OS_MACOSX) - path_ = path_.DirName().DirName().DirName(); -#endif } if (InitFromPath()) return; @@ -171,13 +187,12 @@ bool Package::GetImage(const FilePath& icon_path, gfx::Image* image) { // Read the file from disk. std::string file_contents; - if (path.empty() || !file_util::ReadFileToString(path, &file_contents)) + if (path.empty() || !base::ReadFileToString(path, &file_contents)) return false; // Decode the bitmap using WebKit's image decoder. const unsigned char* data = reinterpret_cast(file_contents.data()); - webkit_glue::ImageDecoder decoder; scoped_ptr decoded(new SkBitmap()); // Note: This class only decodes bitmaps from extension resources. Chrome // doesn't (for security reasons) directly load extension resources provided @@ -185,18 +200,18 @@ bool Package::GetImage(const FilePath& icon_path, gfx::Image* image) { // locked-down utility process. Only if the decoding succeeds is the image // saved from memory to disk and subsequently used in the Chrome UI. // Chrome is therefore decoding bitmaps here that were generated by Chrome. - *decoded = decoder.Decode(data, file_contents.length()); + gfx::PNGCodec::Decode(data, file_contents.length(), decoded.get()); if (decoded->empty()) return false; // Unable to decode. - *image = gfx::Image::CreateFrom1xBitmap(*decoded.release()); + *image = gfx::Image::CreateFrom1xBitmap(*decoded); return true; } GURL Package::GetStartupURL() { - std::string url; + std::string url; // Specify URL in --url - CommandLine* command_line = CommandLine::ForCurrentProcess(); + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); if (command_line->HasSwitch(switches::kUrl)) { url = command_line->GetSwitchValueASCII(switches::kUrl); GURL gurl(url); @@ -211,9 +226,10 @@ GURL Package::GetStartupURL() { return GURL(error_page_url_); // Read from manifest. - if (root()->GetString(switches::kmMain, &url)) + if (root()->GetString(switches::kmMain, &url)) { + VLOG(1) << "Package startup URL: " << GURL(url); return GURL(url); - else + }else return GURL("nw:blank"); } @@ -229,6 +245,12 @@ bool Package::GetUseNode() { return use_node; } +bool Package::GetUseExtension() { + bool use_ext = true; + root()->GetBoolean(switches::kChromeExtension, &use_ext); + return use_ext; +} + base::DictionaryValue* Package::window() { base::DictionaryValue* window; root()->GetDictionaryWithoutPathExpansion(switches::kmWindow, &window); @@ -243,8 +265,8 @@ bool Package::InitFromPath() { // path_/package.json FilePath manifest_path = path_.AppendASCII("package.json"); - file_util::AbsolutePath(&manifest_path); - if (!file_util::PathExists(manifest_path)) { + manifest_path = MakeAbsoluteFilePath(manifest_path); + if (!base::PathExists(manifest_path)) { if (!self_extract()) ReportError("Invalid package", "There is no 'package.json' in the package, please make " @@ -255,7 +277,7 @@ bool Package::InitFromPath() { // Parse file. std::string error; JSONFileValueSerializer serializer(manifest_path); - scoped_ptr root(serializer.Deserialize(NULL, &error)); + scoped_ptr root(serializer.Deserialize(NULL, &error)); if (!root.get()) { ReportError("Unable to parse package.json", error.empty() ? @@ -263,18 +285,24 @@ bool Package::InitFromPath() { manifest_path.AsUTF8Unsafe() : error); return false; - } else if (!root->IsType(Value::TYPE_DICTIONARY)) { + } else if (!root->IsType(base::Value::TYPE_DICTIONARY)) { ReportError("Invalid package.json", "package.json's content should be a object type."); return false; } // Save result in global - root_.reset(static_cast(root.release())); + root_.reset(static_cast(root.release())); + + // Save origin package info + // Since we will change some value in root_, + // We need to catch the origin value of package.json + package_string_ = ""; + JSONStringValueSerializer stringSerializer(&package_string_); + stringSerializer.Serialize(*root_); // Check fields const char* required_fields[] = { - switches::kmMain, switches::kmName }; for (unsigned i = 0; i < arraysize(required_fields); i++) @@ -292,6 +320,15 @@ bool Package::InitFromPath() { root_->Set(switches::kmWindow, window); } + std::string bufsz_str; + if (root_->GetString(switches::kAudioBufferSize, &bufsz_str)) { + int buffer_size = 0; + if (base::StringToInt(bufsz_str, &buffer_size) && buffer_size > 0) { + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + command_line->AppendSwitchASCII(switches::kAudioBufferSize, bufsz_str); + } + } + // Read chromium command line args. ReadChromiumArgs(); @@ -310,7 +347,7 @@ void Package::InitWithDefault() { root()->Set(switches::kmWindow, window); // Hide toolbar if specifed in the command line. - if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoToolbar)) + if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoToolbar)) window->SetBoolean(switches::kmToolbar, false); // Window should show in center by default. @@ -328,12 +365,12 @@ bool Package::ExtractPath() { // Read symbolic link. #if defined(OS_POSIX) FilePath target; - if (file_util::ReadSymbolicLink(path_, &target)) + if (base::ReadSymbolicLink(path_, &target)) path_ = target; #endif // If it's a file then try to extract from it. - if (!file_util::DirectoryExists(path_)) { + if (!base::DirectoryExists(path_)) { FilePath extracted_path; if (ExtractPackage(path_, &extracted_path)) { path_ = extracted_path; @@ -348,24 +385,24 @@ bool Package::ExtractPath() { } bool Package::ExtractPackage(const FilePath& zip_file, FilePath* where) { - // Auto clean our temporary directory - static scoped_ptr scoped_temp_dir; + if (!scoped_temp_dir_.IsValid()) { #if defined(OS_WIN) - if (!file_util::CreateNewTempDirectory(L"nw", where)) { + if (!base::CreateNewTempDirectory(L"nw", where)) { #else - if (!file_util::CreateNewTempDirectory("nw", where)) { + if (!base::CreateNewTempDirectory("nw", where)) { #endif - ReportError("Cannot extract package", - "Unable to create temporary directory."); - return false; - } - - scoped_temp_dir.reset(new base::ScopedTempDir()); - if (!scoped_temp_dir->Set(*where)) { - ReportError("Cannot extract package", - "Unable to set temporary directory."); - return false; + ReportError("Cannot extract package", + "Unable to create temporary directory."); + return false; + } + if (!scoped_temp_dir_.Set(*where)) { + ReportError("Cannot extract package", + "Unable to set temporary directory."); + return false; + } + }else{ + *where = scoped_temp_dir_.path(); } return zip::Unzip(zip_file, *where); @@ -384,24 +421,24 @@ void Package::ReadChromiumArgs() { tokenizer.set_quote_chars("\'"); while (tokenizer.GetNext()) { std::string token = tokenizer.token(); - RemoveChars(token, "\'", &token); + base::RemoveChars(token, "\'", &token); chromium_args.push_back(token); } - CommandLine* command_line = CommandLine::ForCurrentProcess(); + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); for (unsigned i = 0; i < chromium_args.size(); ++i) { - CommandLine::StringType key, value; + base::CommandLine::StringType key, value; #if defined(OS_WIN) // Note:: On Windows, the |CommandLine::StringType| will be |std::wstring|, // so the chromium_args[i] is not compatible. We convert the wstring to // string here is safe beacuse we use ASCII only. - if (!IsSwitch(ASCIIToWide(chromium_args[i]), &key, &value)) + if (!base::IsSwitch(ASCIIToWide(chromium_args[i]), &key, &value)) continue; - command_line->AppendSwitchASCII(WideToASCII(key), - WideToASCII(value)); + command_line->AppendSwitchASCII(base::UTF16ToASCII(key), + base::UTF16ToASCII(value)); #else - if (!IsSwitch(chromium_args[i], &key, &value)) + if (!base::IsSwitch(chromium_args[i], &key, &value)) continue; command_line->AppendSwitchASCII(key, value); #endif @@ -416,7 +453,7 @@ void Package::ReadJsFlags() { if (!root()->GetStringASCII(switches::kmJsFlags, &flags)) return; - CommandLine* command_line = CommandLine::ForCurrentProcess(); + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); command_line->AppendSwitchASCII("js-flags", flags); } diff --git a/src/nw_package.h b/src/nw_package.h index 41fc614490..e211572467 100644 --- a/src/nw_package.h +++ b/src/nw_package.h @@ -24,6 +24,7 @@ #include "base/basictypes.h" #include "base/files/file_path.h" #include "base/memory/scoped_ptr.h" +#include "base/files/scoped_temp_dir.h" #include @@ -65,6 +66,8 @@ class Package { // Return if we enable node.js. bool GetUseNode(); + bool GetUseExtension(); + // Root path of package. FilePath path() const { return path_; } @@ -77,6 +80,9 @@ class Package { // Window field of manifest. base::DictionaryValue* window(); + // Manifest string. + std::string package_string() { return package_string_; } + private: bool InitFromPath(); void InitWithDefault(); @@ -101,9 +107,15 @@ class Package { // The parsed package.json. scoped_ptr root_; + // The origin JSON string package.json. + std::string package_string_; + // Stored url for error page. std::string error_page_url_; + // Auto clean our temporary directory + base::ScopedTempDir scoped_temp_dir_; + DISALLOW_COPY_AND_ASSIGN(Package); }; diff --git a/src/nw_protocol_handler.h b/src/nw_protocol_handler.h index 60439e3786..674eb133d7 100644 --- a/src/nw_protocol_handler.h +++ b/src/nw_protocol_handler.h @@ -32,14 +32,14 @@ class URLRequestJob; } namespace nw { - + class NwProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler { public: NwProtocolHandler(); - virtual net::URLRequestJob* MaybeCreateJob( + net::URLRequestJob* MaybeCreateJob( net::URLRequest* request, - net::NetworkDelegate* network_delegate) const OVERRIDE; + net::NetworkDelegate* network_delegate) const override; private: DISALLOW_COPY_AND_ASSIGN(NwProtocolHandler); diff --git a/src/nw_shell.cc b/src/nw_shell.cc index 33fcfd20eb..fc2cc0c904 100644 --- a/src/nw_shell.cc +++ b/src/nw_shell.cc @@ -21,14 +21,17 @@ #include "content/nw/src/nw_shell.h" #include "base/command_line.h" -#include "base/message_loop.h" -#include "base/string_util.h" -#include "base/utf_string_conversions.h" +#include "base/json/json_reader.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "content/browser/child_process_security_policy_impl.h" +#include "content/browser/renderer_host/render_view_host_impl.h" +#include "content/browser/web_contents/web_contents_impl.h" +#include "content/public/browser/desktop_media_id.h" #include "content/public/browser/devtools_agent_host.h" #include "content/public/browser/devtools_http_handler.h" -#include "content/public/browser/devtools_manager.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/notification_details.h" @@ -42,22 +45,72 @@ #include "content/public/common/renderer_preferences.h" #include "content/public/common/url_constants.h" #include "content/nw/src/api/api_messages.h" +#include "content/nw/src/api/dispatcher_host.h" #include "content/nw/src/api/app/app.h" +#include "content/nw/src/browser/browser_dialogs.h" #include "content/nw/src/browser/file_select_helper.h" #include "content/nw/src/browser/native_window.h" -#include "content/nw/src/browser/shell_devtools_delegate.h" #include "content/nw/src/browser/shell_javascript_dialog_creator.h" +#include "content/nw/src/browser/nw_autofill_client.h" #include "content/nw/src/common/shell_switches.h" #include "content/nw/src/media/media_stream_devices_controller.h" #include "content/nw/src/nw_package.h" #include "content/nw/src/shell_browser_context.h" #include "content/nw/src/shell_browser_main_parts.h" #include "content/nw/src/shell_content_browser_client.h" +#include "content/nw/src/shell_devtools_frontend.h" +//#include "content/nw/src/browser/shell_devtools_delegate.h" +#include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h" #include "grit/nw_resources.h" #include "net/base/escape.h" #include "ui/base/resource/resource_bundle.h" -#include "content/nw/src/browser/printing/print_view_manager.h" +#include "components/autofill/content/browser/content_autofill_driver.h" +#include "components/autofill/content/browser/content_autofill_driver_factory.h" +#include "components/autofill/core/browser/autofill_manager.h" +#include "components/web_modal/web_contents_modal_dialog_manager.h" + +#include "components/app_modal/javascript_dialog_manager.h" + +#if defined(OS_WIN) || defined(OS_LINUX) +#include "content/nw/src/browser/native_window_aura.h" +#include "ui/views/controls/webview/webview.h" +using nw::NativeWindowAura; +#endif +#include "content/public/browser/media_capture_devices.h" +#include "media/audio/audio_manager_base.h" + +#include "chrome/browser/printing/print_view_manager_basic.h" +#include "extensions/common/extension_messages.h" + +using base::MessageLoop; + +using content::MediaCaptureDevices; +using content::MediaStreamDevice; +using content::MediaStreamDevices; +using content::MediaStreamUI; + +namespace chrome { + bool IsNativeWindowInAsh(gfx::NativeWindow native_window) { + return false; + } +} + +namespace { + +const MediaStreamDevice* GetRequestedDeviceOrDefault( + const MediaStreamDevices& devices, + const std::string& requested_device_id) { + if (!requested_device_id.empty()) + return devices.FindById(requested_device_id); + + if (!devices.empty()) + return &devices[0]; + + return NULL; +} + +} namespace content { @@ -74,13 +127,25 @@ Shell* Shell::Create(BrowserContext* browser_context, int routing_id, WebContents* base_web_contents) { WebContents::CreateParams create_params(browser_context, site_instance); + + std::string filename; + base::DictionaryValue* manifest = GetPackage()->root(); + if (manifest->GetString(switches::kmInjectJSDocStart, &filename)) + create_params.nw_inject_js_doc_start = filename; + if (manifest->GetString(switches::kmInjectJSDocEnd, &filename)) + create_params.nw_inject_js_doc_end = filename; + if (manifest->GetString(switches::kmInjectCSS, &filename)) + create_params.nw_inject_css_fn = filename; + create_params.routing_id = routing_id; + WebContents* web_contents = WebContents::Create(create_params); Shell* shell = new Shell(web_contents, GetPackage()->window()); NavigationController::LoadURLParams params(url); - params.transition_type = PAGE_TRANSITION_TYPED; + params.transition_type = PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED); params.override_user_agent = NavigationController::UA_OVERRIDE_TRUE; + params.frame_name = std::string(); web_contents->GetController().LoadURLWithParams(params); @@ -94,11 +159,18 @@ Shell* Shell::Create(WebContents* source_contents, WebContents* new_contents) { Shell* shell = new Shell(new_contents, manifest); - NavigationController::LoadURLParams params(target_url); - params.transition_type = PAGE_TRANSITION_TYPED; - params.override_user_agent = NavigationController::UA_OVERRIDE_TRUE; - new_contents->GetController().LoadURLWithParams(params); + if (!target_url.is_empty()) { + NavigationController::LoadURLParams params(target_url); + params.transition_type = PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED); + params.override_user_agent = NavigationController::UA_OVERRIDE_TRUE; + params.frame_name = std::string(); + + int nw_win_id = 0; + manifest->GetInteger("nw_win_id", &nw_win_id); + params.nw_win_id = nw_win_id; + new_contents->GetController().LoadURLWithParams(params); + } // Use the user agent value from the source WebContents. std::string source_user_agent = source_contents->GetMutableRendererPrefs()->user_agent_override; @@ -111,20 +183,29 @@ Shell* Shell::Create(WebContents* source_contents, Shell* Shell::FromRenderViewHost(RenderViewHost* rvh) { for (size_t i = 0; i < windows_.size(); ++i) { - if (windows_[i]->web_contents() && - windows_[i]->web_contents()->GetRenderViewHost() == rvh) { + WebContents* web_contents = windows_[i]->web_contents(); + if (!web_contents) + continue; + if (web_contents->GetRenderViewHost() == rvh) { return windows_[i]; + }else{ + WebContentsImpl* impl = static_cast(web_contents); + RenderFrameHostManager* rvhm = impl->GetRenderManagerForTesting(); + if (rvhm && static_cast(rvhm->pending_render_view_host()) == rvh) + return windows_[i]; } } return NULL; } Shell::Shell(WebContents* web_contents, base::DictionaryValue* manifest) - : ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)), - is_devtools_(false), - force_close_(false), - id_(-1), - enable_nodejs_(true) + : content::WebContentsObserver(web_contents), + devtools_window_id_(0), + is_devtools_(false), + force_close_(false), + id_(-1), + enable_nodejs_(true), + weak_ptr_factory_(this) { // Register shell. registrar_.Add(this, NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED, @@ -133,30 +214,70 @@ Shell::Shell(WebContents* web_contents, base::DictionaryValue* manifest) NotificationService::AllBrowserContextsAndSources()); windows_.push_back(this); - bool enable_nodejs = true; - if (manifest->GetBoolean(switches::kNodejs, &enable_nodejs)) - enable_nodejs_ = enable_nodejs; + enable_nodejs_ = GetPackage()->GetUseNode(); + VLOG(1) << "enable nodejs from manifest: " << enable_nodejs_; + extension_function_dispatcher_.reset( + new extensions::ExtensionFunctionDispatcher(web_contents->GetBrowserContext(), this)); // Add web contents. web_contents_.reset(web_contents); content::WebContentsObserver::Observe(web_contents); web_contents_->SetDelegate(this); // Create window. - window_.reset(nw::NativeWindow::Create(this, manifest)); + window_.reset(nw::NativeWindow::Create(weak_ptr_factory_.GetWeakPtr(), manifest)); #if defined(ENABLE_PRINTING) - printing::PrintViewManager::CreateForWebContents(web_contents); + printing::PrintViewManagerBasic::CreateForWebContents(web_contents); #endif // Initialize window after we set window_, because some operations of // NativeWindow requires the window_ to be non-NULL. window_->InitFromManifest(manifest); + +#if defined(OS_WIN) || defined(OS_LINUX) + web_modal::WebContentsModalDialogManager::CreateForWebContents(web_contents); + web_modal::WebContentsModalDialogManager::FromWebContents(web_contents)->SetDelegate(this); + + popup_manager_.reset( + new web_modal::PopupManager(GetWebContentsModalDialogHost())); + popup_manager_->RegisterWith(web_contents); +#endif + +#if 1 + autofill::NWAutofillClient::CreateForWebContents(web_contents); + autofill::ContentAutofillDriverFactory::CreateForWebContentsAndDelegate( + web_contents, + autofill::NWAutofillClient::FromWebContents(web_contents), + "", + autofill::AutofillManager::DISABLE_AUTOFILL_DOWNLOAD_MANAGER); +#endif //FIXME } Shell::~Shell() { SendEvent("closed"); + if (is_devtools_ && devtools_owner_.get()) { + devtools_owner_->SendEvent("devtools-closed"); + nwapi::DispatcherHost* dhost = nwapi::FindDispatcherHost(devtools_owner_->web_contents_->GetRenderViewHost()); + if (devtools_owner_->devtools_window_id_) { + dhost->OnDeallocateObject(devtools_owner_->devtools_window_id_); + devtools_owner_->devtools_window_id_ = 0; + }else if (id_) { + //FIXME: the ownership/ flow of window and shell destruction + //need to be cleared + + // In linux, Shell destruction will be called immediately in + // CloseDevTools but in OSX it won't + dhost->OnDeallocateObject(id_); + } + } + + if (!is_devtools_ && id_ > 0) { + nwapi::DispatcherHost* dhost = nwapi::FindDispatcherHost(web_contents_->GetRenderViewHost()); + dhost->OnDeallocateObject(id_); + } + for (size_t i = 0; i < windows_.size(); ++i) { if (windows_[i] == this) { windows_.erase(windows_.begin() + i); @@ -168,11 +289,23 @@ Shell::~Shell() { } } - if (windows_.empty() && quit_message_loop_) - api::App::Quit(web_contents()->GetRenderProcessHost()); + if (windows_.empty() && quit_message_loop_) { + // If Window object is not clearred here, the Window destructor + // will be called at exit and block the thread exiting on + // Notification registrar destruction + nwapi::DispatcherHost::ClearObjectRegistry(); + nwapi::App::Quit(web_contents()->GetRenderProcessHost()); + } } void Shell::SendEvent(const std::string& event, const std::string& arg1) { + base::ListValue args; + if (!arg1.empty()) + args.AppendString(arg1); + SendEvent(event, args); +} + +void Shell::SendEvent(const std::string& event, const base::ListValue& args) { if (id() < 0) return; @@ -180,50 +313,27 @@ void Shell::SendEvent(const std::string& event, const std::string& arg1) { DVLOG(1) << "Shell::SendEvent " << event << " id():" << id() << " RoutingID: " << web_contents()->GetRoutingID(); - base::ListValue args; - if (!arg1.empty()) - args.AppendString(arg1); + WebContents* web_contents; + if (is_devtools_ && devtools_owner_.get()) + web_contents = devtools_owner_->web_contents(); + else + web_contents = this->web_contents(); - web_contents()->GetRenderViewHost()->Send(new ShellViewMsg_Object_On_Event( - web_contents()->GetRoutingID(), id(), event, args)); + web_contents->GetRenderViewHost()->Send(new ShellViewMsg_Object_On_Event( + web_contents->GetRoutingID(), id(), event, args)); } -bool Shell::ShouldCloseWindow() { +bool Shell::ShouldCloseWindow(bool quit) { if (id() < 0 || force_close_) return true; - SendEvent("close"); + SendEvent("close", quit ? "quit" : ""); return false; } void Shell::PrintCriticalError(const std::string& title, const std::string& content) { - const base::StringPiece template_html( - ResourceBundle::GetSharedInstance().GetRawDataResource( - IDR_NW_FATAL_ERROR)); - - std::string error_page_url; - - if (template_html.empty()) { - // Print hand written error info if nw.pak doesn't exist. - NOTREACHED() << "Unable to load error template."; - error_page_url = "data:text/html;base64,VW5hYmxlIHRvIGZpbmQgbncucGFrLgo="; - } else { - std::string content_with_no_newline, content_with_no_space; - ReplaceChars(net::EscapeForHTML(content), - "\n", "
", &content_with_no_newline); - ReplaceChars(content_with_no_newline, - " ", " ", &content_with_no_space); - - std::vector subst; - subst.push_back(title); - subst.push_back(content_with_no_space); - error_page_url = "data:text/html;charset=utf-8," + - net::EscapeQueryParamValue( - ReplaceStringPlaceholders(template_html, subst, NULL), false); - } - - LoadURL(GURL(error_page_url)); + LOG(ERROR) << content; } nw::Package* Shell::GetPackage() { @@ -233,18 +343,26 @@ nw::Package* Shell::GetPackage() { } void Shell::LoadURL(const GURL& url) { - web_contents_->GetController().LoadURL( - url, - Referrer(), - PAGE_TRANSITION_TYPED, - std::string()); - web_contents_->GetView()->Focus(); + if (url.is_empty() || !url.is_valid()) { + LOG(ERROR) << "Unable to load URL: " << url; + return; + } + NavigationController::LoadURLParams params(url); + params.transition_type = ui::PageTransitionFromInt( + ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR); + web_contents_->GetController().LoadURLWithParams(params); + // web_contents_->GetController().LoadURL( + // url, + // Referrer(), + // PAGE_TRANSITION_TYPED, + // std::string()); + web_contents_->Focus(); window()->SetToolbarButtonEnabled(nw::NativeWindow::BUTTON_FORWARD, false); } void Shell::GoBackOrForward(int offset) { web_contents_->GetController().GoToOffset(offset); - web_contents_->GetView()->Focus(); + web_contents_->Focus(); } void Shell::Reload(ReloadType type) { @@ -270,12 +388,12 @@ void Shell::Reload(ReloadType type) { break; } - web_contents_->GetView()->Focus(); + web_contents_->Focus(); } void Shell::Stop() { web_contents_->Stop(); - web_contents_->GetView()->Focus(); + web_contents_->Focus(); } void Shell::ReloadOrStop() { @@ -285,7 +403,28 @@ void Shell::ReloadOrStop() { Reload(); } -void Shell::ShowDevTools() { +void Shell::CloseDevTools() { + if (!devtools_window_) + return; + devtools_window_->window()->Close(); + devtools_window_.reset(); + devtools_window_id_ = 0; +} + +int Shell::WrapDevToolsWindow() { + if (devtools_window_id_) + return devtools_window_id_; + if (!devtools_window_) + return 0; + nwapi::DispatcherHost* dhost = nwapi::FindDispatcherHost(devtools_window_->web_contents_->GetRenderViewHost()); + int object_id = dhost->AllocateId(); + base::DictionaryValue manifest; + dhost->OnAllocateObject(object_id, "Window", manifest); + devtools_window_id_ = object_id; + return object_id; +} + +void Shell::ShowDevTools(const char* jail_id, bool headless) { ShellContentBrowserClient* browser_client = static_cast( GetContentClient()->browser()); @@ -295,20 +434,36 @@ void Shell::ShowDevTools() { return; } - RenderViewHost* inspected_rvh = web_contents()->GetRenderViewHost(); - scoped_refptr agent(DevToolsAgentHost::GetFor(inspected_rvh)); - DevToolsManager* manager = DevToolsManager::GetInstance(); - DevToolsClientHost* host = manager->GetDevToolsClientHostFor(agent.get()); + // RenderViewHost* inspected_rvh = web_contents()->GetRenderViewHost(); + if (nodejs()) { + std::string jscript = std::string("require('nw.gui').Window.get().__setDevToolsJail('") + + (jail_id ? jail_id : "(null)") + "');"; + web_contents()->GetMainFrame()->ExecuteJavaScript(base::UTF8ToUTF16(jscript.c_str())); + } + + scoped_refptr agent(DevToolsAgentHost::GetOrCreateFor(web_contents())); - if (host) { + if (agent->IsAttached()) { // Break remote debugging debugging session. - manager->UnregisterDevToolsClientHostFor(agent.get()); + content::DevToolsAgentHost::DetachAllClients(); } - ShellDevToolsDelegate* delegate = - browser_client->shell_browser_main_parts()->devtools_delegate(); - GURL url = delegate->devtools_http_handler()->GetFrontendURL(agent.get()); - + DevToolsHttpHandler* http_handler = + browser_client->shell_browser_main_parts()->devtools_handler(); + GURL url = http_handler->GetFrontendURL("/devtools/devtools.html"); + http_handler->EnumerateTargets(); + +#if 0 + if (headless) { + DevToolsAgentHost* agent_host = DevToolsAgentHost::GetOrCreateFor(web_contents()).get(); + url = delegate->devtools_http_handler()->GetFrontendURL(agent_host); + DevToolsHttpHandlerImpl* http_handler = static_cast(delegate->devtools_http_handler()); + http_handler->EnumerateTargets(); + SendEvent("devtools-opened", url.spec()); + return; + } +#endif + SendEvent("devtools-opened", url.spec()); // Use our minimum set manifest base::DictionaryValue manifest; manifest.SetBoolean(switches::kmToolbar, false); @@ -326,14 +481,22 @@ void Shell::ShowDevTools() { WebContents::CreateParams create_params(web_contents()->GetBrowserContext(), NULL); WebContents* web_contents = WebContents::Create(create_params); Shell* shell = new Shell(web_contents, &manifest); - browser_context->set_pinning_renderer(true); + + new ShellDevToolsFrontend( + shell, + agent.get()); int rh_id = shell->web_contents_->GetRenderProcessHost()->GetID(); - ChildProcessSecurityPolicyImpl::GetInstance()->GrantScheme(rh_id, chrome::kFileScheme); + ChildProcessSecurityPolicyImpl::GetInstance()->GrantScheme(rh_id, url::kFileScheme); + ChildProcessSecurityPolicyImpl::GetInstance()->GrantScheme(rh_id, "app"); shell->is_devtools_ = true; + shell->devtools_owner_ = weak_ptr_factory_.GetWeakPtr(); shell->force_close_ = true; shell->LoadURL(url); + // LoadURL() could allocate new SiteInstance so we have to pin the + // renderer after it + browser_context->set_pinning_renderer(true); // Save devtools window in current shell. devtools_window_ = shell->weak_ptr_factory_.GetWeakPtr(); } @@ -348,6 +511,7 @@ bool Shell::OnMessageReceived(const IPC::Message& message) { IPC_BEGIN_MESSAGE_MAP(Shell, message) IPC_MESSAGE_HANDLER(ShellViewHostMsg_UpdateDraggableRegions, UpdateDraggableRegions) + IPC_MESSAGE_HANDLER(ExtensionHostMsg_Request, OnRequest) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; @@ -362,7 +526,7 @@ WebContents* Shell::OpenURLFromTab(WebContents* source, return source; } -void Shell::LoadingStateChanged(WebContents* source) { +void Shell::LoadingStateChanged(WebContents* source, bool to_different_document) { int current_index = web_contents_->GetController().GetCurrentEntryIndex(); int max_index = web_contents_->GetController().GetEntryCount() - 1; @@ -381,6 +545,12 @@ void Shell::LoadingStateChanged(WebContents* source) { SendEvent("loaded"); } +void Shell::LoadProgressChanged(content::WebContents* source, double progress) { + std::ostringstream oss; + oss << progress; + SendEvent("progress", oss.str()); +} + void Shell::ActivateContents(content::WebContents* contents) { window()->Focus(true); } @@ -414,15 +584,23 @@ bool Shell::IsPopupOrPanel(const WebContents* source) const { // Window opened by window.open void Shell::WebContentsCreated(WebContents* source_contents, - int64 source_frame_id, + int source_frame_id, + const base::string16& frame_name, const GURL& target_url, - WebContents* new_contents) { + WebContents* new_contents, + const base::string16& nw_window_manifest) { // Create with package's manifest scoped_ptr manifest( GetPackage()->window()->DeepCopy()); + scoped_ptr val; + std::string manifest_str = base::UTF16ToUTF8(nw_window_manifest); + val.reset(base::JSONReader().ReadToValue(manifest_str)); + if (val.get() && val->IsType(base::Value::TYPE_DICTIONARY)) + manifest.reset(static_cast(val.release())); + // Get window features - WebKit::WebWindowFeatures features = new_contents->GetWindowFeatures(); + blink::WebWindowFeatures features = new_contents->GetWindowFeatures(); manifest->SetBoolean(switches::kmResizable, features.resizable); manifest->SetBoolean(switches::kmFullscreen, features.fullscreen); if (features.widthSet) @@ -436,7 +614,44 @@ void Shell::WebContentsCreated(WebContents* source_contents, // window.open should show the window by default. manifest->SetBoolean(switches::kmShow, true); - Shell::Create(source_contents, target_url, manifest.get(), new_contents); + // don't pass the url on window.open case + Shell::Create(source_contents, GURL::EmptyGURL(), manifest.get(), new_contents); + + // in Chromium 32 RenderViewCreated will not be called so the case + // should be handled here + new nwapi::DispatcherHost(new_contents->GetRenderViewHost()); + +#if defined(ENABLE_PRINTING) + printing::PrintViewManagerBasic::CreateForWebContents(new_contents); +#endif + +#if defined(OS_WIN) || defined(OS_LINUX) + web_modal::WebContentsModalDialogManager::CreateForWebContents(new_contents); + web_modal::WebContentsModalDialogManager::FromWebContents(new_contents)->SetDelegate(this); +#endif + +#if 0 //FIXME + autofill::TabAutofillManagerDelegate::CreateForWebContents(new_contents); + autofill::ContentAutofillDriver::CreateForWebContentsAndDelegate( + new_contents, + autofill::TabAutofillManagerDelegate::FromWebContents(new_contents), + "", + autofill::AutofillManager::ENABLE_AUTOFILL_DOWNLOAD_MANAGER); +#endif +} + +#if defined(OS_WIN) || defined(OS_LINUX) +void Shell::WebContentsFocused(content::WebContents* web_contents) { + NativeWindowAura* win = static_cast(window_.get()); + if (win) // on aura this function is called in the middle of window creation + win->web_view_->OnWebContentsFocused(web_contents); +} +#endif + +content::ColorChooser* +Shell::OpenColorChooser(content::WebContents* web_contents, SkColor color, + const std::vector& suggestions) { + return nw::ShowColorChooser(web_contents, color); } void Shell::RunFileChooser(WebContents* web_contents, @@ -454,17 +669,21 @@ void Shell::DidNavigateMainFramePostCommit(WebContents* web_contents) { window()->SetToolbarUrlEntry(web_contents->GetURL().spec()); } -JavaScriptDialogManager* Shell::GetJavaScriptDialogManager() { +JavaScriptDialogManager* Shell::GetJavaScriptDialogManager(WebContents* source) { +#if defined(OS_LINUX) + return app_modal::JavaScriptDialogManager::GetInstance(); +#else if (!dialog_creator_.get()) dialog_creator_.reset(new ShellJavaScriptDialogCreator()); return dialog_creator_.get(); +#endif } bool Shell::AddMessageToConsole(WebContents* source, int32 level, - const string16& message, + const base::string16& message, int32 line_no, - const string16& source_id) { + const base::string16& source_id) { return false; } @@ -483,10 +702,43 @@ void Shell::RequestMediaAccessPermission( WebContents* web_contents, const MediaStreamRequest& request, const MediaResponseCallback& callback) { - scoped_ptr - controller(new MediaStreamDevicesController(request, - callback)); - controller->DismissInfoBarAndTakeActionOnSettings(); + MediaStreamDevices devices; + + if (request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE) { + const MediaStreamDevice* device = GetRequestedDeviceOrDefault( + MediaCaptureDevices::GetInstance()->GetAudioCaptureDevices(), + request.requested_audio_device_id); + if (device) + devices.push_back(*device); + } + + if (request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE) { + const MediaStreamDevice* device = GetRequestedDeviceOrDefault( + MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices(), + request.requested_video_device_id); + if (device) + devices.push_back(*device); + } + + if (request.video_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE && + !request.requested_video_device_id.empty()) { + content::DesktopMediaID media_id = + content::DesktopMediaID::Parse(request.requested_video_device_id); + + devices.push_back(content::MediaStreamDevice( + content::MEDIA_DESKTOP_VIDEO_CAPTURE, media_id.ToString(), "Screen")); + } +#if defined(OS_WIN) + if (request.audio_type == content::MEDIA_DESKTOP_AUDIO_CAPTURE) { + devices.push_back(content::MediaStreamDevice(content::MEDIA_DESKTOP_AUDIO_CAPTURE, media::AudioManagerBase::kLoopbackInputDeviceId, "System Audio")); + } +#endif + // TODO(jamescook): Should we show a recording icon somewhere? If so, where? + scoped_ptr ui; + callback.Run(devices, + devices.empty() ? content::MEDIA_DEVICE_INVALID_STATE + : content::MEDIA_DEVICE_OK, + ui.Pass()); } void Shell::Observe(int type, @@ -497,19 +749,85 @@ void Shell::Observe(int type, Details >(details).ptr(); if (title->first) { - string16 text = title->first->GetTitle(); - window()->SetTitle(UTF16ToUTF8(text)); + base::string16 text = title->first->GetTitle(); + window()->SetTitle(base::UTF16ToUTF8(text)); } } else if (type == NOTIFICATION_RENDERER_PROCESS_CLOSED) { - exit_code_ = - content::Details( - details)->exit_code; + content::RenderProcessHost::RendererClosedDetails* process_details = + content::Details(details).ptr(); + content::RenderProcessHost* host = + content::Source(source).ptr(); + exit_code_ = process_details->exit_code; #if defined(OS_POSIX) if (WIFEXITED(exit_code_)) exit_code_ = WEXITSTATUS(exit_code_); #endif - MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure()); + if (host->GetHandle() == web_contents_->GetRenderProcessHost()->GetHandle()) { + set_force_close(true); + window()->Close(); + } + } +} + +GURL Shell::OverrideDOMStorageOrigin(const GURL& origin) { + if (!is_devtools()) + return origin; + return GURL("devtools://"); +} + +void Shell::RenderViewCreated(RenderViewHost* render_view_host) { + //FIXME: handle removal + new nwapi::DispatcherHost(render_view_host); + window()->SetTransparent(window()->IsTransparent()); +} + +#if defined(OS_WIN) || defined(OS_LINUX) +bool Shell::IsWebContentsVisible(content::WebContents* web_contents) { + //FIXME + return true; +} +#endif + +void Shell::ToggleFullscreenModeForTab(WebContents* web_contents, + bool enter_fullscreen) { + window()->SetFullscreen(enter_fullscreen); +} + +bool Shell::IsFullscreenForTabOrPending(const WebContents* web_contents) const { + return window()->IsFullscreen(); +} + +#if defined(OS_WIN) || defined(OS_LINUX) +web_modal::WebContentsModalDialogHost* Shell::GetWebContentsModalDialogHost() { + return (web_modal::WebContentsModalDialogHost*)window(); +} +#endif + +void Shell::Cleanup() { + std::vector list = windows(); + for (size_t i = 0; i < list.size(); ++i) { + delete list[i]; } } +extensions::WindowController* Shell::GetExtensionWindowController() const { + return NULL; +} + +content::WebContents* Shell::GetAssociatedWebContents() const { + return web_contents_.get(); +} + +void Shell::OnRequest( + const ExtensionHostMsg_Request_Params& params) { + extension_function_dispatcher_->Dispatch( + params, web_contents_->GetRenderViewHost()); +} + +bool Shell::CheckMediaAccessPermission(WebContents* web_contents, + const GURL& security_origin, + MediaStreamType type) { + return true; +} + } // namespace content diff --git a/src/nw_shell.h b/src/nw_shell.h index 7cab7255ab..120b9e5837 100644 --- a/src/nw_shell.h +++ b/src/nw_shell.h @@ -30,8 +30,14 @@ #include "content/public/browser/notification_observer.h" #include "content/public/browser/web_contents_delegate.h" #include "content/public/browser/web_contents_observer.h" +#if defined(OS_WIN) || defined(OS_LINUX) +#include "components/web_modal/popup_manager.h" +#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h" +#endif #include "ipc/ipc_channel.h" +#include "extensions/browser/extension_function_dispatcher.h" + namespace base { class DictionaryValue; class FilePath; @@ -39,6 +45,7 @@ class FilePath; namespace extensions { struct DraggableRegion; +class ExtensionFunctionDispatcher; } class GURL; @@ -51,6 +58,7 @@ class Package; namespace content { class BrowserContext; +class ShellDevToolsFrontend; class ShellJavaScriptDialogCreator; class SiteInstance; class WebContents; @@ -60,7 +68,11 @@ using base::FilePath; // This represents one window of the Content Shell, i.e. all the UI including // buttons and url bar, as well as the web content area. class Shell : public WebContentsDelegate, +#if defined(OS_WIN) || defined(OS_LINUX) + public web_modal::WebContentsModalDialogManagerDelegate, +#endif public content::WebContentsObserver, + public extensions::ExtensionFunctionDispatcher::Delegate, public NotificationObserver { public: enum ReloadType { @@ -73,7 +85,7 @@ class Shell : public WebContentsDelegate, }; explicit Shell(WebContents* web_contents, base::DictionaryValue* manifest); - virtual ~Shell(); + ~Shell() final; // Create a new shell. static Shell* Create(BrowserContext* browser_context, @@ -90,24 +102,30 @@ class Shell : public WebContentsDelegate, // Returns the Shell object corresponding to the given RenderViewHost. static Shell* FromRenderViewHost(RenderViewHost* rvh); + static void Cleanup(); void LoadURL(const GURL& url); void GoBackOrForward(int offset); void Reload(ReloadType type = RELOAD); void Stop(); void ReloadOrStop(); - void ShowDevTools(); - + void ShowDevTools(const char* jail_id = NULL, bool headless = false); + void CloseDevTools(); + bool devToolsOpen() { return devtools_window_.get() != NULL; } // Send an event to renderer. void SendEvent(const std::string& event, const std::string& arg1 = ""); + void SendEvent(const std::string& event, const base::ListValue& args); // Decide whether we should close the window. - bool ShouldCloseWindow(); + bool ShouldCloseWindow(bool quit = false); + + virtual GURL OverrideDOMStorageOrigin(const GURL& origin); // Print critical error. void PrintCriticalError(const std::string& title, const std::string& content); + int WrapDevToolsWindow(); // Returns the currently open windows. static std::vector& windows() { return windows_; } @@ -118,7 +136,7 @@ class Shell : public WebContentsDelegate, static int exit_code() { return exit_code_; } WebContents* web_contents() const { return web_contents_.get(); } - nw::NativeWindow* window() { return window_.get(); } + nw::NativeWindow* window() const { return window_.get(); } void set_force_close(bool force) { force_close_ = force; } bool is_devtools() const { return is_devtools_; } @@ -127,70 +145,108 @@ class Shell : public WebContentsDelegate, void set_id(int id) { id_ = id; } int id() const { return id_; } + void RenderViewCreated(RenderViewHost* render_view_host) override; +#if defined(OS_WIN) || defined(OS_LINUX) + void SetWebContentsBlocked(content::WebContents* web_contents, bool) override {} + bool IsWebContentsVisible(content::WebContents* web_contents) override; + web_modal::WebContentsModalDialogHost* GetWebContentsModalDialogHost() override; +#endif + bool CheckMediaAccessPermission(WebContents* web_contents, + const GURL& security_origin, + MediaStreamType type) override; protected: // content::WebContentsObserver implementation. - virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + bool OnMessageReceived(const IPC::Message& message) override; // content::WebContentsDelegate implementation. - virtual WebContents* OpenURLFromTab(WebContents* source, - const OpenURLParams& params) OVERRIDE; - virtual void LoadingStateChanged(WebContents* source) OVERRIDE; - virtual void ActivateContents(content::WebContents* contents) OVERRIDE; - virtual void DeactivateContents(content::WebContents* contents) OVERRIDE; - virtual void CloseContents(WebContents* source) OVERRIDE; - virtual void MoveContents(WebContents* source, const gfx::Rect& pos) OVERRIDE; - virtual bool IsPopupOrPanel(const WebContents* source) const OVERRIDE; - virtual void WebContentsCreated(WebContents* source_contents, - int64 source_frame_id, + WebContents* OpenURLFromTab(WebContents* source, + const OpenURLParams& params) override; + void LoadingStateChanged(WebContents* source, + bool to_different_document) override; + void LoadProgressChanged(content::WebContents* source, + double progress) override; + void ActivateContents(content::WebContents* contents) override; + void DeactivateContents(content::WebContents* contents) override; + void CloseContents(WebContents* source) override; + void MoveContents(WebContents* source, const gfx::Rect& pos) override; + bool IsPopupOrPanel(const WebContents* source) const override; + void WebContentsCreated(WebContents* source_contents, + int source_frame_id, + const base::string16& frame_name, const GURL& target_url, - WebContents* new_contents) OVERRIDE; - virtual void RunFileChooser( + WebContents* new_contents, + const base::string16& nw_window_manifest) override; + void ToggleFullscreenModeForTab(WebContents* web_contents, + bool enter_fullscreen) override; + bool IsFullscreenForTabOrPending( + const WebContents* web_contents) const override; +#if defined(OS_WIN) || defined(OS_LINUX) + void WebContentsFocused(WebContents* contents) override; +#endif + content::ColorChooser* OpenColorChooser( + content::WebContents* web_contents, + SkColor color, + const std::vector& suggestions) override; + void RunFileChooser( content::WebContents* web_contents, - const content::FileChooserParams& params) OVERRIDE; - virtual void EnumerateDirectory(content::WebContents* web_contents, + const content::FileChooserParams& params) override; + void EnumerateDirectory(content::WebContents* web_contents, int request_id, - const FilePath& path) OVERRIDE; - virtual void DidNavigateMainFramePostCommit( - WebContents* web_contents) OVERRIDE; - virtual JavaScriptDialogManager* GetJavaScriptDialogManager() OVERRIDE; - virtual void RequestToLockMouse(WebContents* web_contents, + const FilePath& path) override; + void DidNavigateMainFramePostCommit( + WebContents* web_contents) override; + JavaScriptDialogManager* GetJavaScriptDialogManager(WebContents* source) override; + void RequestToLockMouse(WebContents* web_contents, bool user_gesture, - bool last_unlocked_by_target) OVERRIDE; - virtual void HandleKeyboardEvent( + bool last_unlocked_by_target) override; + void HandleKeyboardEvent( WebContents* source, - const NativeWebKeyboardEvent& event) OVERRIDE; - virtual bool AddMessageToConsole(WebContents* source, + const NativeWebKeyboardEvent& event) override; + bool AddMessageToConsole(WebContents* source, int32 level, - const string16& message, + const base::string16& message, int32 line_no, - const string16& source_id) OVERRIDE; - virtual void RequestMediaAccessPermission( + const base::string16& source_id) override; + void RequestMediaAccessPermission( WebContents* web_contents, const MediaStreamRequest& request, - const MediaResponseCallback& callback) OVERRIDE; + const MediaResponseCallback& callback) override; private: + // ExtensionFunctionDispatcher::Delegate + extensions::WindowController* GetExtensionWindowController() const override; + content::WebContents* GetAssociatedWebContents() const override; + + void OnRequest(const ExtensionHostMsg_Request_Params& params); + void UpdateDraggableRegions( const std::vector& regions); // NotificationObserver - virtual void Observe(int type, + void Observe(int type, const NotificationSource& source, - const NotificationDetails& details) OVERRIDE; + const NotificationDetails& details) override; scoped_ptr dialog_creator_; scoped_ptr web_contents_; scoped_ptr window_; + scoped_ptr extension_function_dispatcher_; + +#if defined(OS_WIN) || defined(OS_LINUX) + scoped_ptr popup_manager_; +#endif // Notification manager. NotificationRegistrar registrar_; // Weak potiner to devtools window. base::WeakPtr devtools_window_; + base::WeakPtr devtools_owner_; - // Factory to generate weak pointer, used by devtools. - base::WeakPtrFactory weak_ptr_factory_; - + int devtools_window_id_; +#if 0 + ShellDevToolsFrontend* devtools_frontend_; +#endif // Whether this shell is devtools window. bool is_devtools_; @@ -210,6 +266,9 @@ class Shell : public WebContentsDelegate, static bool quit_message_loop_; static int exit_code_; + + // Factory to generate weak pointer, used by devtools. + base::WeakPtrFactory weak_ptr_factory_; }; } // namespace content diff --git a/src/nw_version.h b/src/nw_version.h index 68653d2223..b7afd76345 100644 --- a/src/nw_version.h +++ b/src/nw_version.h @@ -22,8 +22,8 @@ #define NW_VERSION_H #define NW_MAJOR_VERSION 0 -#define NW_MINOR_VERSION 5 -#define NW_PATCH_VERSION 1 +#define NW_MINOR_VERSION 12 +#define NW_PATCH_VERSION 3 #define NW_VERSION_IS_RELEASE 1 #ifndef NW_STRINGIFY @@ -38,11 +38,12 @@ #else # define NW_VERSION_STRING NW_STRINGIFY(NW_MAJOR_VERSION) "." \ NW_STRINGIFY(NW_MINOR_VERSION) "." \ - NW_STRINGIFY(NW_PATCH_VERSION) "-pre" + NW_STRINGIFY(NW_PATCH_VERSION) "-rc2" #endif #define NW_VERSION "v" NW_VERSION_STRING +#define CHROME_VERSION "41.0.2272.76" #define NW_VERSION_AT_LEAST(major, minor, patch) \ (( (major) < NW_MAJOR_VERSION) \ diff --git a/src/paths_mac.h b/src/paths_mac.h index b906564c63..1e167d85a0 100644 --- a/src/paths_mac.h +++ b/src/paths_mac.h @@ -14,6 +14,9 @@ void OverrideFrameworkBundlePath(); void OverrideChildProcessPath(); // Gets the path to the content shell's pak file. -base::FilePath GetResourcesPakFilePath(); +bool GetResourcesPakFilePath(base::FilePath& output); + +bool GetLocalePakFilePath(const std::string& locale, base::FilePath& output); +base::FilePath GetFrameworksPath(); #endif // CONTENT_SHELL_PATHS_MAC_H_ diff --git a/src/paths_mac.mm b/src/paths_mac.mm index 1fc268c7e7..354e8a36a2 100644 --- a/src/paths_mac.mm +++ b/src/paths_mac.mm @@ -23,12 +23,11 @@ #include "base/mac/bundle_locations.h" #include "base/mac/foundation_util.h" #include "base/path_service.h" +#include "base/strings/sys_string_conversions.h" #include "content/public/common/content_paths.h" using base::FilePath; -namespace { - FilePath GetFrameworksPath() { // Start out with the path to the running executable. FilePath path; @@ -50,28 +49,40 @@ FilePath GetFrameworksPath() { return path.Append("Frameworks"); } -} // namespace - void OverrideFrameworkBundlePath() { FilePath helper_path = - GetFrameworksPath().Append("node-webkit Framework.framework"); + GetFrameworksPath().Append("nwjs Framework.framework"); base::mac::SetOverrideFrameworkBundlePath(helper_path); } void OverrideChildProcessPath() { - FilePath helper_path = GetFrameworksPath().Append("node-webkit Helper.app") + FilePath helper_path = GetFrameworksPath().Append("nwjs Helper.app") .Append("Contents") .Append("MacOS") - .Append("node-webkit Helper"); + .Append("nwjs Helper"); PathService::Override(content::CHILD_PROCESS_EXE, helper_path); } -FilePath GetResourcesPakFilePath() { +bool GetResourcesPakFilePath(FilePath& output) { NSString* pak_path = [base::mac::FrameworkBundle() pathForResource:@"nw" ofType:@"pak"]; - return FilePath([pak_path fileSystemRepresentation]); + if (!pak_path) + return false; + output = FilePath([pak_path fileSystemRepresentation]); + return true; +} + +bool GetLocalePakFilePath(const std::string& locale, FilePath& output) { + NSString* pak_path = + [base::mac::FrameworkBundle() pathForResource:base::SysUTF8ToNSString(locale) + ofType:@"pak"]; + + if (!pak_path) + return false; + output = FilePath([pak_path fileSystemRepresentation]); + return true; } diff --git a/src/renderer/autofill_agent.cc b/src/renderer/autofill_agent.cc index affe2434a2..b372a6025f 100644 --- a/src/renderer/autofill_agent.cc +++ b/src/renderer/autofill_agent.cc @@ -21,16 +21,16 @@ #include "content/nw/src/renderer/autofill_agent.h" #include "base/bind.h" -#include "base/message_loop.h" -#include "base/string_util.h" -#include "base/string_split.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_util.h" +#include "base/strings/string_split.h" #include "content/public/renderer/render_view.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebNode.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebNodeCollection.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebOptionElement.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" -#include "ui/base/keycodes/keyboard_codes.h" +#include "third_party/WebKit/public/web/WebInputEvent.h" +#include "third_party/WebKit/public/web/WebNode.h" +#include "third_party/WebKit/public/web/WebNodeCollection.h" +#include "third_party/WebKit/public/web/WebOptionElement.h" +#include "third_party/WebKit/public/web/WebView.h" +#include "ui/events/keycodes/keyboard_codes.h" using WebKit::WebAutofillClient; using WebKit::WebFormElement; @@ -88,26 +88,24 @@ void AppendDataListSuggestions(const WebKit::WebInputElement& element, AutofillAgent::AutofillAgent(content::RenderView* render_view) : content::RenderViewObserver(render_view), - ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { + weak_ptr_factory_(this) { render_view->GetWebView()->setAutofillClient(this); } AutofillAgent::~AutofillAgent() { } -bool AutofillAgent::InputElementClicked(const WebInputElement& element, +void AutofillAgent::InputElementClicked(const WebInputElement& element, bool was_focused, bool is_focused) { if (was_focused) ShowSuggestions(element, true, false, true); - return false; } -bool AutofillAgent::InputElementLostFocus() { - return false; +void AutofillAgent::InputElementLostFocus() { } - + void AutofillAgent::didAcceptAutofillSuggestion(const WebNode& node, const WebString& value, const WebString& label, @@ -154,7 +152,7 @@ void AutofillAgent::textFieldDidChange(const WebInputElement& element) { // properly at this point (http://bugs.webkit.org/show_bug.cgi?id=16976) and // it is needed to trigger autofill. weak_ptr_factory_.InvalidateWeakPtrs(); - MessageLoop::current()->PostTask( + base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&AutofillAgent::TextFieldDidChangeImpl, weak_ptr_factory_.GetWeakPtr(), element)); diff --git a/src/renderer/autofill_agent.h b/src/renderer/autofill_agent.h index 4ebae70ea5..696f2f99b5 100644 --- a/src/renderer/autofill_agent.h +++ b/src/renderer/autofill_agent.h @@ -21,10 +21,10 @@ #include "base/basictypes.h" #include "base/compiler_specific.h" #include "base/memory/weak_ptr.h" -#include "chrome/renderer/page_click_listener.h" +#include "components/autofill/content/renderer/page_click_listener.h" #include "content/public/renderer/render_view_observer.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebAutofillClient.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputElement.h" +#include "third_party/WebKit/public/web/WebAutofillClient.h" +#include "third_party/WebKit/public/web/WebInputElement.h" namespace content { class RenderView; @@ -37,7 +37,7 @@ class WebNode; namespace nw { class AutofillAgent : public content::RenderViewObserver, - public PageClickListener, + public autofill::PageClickListener, public WebKit::WebAutofillClient { public: AutofillAgent(content::RenderView* render_view); @@ -51,10 +51,10 @@ class AutofillAgent : public content::RenderViewObserver, }; // PageClickListener: - virtual bool InputElementClicked(const WebKit::WebInputElement& element, + virtual void InputElementClicked(const WebKit::WebInputElement& element, bool was_focused, bool is_focused) OVERRIDE; - virtual bool InputElementLostFocus() OVERRIDE; + virtual void InputElementLostFocus() OVERRIDE; // WebKit::WebAutofillClient: virtual void didAcceptAutofillSuggestion(const WebKit::WebNode& node, diff --git a/src/renderer/common/render_messages.h b/src/renderer/common/render_messages.h index 7a1cfc2415..3b9556160e 100644 --- a/src/renderer/common/render_messages.h +++ b/src/renderer/common/render_messages.h @@ -19,7 +19,11 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "content/public/common/common_param_traits.h" +#include "ipc/ipc_message.h" +#include "ipc/ipc_message_macros.h" +#include "ipc/ipc_param_traits.h" #include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/ipc/gfx_param_traits.h" // Singly-included section #ifndef CONTENT_NW_SRC_RENDERER_COMMON_RENDER_MESSAGES_H_ @@ -38,3 +42,25 @@ IPC_MESSAGE_ROUTED0(NwViewMsg_CaptureSnapshot) // Send a snapshot of the tab contents to the render host. IPC_MESSAGE_ROUTED1(NwViewHostMsg_Snapshot, SkBitmap /* bitmap */) + +// Brings up SaveAs... dialog to save specified URL. +IPC_MESSAGE_ROUTED2(ChromeViewHostMsg_PDFSaveURLAs, + GURL /* url */, + content::Referrer /* referrer */) + +// Updates the content restrictions, i.e. to disable print/copy. +IPC_MESSAGE_ROUTED1(ChromeViewHostMsg_PDFUpdateContentRestrictions, + int /* restrictions */) + +// Brings up a Password... dialog for protected documents. +IPC_SYNC_MESSAGE_ROUTED1_1(ChromeViewHostMsg_PDFModalPromptForPassword, + std::string /* prompt */, + std::string /* actual_value */) + +#if defined(ENABLE_PLUGINS) +// Sent by the renderer to check if crash reporting is enabled. +IPC_SYNC_MESSAGE_CONTROL0_1(ChromeViewHostMsg_IsCrashReportingEnabled, + bool /* enabled */) +#endif + + diff --git a/src/renderer/nw_render_view_observer.cc b/src/renderer/nw_render_view_observer.cc index 0b93c2671c..50c0b66877 100644 --- a/src/renderer/nw_render_view_observer.cc +++ b/src/renderer/nw_render_view_observer.cc @@ -20,18 +20,27 @@ #include "content/nw/src/renderer/nw_render_view_observer.h" +#include + +#include "base/files/file_util.h" +#include "base/strings/utf_string_conversions.h" #include "content/nw/src/renderer/common/render_messages.h" #include "content/public/renderer/render_view.h" +#include "content/renderer/render_view_impl.h" #include "skia/ext/platform_canvas.h" -#include "third_party/WebKit/Source/Platform/chromium/public/WebRect.h" -#include "third_party/WebKit/Source/Platform/chromium/public/WebSize.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" -#include "webkit/glue/webkit_glue.h" +#include "third_party/WebKit/public/platform/WebRect.h" +#include "third_party/WebKit/public/platform/WebSize.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" +#include "third_party/WebKit/public/web/WebScriptSource.h" +#include "third_party/WebKit/public/web/WebView.h" + +using content::RenderView; +using content::RenderViewImpl; -using WebKit::WebFrame; -using WebKit::WebRect; -using WebKit::WebSize; +using blink::WebFrame; +using blink::WebRect; +using blink::WebScriptSource; +using blink::WebSize; namespace nw { @@ -70,7 +79,40 @@ void NwRenderViewObserver::OnCaptureSnapshot() { Send(new NwViewHostMsg_Snapshot(routing_id(), snapshot)); } -bool NwRenderViewObserver::CaptureSnapshot(WebKit::WebView* view, +void NwRenderViewObserver::DidFinishDocumentLoad(blink::WebLocalFrame* frame) { + RenderViewImpl* rv = RenderViewImpl::FromWebView(frame->view()); + if (!rv) + return; + std::string js_fn = rv->renderer_preferences_.nw_inject_js_doc_end; + OnDocumentCallback(rv, js_fn, frame); +} + +void NwRenderViewObserver::DidCreateDocumentElement(blink::WebLocalFrame* frame) { + RenderViewImpl* rv = RenderViewImpl::FromWebView(frame->view()); + if (!rv) + return; + std::string js_fn = rv->renderer_preferences_.nw_inject_js_doc_start; + OnDocumentCallback(rv, js_fn, frame); +} + +void NwRenderViewObserver::OnDocumentCallback(RenderViewImpl* rv, + const std::string& js_fn, + blink::WebFrame* frame) { + if (js_fn.empty()) + return; + std::string content; + base::FilePath path = rv->renderer_preferences_.nw_app_root_path.AppendASCII(js_fn); + if (!base::ReadFileToString(path, &content)) { + LOG(WARNING) << "Failed to load js script file: " << path.value(); + return; + } + base::string16 jscript = base::UTF8ToUTF16(content); + v8::HandleScope handle_scope(v8::Isolate::GetCurrent()); + // v8::Handle result; + frame->executeScriptAndReturnValue(WebScriptSource(jscript)); +} + +bool NwRenderViewObserver::CaptureSnapshot(blink::WebView* view, SkBitmap* snapshot) { view->layout(); const WebSize& size = view->size(); @@ -81,15 +123,15 @@ bool NwRenderViewObserver::CaptureSnapshot(WebKit::WebView* view, if (!canvas) return false; - view->paint(webkit_glue::ToWebCanvas(canvas.get()), + view->paint(canvas.get(), WebRect(0, 0, size.width, size.height)); // TODO: Add a way to snapshot the whole page, not just the currently // visible part. - SkDevice* device = skia::GetTopDevice(*canvas); + SkBaseDevice* device = skia::GetTopDevice(*canvas); const SkBitmap& bitmap = device->accessBitmap(false); - if (!bitmap.copyTo(snapshot, SkBitmap::kARGB_8888_Config)) + if (!bitmap.copyTo(snapshot, kN32_SkColorType)) return false; return true; diff --git a/src/renderer/nw_render_view_observer.h b/src/renderer/nw_render_view_observer.h index 1e3e36ed63..157b726b41 100644 --- a/src/renderer/nw_render_view_observer.h +++ b/src/renderer/nw_render_view_observer.h @@ -23,9 +23,15 @@ #include "content/public/renderer/render_view_observer.h" +#include + class SkBitmap; -namespace WebKit { +namespace content { +class RenderViewImpl; +} + +namespace blink { class WebView; } @@ -34,17 +40,24 @@ namespace nw { class NwRenderViewObserver : public content::RenderViewObserver { public: NwRenderViewObserver(content::RenderView* render_view); - virtual ~NwRenderViewObserver(); + ~NwRenderViewObserver() final; - private: // RenderViewObserver implementation. - virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + bool OnMessageReceived(const IPC::Message& message) override; + void DidCreateDocumentElement(blink::WebLocalFrame* frame) override; + void DidFinishDocumentLoad(blink::WebLocalFrame* frame) override; + + private: void OnCaptureSnapshot(); // Capture a snapshot of a view. This is used to allow an extension // to get a snapshot of a tab using chrome.tabs.captureVisibleTab(). - bool CaptureSnapshot(WebKit::WebView* view, SkBitmap* snapshot); + bool CaptureSnapshot(blink::WebView* view, SkBitmap* snapshot); + + void OnDocumentCallback(content::RenderViewImpl* rv, + const std::string& js_fn, + blink::WebFrame* frame); DISALLOW_COPY_AND_ASSIGN(NwRenderViewObserver); }; diff --git a/src/renderer/pepper_uma_host.cc b/src/renderer/pepper_uma_host.cc new file mode 100644 index 0000000000..b07add1447 --- /dev/null +++ b/src/renderer/pepper_uma_host.cc @@ -0,0 +1,208 @@ +// 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/renderer/pepper/pepper_uma_host.h" + +#include "base/metrics/histogram.h" +#include "base/sha1.h" +#include "base/strings/string_number_conversions.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/render_messages.h" +#include "chrome/renderer/chrome_content_renderer_client.h" +#include "content/public/renderer/pepper_plugin_instance.h" +#include "content/public/renderer/render_thread.h" +#include "content/public/renderer/renderer_ppapi_host.h" +#include "extensions/common/constants.h" +#include "extensions/common/extension.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/host/dispatch_host_message.h" +#include "ppapi/host/host_message_context.h" +#include "ppapi/host/ppapi_host.h" +#include "ppapi/proxy/ppapi_messages.h" + +#include "widevine_cdm_version.h" // In SHARED_INTERMEDIATE_DIR. + +namespace { + +const char* const kPredefinedAllowedUMAOrigins[] = { + "6EAED1924DB611B6EEF2A664BD077BE7EAD33B8F", // see http://crbug.com/317833 + "4EB74897CB187C7633357C2FE832E0AD6A44883A" // see http://crbug.com/317833 +}; + +const char* const kWhitelistedHistogramPrefixes[] = { + "22F67DA2061FFC4DC9A4974036348D9C38C22919" // see http://crbug.com/390221 +}; + +const char* const kWhitelistedPluginBaseNames[] = { +#if defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS) + kWidevineCdmAdapterFileName, // see http://crbug.com/368743 + // and http://crbug.com/410630 +#endif + "libpdf.so" // see http://crbug.com/405305 +}; + +std::string HashPrefix(const std::string& histogram) { + const std::string id_hash = + base::SHA1HashString(histogram.substr(0, histogram.find('.'))); + DCHECK_EQ(id_hash.length(), base::kSHA1Length); + return base::HexEncode(id_hash.c_str(), id_hash.length()); +} + +} // namespace + +PepperUMAHost::PepperUMAHost(content::RendererPpapiHost* host, + PP_Instance instance, + PP_Resource resource) + : ResourceHost(host->GetPpapiHost(), instance, resource), + document_url_(host->GetDocumentURL(instance)), + is_plugin_in_process_(host->IsRunningInProcess()) { + if (host->GetPluginInstance(instance)) { + plugin_base_name_ = + host->GetPluginInstance(instance)->GetModulePath().BaseName(); + } + + for (size_t i = 0; i < arraysize(kPredefinedAllowedUMAOrigins); ++i) + allowed_origins_.insert(kPredefinedAllowedUMAOrigins[i]); + for (size_t i = 0; i < arraysize(kWhitelistedHistogramPrefixes); ++i) + allowed_histogram_prefixes_.insert(kWhitelistedHistogramPrefixes[i]); + for (size_t i = 0; i < arraysize(kWhitelistedPluginBaseNames); ++i) + allowed_plugin_base_names_.insert(kWhitelistedPluginBaseNames[i]); +} + +PepperUMAHost::~PepperUMAHost() {} + +int32_t PepperUMAHost::OnResourceMessageReceived( + const IPC::Message& msg, + ppapi::host::HostMessageContext* context) { + PPAPI_BEGIN_MESSAGE_MAP(PepperUMAHost, msg) + PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_UMA_HistogramCustomTimes, + OnHistogramCustomTimes) + PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_UMA_HistogramCustomCounts, + OnHistogramCustomCounts) + PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_UMA_HistogramEnumeration, + OnHistogramEnumeration) + PPAPI_DISPATCH_HOST_RESOURCE_CALL_0( + PpapiHostMsg_UMA_IsCrashReportingEnabled, OnIsCrashReportingEnabled) + PPAPI_END_MESSAGE_MAP() + return PP_ERROR_FAILED; +} + +bool PepperUMAHost::IsPluginWhitelisted() { +#if defined(ENABLE_EXTENSIONS) + return true; +#else + return false; +#endif +} + +bool PepperUMAHost::IsHistogramAllowed(const std::string& histogram) { + if (is_plugin_in_process_ && histogram.find("NaCl.") == 0) { + return true; + } + + if (IsPluginWhitelisted() && + allowed_histogram_prefixes_.find(HashPrefix(histogram)) != + allowed_histogram_prefixes_.end()) { + return true; + } + + if (allowed_plugin_base_names_.find(plugin_base_name_.MaybeAsASCII()) != + allowed_plugin_base_names_.end()) { + return true; + } + + LOG(ERROR) << "Host or histogram name is not allowed to use the UMA API."; + return false; +} + +#define RETURN_IF_BAD_ARGS(_min, _max, _buckets) \ + do { \ + if (_min >= _max || _buckets <= 1) \ + return PP_ERROR_BADARGUMENT; \ + } while (0) + +int32_t PepperUMAHost::OnHistogramCustomTimes( + ppapi::host::HostMessageContext* context, + const std::string& name, + int64_t sample, + int64_t min, + int64_t max, + uint32_t bucket_count) { + if (!IsHistogramAllowed(name)) { + return PP_ERROR_NOACCESS; + } + RETURN_IF_BAD_ARGS(min, max, bucket_count); + + base::HistogramBase* counter = base::Histogram::FactoryTimeGet( + name, + base::TimeDelta::FromMilliseconds(min), + base::TimeDelta::FromMilliseconds(max), + bucket_count, + base::HistogramBase::kUmaTargetedHistogramFlag); + // The histogram can be NULL if it is constructed with bad arguments. Ignore + // that data for this API. An error message will be logged. + if (counter) + counter->AddTime(base::TimeDelta::FromMilliseconds(sample)); + return PP_OK; +} + +int32_t PepperUMAHost::OnHistogramCustomCounts( + ppapi::host::HostMessageContext* context, + const std::string& name, + int32_t sample, + int32_t min, + int32_t max, + uint32_t bucket_count) { + if (!IsHistogramAllowed(name)) { + return PP_ERROR_NOACCESS; + } + RETURN_IF_BAD_ARGS(min, max, bucket_count); + + base::HistogramBase* counter = base::Histogram::FactoryGet( + name, + min, + max, + bucket_count, + base::HistogramBase::kUmaTargetedHistogramFlag); + // The histogram can be NULL if it is constructed with bad arguments. Ignore + // that data for this API. An error message will be logged. + if (counter) + counter->Add(sample); + return PP_OK; +} + +int32_t PepperUMAHost::OnHistogramEnumeration( + ppapi::host::HostMessageContext* context, + const std::string& name, + int32_t sample, + int32_t boundary_value) { + if (!IsHistogramAllowed(name)) { + return PP_ERROR_NOACCESS; + } + RETURN_IF_BAD_ARGS(0, boundary_value, boundary_value + 1); + + base::HistogramBase* counter = base::LinearHistogram::FactoryGet( + name, + 1, + boundary_value, + boundary_value + 1, + base::HistogramBase::kUmaTargetedHistogramFlag); + // The histogram can be NULL if it is constructed with bad arguments. Ignore + // that data for this API. An error message will be logged. + if (counter) + counter->Add(sample); + return PP_OK; +} + +int32_t PepperUMAHost::OnIsCrashReportingEnabled( + ppapi::host::HostMessageContext* context) { + if (!IsPluginWhitelisted()) + return PP_ERROR_NOACCESS; + bool enabled = false; + content::RenderThread::Get()->Send( + new ChromeViewHostMsg_IsCrashReportingEnabled(&enabled)); + if (enabled) + return PP_OK; + return PP_ERROR_FAILED; +} diff --git a/src/renderer/prerenderer/prerenderer_client.cc b/src/renderer/prerenderer/prerenderer_client.cc index 162140994e..22d4d1bd5b 100644 --- a/src/renderer/prerenderer/prerenderer_client.cc +++ b/src/renderer/prerenderer/prerenderer_client.cc @@ -22,7 +22,7 @@ #include "base/logging.h" #include "content/public/renderer/render_view.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" +#include "third_party/WebKit/public/web/WebView.h" namespace prerender { @@ -37,7 +37,7 @@ PrerendererClient::~PrerendererClient() { } void PrerendererClient::willAddPrerender( - WebKit::WebPrerender* prerender) { + blink::WebPrerender* prerender) { } } // namespace prerender diff --git a/src/renderer/prerenderer/prerenderer_client.h b/src/renderer/prerenderer/prerenderer_client.h index 5ac9f310d7..ef9b98e99c 100644 --- a/src/renderer/prerenderer/prerenderer_client.h +++ b/src/renderer/prerenderer/prerenderer_client.h @@ -23,20 +23,20 @@ #include "base/compiler_specific.h" #include "content/public/renderer/render_view_observer.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebPrerendererClient.h" +#include "third_party/WebKit/public/web/WebPrerendererClient.h" namespace prerender { class PrerendererClient : public content::RenderViewObserver, - public WebKit::WebPrerendererClient { + public blink::WebPrerendererClient { public: explicit PrerendererClient(content::RenderView* render_view); private: - virtual ~PrerendererClient(); + ~PrerendererClient() final; - // Implements WebKit::WebPrerendererClient - virtual void willAddPrerender(WebKit::WebPrerender* prerender) OVERRIDE; + // Implements blink::WebPrerendererClient + virtual void willAddPrerender(blink::WebPrerender* prerender) override; }; } // namespace prerender diff --git a/src/renderer/printing/print_web_view_helper.cc b/src/renderer/printing/print_web_view_helper.cc index 5eed7cb6d5..8e7d48769f 100644 --- a/src/renderer/printing/print_web_view_helper.cc +++ b/src/renderer/printing/print_web_view_helper.cc @@ -7,65 +7,76 @@ #include #include "base/auto_reset.h" -#include "base/command_line.h" #include "base/json/json_writer.h" #include "base/logging.h" -#include "base/message_loop.h" +#include "base/message_loop/message_loop.h" #include "base/metrics/histogram.h" -#include "base/process_util.h" -#include "base/stringprintf.h" +#include "base/process/process_handle.h" #include "base/strings/string_number_conversions.h" -#include "base/utf_string_conversions.h" -#include "chrome/common/chrome_switches.h" -#include "chrome/common/print_messages.h" -#include "chrome/common/render_messages.h" -#include "chrome/renderer/prerender/prerender_helper.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "content/nw/src/common/print_messages.h" +//#include "chrome/grit/browser_resources.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 "grit/nw_resources.h" -//#include "grit/generated_resources.h" -#include "printing/metafile.h" -#include "printing/metafile_impl.h" +#include "net/base/escape.h" +#include "printing/pdf_metafile_skia.h" #include "printing/units.h" -#include "skia/ext/vector_platform_device_skia.h" -#include "third_party/WebKit/Source/Platform/chromium/public/WebSize.h" -#include "third_party/WebKit/Source/Platform/chromium/public/WebURLRequest.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebConsoleMessage.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebElement.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrameClient.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebPlugin.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebPluginDocument.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebPrintParams.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebPrintScalingOption.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebScriptSource.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebSettings.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebViewClient.h" -#include "ui/base/l10n/l10n_util.h" +#include "third_party/WebKit/public/platform/WebSize.h" +#include "third_party/WebKit/public/platform/WebURLRequest.h" +#include "third_party/WebKit/public/web/WebConsoleMessage.h" +#include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/public/web/WebElement.h" +#include "third_party/WebKit/public/web/WebFrameClient.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" +#include "third_party/WebKit/public/web/WebPlugin.h" +#include "third_party/WebKit/public/web/WebPluginDocument.h" +#include "third_party/WebKit/public/web/WebPrintParams.h" +#include "third_party/WebKit/public/web/WebPrintPresetOptions.h" +#include "third_party/WebKit/public/web/WebPrintScalingOption.h" +#include "third_party/WebKit/public/web/WebScriptSource.h" +#include "third_party/WebKit/public/web/WebSettings.h" +#include "third_party/WebKit/public/web/WebView.h" +#include "third_party/WebKit/public/web/WebViewClient.h" #include "ui/base/resource/resource_bundle.h" -#include "webkit/glue/webpreferences.h" + +using content::WebPreferences; namespace printing { namespace { +enum PrintPreviewHelperEvents { + PREVIEW_EVENT_REQUESTED, + PREVIEW_EVENT_CACHE_HIT, // Unused + PREVIEW_EVENT_CREATE_DOCUMENT, + PREVIEW_EVENT_NEW_SETTINGS, // Unused + PREVIEW_EVENT_MAX, +}; + const double kMinDpi = 1.0; +#if !defined(ENABLE_PRINT_PREVIEW) +bool g_is_preview_enabled_ = false; +#else +bool g_is_preview_enabled_ = true; + const char kPageLoadScriptFormat[] = "document.open(); document.write(%s); document.close();"; const char kPageSetupScriptFormat[] = "setup(%s);"; -void ExecuteScript(WebKit::WebFrame* frame, +void ExecuteScript(blink::WebFrame* frame, const char* script_format, const base::Value& parameters) { std::string json; base::JSONWriter::Write(¶meters, &json); - std::string script = StringPrintf(script_format, json.c_str()); - frame->executeScript(WebKit::WebString(UTF8ToUTF16(script))); + std::string script = base::StringPrintf(script_format, json.c_str()); + frame->executeScript(blink::WebString(base::UTF8ToUTF16(script))); } +#endif // !defined(ENABLE_PRINT_PREVIEW) int GetDPI(const PrintMsg_Print_Params* print_params) { #if defined(OS_MACOSX) @@ -81,52 +92,18 @@ bool PrintMsg_Print_Params_IsValid(const PrintMsg_Print_Params& params) { return !params.content_size.IsEmpty() && !params.page_size.IsEmpty() && !params.printable_area.IsEmpty() && params.document_cookie && params.desired_dpi && params.max_shrink && params.min_shrink && - params.dpi && (params.margin_top >= 0) && (params.margin_left >= 0); -} - -bool PageLayoutIsEqual(const PrintMsg_PrintPages_Params& oldParams, - const PrintMsg_PrintPages_Params& newParams) { - return oldParams.params.content_size == newParams.params.content_size && - oldParams.params.printable_area == newParams.params.printable_area && - oldParams.params.page_size == newParams.params.page_size && - oldParams.params.margin_top == newParams.params.margin_top && - oldParams.params.margin_left == newParams.params.margin_left && - oldParams.params.desired_dpi == newParams.params.desired_dpi && - oldParams.params.dpi == newParams.params.dpi; -} - -bool PrintMsg_Print_Params_IsEqual( - const PrintMsg_PrintPages_Params& oldParams, - const PrintMsg_PrintPages_Params& newParams) { - return PageLayoutIsEqual(oldParams, newParams) && - oldParams.params.max_shrink == newParams.params.max_shrink && - oldParams.params.min_shrink == newParams.params.min_shrink && - oldParams.params.selection_only == newParams.params.selection_only && - oldParams.params.supports_alpha_blend == - newParams.params.supports_alpha_blend && - oldParams.pages.size() == newParams.pages.size() && - oldParams.params.print_to_pdf == newParams.params.print_to_pdf && - oldParams.params.print_scaling_option == - newParams.params.print_scaling_option && - oldParams.params.display_header_footer == - newParams.params.display_header_footer && - oldParams.params.date == newParams.params.date && - oldParams.params.title == newParams.params.title && - oldParams.params.url == newParams.params.url && - std::equal(oldParams.pages.begin(), oldParams.pages.end(), - newParams.pages.begin()) && - oldParams.params.should_print_backgrounds == - newParams.params.should_print_backgrounds; + params.dpi && (params.margin_top >= 0) && (params.margin_left >= 0) && + params.dpi > kMinDpi && params.document_cookie != 0; } PrintMsg_Print_Params GetCssPrintParams( - WebKit::WebFrame* frame, + blink::WebFrame* frame, int page_index, const PrintMsg_Print_Params& page_params) { PrintMsg_Print_Params page_css_params = page_params; int dpi = GetDPI(&page_params); - WebKit::WebSize page_size_in_pixels( + blink::WebSize page_size_in_pixels( ConvertUnit(page_params.page_size.width(), dpi, kPixelsPerInch), ConvertUnit(page_params.page_size.height(), dpi, kPixelsPerInch)); int margin_top_in_pixels = @@ -143,7 +120,7 @@ PrintMsg_Print_Params GetCssPrintParams( page_params.margin_left, dpi, kPixelsPerInch); - WebKit::WebSize original_page_size_in_pixels = page_size_in_pixels; + blink::WebSize original_page_size_in_pixels = page_size_in_pixels; if (frame) { frame->pageSizeAndMarginsInPixels(page_index, @@ -270,7 +247,7 @@ void EnsureOrientationMatches(const PrintMsg_Print_Params& css_params, void ComputeWebKitPrintParamsInDesiredDpi( const PrintMsg_Print_Params& print_params, - WebKit::WebPrintParams* webkit_print_params) { + blink::WebPrintParams* webkit_print_params) { int dpi = GetDPI(&print_params); webkit_print_params->printerDPI = dpi; webkit_print_params->printScalingOption = print_params.print_scaling_option; @@ -303,18 +280,20 @@ void ComputeWebKitPrintParamsInDesiredDpi( print_params.desired_dpi); } -bool PrintingNodeOrPdfFrame(const WebKit::WebFrame* frame, - const WebKit::WebNode& node) { +blink::WebPlugin* GetPlugin(const blink::WebFrame* frame) { + return frame->document().isPluginDocument() ? + frame->document().to().plugin() : NULL; +} + +bool PrintingNodeOrPdfFrame(const blink::WebFrame* frame, + const blink::WebNode& node) { if (!node.isNull()) return true; - if (!frame->document().isPluginDocument()) - return false; - WebKit::WebPlugin* plugin = - frame->document().to().plugin(); + blink::WebPlugin* plugin = GetPlugin(frame); return plugin && plugin->supportsPaginatedPrint(); } -bool PrintingFrameHasPageSizeStyle(WebKit::WebFrame* frame, +bool PrintingFrameHasPageSizeStyle(blink::WebFrame* frame, int total_page_count) { if (!frame) return false; @@ -328,15 +307,15 @@ bool PrintingFrameHasPageSizeStyle(WebKit::WebFrame* frame, return frame_has_custom_page_size_style; } -MarginType GetMarginsForPdf(WebKit::WebFrame* frame, - const WebKit::WebNode& node) { +MarginType GetMarginsForPdf(blink::WebFrame* frame, + const blink::WebNode& node) { if (frame->isPrintScalingDisabledForPlugin(node)) return NO_MARGINS; else return PRINTABLE_AREA_MARGINS; } -bool FitToPageEnabled(const DictionaryValue& job_settings) { +bool FitToPageEnabled(const base::DictionaryValue& job_settings) { bool fit_to_paper_size = false; if (!job_settings.GetBoolean(kSettingFitToPageEnabled, &fit_to_paper_size)) { NOTREACHED(); @@ -344,8 +323,43 @@ bool FitToPageEnabled(const DictionaryValue& job_settings) { return fit_to_paper_size; } +// Returns the print scaling option to retain/scale/crop the source page size +// to fit the printable area of the paper. +// +// We retain the source page size when the current destination printer is +// SAVE_AS_PDF. +// +// We crop the source page size to fit the printable area or we print only the +// left top page contents when +// (1) Source is PDF and the user has requested not to fit to printable area +// via |job_settings|. +// (2) Source is PDF. This is the first preview request and print scaling +// option is disabled for initiator renderer plugin. +// +// In all other cases, we scale the source page to fit the printable area. +blink::WebPrintScalingOption GetPrintScalingOption( + blink::WebFrame* frame, + const blink::WebNode& node, + bool source_is_html, + const base::DictionaryValue& job_settings, + const PrintMsg_Print_Params& params) { + if (params.print_to_pdf) + return blink::WebPrintScalingOptionSourceSize; + + if (!source_is_html) { + if (!FitToPageEnabled(job_settings)) + return blink::WebPrintScalingOptionNone; + + bool no_plugin_scaling = frame->isPrintScalingDisabledForPlugin(node); + + if (params.is_first_request && no_plugin_scaling) + return blink::WebPrintScalingOptionNone; + } + return blink::WebPrintScalingOptionFitToPrintableArea; +} + PrintMsg_Print_Params CalculatePrintParamsForCss( - WebKit::WebFrame* frame, + blink::WebFrame* frame, int page_index, const PrintMsg_Print_Params& page_params, bool ignore_css_margins, @@ -387,80 +401,110 @@ PrintMsg_Print_Params CalculatePrintParamsForCss( return result_params; } -bool IsPrintPreviewEnabled() { - return CommandLine::ForCurrentProcess()->HasSwitch( - switches::kRendererPrintPreview); +} // namespace + +FrameReference::FrameReference(blink::WebLocalFrame* frame) { + Reset(frame); } -bool IsPrintThrottlingDisabled() { - return CommandLine::ForCurrentProcess()->HasSwitch( - switches::kDisableScriptedPrintThrottling); +FrameReference::FrameReference() { + Reset(NULL); } -} // namespace +FrameReference::~FrameReference() { +} +void FrameReference::Reset(blink::WebLocalFrame* frame) { + if (frame) { + view_ = frame->view(); + frame_ = frame; + } else { + view_ = NULL; + frame_ = NULL; + } +} + +blink::WebLocalFrame* FrameReference::GetFrame() { + if (view_ == NULL || frame_ == NULL) + return NULL; + for (blink::WebFrame* frame = view_->mainFrame(); frame != NULL; + frame = frame->traverseNext(false)) { + if (frame == frame_) + return frame_; + } + return NULL; +} + +blink::WebView* FrameReference::view() { + return view_; +} + +#if defined(ENABLE_PRINT_PREVIEW) // static - Not anonymous so that platform implementations can use it. void PrintWebViewHelper::PrintHeaderAndFooter( - WebKit::WebCanvas* canvas, + blink::WebCanvas* canvas, int page_number, int total_pages, + const blink::WebFrame& source_frame, float webkit_scale_factor, const PageSizeMargins& page_layout, - const DictionaryValue& header_footer_info, const PrintMsg_Print_Params& params) { - skia::VectorPlatformDeviceSkia* device = - static_cast(canvas->getTopDevice()); - device->setDrawingArea(SkPDFDevice::kMargin_DrawingArea); - SkAutoCanvasRestore auto_restore(canvas, true); canvas->scale(1 / webkit_scale_factor, 1 / webkit_scale_factor); - WebKit::WebSize page_size(page_layout.margin_left + page_layout.margin_right + - page_layout.content_width, - page_layout.margin_top + page_layout.margin_bottom + - page_layout.content_height); + blink::WebSize page_size(page_layout.margin_left + page_layout.margin_right + + page_layout.content_width, + page_layout.margin_top + page_layout.margin_bottom + + page_layout.content_height); - WebKit::WebView* web_view = WebKit::WebView::create(NULL); + blink::WebView* web_view = blink::WebView::create(NULL); web_view->settings()->setJavaScriptEnabled(true); - web_view->initializeMainFrame(NULL); - WebKit::WebFrame* frame = web_view->mainFrame(); + blink::WebLocalFrame* frame = blink::WebLocalFrame::create(NULL); + web_view->setMainFrame(frame); - base::StringValue html( - ResourceBundle::GetSharedInstance().GetLocalizedString( - IDR_PRINT_PREVIEW_PAGE)); + base::StringValue html(ResourceBundle::GetSharedInstance().GetLocalizedString( + IDR_PRINT_PREVIEW_PAGE)); // Load page with script to avoid async operations. ExecuteScript(frame, kPageLoadScriptFormat, html); - scoped_ptr options(header_footer_info.DeepCopy()); + scoped_ptr options(new base::DictionaryValue()); + options.reset(new base::DictionaryValue()); + options->SetDouble(kSettingHeaderFooterDate, base::Time::Now().ToJsTime()); options->SetDouble("width", page_size.width); options->SetDouble("height", page_size.height); options->SetDouble("topMargin", page_layout.margin_top); options->SetDouble("bottomMargin", page_layout.margin_bottom); options->SetString("pageNumber", - StringPrintf("%d/%d", page_number, total_pages)); + base::StringPrintf("%d/%d", page_number, total_pages)); + + // Fallback to initiator URL and title if it's empty for printed frame. + base::string16 url = source_frame.document().url().string(); + options->SetString("url", url.empty() ? params.url : url); + base::string16 title = source_frame.document().title(); + options->SetString("title", title.empty() ? params.title : title); ExecuteScript(frame, kPageSetupScriptFormat, *options); - WebKit::WebPrintParams webkit_params(page_size); + blink::WebPrintParams webkit_params(page_size); webkit_params.printerDPI = GetDPI(¶ms); - frame->printBegin(webkit_params, WebKit::WebNode(), NULL); + frame->printBegin(webkit_params); frame->printPage(0, canvas); frame->printEnd(); web_view->close(); - - device->setDrawingArea(SkPDFDevice::kContent_DrawingArea); + frame->close(); } +#endif // defined(ENABLE_PRINT_PREVIEW) // static - Not anonymous so that platform implementations can use it. -float PrintWebViewHelper::RenderPageContent(WebKit::WebFrame* frame, +float PrintWebViewHelper::RenderPageContent(blink::WebFrame* frame, int page_number, const gfx::Rect& canvas_area, const gfx::Rect& content_area, double scale_factor, - WebKit::WebCanvas* canvas) { + blink::WebCanvas* canvas) { SkAutoCanvasRestore auto_restore(canvas, true); if (content_area != canvas_area) { canvas->translate((content_area.x() - canvas_area.x()) / scale_factor, @@ -480,28 +524,28 @@ float PrintWebViewHelper::RenderPageContent(WebKit::WebFrame* frame, // Class that calls the Begin and End print functions on the frame and changes // the size of the view temporarily to support full page printing.. -class PrepareFrameAndViewForPrint : public WebKit::WebViewClient, - public WebKit::WebFrameClient { +class PrepareFrameAndViewForPrint : public blink::WebViewClient, + public blink::WebFrameClient { public: PrepareFrameAndViewForPrint(const PrintMsg_Print_Params& params, - WebKit::WebFrame* frame, - const WebKit::WebNode& node, + blink::WebLocalFrame* frame, + const blink::WebNode& node, bool ignore_css_margins); virtual ~PrepareFrameAndViewForPrint(); // Optional. Replaces |frame_| with selection if needed. Will call |on_ready| // when completed. - void CopySelectionIfNeeded(const webkit_glue::WebPreferences& preferences, + void CopySelectionIfNeeded(const WebPreferences& preferences, const base::Closure& on_ready); // Prepares frame for printing. void StartPrinting(); - WebKit::WebFrame* frame() const { - return frame_; + blink::WebLocalFrame* frame() { + return frame_.GetFrame(); } - const WebKit::WebNode& node() const { + const blink::WebNode& node() const { return node_to_print_; } @@ -509,32 +553,37 @@ class PrepareFrameAndViewForPrint : public WebKit::WebViewClient, return expected_pages_count_; } - gfx::Size GetPrintCanvasSize() const; - void FinishPrinting(); - bool IsLoadingSelection() const { + bool IsLoadingSelection() { // It's not selection if not |owns_web_view_|. - return owns_web_view_ && frame_ && frame_->isLoading(); + return owns_web_view_ && frame() && frame()->isLoading(); } + // TODO(ojan): Remove this override and have this class use a non-null + // layerTreeView. + // blink::WebViewClient override: + virtual bool allowsBrokenNullLayerTreeView() const; + protected: - // WebKit::WebViewClient override: + // blink::WebViewClient override: virtual void didStopLoading(); - virtual void CallOnReady(); + // blink::WebFrameClient override: + virtual blink::WebFrame* createChildFrame(blink::WebLocalFrame* parent, + const blink::WebString& name); + virtual void frameDetached(blink::WebFrame* frame); private: + void CallOnReady(); void ResizeForPrinting(); void RestoreSize(); - void CopySelection(const webkit_glue::WebPreferences& preferences); - - base::WeakPtrFactory weak_ptr_factory_; + void CopySelection(const WebPreferences& preferences); - WebKit::WebFrame* frame_; - WebKit::WebNode node_to_print_; + FrameReference frame_; + blink::WebNode node_to_print_; bool owns_web_view_; - WebKit::WebPrintParams web_print_params_; + blink::WebPrintParams web_print_params_; gfx::Size prev_view_size_; gfx::Size prev_scroll_offset_; int expected_pages_count_; @@ -543,33 +592,37 @@ class PrepareFrameAndViewForPrint : public WebKit::WebViewClient, bool should_print_selection_only_; bool is_printing_started_; + base::WeakPtrFactory weak_ptr_factory_; + DISALLOW_COPY_AND_ASSIGN(PrepareFrameAndViewForPrint); }; PrepareFrameAndViewForPrint::PrepareFrameAndViewForPrint( const PrintMsg_Print_Params& params, - WebKit::WebFrame* frame, - const WebKit::WebNode& node, + blink::WebLocalFrame* frame, + const blink::WebNode& node, bool ignore_css_margins) - : weak_ptr_factory_(this), - frame_(frame), + : frame_(frame), node_to_print_(node), owns_web_view_(false), expected_pages_count_(0), should_print_backgrounds_(params.should_print_backgrounds), should_print_selection_only_(params.selection_only), - is_printing_started_(false) { + is_printing_started_(false), + weak_ptr_factory_(this) { PrintMsg_Print_Params print_params = params; if (!should_print_selection_only_ || - !PrintingNodeOrPdfFrame(frame_, node_to_print_)) { + !PrintingNodeOrPdfFrame(frame, node_to_print_)) { bool fit_to_page = ignore_css_margins && print_params.print_scaling_option == - WebKit::WebPrintScalingOptionFitToPrintableArea; - print_params = CalculatePrintParamsForCss(frame_, 0, print_params, + blink::WebPrintScalingOptionFitToPrintableArea; + ComputeWebKitPrintParamsInDesiredDpi(params, &web_print_params_); + frame->printBegin(web_print_params_, node_to_print_); + print_params = CalculatePrintParamsForCss(frame, 0, print_params, ignore_css_margins, fit_to_page, NULL); + frame->printEnd(); } - ComputeWebKitPrintParamsInDesiredDpi(print_params, &web_print_params_); } @@ -588,13 +641,11 @@ void PrepareFrameAndViewForPrint::ResizeForPrinting() { print_layout_size.set_height( static_cast(static_cast(print_layout_size.height()) * 1.25)); - if (!frame_) + if (!frame()) return; - - WebKit::WebView* web_view = frame_->view(); - + blink::WebView* web_view = frame_.view(); // Backup size and offset. - if (WebKit::WebFrame* web_frame = web_view->mainFrame()) + if (blink::WebFrame* web_frame = web_view->mainFrame()) prev_scroll_offset_ = web_frame->scrollOffset(); prev_view_size_ = web_view->size(); @@ -604,160 +655,198 @@ void PrepareFrameAndViewForPrint::ResizeForPrinting() { void PrepareFrameAndViewForPrint::StartPrinting() { ResizeForPrinting(); - WebKit::WebView* web_view = frame_->view(); + blink::WebView* web_view = frame_.view(); web_view->settings()->setShouldPrintBackgrounds(should_print_backgrounds_); - // TODO(vitalybuka): Update call after - // https://bugs.webkit.org/show_bug.cgi?id=107718 is fixed. - expected_pages_count_ = frame_->printBegin(web_print_params_, node_to_print_, - NULL); + expected_pages_count_ = + frame()->printBegin(web_print_params_, node_to_print_); is_printing_started_ = true; } void PrepareFrameAndViewForPrint::CopySelectionIfNeeded( - const webkit_glue::WebPreferences& preferences, + const WebPreferences& preferences, const base::Closure& on_ready) { on_ready_ = on_ready; - if (should_print_selection_only_) + if (should_print_selection_only_) { CopySelection(preferences); - else - didStopLoading(); + } else { + // Call immediately, async call crashes scripting printing. + CallOnReady(); + } } void PrepareFrameAndViewForPrint::CopySelection( - const webkit_glue::WebPreferences& preferences) { + const WebPreferences& preferences) { ResizeForPrinting(); std::string url_str = "data:text/html;charset=utf-8,"; - url_str.append(frame_->selectionAsMarkup().utf8()); + url_str.append( + net::EscapeQueryParamValue(frame()->selectionAsMarkup().utf8(), false)); RestoreSize(); // Create a new WebView with the same settings as the current display one. // Except that we disable javascript (don't want any active content running // on the page). - webkit_glue::WebPreferences prefs = preferences; + WebPreferences prefs = preferences; prefs.javascript_enabled = false; prefs.java_enabled = false; - WebKit::WebView* web_view = WebKit::WebView::create(this); + blink::WebView* web_view = blink::WebView::create(this); owns_web_view_ = true; - prefs.Apply(web_view); - web_view->initializeMainFrame(this); - frame_ = web_view->mainFrame(); + content::RenderView::ApplyWebPreferences(prefs, web_view); + web_view->setMainFrame(blink::WebLocalFrame::create(this)); + frame_.Reset(web_view->mainFrame()->toWebLocalFrame()); node_to_print_.reset(); // When loading is done this will call didStopLoading() and that will do the // actual printing. - frame_->loadRequest(WebKit::WebURLRequest(GURL(url_str))); + frame()->loadRequest(blink::WebURLRequest(GURL(url_str))); +} + +bool PrepareFrameAndViewForPrint::allowsBrokenNullLayerTreeView() const { + return true; } void PrepareFrameAndViewForPrint::didStopLoading() { DCHECK(!on_ready_.is_null()); // Don't call callback here, because it can delete |this| and WebView that is // called didStopLoading. - MessageLoop::current()->PostTask(FROM_HERE, + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&PrepareFrameAndViewForPrint::CallOnReady, weak_ptr_factory_.GetWeakPtr())); } -void PrepareFrameAndViewForPrint::CallOnReady() { - return on_ready_.Run(); // Can delete |this|. +blink::WebFrame* PrepareFrameAndViewForPrint::createChildFrame( + blink::WebLocalFrame* parent, + const blink::WebString& name) { + blink::WebFrame* frame = blink::WebLocalFrame::create(this); + parent->appendChild(frame); + return frame; } -gfx::Size PrepareFrameAndViewForPrint::GetPrintCanvasSize() const { - return gfx::Size(web_print_params_.printContentArea.width, - web_print_params_.printContentArea.height); +void PrepareFrameAndViewForPrint::frameDetached(blink::WebFrame* frame) { + if (frame->parent()) + frame->parent()->removeChild(frame); + frame->close(); +} + +void PrepareFrameAndViewForPrint::CallOnReady() { + return on_ready_.Run(); // Can delete |this|. } void PrepareFrameAndViewForPrint::RestoreSize() { - if (frame_) { - WebKit::WebView* web_view = frame_->view(); + if (frame()) { + blink::WebView* web_view = frame_.GetFrame()->view(); web_view->resize(prev_view_size_); - if (WebKit::WebFrame* web_frame = web_view->mainFrame()) + if (blink::WebFrame* web_frame = web_view->mainFrame()) web_frame->setScrollOffset(prev_scroll_offset_); } } void PrepareFrameAndViewForPrint::FinishPrinting() { - if (frame_) { - WebKit::WebView* web_view = frame_->view(); + blink::WebLocalFrame* frame = frame_.GetFrame(); + if (frame) { + blink::WebView* web_view = frame->view(); if (is_printing_started_) { is_printing_started_ = false; - frame_->printEnd(); + frame->printEnd(); if (!owns_web_view_) { web_view->settings()->setShouldPrintBackgrounds(false); RestoreSize(); } } if (owns_web_view_) { - DCHECK(!frame_->isLoading()); + DCHECK(!frame->isLoading()); owns_web_view_ = false; web_view->close(); } } - frame_ = NULL; + frame_.Reset(NULL); on_ready_.Reset(); } -PrintWebViewHelper::PrintWebViewHelper(content::RenderView* render_view) +PrintWebViewHelper::PrintWebViewHelper( + content::RenderView* render_view, + bool out_of_process_pdf_enabled, + bool print_preview_disabled, + scoped_ptr delegate) : content::RenderViewObserver(render_view), content::RenderViewObserverTracker(render_view), reset_prep_frame_view_(false), - is_preview_enabled_(IsPrintPreviewEnabled()), - is_scripted_print_throttling_disabled_(IsPrintThrottlingDisabled()), is_print_ready_metafile_sent_(false), ignore_css_margins_(false), - user_cancelled_scripted_print_count_(0), is_scripted_printing_blocked_(false), notify_browser_of_print_failure_(true), print_for_preview_(false), - print_node_in_progress_(false) { + out_of_process_pdf_enabled_(out_of_process_pdf_enabled), + delegate_(delegate.Pass()), + print_node_in_progress_(false), + is_loading_(false), + is_scripted_preview_delayed_(false), + weak_ptr_factory_(this) { + if (print_preview_disabled) + DisablePreview(); } PrintWebViewHelper::~PrintWebViewHelper() {} +// static +void PrintWebViewHelper::DisablePreview() { + g_is_preview_enabled_ = false; +} + bool PrintWebViewHelper::IsScriptInitiatedPrintAllowed( - WebKit::WebFrame* frame, bool user_initiated) { - if (is_scripted_printing_blocked_) - return false; + blink::WebFrame* frame, bool user_initiated) { // If preview is enabled, then the print dialog is tab modal, and the user // can always close the tab on a mis-behaving page (the system print dialog // is app modal). If the print was initiated through user action, don't // throttle. Or, if the command line flag to skip throttling has been set. - if (!is_scripted_print_throttling_disabled_ && - !is_preview_enabled_ && - !user_initiated) - return !IsScriptInitiatedPrintTooFrequent(frame); - return true; + return !is_scripted_printing_blocked_ && + (user_initiated || g_is_preview_enabled_ || + scripting_throttler_.IsAllowed(frame)); +} + +void PrintWebViewHelper::DidStartLoading() { + is_loading_ = true; +} + +void PrintWebViewHelper::DidStopLoading() { + is_loading_ = false; + if (!on_stop_loading_closure_.is_null()) { + on_stop_loading_closure_.Run(); + on_stop_loading_closure_.Reset(); + } } // Prints |frame| which called window.print(). -void PrintWebViewHelper::PrintPage(WebKit::WebFrame* frame, +void PrintWebViewHelper::PrintPage(blink::WebLocalFrame* frame, bool user_initiated) { DCHECK(frame); + // Allow Prerendering to cancel this print request if necessary. + if (delegate_ && delegate_->CancelPrerender(render_view(), routing_id())) + return; + if (!IsScriptInitiatedPrintAllowed(frame, user_initiated)) return; - IncrementScriptedPrintCount(); - if (is_preview_enabled_) { + if (!g_is_preview_enabled_) { + Print(frame, blink::WebNode(), true); + } else { print_preview_context_.InitWithFrame(frame); RequestPrintPreview(PRINT_PREVIEW_SCRIPTED); - } else { - Print(frame, WebKit::WebNode()); } } bool PrintWebViewHelper::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(PrintWebViewHelper, message) +#if defined(ENABLE_BASIC_PRINTING) IPC_MESSAGE_HANDLER(PrintMsg_PrintPages, OnPrintPages) IPC_MESSAGE_HANDLER(PrintMsg_PrintForSystemDialog, OnPrintForSystemDialog) +#endif // ENABLE_BASIC_PRINTING IPC_MESSAGE_HANDLER(PrintMsg_InitiatePrintPreview, OnInitiatePrintPreview) - IPC_MESSAGE_HANDLER(PrintMsg_PrintNodeUnderContextMenu, - OnPrintNodeUnderContextMenu) IPC_MESSAGE_HANDLER(PrintMsg_PrintPreview, OnPrintPreview) IPC_MESSAGE_HANDLER(PrintMsg_PrintForPrintPreview, OnPrintForPrintPreview) IPC_MESSAGE_HANDLER(PrintMsg_PrintingDone, OnPrintingDone) - IPC_MESSAGE_HANDLER(PrintMsg_ResetScriptedPrintCount, - ResetScriptedPrintCount) IPC_MESSAGE_HANDLER(PrintMsg_SetScriptedPrintingBlocked, SetScriptedPrintBlocked) IPC_MESSAGE_UNHANDLED(handled = false) @@ -766,33 +855,45 @@ bool PrintWebViewHelper::OnMessageReceived(const IPC::Message& message) { } void PrintWebViewHelper::OnPrintForPrintPreview( - const DictionaryValue& job_settings) { - DCHECK(is_preview_enabled_); + const base::DictionaryValue& job_settings) { // If still not finished with earlier print request simply ignore. if (prep_frame_view_) return; if (!render_view()->GetWebView()) return; - WebKit::WebFrame* main_frame = render_view()->GetWebView()->mainFrame(); + blink::WebFrame* main_frame = render_view()->GetWebView()->mainFrame(); if (!main_frame) return; - WebKit::WebDocument document = main_frame->document(); - // with id="pdf-viewer" is created in + blink::WebDocument document = main_frame->document(); + // / + + + \ No newline at end of file diff --git a/tests/automatic_tests/crashonreload/mocha_test.js b/tests/automatic_tests/crashonreload/mocha_test.js new file mode 100644 index 0000000000..5b840fb287 --- /dev/null +++ b/tests/automatic_tests/crashonreload/mocha_test.js @@ -0,0 +1,23 @@ +var spawn = require('child_process').spawn; +var path = require('path'); +var app_test = require('./nw_test_app'); +describe('crash on reload',function(){ + it('nw should not crash when reloading with devtool opened',function(done){ + this.timeout(0); + var result = false; + var exec_argv = [path.join(global.tests_dir, 'crashonreload')]; + + var app = spawn(process.execPath, exec_argv); + app.on('exit', function (code){ + if (code != 0) return done('nw crashes'); + result = true; + done(); + }); + setTimeout(function(){ + if (!result) { + app.kill(); + done(); + } + }, 7500); + }) +}) \ No newline at end of file diff --git a/tests/automatic_tests/crashonreload/package.json b/tests/automatic_tests/crashonreload/package.json new file mode 100644 index 0000000000..c04044da00 --- /dev/null +++ b/tests/automatic_tests/crashonreload/package.json @@ -0,0 +1,4 @@ +{ + "name":"dev tools", + "main":"index.html" +} \ No newline at end of file diff --git a/tests/automatic_tests/crashonreload/test.html b/tests/automatic_tests/crashonreload/test.html new file mode 100644 index 0000000000..c72e1692d8 --- /dev/null +++ b/tests/automatic_tests/crashonreload/test.html @@ -0,0 +1,9 @@ + + + +test dev tools + + +hello world + + \ No newline at end of file diff --git a/tests/automatic_tests/datapath/datacash-path/index.html b/tests/automatic_tests/datapath/datacash-path/index.html new file mode 100644 index 0000000000..2dd250d278 --- /dev/null +++ b/tests/automatic_tests/datapath/datacash-path/index.html @@ -0,0 +1,20 @@ + + +testing data path + + +

Testing data path

+the data path should be set as + + + \ No newline at end of file diff --git a/tests/automatic_tests/datapath/datacash-path/package.json b/tests/automatic_tests/datapath/datacash-path/package.json new file mode 100644 index 0000000000..56305d8d64 --- /dev/null +++ b/tests/automatic_tests/datapath/datacash-path/package.json @@ -0,0 +1,5 @@ +{ + "name": "data path", + "main": "index.html", + "chromium-args": "--data-path='./data-cash/dataPath/'" +} diff --git a/tests/automatic_tests/datapath/datacash/index.html b/tests/automatic_tests/datapath/datacash/index.html new file mode 100644 index 0000000000..2dd250d278 --- /dev/null +++ b/tests/automatic_tests/datapath/datacash/index.html @@ -0,0 +1,20 @@ + + +testing data path + + +

Testing data path

+the data path should be set as + + + \ No newline at end of file diff --git a/tests/automatic_tests/datapath/datacash/package.json b/tests/automatic_tests/datapath/datacash/package.json new file mode 100644 index 0000000000..3551a65874 --- /dev/null +++ b/tests/automatic_tests/datapath/datacash/package.json @@ -0,0 +1,5 @@ +{ + "name": "data path", + "main": "index.html", + "chromium-args": "--data-path='./data-cash/'" +} diff --git a/tests/automatic_tests/datapath/datapath-cash/index.html b/tests/automatic_tests/datapath/datapath-cash/index.html new file mode 100644 index 0000000000..2dd250d278 --- /dev/null +++ b/tests/automatic_tests/datapath/datapath-cash/index.html @@ -0,0 +1,20 @@ + + +testing data path + + +

Testing data path

+the data path should be set as + + + \ No newline at end of file diff --git a/tests/automatic_tests/datapath/datapath-cash/package.json b/tests/automatic_tests/datapath/datapath-cash/package.json new file mode 100644 index 0000000000..93f8b5f9d6 --- /dev/null +++ b/tests/automatic_tests/datapath/datapath-cash/package.json @@ -0,0 +1,5 @@ +{ + "name": "data path", + "main": "index.html", + "chromium-args": "--data-path='./dataPath/data-cash/'" +} diff --git a/tests/automatic_tests/datapath/datapath/index.html b/tests/automatic_tests/datapath/datapath/index.html new file mode 100644 index 0000000000..2dd250d278 --- /dev/null +++ b/tests/automatic_tests/datapath/datapath/index.html @@ -0,0 +1,20 @@ + + +testing data path + + +

Testing data path

+the data path should be set as + + + \ No newline at end of file diff --git a/tests/automatic_tests/datapath/datapath/package.json b/tests/automatic_tests/datapath/datapath/package.json new file mode 100644 index 0000000000..7843ddcba3 --- /dev/null +++ b/tests/automatic_tests/datapath/datapath/package.json @@ -0,0 +1,5 @@ +{ + "name": "data path", + "main": "index.html", + "chromium-args": "--data-path='./dataPath/'" +} diff --git a/tests/automatic_tests/datapath/mocha_test.js b/tests/automatic_tests/datapath/mocha_test.js new file mode 100644 index 0000000000..fe5ac2a4bc --- /dev/null +++ b/tests/automatic_tests/datapath/mocha_test.js @@ -0,0 +1,74 @@ +var path = require('path'); +var app_test = require('./nw_test_app'); +var fs = require('fs-extra'); +describe('data-path', function() { + before(function(done){ + this.timeout(0); + fs.mkdirsSync('./data-cash'); + fs.mkdirsSync('./dataPath'); + fs.mkdirsSync('./dataPath/data-cash'); + fs.mkdirsSync('./data-cash/dataPath'); + done(); + }) + + after(function() { + setTimeout(function() { + fs.remove('./data-cash'); + fs.remove('./dataPath'); + fs.remove('./dataPath/data-cash'); + fs.remove('./data-cash/dataPath'); + }, 1000); + }) + + it('setting datapath as ./data-cash/ should pass', + function(done) { + this.timeout(0); + var child = app_test.createChildProcess({ + execPath: process.execPath, + appPath: path.join(global.tests_dir, 'datapath/datacash'), + end: function(data, app) { + app.kill(); + done(); + } + }); + }) + it('setting datapath as ./dataPath/ should pass', + function(done) { + this.timeout(0); + + var child = app_test.createChildProcess({ + execPath: process.execPath, + appPath: path.join(global.tests_dir, 'datapath/datapath'), + end: function(data, app) { + app.kill(); + done(); + } + }); + }) + it('setting datapath as ./dataPath/data-cash/ should pass', + function(done) { + this.timeout(0); + + var child = app_test.createChildProcess({ + execPath: process.execPath, + appPath: path.join(global.tests_dir, 'datapath/datapath-cash'), + end: function(data, app) { + app.kill(); + done(); + } + }); + }) + it('setting datapath as ./data-cash/dataPath/ should pass', + function(done) { + this.timeout(0); + + var child = app_test.createChildProcess({ + execPath: process.execPath, + appPath: path.join(global.tests_dir, 'datapath/datacash-path'), + end: function(data, app) { + app.kill(); + done(); + } + }); + }) +}) diff --git a/tests/automatic_tests/document_cookies/app/index.html b/tests/automatic_tests/document_cookies/app/index.html new file mode 100644 index 0000000000..25f3a00e32 --- /dev/null +++ b/tests/automatic_tests/document_cookies/app/index.html @@ -0,0 +1,53 @@ + + + + + + + + diff --git a/tests/automatic_tests/document_cookies/app/package.json b/tests/automatic_tests/document_cookies/app/package.json new file mode 100644 index 0000000000..4a73bfc451 --- /dev/null +++ b/tests/automatic_tests/document_cookies/app/package.json @@ -0,0 +1,7 @@ +{ + "name": "nw", + "main": "app://whatever/index.html", + "window": { + "show": false + } +} diff --git a/tests/automatic_tests/document_cookies/file/index.html b/tests/automatic_tests/document_cookies/file/index.html new file mode 100644 index 0000000000..25f3a00e32 --- /dev/null +++ b/tests/automatic_tests/document_cookies/file/index.html @@ -0,0 +1,53 @@ + + + + + + + + diff --git a/tests/automatic_tests/document_cookies/file/package.json b/tests/automatic_tests/document_cookies/file/package.json new file mode 100644 index 0000000000..18d312afa6 --- /dev/null +++ b/tests/automatic_tests/document_cookies/file/package.json @@ -0,0 +1,7 @@ +{ + "name": "nw", + "main": "index.html", + "window": { + "show": false + } +} diff --git a/tests/automatic_tests/document_cookies/mocha_test.js b/tests/automatic_tests/document_cookies/mocha_test.js new file mode 100644 index 0000000000..c06b748823 --- /dev/null +++ b/tests/automatic_tests/document_cookies/mocha_test.js @@ -0,0 +1,63 @@ +var path = require('path'); +var app_test = require('./nw_test_app'); +var assert = require('assert'); +var gui = require('nw.gui'); +var results = new Array(); +describe('document.cookies', function() { + + describe('http', function() { + before(function(done) { + this.timeout(0); + var url = "http://127.0.0.1:8123/document_cookies.html"; + var win = gui.Window.open(url); + + setTimeout(function() { + results.push(win.window.msg.textContent); + done(); + win.window.close(); + }, 1000); + }); + it('should be set', function() { + assert.equal(results[0], '123'); + }); + }); + + describe('file', function() { + before(function(done) { + this.timeout(0); + var child = app_test.createChildProcess({ + execPath: process.execPath, + appPath: path.join(global.tests_dir, 'document_cookies', 'file'), + end: function(data, app) { + results.push(data); + app.kill(); + done(); + } + }); + setTimeout(done, 3000); + }); + it ('should be set', function() { + assert.equal(results[1], '123'); + }); + }); + + describe('app', function() { + before(function(done) { + this.timeout(0); + var child = app_test.createChildProcess({ + execPath: process.execPath, + appPath: path.join(global.tests_dir, 'document_cookies', 'app'), + end: function(data, app) { + results.push(data); + app.kill(); + done(); + } + }); + setTimeout(done, 3000); + }); + it ('should be set', function() { + assert.equal(results[2], '123'); + }); + }); + +}); diff --git a/tests/automatic_tests/fast_open_and_close_devtools/index.html b/tests/automatic_tests/fast_open_and_close_devtools/index.html new file mode 100644 index 0000000000..8a54574ce4 --- /dev/null +++ b/tests/automatic_tests/fast_open_and_close_devtools/index.html @@ -0,0 +1,17 @@ + + + + + Test Case For Fast Open&Close Devtools Crashes + + + + + diff --git a/tests/automatic_tests/fast_open_and_close_devtools/mocha_test.js b/tests/automatic_tests/fast_open_and_close_devtools/mocha_test.js new file mode 100644 index 0000000000..58e2fdc298 --- /dev/null +++ b/tests/automatic_tests/fast_open_and_close_devtools/mocha_test.js @@ -0,0 +1,19 @@ +var path = require('path'); +var assert = require('assert'); +var spawn = require('child_process').spawn; +var result = false; +describe('Fast open&close devtools from #1391', function() { + before(function(done) { + this.timeout(0); + var app = spawn(process.execPath, [path.join(global.tests_dir, 'fast_open_and_close_devtools')]); + app.on('exit', function(code) { + if (code != null) + result = true; + done(); + }); + }); + + it('should not crash', function() { + assert.equal(result, true); + }); +}); diff --git a/tests/automatic_tests/fast_open_and_close_devtools/package.json b/tests/automatic_tests/fast_open_and_close_devtools/package.json new file mode 100644 index 0000000000..fedeac13b7 --- /dev/null +++ b/tests/automatic_tests/fast_open_and_close_devtools/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw", + "main": "index.html" +} diff --git a/tests/automatic_tests/filevalue/index.html b/tests/automatic_tests/filevalue/index.html new file mode 100644 index 0000000000..c3b959a68c --- /dev/null +++ b/tests/automatic_tests/filevalue/index.html @@ -0,0 +1,34 @@ + + + + + test setting file input value + + + + + + + \ No newline at end of file diff --git a/tests/automatic_tests/filevalue/mocha_test.js b/tests/automatic_tests/filevalue/mocha_test.js new file mode 100644 index 0000000000..c3ecaa54f1 --- /dev/null +++ b/tests/automatic_tests/filevalue/mocha_test.js @@ -0,0 +1,21 @@ +var app_test = require('./nw_test_app'); +var path = require('path'); + +describe('file input',function(){ + describe('set value',function(){ + it('the value should be set without exception',function(done){ + this.timeout(0); + var child = app_test.createChildProcess({ + execPath : process.execPath, + appPath : path.join(global.tests_dir, 'filevalue'), + end: function(data, app){ + app.kill(); + if (data == 'mytestfile') + done(); + else + done('the value of the file input is not set correctly'); + } + }) + }) + }) +}) \ No newline at end of file diff --git a/tests/automatic_tests/filevalue/package.json b/tests/automatic_tests/filevalue/package.json new file mode 100644 index 0000000000..602dd5d3e4 --- /dev/null +++ b/tests/automatic_tests/filevalue/package.json @@ -0,0 +1,4 @@ +{ + "name":"nw-file-input-value", + "main":"index.html" +} \ No newline at end of file diff --git a/tests/automatic_tests/filevalue/testfile b/tests/automatic_tests/filevalue/testfile new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/automatic_tests/inject-js/end.js b/tests/automatic_tests/inject-js/end.js new file mode 100644 index 0000000000..613f42bcdb --- /dev/null +++ b/tests/automatic_tests/inject-js/end.js @@ -0,0 +1,2 @@ +var result = result ||[]; +result.push('inject-js-end'); \ No newline at end of file diff --git a/tests/automatic_tests/inject-js/index.html b/tests/automatic_tests/inject-js/index.html new file mode 100644 index 0000000000..0385f50489 --- /dev/null +++ b/tests/automatic_tests/inject-js/index.html @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tests/automatic_tests/inject-js/mocha_test.js b/tests/automatic_tests/inject-js/mocha_test.js new file mode 100644 index 0000000000..cd353d30c3 --- /dev/null +++ b/tests/automatic_tests/inject-js/mocha_test.js @@ -0,0 +1,59 @@ +var path = require('path'); +var spawn = require('child_process').spawn; +var server = global.server; +var server_port = global.port; +var assert = require('assert'); + + + +describe('inject-js',function(){ + var result = null; + var child = null; + + var connection_handler_callback = undefined; + var connection_handler = function(socket){ + socket.on('data',function(data){ + result = JSON.parse(data); + if (typeof connection_handler_callback === 'function'){ + (connection_handler_callback)(); + } + }); + }; + + before(function(done){ + this.timeout(0); + var app_path = path.join(global.tests_dir,'inject-js'); + var exec_argv = [app_path,server_port]; + child = spawn(process.execPath,exec_argv); + server.on('connection',connection_handler); + connection_handler_callback = done; + }); + + after(function(done){ + server.removeListener('connection',connection_handler); + child.kill(); + done(); + }); + + it('result.length should equal 4',function(done){ + assert.notEqual(result,null); + assert.equal(result.length,4); + done(); + }); + it('inject-js-start should run first',function(done){ + assert.equal(result[0],'inject-js-start'); + done(); + }); + it('document script should run after inject-js-start',function(done){ + assert.equal(result[1],'script-start'); + done(); + }); + it('inject-js-end should before window.onload',function(done){ + assert.equal(result[2],'inject-js-end'); + done(); + }); + it('inject-js-start should run last',function(done){ + assert.equal(result[3],'onload'); + done(); + }); +}); \ No newline at end of file diff --git a/tests/automatic_tests/inject-js/package.json b/tests/automatic_tests/inject-js/package.json new file mode 100644 index 0000000000..d36df79c46 --- /dev/null +++ b/tests/automatic_tests/inject-js/package.json @@ -0,0 +1,6 @@ +{ + "name":"nw_1403514049", + "main":"index.html", + "inject-js-end":"./end.js", + "inject-js-start":"./start.js" +} \ No newline at end of file diff --git a/tests/automatic_tests/inject-js/start.js b/tests/automatic_tests/inject-js/start.js new file mode 100644 index 0000000000..5dd62d1045 --- /dev/null +++ b/tests/automatic_tests/inject-js/start.js @@ -0,0 +1,6 @@ +var result = result ||[]; +result.push('inject-js-start'); + +window.onload = function(){ + result.push('onload') +}; \ No newline at end of file diff --git a/tests/automatic_tests/node-main/mocha_test.js b/tests/automatic_tests/node-main/mocha_test.js index 6ca189d9d7..dacc98477e 100644 --- a/tests/automatic_tests/node-main/mocha_test.js +++ b/tests/automatic_tests/node-main/mocha_test.js @@ -1,5 +1,6 @@ var path = require('path'); var app_test = require('./nw_test_app'); +var assert = require('assert'); describe('node-main', function() { describe('create http server in node-main', function() { @@ -31,7 +32,7 @@ describe('node-main', function() { //child.app.stderr.on('data', function(d){ console.log ('app' + d);}); }) - }) + }); describe('call require() in app', function() { it('nw should can require modules', function(done){ @@ -50,6 +51,22 @@ describe('node-main', function() { } }); }) - }) + }); + + describe('reference node-main module',function(){ + it('nw should be able to reference node-main module',function(done){ + this.timeout(0); + + var child = app_test.createChildProcess({ + execPath: process.execPath, + appPath: path.join(global.tests_dir, 'reference-node-main'), + end: function(data, app) { + app.kill(); + assert.equal(data,true); + done(); + } + }); + }); + }); }) diff --git a/tests/automatic_tests/node-remote/index.html b/tests/automatic_tests/node-remote/index.html index c41812c387..4ec84c0284 100644 --- a/tests/automatic_tests/node-remote/index.html +++ b/tests/automatic_tests/node-remote/index.html @@ -44,27 +44,27 @@ } if (!program.auto) { - connectToRemotePage('http://127.0.0.1/node_remote_test.html', 'msg80'); - connectToRemotePage('http://127.0.0.1:8080/node_remote_test.html', 'msg8080'); + connectToRemotePage('http://127.0.0.1:8123/node_remote_test.html', 'msg8123'); + connectToRemotePage('http://127.0.0.1:8124/node_remote_test.html', 'msg8124'); } else { var client; client = net.connect({port: port}); client.setEncoding('utf8'); client.on('data', function(data) { - if (data == '80') { - connectToRemotePage('http://127.0.0.1/node_remote_test.html', - 'msg80', function(result){ + if (data == '8123') { + connectToRemotePage('http://127.0.0.1:8123/node_remote_test.html', + 'msg8123', function(result){ client.write(result); }); - }//if (data == '80') + }//if (data == '8123') - if (data == '8080') { - connectToRemotePage('http://127.0.0.1:8080/node_remote_test.html', - 'msg8080', function(result){ + if (data == '8124') { + connectToRemotePage('http://127.0.0.1:8124/node_remote_test.html', + 'msg8124', function(result){ client.write(result); }); - }//if (data == '80') + }//if (data == '8124') }); } @@ -77,16 +77,16 @@ diff --git a/tests/automatic_tests/node-remote/mocha_test.js b/tests/automatic_tests/node-remote/mocha_test.js index aae6846c8a..737c129899 100644 --- a/tests/automatic_tests/node-remote/mocha_test.js +++ b/tests/automatic_tests/node-remote/mocha_test.js @@ -5,7 +5,7 @@ var server = global.server; var cb; describe('node-remote', function() { - describe('enable remote site http://127.0.0.1:80/', function() { + describe('enable remote site http://127.0.0.1:8123/', function() { var app; var exec_argv; var socket; @@ -39,7 +39,7 @@ describe('node-remote', function() { socket.removeAllListeners('data'); }) - it('http://127.0.0.1/node_remote_test.html should be able call Node', + it('http://127.0.0.1:8123/node_remote_test.html should be able call Node', function(done) { this.timeout(0); socket.on('data', function(data) { @@ -50,10 +50,10 @@ describe('node-remote', function() { } }); - socket.write('80'); + socket.write('8123'); }) - it('http://127.0.0.1:8080/node_remote_test.html should not be able call Node', + it('http://127.0.0.1:8124/node_remote_test.html should not be able call Node', function(done) { this.timeout(0); socket.on('data', function(data) { @@ -64,7 +64,7 @@ describe('node-remote', function() { } }); - socket.write('8080'); + socket.write('8124'); }) }) }) diff --git a/tests/automatic_tests/node-remote/package.json b/tests/automatic_tests/node-remote/package.json index df8a66c2cf..e79fa87ac3 100644 --- a/tests/automatic_tests/node-remote/package.json +++ b/tests/automatic_tests/node-remote/package.json @@ -2,7 +2,7 @@ "name": "nw-demo", "main": "index.html", "user-agent": "%name-a-b", - "node-remote": "127.0.0.1:80", + "node-remote": "127.0.0.1:8123", "version": "1.0", "window": { "show" : false } } diff --git a/tests/automatic_tests/node/mocha_test.js b/tests/automatic_tests/node/mocha_test.js index e32b8a10e9..e54ec13c7e 100644 --- a/tests/automatic_tests/node/mocha_test.js +++ b/tests/automatic_tests/node/mocha_test.js @@ -32,10 +32,14 @@ describe('require', function() { describe('node', function() { + var mocha_callback = null; before(function() { - var mocha_callback = process.listeners('uncaughtException')[1]; + mocha_callback = process.listeners('uncaughtException')[1]; process.removeListener('uncaughtException', mocha_callback); }); + after(function(){ + process.on('uncaughtException',mocha_callback); + }) describe('process', function() { it('uncaughtException should have a default listener', function() { @@ -139,7 +143,13 @@ describe('module', function() { }); it('native modules without handle scope', function() { - require('./node_modules/nw_test_loop_without_handle'); + require('./node_modules/nw_test_loop_without_handle'); + }); + + it('native modules should work', function() { + var nativeModules = new Array("dtrace-provider", "ref", "lame"); + for (var i = 0; i < nativeModules.length; i++) + assert.equal((typeof require(nativeModules[i])), "object"); }); }); }); diff --git a/tests/automatic_tests/nw-in-mem/mocha_test.js b/tests/automatic_tests/nw-in-mem/mocha_test.js new file mode 100644 index 0000000000..3a0f57fce2 --- /dev/null +++ b/tests/automatic_tests/nw-in-mem/mocha_test.js @@ -0,0 +1,33 @@ +var os = require('os'); +var path = require('path'); +var exec = require('child_process').exec; +var spawn = require('child_process').spawn; +describe('nw in memory after quiting',function(){ + it('nw.exe should not be in memory after gui.App.quit is called on windows',function(done){ + this.timeout(0); + if(os.platform() == "win32") { + var app = spawn(process.execPath, [path.join(global.tests_dir,'nw-in-mem/package')]) + app.on('close',function(){ + var nwname = "nw.exe"; + var nwcount = 0; + setTimeout(function() { + exec("tasklist", function(err, stdout, stderr) { + if(err){ return console.log(err); } + stdout.split('\n').filter(function(line){ + var p=line.trim().split(/\s+/),pname=p[0]; + if(pname.toLowerCase().indexOf(nwname)>=0) + nwcount++; + }); + + if(nwcount == 2) + done(); + else if(nwcount > 2) + done('nw.exe is still in memory after the quit of app'); + }) + }, 500); + }); + } + else + done(); + }) +}) \ No newline at end of file diff --git a/tests/automatic_tests/nw-in-mem/package/c2.png b/tests/automatic_tests/nw-in-mem/package/c2.png new file mode 100644 index 0000000000..ba48f98d2c Binary files /dev/null and b/tests/automatic_tests/nw-in-mem/package/c2.png differ diff --git a/tests/automatic_tests/nw-in-mem/package/c2runtime.js b/tests/automatic_tests/nw-in-mem/package/c2runtime.js new file mode 100644 index 0000000000..1ffdea04c2 --- /dev/null +++ b/tests/automatic_tests/nw-in-mem/package/c2runtime.js @@ -0,0 +1,2 @@ + +var cr={plugins_:{},behaviors:{}};"function"!==typeof Object.getPrototypeOf&&(Object.getPrototypeOf="object"===typeof"test".__proto__?function(a){return a.__proto__}:function(a){return a.constructor.prototype});(function(){function a(a,p){this.x=a;this.y=p;cr.seal(this)}function b(a,p,g,u){this.set(a,p,g,u);cr.seal(this)}function d(){this.bly=this.blx=this.bry=this.brx=this.try_=this.trx=this.tly=this.tlx=0;cr.seal(this)}function e(){this.items={};this.item_count=0;this.values_cache=[];this.cache_valid=!0;cr.seal(this)}function f(){this.sum=this.t=this.y=this.c=0;cr.seal(this)}function h(a){this.pts_cache=[];this.set_pts(a);cr.seal(this)}cr.logexport=function(a){window.console&&window.console.log&&window.console.log(a)};cr.seal=function(a){return a};cr.freeze=function(a){return a};cr.is_undefined=function(a){return"undefined"===typeof a};cr.is_number=function(a){return"number"===typeof a};cr.is_string=function(a){return"string"===typeof a};cr.isPOT=function(a){return 0a?-a:a};cr.max=function(a,p){return a>p?a:p};cr.min=function(a,p){return acr.max(c,s)||cr.max(p,u)cr.max(k,m))return!1;var f=c-a+s-g,b=k-p+m-u;a=g-a;p=u-p;c=s-c;k=m-k;m=cr.abs(p*c-k*a);a=a*b-p*f;return cr.abs(c*b-k*f)<=m&&cr.abs(a)<=m};b.prototype.set=function(a,p,g,u){this.left=a;this.top=p;this.right=g;this.bottom=u};b.prototype.width=function(){return this.right-this.left};b.prototype.height=function(){return this.bottom-this.top};b.prototype.offset=function(a,p){this.left+=a;this.top+=p;this.right+=a;this.bottom+=p;return this};b.prototype.intersects_rect=function(a){return!(a.rightthis.right||a.top>this.bottom)};b.prototype.contains_pt=function(a,p){return a>=this.left&&a<=this.right&&p>=this.top&&p<=this.bottom};cr.rect=b;d.prototype.set_from_rect=function(a){this.tlx=a.left;this.tly=a.top;this.trx=a.right;this.try_=a.top;this.brx=a.right;this.bry=a.bottom;this.blx=a.left;this.bly=a.bottom};d.prototype.set_from_rotated_rect=function(a,p){if(0===p)this.set_from_rect(a);else{var g=Math.sin(p),u=Math.cos(p),c=a.left*g,k=a.top*g,s=a.right*g,g=a.bottom*g,m=a.left*u,f=a.top*u,b=a.right*u,u=a.bottom*u;this.tlx=m-k;this.tly=f+c;this.trx=b-k;this.try_=f+s;this.brx=b-g;this.bry=u+s;this.blx=m-g;this.bly=u+c}};d.prototype.offset=function(a,p){this.tlx+=a;this.tly+=p;this.trx+=a;this.try_+=p;this.brx+=a;this.bry+=p;this.blx+=a;this.bly+=p;return this};d.prototype.bounding_box=function(a){a.left=cr.min(cr.min(this.tlx,this.trx),cr.min(this.brx,this.blx));a.top=cr.min(cr.min(this.tly,this.try_),cr.min(this.bry,this.bly));a.right=cr.max(cr.max(this.tlx,this.trx),cr.max(this.brx,this.blx));a.bottom=cr.max(cr.max(this.tly,this.try_),cr.max(this.bry,this.bly))};d.prototype.contains_pt=function(a,p){var g=this.trx-this.tlx,u=this.try_-this.tly,c=this.brx-this.tlx,k=this.bry-this.tly,s=a-this.tlx,m=p-this.tly,f=g*g+u*u,b=g*c+u*k,u=g*s+u*m,l=c*c+k*k,d=c*s+k*m,e=1/(f*l-b*b),g=(l*u-b*d)*e,f=(f*d-b*u)*e;if(0<=g&&0g+f)return!0;g=this.blx-this.tlx;u=this.bly-this.tly;f=g*g+u*u;b=g*c+u*k;u=g*s+u*m;e=1/(f*l-b*b);g=(l*u-b*d)*e;f=(f*d-b*u)*e;return 0<=g&&0g+f};d.prototype.at=function(a,p){switch(a){case 0:return p?this.tlx:this.tly;case 1:return p?this.trx:this.try_;case 2:return p?this.brx:this.bry;case 3:return p?this.blx:this.bly;case 4:return p?this.tlx:this.tly;default:return p?this.tlx:this.tly}};d.prototype.midX=function(){return(this.tlx+this.trx+this.brx+this.blx)/4};d.prototype.midY=function(){return(this.tly+this.try_+this.bry+this.bly)/4};d.prototype.intersects_segment=function(a,p,g,u){if(this.contains_pt(a,p)||this.contains_pt(g,u))return!0;var c,k,s,m,f;for(f=0;4>f;f++)if(c=this.at(f,!0),k=this.at(f,!1),s=this.at(f+1,!0),m=this.at(f+1,!1),cr.segments_intersect(a,p,g,u,c,k,s,m))return!0;return!1};d.prototype.intersects_quad=function(a){var p=a.midX(),g=a.midY();if(this.contains_pt(p,g))return!0;p=this.midX();g=this.midY();if(a.contains_pt(p,g))return!0;var u,c,k,s,m,f,b,l;for(b=0;4>b;b++)for(l=0;4>l;l++)if(p=this.at(b,!0),g=this.at(b,!1),u=this.at(b+1,!0),c=this.at(b+1,!1),k=a.at(l,!0),s=a.at(l,!1),m=a.at(l+1,!0),f=a.at(l+1,!1),cr.segments_intersect(p,g,u,c,k,s,m,f))return!0;return!1};cr.quad=d;cr.RGB=function(a,p,g){return Math.max(Math.min(a,255),0)|Math.max(Math.min(p,255),0)<<8|Math.max(Math.min(g,255),0)<<16};cr.GetRValue=function(a){return a&255};cr.GetGValue=function(a){return(a&65280)>>8};cr.GetBValue=function(a){return(a&16711680)>>16};cr.shallowCopy=function(a,p,g){for(var u in p)p.hasOwnProperty(u)&&(a[u]=p[u]);return a};cr.arrayRemove=function(a,p){var g,u;p=cr.floor(p);if(!(0>p||p>=a.length))if(0===p)a.shift();else if(p===a.length-1)a.pop();else{g=p;for(u=a.length-1;gg?g:a};cr.to_radians=function(a){return a/(180/cr.PI)};cr.to_degrees=function(a){return a*(180/cr.PI)};cr.clamp_angle_degrees=function(a){a%=360;0>a&&(a+=360);return a};cr.clamp_angle=function(a){a%=2*cr.PI;0>a&&(a+=2*cr.PI);return a};cr.to_clamped_degrees=function(a){return cr.clamp_angle_degrees(cr.to_degrees(a))};cr.to_clamped_radians=function(a){return cr.clamp_angle(cr.to_radians(a))};cr.angleTo=function(a,p,g,u){return Math.atan2(u-p,g-a)};cr.angleDiff=function(a,p){if(a===p)return 0;var g=Math.sin(a),u=Math.cos(a),c=Math.sin(p),k=Math.cos(p),g=g*c+u*k;return 1<=g?0:-1>=g?cr.PI:Math.acos(g)};cr.angleRotate=function(a,p,g){var u=Math.sin(a),c=Math.cos(a),k=Math.sin(p),s=Math.cos(p);return Math.acos(u*k+c*s)>g?0=u*c-g*k};cr.rotatePtAround=function(a,p,g,u,c,k){if(0===g)return k?a:p;var s=Math.sin(g);g=Math.cos(g);a-=u;p-=c;var m=a*s;a=a*g-p*s;p=p*g+m;return k?a+u:p+c};cr.distanceTo=function(a,p,g,u){a=g-a;p=u-p;return Math.sqrt(a*a+p*p)};cr.xor=function(a,p){return!a!==!p};cr.lerp=function(a,p,g){return a+(p-a)*g};cr.hasAnyOwnProperty=function(a){for(var p in a)if(a.hasOwnProperty(p))return!0;return!1};cr.wipe=function(a){for(var p in a)a.hasOwnProperty(p)&&delete a[p]};var k=+new Date;cr.performance_now=function(){if("undefined"!==typeof window.performance){var a=window.performance;if("undefined"!==typeof a.now)return a.now();if("undefined"!==typeof a.webkitNow)return a.webkitNow();if("undefined"!==typeof a.msNow)return a.msNow()}return Date.now()-k};e.prototype.contains=function(a){return this.items.hasOwnProperty(a.toString())};e.prototype.add=function(a){var p=a.toString();this.items.hasOwnProperty(p)||(this.items[p]=a,this.item_count++,this.cache_valid=!1);return this};e.prototype.remove=function(a){a=a.toString();this.items.hasOwnProperty(a)&&(delete this.items[a],this.item_count--,this.cache_valid=!1);return this};e.prototype.clear=function(){cr.wipe(this.items);this.item_count=0;this.values_cache.length=0;this.cache_valid=!0;return this};e.prototype.isEmpty=function(){return 0===this.item_count};e.prototype.count=function(){return this.item_count};e.prototype.update_cache=function(){if(!this.cache_valid){this.values_cache.length=this.item_count;var a,p=0;for(a in this.items)this.items.hasOwnProperty(a)&&(this.values_cache[p++]=this.items[a]);this.cache_valid=!0}};e.prototype.values=function(){this.update_cache();return this.values_cache.slice(0)};e.prototype.valuesRef=function(){this.update_cache();return this.values_cache};cr.ObjectSet=e;f.prototype.add=function(a){this.y=a-this.c;this.t=this.sum+this.y;this.c=this.t-this.sum-this.y;this.sum=this.t};f.prototype.reset=function(){this.sum=this.t=this.y=this.c=0};cr.KahanAdder=f;cr.regexp_escape=function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&")};h.prototype.set_pts=function(a){this.pts_array=a;this.pts_count=a.length/2;this.pts_cache.length=a.length;this.cache_height=this.cache_width=-1;this.cache_angle=0};h.prototype.is_empty=function(){return!this.pts_array.length};h.prototype.set_from_quad=function(a,p,g,u,c){this.pts_cache.length=8;this.pts_count=4;var k=this.pts_cache;k[0]=a.tlx-p;k[1]=a.tly-g;k[2]=a.trx-p;k[3]=a.try_-g;k[4]=a.brx-p;k[5]=a.bry-g;k[6]=a.blx-p;k[7]=a.bly-g;this.cache_width=u;this.cache_height=c};h.prototype.set_from_poly=function(a){this.pts_count=a.pts_count;cr.shallowAssignArray(this.pts_cache,a.pts_cache)};h.prototype.cache_poly=function(a,p,g){if(this.cache_width!==a||this.cache_height!==p||this.cache_angle!==g){this.cache_width=a;this.cache_height=p;this.cache_angle=g;var u,c,k,s=0,m=1,f=this.pts_array,b=this.pts_cache;0!==g&&(s=Math.sin(g),m=Math.cos(g));g=0;for(u=this.pts_count;gf&&(f=c),kl&&(l=k);c=m-110;var b=b-101,f=f+131,l=l+120,d,e,r=0,h=0;for(u=0;u=a||11<=a?"source-over":c[a-1]};cr.setGLBlend=function(a,p,g){if(g)switch(a.srcBlend=g.ONE,a.destBlend=g.ONE_MINUS_SRC_ALPHA,p){case 1:a.srcBlend=g.ONE;a.destBlend=g.ONE;break;case 3:a.srcBlend=g.ONE;a.destBlend=g.ZERO;break;case 4:a.srcBlend=g.ONE_MINUS_DST_ALPHA;a.destBlend=g.ONE;break;case 5:a.srcBlend=g.DST_ALPHA;a.destBlend=g.ZERO;break;case 6:a.srcBlend=g.ZERO;a.destBlend=g.SRC_ALPHA;break;case 7:a.srcBlend=g.ONE_MINUS_DST_ALPHA;a.destBlend=g.ZERO;break;case 8:a.srcBlend=g.ZERO;a.destBlend=g.ONE_MINUS_SRC_ALPHA;break;case 9:a.srcBlend=g.DST_ALPHA;a.destBlend=g.ONE_MINUS_SRC_ALPHA;break;case 10:a.srcBlend=g.ONE_MINUS_DST_ALPHA,a.destBlend=g.SRC_ALPHA}};cr.round6dp=function(a){return Math.round(1E6*a)/1E6};var r={usage:"search",sensitivity:"accent"},n=!!"a".localeCompare,l=n&&0==="a".localeCompare("A",void 0,r),q=n&&0!=="a".localeCompare("\u00e1",void 0,r),v=n&&l&&q;cr.equals_nocase=function(a,p){return"string"!==typeof a||"string"!==typeof p||a.length!==p.length?!1:a===p?!0:v?0===a.localeCompare(p,void 0,r):a.toLowerCase()===p.toLowerCase()}})();var MatrixArray="undefined"!==typeof Float32Array?Float32Array:Array,glMatrixArrayType=MatrixArray,vec3={},mat3={},mat4={},quat4={};vec3.create=function(a){var b=new MatrixArray(3);a&&(b[0]=a[0],b[1]=a[1],b[2]=a[2]);return b};vec3.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];return b};vec3.add=function(a,b,d){if(!d||a===d)return a[0]+=b[0],a[1]+=b[1],a[2]+=b[2],a;d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];return d};vec3.subtract=function(a,b,d){if(!d||a===d)return a[0]-=b[0],a[1]-=b[1],a[2]-=b[2],a;d[0]=a[0]-b[0];d[1]=a[1]-b[1];d[2]=a[2]-b[2];return d};vec3.negate=function(a,b){b||(b=a);b[0]=-a[0];b[1]=-a[1];b[2]=-a[2];return b};vec3.scale=function(a,b,d){if(!d||a===d)return a[0]*=b,a[1]*=b,a[2]*=b,a;d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;return d};vec3.normalize=function(a,b){b||(b=a);var d=a[0],e=a[1],f=a[2],h=Math.sqrt(d*d+e*e+f*f);if(h){if(1===h)return b[0]=d,b[1]=e,b[2]=f,b}else return b[0]=0,b[1]=0,b[2]=0,b;h=1/h;b[0]=d*h;b[1]=e*h;b[2]=f*h;return b};vec3.cross=function(a,b,d){d||(d=a);var e=a[0],f=a[1];a=a[2];var h=b[0],k=b[1];b=b[2];d[0]=f*b-a*k;d[1]=a*h-e*b;d[2]=e*k-f*h;return d};vec3.length=function(a){var b=a[0],d=a[1];a=a[2];return Math.sqrt(b*b+d*d+a*a)};vec3.dot=function(a,b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]};vec3.direction=function(a,b,d){d||(d=a);var e=a[0]-b[0],f=a[1]-b[1];a=a[2]-b[2];b=Math.sqrt(e*e+f*f+a*a);if(!b)return d[0]=0,d[1]=0,d[2]=0,d;b=1/b;d[0]=e*b;d[1]=f*b;d[2]=a*b;return d};vec3.lerp=function(a,b,d,e){e||(e=a);e[0]=a[0]+d*(b[0]-a[0]);e[1]=a[1]+d*(b[1]-a[1]);e[2]=a[2]+d*(b[2]-a[2]);return e};vec3.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+"]"};mat3.create=function(a){var b=new MatrixArray(9);a&&(b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b[4]=a[4],b[5]=a[5],b[6]=a[6],b[7]=a[7],b[8]=a[8]);return b};mat3.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];return b};mat3.identity=function(a){a[0]=1;a[1]=0;a[2]=0;a[3]=0;a[4]=1;a[5]=0;a[6]=0;a[7]=0;a[8]=1;return a};mat3.transpose=function(a,b){if(!b||a===b){var d=a[1],e=a[2],f=a[5];a[1]=a[3];a[2]=a[6];a[3]=d;a[5]=a[7];a[6]=e;a[7]=f;return a}b[0]=a[0];b[1]=a[3];b[2]=a[6];b[3]=a[1];b[4]=a[4];b[5]=a[7];b[6]=a[2];b[7]=a[5];b[8]=a[8];return b};mat3.toMat4=function(a,b){b||(b=mat4.create());b[15]=1;b[14]=0;b[13]=0;b[12]=0;b[11]=0;b[10]=a[8];b[9]=a[7];b[8]=a[6];b[7]=0;b[6]=a[5];b[5]=a[4];b[4]=a[3];b[3]=0;b[2]=a[2];b[1]=a[1];b[0]=a[0];return b};mat3.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+", "+a[4]+", "+a[5]+", "+a[6]+", "+a[7]+", "+a[8]+"]"};mat4.create=function(a){var b=new MatrixArray(16);a&&(b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b[4]=a[4],b[5]=a[5],b[6]=a[6],b[7]=a[7],b[8]=a[8],b[9]=a[9],b[10]=a[10],b[11]=a[11],b[12]=a[12],b[13]=a[13],b[14]=a[14],b[15]=a[15]);return b};mat4.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=a[12];b[13]=a[13];b[14]=a[14];b[15]=a[15];return b};mat4.identity=function(a){a[0]=1;a[1]=0;a[2]=0;a[3]=0;a[4]=0;a[5]=1;a[6]=0;a[7]=0;a[8]=0;a[9]=0;a[10]=1;a[11]=0;a[12]=0;a[13]=0;a[14]=0;a[15]=1;return a};mat4.transpose=function(a,b){if(!b||a===b){var d=a[1],e=a[2],f=a[3],h=a[6],k=a[7],c=a[11];a[1]=a[4];a[2]=a[8];a[3]=a[12];a[4]=d;a[6]=a[9];a[7]=a[13];a[8]=e;a[9]=h;a[11]=a[14];a[12]=f;a[13]=k;a[14]=c;return a}b[0]=a[0];b[1]=a[4];b[2]=a[8];b[3]=a[12];b[4]=a[1];b[5]=a[5];b[6]=a[9];b[7]=a[13];b[8]=a[2];b[9]=a[6];b[10]=a[10];b[11]=a[14];b[12]=a[3];b[13]=a[7];b[14]=a[11];b[15]=a[15];return b};mat4.determinant=function(a){var b=a[0],d=a[1],e=a[2],f=a[3],h=a[4],k=a[5],c=a[6],r=a[7],n=a[8],l=a[9],q=a[10],v=a[11],t=a[12],p=a[13],g=a[14];a=a[15];return t*l*c*f-n*p*c*f-t*k*q*f+h*p*q*f+n*k*g*f-h*l*g*f-t*l*e*r+n*p*e*r+t*d*q*r-b*p*q*r-n*d*g*r+b*l*g*r+t*k*e*v-h*p*e*v-t*d*c*v+b*p*c*v+h*d*g*v-b*k*g*v-n*k*e*a+h*l*e*a+n*d*c*a-b*l*c*a-h*d*q*a+b*k*q*a};mat4.inverse=function(a,b){b||(b=a);var d=a[0],e=a[1],f=a[2],h=a[3],k=a[4],c=a[5],r=a[6],n=a[7],l=a[8],q=a[9],v=a[10],t=a[11],p=a[12],g=a[13],u=a[14],D=a[15],B=d*c-e*k,s=d*r-f*k,m=d*n-h*k,P=e*r-f*c,x=e*n-h*c,z=f*n-h*r,G=l*g-q*p,C=l*u-v*p,F=l*D-t*p,y=q*u-v*g,H=q*D-t*g,Q=v*D-t*u,w=1/(B*Q-s*H+m*y+P*F-x*C+z*G);b[0]=(c*Q-r*H+n*y)*w;b[1]=(-e*Q+f*H-h*y)*w;b[2]=(g*z-u*x+D*P)*w;b[3]=(-q*z+v*x-t*P)*w;b[4]=(-k*Q+r*F-n*C)*w;b[5]=(d*Q-f*F+h*C)*w;b[6]=(-p*z+u*m-D*s)*w;b[7]=(l*z-v*m+t*s)*w;b[8]=(k*H-c*F+n*G)*w;b[9]=(-d*H+e*F-h*G)*w;b[10]=(p*x-g*m+D*B)*w;b[11]=(-l*x+q*m-t*B)*w;b[12]=(-k*y+c*C-r*G)*w;b[13]=(d*y-e*C+f*G)*w;b[14]=(-p*P+g*s-u*B)*w;b[15]=(l*P-q*s+v*B)*w;return b};mat4.toRotationMat=function(a,b){b||(b=mat4.create());b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=0;b[13]=0;b[14]=0;b[15]=1;return b};mat4.toMat3=function(a,b){b||(b=mat3.create());b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[4];b[4]=a[5];b[5]=a[6];b[6]=a[8];b[7]=a[9];b[8]=a[10];return b};mat4.toInverseMat3=function(a,b){var d=a[0],e=a[1],f=a[2],h=a[4],k=a[5],c=a[6],r=a[8],n=a[9],l=a[10],q=l*k-c*n,v=-l*h+c*r,t=n*h-k*r,p=d*q+e*v+f*t;if(!p)return null;p=1/p;b||(b=mat3.create());b[0]=q*p;b[1]=(-l*e+f*n)*p;b[2]=(c*e-f*k)*p;b[3]=v*p;b[4]=(l*d-f*r)*p;b[5]=(-c*d+f*h)*p;b[6]=t*p;b[7]=(-n*d+e*r)*p;b[8]=(k*d-e*h)*p;return b};mat4.multiply=function(a,b,d){d||(d=a);var e=a[0],f=a[1],h=a[2],k=a[3],c=a[4],r=a[5],n=a[6],l=a[7],q=a[8],v=a[9],t=a[10],p=a[11],g=a[12],u=a[13],D=a[14];a=a[15];var B=b[0],s=b[1],m=b[2],P=b[3],x=b[4],z=b[5],G=b[6],C=b[7],F=b[8],y=b[9],H=b[10],Q=b[11],w=b[12],N=b[13],R=b[14];b=b[15];d[0]=B*e+s*c+m*q+P*g;d[1]=B*f+s*r+m*v+P*u;d[2]=B*h+s*n+m*t+P*D;d[3]=B*k+s*l+m*p+P*a;d[4]=x*e+z*c+G*q+C*g;d[5]=x*f+z*r+G*v+C*u;d[6]=x*h+z*n+G*t+C*D;d[7]=x*k+z*l+G*p+C*a;d[8]=F*e+y*c+H*q+Q*g;d[9]=F*f+y*r+H*v+Q*u;d[10]=F*h+y*n+H*t+Q*D;d[11]=F*k+y*l+H*p+Q*a;d[12]=w*e+N*c+R*q+b*g;d[13]=w*f+N*r+R*v+b*u;d[14]=w*h+N*n+R*t+b*D;d[15]=w*k+N*l+R*p+b*a;return d};mat4.multiplyVec3=function(a,b,d){d||(d=b);var e=b[0],f=b[1];b=b[2];d[0]=a[0]*e+a[4]*f+a[8]*b+a[12];d[1]=a[1]*e+a[5]*f+a[9]*b+a[13];d[2]=a[2]*e+a[6]*f+a[10]*b+a[14];return d};mat4.multiplyVec4=function(a,b,d){d||(d=b);var e=b[0],f=b[1],h=b[2];b=b[3];d[0]=a[0]*e+a[4]*f+a[8]*h+a[12]*b;d[1]=a[1]*e+a[5]*f+a[9]*h+a[13]*b;d[2]=a[2]*e+a[6]*f+a[10]*h+a[14]*b;d[3]=a[3]*e+a[7]*f+a[11]*h+a[15]*b;return d};mat4.translate=function(a,b,d){var e=b[0],f=b[1];b=b[2];var h,k,c,r,n,l,q,v,t,p,g,u;if(!d||a===d)return a[12]=a[0]*e+a[4]*f+a[8]*b+a[12],a[13]=a[1]*e+a[5]*f+a[9]*b+a[13],a[14]=a[2]*e+a[6]*f+a[10]*b+a[14],a[15]=a[3]*e+a[7]*f+a[11]*b+a[15],a;h=a[0];k=a[1];c=a[2];r=a[3];n=a[4];l=a[5];q=a[6];v=a[7];t=a[8];p=a[9];g=a[10];u=a[11];d[0]=h;d[1]=k;d[2]=c;d[3]=r;d[4]=n;d[5]=l;d[6]=q;d[7]=v;d[8]=t;d[9]=p;d[10]=g;d[11]=u;d[12]=h*e+n*f+t*b+a[12];d[13]=k*e+l*f+p*b+a[13];d[14]=c*e+q*f+g*b+a[14];d[15]=r*e+v*f+u*b+a[15];return d};mat4.scale=function(a,b,d){var e=b[0],f=b[1];b=b[2];if(!d||a===d)return a[0]*=e,a[1]*=e,a[2]*=e,a[3]*=e,a[4]*=f,a[5]*=f,a[6]*=f,a[7]*=f,a[8]*=b,a[9]*=b,a[10]*=b,a[11]*=b,a;d[0]=a[0]*e;d[1]=a[1]*e;d[2]=a[2]*e;d[3]=a[3]*e;d[4]=a[4]*f;d[5]=a[5]*f;d[6]=a[6]*f;d[7]=a[7]*f;d[8]=a[8]*b;d[9]=a[9]*b;d[10]=a[10]*b;d[11]=a[11]*b;d[12]=a[12];d[13]=a[13];d[14]=a[14];d[15]=a[15];return d};mat4.rotate=function(a,b,d,e){var f=d[0],h=d[1];d=d[2];var k=Math.sqrt(f*f+h*h+d*d),c,r,n,l,q,v,t,p,g,u,D,B,s,m,P,x,z,G,C,F;if(!k)return null;1!==k&&(k=1/k,f*=k,h*=k,d*=k);c=Math.sin(b);r=Math.cos(b);n=1-r;b=a[0];k=a[1];l=a[2];q=a[3];v=a[4];t=a[5];p=a[6];g=a[7];u=a[8];D=a[9];B=a[10];s=a[11];m=f*f*n+r;P=h*f*n+d*c;x=d*f*n-h*c;z=f*h*n-d*c;G=h*h*n+r;C=d*h*n+f*c;F=f*d*n+h*c;f=h*d*n-f*c;h=d*d*n+r;e?a!==e&&(e[12]=a[12],e[13]=a[13],e[14]=a[14],e[15]=a[15]):e=a;e[0]=b*m+v*P+u*x;e[1]=k*m+t*P+D*x;e[2]=l*m+p*P+B*x;e[3]=q*m+g*P+s*x;e[4]=b*z+v*G+u*C;e[5]=k*z+t*G+D*C;e[6]=l*z+p*G+B*C;e[7]=q*z+g*G+s*C;e[8]=b*F+v*f+u*h;e[9]=k*F+t*f+D*h;e[10]=l*F+p*f+B*h;e[11]=q*F+g*f+s*h;return e};mat4.rotateX=function(a,b,d){var e=Math.sin(b);b=Math.cos(b);var f=a[4],h=a[5],k=a[6],c=a[7],r=a[8],n=a[9],l=a[10],q=a[11];d?a!==d&&(d[0]=a[0],d[1]=a[1],d[2]=a[2],d[3]=a[3],d[12]=a[12],d[13]=a[13],d[14]=a[14],d[15]=a[15]):d=a;d[4]=f*b+r*e;d[5]=h*b+n*e;d[6]=k*b+l*e;d[7]=c*b+q*e;d[8]=f*-e+r*b;d[9]=h*-e+n*b;d[10]=k*-e+l*b;d[11]=c*-e+q*b;return d};mat4.rotateY=function(a,b,d){var e=Math.sin(b);b=Math.cos(b);var f=a[0],h=a[1],k=a[2],c=a[3],r=a[8],n=a[9],l=a[10],q=a[11];d?a!==d&&(d[4]=a[4],d[5]=a[5],d[6]=a[6],d[7]=a[7],d[12]=a[12],d[13]=a[13],d[14]=a[14],d[15]=a[15]):d=a;d[0]=f*b+r*-e;d[1]=h*b+n*-e;d[2]=k*b+l*-e;d[3]=c*b+q*-e;d[8]=f*e+r*b;d[9]=h*e+n*b;d[10]=k*e+l*b;d[11]=c*e+q*b;return d};mat4.rotateZ=function(a,b,d){var e=Math.sin(b);b=Math.cos(b);var f=a[0],h=a[1],k=a[2],c=a[3],r=a[4],n=a[5],l=a[6],q=a[7];d?a!==d&&(d[8]=a[8],d[9]=a[9],d[10]=a[10],d[11]=a[11],d[12]=a[12],d[13]=a[13],d[14]=a[14],d[15]=a[15]):d=a;d[0]=f*b+r*e;d[1]=h*b+n*e;d[2]=k*b+l*e;d[3]=c*b+q*e;d[4]=f*-e+r*b;d[5]=h*-e+n*b;d[6]=k*-e+l*b;d[7]=c*-e+q*b;return d};mat4.frustum=function(a,b,d,e,f,h,k){k||(k=mat4.create());var c=b-a,r=e-d,n=h-f;k[0]=2*f/c;k[1]=0;k[2]=0;k[3]=0;k[4]=0;k[5]=2*f/r;k[6]=0;k[7]=0;k[8]=(b+a)/c;k[9]=(e+d)/r;k[10]=-(h+f)/n;k[11]=-1;k[12]=0;k[13]=0;k[14]=-(2*h*f)/n;k[15]=0;return k};mat4.perspective=function(a,b,d,e,f){a=d*Math.tan(a*Math.PI/360);b*=a;return mat4.frustum(-b,b,-a,a,d,e,f)};mat4.ortho=function(a,b,d,e,f,h,k){k||(k=mat4.create());var c=b-a,r=e-d,n=h-f;k[0]=2/c;k[1]=0;k[2]=0;k[3]=0;k[4]=0;k[5]=2/r;k[6]=0;k[7]=0;k[8]=0;k[9]=0;k[10]=-2/n;k[11]=0;k[12]=-(a+b)/c;k[13]=-(e+d)/r;k[14]=-(h+f)/n;k[15]=1;return k};mat4.lookAt=function(a,b,d,e){e||(e=mat4.create());var f,h,k,c,r,n,l,q,v=a[0],t=a[1];a=a[2];h=d[0];k=d[1];f=d[2];d=b[1];n=b[2];if(v===b[0]&&t===d&&a===n)return mat4.identity(e);d=v-b[0];n=t-b[1];l=a-b[2];q=1/Math.sqrt(d*d+n*n+l*l);d*=q;n*=q;l*=q;b=k*l-f*n;f=f*d-h*l;h=h*n-k*d;(q=Math.sqrt(b*b+f*f+h*h))?(q=1/q,b*=q,f*=q,h*=q):h=f=b=0;k=n*h-l*f;c=l*b-d*h;r=d*f-n*b;(q=Math.sqrt(k*k+c*c+r*r))?(q=1/q,k*=q,c*=q,r*=q):r=c=k=0;e[0]=b;e[1]=k;e[2]=d;e[3]=0;e[4]=f;e[5]=c;e[6]=n;e[7]=0;e[8]=h;e[9]=r;e[10]=l;e[11]=0;e[12]=-(b*v+f*t+h*a);e[13]=-(k*v+c*t+r*a);e[14]=-(d*v+n*t+l*a);e[15]=1;return e};mat4.fromRotationTranslation=function(a,b,d){d||(d=mat4.create());var e=a[0],f=a[1],h=a[2],k=a[3],c=e+e,r=f+f,n=h+h;a=e*c;var l=e*r,e=e*n,q=f*r,f=f*n,h=h*n,c=c*k,r=r*k,k=k*n;d[0]=1-(q+h);d[1]=l+k;d[2]=e-r;d[3]=0;d[4]=l-k;d[5]=1-(a+h);d[6]=f+c;d[7]=0;d[8]=e+r;d[9]=f-c;d[10]=1-(a+q);d[11]=0;d[12]=b[0];d[13]=b[1];d[14]=b[2];d[15]=1;return d};mat4.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+", "+a[4]+", "+a[5]+", "+a[6]+", "+a[7]+", "+a[8]+", "+a[9]+", "+a[10]+", "+a[11]+", "+a[12]+", "+a[13]+", "+a[14]+", "+a[15]+"]"};quat4.create=function(a){var b=new MatrixArray(4);a&&(b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3]);return b};quat4.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];return b};quat4.calculateW=function(a,b){var d=a[0],e=a[1],f=a[2];if(!b||a===b)return a[3]=-Math.sqrt(Math.abs(1-d*d-e*e-f*f)),a;b[0]=d;b[1]=e;b[2]=f;b[3]=-Math.sqrt(Math.abs(1-d*d-e*e-f*f));return b};quat4.inverse=function(a,b){if(!b||a===b)return a[0]*=-1,a[1]*=-1,a[2]*=-1,a;b[0]=-a[0];b[1]=-a[1];b[2]=-a[2];b[3]=a[3];return b};quat4.length=function(a){var b=a[0],d=a[1],e=a[2];a=a[3];return Math.sqrt(b*b+d*d+e*e+a*a)};quat4.normalize=function(a,b){b||(b=a);var d=a[0],e=a[1],f=a[2],h=a[3],k=Math.sqrt(d*d+e*e+f*f+h*h);if(0===k)return b[0]=0,b[1]=0,b[2]=0,b[3]=0,b;k=1/k;b[0]=d*k;b[1]=e*k;b[2]=f*k;b[3]=h*k;return b};quat4.multiply=function(a,b,d){d||(d=a);var e=a[0],f=a[1],h=a[2];a=a[3];var k=b[0],c=b[1],r=b[2];b=b[3];d[0]=e*b+a*k+f*r-h*c;d[1]=f*b+a*c+h*k-e*r;d[2]=h*b+a*r+e*c-f*k;d[3]=a*b-e*k-f*c-h*r;return d};quat4.multiplyVec3=function(a,b,d){d||(d=b);var e=b[0],f=b[1],h=b[2];b=a[0];var k=a[1],c=a[2];a=a[3];var r=a*e+k*h-c*f,n=a*f+c*e-b*h,l=a*h+b*f-k*e,e=-b*e-k*f-c*h;d[0]=r*a+e*-b+n*-c-l*-k;d[1]=n*a+e*-k+l*-b-r*-c;d[2]=l*a+e*-c+r*-k-n*-b;return d};quat4.toMat3=function(a,b){b||(b=mat3.create());var d=a[0],e=a[1],f=a[2],h=a[3],k=d+d,c=e+e,r=f+f,n=d*k,l=d*c,d=d*r,q=e*c,e=e*r,f=f*r,k=k*h,c=c*h,h=h*r;b[0]=1-(q+f);b[1]=l+h;b[2]=d-c;b[3]=l-h;b[4]=1-(n+f);b[5]=e+k;b[6]=d+c;b[7]=e-k;b[8]=1-(n+q);return b};quat4.toMat4=function(a,b){b||(b=mat4.create());var d=a[0],e=a[1],f=a[2],h=a[3],k=d+d,c=e+e,r=f+f,n=d*k,l=d*c,d=d*r,q=e*c,e=e*r,f=f*r,k=k*h,c=c*h,h=h*r;b[0]=1-(q+f);b[1]=l+h;b[2]=d-c;b[3]=0;b[4]=l-h;b[5]=1-(n+f);b[6]=e+k;b[7]=0;b[8]=d+c;b[9]=e-k;b[10]=1-(n+q);b[11]=0;b[12]=0;b[13]=0;b[14]=0;b[15]=1;return b};quat4.slerp=function(a,b,d,e){e||(e=a);var f=a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3],h,k;if(1<=Math.abs(f))return e!==a&&(e[0]=a[0],e[1]=a[1],e[2]=a[2],e[3]=a[3]),e;h=Math.acos(f);k=Math.sqrt(1-f*f);if(0.001>Math.abs(k))return e[0]=0.5*a[0]+0.5*b[0],e[1]=0.5*a[1]+0.5*b[1],e[2]=0.5*a[2]+0.5*b[2],e[3]=0.5*a[3]+0.5*b[3],e;f=Math.sin((1-d)*h)/k;d=Math.sin(d*h)/k;e[0]=a[0]*f+b[0]*d;e[1]=a[1]*f+b[1]*d;e[2]=a[2]*f+b[2]*d;e[3]=a[3]*f+b[3]*d;return e};quat4.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+"]"};(function(){function a(a,c){this.height=this.width=0;this.cam=vec3.create([0,0,100]);this.look=vec3.create([0,0,0]);this.up=vec3.create([0,1,0]);this.worldScale=vec3.create([1,1,1]);this.matP=mat4.create();this.matMV=mat4.create();this.lastMV=mat4.create();this.currentMV=mat4.create();this.gl=a;this.initState()}function b(a,c,f){this.gl=a;this.shaderProgram=c;this.name=f;this.locAPos=a.getAttribLocation(c,"aPos");this.locATex=a.getAttribLocation(c,"aTex");this.locMatP=a.getUniformLocation(c,"matP");this.locMatMV=a.getUniformLocation(c,"matMV");this.locOpacity=a.getUniformLocation(c,"opacity");this.locSamplerFront=a.getUniformLocation(c,"samplerFront");this.locSamplerBack=a.getUniformLocation(c,"samplerBack");this.locDestStart=a.getUniformLocation(c,"destStart");this.locDestEnd=a.getUniformLocation(c,"destEnd");this.locSeconds=a.getUniformLocation(c,"seconds");this.locPixelWidth=a.getUniformLocation(c,"pixelWidth");this.locPixelHeight=a.getUniformLocation(c,"pixelHeight");this.locLayerScale=a.getUniformLocation(c,"layerScale");this.locOpacity&&a.uniform1f(this.locOpacity,1);this.locSamplerFront&&a.uniform1i(this.locSamplerFront,0);this.locSamplerBack&&a.uniform1i(this.locSamplerBack,1);this.locDestStart&&a.uniform2f(this.locDestStart,0,0);this.locDestEnd&&a.uniform2f(this.locDestEnd,1,1);this.hasCurrentMatMV=!1}function d(a,c){this.type=a;this.glwrap=c;this.gl=c.gl;this.indexCount=this.startIndex=this.opacityParam=0;this.mat4param=this.texParam=null;this.shaderParams=[];cr.seal(this)}function e(a){--a;for(var c=1;32>c;c<<=1)a|=a>>c;return a+1}a.prototype.initState=function(){var a=this.gl,c;this.lastOpacity=1;this.lastTexture=null;this.currentOpacity=1;a.clearColor(0,0,0,0);a.clear(a.COLOR_BUFFER_BIT);a.enable(a.BLEND);a.blendFunc(a.ONE,a.ONE_MINUS_SRC_ALPHA);a.disable(a.CULL_FACE);a.disable(a.DEPTH_TEST);this.maxTextureSize=a.getParameter(a.MAX_TEXTURE_SIZE);this.lastSrcBlend=a.ONE;this.lastDestBlend=a.ONE_MINUS_SRC_ALPHA;this.pointBuffer=a.createBuffer();a.bindBuffer(a.ARRAY_BUFFER,this.pointBuffer);this.vertexBuffers=Array(4);this.texcoordBuffers=Array(4);for(c=0;4>c;c++)this.vertexBuffers[c]=a.createBuffer(),a.bindBuffer(a.ARRAY_BUFFER,this.vertexBuffers[c]),this.texcoordBuffers[c]=a.createBuffer(),a.bindBuffer(a.ARRAY_BUFFER,this.texcoordBuffers[c]);this.curBuffer=0;this.indexBuffer=a.createBuffer();a.bindBuffer(a.ELEMENT_ARRAY_BUFFER,this.indexBuffer);this.vertexData=new Float32Array(16E3);this.texcoordData=new Float32Array(16E3);this.pointData=new Float32Array(32E3);for(var f=new Uint16Array(12E3),b=c=0;12E3>c;)f[c++]=b,f[c++]=b+1,f[c++]=b+2,f[c++]=b,f[c++]=b+2,f[c++]=b+3,b+=4;a.bufferData(a.ELEMENT_ARRAY_BUFFER,f,a.STATIC_DRAW);this.pointPtr=this.vertexPtr=0;this.shaderPrograms=[];c=this.createShaderProgram({src:"varying mediump vec2 vTex;\nuniform lowp float opacity;\nuniform lowp sampler2D samplerFront;\nvoid main(void) {\n\tgl_FragColor = texture2D(samplerFront, vTex);\n\tgl_FragColor *= opacity;\n}"},"attribute highp vec2 aPos;\nattribute mediump vec2 aTex;\nvarying mediump vec2 vTex;\nuniform highp mat4 matP;\nuniform highp mat4 matMV;\nvoid main(void) {\n\tgl_Position = matP * matMV * vec4(aPos.x, aPos.y, 0.0, 1.0);\n\tvTex = aTex;\n}","");this.shaderPrograms.push(c);c=this.createShaderProgram({src:"uniform mediump sampler2D samplerFront;\nvarying lowp float opacity;\nvoid main(void) {\n\tgl_FragColor = texture2D(samplerFront, gl_PointCoord);\n\tgl_FragColor *= opacity;\n}"},"attribute vec4 aPos;\nvarying float opacity;\nuniform mat4 matP;\nuniform mat4 matMV;\nvoid main(void) {\n\tgl_Position = matP * matMV * vec4(aPos.x, aPos.y, 0.0, 1.0);\n\tgl_PointSize = aPos.z;\n\topacity = aPos.w;\n}","");this.shaderPrograms.push(c);for(var l in cr.shaders)cr.shaders.hasOwnProperty(l)&&this.shaderPrograms.push(this.createShaderProgram(cr.shaders[l],"attribute highp vec2 aPos;\nattribute mediump vec2 aTex;\nvarying mediump vec2 vTex;\nuniform highp mat4 matP;\nuniform highp mat4 matMV;\nvoid main(void) {\n\tgl_Position = matP * matMV * vec4(aPos.x, aPos.y, 0.0, 1.0);\n\tvTex = aTex;\n}",l));a.activeTexture(a.TEXTURE0);a.bindTexture(a.TEXTURE_2D,null);this.batch=[];this.batchPtr=0;this.hasPointBatchTop=this.hasQuadBatchTop=!1;this.currentProgram=this.lastProgram=-1;this.currentShader=null;this.fbo=a.createFramebuffer();this.renderToTex=null;this.tmpVec3=vec3.create([0,0,0]);a=a.getParameter(a.ALIASED_POINT_SIZE_RANGE);this.minPointSize=a[0];this.maxPointSize=a[1];this.switchProgram(0);cr.seal(this)};a.prototype.createShaderProgram=function(a,c,f){var d=this.gl,l=d.createShader(d.FRAGMENT_SHADER);d.shaderSource(l,a.src);d.compileShader(l);if(!d.getShaderParameter(l,d.COMPILE_STATUS))return d.deleteShader(l),null;var e=d.createShader(d.VERTEX_SHADER);d.shaderSource(e,c);d.compileShader(e);if(!d.getShaderParameter(e,d.COMPILE_STATUS))return d.deleteShader(l),d.deleteShader(e),null;c=d.createProgram();d.attachShader(c,l);d.attachShader(c,e);d.linkProgram(c);if(!d.getProgramParameter(c,d.LINK_STATUS))return d.deleteShader(l),d.deleteShader(e),d.deleteProgram(c),null;d.useProgram(c);d.validateProgram(c);d.deleteShader(l);d.deleteShader(e);f=new b(d,c,f);f.extendBoxHorizontal=a.extendBoxHorizontal||0;f.extendBoxVertical=a.extendBoxVertical||0;f.crossSampling=!!a.crossSampling;f.animated=!!a.animated;f.parameters=a.parameters||[];a=0;for(l=f.parameters.length;ac;c++)if(this.lastMV[c]!==this.matMV[c]){a=!0;break}a&&(a=this.pushBatch(),a.type=5,a.mat4param?mat4.set(this.matMV,a.mat4param):a.mat4param=mat4.create(this.matMV),mat4.set(this.matMV,this.lastMV),this.hasPointBatchTop=this.hasQuadBatchTop=!1)};d.prototype.doSetTexture=function(){this.gl.bindTexture(this.gl.TEXTURE_2D,this.texParam)};d.prototype.doSetOpacity=function(){var a=this.opacityParam,c=this.glwrap;c.currentOpacity=a;c=c.currentShader;c.locOpacity&&this.gl.uniform1f(c.locOpacity,a)};d.prototype.doQuad=function(){this.gl.drawElements(this.gl.TRIANGLES,this.indexCount,this.gl.UNSIGNED_SHORT,2*this.startIndex)};d.prototype.doSetBlend=function(){this.gl.blendFunc(this.startIndex,this.indexCount)};d.prototype.doUpdateModelView=function(){var a,c,f,b=this.glwrap.shaderPrograms,l=this.glwrap.currentProgram;a=0;for(c=b.length;a"===c.name)&&a.vertexAttribPointer(c.locAPos,4,a.FLOAT,!1,0,0));if(0"!==c.name)&&a.vertexAttribPointer(c.locAPos,2,a.FLOAT,!1,0,0);a.bindBuffer(a.ARRAY_BUFFER,this.texcoordBuffers[this.curBuffer]);a.bufferData(a.ARRAY_BUFFER,this.texcoordData.subarray(0,this.vertexPtr),a.STREAM_DRAW);c&&(0<=c.locATex&&""!==c.name)&&a.vertexAttribPointer(c.locATex,2,a.FLOAT,!1,0,0)}for(var f,a=0,c=this.batchPtr;am?(m*=g,5===e?(e=m/this.original_width,1e&&(e=1/Math.ceil(1/e)),m=this.original_width*e,d=this.original_height*e,b=(a-m)/2,s=(g-d)/2,a=m,g=d):(b=(a-m)/2,a=m)):(d=a/m,5===e?(e=d/this.original_height,1e&&(e=1/Math.ceil(1/e)),m=this.original_width*e,d=this.original_height*e,b=(a-m)/2,s=(g-d)/2,a=m):s=(g-d)/2,g=d),l&&!this.isNodeWebkit&&(s=b=0),b=Math.floor(b),s=Math.floor(s),a=Math.floor(a),g=Math.floor(g)):this.isNodeWebkit&&(this.isNodeFullscreen&&0===this.fullscreen_mode_set)&&(b=Math.floor((a-this.original_width)/2),s=Math.floor((g-this.original_height)/2),a=this.original_width,g=this.original_height);this.isRetina&&(this.isiPad&&1=s)s=5E4;a+=s;this.wait_for_textures[c].complete||this.wait_for_textures[c].loaded?g+=s:f=!1}this.progress=0==a?0:g/a;return f};a.prototype.go=function(){if(this.ctx||this.glwrap){var a=this.ctx||this.overlay_ctx;this.overlay_canvas&&this.positionOverlayCanvas();this.progress=0;this.last_progress=-1;if(this.areAllTexturesLoaded())this.go_textures_done();else{var g=Date.now()-this.start_time,c=1;this.isTizen&&(c=this.devicePixelRatio);if(a){if(3!==this.loaderstyle&&500<=g&&this.last_progress!=this.progress){a.clearRect(0,0,this.width,this.height);var g=this.width/(2*c),c=this.height/(2*c),f=0===this.loaderstyle&&this.loaderlogo.complete,b=40,s=0,m=80;f&&(m=this.loaderlogo.width,b=m/2,s=this.loaderlogo.height/2,a.drawImage(this.loaderlogo,cr.floor(g-b),cr.floor(c-s)));1>=this.loaderstyle?(c+=s+(f?12:0),g=cr.floor(g-b)+0.5,c=cr.floor(c)+0.5,a.fillStyle="DodgerBlue",a.fillRect(g,c,Math.floor(m*this.progress),6),a.strokeStyle="black",a.strokeRect(g,c,m,6),a.strokeStyle="white",a.strokeRect(g-1,c-1,m+2,8)):2===this.loaderstyle&&(a.font="12pt Arial",a.fillStyle="#999",a.textBaseLine="middle",f=Math.round(100*this.progress)+"%",b=a.measureText?a.measureText(f):null,a.fillText(f,g-(b?b.width:0)/2,c))}this.last_progress=this.progress}setTimeout(function(a){return function(){a.go()}}(this),100)}}};a.prototype.go_textures_done=function(){this.overlay_canvas&&(this.canvas.parentNode.removeChild(this.overlay_canvas),this.overlay_canvas=this.overlay_ctx=null);this.start_time=Date.now();this.last_fps_time=cr.performance_now();var a,g,c;if(this.uses_loader_layout)for(a=0,g=this.types_by_index.length;aa||2===this.fullscreen_mode&&gg||2===f&&ca++;)this.doChangeLayout(this.changelayout);a=0;for(g=this.eventsheets_by_index.length;ag.deadCache.length&&g.deadCache.push(a);g.stale_iids=!0}this.deathRow.isEmpty()||(this.redraw=!0);this.deathRow.clear();this.isInClearDeathRow=!1};a.prototype.createInstance=function(a,g,c,f){if(a.is_family){var b=cr.floor(Math.random()*a.members.length);return this.createInstance(a.members[b],g,c,f)}return a.default_instance?this.createInstanceFromInit(a.default_instance,g,!1,c,f,!1):null};var r=[];a.prototype.createInstanceFromInit=function(a,g,c,f,b,s){var m,d,e,l;if(!a)return null;var h=this.types_by_index[a[1]],k=h.plugin.is_world;if(this.isloading&&k&&!h.isOnLoaderLayout||k&&!this.glwrap&&11===a[0][11])return null;var t=g;k||(g=null);var q;h.deadCache.length?(q=h.deadCache.pop(),q.recycled=!0,h.plugin.Instance.call(q,h)):(q=new h.plugin.Instance(h),q.recycled=!1);q.uid=c&&!s?a[2]:this.next_uid++;this.objectsByUid[q.uid.toString()]=q;q.puid=this.next_puid++;q.iid=0;q.get_iid=cr.inst_get_iid;h.stale_iids=!0;e=a[3];if(q.recycled)cr.wipe(q.extra);else{q.extra={};if("undefined"!==typeof cr_is_preview)for(q.instance_var_names=[],q.instance_var_names.length=e.length,m=0,d=e.length;ma&&(a=0);a>=this.running_layout.layers.length&&(a=this.running_layout.layers.length-1);return this.running_layout.layers[a]};a.prototype.getLayer=function(a){return cr.is_number(a)?this.getLayerByNumber(a):this.getLayerByName(a.toString())};a.prototype.clearSol=function(a){var g,c;g=0;for(c=a.length;gk;k++)if(t=e-k*n,a.x=c+Math.cos(t)*m,a.y=f+Math.sin(t)*m,a.set_bbox_changed(),!this.testOverlap(a,h)&&(h=b?null:this.testOverlapSolid(a),!h)){q=t;break}36===k&&(q=cr.clamp_angle(e+cr.PI));h=l;for(k=1;36>k;k++)if(t=e+k*n,a.x=c+Math.cos(t)*m,a.y=f+Math.sin(t)*m,a.set_bbox_changed(),!this.testOverlap(a,h)&&(h=b?null:this.testOverlapSolid(a),!h)){r=t;break}36===k&&(r=cr.clamp_angle(e+cr.PI));a.x=d;a.y=s;a.set_bbox_changed();if(r===q)return r;a=cr.angleDiff(r,q)/2;a=cr.angleClockwise(r,q)?cr.clamp_angle(q+a+cr.PI):cr.clamp_angle(r+a);q=Math.cos(e);e=Math.sin(e);r=Math.cos(a);a=Math.sin(a);c=q*r+e*a;return cr.angleTo(0,0,q-2*c*r,e-2*c*a)};var v=[],t=-1;a.prototype.trigger=function(a,c,f){if(!this.running_layout)return!1;var b=this.running_layout.event_sheet;if(!b)return!1;t++;t===v.length?v.push(new cr.ObjectSet):v[t].clear();a=this.triggerOnSheet(a,c,b,f);t--;return a};a.prototype.triggerOnSheet=function(a,c,f,b){var d=v[t];if(d.contains(f))return!1;d.add(f);var d=f.includes.valuesRef(),s=!1,m,e,l;m=0;for(e=d.length;m=this.localvar_stack.length&&this.localvar_stack.push([])};a.prototype.popLocalVarStack=function(){this.localvar_stack_index--};a.prototype.getCurrentLocalVarStack=function(){return this.localvar_stack[this.localvar_stack_index]};a.prototype.pushEventStack=function(a){this.event_stack_index++;this.event_stack_index>=this.event_stack.length&&this.event_stack.push(new cr.eventStackFrame);var c=this.getCurrentEventStack();c.reset(a);return c};a.prototype.popEventStack=function(){this.event_stack_index--};a.prototype.getCurrentEventStack=function(){return this.event_stack[this.event_stack_index]};a.prototype.pushLoopStack=function(a){this.loop_stack_index++;this.loop_stack_index>=this.loop_stack.length&&this.loop_stack.push(cr.seal({name:a,index:0,stopped:!1}));var c=this.getCurrentLoop();c.name=a;c.index=0;c.stopped=!1;return c};a.prototype.popLoopStack=function(){this.loop_stack_index--};a.prototype.getCurrentLoop=function(){return this.loop_stack[this.loop_stack_index]};a.prototype.getEventVariableByName=function(a,c){for(var f,b,d,s,m,e;c;){f=0;for(b=c.subevents.length;fe||e>=a.instance_vars.length||(a.instance_vars[e]=b[f]));if(d.is_world){e=c.w;a.layer.sid!==e.l&&(b=a.layer,a.layer=this.running_layout.getLayerBySid(e.l),a.layer?(a.layer.instances.push(a),a.layer.zindices_stale=!0,cr.arrayFindRemove(b.instances,a),b.zindices_stale=!0):(a.layer=b,this.DestroyInstance(a)));a.x=e.x;a.y=e.y;a.width=e.w;a.height=e.h;a.zindex=e.zi;a.angle=e.hasOwnProperty("a")?e.a:0;a.opacity=e.hasOwnProperty("o")?e.o:1;a.hotspotX=e.hasOwnProperty("hX")?e.hX:0.5;a.hotspotY=e.hasOwnProperty("hY")?e.hY:0.5;a.visible=e.hasOwnProperty("v")?e.v:!0;a.collisionsEnabled=e.hasOwnProperty("ce")?e.ce:!0;a.my_timescale=e.hasOwnProperty("mts")?e.mts:-1;a.blend_mode=e.hasOwnProperty("bm")?e.bm:0;a.compositeOp=cr.effectToCompositeOp(a.blend_mode);this.gl&&cr.setGLBlend(a,a.blend_mode,this.gl);a.set_bbox_changed();if(e.hasOwnProperty("fx"))for(b=0,d=e.fx.length;bm||(a.active_effect_flags[m]=e.fx[b].active,a.effect_params[m]=e.fx[b].params);a.updateActiveEffects()}if(l=c.behs)for(f in l)l.hasOwnProperty(f)&&(e=this.getBehaviorIndexBySid(a,parseInt(f,10)),0>e||a.behavior_insts[e].loadFromJSON(l[f]));c.data&&a.loadFromJSON(c.data)};cr.runtime=a;cr.createRuntime=function(c){return new a(document.getElementById(c))};cr.createDCRuntime=function(c,f){return new a({dc:!0,width:c,height:f})};window.cr_createRuntime=cr.createRuntime;window.cr_createDCRuntime=cr.createDCRuntime;window.createCocoonJSRuntime=function(){window.c2cocoonjs=!0;var c=document.createElement("screencanvas")||document.createElement("canvas");document.body.appendChild(c);c=new a(c);window.c2runtime=c;window.addEventListener("orientationchange",function(){window.c2runtime.setSize(window.innerWidth,window.innerHeight)});window.c2runtime.setSize(window.innerWidth,window.innerHeight);return c}})();window.cr_getC2Runtime=function(){var a=document.getElementById("c2canvas");return a?a.c2runtime:window.c2runtime?window.c2runtime:null};window.cr_sizeCanvas=function(a,b){if(0!==a&&0!==b){var d=window.cr_getC2Runtime();d&&d.setSize(a,b)}};window.cr_setSuspended=function(a){var b=window.cr_getC2Runtime();b&&b.setSuspended(a)};(function(){function a(a,b){this.runtime=a;this.event_sheet=null;this.scrollX=this.runtime.original_width/2;this.scrollY=this.runtime.original_height/2;this.scale=1;this.angle=0;this.first_visit=!0;this.name=b[0];this.width=b[1];this.height=b[2];this.unbounded_scrolling=b[3];this.sheetname=b[4];this.sid=b[5];var d=b[6],c,e;this.layers=[];this.initial_types=[];c=0;for(e=d.length;c=this.layers.length&&(q=this.layers.length-1);n.layer=this.layers[q];n.layer.instances.push(n);n.layer.zindices_stale=!0}e.length=0;this.boundScrolling();a=0;for(k=this.layers.length;ak?n.siblings.push(l.instances[k]):l.default_instance&&(r=this.runtime.createInstanceFromInit(l.default_instance,n.layer,!0,n.x,n.y,!0),this.runtime.ClearDeathRow(),l.updateIIDs(),n.siblings.push(r),e.push(r)));a=0;for(k=this.initial_nonworld.length;a=this.active_effect_types.length?(1===this.active_effect_types.length?(b=this.active_effect_types[0].index,a.switchProgram(this.active_effect_types[0].shaderindex),a.setProgramParameters(null,1/this.runtime.width,1/this.runtime.height,0,0,1,1,this.scale,this.effect_params[b]),a.programIsAnimated(this.active_effect_types[0].shaderindex)&&(this.runtime.redraw=!0)):a.switchProgram(0),a.setRenderingToTexture(null),a.setOpacity(1),a.setTexture(this.runtime.layout_tex),a.setAlphaBlend(),a.resetModelView(),a.updateModelView(),b=this.runtime.width/2,d=this.runtime.height/2,a.quad(-b,d,b,d,b,-d,-b,-d),a.setTexture(null)):this.renderEffectChain(a,null,null,null));a.present()};a.prototype.getRenderTarget=function(){return 0this.width-b&&(a=this.width-b);athis.height-b&&(a=this.height-b);aG&&(G=0);0>F&&(F=0);H>B&&(H=B);w>s&&(w=s);0>C&&(C=0);0>y&&(y=0);Q>B&&(Q=B);N>s&&(N=s);x.left=G/B;x.top=1-F/s;x.right=H/B;x.bottom=1-w/s}else x.left=z.left=0,x.top=z.top=0,x.right=z.right=1,x.bottom=z.bottom=1;R=d&&((d.angle||W)&&a.programUsesDest(e[0].shaderindex)||0!==t||0!==R||1!==d.opacity||d.type.plugin.must_predraw)||b&&!d&&1!==b.opacity;a.setAlphaBlend();if(R){l[g]||(l[g]=a.createEmptyTexture(B,s,this.runtime.linearSampling));if(l[g].c2width!==B||l[g].c2height!==s)a.deleteTexture(l[g]),l[g]=a.createEmptyTexture(B,s,this.runtime.linearSampling);a.switchProgram(0);a.setRenderingToTexture(l[g]);D=N-y;a.clearRect(C,s-y-D,Q-C,D);d?d.drawGL(a):(a.setTexture(this.runtime.layer_tex),a.setOpacity(b.opacity),a.resetModelView(),a.translate(-m,-P),a.updateModelView(),a.quadTex(G,w,H,w,H,F,G,F,x));z.left=z.top=0;z.right=z.bottom=1;d&&(p=x.top,x.top=x.bottom,x.bottom=p);g=1;u=0}a.setOpacity(1);t=e.length-1;var W=a.programUsesCrossSampling(e[t].shaderindex),U=0;q=0;for(v=e.length;qthis.viewRight||l.top>this.viewBottom)||(d.globalCompositeOperation=n.compositeOp,n.draw(d)));d.restore();this.render_offscreen&&(a.globalCompositeOperation=this.compositeOp,a.globalAlpha=this.opacity,a.drawImage(b,0,0))};b.prototype.rotateViewport=function(a,b,d){var c=this.getScale();this.viewLeft=a;this.viewTop=b;this.viewRight=a+this.runtime.width*(1/c);this.viewBottom=b+this.runtime.height*(1/c);a=this.getAngle();0!==a&&(d&&(d.translate(this.runtime.width/2,this.runtime.height/2),d.rotate(-a),d.translate(this.runtime.width/-2,this.runtime.height/-2)),this.tmprect.set(this.viewLeft,this.viewTop,this.viewRight,this.viewBottom),this.tmprect.offset((this.viewLeft+this.viewRight)/-2,(this.viewTop+this.viewBottom)/-2),this.tmpquad.set_from_rotated_rect(this.tmprect,a),this.tmpquad.bounding_box(this.tmprect),this.tmprect.offset((this.viewLeft+this.viewRight)/2,(this.viewTop+this.viewBottom)/2),this.viewLeft=this.tmprect.left,this.viewTop=this.tmprect.top,this.viewRight=this.tmprect.right,this.viewBottom=this.tmprect.bottom)};b.prototype.drawGL=function(a){var b=this.runtime.width,d=this.runtime.height,c=0,e=0;if(this.render_offscreen=this.forceOwnTexture||1!==this.opacity||0this.viewRight||c.top>this.viewBottom)))if(v.uses_shaders)if(c=v.active_effect_types[0].shaderindex,e=v.active_effect_types[0].index,1!==v.active_effect_types.length||a.programUsesCrossSampling(c)||a.programExtendsBox(c)||(v.angle||v.layer.getAngle())&&a.programUsesDest(c)||1!==v.opacity||v.type.plugin.must_predraw)this.layout.renderEffectChain(a,this,v,this.render_offscreen?this.runtime.layer_tex:this.layout.getRenderTarget()),a.resetModelView(),a.scale(n,n),a.rotateZ(-this.getAngle()),a.translate((this.viewLeft+this.viewRight)/-2,(this.viewTop+this.viewBottom)/-2),a.updateModelView();else{a.switchProgram(c);a.setBlend(v.srcBlend,v.destBlend);a.programIsAnimated(c)&&(this.runtime.redraw=!0);var t=0,p=0,g=0,u=0;a.programUsesDest(c)&&(c=v.bbox,t=this.layerToCanvas(c.left,c.top,!0),p=this.layerToCanvas(c.left,c.top,!1),g=this.layerToCanvas(c.right,c.bottom,!0),c=this.layerToCanvas(c.right,c.bottom,!1),t/=b,p=1-p/d,g/=b,u=1-c/d);a.setProgramParameters(this.render_offscreen?this.runtime.layer_tex:this.layout.getRenderTarget(),1/v.width,1/v.height,t,p,g,u,this.getScale(),v.effect_params[e]);v.drawGL(a)}else a.switchProgram(0),a.setBlend(v.srcBlend,v.destBlend),v.drawGL(a);this.render_offscreen&&(c=this.active_effect_types.length?this.active_effect_types[0].shaderindex:0,e=this.active_effect_types.length?this.active_effect_types[0].index:0,0===this.active_effect_types.length||1===this.active_effect_types.length&&!a.programUsesCrossSampling(c)&&1===this.opacity?(1===this.active_effect_types.length?(a.switchProgram(c),a.setProgramParameters(this.layout.getRenderTarget(),1/this.runtime.width,1/this.runtime.height,0,0,1,1,this.getScale(),this.effect_params[e]),a.programIsAnimated(c)&&(this.runtime.redraw=!0)):a.switchProgram(0),a.setRenderingToTexture(this.layout.getRenderTarget()),a.setOpacity(this.opacity),a.setTexture(this.runtime.layer_tex),a.setBlend(this.srcBlend,this.destBlend),a.resetModelView(),a.updateModelView(),b=this.runtime.width/2,d=this.runtime.height/2,a.quad(-b,d,b,d,b,-d,-b,-d),a.setTexture(null)):this.layout.renderEffectChain(a,this,null,this.layout.getRenderTarget()))};b.prototype.canvasToLayer=function(a,b,d){var c=this.runtime.devicePixelRatio;this.runtime.isRetina&&0c[1].index&&(d=c[0],c[0]=c[1],c[1]=d):2=t.length&&(t.length=c.length+1);t[c.length]||(t[c.length]=[]);m=t[c.length];d=0;for(e=m.length;d=c.length&&(c.length=this.localIndex+1),c[this.localIndex]=a):this.data=a};l.prototype.getValue=function(){var a=this.runtime.getCurrentLocalVarStack();return!this.parent||this.is_static||!a||this.is_constant?this.data:this.localIndex>=a.length||"undefined"===typeof a[this.localIndex]?this.initial:a[this.localIndex]};l.prototype.run=function(){!this.parent||(this.is_static||this.is_constant)||this.setValue(this.initial)};cr.eventvariable=l;q.prototype.toString=function(){return"include:"+this.include_sheet.toString()};q.prototype.postInit=function(){this.include_sheet=this.runtime.eventsheets[this.include_sheet_name];this.sheet.includes.add(this);this.solModifiers=d(this.solModifiers)};q.prototype.run=function(){this.parent&&this.runtime.pushCleanSol(this.runtime.types_by_index);this.include_sheet.hasRun||this.include_sheet.run();this.parent&&this.runtime.popSol(this.runtime.types_by_index)};q.prototype.isActive=function(){for(var a=this.parent;a;){if(a.group&&!this.runtime.activeGroups[a.group_name.toLowerCase()])return!1;a=a.parent}return!0};cr.eventinclude=q;v.prototype.reset=function(a){this.current_event=a;this.actindex=this.cndindex=0;this.temp_parents_arr.length=0;this.else_branch_ran=this.last_event_true=!1};v.prototype.isModifierAfterCnds=function(){return this.current_event.solWriterAfterCnds?!0:this.cndindex=this.type&&(this.first=new cr.expNode(a,b[1]),this.second=new cr.expNode(a,b[2]));if(f){var h,k;h=0;for(k=f.length;hb&&(b+=f.length);f=f[b];-1b&&(b+=f.length);f=f[b].instance_vars[this.varindex];cr.is_string(f)?a.set_string(f):a.set_float(f);this.owner.popTempValue();return}this.owner.popTempValue()}b%=f.length;0>b&&(b+=f.length);f=f[b];b=0;this.object_type.is_family&&(b=f.type.family_var_map[this.object_type.family_index]);f=f.instance_vars[this.varindex+b];cr.is_string(f)?a.set_string(f):a.set_float(f)};a.prototype.eval_int=function(a){a.type=cr.exptype.Integer;a.data=this.value};a.prototype.eval_float=function(a){a.type=cr.exptype.Float;a.data=this.value};a.prototype.eval_string=function(a){a.type=cr.exptype.String;a.data=this.value};a.prototype.eval_unaryminus=function(a){this.first.get(a);a.is_number()&&(a.data=-a.data)};a.prototype.eval_add=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.is_number()&&b.is_number()&&(a.data+=b.data,b.is_float()&&a.make_float());this.owner.popTempValue()};a.prototype.eval_subtract=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.is_number()&&b.is_number()&&(a.data-=b.data,b.is_float()&&a.make_float());this.owner.popTempValue()};a.prototype.eval_multiply=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.is_number()&&b.is_number()&&(a.data*=b.data,b.is_float()&&a.make_float());this.owner.popTempValue()};a.prototype.eval_divide=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.is_number()&&b.is_number()&&(a.data/=b.data,a.make_float());this.owner.popTempValue()};a.prototype.eval_mod=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.is_number()&&b.is_number()&&(a.data%=b.data,b.is_float()&&a.make_float());this.owner.popTempValue()};a.prototype.eval_power=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.is_number()&&b.is_number()&&(a.data=Math.pow(a.data,b.data),b.is_float()&&a.make_float());this.owner.popTempValue()};a.prototype.eval_and=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.is_number()?b.is_string()?a.set_string(a.data.toString()+b.data):a.data&&b.data?a.set_int(1):a.set_int(0):a.is_string()&&(b.is_string()?a.data+=b.data:a.data+=(Math.round(1E10*b.data)/1E10).toString());this.owner.popTempValue()};a.prototype.eval_or=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.is_number()&&b.is_number()&&(a.data||b.data?a.set_int(1):a.set_int(0));this.owner.popTempValue()};a.prototype.eval_conditional=function(a){this.first.get(a);a.data?this.second.get(a):this.third.get(a)};a.prototype.eval_equal=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.set_int(a.data===b.data?1:0);this.owner.popTempValue()};a.prototype.eval_notequal=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.set_int(a.data!==b.data?1:0);this.owner.popTempValue()};a.prototype.eval_less=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.set_int(a.datab.data?1:0);this.owner.popTempValue()};a.prototype.eval_greaterequal=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.set_int(a.data>=b.data?1:0);this.owner.popTempValue()};a.prototype.eval_eventvar_exp=function(a){var b=this.eventvar.getValue();cr.is_number(b)?a.set_float(b):a.set_string(b)};cr.expNode=a;b.prototype.is_int=function(){return this.type===cr.exptype.Integer};b.prototype.is_float=function(){return this.type===cr.exptype.Float};b.prototype.is_number=function(){return this.type===cr.exptype.Integer||this.type===cr.exptype.Float};b.prototype.is_string=function(){return this.type===cr.exptype.String};b.prototype.make_int=function(){this.is_int()||(this.is_float()?this.data=Math.floor(this.data):this.is_string()&&(this.data=parseInt(this.data,10)),this.type=cr.exptype.Integer)};b.prototype.make_float=function(){this.is_float()||(this.is_string()&&(this.data=parseFloat(this.data)),this.type=cr.exptype.Float)};b.prototype.make_string=function(){this.is_string()||(this.data=this.data.toString(),this.type=cr.exptype.String)};b.prototype.set_int=function(a){this.type=cr.exptype.Integer;this.data=Math.floor(a)};b.prototype.set_float=function(a){this.type=cr.exptype.Float;this.data=a};b.prototype.set_string=function(a){this.type=cr.exptype.String;this.data=a};b.prototype.set_any=function(a){cr.is_number(a)?(this.type=cr.exptype.Float,this.data=a):cr.is_string(a)?(this.type=cr.exptype.String,this.data=a.toString()):(this.type=cr.exptype.Integer,this.data=0)};cr.expvalue=b;cr.exptype={Integer:0,Float:1,String:2}})();cr.system_object=function(a){this.runtime=a;this.waits=[]};cr.system_object.prototype.saveToJSON=function(){var a={},b,d,e,f,h,k,c,r;a.waits=[];var n=a.waits,l;b=0;for(d=this.waits.length;bd?1:0}function d(a,b){n&&a===l&&b===q||(n=RegExp(a,b),l=a,q=b);n.lastIndex=0;return n}function e(){}function f(){}function h(a,b,c){if(a!==u||b!==D||c!==B){var e=d(b,c);g=a.match(e);u=a;D=b;B=c}}var k=cr.system_object.prototype;a.prototype.EveryTick=function(){return!0};a.prototype.OnLayoutStart=function(){return!0};a.prototype.OnLayoutEnd=function(){return!0};a.prototype.Compare=function(a,b,c){return cr.do_cmp(a,b,c)};a.prototype.CompareTime=function(a,b){var c=this.runtime.kahanTime.sum;if(0===a){var d=this.runtime.getCurrentCondition();return!d.extra.CompareTime_executed&&c>=b?d.extra.CompareTime_executed=!0:!1}return cr.do_cmp(c,a,b)};a.prototype.LayerVisible=function(a){return a?a.visible:!1};a.prototype.LayerCmpOpacity=function(a,b,c){return a?cr.do_cmp(100*a.opacity,b,c):!1};a.prototype.Repeat=function(a){var b=this.runtime.getCurrentEventStack(),c=b.current_event,d=b.isModifierAfterCnds(),b=this.runtime.pushLoopStack();if(d)for(d=0;d=c&&!a.stopped;--b)this.runtime.pushCopySol(e.solModifiers),a.index=b,e.retrigger(),this.runtime.popSol(e.solModifiers);else for(;b>=c&&!a.stopped;--b)a.index=b,e.retrigger();else if(d)for(;b<=c&&!a.stopped;++b)this.runtime.pushCopySol(e.solModifiers),a.index=b,e.retrigger(),this.runtime.popSol(e.solModifiers);else for(;b<=c&&!a.stopped;++b)a.index=b,e.retrigger();this.runtime.popLoopStack();return!1};var c=[],r=-1;a.prototype.ForEach=function(a){var b=a.getCurrentSol();r++;c.length===r&&c.push([]);var d=c[r];cr.shallowAssignArray(d,b.getObjects());var e=this.runtime.getCurrentEventStack(),f=e.current_event,g=e.isModifierAfterCnds(),e=this.runtime.pushLoopStack(),l,t,p,q,h,k,n=a.is_contained;if(g)for(g=0,l=d.length;g=c+e?(b.extra.Every_lastTime=c+e,d>=b.extra.Every_lastTime+e&&(b.extra.Every_lastTime=d),b.extra.Every_seconds=a,!0):!1};a.prototype.PickNth=function(a,b){if(!a)return!1;var c=a.getCurrentSol(),d=c.getObjects();b=cr.floor(b);if(0>b||b>=d.length)return!1;c.pick_one(d[b]);a.applySolToContainer();return!0};a.prototype.PickRandom=function(a){if(!a)return!1;var b=a.getCurrentSol(),c=b.getObjects(),d=cr.floor(Math.random()*c.length);if(d>=c.length)return!1;b.pick_one(c[d]);a.applySolToContainer();return!0};a.prototype.CompareVar=function(a,b,c){return cr.do_cmp(a.getValue(),b,c)};a.prototype.IsGroupActive=function(a){return this.runtime.activeGroups[a.toLowerCase()]};a.prototype.IsPreview=function(){return"undefined"!==typeof cr_is_preview};a.prototype.PickAll=function(a){if(!a||!a.instances.length)return!1;a.getCurrentSol().select_all=!0;a.applySolToContainer();return!0};a.prototype.IsMobile=function(){return this.runtime.isMobile};a.prototype.CompareBetween=function(a,b,c){return a>=b&&a<=c};a.prototype.Else=function(){var a=this.runtime.getCurrentEventStack();return a.else_branch_ran?!1:!a.last_event_true};a.prototype.OnLoadFinished=function(){return!0};a.prototype.OnCanvasSnapshot=function(){return!0};a.prototype.EffectsSupported=function(){return!!this.runtime.glwrap};a.prototype.OnSaveComplete=function(){return!0};a.prototype.OnLoadComplete=function(){return!0};a.prototype.OnLoadFailed=function(){return!0};a.prototype.ObjectUIDExists=function(a){return!!this.runtime.getObjectByUID(a)};a.prototype.IsOnPlatform=function(a){var b=this.runtime;switch(a){case 0:return!b.isDomFree&&!b.isNodeWebkit&&!b.isPhoneGap&&!b.isWindows8App&&!b.isWindowsPhone8&&!b.isBlackberry10;case 1:return b.isiOS;case 2:return b.isAndroid;case 3:return b.isWindows8App;case 4:return b.isWindowsPhone8;case 5:return b.isBlackberry10;case 6:return b.isTizen;case 7:return b.isNodeWebkit;case 8:return b.isCocoonJs;case 9:return b.isPhoneGap;case 10:return b.isArcade;case 11:return b.isNodeWebkit;default:return!1}};var n=null,l="",q="";a.prototype.RegexTest=function(a,b,c){return d(b,c).test(a)};var v=[];a.prototype.PickOverlappingPoint=function(a,b,c){if(!a)return!1;var d=a.getCurrentSol(),e=d.getObjects(),f=this.runtime.getCurrentEventStack().current_event.orblock,g=this.runtime.getCurrentCondition(),l,t;d.select_all?(cr.shallowAssignArray(v,e),d.else_instances.length=0,d.select_all=!1,d.instances.length=0):f?(cr.shallowAssignArray(v,d.else_instances),d.else_instances.length=0):(cr.shallowAssignArray(v,e),d.instances.length=0);e=0;for(f=v.length;ea&&(a=0);this.runtime.timescale=a};e.prototype.SetObjectTimescale=function(a,b){var c=b;0>c&&(c=0);if(a){var d=a.getCurrentSol().getObjects(),e,f;e=0;for(f=d.length;ea)){var b,c,d,e=this.runtime.getCurrentEventStack(),f;f=t.length?t.pop():{sols:{},solModifiers:[]};f.deleteme=!1;f.time=this.runtime.kahanTime.sum+a;f.ev=e.current_event;f.actindex=e.actindex+1;a=0;for(b=this.runtime.types_by_index.length;athis.runtime.loop_stack_index||(this.runtime.getCurrentLoop().stopped=!0)};e.prototype.GoToLayoutByName=function(a){if(!this.runtime.isloading&&!this.runtime.changelayout)for(var b in this.runtime.layouts)if(this.runtime.layouts.hasOwnProperty(b)&&cr.equals_nocase(b,a)){this.runtime.changelayout=this.runtime.layouts[b];break}};e.prototype.RestartLayout=function(a){if(!this.runtime.isloading&&!this.runtime.changelayout&&this.runtime.running_layout){this.runtime.changelayout=this.runtime.running_layout;var b,c;a=0;for(b=this.runtime.allGroups.length;a=a||0>=b||this.runtime.setSize(a,b)};e.prototype.SetLayoutEffectEnabled=function(a,b){if(this.runtime.running_layout&&this.runtime.glwrap){var c=this.runtime.running_layout.getEffectByName(b);if(c){var d=1===a;c.active!=d&&(c.active=d,this.runtime.running_layout.updateActiveEffects(),this.runtime.redraw=!0)}}};e.prototype.SetLayerEffectEnabled=function(a,b,c){a&&this.runtime.glwrap&&(c=a.getEffectByName(c))&&(b=1===b,c.active!=b&&(c.active=b,a.updateActiveEffects(),this.runtime.redraw=!0))};e.prototype.SetLayoutEffectParam=function(a,b,c){if(this.runtime.running_layout&&this.runtime.glwrap&&(a=this.runtime.running_layout.getEffectByName(a))){var d=this.runtime.running_layout.effect_params[a.index];b=Math.floor(b);0>b||b>=d.length||(1===this.runtime.glwrap.getProgramParameterType(a.shaderindex,b)&&(c/=100),d[b]!==c&&(d[b]=c,a.active&&(this.runtime.redraw=!0)))}};e.prototype.SetLayerEffectParam=function(a,b,c,d){a&&this.runtime.glwrap&&(b=a.getEffectByName(b))&&(a=a.effect_params[b.index],c=Math.floor(c),0>c||c>=a.length||(1===this.runtime.glwrap.getProgramParameterType(b.shaderindex,c)&&(d/=100),a[c]!==d&&(a[c]=d,b.active&&(this.runtime.redraw=!0))))};e.prototype.SaveState=function(a){this.runtime.saveToSlot=a};e.prototype.LoadState=function(a){this.runtime.loadFromSlot=a};e.prototype.LoadStateJSON=function(a){this.runtime.loadFromJson=a};k.acts=new e;f.prototype["int"]=function(a,b){cr.is_string(b)?(a.set_int(parseInt(b,10)),isNaN(a.data)&&(a.data=0)):a.set_int(b)};f.prototype["float"]=function(a,b){cr.is_string(b)?(a.set_float(parseFloat(b)),isNaN(a.data)&&(a.data=0)):a.set_float(b)};f.prototype.str=function(a,b){cr.is_string(b)?a.set_string(b):a.set_string(b.toString())};f.prototype.len=function(a,b){a.set_int(b.length||0)};f.prototype.random=function(a,b,c){void 0===c?a.set_float(Math.random()*b):a.set_float(Math.random()*(c-b)+b)};f.prototype.sqrt=function(a,b){a.set_float(Math.sqrt(b))};f.prototype.abs=function(a,b){a.set_float(Math.abs(b))};f.prototype.round=function(a,b){a.set_int(Math.round(b))};f.prototype.floor=function(a,b){a.set_int(Math.floor(b))};f.prototype.ceil=function(a,b){a.set_int(Math.ceil(b))};f.prototype.sin=function(a,b){a.set_float(Math.sin(cr.to_radians(b)))};f.prototype.cos=function(a,b){a.set_float(Math.cos(cr.to_radians(b)))};f.prototype.tan=function(a,b){a.set_float(Math.tan(cr.to_radians(b)))};f.prototype.asin=function(a,b){a.set_float(cr.to_degrees(Math.asin(b)))};f.prototype.acos=function(a,b){a.set_float(cr.to_degrees(Math.acos(b)))};f.prototype.atan=function(a,b){a.set_float(cr.to_degrees(Math.atan(b)))};f.prototype.exp=function(a,b){a.set_float(Math.exp(b))};f.prototype.ln=function(a,b){a.set_float(Math.log(b))};f.prototype.log10=function(a,b){a.set_float(Math.log(b)/Math.LN10)};f.prototype.max=function(a){var b=arguments[1],c,d;c=2;for(d=arguments.length;carguments[c]&&(b=arguments[c]);a.set_float(b)};f.prototype.dt=function(a){a.set_float(this.runtime.dt)};f.prototype.timescale=function(a){a.set_float(this.runtime.timescale)};f.prototype.wallclocktime=function(a){a.set_float((Date.now()-this.runtime.start_time)/1E3)};f.prototype.time=function(a){a.set_float(this.runtime.kahanTime.sum)};f.prototype.tickcount=function(a){a.set_int(this.runtime.tickcount)};f.prototype.objectcount=function(a){a.set_int(this.runtime.objectcount)};f.prototype.fps=function(a){a.set_int(this.runtime.fps)};f.prototype.loopindex=function(a,b){var c,d,e;if(this.runtime.loop_stack.length)if(b){d=0;for(e=this.runtime.loop_stack.length;dd?a.set_float(d):a.set_float(b)};f.prototype.layerscale=function(a,b){var c=this.runtime.getLayer(b);c?a.set_float(c.scale):a.set_float(0)};f.prototype.layeropacity=function(a,b){var c=this.runtime.getLayer(b);c?a.set_float(100*c.opacity):a.set_float(0)};f.prototype.layerscalerate=function(a,b){var c=this.runtime.getLayer(b);c?a.set_float(c.zoomRate):a.set_float(0)};f.prototype.layerparallaxx=function(a,b){var c=this.runtime.getLayer(b);c?a.set_float(100*c.parallaxX):a.set_float(0)};f.prototype.layerparallaxy=function(a,b){var c=this.runtime.getLayer(b);c?a.set_float(100*c.parallaxY):a.set_float(0)};f.prototype.layoutscale=function(a){this.runtime.running_layout?a.set_float(this.runtime.running_layout.scale):a.set_float(0)};f.prototype.layoutangle=function(a){a.set_float(cr.to_degrees(this.runtime.running_layout.angle))};f.prototype.layerangle=function(a,b){var c=this.runtime.getLayer(b);c?a.set_float(cr.to_degrees(c.angle)):a.set_float(0)};f.prototype.layoutwidth=function(a){a.set_int(this.runtime.running_layout.width)};f.prototype.layoutheight=function(a){a.set_int(this.runtime.running_layout.height)};f.prototype.find=function(a,b,c){cr.is_string(b)&&cr.is_string(c)?a.set_int(b.search(RegExp(cr.regexp_escape(c),"i"))):a.set_int(-1)};f.prototype.left=function(a,b,c){a.set_string(cr.is_string(b)?b.substr(0,c):"")};f.prototype.right=function(a,b,c){a.set_string(cr.is_string(b)?b.substr(b.length-c):"")};f.prototype.mid=function(a,b,c,d){a.set_string(cr.is_string(b)?b.substr(c,d):"")};f.prototype.tokenat=function(a,b,c,d){cr.is_string(b)&&cr.is_string(d)?(b=b.split(d),c=cr.floor(c),0>c||c>=b.length?a.set_string(""):a.set_string(b[c])):a.set_string("")};f.prototype.tokencount=function(a,b,c){cr.is_string(b)&&b.length?a.set_int(b.split(c).length):a.set_int(0)};f.prototype.replace=function(a,b,c,d){cr.is_string(b)&&cr.is_string(c)&&cr.is_string(d)?a.set_string(b.replace(RegExp(cr.regexp_escape(c),"gi"),d)):a.set_string(cr.is_string(b)?b:"")};f.prototype.trim=function(a,b){a.set_string(cr.is_string(b)?b.trim():"")};f.prototype.pi=function(a){a.set_float(cr.PI)};f.prototype.layoutname=function(a){this.runtime.running_layout?a.set_string(this.runtime.running_layout.name):a.set_string("")};f.prototype.renderer=function(a){a.set_string(this.runtime.gl?"webgl":"canvas2d")};f.prototype.anglediff=function(a,b,c){a.set_float(cr.to_degrees(cr.angleDiff(cr.to_radians(b),cr.to_radians(c))))};f.prototype.choose=function(a){var b=cr.floor(Math.random()*(arguments.length-1));a.set_any(arguments[b+1])};f.prototype.rgb=function(a,b,c,d){a.set_int(cr.RGB(b,c,d))};f.prototype.projectversion=function(a){a.set_string(this.runtime.versionstr)};f.prototype.anglelerp=function(a,b,c,d){b=cr.to_radians(b);c=cr.to_radians(c);var e=cr.angleDiff(b,c);cr.angleClockwise(c,b)?a.set_float(cr.to_clamped_degrees(b+e*d)):a.set_float(cr.to_clamped_degrees(b-e*d))};f.prototype.anglerotate=function(a,b,c,d){b=cr.to_radians(b);c=cr.to_radians(c);d=cr.to_radians(d);a.set_float(cr.to_clamped_degrees(cr.angleRotate(b,c,d)))};f.prototype.zeropad=function(a,b,c){var d=0>b?"-":"";0>b&&(b=-b);c-=b.toString().length;for(var e=0;ef||f>=g.length?a.set_string(""):a.set_string(g[f])};f.prototype.infinity=function(a){a.set_float(Infinity)};k.exps=new f;k.runWaits=function(){var a,b,c,d,e,f,g=this.runtime.getCurrentEventStack();a=0;for(c=this.waits.length;athis.runtime.kahanTime.sum)){g.current_event=d.ev;g.actindex=d.actindex;g.cndindex=0;for(b in d.sols)d.sols.hasOwnProperty(b)&&(e=this.runtime.types_by_index[parseInt(b,10)].getCurrentSol(),f=d.sols[b],e.select_all=f.sa,cr.shallowAssignArray(e.instances,f.insts),e=f,e.insts.length=0,p.push(e));d.ev.resume_actions_and_subevents();this.runtime.clearSol(d.solModifiers);d.deleteme=!0}b=a=0;for(c=this.waits.length;aa.viewRight||b.top>a.viewBottom)},r.IsOutsideLayout=function(){this.update_bbox();var a=this.bbox,b=this.runtime.running_layout;return 0>a.right||0>a.bottom||a.left>b.width||a.top>b.height},r.PickDistance=function(a,b,c){var d=this.getCurrentSol(),e=d.getObjects();if(!e.length)return!1;var f=e[0],h=f,k=cr.distanceTo(f.x,f.y,b,c),n,r,m;n=1;for(r=e.length;nk)k=m,h=f;d.pick_one(h);return!0},n.SetX=function(a){this.x!==a&&(this.x=a,this.set_bbox_changed())},n.SetY=function(a){this.y!==a&&(this.y=a,this.set_bbox_changed())},n.SetPos=function(a,b){if(this.x!==a||this.y!==b)this.x=a,this.y=b,this.set_bbox_changed()},n.SetPosToObject=function(a,b){var c=a.getPairedInstance(this);if(c){var d;c.getImagePoint?(d=c.getImagePoint(b,!0),c=c.getImagePoint(b,!1)):(d=c.x,c=c.y);if(this.x!==d||this.y!==c)this.x=d,this.y=c,this.set_bbox_changed()}},n.MoveForward=function(a){0!==a&&(this.x+=Math.cos(this.angle)*a,this.y+=Math.sin(this.angle)*a,this.set_bbox_changed())},n.MoveAtAngle=function(a,b){0!==b&&(this.x+=Math.cos(cr.to_radians(a))*b,this.y+=Math.sin(cr.to_radians(a))*b,this.set_bbox_changed())},b.X=function(a){a.set_float(this.x)},b.Y=function(a){a.set_float(this.y)},b.dt=function(a){a.set_float(this.runtime.getDt(this))});f&&(r.CompareWidth=function(a,b){return cr.do_cmp(this.width,a,b)},r.CompareHeight=function(a,b){return cr.do_cmp(this.height,a,b)},n.SetWidth=function(a){this.width!==a&&(this.width=a,this.set_bbox_changed())},n.SetHeight=function(a){this.height!==a&&(this.height=a,this.set_bbox_changed())},n.SetSize=function(a,b){if(this.width!==a||this.height!==b)this.width=a,this.height=b,this.set_bbox_changed()},b.Width=function(a){a.set_float(this.width)},b.Height=function(a){a.set_float(this.height)},b.BBoxLeft=function(a){this.update_bbox();a.set_float(this.bbox.left)},b.BBoxTop=function(a){this.update_bbox();a.set_float(this.bbox.top)},b.BBoxRight=function(a){this.update_bbox();a.set_float(this.bbox.right)},b.BBoxBottom=function(a){this.update_bbox();a.set_float(this.bbox.bottom)});h&&(r.AngleWithin=function(a,b){return cr.angleDiff(this.angle,cr.to_radians(b))<=cr.to_radians(a)},r.IsClockwiseFrom=function(a){return cr.angleClockwise(this.angle,cr.to_radians(a))},r.IsBetweenAngles=function(a,b){var c=cr.to_clamped_radians(a),d=cr.to_clamped_radians(b),e=cr.clamp_angle(this.angle);return cr.angleClockwise(d,c)?cr.angleClockwise(e,c)&&!cr.angleClockwise(e,d):!(!cr.angleClockwise(e,c)&&cr.angleClockwise(e,d))},n.SetAngle=function(a){a=cr.to_radians(cr.clamp_angle_degrees(a));isNaN(a)||this.angle===a||(this.angle=a,this.set_bbox_changed())},n.RotateClockwise=function(a){0===a||isNaN(a)||(this.angle+=cr.to_radians(a),this.angle=cr.clamp_angle(this.angle),this.set_bbox_changed())},n.RotateCounterclockwise=function(a){0===a||isNaN(a)||(this.angle-=cr.to_radians(a),this.angle=cr.clamp_angle(this.angle),this.set_bbox_changed())},n.RotateTowardAngle=function(a,b){var c=cr.angleRotate(this.angle,cr.to_radians(b),cr.to_radians(a));isNaN(c)||this.angle===c||(this.angle=c,this.set_bbox_changed())},n.RotateTowardPosition=function(a,b,c){b=Math.atan2(c-this.y,b-this.x);a=cr.angleRotate(this.angle,b,cr.to_radians(a));isNaN(a)||this.angle===a||(this.angle=a,this.set_bbox_changed())},n.SetTowardPosition=function(a,b){var c=Math.atan2(b-this.y,a-this.x);isNaN(c)||this.angle===c||(this.angle=c,this.set_bbox_changed())},b.Angle=function(a){a.set_float(cr.to_clamped_degrees(this.angle))});d||(r.CompareInstanceVar=function(a,b,c){return cr.do_cmp(this.instance_vars[a],b,c)},r.IsBoolInstanceVarSet=function(a){return this.instance_vars[a]},r.PickInstVarHiLow=function(a,b){var c=this.getCurrentSol(),d=c.getObjects();if(!d.length)return!1;var e=d[0],f=e,h=e.instance_vars[b],k,n,r;k=1;for(n=d.length;kh)h=r,f=e;c.pick_one(f);return!0},r.PickByUID=function(a){var b,c,d,e,f;if(this.runtime.getCurrentCondition().inverted){f=this.getCurrentSol();if(f.select_all)for(f.select_all=!1,f.instances.length=0,f.else_instances.length=0,d=this.instances,b=0,c=d.length;ba?a=0:1e.layer.index||d.layer.index===e.layer.index&&d.get_zindex()>e.get_zindex())e=d}else if(d.layer.indexc)){var d=1===a;this.active_effect_flags[c]!==d&&(this.active_effect_flags[c]=d,this.updateActiveEffects(),this.runtime.redraw=!0)}}},n.SetEffectParam=function(a,b,c){if(this.runtime.glwrap){var d=this.type.getEffectIndexByName(a);0>d||(a=this.type.effect_types[d],d=this.effect_params[d],b=Math.floor(b),0>b||b>=d.length||(1===this.runtime.glwrap.getProgramParameterType(a.shaderindex,b)&&(c/=100),d[b]!==c&&(d[b]=c,a.active&&(this.runtime.redraw=!0))))}})};cr.set_bbox_changed=function(){this.bbox_changed=!0;this.runtime.redraw=!0;var a,b;a=0;for(b=this.bbox_changed_callbacks.length;athis.bbox.right&&(a=this.bbox.left,this.bbox.left=this.bbox.right,this.bbox.right=a);this.bbox.top>this.bbox.bottom&&(a=this.bbox.top,this.bbox.top=this.bbox.bottom,this.bbox.bottom=a);this.bbox_changed=!1}};cr.inst_contains_pt=function(a,b){return this.bbox.contains_pt(a,b)&&this.bquad.contains_pt(a,b)?this.collision_poly&&!this.collision_poly.is_empty()?(this.collision_poly.cache_poly(this.width,this.height,this.angle),this.collision_poly.contains_pt(a-this.x,b-this.y)):!0:!1};cr.inst_get_iid=function(){this.type.updateIIDs();return this.iid};cr.inst_get_zindex=function(){this.layer.updateZIndices();return this.zindex};cr.inst_updateActiveEffects=function(){this.active_effect_types.length=0;var a,b;a=0;for(b=this.active_effect_flags.length;ad;case 5:return a>=d;default:return!1}};cr.shaders={};cr.shaders.warpripple={src:"varying mediump vec2 vTex;\nuniform lowp sampler2D samplerFront;\nuniform mediump float seconds;\nuniform mediump float pixelWidth;\nuniform mediump float layerScale;\nuniform mediump float freq;\nuniform mediump float amp;\nuniform mediump float speed;\nconst mediump float PI = 3.1415926;\nvoid main(void)\n{\nmediump vec2 p = vTex;\nmediump vec2 tex = vTex * 2.0 - 1.0;\nmediump float d = length(tex);\nmediump float a = atan(tex.y, tex.x);\nd += sin((d * 2.0 * PI) * freq / layerScale / (pixelWidth * 750.0) + (seconds * speed)) * amp * (pixelWidth * 750.0) * layerScale;\ntex.x = cos(a) * d;\ntex.y = sin(a) * d;\ntex = (tex + 1.0) / 2.0;\ngl_FragColor = texture2D(samplerFront, tex);\n}",extendBoxHorizontal:50,extendBoxVertical:50,crossSampling:!1,animated:!0,parameters:[["freq",0,0],["amp",0,1],["speed",0,0]]};cr.plugins_.Audio=function(a){this.runtime=a};(function(){function a(a){a=Math.pow(10,a/20);0>a&&(a=0);1a&&(a=0);1b&&(b=0);1c&&(c=0);1b&&(b=0),1b&&(b=0);1b&&(b=0);1b&&(b=0);1b&&(b=0);1a&&(a=0.01);this.preGain.gain.value=a;this.postGain.gain.value=Math.pow(1/a,0.6)*b};D.prototype.shape=function(a,b,c){var d=1.05*c*b-b;c=0>a?-1:1;a=0>a?-a:a;b=af;++f)e=f/32768,e=this.shape(e,c,d),this.curve[32768+f]=e,this.curve[32768-f-1]=-e};D.prototype.connectTo=function(a){this.wetNode.disconnect();this.wetNode.connect(a);this.dryNode.disconnect();this.dryNode.connect(a)};D.prototype.remove=function(){this.inputNode.disconnect();this.preGain.disconnect();this.waveShaper.disconnect();this.postGain.disconnect();this.wetNode.disconnect();this.dryNode.disconnect()};D.prototype.getInputNode=function(){return this.inputNode};D.prototype.setParam=function(a,b,c,d){switch(a){case 0:b/=100,0>b&&(b=0),1e&&(e=-e),this.peakthis.speeds.length||this.speeds.shift(),this.speeds.push(a),this.lastX=this.obj.x,this.lastY=this.obj.y)};m.prototype.getSpeed=function(){if(!this.speeds.length)return 0;var a,b,c=0;a=0;for(b=this.speeds.length;athis.buffer.bufferObject.duration:this.instanceObject.ended;case V:return this.pgended;case aa:!0}return!0};x.prototype.canBeRecycled=function(){return this.fresh||this.stopped?!0:this.hasEnded()};x.prototype.setPannerEnabled=function(a){J===E&&(!this.pannerEnabled&&a?(this.pannerNode||(this.pannerNode=I.createPanner(),this.pannerNode.panningModel="number"===typeof this.pannerNode.panningModel?ha:["equalpower","HRTF","soundfield"][ha],this.pannerNode.distanceModel="number"===typeof this.pannerNode.distanceModel?ia:["linear","inverse","exponential"][ia],this.pannerNode.refDistance=ka,this.pannerNode.maxDistance=la,this.pannerNode.rolloffFactor=ma),this.gainNode.disconnect(),this.gainNode.connect(this.pannerNode),this.pannerNode.connect(d(this.tag)),this.pannerEnabled=!0):this.pannerEnabled&&!a&&(this.pannerNode.disconnect(),this.gainNode.disconnect(),this.gainNode.connect(d(this.tag)),this.pannerEnabled=!1))};x.prototype.setPan=function(a,b,c,d,e,f){this.pannerEnabled&&J===E&&(this.pannerNode.setPosition(a,b,0),this.pannerNode.setOrientation(Math.cos(cr.to_radians(c)),Math.sin(cr.to_radians(c)),0),this.pannerNode.coneInnerAngle=d,this.pannerNode.coneOuterAngle=e,this.pannerNode.coneOuterGain=f,this.panX=a,this.panY=b,this.panAngle=c,this.panConeInner=d,this.panConeOuter=e,this.panConeOuterGain=f)};x.prototype.setObject=function(a){this.pannerEnabled&&J===E&&(this.objectTracker||(this.objectTracker=new m),this.objectTracker.setObject(a))};x.prototype.tick=function(a){if(this.pannerEnabled&&J===E&&this.objectTracker&&this.objectTracker.hasObject()&&this.isPlaying()){this.objectTracker.tick(a);a=this.objectTracker.obj;var b=cr.rotatePtAround(a.x,a.y,-a.layer.getAngle(),Z,$,!0),c=cr.rotatePtAround(a.x,a.y,-a.layer.getAngle(),Z,$,!1);this.pannerNode.setPosition(b,c,0);b=0;"undefined"!==typeof this.objectTracker.obj.angle&&(b=a.angle-a.layer.getAngle(),this.pannerNode.setOrientation(Math.cos(b),Math.sin(b),0));this.pannerNode.setVelocity(this.objectTracker.getVelocityX(),this.objectTracker.getVelocityY(),0)}};x.prototype.play=function(a,b,c){var d=this.instanceObject;this.looping=a;this.volume=b;c=c||0;switch(this.myapi){case S:1!==d.playbackRate&&(d.playbackRate=1);d.volume!==b*Y&&(d.volume=b*Y);d.loop!==a&&(d.loop=a);d.muted&&(d.muted=!1);if(d.currentTime!==c)try{d.currentTime=c}catch(e){}this.instanceObject.play();break;case E:this.muted=!1;this.mutevol=1;if(this.buffer.myapi===E)this.fresh||(this.instanceObject=I.createBufferSource(),this.instanceObject.buffer=this.buffer.bufferObject,this.instanceObject.connect(this.gainNode)),this.instanceObject.loop=a,this.gainNode.gain.value=b*Y,0===c?h(this.instanceObject):k(this.instanceObject,c,this.getDuration());else{1!==d.playbackRate&&(d.playbackRate=1);d.loop!==a&&(d.loop=a);this.gainNode.gain.value=b*Y;if(d.currentTime!==c)try{d.currentTime=c}catch(f){}d.play()}break;case V:(!this.fresh&&this.stopped||0!==c)&&d.seekTo(c);d.play();this.pgended=!1;break;case aa:N.isDirectCanvas?AppMobi.context.playSound(this.src):AppMobi.player.playSound(this.src)}this.playbackRate=1;this.startTime=N.kahanTime.sum-c;this.is_paused=this.stopped=this.fresh=!1};x.prototype.stop=function(){switch(this.myapi){case S:this.instanceObject.paused||this.instanceObject.pause();break;case E:this.buffer.myapi===E?c(this.instanceObject):this.instanceObject.paused||this.instanceObject.pause();break;case V:this.instanceObject.stop()}this.stopped=!0;this.is_paused=!1};x.prototype.pause=function(){if(!(this.fresh||this.stopped||this.hasEnded()||this.is_paused)){switch(this.myapi){case S:this.instanceObject.paused||this.instanceObject.pause();break;case E:this.buffer.myapi===E?(this.resume_position=this.getPlaybackTime(),this.looping&&(this.resume_position%=this.getDuration()),c(this.instanceObject)):this.instanceObject.paused||this.instanceObject.pause();break;case V:this.instanceObject.pause()}this.is_paused=!0}};x.prototype.resume=function(){if(!this.fresh&&!this.stopped&&!this.hasEnded()&&this.is_paused){switch(this.myapi){case S:this.instanceObject.play();break;case E:this.buffer.myapi===E?(this.instanceObject=I.createBufferSource(),this.instanceObject.buffer=this.buffer.bufferObject,this.instanceObject.connect(this.gainNode),this.instanceObject.loop=this.looping,this.gainNode.gain.value=Y*this.volume*this.mutevol,this.startTime=N.kahanTime.sum-this.resume_position,k(this.instanceObject,this.resume_position,this.getDuration())):this.instanceObject.play();break;case V:this.instanceObject.play()}this.is_paused=!1}};x.prototype.seek=function(a){if(!(this.fresh||this.stopped||this.hasEnded()))switch(this.myapi){case S:try{this.instanceObject.currentTime=a}catch(b){}break;case E:if(this.buffer.myapi===E)this.is_paused?this.resume_position=a:(this.pause(),this.resume_position=a,this.resume());else try{this.instanceObject.currentTime=a}catch(c){}}};x.prototype.reconnect=function(a){this.myapi===E&&(this.pannerEnabled?(this.pannerNode.disconnect(),this.pannerNode.connect(a)):(this.gainNode.disconnect(),this.gainNode.connect(a)))};x.prototype.getDuration=function(){switch(this.myapi){case S:if("undefined"!==typeof this.instanceObject.duration)return this.instanceObject.duration;break;case E:return this.buffer.bufferObject.duration;case V:return this.instanceObject.getDuration()}return 0};x.prototype.getPlaybackTime=function(){var a=this.getDuration(),b=0;switch(this.myapi){case S:"undefined"!==typeof this.instanceObject.currentTime&&(b=this.instanceObject.currentTime);break;case E:if(this.buffer.myapi===E){if(this.is_paused)return this.resume_position;b=N.kahanTime.sum-this.startTime}else"undefined"!==typeof this.instanceObject.currentTime&&(b=this.instanceObject.currentTime)}!this.looping&&b>a&&(b=a);return b};x.prototype.isPlaying=function(){return!this.is_paused&&!this.fresh&&!this.stopped&&!this.hasEnded()};x.prototype.setVolume=function(a){this.volume=a;this.updateVolume()};x.prototype.updateVolume=function(){var a=this.volume*Y;switch(this.myapi){case S:this.instanceObject.volume&&this.instanceObject.volume!==a&&(this.instanceObject.volume=a);break;case E:this.gainNode.gain.value=a*this.mutevol}};x.prototype.getVolume=function(){return this.volume};x.prototype.doSetMuted=function(a){switch(this.myapi){case S:this.instanceObject.muted!==!!a&&(this.instanceObject.muted=!!a);break;case E:this.mutevol=a?0:1,this.gainNode.gain.value=Y*this.volume*this.mutevol}};x.prototype.setMuted=function(a){this.is_muted=!!a;this.doSetMuted(this.is_muted||this.is_silent)};x.prototype.setSilent=function(a){this.is_silent=!!a;this.doSetMuted(this.is_muted||this.is_silent)};x.prototype.setLooping=function(a){this.looping=a;switch(this.myapi){case S:this.instanceObject.loop!==!!a&&(this.instanceObject.loop=!!a);break;case E:this.instanceObject.loop!==!!a&&(this.instanceObject.loop=!!a)}};x.prototype.setPlaybackRate=function(a){this.playbackRate=a;this.updatePlaybackRate()};x.prototype.updatePlaybackRate=function(){var a=this.playbackRate;if(1===fa&&!this.is_music||2===fa)a*=N.timescale;switch(this.myapi){case S:this.instanceObject.playbackRate!==a&&(this.instanceObject.playbackRate=a);break;case E:this.buffer.myapi===E?this.instanceObject.playbackRate.value!==a&&(this.instanceObject.playbackRate.value=a):this.instanceObject.playbackRate!==a&&(this.instanceObject.playbackRate=a)}};x.prototype.setSuspended=function(a){switch(this.myapi){case S:a?this.isPlaying()?(this.instanceObject.pause(),this.resume_me=!0):this.resume_me=!1:this.resume_me&&this.instanceObject.play();break;case E:a?this.isPlaying()?(this.buffer.myapi===E?(this.resume_position=this.getPlaybackTime(),this.looping&&(this.resume_position%=this.getDuration()),c(this.instanceObject)):this.instanceObject.pause(),this.resume_me=!0):this.resume_me=!1:this.resume_me&&(this.buffer.myapi===E?(this.instanceObject=I.createBufferSource(),this.instanceObject.buffer=this.buffer.bufferObject,this.instanceObject.connect(this.gainNode),this.instanceObject.loop=this.looping,this.gainNode.gain.value=Y*this.volume*this.mutevol,this.startTime=N.kahanTime.sum-this.resume_position,k(this.instanceObject,this.resume_position,this.getDuration())):this.instanceObject.play());break;case V:a?this.isPlaying()?(this.instanceObject.pause(),this.resume_me=!0):this.resume_me=!1:this.resume_me&&this.instanceObject.play()}};w.Instance=function(a){this.type=a;N=this.runtime=a.runtime;R=this;this.listenerTracker=null;this.listenerZ=-600;I=null;"undefined"!==typeof AudioContext?(J=E,I=new AudioContext):"undefined"!==typeof webkitAudioContext&&(J=E,I=new webkitAudioContext);this.runtime.isiOS&&J===E&&document.addEventListener("touchstart",function(){if(!na){var a=I.createBuffer(1,1,22050),b=I.createBufferSource();b.buffer=a;b.connect(I.destination);h(b);na=!0}},!0);J!==E&&(this.runtime.isPhoneGap?J=V:this.runtime.isAppMobi&&(J=aa));J===V&&(U=location.href,a=U.lastIndexOf("/"),-1"!==b&&(a.playTagWhenReady=b,a.loopWhenReady=d,a.volumeWhenReady=e),null;l=new x(a,b);K.push(l);return l};var M=[];F.prototype.OnEnded=function(a){return cr.equals_nocase(W,a)};F.prototype.PreloadsComplete=function(){var a,b;a=0;for(b=da.length;a",b,!1)}};y.prototype.PreloadByName=function(a,b){if(!T){var c=1===a,d=this.runtime.files_subfolder+b.toLowerCase()+(X?".ogg":".m4a");J===aa?this.runtime.isDirectCanvas?AppMobi.context.loadSound(d):AppMobi.player.loadSound(d):J!==V&&this.getAudioInstance(d,"",c,!1)}};y.prototype.SetPlaybackRate=function(a,b){z(a);0>b&&(b=0);var c,d;c=0;for(d=M.length;cb||b>=ja.length)||(a=a.toLowerCase(),g/=100,0>g&&(g=0),1e&&(e=0),1f&&(f=0),1g&&(g=0),1d&&(d=0);1c&&(c=0),1c&&(c=0),1f&&(f=0),1b||b>=a.length||a[b].setParam(c,d,e,f)))};y.prototype.SetListenerObject=function(a){a&&J===E&&(a=a.getFirstPicked())&&(this.listenerTracker.setObject(a),Z=a.x,$=a.y)};y.prototype.SetListenerZ=function(a){this.listenerZ=a};w.acts=new y;H.prototype.Duration=function(a,b){z(b);M.length?a.set_float(M[0].getDuration()):a.set_float(0)};H.prototype.PlaybackTime=function(a,b){z(b);M.length?a.set_float(M[0].getPlaybackTime()):a.set_float(0)};H.prototype.Volume=function(a,c){z(c);if(M.length){var d=M[0].getVolume();a.set_float(b(d))}else a.set_float(0)};H.prototype.MasterVolume=function(a){a.set_float(Y)};H.prototype.EffectCount=function(a,b){b=b.toLowerCase();var c=null;L.hasOwnProperty(b)&&(c=L[b]);a.set_int(c?c.length:0)};H.prototype.AnalyserFreqBinCount=function(a,b,c){b=b.toLowerCase();c=Math.floor(c);b=Q(b,c);a.set_int(b?b.node.frequencyBinCount:0)};H.prototype.AnalyserFreqBinAt=function(a,b,c,d){b=b.toLowerCase();c=Math.floor(c);d=Math.floor(d);(b=Q(b,c))?0>d||d>=b.node.frequencyBinCount?a.set_float(0):a.set_float(b.freqBins[d]):a.set_float(0)};H.prototype.AnalyserPeakLevel=function(a,b,c){b=b.toLowerCase();c=Math.floor(c);(b=Q(b,c))?a.set_float(b.peak):a.set_float(0)};H.prototype.AnalyserRMSLevel=function(a,b,c){b=b.toLowerCase();c=Math.floor(c);(b=Q(b,c))?a.set_float(b.rms):a.set_float(0)};w.exps=new H})();cr.plugins_.Browser=function(a){this.runtime=a};(function(){function a(){}function b(){}function d(){"undefined"!==typeof jQuery&&k.setSize(jQuery(window).width(),jQuery(window).height())}function e(){}var f=cr.plugins_.Browser.prototype;f.Type=function(a){this.plugin=a;this.runtime=a.runtime};f.Type.prototype.onCreate=function(){};f.Instance=function(a){this.type=a;this.runtime=a.runtime};f.Instance.prototype.onCreate=function(){var a=this;window.addEventListener("resize",function(){a.runtime.trigger(cr.plugins_.Browser.prototype.cnds.OnResize,a)});"undefined"!==typeof navigator.onLine&&(window.addEventListener("online",function(){a.runtime.trigger(cr.plugins_.Browser.prototype.cnds.OnOnline,a)}),window.addEventListener("offline",function(){a.runtime.trigger(cr.plugins_.Browser.prototype.cnds.OnOffline,a)}));"undefined"!==typeof window.applicationCache&&(window.applicationCache.addEventListener("updateready",function(){a.runtime.loadingprogress=1;a.runtime.trigger(cr.plugins_.Browser.prototype.cnds.OnUpdateReady,a)}),window.applicationCache.addEventListener("progress",function(b){a.runtime.loadingprogress=b.loaded/b.total}));this.runtime.isDirectCanvas||(document.addEventListener("appMobi.device.update.available",function(){a.runtime.trigger(cr.plugins_.Browser.prototype.cnds.OnUpdateReady,a)}),document.addEventListener("menubutton",function(){a.runtime.trigger(cr.plugins_.Browser.prototype.cnds.OnMenuButton,a)}),document.addEventListener("searchbutton",function(){a.runtime.trigger(cr.plugins_.Browser.prototype.cnds.OnSearchButton,a)}));this.runtime.addSuspendCallback(function(b){b?a.runtime.trigger(cr.plugins_.Browser.prototype.cnds.OnPageHidden,a):a.runtime.trigger(cr.plugins_.Browser.prototype.cnds.OnPageVisible,a)});this.is_arcade="undefined"!==typeof window.is_scirra_arcade;this.fullscreenOldMarginCss=""};a.prototype.CookiesEnabled=function(){return navigator?navigator.cookieEnabled:!1};a.prototype.IsOnline=function(){return navigator?navigator.onLine:!1};a.prototype.HasJava=function(){return navigator?navigator.javaEnabled():!1};a.prototype.OnOnline=function(){return!0};a.prototype.OnOffline=function(){return!0};a.prototype.IsDownloadingUpdate=function(){return"undefined"===typeof window.applicationCache?!1:window.applicationCache.status===window.applicationCache.DOWNLOADING};a.prototype.OnUpdateReady=function(){return!0};a.prototype.PageVisible=function(){return!this.runtime.isSuspended};a.prototype.OnPageVisible=function(){return!0};a.prototype.OnPageHidden=function(){return!0};a.prototype.OnResize=function(){return!0};a.prototype.IsFullscreen=function(){return!!(document.mozFullScreen||document.webkitIsFullScreen||document.fullScreen||this.runtime.isNodeFullscreen)};a.prototype.OnMenuButton=function(){return!0};a.prototype.OnSearchButton=function(){return!0};a.prototype.IsMetered=function(){var a=navigator.connection||navigator.mozConnection||navigator.webkitConnection;return a?a.metered:!1};a.prototype.IsCharging=function(){var a=navigator.battery||navigator.mozBattery||navigator.webkitBattery;return a?a.charging:!0};f.cnds=new a;b.prototype.Alert=function(a){this.runtime.isDomFree||alert(a.toString())};b.prototype.Close=function(){this.runtime.isCocoonJs?CocoonJS.App.forceToFinish():this.runtime.isNodeWebkit?window.nwgui.App.quit():this.is_arcade||this.runtime.isDomFree||window.close()};b.prototype.Focus=function(){this.runtime.isNodeWebkit?window.nwgui.Window.get().focus():this.is_arcade||this.runtime.isDomFree||window.focus()};b.prototype.Blur=function(){this.runtime.isNodeWebkit?window.nwgui.Window.get().blur():this.is_arcade||this.runtime.isDomFree||window.blur()};b.prototype.GoBack=function(){this.is_arcade||(this.runtime.isDomFree||!window.back)||window.back()};b.prototype.GoForward=function(){this.is_arcade||(this.runtime.isDomFree||!window.forward)||window.forward()};b.prototype.GoHome=function(){this.is_arcade||(this.runtime.isDomFree||!window.home)||window.home()};b.prototype.GoToURL=function(a){this.runtime.isCocoonJs?CocoonJS.App.openURL(a):this.is_arcade||this.runtime.isDomFree||(window.location=a)};b.prototype.GoToURLWindow=function(a,b){this.runtime.isCocoonJs?CocoonJS.App.openURL(a):this.is_arcade||this.runtime.isDomFree||window.open(a,b)};b.prototype.Reload=function(){this.is_arcade||this.runtime.isDomFree||window.location.reload()};var h=!0,k=null;b.prototype.RequestFullScreen=function(a){this.runtime.isDomFree?cr.logexport("[Construct 2] Requesting fullscreen is not supported on this platform - the request has been ignored"):(2<=a&&(a+=1),6===a&&(a=2),this.runtime.isNodeWebkit?this.runtime.isNodeFullscreen||(window.nwgui.Window.get().enterFullscreen(),this.runtime.isNodeFullscreen=!0):document.mozFullScreen||(document.webkitIsFullScreen||document.fullScreen)||(this.fullscreenOldMarginCss=jQuery(this.runtime.canvasdiv).css("margin"),jQuery(this.runtime.canvasdiv).css("margin","0"),window.c2resizestretchmode=0a||a>=this.keyMap.length?!1:this.keyMap[a]};a.prototype.OnKeyCode=function(a){return a===this.triggerKey};a.prototype.OnKeyCodeReleased=function(a){return a===this.triggerKey};e.cnds=new a;e.acts=new function(){};b.prototype.LastKeyCode=function(a){a.set_int(this.triggerKey)};b.prototype.StringFromKeyCode=function(a,b){a.set_string(d(b))};e.exps=new b})();cr.plugins_.NodeWebkit=function(a){this.runtime=a};(function(){function a(){}function b(){}function d(){}var e=!1,f=null,h=null,k=null,c="",r="",n="\\",l=[],q="",v="",t=cr.plugins_.NodeWebkit.prototype;t.Type=function(a){this.plugin=a;this.runtime=a.runtime};t.Type.prototype.onCreate=function(){};t.Instance=function(a){this.type=a;this.runtime=a.runtime};var p=t.Instance.prototype;p.onCreate=function(){e=this.runtime.isNodeWebkit;var a=this;e&&(f=require("path"),h=require("fs"),k=require("child_process"),"win32"!==process.platform&&(n="/"),c=f.dirname(process.execPath)+n,r=(process.env.HOME||process.env.HOMEPATH||process.env.USERPROFILE)+n,window.ondrop=function(b){b.preventDefault();for(var c=0;cb||b>=l.length?a.set_string(""):a.set_string(l[b])};d.prototype.DroppedFile=function(a){a.set_string(q)};d.prototype.ChosenPath=function(a){a.set_string(v)};t.exps=new d})();cr.plugins_.Particles=function(a){this.runtime=a};(function(){function a(a){this.owner=a;this.active=!1;this.angle=this.speed=this.y=this.x=0;this.opacity=1;this.age=this.gs=this.size=this.grow=0;cr.seal(this)}function b(){}function d(){}function e(){}var f=cr.plugins_.Particles.prototype;f.Type=function(a){this.plugin=a;this.runtime=a.runtime};var h=f.Type.prototype;h.onCreate=function(){this.is_family||(this.texture_img=new Image,this.texture_img.src=this.texture_file,this.texture_img.cr_filesize=this.texture_filesize,this.webGL_texture=null,this.runtime.wait_for_textures.push(this.texture_img))};h.onLostWebGLContext=function(){this.is_family||(this.webGL_texture=null)};h.onRestoreWebGLContext=function(){this.is_family||!this.instances.length||this.webGL_texture||(this.webGL_texture=this.runtime.glwrap.loadTexture(this.texture_img,!0,this.runtime.linearSampling,this.texture_pixelformat))};h.loadTextures=function(){this.is_family||(this.webGL_texture||!this.runtime.glwrap)||(this.webGL_texture=this.runtime.glwrap.loadTexture(this.texture_img,!0,this.runtime.linearSampling,this.texture_pixelformat))};h.unloadTextures=function(){this.is_family||(this.instances.length||!this.webGL_texture)||(this.runtime.glwrap.deleteTexture(this.webGL_texture),this.webGL_texture=null)};h.preloadCanvas2D=function(a){a.drawImage(this.texture_img,0,0)};a.prototype.init=function(){var a=this.owner;this.x=a.x-a.xrandom/2+Math.random()*a.xrandom;this.y=a.y-a.yrandom/2+Math.random()*a.yrandom;this.speed=a.initspeed-a.speedrandom/2+Math.random()*a.speedrandom;this.angle=a.angle-a.spraycone/2+Math.random()*a.spraycone;this.opacity=a.initopacity;this.size=a.initsize-a.sizerandom/2+Math.random()*a.sizerandom;this.grow=a.growrate-a.growrandom/2+Math.random()*a.growrandom;this.age=this.gs=0};a.prototype.tick=function(a){var b=this.owner;this.x+=Math.cos(this.angle)*this.speed*a;this.y+=Math.sin(this.angle)*this.speed*a;this.y+=this.gs*a;this.speed+=b.acc*a;this.size+=this.grow*a;this.gs+=b.g*a;this.age+=a;1>this.size?this.active=!1:(0!==b.lifeanglerandom&&(this.angle+=Math.random()*b.lifeanglerandom*a-b.lifeanglerandom*a/2),0!==b.lifespeedrandom&&(this.speed+=Math.random()*b.lifespeedrandom*a-b.lifespeedrandom*a/2),0!==b.lifeopacityrandom&&(this.opacity+=Math.random()*b.lifeopacityrandom*a-b.lifeopacityrandom*a/2,0>this.opacity?this.opacity=0:1=b.destroymode&&this.age>=b.timeout&&(this.active=!1),2===b.destroymode&&0>=this.speed&&(this.active=!1))};a.prototype.draw=function(a){var b=this.owner.opacity*this.opacity;if(0!==b){0===this.owner.destroymode&&(b*=1-this.age/this.owner.timeout);a.globalAlpha=b;var b=this.x-this.size/2,d=this.y-this.size/2;this.owner.runtime.pixel_rounding&&(b=b+0.5|0,d=d+0.5|0);a.drawImage(this.owner.type.texture_img,b,d,this.size,this.size)}};a.prototype.drawGL=function(a){var b=this.owner.opacity*this.opacity;0===this.owner.destroymode&&(b*=1-this.age/this.owner.timeout);var d=this.size,e=d*this.owner.particlescale,f=this.x-d/2,h=this.y-d/2;this.owner.runtime.pixel_rounding&&(f=f+0.5|0,h=h+0.5|0);1>e||0===b||(ea.maxPointSize?(a.setOpacity(b),a.quad(f,h,f+d,h,f+d,h+d,f,h+d)):a.point(this.x,this.y,e,b))};a.prototype.left=function(){return this.x-this.size/2};a.prototype.right=function(){return this.x+this.size/2};a.prototype.top=function(){return this.y-this.size/2};a.prototype.bottom=function(){return this.y+this.size/2};f.Instance=function(a){this.type=a;this.runtime=a.runtime};var h=f.Instance.prototype,k=[];h.onCreate=function(){var a=this.properties;this.rate=a[0];this.spraycone=cr.to_radians(a[1]);this.spraytype=a[2];this.spraying=!0;this.initspeed=a[3];this.initsize=a[4];this.initopacity=a[5]/100;this.growrate=a[6];this.xrandom=a[7];this.yrandom=a[8];this.speedrandom=a[9];this.sizerandom=a[10];this.growrandom=a[11];this.acc=a[12];this.g=a[13];this.lifeanglerandom=a[14];this.lifespeedrandom=a[15];this.lifeopacityrandom=a[16];this.destroymode=a[17];this.timeout=a[18];this.particleCreateCounter=0;this.particlescale=1;this.particleBoxLeft=this.x;this.particleBoxTop=this.y;this.particleBoxRight=this.x;this.particleBoxBottom=this.y;this.add_bbox_changed_callback(function(a){a.bbox.set(a.particleBoxLeft,a.particleBoxTop,a.particleBoxRight,a.particleBoxBottom);a.bquad.set_from_rect(a.bbox);a.bbox_changed=!1});this.recycled||(this.particles=[]);this.runtime.tickMe(this);this.type.loadTextures();if(1===this.spraytype)for(a=0;athis.particleBoxRight&&(this.particleBoxRight=e.right()),e.top()this.particleBoxBottom&&(this.particleBoxBottom=e.bottom()),f++):k.push(e);this.particles.length=f;this.set_bbox_changed();this.first_tick=!1;1===this.spraytype&&0===this.particles.length&&this.runtime.DestroyInstance(this)};h.draw=function(a){var b,d,e,f=this.layer;b=0;for(d=this.particles.length;b=f.viewLeft&&(e.bottom()>=f.viewTop&&e.left()<=f.viewRight&&e.top()<=f.viewBottom)&&e.draw(a)};h.drawGL=function(a){this.particlescale=this.layer.getScale();a.setTexture(this.type.webGL_texture);var b,d,e,f=this.layer;b=0;for(d=this.particles.length;b=f.viewLeft&&(e.bottom()>=f.viewTop&&e.left()<=f.viewRight&&e.top()<=f.viewBottom)&&e.drawGL(a)};b.prototype.IsSpraying=function(){return this.spraying};f.cnds=new b;d.prototype.SetSpraying=function(a){this.spraying=0!==a};d.prototype.SetEffect=function(a){this.compositeOp=cr.effectToCompositeOp(a);cr.setGLBlend(this,a,this.runtime.gl);this.runtime.redraw=!0};d.prototype.SetRate=function(a){this.rate=a;var b;if(1===this.spraytype&&this.first_tick)if(athis.particles.length)for(a-=this.particles.length,b=0;ba.viewRight||b.top>a.viewBottom)this.runtime.glwrap.deleteTexture(this.mytex),this.mycanvas=this.myctx=this.mytex=null}};k.onDestroy=function(){this.mycanvas=this.myctx=null;this.runtime.glwrap&&this.mytex&&this.runtime.glwrap.deleteTexture(this.mytex);this.mytex=null};k.updateFont=function(){this.font=this.fontstyle+" "+this.ptSize.toString()+"pt "+this.facename;this.text_changed=!0;this.runtime.redraw=!0};k.draw=function(a,b){a.font=this.font;a.textBaseline="top";a.fillStyle=this.color;a.globalAlpha=b?1:this.opacity;var c=1;b&&(c=this.layer.getScale(),a.save(),a.scale(c,c));if(this.text_changed||this.width!==this.lastwrapwidth)this.type.plugin.WordWrap(this.text,this.lines,a,this.width,this.wrapbyword),this.text_changed=!1,this.lastwrapwidth=this.width;this.update_bbox();var c=b?0:this.bquad.tlx,d=b?0:this.bquad.tly;this.runtime.pixel_rounding&&(c=c+0.5|0,d=d+0.5|0);0===this.angle||b||(a.save(),a.translate(c,d),a.rotate(this.angle),d=c=0);var e=d+this.height,f=this.pxHeight,f=f+this.line_height_offset*this.runtime.devicePixelRatio,h,k;1===this.valign?d+=Math.max(this.height/2-this.lines.length*f/2,0):2===this.valign&&(d+=Math.max(this.height-this.lines.length*f-2,0));for(k=0;k=e-f);k++);(0!==this.angle||b)&&a.restore();this.last_render_tick=this.runtime.tickcount};k.drawGL=function(a){if(!(1>this.width||1>this.height)){var b=this.text_changed||this.need_text_redraw;this.need_text_redraw=!1;var c=this.layer.getScale(),d=this.layer.getAngle(),e=this.rcTex,f=c*this.width,h=c*this.height,k=Math.ceil(f),n=Math.ceil(h),s=this.runtime.width/2,m=this.runtime.height/2;this.myctx||(this.mycanvas=document.createElement("canvas"),this.mycanvas.width=k,this.mycanvas.height=n,this.lastwidth=k,this.lastheight=n,b=!0,this.myctx=this.mycanvas.getContext("2d"));if(k!==this.lastwidth||n!==this.lastheight)this.mycanvas.width=k,this.mycanvas.height=n,this.mytex&&(a.deleteTexture(this.mytex),this.mytex=null),b=!0;b&&(this.myctx.clearRect(0,0,k,n),this.draw(this.myctx,!0),this.mytex||(this.mytex=a.createEmptyTexture(k,n,this.runtime.linearSampling,this.runtime.isMobile)),a.videoToTexture(this.mycanvas,this.mytex,this.runtime.isMobile));this.lastwidth=k;this.lastheight=n;a.setTexture(this.mytex);a.setOpacity(this.opacity);a.resetModelView();a.translate(-s,-m);a.updateModelView();var r=this.bquad,b=this.layer.layerToCanvas(r.tlx,r.tly,!0),s=this.layer.layerToCanvas(r.tlx,r.tly,!1),m=this.layer.layerToCanvas(r.trx,r.try_,!0),x=this.layer.layerToCanvas(r.trx,r.try_,!1),z=this.layer.layerToCanvas(r.brx,r.bry,!0),G=this.layer.layerToCanvas(r.brx,r.bry,!1),C=this.layer.layerToCanvas(r.blx,r.bly,!0),r=this.layer.layerToCanvas(r.blx,r.bly,!1);if(this.runtime.pixel_rounding||0===this.angle&&0===d)var F=(b+0.5|0)-b,y=(s+0.5|0)-s,b=b+F,s=s+y,m=m+F,x=x+y,z=z+F,G=G+y,C=C+F,r=r+y;0===this.angle&&0===d?(m=b+k,x=s,z=m,G=s+n,C=b,r=G,e.right=1,e.bottom=1):(e.right=f/k,e.bottom=h/n);a.quadTex(b,s,m,x,z,G,C,r,e);a.resetModelView();a.scale(c,c);a.rotateZ(-this.layer.getAngle());a.translate((this.layer.viewLeft+this.layer.viewRight)/-2,(this.layer.viewTop+this.layer.viewBottom)/-2);a.updateModelView();this.last_render_tick=this.runtime.tickcount}};var r=[];h.TokeniseWords=function(a){r.length=0;for(var b="",c,d=0;d=f)b(d);else{if(100>=c.length&&-1===c.indexOf("\n")){var g=e.measureText(c).width;if(g<=f){b(d);d.push(a());d[0].text=c;d[0].width=g;return}}this.WrapText(c,d,e,f,h)}else b(d)};h.WrapText=function(b,c,d,e,f){f&&(this.TokeniseWords(b),b=r);var g="",h,k,B,s=0;for(B=0;B=c.length&&c.push(a()),k=c[s],k.text=g,k.width=d.measureText(g).width,s++,g=""):(h=g,g+=b[B],k=d.measureText(g).width,k>=e&&(s>=c.length&&c.push(a()),k=c[s],k.text=h,k.width=d.measureText(h).width,s++,g=b[B],f||" "!==g||(g="")));g.length&&(s>=c.length&&c.push(a()),k=c[s],k.text=g,k.width=d.measureText(g).width,s++);for(B=s;Ba&&(a=Math.round(1E10*a)/1E10);a=a.toString();this.text!==a&&(this.text=a,this.text_changed=!0,this.runtime.redraw=!0)};e.prototype.AppendText=function(a){cr.is_number(a)&&(a=Math.round(1E10*a)/1E10);if(a=a.toString())this.text+=a,this.text_changed=!0,this.runtime.redraw=!0};e.prototype.SetFontFace=function(a,b){var c="";switch(b){case 1:c="bold";break;case 2:c="italic";break;case 3:c="bold italic"}if(a!==this.facename||c!==this.fontstyle)this.facename=a,this.fontstyle=c,this.updateFont()};e.prototype.SetFontSize=function(a){this.ptSize!==a&&(this.ptSize=a,this.pxHeight=Math.ceil(96*(this.ptSize/72))+4,this.updateFont())};e.prototype.SetFontColor=function(a){a="rgb("+cr.GetRValue(a).toString()+","+cr.GetGValue(a).toString()+","+cr.GetBValue(a).toString()+")";a!==this.color&&(this.color=a,this.need_text_redraw=!0,this.runtime.redraw=!0)};e.prototype.SetWebFont=function(a,b){if(this.runtime.isDomFree)cr.logexport("[Construct 2] Text plugin: 'Set web font' not supported on this platform - the action has been ignored");else{var d=this,e=function(){d.runtime.redraw=!0;d.text_changed=!0};if(c.hasOwnProperty(b)){var f="'"+a+"'";if(this.facename!==f)for(this.facename=f,this.updateFont(),f=1;10>f;f++)setTimeout(e,100*f),setTimeout(e,1E3*f)}else for(f=document.createElement("link"),f.href=b,f.rel="stylesheet",f.type="text/css",f.onload=e,document.getElementsByTagName("head")[0].appendChild(f),c[b]=!0,this.facename="'"+a+"'",this.updateFont(),f=1;10>f;f++)setTimeout(e,100*f),setTimeout(e,1E3*f)}};e.prototype.SetEffect=function(a){this.compositeOp=cr.effectToCompositeOp(a);cr.setGLBlend(this,a,this.runtime.gl);this.runtime.redraw=!0};h.acts=new e;f.prototype.Text=function(a){a.set_string(this.text)};f.prototype.FaceName=function(a){a.set_string(this.facename)};f.prototype.FaceSize=function(a){a.set_int(this.ptSize)};f.prototype.TextWidth=function(a){var b=0,c,d,e;c=0;for(d=this.lines.length;cc-b.time||(b.lasttime=b.time,b.lastx=b.x,b.lasty=b.y,b.time=c,b.x=a.pageX-d.left,b.y=a.pageY-d.top)}}};h.onPointerStart=function(a){if(a.pointerType!==a.MSPOINTER_TYPE_MOUSE){a.preventDefault&&a.preventDefault();var b=this.runtime.isDomFree?k:jQuery(this.runtime.canvas).offset(),c=a.pageX-b.left,b=a.pageY-b.top,d=cr.performance_now();this.trigger_index=this.touches.length;this.trigger_id=a.pointerId;this.touches.push({time:d,x:c,y:b,lasttime:d,lastx:c,lasty:b,id:a.pointerId,startindex:this.trigger_index});this.runtime.trigger(cr.plugins_.Touch.prototype.cnds.OnNthTouchStart,this);this.runtime.trigger(cr.plugins_.Touch.prototype.cnds.OnTouchStart,this);this.curTouchX=c;this.curTouchY=b;this.runtime.trigger(cr.plugins_.Touch.prototype.cnds.OnTouchObject,this)}};h.onPointerEnd=function(a){a.pointerType!==a.MSPOINTER_TYPE_MOUSE&&(a.preventDefault&&a.preventDefault(),a=this.findTouch(a.pointerId),this.trigger_index=0<=a?this.touches[a].startindex:-1,this.trigger_id=0<=a?this.touches[a].id:-1,this.runtime.trigger(cr.plugins_.Touch.prototype.cnds.OnNthTouchEnd,this),this.runtime.trigger(cr.plugins_.Touch.prototype.cnds.OnTouchEnd,this),0<=a&&this.touches.splice(a,1))};h.onTouchMove=function(a){a.preventDefault&&a.preventDefault();var b=cr.performance_now(),c,d,e,f;c=0;for(d=a.changedTouches.length;cb-f.time||(f.lasttime=f.time,f.lastx=f.x,f.lasty=f.y,f.time=b,f.x=e.pageX-h.left,f.y=e.pageY-h.top)}};h.onTouchStart=function(a){a.preventDefault&&a.preventDefault();var b=this.runtime.isDomFree?k:jQuery(this.runtime.canvas).offset(),c=cr.performance_now(),d,e,f,h;d=0;for(e=a.changedTouches.length;da||a>=this.touches.length)return!1;var d=this.touches[a];a=cr.distanceTo(d.x,d.y,d.lastx,d.lasty);var d=(d.time-d.lasttime)/1E3,e=0;0=a+1};f.cnds=new d;e.prototype.TouchCount=function(a){a.set_int(this.touches.length)};e.prototype.X=function(a,b){if(this.touches.length){var c,d,e,f,h;cr.is_undefined(b)?(c=this.runtime.getLayerByNumber(0),d=c.scale,e=c.zoomRate,f=c.parallaxX,h=c.angle,c.scale=this.runtime.running_layout.scale,c.zoomRate=1,c.parallaxX=1,c.angle=this.runtime.running_layout.angle,a.set_float(c.canvasToLayer(this.touches[0].x,this.touches[0].y,!0)),c.scale=d,c.zoomRate=e,c.parallaxX=f,c.angle=h):(c=cr.is_number(b)?this.runtime.getLayerByNumber(b):this.runtime.getLayerByName(b))?a.set_float(c.canvasToLayer(this.touches[0].x,this.touches[0].y,!0)):a.set_float(0)}else a.set_float(0)};e.prototype.XAt=function(a,b,c){b=Math.floor(b);if(0>b||b>=this.touches.length)a.set_float(0);else{var d,e,f,h;cr.is_undefined(c)?(c=this.runtime.getLayerByNumber(0),d=c.scale,e=c.zoomRate,f=c.parallaxX,h=c.angle,c.scale=this.runtime.running_layout.scale,c.zoomRate=1,c.parallaxX=1,c.angle=this.runtime.running_layout.angle,a.set_float(c.canvasToLayer(this.touches[b].x,this.touches[b].y,!0)),c.scale=d,c.zoomRate=e,c.parallaxX=f,c.angle=h):(c=cr.is_number(c)?this.runtime.getLayerByNumber(c):this.runtime.getLayerByName(c))?a.set_float(c.canvasToLayer(this.touches[b].x,this.touches[b].y,!0)):a.set_float(0)}};e.prototype.XForID=function(a,b,c){b=this.findTouch(b);if(0>b)a.set_float(0);else{b=this.touches[b];var d,e,f,h;cr.is_undefined(c)?(c=this.runtime.getLayerByNumber(0),d=c.scale,e=c.zoomRate,f=c.parallaxX,h=c.angle,c.scale=this.runtime.running_layout.scale,c.zoomRate=1,c.parallaxX=1,c.angle=this.runtime.running_layout.angle,a.set_float(c.canvasToLayer(b.x,b.y,!0)),c.scale=d,c.zoomRate=e,c.parallaxX=f,c.angle=h):(c=cr.is_number(c)?this.runtime.getLayerByNumber(c):this.runtime.getLayerByName(c))?a.set_float(c.canvasToLayer(b.x,b.y,!0)):a.set_float(0)}};e.prototype.Y=function(a,b){if(this.touches.length){var c,d,e,f,h;cr.is_undefined(b)?(c=this.runtime.getLayerByNumber(0),d=c.scale,e=c.zoomRate,f=c.parallaxY,h=c.angle,c.scale=this.runtime.running_layout.scale,c.zoomRate=1,c.parallaxY=1,c.angle=this.runtime.running_layout.angle,a.set_float(c.canvasToLayer(this.touches[0].x,this.touches[0].y,!1)),c.scale=d,c.zoomRate=e,c.parallaxY=f,c.angle=h):(c=cr.is_number(b)?this.runtime.getLayerByNumber(b):this.runtime.getLayerByName(b))?a.set_float(c.canvasToLayer(this.touches[0].x,this.touches[0].y,!1)):a.set_float(0)}else a.set_float(0)};e.prototype.YAt=function(a,b,c){b=Math.floor(b);if(0>b||b>=this.touches.length)a.set_float(0);else{var d,e,f,h;cr.is_undefined(c)?(c=this.runtime.getLayerByNumber(0),d=c.scale,e=c.zoomRate,f=c.parallaxY,h=c.angle,c.scale=this.runtime.running_layout.scale,c.zoomRate=1,c.parallaxY=1,c.angle=this.runtime.running_layout.angle,a.set_float(c.canvasToLayer(this.touches[b].x,this.touches[b].y,!1)),c.scale=d,c.zoomRate=e,c.parallaxY=f,c.angle=h):(c=cr.is_number(c)?this.runtime.getLayerByNumber(c):this.runtime.getLayerByName(c))?a.set_float(c.canvasToLayer(this.touches[b].x,this.touches[b].y,!1)):a.set_float(0)}};e.prototype.YForID=function(a,b,c){b=this.findTouch(b);if(0>b)a.set_float(0);else{b=this.touches[b];var d,e,f,h;cr.is_undefined(c)?(c=this.runtime.getLayerByNumber(0),d=c.scale,e=c.zoomRate,f=c.parallaxY,h=c.angle,c.scale=this.runtime.running_layout.scale,c.zoomRate=1,c.parallaxY=1,c.angle=this.runtime.running_layout.angle,a.set_float(c.canvasToLayer(b.x,b.y,!1)),c.scale=d,c.zoomRate=e,c.parallaxY=f,c.angle=h):(c=cr.is_number(c)?this.runtime.getLayerByNumber(c):this.runtime.getLayerByName(c))?a.set_float(c.canvasToLayer(b.x,b.y,!1)):a.set_float(0)}};e.prototype.AbsoluteX=function(a){this.touches.length?a.set_float(this.touches[0].x):a.set_float(0)};e.prototype.AbsoluteXAt=function(a,b){b=Math.floor(b);0>b||b>=this.touches.length?a.set_float(0):a.set_float(this.touches[b].x)};e.prototype.AbsoluteXForID=function(a,b){var c=this.findTouch(b);0>c?a.set_float(0):a.set_float(this.touches[c].x)};e.prototype.AbsoluteY=function(a){this.touches.length?a.set_float(this.touches[0].y):a.set_float(0)};e.prototype.AbsoluteYAt=function(a,b){b=Math.floor(b);0>b||b>=this.touches.length?a.set_float(0):a.set_float(this.touches[b].y)};e.prototype.AbsoluteYForID=function(a,b){var c=this.findTouch(b);0>c?a.set_float(0):a.set_float(this.touches[c].y)};e.prototype.SpeedAt=function(a,b){b=Math.floor(b);if(0>b||b>=this.touches.length)a.set_float(0);else{var c=this.touches[b],d=cr.distanceTo(c.x,c.y,c.lastx,c.lasty),c=(c.time-c.lasttime)/1E3;0===c?a.set_float(0):a.set_float(d/c)}};e.prototype.SpeedForID=function(a,b){var c=this.findTouch(b);if(0>c)a.set_float(0);else{var d=this.touches[c],c=cr.distanceTo(d.x,d.y,d.lastx,d.lasty),d=(d.time-d.lasttime)/1E3;0===d?a.set_float(0):a.set_float(c/d)}};e.prototype.AngleAt=function(a,b){b=Math.floor(b);if(0>b||b>=this.touches.length)a.set_float(0);else{var c=this.touches[b];a.set_float(cr.to_degrees(cr.angleTo(c.lastx,c.lasty,c.x,c.y)))}};e.prototype.AngleForID=function(a,b){var c=this.findTouch(b);0>c?a.set_float(0):(c=this.touches[c],a.set_float(cr.to_degrees(cr.angleTo(c.lastx,c.lasty,c.x,c.y))))};e.prototype.Alpha=function(a){a.set_float(this.getAlpha())};e.prototype.Beta=function(a){a.set_float(this.getBeta())};e.prototype.Gamma=function(a){a.set_float(this.getGamma())};e.prototype.AccelerationXWithG=function(a){a.set_float(this.acc_g_x)};e.prototype.AccelerationYWithG=function(a){a.set_float(this.acc_g_y)};e.prototype.AccelerationZWithG=function(a){a.set_float(this.acc_g_z)};e.prototype.AccelerationX=function(a){a.set_float(this.acc_x)};e.prototype.AccelerationY=function(a){a.set_float(this.acc_y)};e.prototype.AccelerationZ=function(a){a.set_float(this.acc_z)};e.prototype.TouchIndex=function(a){a.set_int(this.trigger_index)};e.prototype.TouchID=function(a){a.set_float(this.trigger_id)};f.exps=new e})();cr.getProjectModel=function(){return[null,"Loader",[[cr.plugins_.Browser,!0,!1,!1,!1,!1,!1,!1,!1,!1],[cr.plugins_.Audio,!0,!1,!1,!1,!1,!1,!1,!1,!1],[cr.plugins_.Keyboard,!0,!1,!1,!1,!1,!1,!1,!1,!1],[cr.plugins_.NodeWebkit,!0,!1,!1,!1,!1,!1,!1,!1,!1],[cr.plugins_.Particles,!1,!0,!0,!1,!0,!0,!0,!0,!0],[cr.plugins_.Text,!1,!0,!0,!0,!0,!0,!0,!0,!1],[cr.plugins_.Touch,!0,!1,!1,!1,!1,!1,!1,!1,!1]],[["t0",cr.plugins_.Browser,!1,[],0,0,null,null,[],!1,!1,0xe7508259f1049,[],[]],["t1",cr.plugins_.Keyboard,!1,[],0,0,null,null,[],!1,!1,8268562079040081,[],[]],["t2",cr.plugins_.Particles,!1,[],0,0,["c2.png",29833,0],null,[],!1,!1,723074460476516,[]],["t3",cr.plugins_.Audio,!1,[],0,0,null,null,[],!1,!1,0x87d7a273e6e76,[],[0,1,1,600,600,1E4,1,5E3,1]],["t4",cr.plugins_.NodeWebkit,!1,[],0,0,null,null,[],!1,!1,0x418c17d077bfa,[],[]],["t5",cr.plugins_.Particles,!1,[],0,0,["nw.png",2403,0],null,[],!1,!1,7413958693205721,[]],["t6",cr.plugins_.Text,!1,[],0,0,null,null,[],!1,!1,0xadbe7ef820faf,[]],["t7",cr.plugins_.Touch,!1,[],0,0,null,null,[],!1,!1,4854103537132793,[],[1]]],[],[["Loader",800,600,!1,"Loader_events",9256725879576544,[["Loader",0,8771806647801429,!0,[0,0,0],!1,1,1,1,!1,1,0,0,[],[]]],[],[]],["Veille",800,600,!1,"Veille_events",306290045656015,[["Veille",0,0x9c56068fd6acc,!0,[0,0,0],!1,1,1,1,!1,1,1,0,[[[400,-75,0,32,320,0,1.5708,1,0,0.5,0,0,[]],2,1,[],[],[5,120,0,250,40,0,-2,40,0,0,8,0,-150,100,2,800,2,0,5]],[[400,700,0,32,320,0,-1.5708,1,0,0.5,0,0,[]],5,7,[],[],[5,120,0,250,40,0,-2,40,0,0,8,0,-150,-100,2,800,2,0,5]],[[579,30,0,200,540,0,0,0.5,0,0,1,0,[]],6,4,[],[],["",1,"bold 12pt Arial","rgb(255,255,255)",1,1,0,0,0]]],[["warpripple","WarpRipple",[1,0.01,-5]]]]],[],[]],["Exit",800,600,!1,"Exit_events",6113943557441396,[["Exit",0,0xb124a9f08ea67,!0,[0,0,0],!1,1,1,1,!1,1,0,0,[],[]]],[],[]]],[["Loader_events",[[1,"NumVeille",0,-2,!1,!1,8964089995488567],[1,"NWfile",1,"",!1,!1,0xe4fbfc76f13ab],[0,null,!1,0x4dfd65888a214,[[-1,cr.system_object.prototype.cnds.OnLayoutStart,null,1,!1,!1,!1,9184290607372780]],[[0,cr.plugins_.Browser.prototype.acts.RequestFullScreen,null,7636186985065621,[[3,5]]]]],[0,null,!1,0x5bb13aa690f60,[[-1,cr.system_object.prototype.cnds.OnLoadFinished,null,1,!1,!1,!1,0x82e0442152500]],[[-1,cr.system_object.prototype.acts.Wait,null,9840344659686878,[[0,[1,0.01]]]],[-1,cr.system_object.prototype.acts.GoToLayout,null,0x5200955dfe05a,[[6,"Veille"]]]]]]],["Veille_events",[[1,"RMS",0,0,!1,!1,8272711222301935],[1,"Restart",0,0,!1,!1,5338948241480027],[0,null,!1,9883295321069700,[[-1,cr.system_object.prototype.cnds.OnLayoutStart,null,1,!1,!1,!1,0xd39cedc82dd33]],[[3,cr.plugins_.Audio.prototype.acts.PlayByName,null,5202980257560176,[[3,1],[1,[2,"Evening_Fall_Harp"]],[3,0],[0,[0,0]],[1,[2,""]]]],[2,cr.plugins_.Particles.prototype.acts.SetRate,null,0xc15240e82c978,[[0,[0,5]]]],[5,cr.plugins_.Particles.prototype.acts.SetRate,null,0x6ff8687e80572,[[0,[0,5]]]],[2,cr.plugins_.Particles.prototype.acts.SetSpraying,null,5500294466648475,[[3,1]]],[5,cr.plugins_.Particles.prototype.acts.SetSpraying,null,4830535051445549,[[3,1]]]]],[0,[!1,"Start"],!1,5670715518694284,[[-1,cr.system_object.prototype.cnds.IsGroupActive,null,0,!1,!1,!1,5670715518694284,[[1,[2,"Start"]]]]],[],[[0,null,!1,8365481790965971,[[-1,cr.system_object.prototype.cnds.Compare,null,0,!1,!1,!1,681938087295472,[[7,[19,cr.system_object.prototype.exps.layeropacity,[[2,"Veille"]]]],[8,0],[7,[0,100]]]]],[[-1,cr.system_object.prototype.acts.SetGroupActive,null,0x939cfc17f135c,[[1,[2,"Start"]],[3,0]]]]],[0,null,!1,0xaccce4249a717,[[-1,cr.system_object.prototype.cnds.Else,null,0,!1,!1,!1,9645145373693224],[-1,cr.system_object.prototype.cnds.EveryTick,null,0,!1,!1,!1,0xc1f6300af58e8]],[[-1,cr.system_object.prototype.acts.SetLayerOpacity,null,0x50fa048c371f6,[[5,[2,"Veille"]],[0,[4,[19,cr.system_object.prototype.exps.layeropacity,[[2,"Veille"]]],[0,1]]]]],[2,cr.plugins_.Particles.prototype.acts.SetInitOpacity,null,0xa82f69b81a210,[[0,[5,[19,cr.system_object.prototype.exps.layeropacity,[[2,"Veille"]]],[0,10]]]]],[5,cr.plugins_.Particles.prototype.acts.SetInitOpacity,null,9087421245893464,[[0,[5,[19,cr.system_object.prototype.exps.layeropacity,[[2,"Veille"]]],[0,10]]]]]]]]],[0,[!1,"Body"],!1,7028360535295121,[[-1,cr.system_object.prototype.cnds.IsGroupActive,null,0,!1,!1,!1,7028360535295121,[[1,[2,"Body"]]]]],[],[[0,null,!1,0x51e8a6e11ca45,[[1,cr.plugins_.Keyboard.prototype.cnds.OnKey,null,1,!1,!1,!1,0xaf5a5a8d1a494,[[9,13]]]],[],[[0,null,!1,6545261610976676,[[6,cr.plugins_.Text.prototype.cnds.IsVisible,null,0,!1,!0,!1,57733252997832]],[[6,cr.plugins_.Text.prototype.acts.SetVisible,null,0x8bbeaf0a9536a,[[3,1]]]]],[0,null,!1,7903978118133453,[[-1,cr.system_object.prototype.cnds.Else,null,0,!1,!1,!1,7093124615072028]],[[6,cr.plugins_.Text.prototype.acts.SetVisible,null,9016231935903996,[[3,0]]]]]]],[0,null,!1,0xc3a65bf4f2a22,[[-1,cr.system_object.prototype.cnds.Every,null,0,!1,!1,!1,7317966506217073,[[0,[1,1]]]]],[[2,cr.plugins_.Particles.prototype.acts.SetRate,null,8632633260751814,[[0,[19,cr.system_object.prototype.exps.clamp,[[18,[16,[7,[4,[20,2,cr.plugins_.Particles.prototype.exps.Rate,!1,null],[20,2,cr.plugins_.Particles.prototype.exps.ParticleCount,!1,null]],[7,[19,cr.system_object.prototype.exps.fps],[0,25]]],[19,cr.system_object.prototype.exps.fps]],[5,[20,2,cr.plugins_.Particles.prototype.exps.Rate,!1,null],[19,cr.system_object.prototype.exps.random,[[0,1],[0,2]]]],[4,[20,2,cr.plugins_.Particles.prototype.exps.Rate,!1,null],[19,cr.system_object.prototype.exps.random,[[0,1],[0,2]]]]],[0,5],[0,25]]]]]],[5,cr.plugins_.Particles.prototype.acts.SetRate,null,6062215411365679,[[0,[19,cr.system_object.prototype.exps.clamp,[[18,[16,[7,[4,[20,5,cr.plugins_.Particles.prototype.exps.Rate,!1,null],[20,5,cr.plugins_.Particles.prototype.exps.ParticleCount,!1,null]],[7,[19,cr.system_object.prototype.exps.fps],[0,25]]],[19,cr.system_object.prototype.exps.fps]],[5,[20,5,cr.plugins_.Particles.prototype.exps.Rate,!1,null],[19,cr.system_object.prototype.exps.random,[[0,1],[0,2]]]],[4,[20,5,cr.plugins_.Particles.prototype.exps.Rate,!1,null],[19,cr.system_object.prototype.exps.random,[[0,1],[0,2]]]]],[0,5],[0,25]]]]]],[6,cr.plugins_.Text.prototype.acts.SetText,null,9405966255769602,[[7,[10,[10,[10,[10,[10,[10,[10,[10,[10,[10,[10,[10,[10,[10,[10,[10,[10,[2,"#"],[20,2,cr.plugins_.Particles.prototype.exps.ParticleCount,!1,null]],[2," +"]],[19,cr.system_object.prototype.exps["int"],[[20,2,cr.plugins_.Particles.prototype.exps.Rate,!1,null]]]],[2,"/sec"]],[19,cr.system_object.prototype.exps.newline]],[19,cr.system_object.prototype.exps.newline]],[2,"FPS:"]],[19,cr.system_object.prototype.exps.fps]],[2," RMS:"]],[7,[19,cr.system_object.prototype.exps.floor,[[6,[23,"RMS"],[0,100]]]],[0,100]]],[19,cr.system_object.prototype.exps.newline]],[19,cr.system_object.prototype.exps.newline]],[2,"#"]],[20,5,cr.plugins_.Particles.prototype.exps.ParticleCount,!1,null]],[2," +"]],[19,cr.system_object.prototype.exps["int"],[[20,5,cr.plugins_.Particles.prototype.exps.Rate,!1,null]]]],[2,"/sec"]]]]]]],[0,null,!1,0x928915e9cd3bb,[[-1,cr.system_object.prototype.cnds.EveryTick,null,0,!1,!1,!1,6917607399799475],[3,cr.plugins_.Audio.prototype.cnds.AdvancedAudioSupported,null,0,!1,!1,!1,9849462572130356]],[[-1,cr.system_object.prototype.acts.SetVar,null,9168531653053006,[[11,"RMS"],[7,[20,3,cr.plugins_.Audio.prototype.exps.AnalyserRMSLevel,!1,null,[[2,"a"],[0,0]]]]]],[2,cr.plugins_.Particles.prototype.acts.SetGravity,null,0xcf6eb9b13cfb,[[0,[6,[23,"RMS"],[1,-2.1]]]]],[5,cr.plugins_.Particles.prototype.acts.SetGravity,null,8351761715539176,[[0,[6,[23,"RMS"],[1,2.1]]]]]]],[0,null,!1,8242976808981609,[[-1,cr.system_object.prototype.cnds.Compare,null,0,!1,!1,!1,5987721542830278,[[7,[19,cr.system_object.prototype.exps.time]],[8,5],[7,[0,140]]]],[-1,cr.system_object.prototype.cnds.CompareVar,null,0,!1,!1,!1,8881592830623784,[[11,"Restart"],[8,0],[7,[0,0]]]]],[[-1,cr.system_object.prototype.acts.SetVar,null,8871831640987063,[[11,"Restart"],[7,[0,1]]]]]],[0,null,!0,0xf980861b0c5a4,[[1,cr.plugins_.Keyboard.prototype.cnds.OnKey,null,1,!1,!1,!1,7240018894178457,[[9,27]]],[7,cr.plugins_.Touch.prototype.cnds.IsInTouch,null,0,!1,!1,!1,0xe41159773516],[-1,cr.system_object.prototype.cnds.CompareVar,null,0,!1,!1,!1,0xa522af7e93f2e,[[11,"Restart"],[8,0],[7,[0,1]]]]],[[-1,cr.system_object.prototype.acts.SetVar,null,0x9f9a93e787142,[[11,"Restart"],[7,[3,[23,"Restart"]]]]],[-1,cr.system_object.prototype.acts.SetGroupActive,null,4593935824136996,[[1,[2,"Start"]],[3,0]]],[-1,cr.system_object.prototype.acts.SetGroupActive,null,7749099240835881,[[1,[2,"Body"]],[3,0]]],[2,cr.plugins_.Particles.prototype.acts.SetSpraying,null,6865126891265061,[[3,0]]],[5,cr.plugins_.Particles.prototype.acts.SetSpraying,null,0x44b2c5ab55ee5,[[3,0]]],[-1,cr.system_object.prototype.acts.SetGroupActive,null,9864700388762392,[[1,[2,"End"]],[3,1]]]]]]],[0,[!1,"End"],!1,0x7309bde3e92e,[[-1,cr.system_object.prototype.cnds.IsGroupActive,null,0,!1,!1,!1,0x7309bde3e92e,[[1,[2,"End"]]]]],[],[[0,null,!1,0x7321b49aa09e1,[[-1,cr.system_object.prototype.cnds.Compare,null,0,!1,!1,!1,4511261893892535,[[7,[19,cr.system_object.prototype.exps.layeropacity,[[2,"Veille"]]]],[8,0],[7,[0,0]]]]],[[-1,cr.system_object.prototype.acts.SetGroupActive,null,4772013783097554,[[1,[2,"End"]],[3,0]]],[3,cr.plugins_.Audio.prototype.acts.StopAll,null,832829439830883]],[[0,null,!1,7259102832799663,[[-1,cr.system_object.prototype.cnds.CompareVar,null,0,!1,!1,!1,0x5012013f72140,[[11,"Restart"],[8,0],[7,[0,0]]]]],[[-1,cr.system_object.prototype.acts.GoToLayoutByName,null,0xe7d6f7283c74b,[[1,[2,"exit"]]]]]],[0,null,!1,8363975973112161,[[-1,cr.system_object.prototype.cnds.Else,null,0,!1,!1,!1,0x60cf0d437e8dc]],[[3,cr.plugins_.Audio.prototype.acts.StopAll,null,5893738282654444],[0,cr.plugins_.Browser.prototype.acts.Reload,null,0x62cf4bced8610]]]]],[0,null,!1,7218792687155007,[[-1,cr.system_object.prototype.cnds.Else,null,0,!1,!1,!1,7503399522701357],[-1,cr.system_object.prototype.cnds.EveryTick,null,0,!1,!1,!1,5378603673816071]],[[-1,cr.system_object.prototype.acts.SetLayerOpacity,null,0xccdd38cda519a,[[5,[2,"Veille"]],[0,[5,[19,cr.system_object.prototype.exps.layeropacity,[[2,"Veille"]]],[0,1]]]]],[3,cr.plugins_.Audio.prototype.acts.SetVolume,null,5245460560361351,[[1,[2,"a"]],[0,[5,[20,3,cr.plugins_.Audio.prototype.exps.Volume,!1,null,[[2,"a"]]],[1,0.25]]]]]]]]],[0,null,!1,0xf82f8373efea5,[[7,cr.plugins_.Touch.prototype.cnds.IsInTouch,null,0,!1,!1,!1,0xb4decc1bcddd3]],[[-1,cr.system_object.prototype.acts.GoToLayoutByName,null,0x59bbef9fef967,[[1,[2,"exit"]]]]]]]],["Exit_events",[[0,null,!1,0xe7e976c27bdd5,[[-1,cr.system_object.prototype.cnds.OnLayoutStart,null,1,!1,!1,!1,7777797351584432]],[[-1,cr.system_object.prototype.acts.Wait,null,5810891606874927,[[0,[1,0.01]]]],[0,cr.plugins_.Browser.prototype.acts.Close,null,82255096705349]]]]]],"",!0,800,600,2,!0,!0,!1,"1.1",0,!0,3,!0,8,!1,[]]}; \ No newline at end of file diff --git a/tests/automatic_tests/nw-in-mem/package/evening_fall_harp.ogg b/tests/automatic_tests/nw-in-mem/package/evening_fall_harp.ogg new file mode 100644 index 0000000000..003803ce2e Binary files /dev/null and b/tests/automatic_tests/nw-in-mem/package/evening_fall_harp.ogg differ diff --git a/tests/automatic_tests/nw-in-mem/package/icon-114.png b/tests/automatic_tests/nw-in-mem/package/icon-114.png new file mode 100644 index 0000000000..bf56364da4 Binary files /dev/null and b/tests/automatic_tests/nw-in-mem/package/icon-114.png differ diff --git a/tests/automatic_tests/nw-in-mem/package/icon-128.png b/tests/automatic_tests/nw-in-mem/package/icon-128.png new file mode 100644 index 0000000000..523ffd0ff7 Binary files /dev/null and b/tests/automatic_tests/nw-in-mem/package/icon-128.png differ diff --git a/tests/automatic_tests/nw-in-mem/package/icon-16.png b/tests/automatic_tests/nw-in-mem/package/icon-16.png new file mode 100644 index 0000000000..e1b785cc71 Binary files /dev/null and b/tests/automatic_tests/nw-in-mem/package/icon-16.png differ diff --git a/tests/automatic_tests/nw-in-mem/package/icon-32.png b/tests/automatic_tests/nw-in-mem/package/icon-32.png new file mode 100644 index 0000000000..02edf8c2b4 Binary files /dev/null and b/tests/automatic_tests/nw-in-mem/package/icon-32.png differ diff --git a/tests/automatic_tests/nw-in-mem/package/index.html b/tests/automatic_tests/nw-in-mem/package/index.html new file mode 100644 index 0000000000..ea89fa8452 --- /dev/null +++ b/tests/automatic_tests/nw-in-mem/package/index.html @@ -0,0 +1,107 @@ + + + + + + Veille + + + + + + + + + + + + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/automatic_tests/nw-in-mem/package/jquery-2.0.0.min.js b/tests/automatic_tests/nw-in-mem/package/jquery-2.0.0.min.js new file mode 100644 index 0000000000..fbb594e8f3 --- /dev/null +++ b/tests/automatic_tests/nw-in-mem/package/jquery-2.0.0.min.js @@ -0,0 +1,6 @@ +/*! jQuery v2.0.0 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license +//@ sourceMappingURL=jquery.min.map +*/ +(function(e,undefined){var t,n,r=typeof undefined,i=e.location,o=e.document,s=o.documentElement,a=e.jQuery,u=e.$,l={},c=[],f="2.0.0",p=c.concat,h=c.push,d=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=f.trim,x=function(e,n){return new x.fn.init(e,n,t)},b=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^-ms-/,N=/-([\da-z])/gi,E=function(e,t){return t.toUpperCase()},S=function(){o.removeEventListener("DOMContentLoaded",S,!1),e.removeEventListener("load",S,!1),x.ready()};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,t,n){var r,i;if(!e)return this;if("string"==typeof e){if(r="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:T.exec(e),!r||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof x?t[0]:t,x.merge(this,x.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:o,!0)),C.test(r[1])&&x.isPlainObject(t))for(r in t)x.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return i=o.getElementById(r[2]),i&&i.parentNode&&(this.length=1,this[0]=i),this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?n.ready(e):(e.selector!==undefined&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return d.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,t,n,r,i,o,s=arguments[0]||{},a=1,u=arguments.length,l=!1;for("boolean"==typeof s&&(l=s,s=arguments[1]||{},a=2),"object"==typeof s||x.isFunction(s)||(s={}),u===a&&(s=this,--a);u>a;a++)if(null!=(e=arguments[a]))for(t in e)n=s[t],r=e[t],s!==r&&(l&&r&&(x.isPlainObject(r)||(i=x.isArray(r)))?(i?(i=!1,o=n&&x.isArray(n)?n:[]):o=n&&x.isPlainObject(n)?n:{},s[t]=x.extend(l,o,r)):r!==undefined&&(s[t]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=a),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){(e===!0?--x.readyWait:x.isReady)||(x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(o,[x]),x.fn.trigger&&x(o).trigger("ready").off("ready")))},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray,isWindow:function(e){return null!=e&&e===e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if("object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(t){return!1}return!0},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:JSON.parse,parseXML:function(e){var t,n;if(!e||"string"!=typeof e)return null;try{n=new DOMParser,t=n.parseFromString(e,"text/xml")}catch(r){t=undefined}return(!t||t.getElementsByTagName("parsererror").length)&&x.error("Invalid XML: "+e),t},noop:function(){},globalEval:function(e){var t,n=eval;e=x.trim(e),e&&(1===e.indexOf("use strict")?(t=o.createElement("script"),t.text=e,o.head.appendChild(t).parentNode.removeChild(t)):n(e))},camelCase:function(e){return e.replace(k,"ms-").replace(N,E)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,s=j(e);if(n){if(s){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(s){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:function(e){return null==e?"":v.call(e)},makeArray:function(e,t){var n=t||[];return null!=e&&(j(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:g.call(t,e,n)},merge:function(e,t){var n=t.length,r=e.length,i=0;if("number"==typeof n)for(;n>i;i++)e[r++]=t[i];else while(t[i]!==undefined)e[r++]=t[i++];return e.length=r,e},grep:function(e,t,n){var r,i=[],o=0,s=e.length;for(n=!!n;s>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,s=j(e),a=[];if(s)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(a[a.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(a[a.length]=r);return p.apply([],a)},guid:1,proxy:function(e,t){var n,r,i;return"string"==typeof t&&(n=e[t],t=e,e=n),x.isFunction(e)?(r=d.call(arguments,2),i=function(){return e.apply(t||this,r.concat(d.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):undefined},access:function(e,t,n,r,i,o,s){var a=0,u=e.length,l=null==n;if("object"===x.type(n)){i=!0;for(a in n)x.access(e,t,a,n[a],!0,o,s)}else if(r!==undefined&&(i=!0,x.isFunction(r)||(s=!0),l&&(s?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(x(e),n)})),t))for(;u>a;a++)t(e[a],n,s?r:r.call(e[a],a,t(e[a],n)));return i?e:l?t.call(e):u?t(e[0],n):o},now:Date.now,swap:function(e,t,n,r){var i,o,s={};for(o in t)s[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=s[o];return i}}),x.ready.promise=function(t){return n||(n=x.Deferred(),"complete"===o.readyState?setTimeout(x.ready):(o.addEventListener("DOMContentLoaded",S,!1),e.addEventListener("load",S,!1))),n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function j(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}t=x(o),function(e,undefined){var t,n,r,i,o,s,a,u,l,c,f,p,h,d,g,m,y="sizzle"+-new Date,v=e.document,b={},w=0,T=0,C=ot(),k=ot(),N=ot(),E=!1,S=function(){return 0},j=typeof undefined,D=1<<31,A=[],L=A.pop,q=A.push,H=A.push,O=A.slice,F=A.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},P="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",R="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=M.replace("w","w#"),$="\\["+R+"*("+M+")"+R+"*(?:([*^$|!~]?=)"+R+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+R+"*\\]",B=":("+M+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",I=RegExp("^"+R+"+|((?:^|[^\\\\])(?:\\\\.)*)"+R+"+$","g"),z=RegExp("^"+R+"*,"+R+"*"),_=RegExp("^"+R+"*([>+~]|"+R+")"+R+"*"),X=RegExp(R+"*[+~]"),U=RegExp("="+R+"*([^\\]'\"]*)"+R+"*\\]","g"),Y=RegExp(B),V=RegExp("^"+W+"$"),G={ID:RegExp("^#("+M+")"),CLASS:RegExp("^\\.("+M+")"),TAG:RegExp("^("+M.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+B),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+R+"*(even|odd|(([+-]|)(\\d*)n|)"+R+"*(?:([+-]|)"+R+"*(\\d+)|))"+R+"*\\)|)","i"),"boolean":RegExp("^(?:"+P+")$","i"),needsContext:RegExp("^"+R+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+R+"*((?:-\\d)?\\d*)"+R+"*\\)|)(?=[^-]|$)","i")},J=/^[^{]+\{\s*\[native \w/,Q=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,et=/'|\\/g,tt=/\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,nt=function(e,t){var n="0x"+t-65536;return n!==n?t:0>n?String.fromCharCode(n+65536):String.fromCharCode(55296|n>>10,56320|1023&n)};try{H.apply(A=O.call(v.childNodes),v.childNodes),A[v.childNodes.length].nodeType}catch(rt){H={apply:A.length?function(e,t){q.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function it(e){return J.test(e+"")}function ot(){var e,t=[];return e=function(n,i){return t.push(n+=" ")>r.cacheLength&&delete e[t.shift()],e[n]=i}}function st(e){return e[y]=!0,e}function at(e){var t=c.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ut(e,t,n,r){var i,o,s,a,u,f,d,g,x,w;if((t?t.ownerDocument||t:v)!==c&&l(t),t=t||c,n=n||[],!e||"string"!=typeof e)return n;if(1!==(a=t.nodeType)&&9!==a)return[];if(p&&!r){if(i=Q.exec(e))if(s=i[1]){if(9===a){if(o=t.getElementById(s),!o||!o.parentNode)return n;if(o.id===s)return n.push(o),n}else if(t.ownerDocument&&(o=t.ownerDocument.getElementById(s))&&m(t,o)&&o.id===s)return n.push(o),n}else{if(i[2])return H.apply(n,t.getElementsByTagName(e)),n;if((s=i[3])&&b.getElementsByClassName&&t.getElementsByClassName)return H.apply(n,t.getElementsByClassName(s)),n}if(b.qsa&&(!h||!h.test(e))){if(g=d=y,x=t,w=9===a&&e,1===a&&"object"!==t.nodeName.toLowerCase()){f=gt(e),(d=t.getAttribute("id"))?g=d.replace(et,"\\$&"):t.setAttribute("id",g),g="[id='"+g+"'] ",u=f.length;while(u--)f[u]=g+mt(f[u]);x=X.test(e)&&t.parentNode||t,w=f.join(",")}if(w)try{return H.apply(n,x.querySelectorAll(w)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(I,"$1"),t,n,r)}o=ut.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},l=ut.setDocument=function(e){var t=e?e.ownerDocument||e:v;return t!==c&&9===t.nodeType&&t.documentElement?(c=t,f=t.documentElement,p=!o(t),b.getElementsByTagName=at(function(e){return e.appendChild(t.createComment("")),!e.getElementsByTagName("*").length}),b.attributes=at(function(e){return e.className="i",!e.getAttribute("className")}),b.getElementsByClassName=at(function(e){return e.innerHTML="
",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),b.sortDetached=at(function(e){return 1&e.compareDocumentPosition(c.createElement("div"))}),b.getById=at(function(e){return f.appendChild(e).id=y,!t.getElementsByName||!t.getElementsByName(y).length}),b.getById?(r.find.ID=function(e,t){if(typeof t.getElementById!==j&&p){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},r.filter.ID=function(e){var t=e.replace(tt,nt);return function(e){return e.getAttribute("id")===t}}):(r.find.ID=function(e,t){if(typeof t.getElementById!==j&&p){var n=t.getElementById(e);return n?n.id===e||typeof n.getAttributeNode!==j&&n.getAttributeNode("id").value===e?[n]:undefined:[]}},r.filter.ID=function(e){var t=e.replace(tt,nt);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),r.find.TAG=b.getElementsByTagName?function(e,t){return typeof t.getElementsByTagName!==j?t.getElementsByTagName(e):undefined}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=b.getElementsByClassName&&function(e,t){return typeof t.getElementsByClassName!==j&&p?t.getElementsByClassName(e):undefined},d=[],h=[],(b.qsa=it(t.querySelectorAll))&&(at(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||h.push("\\["+R+"*(?:value|"+P+")"),e.querySelectorAll(":checked").length||h.push(":checked")}),at(function(e){var t=c.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&h.push("[*^$]="+R+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||h.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),h.push(",.*:")})),(b.matchesSelector=it(g=f.webkitMatchesSelector||f.mozMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&at(function(e){b.disconnectedMatch=g.call(e,"div"),g.call(e,"[s!='']:x"),d.push("!=",B)}),h=h.length&&RegExp(h.join("|")),d=d.length&&RegExp(d.join("|")),m=it(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},S=f.compareDocumentPosition?function(e,n){if(e===n)return E=!0,0;var r=n.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(n);return r?1&r||!b.sortDetached&&n.compareDocumentPosition(e)===r?e===t||m(v,e)?-1:n===t||m(v,n)?1:u?F.call(u,e)-F.call(u,n):0:4&r?-1:1:e.compareDocumentPosition?-1:1}:function(e,n){var r,i=0,o=e.parentNode,s=n.parentNode,a=[e],l=[n];if(e===n)return E=!0,0;if(!o||!s)return e===t?-1:n===t?1:o?-1:s?1:u?F.call(u,e)-F.call(u,n):0;if(o===s)return lt(e,n);r=e;while(r=r.parentNode)a.unshift(r);r=n;while(r=r.parentNode)l.unshift(r);while(a[i]===l[i])i++;return i?lt(a[i],l[i]):a[i]===v?-1:l[i]===v?1:0},c):c},ut.matches=function(e,t){return ut(e,null,null,t)},ut.matchesSelector=function(e,t){if((e.ownerDocument||e)!==c&&l(e),t=t.replace(U,"='$1']"),!(!b.matchesSelector||!p||d&&d.test(t)||h&&h.test(t)))try{var n=g.call(e,t);if(n||b.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(r){}return ut(t,c,null,[e]).length>0},ut.contains=function(e,t){return(e.ownerDocument||e)!==c&&l(e),m(e,t)},ut.attr=function(e,t){(e.ownerDocument||e)!==c&&l(e);var n=r.attrHandle[t.toLowerCase()],i=n&&n(e,t,!p);return i===undefined?b.attributes||!p?e.getAttribute(t):(i=e.getAttributeNode(t))&&i.specified?i.value:null:i},ut.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},ut.uniqueSort=function(e){var t,n=[],r=0,i=0;if(E=!b.detectDuplicates,u=!b.sortStable&&e.slice(0),e.sort(S),E){while(t=e[i++])t===e[i]&&(r=n.push(i));while(r--)e.splice(n[r],1)}return e};function lt(e,t){var n=t&&e,r=n&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ct(e,t,n){var r;return n?undefined:(r=e.getAttributeNode(t))&&r.specified?r.value:e[t]===!0?t.toLowerCase():null}function ft(e,t,n){var r;return n?undefined:r=e.getAttribute(t,"type"===t.toLowerCase()?1:2)}function pt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ht(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function dt(e){return st(function(t){return t=+t,st(function(n,r){var i,o=e([],n.length,t),s=o.length;while(s--)n[i=o[s]]&&(n[i]=!(r[i]=n[i]))})})}i=ut.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else for(;t=e[r];r++)n+=i(t);return n},r=ut.selectors={cacheLength:50,createPseudo:st,match:G,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(tt,nt),e[3]=(e[4]||e[5]||"").replace(tt,nt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||ut.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&ut.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return G.CHILD.test(e[0])?null:(e[4]?e[2]=e[4]:n&&Y.test(n)&&(t=gt(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(tt,nt).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=C[e+" "];return t||(t=RegExp("(^|"+R+")"+e+"("+R+"|$)"))&&C(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=ut.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),s="last"!==e.slice(-4),a="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,h,d,g=o!==s?"nextSibling":"previousSibling",m=t.parentNode,v=a&&t.nodeName.toLowerCase(),x=!u&&!a;if(m){if(o){while(g){f=t;while(f=f[g])if(a?f.nodeName.toLowerCase()===v:1===f.nodeType)return!1;d=g="only"===e&&!d&&"nextSibling"}return!0}if(d=[s?m.firstChild:m.lastChild],s&&x){c=m[y]||(m[y]={}),l=c[e]||[],h=l[0]===w&&l[1],p=l[0]===w&&l[2],f=h&&m.childNodes[h];while(f=++h&&f&&f[g]||(p=h=0)||d.pop())if(1===f.nodeType&&++p&&f===t){c[e]=[w,h,p];break}}else if(x&&(l=(t[y]||(t[y]={}))[e])&&l[0]===w)p=l[1];else while(f=++h&&f&&f[g]||(p=h=0)||d.pop())if((a?f.nodeName.toLowerCase()===v:1===f.nodeType)&&++p&&(x&&((f[y]||(f[y]={}))[e]=[w,p]),f===t))break;return p-=i,p===r||0===p%r&&p/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||ut.error("unsupported pseudo: "+e);return i[y]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?st(function(e,n){var r,o=i(e,t),s=o.length;while(s--)r=F.call(e,o[s]),e[r]=!(n[r]=o[s])}):function(e){return i(e,0,n)}):i}},pseudos:{not:st(function(e){var t=[],n=[],r=s(e.replace(I,"$1"));return r[y]?st(function(e,t,n,i){var o,s=r(e,null,i,[]),a=e.length;while(a--)(o=s[a])&&(e[a]=!(t[a]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:st(function(e){return function(t){return ut(e,t).length>0}}),contains:st(function(e){return function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:st(function(e){return V.test(e||"")||ut.error("unsupported lang: "+e),e=e.replace(tt,nt).toLowerCase(),function(t){var n;do if(n=p?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===c.activeElement&&(!c.hasFocus||c.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Z.test(e.nodeName)},input:function(e){return K.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:dt(function(){return[0]}),last:dt(function(e,t){return[t-1]}),eq:dt(function(e,t,n){return[0>n?n+t:n]}),even:dt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:dt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:dt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:dt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(t in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})r.pseudos[t]=pt(t);for(t in{submit:!0,reset:!0})r.pseudos[t]=ht(t);function gt(e,t){var n,i,o,s,a,u,l,c=k[e+" "];if(c)return t?0:c.slice(0);a=e,u=[],l=r.preFilter;while(a){(!n||(i=z.exec(a)))&&(i&&(a=a.slice(i[0].length)||a),u.push(o=[])),n=!1,(i=_.exec(a))&&(n=i.shift(),o.push({value:n,type:i[0].replace(I," ")}),a=a.slice(n.length));for(s in r.filter)!(i=G[s].exec(a))||l[s]&&!(i=l[s](i))||(n=i.shift(),o.push({value:n,type:s,matches:i}),a=a.slice(n.length));if(!n)break}return t?a.length:a?ut.error(e):k(e,u).slice(0)}function mt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function yt(e,t,r){var i=t.dir,o=r&&"parentNode"===i,s=T++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,r,a){var u,l,c,f=w+" "+s;if(a){while(t=t[i])if((1===t.nodeType||o)&&e(t,r,a))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[y]||(t[y]={}),(l=c[i])&&l[0]===f){if((u=l[1])===!0||u===n)return u===!0}else if(l=c[i]=[f],l[1]=e(t,r,a)||n,l[1]===!0)return!0}}function vt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,s=[],a=0,u=e.length,l=null!=t;for(;u>a;a++)(o=e[a])&&(!n||n(o,r,i))&&(s.push(o),l&&t.push(a));return s}function bt(e,t,n,r,i,o){return r&&!r[y]&&(r=bt(r)),i&&!i[y]&&(i=bt(i,o)),st(function(o,s,a,u){var l,c,f,p=[],h=[],d=s.length,g=o||Ct(t||"*",a.nodeType?[a]:a,[]),m=!e||!o&&t?g:xt(g,p,e,a,u),y=n?i||(o?e:d||r)?[]:s:m;if(n&&n(m,y,a,u),r){l=xt(y,h),r(l,[],a,u),c=l.length;while(c--)(f=l[c])&&(y[h[c]]=!(m[h[c]]=f))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(f=y[c])&&l.push(m[c]=f);i(null,y=[],l,u)}c=y.length;while(c--)(f=y[c])&&(l=i?F.call(o,f):p[c])>-1&&(o[l]=!(s[l]=f))}}else y=xt(y===s?y.splice(d,y.length):y),i?i(null,s,y,u):H.apply(s,y)})}function wt(e){var t,n,i,o=e.length,s=r.relative[e[0].type],u=s||r.relative[" "],l=s?1:0,c=yt(function(e){return e===t},u,!0),f=yt(function(e){return F.call(t,e)>-1},u,!0),p=[function(e,n,r){return!s&&(r||n!==a)||((t=n).nodeType?c(e,n,r):f(e,n,r))}];for(;o>l;l++)if(n=r.relative[e[l].type])p=[yt(vt(p),n)];else{if(n=r.filter[e[l].type].apply(null,e[l].matches),n[y]){for(i=++l;o>i;i++)if(r.relative[e[i].type])break;return bt(l>1&&vt(p),l>1&&mt(e.slice(0,l-1)).replace(I,"$1"),n,i>l&&wt(e.slice(l,i)),o>i&&wt(e=e.slice(i)),o>i&&mt(e))}p.push(n)}return vt(p)}function Tt(e,t){var i=0,o=t.length>0,s=e.length>0,u=function(u,l,f,p,h){var d,g,m,y=[],v=0,x="0",b=u&&[],T=null!=h,C=a,k=u||s&&r.find.TAG("*",h&&l.parentNode||l),N=w+=null==C?1:Math.random()||.1;for(T&&(a=l!==c&&l,n=i);null!=(d=k[x]);x++){if(s&&d){g=0;while(m=e[g++])if(m(d,l,f)){p.push(d);break}T&&(w=N,n=++i)}o&&((d=!m&&d)&&v--,u&&b.push(d))}if(v+=x,o&&x!==v){g=0;while(m=t[g++])m(b,y,l,f);if(u){if(v>0)while(x--)b[x]||y[x]||(y[x]=L.call(p));y=xt(y)}H.apply(p,y),T&&!u&&y.length>0&&v+t.length>1&&ut.uniqueSort(p)}return T&&(w=N,a=C),b};return o?st(u):u}s=ut.compile=function(e,t){var n,r=[],i=[],o=N[e+" "];if(!o){t||(t=gt(e)),n=t.length;while(n--)o=wt(t[n]),o[y]?r.push(o):i.push(o);o=N(e,Tt(i,r))}return o};function Ct(e,t,n){var r=0,i=t.length;for(;i>r;r++)ut(e,t[r],n);return n}function kt(e,t,n,i){var o,a,u,l,c,f=gt(e);if(!i&&1===f.length){if(a=f[0]=f[0].slice(0),a.length>2&&"ID"===(u=a[0]).type&&9===t.nodeType&&p&&r.relative[a[1].type]){if(t=(r.find.ID(u.matches[0].replace(tt,nt),t)||[])[0],!t)return n;e=e.slice(a.shift().value.length)}o=G.needsContext.test(e)?0:a.length;while(o--){if(u=a[o],r.relative[l=u.type])break;if((c=r.find[l])&&(i=c(u.matches[0].replace(tt,nt),X.test(a[0].type)&&t.parentNode||t))){if(a.splice(o,1),e=i.length&&mt(a),!e)return H.apply(n,i),n;break}}}return s(e,f)(i,t,!p,n,X.test(e)),n}r.pseudos.nth=r.pseudos.eq;function Nt(){}Nt.prototype=r.filters=r.pseudos,r.setFilters=new Nt,b.sortStable=y.split("").sort(S).join("")===y,l(),[0,0].sort(S),b.detectDuplicates=E,at(function(e){if(e.innerHTML="","#"!==e.firstChild.getAttribute("href")){var t="type|href|height|width".split("|"),n=t.length;while(n--)r.attrHandle[t[n]]=ft}}),at(function(e){if(null!=e.getAttribute("disabled")){var t=P.split("|"),n=t.length;while(n--)r.attrHandle[t[n]]=ct}}),x.find=ut,x.expr=ut.selectors,x.expr[":"]=x.expr.pseudos,x.unique=ut.uniqueSort,x.text=ut.getText,x.isXMLDoc=ut.isXML,x.contains=ut.contains}(e);var D={};function A(e){var t=D[e]={};return x.each(e.match(w)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?D[e]||A(e):x.extend({},e);var t,n,r,i,o,s,a=[],u=!e.once&&[],l=function(f){for(t=e.memory&&f,n=!0,s=i||0,i=0,o=a.length,r=!0;a&&o>s;s++)if(a[s].apply(f[0],f[1])===!1&&e.stopOnFalse){t=!1;break}r=!1,a&&(u?u.length&&l(u.shift()):t?a=[]:c.disable())},c={add:function(){if(a){var n=a.length;(function s(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&c.has(n)||a.push(n):n&&n.length&&"string"!==r&&s(n)})})(arguments),r?o=a.length:t&&(i=n,l(t))}return this},remove:function(){return a&&x.each(arguments,function(e,t){var n;while((n=x.inArray(t,a,n))>-1)a.splice(n,1),r&&(o>=n&&o--,s>=n&&s--)}),this},has:function(e){return e?x.inArray(e,a)>-1:!(!a||!a.length)},empty:function(){return a=[],o=0,this},disable:function(){return a=u=t=undefined,this},disabled:function(){return!a},lock:function(){return u=undefined,t||c.disable(),this},locked:function(){return!u},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!a||n&&!u||(r?u.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!n}};return c},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var s=o[0],a=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=a&&a.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===r?n.promise():this,a?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var s=o[2],a=o[3];r[o[1]]=s.add,a&&s.add(function(){n=a},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=s.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=d.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),s=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?d.call(arguments):r,n===a?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},a,u,l;if(r>1)for(a=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(s(t,l,n)).fail(o.reject).progress(s(t,u,a)):--i;return i||o.resolveWith(l,n),o.promise()}}),x.support=function(t){var n=o.createElement("input"),r=o.createDocumentFragment(),i=o.createElement("div"),s=o.createElement("select"),a=s.appendChild(o.createElement("option"));return n.type?(n.type="checkbox",t.checkOn=""!==n.value,t.optSelected=a.selected,t.reliableMarginRight=!0,t.boxSizingReliable=!0,t.pixelPosition=!1,n.checked=!0,t.noCloneChecked=n.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!a.disabled,n=o.createElement("input"),n.value="t",n.type="radio",t.radioValue="t"===n.value,n.setAttribute("checked","t"),n.setAttribute("name","t"),r.appendChild(n),t.checkClone=r.cloneNode(!0).cloneNode(!0).lastChild.checked,t.focusinBubbles="onfocusin"in e,i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===i.style.backgroundClip,x(function(){var n,r,s="padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box",a=o.getElementsByTagName("body")[0];a&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",a.appendChild(n).appendChild(i),i.innerHTML="",i.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%",x.swap(a,null!=a.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===i.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(i,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(i,null)||{width:"4px"}).width,r=i.appendChild(o.createElement("div")),r.style.cssText=i.style.cssText=s,r.style.marginRight=r.style.width="0",i.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),a.removeChild(n))}),t):t}({});var L,q,H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,O=/([A-Z])/g;function F(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=x.expando+Math.random()}F.uid=1,F.accepts=function(e){return e.nodeType?1===e.nodeType||9===e.nodeType:!0},F.prototype={key:function(e){if(!F.accepts(e))return 0;var t={},n=e[this.expando];if(!n){n=F.uid++;try{t[this.expando]={value:n},Object.defineProperties(e,t)}catch(r){t[this.expando]=n,x.extend(e,t)}}return this.cache[n]||(this.cache[n]={}),n},set:function(e,t,n){var r,i=this.key(e),o=this.cache[i];if("string"==typeof t)o[t]=n;else if(x.isEmptyObject(o))this.cache[i]=t;else for(r in t)o[r]=t[r]},get:function(e,t){var n=this.cache[this.key(e)];return t===undefined?n:n[t]},access:function(e,t,n){return t===undefined||t&&"string"==typeof t&&n===undefined?this.get(e,t):(this.set(e,t,n),n!==undefined?n:t)},remove:function(e,t){var n,r,i=this.key(e),o=this.cache[i];if(t===undefined)this.cache[i]={};else{x.isArray(t)?r=t.concat(t.map(x.camelCase)):t in o?r=[t]:(r=x.camelCase(t),r=r in o?[r]:r.match(w)||[]),n=r.length;while(n--)delete o[r[n]]}},hasData:function(e){return!x.isEmptyObject(this.cache[e[this.expando]]||{})},discard:function(e){delete this.cache[this.key(e)]}},L=new F,q=new F,x.extend({acceptData:F.accepts,hasData:function(e){return L.hasData(e)||q.hasData(e)},data:function(e,t,n){return L.access(e,t,n)},removeData:function(e,t){L.remove(e,t)},_data:function(e,t,n){return q.access(e,t,n)},_removeData:function(e,t){q.remove(e,t)}}),x.fn.extend({data:function(e,t){var n,r,i=this[0],o=0,s=null;if(e===undefined){if(this.length&&(s=L.get(i),1===i.nodeType&&!q.get(i,"hasDataAttrs"))){for(n=i.attributes;n.length>o;o++)r=n[o].name,0===r.indexOf("data-")&&(r=x.camelCase(r.substring(5)),P(i,r,s[r]));q.set(i,"hasDataAttrs",!0)}return s}return"object"==typeof e?this.each(function(){L.set(this,e)}):x.access(this,function(t){var n,r=x.camelCase(e);if(i&&t===undefined){if(n=L.get(i,e),n!==undefined)return n;if(n=L.get(i,r),n!==undefined)return n;if(n=P(i,r,undefined),n!==undefined)return n}else this.each(function(){var n=L.get(this,r);L.set(this,r,t),-1!==e.indexOf("-")&&n!==undefined&&L.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){L.remove(this,e)})}});function P(e,t,n){var r;if(n===undefined&&1===e.nodeType)if(r="data-"+t.replace(O,"-$1").toLowerCase(),n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:H.test(n)?JSON.parse(n):n}catch(i){}L.set(e,t,n)}else n=undefined;return n}x.extend({queue:function(e,t,n){var r;return e?(t=(t||"fx")+"queue",r=q.get(e,t),n&&(!r||x.isArray(n)?r=q.access(e,t,x.makeArray(n)):r.push(n)),r||[]):undefined},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),s=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),o.cur=i,i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,s,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return q.get(e,n)||q.access(e,n,{empty:x.Callbacks("once memory").add(function(){q.remove(e,[t+"queue",n])})})}}),x.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),n>arguments.length?x.queue(this[0],e):t===undefined?this:this.each(function(){var n=x.queue(this,e,t); +x._queueHooks(this,e),"fx"===e&&"inprogress"!==n[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=x.Deferred(),o=this,s=this.length,a=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=undefined),e=e||"fx";while(s--)n=q.get(o[s],e+"queueHooks"),n&&n.empty&&(r++,n.empty.add(a));return a(),i.promise(t)}});var R,M,W=/[\t\r\n]/g,$=/\r/g,B=/^(?:input|select|textarea|button)$/i;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[x.propFix[e]||e]})},addClass:function(e){var t,n,r,i,o,s=0,a=this.length,u="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,s=0,a=this.length,u=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,i="boolean"==typeof t;return x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var o,s=0,a=x(this),u=t,l=e.match(w)||[];while(o=l[s++])u=i?u:!a.hasClass(o),a[u?"addClass":"removeClass"](o)}else(n===r||"boolean"===n)&&(this.className&&q.set(this,"__className__",this.className),this.className=this.className||e===!1?"":q.get(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(W," ").indexOf(t)>=0)return!0;return!1},val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=x.isFunction(e),this.each(function(n){var i,o=x(this);1===this.nodeType&&(i=r?e.call(this,n,o.val()):e,null==i?i="":"number"==typeof i?i+="":x.isArray(i)&&(i=x.map(i,function(e){return null==e?"":e+""})),t=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],t&&"set"in t&&t.set(this,i,"value")!==undefined||(this.value=i))});if(i)return t=x.valHooks[i.type]||x.valHooks[i.nodeName.toLowerCase()],t&&"get"in t&&(n=t.get(i,"value"))!==undefined?n:(n=i.value,"string"==typeof n?n.replace($,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,s=o?null:[],a=o?i+1:r.length,u=0>i?a:o?i:0;for(;a>u;u++)if(n=r[u],!(!n.selected&&u!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),s=i.length;while(s--)r=i[s],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,t,n){var i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===r?x.prop(e,t,n):(1===s&&x.isXMLDoc(e)||(t=t.toLowerCase(),i=x.attrHooks[t]||(x.expr.match.boolean.test(t)?M:R)),n===undefined?i&&"get"in i&&null!==(o=i.get(e,t))?o:(o=x.find.attr(e,t),null==o?undefined:o):null!==n?i&&"set"in i&&(o=i.set(e,n,t))!==undefined?o:(e.setAttribute(t,n+""),n):(x.removeAttr(e,t),undefined))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.boolean.test(n)&&(e[r]=!1),e.removeAttribute(n)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,t,n){var r,i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return o=1!==s||!x.isXMLDoc(e),o&&(t=x.propFix[t]||t,i=x.propHooks[t]),n!==undefined?i&&"set"in i&&(r=i.set(e,n,t))!==undefined?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){return e.hasAttribute("tabindex")||B.test(e.nodeName)||e.href?e.tabIndex:-1}}}}),M={set:function(e,t,n){return t===!1?x.removeAttr(e,n):e.setAttribute(n,n),n}},x.each(x.expr.match.boolean.source.match(/\w+/g),function(e,t){var n=x.expr.attrHandle[t]||x.find.attr;x.expr.attrHandle[t]=function(e,t,r){var i=x.expr.attrHandle[t],o=r?undefined:(x.expr.attrHandle[t]=undefined)!=n(e,t,r)?t.toLowerCase():null;return x.expr.attrHandle[t]=i,o}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,t){return x.isArray(t)?e.checked=x.inArray(x(e).val(),t)>=0:undefined}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var I=/^key/,z=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,X=/^([^.]*)(?:\.(.+)|)$/;function U(){return!0}function Y(){return!1}function V(){try{return o.activeElement}catch(e){}}x.event={global:{},add:function(e,t,n,i,o){var s,a,u,l,c,f,p,h,d,g,m,y=q.get(e);if(y){n.handler&&(s=n,n=s.handler,o=s.selector),n.guid||(n.guid=x.guid++),(l=y.events)||(l=y.events={}),(a=y.handle)||(a=y.handle=function(e){return typeof x===r||e&&x.event.triggered===e.type?undefined:x.event.dispatch.apply(a.elem,arguments)},a.elem=e),t=(t||"").match(w)||[""],c=t.length;while(c--)u=X.exec(t[c])||[],d=m=u[1],g=(u[2]||"").split(".").sort(),d&&(p=x.event.special[d]||{},d=(o?p.delegateType:p.bindType)||d,p=x.event.special[d]||{},f=x.extend({type:d,origType:m,data:i,handler:n,guid:n.guid,selector:o,needsContext:o&&x.expr.match.needsContext.test(o),namespace:g.join(".")},s),(h=l[d])||(h=l[d]=[],h.delegateCount=0,p.setup&&p.setup.call(e,i,g,a)!==!1||e.addEventListener&&e.addEventListener(d,a,!1)),p.add&&(p.add.call(e,f),f.handler.guid||(f.handler.guid=n.guid)),o?h.splice(h.delegateCount++,0,f):h.push(f),x.event.global[d]=!0);e=null}},remove:function(e,t,n,r,i){var o,s,a,u,l,c,f,p,h,d,g,m=q.hasData(e)&&q.get(e);if(m&&(u=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(a=X.exec(t[l])||[],h=g=a[1],d=(a[2]||"").split(".").sort(),h){f=x.event.special[h]||{},h=(r?f.delegateType:f.bindType)||h,p=u[h]||[],a=a[2]&&RegExp("(^|\\.)"+d.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||a&&!a.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));s&&!p.length&&(f.teardown&&f.teardown.call(e,d,m.handle)!==!1||x.removeEvent(e,h,m.handle),delete u[h])}else for(h in u)x.event.remove(e,h+t[l],n,r,!0);x.isEmptyObject(u)&&(delete m.handle,q.remove(e,"events"))}},trigger:function(t,n,r,i){var s,a,u,l,c,f,p,h=[r||o],d=y.call(t,"type")?t.type:t,g=y.call(t,"namespace")?t.namespace.split("."):[];if(a=u=r=r||o,3!==r.nodeType&&8!==r.nodeType&&!_.test(d+x.event.triggered)&&(d.indexOf(".")>=0&&(g=d.split("."),d=g.shift(),g.sort()),c=0>d.indexOf(":")&&"on"+d,t=t[x.expando]?t:new x.Event(d,"object"==typeof t&&t),t.isTrigger=i?2:3,t.namespace=g.join("."),t.namespace_re=t.namespace?RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=undefined,t.target||(t.target=r),n=null==n?[t]:x.makeArray(n,[t]),p=x.event.special[d]||{},i||!p.trigger||p.trigger.apply(r,n)!==!1)){if(!i&&!p.noBubble&&!x.isWindow(r)){for(l=p.delegateType||d,_.test(l+d)||(a=a.parentNode);a;a=a.parentNode)h.push(a),u=a;u===(r.ownerDocument||o)&&h.push(u.defaultView||u.parentWindow||e)}s=0;while((a=h[s++])&&!t.isPropagationStopped())t.type=s>1?l:p.bindType||d,f=(q.get(a,"events")||{})[t.type]&&q.get(a,"handle"),f&&f.apply(a,n),f=c&&a[c],f&&x.acceptData(a)&&f.apply&&f.apply(a,n)===!1&&t.preventDefault();return t.type=d,i||t.isDefaultPrevented()||p._default&&p._default.apply(h.pop(),n)!==!1||!x.acceptData(r)||c&&x.isFunction(r[d])&&!x.isWindow(r)&&(u=r[c],u&&(r[c]=null),x.event.triggered=d,r[d](),x.event.triggered=undefined,u&&(r[c]=u)),t.result}},dispatch:function(e){e=x.event.fix(e);var t,n,r,i,o,s=[],a=d.call(arguments),u=(q.get(this,"events")||{})[e.type]||[],l=x.event.special[e.type]||{};if(a[0]=e,e.delegateTarget=this,!l.preDispatch||l.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),t=0;while((i=s[t++])&&!e.isPropagationStopped()){e.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(o.namespace))&&(e.handleObj=o,e.data=o.data,r=((x.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,a),r!==undefined&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return l.postDispatch&&l.postDispatch.call(this,e),e.result}},handlers:function(e,t){var n,r,i,o,s=[],a=t.delegateCount,u=e.target;if(a&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!==this;u=u.parentNode||this)if(u.disabled!==!0||"click"!==e.type){for(r=[],n=0;a>n;n++)o=t[n],i=o.selector+" ",r[i]===undefined&&(r[i]=o.needsContext?x(i,this).index(u)>=0:x.find(i,this,null,[u]).length),r[i]&&r.push(o);r.length&&s.push({elem:u,handlers:r})}return t.length>a&&s.push({elem:this,handlers:t.slice(a)}),s},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,t){var n,r,i,s=t.button;return null==e.pageX&&null!=t.clientX&&(n=e.target.ownerDocument||o,r=n.documentElement,i=n.body,e.pageX=t.clientX+(r&&r.scrollLeft||i&&i.scrollLeft||0)-(r&&r.clientLeft||i&&i.clientLeft||0),e.pageY=t.clientY+(r&&r.scrollTop||i&&i.scrollTop||0)-(r&&r.clientTop||i&&i.clientTop||0)),e.which||s===undefined||(e.which=1&s?1:2&s?3:4&s?2:0),e}},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=z.test(i)?this.mouseHooks:I.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return 3===e.target.nodeType&&(e.target=e.target.parentNode),s.filter?s.filter(e,o):e},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==V()&&this.focus?(this.focus(),!1):undefined},delegateType:"focusin"},blur:{trigger:function(){return this===V()&&this.blur?(this.blur(),!1):undefined},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&x.nodeName(this,"input")?(this.click(),!1):undefined},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==undefined&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)},x.Event=function(e,t){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.getPreventDefault&&e.getPreventDefault()?U:Y):this.type=e,t&&x.extend(this,t),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,undefined):new x.Event(e,t)},x.Event.prototype={isDefaultPrevented:Y,isPropagationStopped:Y,isImmediatePropagationStopped:Y,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=U,e&&e.preventDefault&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=U,e&&e.stopPropagation&&e.stopPropagation()},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=U,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,t,n,r,i){var o,s;if("object"==typeof e){"string"!=typeof t&&(n=n||t,t=undefined);for(s in e)this.on(s,t,n,e[s],i);return this}if(null==n&&null==r?(r=t,n=t=undefined):null==r&&("string"==typeof t?(r=n,n=undefined):(r=n,n=t,t=undefined)),r===!1)r=Y;else if(!r)return this;return 1===i&&(o=r,r=function(e){return x().off(e),o.apply(this,arguments)},r.guid=o.guid||(o.guid=x.guid++)),this.each(function(){x.event.add(this,e,r,n,t)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,x(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return(t===!1||"function"==typeof t)&&(n=t,t=undefined),n===!1&&(n=Y),this.each(function(){x.event.remove(this,e,n,t)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];return n?x.event.trigger(e,t,n,!0):undefined}});var G=/^.[^:#\[\.,]*$/,J=x.expr.match.needsContext,Q={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n,r,i=this.length;if("string"!=typeof e)return t=this,this.pushStack(x(e).filter(function(){for(r=0;i>r;r++)if(x.contains(t[r],this))return!0}));for(n=[],r=0;i>r;r++)x.find(e,this[r],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=(this.selector?this.selector+" ":"")+e,n},has:function(e){var t=x(e,this),n=t.length;return this.filter(function(){var e=0;for(;n>e;e++)if(x.contains(this,t[e]))return!0})},not:function(e){return this.pushStack(Z(this,e||[],!0))},filter:function(e){return this.pushStack(Z(this,e||[],!1))},is:function(e){return!!e&&("string"==typeof e?J.test(e)?x(e,this.context).index(this[0])>=0:x.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,o=[],s=J.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(s?s.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?g.call(x(e),this[0]):g.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function K(e,t){while((e=e[t])&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return K(e,"nextSibling")},prev:function(e){return K(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(Q[e]||x.unique(i),"p"===e[0]&&i.reverse()),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,t,n){var r=[],i=n!==undefined;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&x(e).is(n))break;r.push(e)}return r},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function Z(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(G.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return g.call(t,e)>=0!==n})}var et=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,tt=/<([\w:]+)/,nt=/<|&#?\w+;/,rt=/<(?:script|style|link)/i,it=/^(?:checkbox|radio)$/i,ot=/checked\s*(?:[^=]|=\s*.checked.)/i,st=/^$|\/(?:java|ecma)script/i,at=/^true\/(.*)/,ut=/^\s*\s*$/g,lt={option:[1,""],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};lt.optgroup=lt.option,lt.tbody=lt.tfoot=lt.colgroup=lt.caption=lt.col=lt.thead,lt.th=lt.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===undefined?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=ct(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=ct(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(gt(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&ht(gt(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++)1===e.nodeType&&(x.cleanData(gt(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var t=this[0]||{},n=0,r=this.length;if(e===undefined&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!rt.test(e)&&!lt[(tt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(et,"<$1>");try{for(;r>n;n++)t=this[n]||{},1===t.nodeType&&(x.cleanData(gt(t,!1)),t.innerHTML=e);t=0}catch(i){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=p.apply([],e);var r,i,o,s,a,u,l=0,c=this.length,f=this,h=c-1,d=e[0],g=x.isFunction(d);if(g||!(1>=c||"string"!=typeof d||x.support.checkClone)&&ot.test(d))return this.each(function(r){var i=f.eq(r);g&&(e[0]=d.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(r=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),i=r.firstChild,1===r.childNodes.length&&(r=i),i)){for(o=x.map(gt(r,"script"),ft),s=o.length;c>l;l++)a=r,l!==h&&(a=x.clone(a,!0,!0),s&&x.merge(o,gt(a,"script"))),t.call(this[l],a,l);if(s)for(u=o[o.length-1].ownerDocument,x.map(o,pt),l=0;s>l;l++)a=o[l],st.test(a.type||"")&&!q.access(a,"globalEval")&&x.contains(u,a)&&(a.src?x._evalUrl(a.src):x.globalEval(a.textContent.replace(ut,"")))}return this}}),x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=[],i=x(e),o=i.length-1,s=0;for(;o>=s;s++)n=s===o?this:this.clone(!0),x(i[s])[t](n),h.apply(r,n.get());return this.pushStack(r)}}),x.extend({clone:function(e,t,n){var r,i,o,s,a=e.cloneNode(!0),u=x.contains(e.ownerDocument,e);if(!(x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(s=gt(a),o=gt(e),r=0,i=o.length;i>r;r++)mt(o[r],s[r]);if(t)if(n)for(o=o||gt(e),s=s||gt(a),r=0,i=o.length;i>r;r++)dt(o[r],s[r]);else dt(e,a);return s=gt(a,"script"),s.length>0&&ht(s,!u&>(e,"script")),a},buildFragment:function(e,t,n,r){var i,o,s,a,u,l,c=0,f=e.length,p=t.createDocumentFragment(),h=[];for(;f>c;c++)if(i=e[c],i||0===i)if("object"===x.type(i))x.merge(h,i.nodeType?[i]:i);else if(nt.test(i)){o=o||p.appendChild(t.createElement("div")),s=(tt.exec(i)||["",""])[1].toLowerCase(),a=lt[s]||lt._default,o.innerHTML=a[1]+i.replace(et,"<$1>")+a[2],l=a[0];while(l--)o=o.firstChild;x.merge(h,o.childNodes),o=p.firstChild,o.textContent=""}else h.push(t.createTextNode(i));p.textContent="",c=0;while(i=h[c++])if((!r||-1===x.inArray(i,r))&&(u=x.contains(i.ownerDocument,i),o=gt(p.appendChild(i),"script"),u&&ht(o),n)){l=0;while(i=o[l++])st.test(i.type||"")&&n.push(i)}return p},cleanData:function(e){var t,n,r,i=e.length,o=0,s=x.event.special;for(;i>o;o++){if(n=e[o],x.acceptData(n)&&(t=q.access(n)))for(r in t.events)s[r]?x.event.remove(n,r):x.removeEvent(n,r,t.handle);L.discard(n),q.discard(n)}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"text",async:!1,global:!1,success:x.globalEval})}});function ct(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function ft(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function pt(e){var t=at.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function ht(e,t){var n=e.length,r=0;for(;n>r;r++)q.set(e[r],"globalEval",!t||q.get(t[r],"globalEval"))}function dt(e,t){var n,r,i,o,s,a,u,l;if(1===t.nodeType){if(q.hasData(e)&&(o=q.access(e),s=x.extend({},o),l=o.events,q.set(t,s),l)){delete s.handle,s.events={};for(i in l)for(n=0,r=l[i].length;r>n;n++)x.event.add(t,i,l[i][n])}L.hasData(e)&&(a=L.access(e),u=x.extend({},a),L.set(t,u))}}function gt(e,t){var n=e.getElementsByTagName?e.getElementsByTagName(t||"*"):e.querySelectorAll?e.querySelectorAll(t||"*"):[];return t===undefined||t&&x.nodeName(e,t)?x.merge([e],n):n}function mt(e,t){var n=t.nodeName.toLowerCase();"input"===n&&it.test(e.type)?t.checked=e.checked:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}x.fn.extend({wrapAll:function(e){var t;return x.isFunction(e)?this.each(function(t){x(this).wrapAll(e.call(this,t))}):(this[0]&&(t=x(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this)},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var yt,vt,xt=/^(none|table(?!-c[ea]).+)/,bt=/^margin/,wt=RegExp("^("+b+")(.*)$","i"),Tt=RegExp("^("+b+")(?!px)[a-z%]+$","i"),Ct=RegExp("^([+-])=("+b+")","i"),kt={BODY:"block"},Nt={position:"absolute",visibility:"hidden",display:"block"},Et={letterSpacing:0,fontWeight:400},St=["Top","Right","Bottom","Left"],jt=["Webkit","O","Moz","ms"];function Dt(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=jt.length;while(i--)if(t=jt[i]+n,t in e)return t;return r}function At(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function Lt(t){return e.getComputedStyle(t,null)}function qt(e,t){var n,r,i,o=[],s=0,a=e.length;for(;a>s;s++)r=e[s],r.style&&(o[s]=q.get(r,"olddisplay"),n=r.style.display,t?(o[s]||"none"!==n||(r.style.display=""),""===r.style.display&&At(r)&&(o[s]=q.access(r,"olddisplay",Pt(r.nodeName)))):o[s]||(i=At(r),(n&&"none"!==n||!i)&&q.set(r,"olddisplay",i?n:x.css(r,"display"))));for(s=0;a>s;s++)r=e[s],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[s]||"":"none"));return e}x.fn.extend({css:function(e,t){return x.access(this,function(e,t,n){var r,i,o={},s=0;if(x.isArray(t)){for(r=Lt(e),i=t.length;i>s;s++)o[t[s]]=x.css(e,t[s],!1,r);return o}return n!==undefined?x.style(e,t,n):x.css(e,t)},e,t,arguments.length>1)},show:function(){return qt(this,!0)},hide:function(){return qt(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:At(this))?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=yt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,s,a=x.camelCase(t),u=e.style;return t=x.cssProps[a]||(x.cssProps[a]=Dt(u,a)),s=x.cssHooks[t]||x.cssHooks[a],n===undefined?s&&"get"in s&&(i=s.get(e,!1,r))!==undefined?i:u[t]:(o=typeof n,"string"===o&&(i=Ct.exec(n))&&(n=(i[1]+1)*i[2]+parseFloat(x.css(e,t)),o="number"),null==n||"number"===o&&isNaN(n)||("number"!==o||x.cssNumber[a]||(n+="px"),x.support.clearCloneStyle||""!==n||0!==t.indexOf("background")||(u[t]="inherit"),s&&"set"in s&&(n=s.set(e,n,r))===undefined||(u[t]=n)),undefined)}},css:function(e,t,n,r){var i,o,s,a=x.camelCase(t);return t=x.cssProps[a]||(x.cssProps[a]=Dt(e.style,a)),s=x.cssHooks[t]||x.cssHooks[a],s&&"get"in s&&(i=s.get(e,!0,n)),i===undefined&&(i=yt(e,t,r)),"normal"===i&&t in Et&&(i=Et[t]),""===n||n?(o=parseFloat(i),n===!0||x.isNumeric(o)?o||0:i):i}}),yt=function(e,t,n){var r,i,o,s=n||Lt(e),a=s?s.getPropertyValue(t)||s[t]:undefined,u=e.style;return s&&(""!==a||x.contains(e.ownerDocument,e)||(a=x.style(e,t)),Tt.test(a)&&bt.test(t)&&(r=u.width,i=u.minWidth,o=u.maxWidth,u.minWidth=u.maxWidth=u.width=a,a=s.width,u.width=r,u.minWidth=i,u.maxWidth=o)),a};function Ht(e,t,n){var r=wt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function Ot(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,s=0;for(;4>o;o+=2)"margin"===n&&(s+=x.css(e,n+St[o],!0,i)),r?("content"===n&&(s-=x.css(e,"padding"+St[o],!0,i)),"margin"!==n&&(s-=x.css(e,"border"+St[o]+"Width",!0,i))):(s+=x.css(e,"padding"+St[o],!0,i),"padding"!==n&&(s+=x.css(e,"border"+St[o]+"Width",!0,i)));return s}function Ft(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Lt(e),s=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=yt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Tt.test(i))return i;r=s&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+Ot(e,t,n||(s?"border":"content"),r,o)+"px"}function Pt(e){var t=o,n=kt[e];return n||(n=Rt(e,t),"none"!==n&&n||(vt=(vt||x(" + + + + + \ No newline at end of file diff --git a/tests/automatic_tests/nwfaketop/mocha_test.js b/tests/automatic_tests/nwfaketop/mocha_test.js new file mode 100644 index 0000000000..c58cfefbbf --- /dev/null +++ b/tests/automatic_tests/nwfaketop/mocha_test.js @@ -0,0 +1,45 @@ +var path = require('path'); +var assert = require('assert'); +var spawn = require('child_process').spawn; +var server = global.server; +var server_port = global.port; + + +describe('nwfaketop',function(){ + + var results = []; + var connection_handler_callback = undefined; + var connection_handler = function(socket){ + socket.on('data',function(data){ + results = JSON.parse(data+""); + connection_handler_callback(); + }); + }; + var child = undefined; + + before(function(done){ + this.timeout(0); + server.on('connection',connection_handler); + var app_path = path.join(global.tests_dir,'nwfaketop'); + var exec_argv = [app_path,server_port]; + child = spawn(process.execPath,exec_argv); + connection_handler_callback = done; + }); + + after(function(done){ + server.removeListener('connection',connection_handler); + child.kill(); + done(); + }); + + it("nwfaketop attribute set",function(done){ + assert(results[0],true); + done(); + }); + it("nwfaketop attribute default",function(done){ + assert(results[1],true); + done(); + }); + + +}); \ No newline at end of file diff --git a/tests/automatic_tests/nwfaketop/package.json b/tests/automatic_tests/nwfaketop/package.json new file mode 100644 index 0000000000..b2bc1566ae --- /dev/null +++ b/tests/automatic_tests/nwfaketop/package.json @@ -0,0 +1,11 @@ +{ + "name": "nwfaketop", + "main": "index.html", + "window": { + "width": 600, + "height": 400, + "position": "center", + "toolbar": true, + "resizable": true + } +} \ No newline at end of file diff --git a/tests/automatic_tests/proxy/export.bat b/tests/automatic_tests/proxy/export.bat new file mode 100644 index 0000000000..fe25b8b4be --- /dev/null +++ b/tests/automatic_tests/proxy/export.bat @@ -0,0 +1 @@ +reg export "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" c:\test.reg \ No newline at end of file diff --git a/tests/automatic_tests/proxy/mocha_test.js b/tests/automatic_tests/proxy/mocha_test.js new file mode 100644 index 0000000000..e09b4bae93 --- /dev/null +++ b/tests/automatic_tests/proxy/mocha_test.js @@ -0,0 +1,45 @@ +var cp = require('child_process'); +var fs = require('fs'); +var os = require('os'); +describe('proxy', function(){ + before(function(done){ + if (os.platform() == "win32"){ + this.timeout(0); + var ex = cp.exec(path.join(global.tests_dir, 'proxy/export.bat'), + function(error, stdout, stderr){ + console.log(stdout); + done(); + }); + } + else + done(); + }) + after(function(){ + this.timeout(0); + if (os.platform() == "win32"){ + fs.unlink('c:/test.reg', function (err) { + if (err) console.log(err); + }); + } + }) + it('the get proxyfor url should work fine', function(done) { + if (os.platform() == "win32"){ + this.timeout(0); + setTimeout(function(){ + var data = fs.readFileSync("c:/test.reg",'utf16le'); + var index = data.indexOf('ProxyServer'); + var right = data.substring(index+14); + var array = right.split('"'); + var gui = require('nw.gui'); + var re = gui.App.getProxyForURL("https://www.google.com.hk"); + if (re == "PROXY "+array[0]) + done(); + else + done('the method getProxyForURL is not right'); + },1000); + } + else { + done(); + } + }) +}) \ No newline at end of file diff --git a/tests/automatic_tests/quit_with_secondary_window_on_top/index.html b/tests/automatic_tests/quit_with_secondary_window_on_top/index.html new file mode 100644 index 0000000000..9ce8558db6 --- /dev/null +++ b/tests/automatic_tests/quit_with_secondary_window_on_top/index.html @@ -0,0 +1,17 @@ + + + + quit_with_secondary_window_on_top! + + +

quit_with_secondary_window_on_top!

+ with the created new window on top, hit CMD-Q or select Menu > Quit + . + + diff --git a/tests/automatic_tests/quit_with_secondary_window_on_top/index1.html b/tests/automatic_tests/quit_with_secondary_window_on_top/index1.html new file mode 100644 index 0000000000..a73f7b5476 --- /dev/null +++ b/tests/automatic_tests/quit_with_secondary_window_on_top/index1.html @@ -0,0 +1,18 @@ + + + + New Window! + + +

New Window!

+ We are using node.js + + + diff --git a/tests/automatic_tests/quit_with_secondary_window_on_top/mocha_test.js b/tests/automatic_tests/quit_with_secondary_window_on_top/mocha_test.js new file mode 100644 index 0000000000..8708ce60c7 --- /dev/null +++ b/tests/automatic_tests/quit_with_secondary_window_on_top/mocha_test.js @@ -0,0 +1,24 @@ +var assert = require('assert'); +var path = require('path'); +var exec = require('child_process').exec; +var spawn = require('child_process').spawn; +var os = require('os'); +var result; + +if (os.platform() != 'darwin') + return; +describe('quit_with_secondary_window_on_top', function() { + before(function(done) { + this.timeout(0); + var app_path = path.join(global.tests_dir, 'quit_with_secondary_window_on_top'); + var exec_path = process.execPath; + exec_path = exec_path.replace(/ /g, '\\ '); + exec(exec_path + ' ' + app_path, function(error, stdout, stderr) { + result = error; + done(); + }); + }) + it('should quit with secondary window on top without error', function() { + assert.equal(result, null); + }); +}); diff --git a/tests/automatic_tests/quit_with_secondary_window_on_top/package.json b/tests/automatic_tests/quit_with_secondary_window_on_top/package.json new file mode 100644 index 0000000000..9b415e218a --- /dev/null +++ b/tests/automatic_tests/quit_with_secondary_window_on_top/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw-quit_with_secondary_window_on_top", + "main": "index.html" +} diff --git a/tests/automatic_tests/reference-node-main/index.html b/tests/automatic_tests/reference-node-main/index.html new file mode 100644 index 0000000000..6337e14bed --- /dev/null +++ b/tests/automatic_tests/reference-node-main/index.html @@ -0,0 +1,51 @@ + + + + + test + + + + + + \ No newline at end of file diff --git a/tests/automatic_tests/reference-node-main/index.js b/tests/automatic_tests/reference-node-main/index.js new file mode 100644 index 0000000000..04b94f07a4 --- /dev/null +++ b/tests/automatic_tests/reference-node-main/index.js @@ -0,0 +1,24 @@ +exports.message = "hello world"; +exports.port = 10000; +exports.ready = false; + +var server = require('net').createServer(); +server.on('connection',function(socket){ + socket.on('data',function(data){ + socket.write(data); + }); +}); + +server.on('error',function(){ + try{ + server.close(); + }catch(e){ + exports.port += 1; + setTimeout(function(){ + server.listen(exports.port); + },0); + } +}); +server.listen(exports.port,function(){ + exports.ready = true; +}); diff --git a/tests/automatic_tests/reference-node-main/package.json b/tests/automatic_tests/reference-node-main/package.json new file mode 100644 index 0000000000..486c567b08 --- /dev/null +++ b/tests/automatic_tests/reference-node-main/package.json @@ -0,0 +1,6 @@ +{ + "name":"nw_1403596464", + "main":"index.html", + "dependencies":{}, + "node-main":"./index.js" +} \ No newline at end of file diff --git a/tests/automatic_tests/reference_xhr_in_node_context/index.html b/tests/automatic_tests/reference_xhr_in_node_context/index.html new file mode 100644 index 0000000000..375615a360 --- /dev/null +++ b/tests/automatic_tests/reference_xhr_in_node_context/index.html @@ -0,0 +1,26 @@ + + + + + test + + +

it works!

+ + + + \ No newline at end of file diff --git a/tests/automatic_tests/reference_xhr_in_node_context/mocha_test.js b/tests/automatic_tests/reference_xhr_in_node_context/mocha_test.js new file mode 100644 index 0000000000..eee8682f48 --- /dev/null +++ b/tests/automatic_tests/reference_xhr_in_node_context/mocha_test.js @@ -0,0 +1,44 @@ +var path = require('path'); +var assert = require('assert'); +var spawn = require('child_process').spawn; +var server = global.server; +var port = global.port||13013; + +describe('reference xhr from node context',function(){ + + var crash = true; + var child = undefined; + var connection_handler_callback = undefined; + var connection_handler = function(socket){ + socket.on('data',function(data){ + crash = false; + connection_handler_callback(); + }); + }; + + + before(function(done){ + this.timeout(0); + server.on('connection',connection_handler); + var exec_path = path.join(global.tests_dir,'reference_xhr_in_node_context'); + var exec_argv = [exec_path,port] + child = spawn(process.execPath,exec_argv); + connection_handler_callback = done; + child.on('exit',function(){ + if (crash){ + done(); + } + }); + }); + + after(function(done){ + server.removeListener('connection',connection_handler); + child.kill(); + done(); + }); + + it("reference xhr from node context should not crash",function(done){ + assert.equal(crash,false); + done(); + }); +}); \ No newline at end of file diff --git a/tests/automatic_tests/reference_xhr_in_node_context/package.json b/tests/automatic_tests/reference_xhr_in_node_context/package.json new file mode 100644 index 0000000000..1f456a50f6 --- /dev/null +++ b/tests/automatic_tests/reference_xhr_in_node_context/package.json @@ -0,0 +1,5 @@ +{ + "name":"nw_1404278514", + "main":"index.html", + "dependencies":{} +} \ No newline at end of file diff --git a/tests/automatic_tests/reference_xhr_in_node_context/test.js b/tests/automatic_tests/reference_xhr_in_node_context/test.js new file mode 100644 index 0000000000..e935f938c6 --- /dev/null +++ b/tests/automatic_tests/reference_xhr_in_node_context/test.js @@ -0,0 +1,11 @@ +exports.xhr = function () { + global.xhr.onreadystatechange = function() { + if (global.xhr.readyState === 4 && global.xhr.status === 200) { + } + } + global.xhr.onload = function(e) { + var uInt8Array = new Uint8Array(this.response); // this.response == uInt8Array.buffer + // var byte3 = uInt8Array[4]; // byte at offset 4 + }; + +} \ No newline at end of file diff --git a/tests/automatic_tests/reload_application/index.html b/tests/automatic_tests/reload_application/index.html index 3f86648d4e..696dbabd34 100644 --- a/tests/automatic_tests/reload_application/index.html +++ b/tests/automatic_tests/reload_application/index.html @@ -78,7 +78,7 @@ client.write('open'); client.on('data', function(data){ if (data == 'quit'){ - if (type == 3) gui.App.quit(); + if (type == 3) setTimeout(gui.App.quit, 1000); if (type == 2) gui.Window.get().close(); } if (data == 'reload'){ diff --git a/tests/automatic_tests/reload_application/mocha_test.js b/tests/automatic_tests/reload_application/mocha_test.js index ad17601f43..f41181b8ab 100644 --- a/tests/automatic_tests/reload_application/mocha_test.js +++ b/tests/automatic_tests/reload_application/mocha_test.js @@ -43,7 +43,7 @@ describe('AppTest', function(){ app.kill(); done("Timeout, Can not close window."); } - }, 10000); + }, 30000); }) @@ -66,7 +66,7 @@ describe('AppTest', function(){ app.kill(); done("Timeout, Can not quit App."); } - }, 10000); + }, 30000); }) @@ -114,7 +114,7 @@ describe('AppTest', function(){ app.kill(); done("Timeout, Can not close window."); } - }, 10000); + }, 30000); }) @@ -161,7 +161,7 @@ describe('AppTest', function(){ app.kill(); done("Timeout, Can not quit App."); } - }, 10000); + }, 30000); }) diff --git a/tests/automatic_tests/remote-img/imgnotshown/package.json b/tests/automatic_tests/remote-img/imgnotshown/package.json new file mode 100644 index 0000000000..d1242b6d18 --- /dev/null +++ b/tests/automatic_tests/remote-img/imgnotshown/package.json @@ -0,0 +1,5 @@ +{ + "name": "remote file access", + "main": "http://localhost:8123/index.html", + "node-remote": "localhost" +} \ No newline at end of file diff --git a/tests/automatic_tests/remote-img/imgshown/index.html b/tests/automatic_tests/remote-img/imgshown/index.html new file mode 100644 index 0000000000..a424b015c2 --- /dev/null +++ b/tests/automatic_tests/remote-img/imgshown/index.html @@ -0,0 +1,21 @@ + + + + + remote file access test + + + +
+ + + \ No newline at end of file diff --git a/tests/automatic_tests/remote-img/imgshown/package.json b/tests/automatic_tests/remote-img/imgshown/package.json new file mode 100644 index 0000000000..d9d91badba --- /dev/null +++ b/tests/automatic_tests/remote-img/imgshown/package.json @@ -0,0 +1,4 @@ +{ + "name": "remote file access", + "main": "index.html" +} \ No newline at end of file diff --git a/tests/automatic_tests/remote-img/mocha_test.js b/tests/automatic_tests/remote-img/mocha_test.js new file mode 100644 index 0000000000..c04c4cfaf4 --- /dev/null +++ b/tests/automatic_tests/remote-img/mocha_test.js @@ -0,0 +1,58 @@ +var path = require('path'); +var spawn = require('child_process').spawn; +var fs = require('fs-extra'); +describe('remote-file-access', function(){ + var os = require('os'); + var platform = os.platform(); + before(function(done){ + this.timeout(0); + if(platform == "win32"){ + fs.copy(global.tests_dir+'/remote-img/star.jpg','/star.jpg'); + } + else { + fs.copy(global.tests_dir+'/remote-img/star.jpg','/tmp/star.jpg'); + } + done(); + }) + after(function() { + this.timeout(0); + if (platform == "win32"){ + fs.remove('/star.jpg'); + } + else { + fs.remove('/tmp/star.jpg'); + } + }) + it ('remote img should work', function(done){ + this.timeout(0); + var exec_argv = [path.join(global.tests_dir, 'remote-img/imgnotshown')] + var result =false; + var app = spawn(process.execPath, exec_argv); + app.on('exit', function(code){ + result = true; + done(); + }) + setTimeout(function(){ + if(!result){ + app.kill(); + done(); + } + }, 3000) + }) + it ('local img should work', function(done){ + this.timeout(0); + var exec_argv = [path.join(global.tests_dir, 'remote-img/imgshown')] + var result = false; + var app = spawn(process.execPath, exec_argv); + app.on('exit', function(code){ + result = true; + done(); + }) + setTimeout(function(){ + if(!result){ + app.kill(); + done(); + } + }, 3000) + }) +}) \ No newline at end of file diff --git a/tests/automatic_tests/remote-img/star.jpg b/tests/automatic_tests/remote-img/star.jpg new file mode 100644 index 0000000000..9d634e2c27 Binary files /dev/null and b/tests/automatic_tests/remote-img/star.jpg differ diff --git a/tests/automatic_tests/save_devtools_settings/index.html b/tests/automatic_tests/save_devtools_settings/index.html new file mode 100644 index 0000000000..c6581ffd47 --- /dev/null +++ b/tests/automatic_tests/save_devtools_settings/index.html @@ -0,0 +1,183 @@ + + + + + Test case for save devtools settings + + +

Please wait to be closed.

+ + + + diff --git a/tests/automatic_tests/save_devtools_settings/mocha_test.js b/tests/automatic_tests/save_devtools_settings/mocha_test.js new file mode 100644 index 0000000000..fcb8c048d7 --- /dev/null +++ b/tests/automatic_tests/save_devtools_settings/mocha_test.js @@ -0,0 +1,101 @@ +var path = require('path'); +var app_test = require('./nw_test_app'); +var assert = require('assert'); +var original; +var changed; +var ok = false; + +function read_changed(done) { + if (!ok) + setTimeout(read_changed, 2000, done); + else { + var child = app_test.createChildProcess({ + execPath: process.execPath, + appPath: path.join(global.tests_dir, 'save_devtools_settings'), + args: [0], + end: function(data, app) { + changed = data; + app.kill(); + done(); + console.log("secnond"); + } + }); + } +} + +describe('save_devtools_settings', function() { + + before(function(done) { + this.timeout(0); + var child = app_test.createChildProcess({ + execPath: process.execPath, + appPath: path.join(global.tests_dir, 'save_devtools_settings'), + args: [1], + end: function(data, app) { + original = data; + app.kill(); + ok = true; + console.log("first"); + } + }); + setTimeout(read_changed, 2000, done); + }); + + it("should save devtools' settings", function() { + var i = 0; + // general + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.equal((original[i] + 1) % 4, changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + + assert.equal((original[i] + 1) % 4, changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + if (!original[i - 1]) + assert.equal((original[i] + 1) % 10, changed[i]); + i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + + // overrides + assert.notEqual(original[i], changed[i]); i++; + if (!original[i - 1]) + // 0~21 + assert.equal((original[i] + 1) % 22, changed[i]); + i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + if (!original[i - 1]) { + assert.equal((original[i] + 1) % 10, changed[i]); i++; + assert.equal((original[i] + 1) % 10, changed[i]); i++; + } + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + if (!original[i - 1]) { + assert.equal((original[i] + 1) % 10, changed[i]); i++; + assert.equal((original[i] + 1) % 10, changed[i]); i++; + assert.equal((original[i] + 1) % 10, changed[i]); i++; + } + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + if (!original[i - 1]) + assert.equal((original[i] + 1) % 9, changed[i]); + i++; + + }); + +}); diff --git a/tests/automatic_tests/save_devtools_settings/package.json b/tests/automatic_tests/save_devtools_settings/package.json new file mode 100644 index 0000000000..095e2476d4 --- /dev/null +++ b/tests/automatic_tests/save_devtools_settings/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw-save-devtools-settings-test", + "main": "index.html" +} diff --git a/tests/automatic_tests/shortcut/mocha_test.js b/tests/automatic_tests/shortcut/mocha_test.js new file mode 100644 index 0000000000..57b1a6aa53 --- /dev/null +++ b/tests/automatic_tests/shortcut/mocha_test.js @@ -0,0 +1,110 @@ +var gui = require('nw.gui'); +var assert = require('assert'); +require('should'); + +describe('Shortcut', function() { + describe('.key', function() { + it('should be undefined if no key specified.', function() { + var shortcut; + try { + shortcut = new gui.Shortcut(); + } catch (err) {} + assert.equal(shortcut, undefined); + }); + + it('should be an object if key specified.', function() { + var shortcut; + try { + shortcut = new gui.Shortcut({"key": "Alt+Shift+S"}); + } catch (err) {} + + shortcut.should.be.a.Object; + assert.equal(shortcut.key, "Alt+Shift+S"); + }); + }); + + describe('.active', function() { + it('should be undefined if active is not an function object.', function() { + var shortcut; + try { + shortcut = new gui.Shortcut({key: 'Alt+Shift+S', active: "foo"}); + } catch(err) {} + + assert.equal(shortcut, undefined); + }); + + it('should be an object if "key" and "active" specified.', function() { + var onActive = function() {}; + var shortcut = new gui.Shortcut({key: 'Alt+Shift+S', active: onActive}); + + shortcut.should.be.a.Object; + assert.equal(shortcut.active, onActive); + }); + }); + + describe('.failed', function() { + it('should be undefined if "failed" is not a function object.', function() { + var shortcut; + try { + shortcut = new gui.Shortcut({key: 'Alt+Shift+S', failed: "foo"}); + } catch(err) {} + + assert.equal(shortcut, undefined); + }); + + it('should be an object if "key" and "failed" specified.', function() { + var onFailed = function() {}; + var shortcut = new gui.Shortcut({key: 'Alt+Shift+S', failed: onFailed}); + + shortcut.should.be.a.Object; + assert.equal(shortcut.failed, onFailed); + }); + }); +}); + +describe('App.registerGlobalHotKey', function() { + it('should register failed if the parameter is not an Shortcut object.', + function(done) { + var object = new Object(); + try { + gui.App.registerGlobalHotKey(object); + } catch(err) { + done(); + } + } + ); + + it('should register failed if the same key has been registered.', + function(done) { + var shortcut = new gui.Shortcut({key: "Alt+Shift+S"}); + shortcut.failed = function(msg) { + assert.equal(msg, "Register global desktop keyboard shortcut failed."); + done(); + }; + + gui.App.registerGlobalHotKey(shortcut); + gui.App.registerGlobalHotKey(shortcut); + } + ); + + it('should register failed for invalid key.', function(done) { + var shortcut = new gui.Shortcut({key: "foo"}); + shortcut.failed = function(msg) { + done(); + } + gui.App.registerGlobalHotKey(shortcut); + }); +}); + +describe('App.unregisterGlobalHotKey', function() { + it('should unregister failed if the parameter is not an Shortcut object.', + function(done) { + var object = new Object(); + try { + gui.App.unregisterGlobalHotKey(object); + } catch(err) { + done(); + } + } + ); +}); diff --git a/tests/automatic_tests/single_instance/mocha_test.js b/tests/automatic_tests/single_instance/mocha_test.js new file mode 100644 index 0000000000..1aa672b9fd --- /dev/null +++ b/tests/automatic_tests/single_instance/mocha_test.js @@ -0,0 +1,179 @@ +var func = require('./' + global.tests_dir +'/start_app/script.js'); +var execPath = func.getExecPath(); +var fs = require('fs-extra'); +var os = require('os'); +var spawn = require('child_process').spawn; +var exec = require('child_process').exec; + +var app = new Array(); +var child1, child2; +var mac_app_path = path.join('tmp-nw', 'node-webkit.app'); +function make_execuable_file(folder_path, done) { + func.copyExecFiles(function() { + func.copySourceFiles(folder_path); + func.zipSourceFiles(function() { + func.makeExecuableFile(); + if (os.platform() == 'darwin') { + var app_path = 'tmp-nw/node-webkit.app/Contents/Resources/app.nw'; + fs.mkdir(app_path, function(err) { + if(err && err.code !== 'EEXIST') throw err + fs.copy('tmp-nw/index.html', path.join(app_path, 'index.html')); + fs.copy('tmp-nw/package.html', path.join(app_path, 'package.html')); + }); + } + done(); + }); + }); +} + +function check_have(i, cmd, options, msg, last, done) { + var result = false; + app[i] = spawn(cmd, options); + app[i].on('exit', function() { + result = true; + }); + + if (last == 2) { + setTimeout(function() { + if (result) { + done(); + } else { + done(msg); + } + app[i].kill(); + app[i - 1].kill(); + }, 3000); + } else { + setTimeout(function() { + if (result) { + done(msg); + } else { + done(); + } + if (last == 1) { + app[i].kill(); + app[i - 1].kill(); + } + }, 3000); + } +} + +describe('single-instance', function() { + this.timeout(0); + + describe('single-instance false', function() { + before(function(done) { + make_execuable_file('single_instance/mul', done); + }); + + after(function() { + fs.remove('tmp-nw', function (er) { + if (er) throw er; + }); + }); + + it('should have a instance', function(done) { + check_have(0, execPath, "", 'not have a instance', 0, done); + }); + + it('should have a second instance', function(done) { + check_have(1, execPath, "", 'not have a second instance', 1, done); + }); + }); + + + describe('single-instance default', function() { + + before(function(done) { + setTimeout(function() { + make_execuable_file('single_instance/single', done); + }, 3000); + }); + + after(function() { + fs.remove('tmp-nw', function (er) { + if (er) throw er; + }); + }); + + it('should have a instance', function(done) { + check_have(2, execPath, "", 'not have a instance', 0, done); + }); + + it('should not have a second instance', function(done) { + check_have(3, execPath, "", 'have a second instance', 2, done); + }); + }); + + if (os.platform() == 'darwin') { + describe('single-instance false(open app)', function(){ + before(function(done) { + setTimeout(function() { + make_execuable_file('single_instance/open_mul', done); + }, 3000); + }); + + after(function() { + fs.remove('tmp-nw', function (er) { + if (er) throw er; + }); + }); + + it('should have a instance (open app)', function(done) { + child1 = exec('open ' + mac_app_path); + setTimeout(function () { + var content = fs.readFileSync('tmp-nw/msg'); + if (content + "" != "") + done(); + else + done("not have a instance"); + }, 6000); + }); + + it('should not have a second instance (open app)', function(done) { + child2 = exec('open ' + mac_app_path); + var content = fs.readFileSync('tmp-nw/msg'); + if (content + "" == "11") + done("have a second instance"); + else + done(); + }); + + }); + + describe('single-instance default(open app)', function(){ + before(function(done) { + setTimeout(function() { + make_execuable_file('single_instance/open_single', done); + }, 3000); + }); + + after(function() { + fs.remove('tmp-nw', function (er) { + if (er) throw er; + }); + }); + + it('should have a instance (open app)', function(done) { + child1 = exec('open ' + mac_app_path); + setTimeout(function () { + var content = fs.readFileSync('tmp-nw/msg_s'); + if (content + "" != "") + done(); + else + done("not have a instance"); + }, 6000); + }); + + it('should not have a second instance (open app)', function(done) { + child2 = exec('open ' + mac_app_path); + var content = fs.readFileSync('tmp-nw/msg_s'); + if (content + "" == "11") + done("have a second instance"); + else + done(); + + }); + }); + } +}); diff --git a/tests/automatic_tests/single_instance/mul/index.html b/tests/automatic_tests/single_instance/mul/index.html new file mode 100644 index 0000000000..c432c370d3 --- /dev/null +++ b/tests/automatic_tests/single_instance/mul/index.html @@ -0,0 +1,10 @@ + + + + Mul-instance + + +

Mul-instance

+ We are using node.js + + diff --git a/tests/automatic_tests/single_instance/mul/package.json b/tests/automatic_tests/single_instance/mul/package.json new file mode 100644 index 0000000000..d00691e29a --- /dev/null +++ b/tests/automatic_tests/single_instance/mul/package.json @@ -0,0 +1,5 @@ +{ + "name": "nw-single-sinstance", + "main": "index.html", + "single-instance": false +} diff --git a/tests/automatic_tests/single_instance/open_mul/index.html b/tests/automatic_tests/single_instance/open_mul/index.html new file mode 100644 index 0000000000..b365a2557b --- /dev/null +++ b/tests/automatic_tests/single_instance/open_mul/index.html @@ -0,0 +1,19 @@ + + + + open Mul-instance + + +

open Mul-instance

+ We are using node.js + + + diff --git a/tests/automatic_tests/single_instance/open_mul/package.json b/tests/automatic_tests/single_instance/open_mul/package.json new file mode 100644 index 0000000000..d00691e29a --- /dev/null +++ b/tests/automatic_tests/single_instance/open_mul/package.json @@ -0,0 +1,5 @@ +{ + "name": "nw-single-sinstance", + "main": "index.html", + "single-instance": false +} diff --git a/tests/automatic_tests/single_instance/open_single/index.html b/tests/automatic_tests/single_instance/open_single/index.html new file mode 100644 index 0000000000..fdadd50e9d --- /dev/null +++ b/tests/automatic_tests/single_instance/open_single/index.html @@ -0,0 +1,18 @@ + + + + open Single-instance + + +

open Single-instance

+ We are using node.js + + + diff --git a/tests/automatic_tests/single_instance/open_single/package.json b/tests/automatic_tests/single_instance/open_single/package.json new file mode 100644 index 0000000000..e5e3247df4 --- /dev/null +++ b/tests/automatic_tests/single_instance/open_single/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw-single-sinstance", + "main": "index.html" +} diff --git a/tests/automatic_tests/single_instance/single/index.html b/tests/automatic_tests/single_instance/single/index.html new file mode 100644 index 0000000000..8281ed49db --- /dev/null +++ b/tests/automatic_tests/single_instance/single/index.html @@ -0,0 +1,10 @@ + + + + Single-instance + + +

Single-instance

+ We are using node.js + + diff --git a/tests/automatic_tests/single_instance/single/package.json b/tests/automatic_tests/single_instance/single/package.json new file mode 100644 index 0000000000..e5e3247df4 --- /dev/null +++ b/tests/automatic_tests/single_instance/single/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw-single-sinstance", + "main": "index.html" +} diff --git a/tests/automatic_tests/snapshot/1266-snapshot-crash-start/file_to_snapshot_to_app.bin.js b/tests/automatic_tests/snapshot/1266-snapshot-crash-start/file_to_snapshot_to_app.bin.js new file mode 100755 index 0000000000..cda251772f --- /dev/null +++ b/tests/automatic_tests/snapshot/1266-snapshot-crash-start/file_to_snapshot_to_app.bin.js @@ -0,0 +1,11 @@ +var sampleFunction; + +(function() +{ + var privateVar = 'private'; + + sampleFunction = function() + { + return privateVar+'67868'; + }; +})(); diff --git a/tests/automatic_tests/snapshot/1266-snapshot-crash-start/index.html b/tests/automatic_tests/snapshot/1266-snapshot-crash-start/index.html new file mode 100755 index 0000000000..e6d9329a9e --- /dev/null +++ b/tests/automatic_tests/snapshot/1266-snapshot-crash-start/index.html @@ -0,0 +1,26 @@ + + + + + snapshot test + + + +
Data...
+ + + + + diff --git a/tests/automatic_tests/snapshot/1266-snapshot-crash-start/package.json b/tests/automatic_tests/snapshot/1266-snapshot-crash-start/package.json new file mode 100755 index 0000000000..10d863b9ea --- /dev/null +++ b/tests/automatic_tests/snapshot/1266-snapshot-crash-start/package.json @@ -0,0 +1,5 @@ +{ + "name": "nw-demo", + "main": "index.html", + "snapshot" : "app.bin" +} diff --git a/tests/automatic_tests/snapshot/1266-snapshot-crash-start/script.js b/tests/automatic_tests/snapshot/1266-snapshot-crash-start/script.js new file mode 100755 index 0000000000..1b9218f143 --- /dev/null +++ b/tests/automatic_tests/snapshot/1266-snapshot-crash-start/script.js @@ -0,0 +1,4 @@ +$(document).ready(function() +{ + $('#content').html(sampleFunction()); +}); \ No newline at end of file diff --git a/tests/automatic_tests/snapshot/mocha_test.js b/tests/automatic_tests/snapshot/mocha_test.js index 9d4048a91d..5a9e5eac6c 100644 --- a/tests/automatic_tests/snapshot/mocha_test.js +++ b/tests/automatic_tests/snapshot/mocha_test.js @@ -3,61 +3,102 @@ var app_test = require('./nw_test_app'); var os = require('os'); var fs = require('fs-extra'); var cp = require('child_process'); +var snapshotPath; describe('snapshot', function() { - before(function(done) { - var snapshotExec; - if (os.platform() == 'darwin' || os.platform() == 'linux') { - snapshotExec = 'nwsnapshot'; - } - if (os.platform() == 'win32') { - snapshotExec = 'nwsnapshot.exe'; - } - - var snapshotPath = path.join(process.execPath, '..', snapshotExec); - - cp.execFile(snapshotPath, - ['--extra_code', 'mytest.js', 'mytest.bin'], - {cwd:'./' + global.tests_dir + '/snapshot/'}, - function (error, stdout, stderr) { - done(); - } - - ); - }) + describe('demo should work fine', function() { + before(function(done) { + var snapshotExec; + if (os.platform() == 'darwin') { + snapshotExec = '../../../../../../nwsnapshot'; + } + if (os.platform() == 'linux') { + snapshotExec = 'nwsnapshot'; + } + if (os.platform() == 'win32') { + snapshotExec = 'nwsnapshot.exe'; + } - after(function() { - fs.unlink(path.join(global.tests_dir, 'snapshot', 'mytest.bin'), function(err) {if(err && err.code !== 'ENOENT') throw err}); - fs.unlink(path.join(global.tests_dir, 'snapshot', 'v8.log'), function(err) {if(err && err.code !== 'ENOENT') throw err}); - }) + snapshotPath = path.join(process.execPath, '..', snapshotExec); + console.log("snapshotPath: " + snapshotPath); + + cp.execFile(snapshotPath, + ['--extra_code', 'mytest.js', 'mytest.bin'], + {cwd:'./' + global.tests_dir + '/snapshot/'}, + function (error, stdout, stderr) { + done(); + } + + ); + }) - it('the native code could be exectuted', - function(done) { - this.timeout(0); - var result = false; - - var child = app_test.createChildProcess({ - execPath: process.execPath, - appPath: path.join(global.tests_dir, 'snapshot'), - end: function(data, app) { - done(); - app.kill(); - result = true; - } - }); - - setTimeout(function(){ - if (!result) { - done('the native code does not been executed'); - child.close(); - //child.removeConnection(); - //child.app.kill(); - } - }, 3000); - //child.app.stderr.on('data', function(d){ console.log ('app' + d);}); + after(function() { + fs.unlink(path.join(global.tests_dir, 'snapshot', 'mytest.bin'), function(err) {if(err && err.code !== 'ENOENT') throw err}); + fs.unlink(path.join(global.tests_dir, 'snapshot', 'v8.log'), function(err) {if(err && err.code !== 'ENOENT') throw err}); + }) + it('the native code could be exectuted', + function(done) { + this.timeout(0); + var result = false; + + var child = app_test.createChildProcess({ + execPath: process.execPath, + appPath: path.join(global.tests_dir, 'snapshot'), + end: function(data, app) { + done(); + app.kill(); + result = true; + } + }); + + setTimeout(function(){ + if (!result) { + done('the native code does not been executed'); + child.close(); + //child.removeConnection(); + //child.app.kill(); + } + }, 3000); + //child.app.stderr.on('data', function(d){ console.log ('app' + d);}); + + }) }) + describe('1266-snapshot-crash-start', function() { + before(function(done) { + cp.execFile(snapshotPath, + ['--extra_code', 'file_to_snapshot_to_app.bin.js', 'app.bin'], + {cwd:'./' + global.tests_dir + '/snapshot/1266-snapshot-crash-start/'}, + function (error, stdout, stderr) { + done(); + } + + ); + }) + + after(function() { + fs.unlink(path.join(global.tests_dir, 'snapshot','1266-snapshot-crash-start', 'app.bin'), function(err) {if(err && err.code !== 'ENOENT') throw err}); + fs.unlink(path.join(global.tests_dir, 'snapshot','1266-snapshot-crash-start', 'v8.log'), function(err) {if(err && err.code !== 'ENOENT') throw err}); + fs.unlink(path.join(global.tests_dir, 'snapshot','1266-snapshot-crash-start','tmp'), function(err) {if(err && err.code !== 'ENOENT') throw err}); + }) + + it('another demo should close nomally', function(done) { + this.timeout(0); + var ppath = process.execPath + " " + path.join(global.tests_dir, 'snapshot', '1266-snapshot-crash-start'); + cp.exec(ppath, function(err, stdout, stderr) { + }); + setTimeout(function(){ + fs.exists(path.join(global.tests_dir, 'snapshot','1266-snapshot-crash-start', 'tmp'), function(exists) { + if (exists) + done(); + else + done('another demo fails'); + }); + }, 3000); + }); + }); + }) diff --git a/tests/automatic_tests/source-maps/index.html b/tests/automatic_tests/source-maps/index.html new file mode 100644 index 0000000000..2f42f24651 --- /dev/null +++ b/tests/automatic_tests/source-maps/index.html @@ -0,0 +1,86 @@ + + + + + Test case for source maps + + + +

Please wait to be closed.

+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/automatic_tests/source-maps/mocha_test.js b/tests/automatic_tests/source-maps/mocha_test.js new file mode 100644 index 0000000000..25dcd9569f --- /dev/null +++ b/tests/automatic_tests/source-maps/mocha_test.js @@ -0,0 +1,44 @@ +var path = require('path'); +var app_test = require('./nw_test_app'); +var assert = require('assert'); +var results; + +describe('source maps', function() { + before(function(done) { + this.timeout(0); + var child = app_test.createChildProcess({ + execPath: process.execPath, + appPath: path.join(global.tests_dir, 'source-maps'), + end: function(data, app) { + results = data; + app.kill(); + done(); + } + }); + }); + + it('should support Closure Compile', function() { + assert.equal("script.closure.js", results[0]); + assert.equal("script1.js", results[8]); + }); + + it('should support JSMin and Grunt', function() { + assert.equal("script.jsmin-grunt.js", results[4]); + assert.equal("script2.js", results[9]); + }); + + it('should support Uglifyjs', function() { + assert.equal('script.uglify.js', results[7]); + assert.equal('script.js', results[3]); + }); + + it('should support CoffeeScript', function() { + assert.equal('script.coffee.min.js', results[2]); + assert.equal('script.coffee.coffee', results[1]); + }); + + it('should support TypeScript', function() { + assert.equal('script.typescript.min.js', results[5]); + assert.equal('script.typescript.ts', results[6]); + }); +}); diff --git a/tests/automatic_tests/source-maps/package.json b/tests/automatic_tests/source-maps/package.json new file mode 100644 index 0000000000..862ea905fc --- /dev/null +++ b/tests/automatic_tests/source-maps/package.json @@ -0,0 +1,5 @@ +{ + "name": "nw-test-source-maps", + "main": "index.html", + "node-remote": "127.0.0.1" +} diff --git a/tests/automatic_tests/source-maps/scripts/compiler.jar b/tests/automatic_tests/source-maps/scripts/compiler.jar new file mode 100644 index 0000000000..53037b0e8a Binary files /dev/null and b/tests/automatic_tests/source-maps/scripts/compiler.jar differ diff --git a/tests/automatic_tests/source-maps/scripts/jquery.d.ts b/tests/automatic_tests/source-maps/scripts/jquery.d.ts new file mode 100644 index 0000000000..a7c4c53892 --- /dev/null +++ b/tests/automatic_tests/source-maps/scripts/jquery.d.ts @@ -0,0 +1,702 @@ +// Typing for the jQuery library, version 1.7.x + +/* + Interface for the AJAX setting that will configure the AJAX request +*/ +interface JQueryAjaxSettings { + accepts?: any; + async?: bool; + beforeSend?(jqXHR: JQueryXHR, settings: JQueryAjaxSettings); + cache?: bool; + complete?(jqXHR: JQueryXHR, textStatus: string); + contents?: { [key: string]: any; }; + contentType?: string; + context?: any; + converters?: { [key: string]: any; }; + crossDomain?: bool; + data?: any; + dataFilter?(data: any, ty: any): any; + dataType?: string; + error?(jqXHR: JQueryXHR, textStatus: string, errorThrow: string): any; + global?: bool; + headers?: { [key: any]: any; }; + ifModified?: bool; + isLocal?: bool; + jsonp?: string; + jsonpCallback?: any; + mimeType?: string; + password?: string; + processData?: bool; + scriptCharset?: string; + statusCode?: { [key: any]: any; }; + success?(data: any, textStatus: string, jqXHR: JQueryXHR); + timeout?: number; + traditional?: bool; + type?: string; + url?: string; + username?: string; + xhr?: any; + xhrFields?: { [key: any]: any; }; +} + +/* + Interface for the jqXHR object +*/ +interface JQueryXHR extends XMLHttpRequest { + overrideMimeType(); +} + +/* + Interface for the JQuery callback +*/ +interface JQueryCallback { + add(...callbacks: any[]): any; + disable(): any; + empty(): any; + fire(...arguments: any[]): any; + fired(): bool; + fireWith(context: any, ...args: any[]): any; + has(callback: any): bool; + lock(): any; + locked(): bool; + removed(...callbacks: any[]): any; +} + +/* + Interface for the JQuery promise, part of callbacks +*/ +interface JQueryPromise { + always(...alwaysCallbacks: any[]): JQueryDeferred; + done(...doneCallbacks: any[]): JQueryDeferred; + fail(...failCallbacks: any[]): JQueryDeferred; + pipe(doneFilter?: (x: any) => any, failFilter?: (x: any) => any, progressFilter?: (x: any) => any): JQueryPromise; + then(doneCallbacks: any, failCallbacks: any, progressCallbacks?: any): JQueryDeferred; +} + +/* + Interface for the JQuery deferred, part of callbacks +*/ +interface JQueryDeferred extends JQueryPromise { + notify(...args: any[]): JQueryDeferred; + notifyWith(context: any, ...args: any[]): JQueryDeferred; + + pipe(doneFilter?: any, failFilter?: any, progressFilter?: any): JQueryPromise; + progress(...progressCallbacks: any[]): JQueryDeferred; + reject(...args: any[]): JQueryDeferred; + rejectWith(context:any, ...args: any[]): JQueryDeferred; + resolve(...args: any[]): JQueryDeferred; + resolveWith(context:any, ...args: any[]): JQueryDeferred; + state(): string; + then(doneCallbacks: any, failCallbacks: any, progressCallbacks?: any): JQueryDeferred; +} + +/* + Interface of the JQuery extension of the W3C event object +*/ +interface JQueryEventObject extends Event { + data: any; + delegateTarget: Element; + isDefaultPrevented(): bool; + isImmediatePropogationStopped(): bool; + isPropogationStopped(): bool; + namespace: string; + preventDefault(): any; + relatedTarget: Element; + result: any; + stopImmediatePropagation(); + stopPropagation(); + pageX: number; + pageY: number; + which: number; + metaKey: any; +} + +/* + Collection of properties of the current browser +*/ +interface JQueryBrowserInfo { + safari:bool; + opera:bool; + msie:bool; + mozilla:bool; + version:string; +} + +interface JQuerySupport { + ajax?: bool; + boxModel?: bool; + changeBubbles?: bool; + checkClone?: bool; + checkOn?: bool; + cors?: bool; + cssFloat?: bool; + hrefNormalized?: bool; + htmlSerialize?: bool; + leadingWhitespace?: bool; + noCloneChecked?: bool; + noCloneEvent?: bool; + opacity?: bool; + optDisabled?: bool; + optSelected?: bool; + scriptEval?(): bool; + style?: bool; + submitBubbles?: bool; + tbody?: bool; +} + +/* + Static members of jQuery (those on $ and jQuery themselves) +*/ +interface JQueryStatic { + + /**** + AJAX + *****/ + ajax(url: string, settings: JQueryAjaxSettings); + + ajaxPrefilter(dataTypes: string, handler: (opts: any, originalOpts: any, jqXHR: JQueryXHR) => any): any; + ajaxPrefilter(handler: (opts: any, originalOpts: any, jqXHR: JQueryXHR) => any): any; + + ajaxSetup(options: any); + + get(url: string, data?: any, success?: any, dataType?: any): JQueryXHR; + getJSON(url: string, data?: any, success?: any): JQueryXHR; + getScript(url: string, success?: any): JQueryXHR; + + param(obj: any): string; + param(obj: any, traditional: bool): string; + + post(url: string, data?: any, success?: any, dataType?: any): JQueryXHR; + + /********* + CALLBACKS + **********/ + Callbacks(flags: any): JQueryCallback; + + /**** + CORE + *****/ + holdReady(hold: bool): any; + + (selector: string, context?: any): JQuery; + (element: Element): JQuery; + (object: { }): JQuery; + (elementArray: Element[]): JQuery; + (object: JQuery): JQuery; + (func: Function): JQuery; + (): JQuery; + + noConflict(removeAll?: bool): Object; + + when(...deferreds: any[]): JQueryPromise; + + /*** + CSS + ****/ + css(e: any, propertyName: string, value?: any); + css(e: any, propertyName: any, value?: any); + cssHooks: { [key: any]: any; }; + + /**** + DATA + *****/ + data(element: Element, key: string, value: any): Object; + + dequeue(element: Element, queueName?: string): any; + + hasData(element: Element): bool; + + queue(element: Element, queueName?: string): any[]; + queue(element: Element, queueName: string, newQueueOrCallback: any): JQuery; + + removeData(element: Element, name?: string): JQuery; + + /******* + EFFECTS + ********/ + fx: { tick: () => void; interval: number; stop: () => void; speeds: { slow: number; fast: number; }; off: bool; step: any; }; + + /****** + EVENTS + *******/ + proxy(context: any, name: any): any; + + /********* + INTERNALS + **********/ + error(message: any); + + /************* + MISCELLANEOUS + **************/ + expr: any; + fn: any; //TODO: Decide how we want to type this + isReady: bool; + + /********** + PROPERTIES + ***********/ + browser: JQueryBrowserInfo; + support: JQuerySupport; + + /********* + UTILITIES + **********/ + contains(container: Element, contained: Element): bool; + + each(collection: any, callback: (indexInArray: any, valueOfElement: any) => any): any; + + extend(target: any, ...objs: any[]): Object; + extend(deep: bool, target: any, ...objs: any[]): Object; + + globalEval(code: string): any; + + grep(array: any[], func: any, invert: bool): any[]; + + inArray(value: any, array: any[], fromIndex?: number): number; + + isArray(obj: any): bool; + isEmptyObject(obj: any): bool; + isFunction(obj: any): bool; + isNumeric(value: any): bool; + isPlainObject(obj: any): bool; + isWindow(obj: any): bool; + isXMLDoc(node: Node): bool; + + makeArray(obj: any): any[]; + + map(array: any[], callback: (elementOfArray: any, indexInArray: any) =>any): JQuery; + + merge(first: any[], second: any[]): any[]; + + noop(): any; + + now(): number; + + parseJSON(json: string): Object; + + //FIXME: This should return an XMLDocument + parseXML(data: string): any; + + queue(element: Element, queueName: string, newQueue: any[]): JQuery; + + trim(str: string): string; + + type(obj: any): string; + + unique(arr: any[]): any[]; +} + +/* + The jQuery instance members +*/ +interface JQuery { + /**** + AJAX + *****/ + ajaxComplete(handler: any): JQuery; + ajaxError(handler: (evt: any, xhr: any, opts: any) => any): JQuery; + ajaxSend(handler: (evt: any, xhr: any, opts: any) => any): JQuery; + ajaxStart(handler: () => any): JQuery; + ajaxStop(handler: () => any): JQuery; + ajaxSuccess(handler: (evt: any, xml: any, opts: any) => any): JQuery; + + load(url: string, data?: any, complete?: any): JQuery; + + serialize(): string; + serializeArray(): any[]; + + /********** + ATTRIBUTES + ***********/ + addClass(classNames: string): JQuery; + addClass(func: (index: any, currentClass: any) => JQuery); + + attr(attributeName: string): string; + attr(attributeName: string, value: any): JQuery; + attr(map: { [key: any]: any; }): JQuery; + attr(attributeName: string, func: (index: any, attr: any) => any): JQuery; + + hasClass(className: string): bool; + + html(htmlString: string): JQuery; + html(): string; + + prop(propertyName: string): string; + prop(propertyName: string, value: any): JQuery; + prop(map: any): JQuery; + prop(propertyName: string, func: (index: any, oldPropertyValue: any) => any): JQuery; + + removeAttr(attributeName: any): JQuery; + + removeClass(className?: any): JQuery; + removeClass(func: (index: any, cls: any) => any): JQuery; + + removeProp(propertyName: any): JQuery; + + toggleClass(className: any, swtch?: bool): JQuery; + toggleClass(swtch?: bool): JQuery; + toggleClass(func: (index: any, cls: any, swtch: any) => any): JQuery; + + val(): any; + val(value: string[]): JQuery; + val(value: string): JQuery; + val(func: (index: any, value: any) => any): JQuery; + + /*** + CSS + ****/ + css(propertyName: string, value?: any); + css(propertyName: any, value?: any); + + height(): number; + height(value: number): JQuery; + height(func: (index: any, height: any) => any): JQuery; + + innerHeight(): number; + innerWidth(): number; + + offset(): Object; + offset(coordinates: any): JQuery; + offset(func: (index: any, coords: any) => any): JQuery; + + outerHeight(includeMargin?: bool): number; + outerWidth(includeMargin?: bool): number; + + position(): { top: number; left: number; }; + + scrollLeft(): number; + scrollLeft(value: number): JQuery; + + scrollTop(): number; + scrollTop(value: number): JQuery; + + width(): number; + width(value: number): JQuery; + width(func: (index: any, height: any) => any): JQuery; + + /**** + DATA + *****/ + clearQueue(queueName?: string): JQuery; + + data(key: string, value: any): JQuery; + data(obj: { [key: string]: any; }): JQuery; + data(key?: string): any; + + dequeue(queueName?: string): JQuery; + + removeData(nameOrList?: any): JQuery; + + /******** + DEFERRED + *********/ + promise(type?: any, target?: any): JQueryPromise; + + /******* + EFFECTS + ********/ + animate(properties: any, duration?: any, easing?: string, complete?: Function): JQuery; + animate(properties: any, options: { duration?: any; easing?: string; complete?: Function; step?: Function; queue?: bool; specialEasing?: any; }); + + delay(duration: number, queueName?: string): JQuery; + + fadeIn(duration?: any, callback?: any): JQuery; + fadeIn(duration?: any, easing?: string, callback?: any): JQuery; + + fadeOut(duration?: any, callback?: any): JQuery; + fadeOut(duration?: any, easing?: string, callback?: any): JQuery; + + fadeTo(duration: any, opacity: number, callback?: any): JQuery; + fadeTo(duration: any, opacity: number, easing?: string, callback?: any): JQuery; + + fadeToggle(duration?: any, easing?: string, callback?: any): JQuery; + + hide(duration?: any, callback?: any): JQuery; + hide(duration?: any, easing?: string, callback?: any): JQuery; + + show(duration?: any, callback?: any): JQuery; + show(duration?: any, easing?: string, callback?: any): JQuery; + + slideDown(duration?: any, callback?: any): JQuery; + slideDown(duration?: any, easing?: string, callback?: any): JQuery; + + slideToggle(duration?: any, callback?: any): JQuery; + slideToggle(duration?: any, easing?: string, callback?: any): JQuery; + + slideUp(duration?: any, callback?: any): JQuery; + slideUp(duration?: any, easing?: string, callback?: any): JQuery; + + stop(clearQueue?: bool, jumpToEnd?: bool): JQuery; + stop(queue?:any, clearQueue?: bool, jumpToEnd?: bool): JQuery; + + toggle(duration?: any, callback?: any): JQuery; + toggle(duration?: any, easing?: string, callback?: any): JQuery; + toggle(showOrHide: bool): JQuery; + + /****** + EVENTS + *******/ + bind(eventType: string, eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + bind(eventType: string, eventData: any, preventBubble:bool): JQuery; + bind(eventType: string, preventBubble:bool): JQuery; + bind(...events: any[]); + + blur(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + blur(handler: (eventObject: JQueryEventObject) => any): JQuery; + + change(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + change(handler: (eventObject: JQueryEventObject) => any): JQuery; + + click(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + click(handler: (eventObject: JQueryEventObject) => any): JQuery; + + dblclick(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + dblclick(handler: (eventObject: JQueryEventObject) => any): JQuery; + + delegate(selector: any, eventType: string, handler: (eventObject: JQueryEventObject) => any): JQuery; + + + focus(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + focus(handler: (eventObject: JQueryEventObject) => any): JQuery; + + focusin(eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + focusin(handler: (eventObject: JQueryEventObject) => any): JQuery; + + focusout(eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + focusout(handler: (eventObject: JQueryEventObject) => any): JQuery; + + hover(handlerIn: (eventObject: JQueryEventObject) => any, handlerOut: (eventObject: JQueryEventObject) => any): JQuery; + hover(handlerInOut: (eventObject: JQueryEventObject) => any): JQuery; + + keydown(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + keydown(handler: (eventObject: JQueryEventObject) => any): JQuery; + + keypress(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + keypress(handler: (eventObject: JQueryEventObject) => any): JQuery; + + keyup(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + keyup(handler: (eventObject: JQueryEventObject) => any): JQuery; + + mousedown(eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + mousedown(handler: (eventObject: JQueryEventObject) => any): JQuery; + + mouseevent(eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + mouseevent(handler: (eventObject: JQueryEventObject) => any): JQuery; + + mouseleave(eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + mouseleave(handler: (eventObject: JQueryEventObject) => any): JQuery; + + mousemove(eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + mousemove(handler: (eventObject: JQueryEventObject) => any): JQuery; + + mouseout(eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + mouseout(handler: (eventObject: JQueryEventObject) => any): JQuery; + + mouseover(eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + mouseover(handler: (eventObject: JQueryEventObject) => any): JQuery; + + mouseup(eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + mouseup(handler: (eventObject: JQueryEventObject) => any): JQuery; + + off(events?: string, selector?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + off(eventsMap: { [key: string]: any; }, selector?: any): JQuery; + + on(events: string, selector?: any, data?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + on(eventsMap: { [key: string]: any; }, selector?: any, data?: any): JQuery; + + one(events: string, selector?: any, data?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + one(eventsMap: { [key: string]: any; }, selector?: any, data?: any): JQuery; + + ready(handler: any): JQuery; + + resize(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + resize(handler: (eventObject: JQueryEventObject) => any): JQuery; + + scroll(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + scroll(handler: (eventObject: JQueryEventObject) => any): JQuery; + + select(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + select(handler: (eventObject: JQueryEventObject) => any): JQuery; + + submit(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + submit(handler: (eventObject: JQueryEventObject) => any): JQuery; + + trigger(eventType: string, ...extraParameters: any[]): JQuery; + trigger(event: JQueryEventObject): JQuery; + + triggerHandler(eventType: string, ...extraParameters: any[]): Object; + + unbind(eventType?: string, handler?: (eventObject: JQueryEventObject) => any): JQuery; + unbind(eventType: string, fls: bool): JQuery; + unbind(evt: any): JQuery; + + undelegate(): JQuery; + undelegate(selector: any, eventType: string, handler?: (eventObject: JQueryEventObject) => any): JQuery; + undelegate(selector: any, events: any): JQuery; + undelegate(namespace: string): JQuery; + + /********* + INTERNALS + **********/ + + context: Element; + jquery: string; + pushStack(elements: any[]): JQuery; + pushStack(elements: any[], name: any, arguments: any): JQuery; + + /************ + MANIPULATION + *************/ + after(...content: any[]): JQuery; + after(func: (index: any) => any); + + append(...content: any[]): JQuery; + append(func: (index: any, html: any) => any); + + appendTo(target: any): JQuery; + + before(...content: any[]): JQuery; + before(func: (index: any) => any); + + clone(withDataAndEvents?: bool, deepWithDataAndEvents?: bool): JQuery; + + detach(selector?: any): JQuery; + + empty(): JQuery; + + insertAfter(target: any): JQuery; + insertBefore(target: any): JQuery; + + prepend(...content: any[]): JQuery; + prepend(func: (index: any, html: any) =>any): JQuery; + + prependTo(target: any): JQuery; + + remove(selector?: any): JQuery; + + replaceAll(target: any): JQuery; + + replaceWith(func: any): JQuery; + + text(textString: string): JQuery; + text(): string; + + toArray(): any[]; + + unwrap(): JQuery; + + wrap(wrappingElement: any): JQuery; + wrap(func: (index: any) =>any): JQuery; + + wrapAll(wrappingElement: any): JQuery; + + wrapInner(wrappingElement: any): JQuery; + wrapInner(func: (index: any) =>any): JQuery; + + /************* + MISCELLANEOUS + **************/ + each(func: (index: any, elem: Element) => JQuery); + + get(index?: number): any; + + index(selectorOrElement?: any): number; + + /********** + PROPERTIES + ***********/ + length: number; + [x: string]: HTMLElement; + [x: number]: HTMLElement; + + /********** + TRAVERSING + ***********/ + add(selector: string, context?: any): JQuery; + add(...elements: any[]): JQuery; + add(html: string): JQuery; + add(obj: JQuery): JQuery; + + andSelf(): JQuery; + + children(selector?: any): JQuery; + + closest(selector: string): JQuery; + closest(selector: string, context?: Element): JQuery; + closest(obj: JQuery): JQuery; + closest(element: any): JQuery; + closest(selectors: any, context?: Element): any[]; + + contents(): JQuery; + + end(): JQuery; + + eq(index: number): JQuery; + + filter(selector: string): JQuery; + filter(func: (index: any) =>any): JQuery; + filter(element: any): JQuery; + filter(obj: JQuery): JQuery; + + find(selector: string): JQuery; + find(element: any): JQuery; + find(obj: JQuery): JQuery; + + first(): JQuery; + + has(selector: string): JQuery; + has(contained: Element): JQuery; + + is(selector: string): JQuery; + is(func: (index: any) =>any): JQuery; + is(element: any): JQuery; + is(obj: JQuery): JQuery; + + last(): JQuery; + + map(callback: (index: any, domElement: Element) =>any): JQuery; + + next(selector?: string): JQuery; + + nextAll(selector?: string): JQuery; + + nextUntil(selector?: string, filter?: string): JQuery; + nextUntil(element?: Element, filter?: string): JQuery; + + not(selector: string): JQuery; + not(func: (index: any) =>any): JQuery; + not(element: any): JQuery; + not(obj: JQuery): JQuery; + + offsetParent(): JQuery; + + parent(selector?: string): JQuery; + + parents(selector?: string): JQuery; + + parentsUntil(selector?: string, filter?: string): JQuery; + parentsUntil(element?: Element, filter?: string): JQuery; + + prev(selector?: string): JQuery; + + prevAll(selector?: string): JQuery; + + prevUntil(selector?: string, filter?:string): JQuery; + prevUntil(element?: Element, filter?:string): JQuery; + + siblings(selector?: string): JQuery; + + slice(start: number, end?: number): JQuery; + + /********* + UTILITIES + **********/ + + queue(queueName?: string): any[]; + queue(queueName: string, newQueueOrCallback: any): JQuery; + queue(newQueueOrCallback: any): JQuery; +} + +declare var jQuery: JQueryStatic; +declare var $: JQueryStatic; \ No newline at end of file diff --git a/tests/automatic_tests/source-maps/scripts/script.closure.js b/tests/automatic_tests/source-maps/scripts/script.closure.js new file mode 100644 index 0000000000..be7ba3131e --- /dev/null +++ b/tests/automatic_tests/source-maps/scripts/script.closure.js @@ -0,0 +1,2 @@ +document.getElementById("color").focus();$(document).keydown(function(a){13==a.keyCode&&$("body").css("background-color",$("#color").val())}); +//@ sourceMappingURL=script.closure.js.map \ No newline at end of file diff --git a/tests/automatic_tests/source-maps/scripts/script.closure.js.map b/tests/automatic_tests/source-maps/scripts/script.closure.js.map new file mode 100644 index 0000000000..a3bb383db0 --- /dev/null +++ b/tests/automatic_tests/source-maps/scripts/script.closure.js.map @@ -0,0 +1,8 @@ +{ + "version":3, + "file":"script.closure.js", + "lineCount":1, + "mappings":"AACAA,QAAAC,eAAA,CAAwB,OAAxB,CAAAC,MAAA,EAEAC,EAAA,CAAEH,QAAF,CAAAI,QAAA,CAAoB,QAAQ,CAACC,CAAD,CAAM,CAGb,EAAnB,EAAIA,CAAAC,QAAJ,EAEEH,CAAA,CAAE,MAAF,CAAAI,IAAA,CAAc,kBAAd,CAAkCJ,CAAA,CAAE,QAAF,CAAAK,IAAA,EAAlC,CAL8B,CAAlC;", + "sources":["script1.js"], + "names":["document","getElementById","focus","$","keydown","evt","keyCode","css","val"] +} diff --git a/tests/automatic_tests/source-maps/scripts/script.coffee.coffee b/tests/automatic_tests/source-maps/scripts/script.coffee.coffee new file mode 100644 index 0000000000..506a5f7a53 --- /dev/null +++ b/tests/automatic_tests/source-maps/scripts/script.coffee.coffee @@ -0,0 +1,5 @@ +document.getElementById("color").focus() + +$(document).keydown (evt) -> + if evt.keyCode == 13 + $("body").css("background-color", $("#color").val()) diff --git a/tests/automatic_tests/source-maps/scripts/script.coffee.js b/tests/automatic_tests/source-maps/scripts/script.coffee.js new file mode 100644 index 0000000000..b7fa109dc5 --- /dev/null +++ b/tests/automatic_tests/source-maps/scripts/script.coffee.js @@ -0,0 +1,13 @@ +// Generated by CoffeeScript 1.4.0 +(function() { + + document.getElementById("color").focus(); + + $(document).keydown(function(evt) { + if (evt.keyCode === 13) { + return $("body").css("background-color", $("#color").val()); + } + }); + +}).call(this); +//@ sourceMappingURL=script.coffee.js.map \ No newline at end of file diff --git a/tests/automatic_tests/source-maps/scripts/script.coffee.js.map b/tests/automatic_tests/source-maps/scripts/script.coffee.js.map new file mode 100644 index 0000000000..26883a4421 --- /dev/null +++ b/tests/automatic_tests/source-maps/scripts/script.coffee.js.map @@ -0,0 +1 @@ +{"version":3,"file":"script.coffee.coffee","sources":["script.coffee.coffee"],"names":[],"mappings":"AAAC;AAAA,QAAQ,eAAe,CAAC,OAAD,CAAS,MAAM,CAAA,EAAtC;AAEA,CAAC,CAAC,QAAD,CAAU,QAAX,CAAoB,SAAA,CAAA,GAAA,CAAA;MACd,GAAG,QAAH,CAAA,GAAA,CAAe;WAChB,CAAC,CAAC,MAAD,CAAQ,IAAI,CAAC,kBAAD,EAAqB,CAAC,CAAC,QAAD,CAAU,IAAI,CAAA,CAApC;CAFlB"} diff --git a/tests/automatic_tests/source-maps/scripts/script.coffee.min.js b/tests/automatic_tests/source-maps/scripts/script.coffee.min.js new file mode 100644 index 0000000000..9c9e11d29e --- /dev/null +++ b/tests/automatic_tests/source-maps/scripts/script.coffee.min.js @@ -0,0 +1,2 @@ +(function(){document.getElementById("color").focus();$(document).keydown(function(evt){if(evt.keyCode===13){return $("body").css("background-color",$("#color").val())}})}).call(this); +//@ sourceMappingURL=script.coffee.min.js.map \ No newline at end of file diff --git a/tests/automatic_tests/source-maps/scripts/script.coffee.min.js.map b/tests/automatic_tests/source-maps/scripts/script.coffee.min.js.map new file mode 100644 index 0000000000..2d70896e3f --- /dev/null +++ b/tests/automatic_tests/source-maps/scripts/script.coffee.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"script.coffee.min.js","sources":["script.coffee.coffee"],"names":[],"mappings":"CAAC,WAEoB,SACX,eAAY,SAAA,OADrB,GAAA,UAAA,QAAA,SAAA,KAAA,GAAA,IAAA,UAAA,GAAA,CAAA,MAAA,GAAA,QAAA,IAAA,mBAAA,EAAA,UAAA,YAAA,KAAA"} \ No newline at end of file diff --git a/tests/automatic_tests/source-maps/scripts/script.js b/tests/automatic_tests/source-maps/scripts/script.js new file mode 100644 index 0000000000..a4aa1db970 --- /dev/null +++ b/tests/automatic_tests/source-maps/scripts/script.js @@ -0,0 +1,12 @@ +// on load, put focus on the input +document.getElementById("color").focus(); + +$(document).keydown(function(evt) { + + // only if the user keypress is ENTER + if (evt.keyCode == 13) { + // css color names and hex code + $("body").css("background-color", $("#color").val()); + } + +}); \ No newline at end of file diff --git a/tests/automatic_tests/source-maps/scripts/script.jsmin-grunt.js b/tests/automatic_tests/source-maps/scripts/script.jsmin-grunt.js new file mode 100644 index 0000000000..e0877b3d38 --- /dev/null +++ b/tests/automatic_tests/source-maps/scripts/script.jsmin-grunt.js @@ -0,0 +1,3 @@ + +document.getElementById("color").focus();$(document).keydown(function(evt){if(evt.keyCode==13){$("body").css("background-color",$("#color").val());}}); +//@ sourceMappingURL=script.jsmin-grunt.js.map \ No newline at end of file diff --git a/tests/automatic_tests/source-maps/scripts/script.jsmin-grunt.js.map b/tests/automatic_tests/source-maps/scripts/script.jsmin-grunt.js.map new file mode 100644 index 0000000000..7377de2425 --- /dev/null +++ b/tests/automatic_tests/source-maps/scripts/script.jsmin-grunt.js.map @@ -0,0 +1 @@ +{"version":3,"file":"script.jsmin-grunt.js","sources":["script2.js"],"names":[],"mappings":"AAAkC;AAClC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAExC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAGhC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,CAAE,CAAC,CAAC,CAAE,CAErB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CACtD,CAEF,CAAC,CAAC"} diff --git a/tests/automatic_tests/source-maps/scripts/script.typescript.js b/tests/automatic_tests/source-maps/scripts/script.typescript.js new file mode 100644 index 0000000000..f4687533d8 --- /dev/null +++ b/tests/automatic_tests/source-maps/scripts/script.typescript.js @@ -0,0 +1,7 @@ +document.getElementById("color").focus(); +$(document).keydown(function (evt) { + if(evt.keyCode == 13) { + $("body").css("background-color", $("#color").val()); + } +}); +//@ sourceMappingURL=script.typescript.js.map diff --git a/tests/automatic_tests/source-maps/scripts/script.typescript.js.map b/tests/automatic_tests/source-maps/scripts/script.typescript.js.map new file mode 100644 index 0000000000..034a30378e --- /dev/null +++ b/tests/automatic_tests/source-maps/scripts/script.typescript.js.map @@ -0,0 +1 @@ +{"version":3,"file":"script.typescript.js","sources":["script.typescript.ts"],"names":[""],"mappings":"AAAA,QAGQ,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE;AAExC,CAAC,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,UAAS,GAAG;IAG9BA,GAAIA,GAAGA,CAACA,OAAOA,IAAIA,EAAEA,CAACA;QAEpBA,CAACA,CAACA,MAAMA,CAACA,CAACA,GAAGA,CAACA,kBAAkBA,EAAEA,CAACA,CAACA,QAAQA,CAACA,CAACA,GAAGA,EAAEA,CAACA;KACrDA;AAEHA,CAACA,CAAC;AAAC"} \ No newline at end of file diff --git a/tests/automatic_tests/source-maps/scripts/script.typescript.min.js b/tests/automatic_tests/source-maps/scripts/script.typescript.min.js new file mode 100644 index 0000000000..75ed335420 --- /dev/null +++ b/tests/automatic_tests/source-maps/scripts/script.typescript.min.js @@ -0,0 +1,2 @@ +document.getElementById("color").focus();$(document).keydown(function(evt){if(evt.keyCode==13){$("body").css("background-color",$("#color").val())}}); +//@ sourceMappingURL=script.typescript.min.js.map \ No newline at end of file diff --git a/tests/automatic_tests/source-maps/scripts/script.typescript.min.js.map b/tests/automatic_tests/source-maps/scripts/script.typescript.min.js.map new file mode 100644 index 0000000000..7cd9eaa424 --- /dev/null +++ b/tests/automatic_tests/source-maps/scripts/script.typescript.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"script.typescript.min.js","sources":["script.typescript.ts"],"names":[],"mappings":"AAAA,SAGS,eAAe,SAAS,OAEjC,GAAE,UAAU,QAAQ,SAAS,KAG3B,GAAI,IAAI,SAAW,GAAG,CAEpB,EAAE,QAAQ,IAAI,mBAAoB,EAAE,UAAU"} \ No newline at end of file diff --git a/tests/automatic_tests/source-maps/scripts/script.typescript.ts b/tests/automatic_tests/source-maps/scripts/script.typescript.ts new file mode 100644 index 0000000000..20481fb5fc --- /dev/null +++ b/tests/automatic_tests/source-maps/scripts/script.typescript.ts @@ -0,0 +1,14 @@ +/// + +// on load, put focus on the input +document.getElementById("color").focus(); + +$(document).keydown(function(evt) { + + // only if the user keypress is ENTER + if (evt.keyCode == 13) { + // css color names and hex code + $("body").css("background-color", $("#color").val()); + } + +}); \ No newline at end of file diff --git a/tests/automatic_tests/source-maps/scripts/script.uglify.js b/tests/automatic_tests/source-maps/scripts/script.uglify.js new file mode 100644 index 0000000000..3392f6df36 --- /dev/null +++ b/tests/automatic_tests/source-maps/scripts/script.uglify.js @@ -0,0 +1,2 @@ +document.getElementById("color").focus();$(document).keydown(function(evt){if(evt.keyCode==13){$("body").css("background-color",$("#color").val())}}); +//@ sourceMappingURL=script.uglify.js.map \ No newline at end of file diff --git a/tests/automatic_tests/source-maps/scripts/script.uglify.js.map b/tests/automatic_tests/source-maps/scripts/script.uglify.js.map new file mode 100644 index 0000000000..cd200f92b4 --- /dev/null +++ b/tests/automatic_tests/source-maps/scripts/script.uglify.js.map @@ -0,0 +1 @@ +{"version":3,"file":"script.uglify.js","sources":["script.js"],"names":["document","getElementById","focus","$","keydown","evt","keyCode","css","val"],"mappings":"AACAA,SAASC,eAAe,SAASC,OAEjCC,GAAEH,UAAUI,QAAQ,SAASC,KAG3B,GAAIA,IAAIC,SAAW,GAAI,CAErBH,EAAE,QAAQI,IAAI,mBAAoBJ,EAAE,UAAUK"} \ No newline at end of file diff --git a/tests/automatic_tests/source-maps/scripts/script1.js b/tests/automatic_tests/source-maps/scripts/script1.js new file mode 100644 index 0000000000..a4aa1db970 --- /dev/null +++ b/tests/automatic_tests/source-maps/scripts/script1.js @@ -0,0 +1,12 @@ +// on load, put focus on the input +document.getElementById("color").focus(); + +$(document).keydown(function(evt) { + + // only if the user keypress is ENTER + if (evt.keyCode == 13) { + // css color names and hex code + $("body").css("background-color", $("#color").val()); + } + +}); \ No newline at end of file diff --git a/tests/automatic_tests/source-maps/scripts/script2.js b/tests/automatic_tests/source-maps/scripts/script2.js new file mode 100644 index 0000000000..a4aa1db970 --- /dev/null +++ b/tests/automatic_tests/source-maps/scripts/script2.js @@ -0,0 +1,12 @@ +// on load, put focus on the input +document.getElementById("color").focus(); + +$(document).keydown(function(evt) { + + // only if the user keypress is ENTER + if (evt.keyCode == 13) { + // css color names and hex code + $("body").css("background-color", $("#color").val()); + } + +}); \ No newline at end of file diff --git a/tests/automatic_tests/source-maps/styles/style.css b/tests/automatic_tests/source-maps/styles/style.css new file mode 100644 index 0000000000..ab7318910d --- /dev/null +++ b/tests/automatic_tests/source-maps/styles/style.css @@ -0,0 +1,20 @@ +@media -sass-debug-info{filename{font-family:file\:\/start\/styles\/style\.sass}line{font-family:\000034}} +body { + background-color: #d9d9d9; } + +@media -sass-debug-info{filename{font-family:file\:\/start\/styles\/style\.sass}line{font-family:\000037}} +input { + background-color: rgba(255, 255, 255, 0.5); + border: 0; + display: block; + font-size: 3em; + margin: 15% auto; + outline: none; + padding: 0.4em; + width: 20%; } + +@media only screen and (max-width: 600px) { +@media -sass-debug-info{filename{font-family:file\:\/start\/styles\/style\.sass}line{font-family:\0000318}} + input { + font-size: 2em; + width: 40%; } } diff --git a/tests/automatic_tests/source-maps/styles/style.sass b/tests/automatic_tests/source-maps/styles/style.sass new file mode 100644 index 0000000000..3a024e308e --- /dev/null +++ b/tests/automatic_tests/source-maps/styles/style.sass @@ -0,0 +1,20 @@ +$main-color: #000 +$secondary-color: #fff + +body + background-color: lighten($main-color, 85%) + +input + background-color: transparentize($secondary-color, 0.5) + border: 0 + display: block + font-size: 3em + margin: 15% auto + outline: none + padding: 0.4em + width: 20% + +@media only screen and (max-width: 600px) + input + font-size: 2em + width: 40% diff --git a/tests/automatic_tests/source-maps/styles/style.scss b/tests/automatic_tests/source-maps/styles/style.scss new file mode 100644 index 0000000000..21bf031a23 --- /dev/null +++ b/tests/automatic_tests/source-maps/styles/style.scss @@ -0,0 +1,24 @@ +$main-color: #000; +$secondary-color: #fff; + +body{ + background-color: lighten($main-color, 85%); +} + +input{ + background-color: transparentize($secondary-color, 0.5); + border: 0; + display: block; + font-size: 3em; + margin: 15% auto; + outline: none; + padding: 0.4em; + width: 20%; +} + +@media only screen and (max-width: 600px){ + input{ + font-size: 2em; + width: 40%; + } +} diff --git a/tests/automatic_tests/start_app/script.js b/tests/automatic_tests/start_app/script.js index 24f12776f5..93f2a649d5 100644 --- a/tests/automatic_tests/start_app/script.js +++ b/tests/automatic_tests/start_app/script.js @@ -5,20 +5,9 @@ var cp = require('child_process'); var exec = cp.exec; var sqawn = cp.sqawn; -var required_file_win = [ - 'ffmpegsumo.dll', - 'icudt.dll', - 'libEGL.dll', - 'libGLESv2.dll', - 'nw.exe', - 'nw.pak' -]; - -var required_file_linux = [ - 'nw', - 'nw.pak', - 'libffmpegsumo.so' -]; +var required_file_win_linux = fs.readdirSync(path.dirname(process.execPath)); +var required_file_win = required_file_win_linux; +var required_file_linux = required_file_win_linux; var required_file_macox = [ 'node-webkit.app' @@ -73,10 +62,12 @@ function copyExecFiles(done) { } -exports.copySourceFiles = function() { - fs.createReadStream(global.tests_dir + '/start_app/index.html').pipe( +exports.copySourceFiles = function(folder) { + if (folder == undefined) + folder = 'start_app'; + fs.createReadStream(global.tests_dir + '/' + folder + '/index.html').pipe( fs.createWriteStream('tmp-nw/index.html')); - fs.createReadStream(global.tests_dir + '/start_app/package.json').pipe( + fs.createReadStream(global.tests_dir + '/' + folder + '/package.json').pipe( fs.createWriteStream('tmp-nw/package.json')); } diff --git a/tests/automatic_tests/temp_dir/index.html b/tests/automatic_tests/temp_dir/index.html new file mode 100644 index 0000000000..cf9f0cafb8 --- /dev/null +++ b/tests/automatic_tests/temp_dir/index.html @@ -0,0 +1,26 @@ + + + + Test CASE FOR TMP DIR SHOULD BE REMOVED AFTER APP EXIT + + +

Hello World!

+ + + diff --git a/tests/automatic_tests/temp_dir/mocha_test.js b/tests/automatic_tests/temp_dir/mocha_test.js new file mode 100644 index 0000000000..8220f01460 --- /dev/null +++ b/tests/automatic_tests/temp_dir/mocha_test.js @@ -0,0 +1,67 @@ +var func = require('./' + global.tests_dir +'/start_app/script.js'); +var execPath = func.getExecPath(); +var fs = require('fs-extra'); +var os = require('os'); +var cp = require('child_process'); +var app_test = require('./nw_test_app'); +var current_path = process.cwd(); +var mac_app_path = path.join('tmp-nw', 'node-webkit.app'); +var assert = require('assert'); +var temp_path; +var execPath; + +if (os.platform() == 'win32') { + execPath = path.join('tmp-nw', 'app.exe'); +} +if (os.platform() == 'linux') { + execPath = path.join('tmp-nw', 'app'); +} +if (os.platform() == 'darwin') { + execPath = path.join('tmp-nw', 'node-webkit.app', 'Contents', 'MacOS', 'node-webkit'); +} + +function make_execuable_file(folder_path, done) { + func.copyExecFiles(function() { + func.copySourceFiles(folder_path); + func.zipSourceFiles(function() { + func.makeExecuableFile(); + if (os.platform() == 'darwin') { + var app_path = 'tmp-nw/node-webkit.app/Contents/Resources/app.nw'; + fs.mkdir(app_path, function(err) { + if(err && err.code !== 'EEXIST') throw err + fs.copy('tmp-nw/index.html', path.join(app_path, 'index.html')); + fs.copy('tmp-nw/package.html', path.join(app_path, 'package.html')); + setTimeout(done, 3000); + + }); + } else { + setTimeout(function() { + var child = cp.spawn(execPath, [current_path]); + child.on('exit', function() { + temp_path = path.dirname(fs.readFileSync(path.join('tmp-nw','path.org'))).substring(8); + done(); + }); + }, 3000); + } + }); + }); +} + +describe('temp_dir', function() { + this.timeout(0); + + before(function(done) { + make_execuable_file('temp_dir', done); + }); + + after(function() { + fs.remove('tmp-nw', function (er) { + if (er) throw er; + }); + }); + + it('should be removed after app exit', function() { + var exist = fs.existsSync(temp_path); + assert.equal(exist, false); + }); +}); diff --git a/tests/automatic_tests/temp_dir/package.json b/tests/automatic_tests/temp_dir/package.json new file mode 100644 index 0000000000..b9b7eb06de --- /dev/null +++ b/tests/automatic_tests/temp_dir/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw", + "main": "index.html" +} diff --git a/tests/automatic_tests/user-agent-app/index.html b/tests/automatic_tests/user-agent-app/index.html new file mode 100644 index 0000000000..79b8ccf5f8 --- /dev/null +++ b/tests/automatic_tests/user-agent-app/index.html @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/tests/automatic_tests/user-agent-app/mocha_test.js b/tests/automatic_tests/user-agent-app/mocha_test.js new file mode 100644 index 0000000000..418475a433 --- /dev/null +++ b/tests/automatic_tests/user-agent-app/mocha_test.js @@ -0,0 +1,33 @@ +var assert = require('assert'); +var fs = require('fs'); +var path = require('path'); +var app_test = require('./nw_test_app'); +describe('user-agent-app', function() { + it('user agent shoud be the same when open a new window', + function(done) { + this.timeout(0); + var result = false; + + var child = app_test.createChildProcess({ + execPath: process.execPath, + appPath: path.join(global.tests_dir, 'user-agent-app'), + end: function(data, app) { + result = true; + var package_info = JSON.parse(fs.readFileSync('automatic_tests/user-agent-app/package.json', 'utf8')); + app.kill(); + if (data == package_info.name) { + done(); + } + else{ + done('the user agent has changed'); + } + } + }); + setTimeout(function(){ + if (!result) { + child.close(); + done('loaded event does not been fired'); + } + }, 10000); + }) +}) diff --git a/tests/automatic_tests/user-agent-app/package.json b/tests/automatic_tests/user-agent-app/package.json new file mode 100644 index 0000000000..ec487cf9a5 --- /dev/null +++ b/tests/automatic_tests/user-agent-app/package.json @@ -0,0 +1,5 @@ +{ + "name": "user agent app", + "main": "index.html", + "user-agent": "%name/%nwver/%ver/%webkit_ver/%osinfo" +} \ No newline at end of file diff --git a/tests/automatic_tests/user-agent/index.html b/tests/automatic_tests/user-agent/index.html new file mode 100644 index 0000000000..08e44f45f2 --- /dev/null +++ b/tests/automatic_tests/user-agent/index.html @@ -0,0 +1,57 @@ + + + + Test Case For user-agent! + + +

Hello User-Agent

+ + + diff --git a/tests/automatic_tests/user-agent/index1.html b/tests/automatic_tests/user-agent/index1.html new file mode 100644 index 0000000000..829a1c6904 --- /dev/null +++ b/tests/automatic_tests/user-agent/index1.html @@ -0,0 +1,14 @@ + + + + Child Window For user-agent! + + +

Hello Child User-Agent

+ + + diff --git a/tests/automatic_tests/user-agent/mocha_test.js b/tests/automatic_tests/user-agent/mocha_test.js index bf70647686..128ba01ffb 100644 --- a/tests/automatic_tests/user-agent/mocha_test.js +++ b/tests/automatic_tests/user-agent/mocha_test.js @@ -1,17 +1,48 @@ var assert = require('assert'); var fs = require('fs'); +var app_test = require('./nw_test_app'); +var path = require('path'); +var results; describe('user-agent', function() { - var user_agent = navigator.userAgent.split('/'); - var package_info = JSON.parse(fs.readFileSync('package.json', 'utf8')); - it('name should be same to the one in package.json', - function() { - assert.equal(user_agent[0], package_info.name); - } - ) - - it('version should be same to the one in package.json', - function() { - assert.equal(user_agent[2], package_info.version); - } - ) + before(function(done) { + this.timeout(0); + var child = app_test.createChildProcess({ + execPath: process.execPath, + appPath: path.join(global.tests_dir, 'user-agent'), + end: function(data, app) { + results = data; + app.kill(); + done(); + } + }); + }); + + describe('user-agent with child window opened', function() { + it('should be same to the one in parent window', function() { + assert.equal(results[0], results[1]); + }); + }); + + describe('user-agent with reload', function() { + it('should be same with all reload method', function() { + assert.equal(results[0], results[2]); + assert.equal(results[0], results[3]); + assert.equal(results[0], results[4]); + assert.equal(results[0], results[5]); + }); + }); + + describe('user-agent with package.json', function() { + var user_agent = navigator.userAgent.split('/'); + var package_info = JSON.parse(fs.readFileSync('package.json', 'utf8')); + it('name should be same to the one in package.json', + function() { + assert.equal(user_agent[0], package_info.name); + }); + + it('version should be same to the one in package.json', + function() { + assert.equal(user_agent[2], package_info.version); + }); + }); }) diff --git a/tests/automatic_tests/user-agent/package.json b/tests/automatic_tests/user-agent/package.json new file mode 100644 index 0000000000..e106169070 --- /dev/null +++ b/tests/automatic_tests/user-agent/package.json @@ -0,0 +1,5 @@ +{ + "name": "nw-user-agent-test", + "main": "index.html", + "user-agent": "%name/%nwver/%ver/%webkit_ver/%osinfo" +} diff --git a/tests/automatic_tests/window-document-event/iframe.html b/tests/automatic_tests/window-document-event/iframe.html new file mode 100644 index 0000000000..bdcdcbb44e --- /dev/null +++ b/tests/automatic_tests/window-document-event/iframe.html @@ -0,0 +1,13 @@ + + + + + + + +

Iframe

+ + + \ No newline at end of file diff --git a/tests/automatic_tests/window-document-event/index.html b/tests/automatic_tests/window-document-event/index.html new file mode 100644 index 0000000000..a3c77ec8ec --- /dev/null +++ b/tests/automatic_tests/window-document-event/index.html @@ -0,0 +1,60 @@ + + + + + test + + +

it works!

+ + + + \ No newline at end of file diff --git a/tests/automatic_tests/window-document-event/mocha_test.js b/tests/automatic_tests/window-document-event/mocha_test.js new file mode 100644 index 0000000000..8d1f0a4efc --- /dev/null +++ b/tests/automatic_tests/window-document-event/mocha_test.js @@ -0,0 +1,77 @@ +var path = require('path'); +var spawn = require('child_process').spawn; +var server = global.server; +var server_port = global.port; +var assert = require('assert'); + +describe('document-start/end',function(){ + + var results = undefined; + var connection_handler_callback = undefined; + var connection_handler = function(socket){ + socket.on('data',function(data){ + results = JSON.parse(data+""); + if (typeof connection_handler_callback == 'function'){ + (connection_handler_callback)(); + } + }) + }; + var child = null; + before(function(done){ + this.timeout(0); + server.on('connection',connection_handler); + var app_path = path.join(global.tests_dir,'window-document-event'); + var exec_argv = [app_path,server_port]; + child = spawn(process.execPath,exec_argv); + connection_handler_callback = done; + //we call done when we receive data from child + }); + after(function(done){ + server.removeListener('connection',connection_handler); + child.kill(); + done(); + }); + + it('results should not equal undefined',function(done){ + assert.notEqual(results,undefined); + done(); + }) + + it('new window document-start run first',function(done){ + assert.equal(results[0]['flag'],true) + assert.equal(results[0]['name'],'top-window-document-start') + done(); + }); + it('new window script run between document-start and document-end',function(done){ + assert.equal(results[1],'new-window-script'); + done(); + }); + it('new window document-end should run before onload event',function(done){ + assert.equal(results[2]['flag'],true) + assert.equal(results[2]['name'],'top-window-document-end') + done(); + }); + it('new window onload should run last',function(done){ + assert.equal(results[3],'onload-from-new-window'); + done(); + }); + + it('iframe document-start should run first',function(done){ + assert.equal(results[4]['flag'],true) + assert.equal(results[4]['name'],'iframe-document-start') + done(); + }); + it('iframe script run between document-start and document-end',function(done){ + assert.equal(results[5],'iframe-script'); + done(); + }); + it('iframe document-end should run later',function(done){ + assert.equal(results[6]['flag'],true) + assert.equal(results[6]['name'],'iframe-document-end') + done(); + }); + it('iframe onload should run last',function(done){ + assert.equal(results[7],'onload-from-iframe'); + done(); + }); +}); \ No newline at end of file diff --git a/tests/automatic_tests/window-document-event/new_win.html b/tests/automatic_tests/window-document-event/new_win.html new file mode 100644 index 0000000000..aa328ef904 --- /dev/null +++ b/tests/automatic_tests/window-document-event/new_win.html @@ -0,0 +1,17 @@ + + + + + + + +

New Window

+ + + + \ No newline at end of file diff --git a/tests/automatic_tests/window-document-event/package.json b/tests/automatic_tests/window-document-event/package.json new file mode 100644 index 0000000000..85ea1d2a75 --- /dev/null +++ b/tests/automatic_tests/window-document-event/package.json @@ -0,0 +1,5 @@ +{ + "name":"nw_1403587578", + "main":"index.html", + "dependencies":{} +} \ No newline at end of file diff --git a/tests/automatic_tests/window-eval/iframe.html b/tests/automatic_tests/window-eval/iframe.html new file mode 100644 index 0000000000..ab1d2c0ffa --- /dev/null +++ b/tests/automatic_tests/window-eval/iframe.html @@ -0,0 +1,14 @@ + + + + + test + + +

iframe

+ + + + \ No newline at end of file diff --git a/tests/automatic_tests/window-eval/index.html b/tests/automatic_tests/window-eval/index.html new file mode 100644 index 0000000000..8df0e04e5a --- /dev/null +++ b/tests/automatic_tests/window-eval/index.html @@ -0,0 +1,32 @@ + + + + + test + + +

it works!

+ + + + + \ No newline at end of file diff --git a/tests/automatic_tests/window-eval/mocha_test.js b/tests/automatic_tests/window-eval/mocha_test.js new file mode 100644 index 0000000000..f595fbbb74 --- /dev/null +++ b/tests/automatic_tests/window-eval/mocha_test.js @@ -0,0 +1,42 @@ +var server = global.server; +var server_port = global.port; +var result = false; +var assert = require('assert'); +var spawn = require('child_process').spawn; +var path = require('path'); + +var connection_handler_callback = undefined; +var connection_handler = function(socket){ + socket.on('data',function(data){ + if (data.toString() == "success"){ + result = true; + } + if (typeof connection_handler_callback === 'function'){ + (connection_handler_callback)(); + } + }) +}; + +describe('Window.eval',function(){ + var child = null; + + before(function(done){ + this.timeout(0); + server.on('connection',connection_handler); + var app_path = path.join(global.tests_dir,'window-eval'); + var exec_args = [app_path,server_port]; + child = spawn(process.execPath,exec_args); + connection_handler_callback = done; + }); + + after(function(done){ + server.removeListener('connection',connection_handler); + child.kill(); + done(); + }); + + it("Window.eval should works",function(done){ + assert.equal(result,true); + done(); + }); +}) \ No newline at end of file diff --git a/tests/automatic_tests/window-eval/package.json b/tests/automatic_tests/window-eval/package.json new file mode 100644 index 0000000000..a33ab28774 --- /dev/null +++ b/tests/automatic_tests/window-eval/package.json @@ -0,0 +1,5 @@ +{ + "name":"nw_1403254112", + "main":"index.html", + "dependencies":{} +} \ No newline at end of file diff --git a/tests/automatic_tests/window/index.html b/tests/automatic_tests/window/index.html new file mode 100644 index 0000000000..3ebd01f650 --- /dev/null +++ b/tests/automatic_tests/window/index.html @@ -0,0 +1,57 @@ + + + + + Test Case for 'Window.focus' + + + +

For now you should manually click the button to test the case

+ + + + diff --git a/tests/automatic_tests/window/index1.html b/tests/automatic_tests/window/index1.html new file mode 100644 index 0000000000..0c6385bceb --- /dev/null +++ b/tests/automatic_tests/window/index1.html @@ -0,0 +1,19 @@ + + + + + new_win1 + + + +

Please focus this before click the according button .

+ + diff --git a/tests/automatic_tests/window/index2.html b/tests/automatic_tests/window/index2.html new file mode 100644 index 0000000000..d4f09e42d1 --- /dev/null +++ b/tests/automatic_tests/window/index2.html @@ -0,0 +1,10 @@ + + + + + new_win2 + + +

Please wait to be closed.

+ + diff --git a/tests/automatic_tests/window/mocha_test.js b/tests/automatic_tests/window/mocha_test.js new file mode 100644 index 0000000000..13a162a8dd --- /dev/null +++ b/tests/automatic_tests/window/mocha_test.js @@ -0,0 +1,29 @@ +var path = require('path'); +var app_test = require('./nw_test_app'); +var assert = require('assert'); +var window_counts = 3; +var results; + +describe('window', function() { + this.timeout(0); + before(function(done) { + this.timeout(0); + var child = app_test.createChildProcess({ + execPath: process.execPath, + appPath: path.join(global.tests_dir, 'window'), + end: function(data, app) { + results = data; + app.kill(); + done(); + } + }); + + }); + describe('focus()', function() { + it('should focus on the window', function() { + for (var i = 0; i < window_counts; i++) + assert.notEqual(results[i], 0) + }); + }) +}) + diff --git a/tests/automatic_tests/window/package.json b/tests/automatic_tests/window/package.json new file mode 100644 index 0000000000..1d6d4d34f4 --- /dev/null +++ b/tests/automatic_tests/window/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw-window-test", + "main": "index.html" +} diff --git a/tests/automation/.gitignore b/tests/automation/.gitignore new file mode 100644 index 0000000000..fdcf63a2db --- /dev/null +++ b/tests/automation/.gitignore @@ -0,0 +1,5 @@ +node_modules +output/ + +!call_require_with_node-main_set/node_modules/ + diff --git a/tests/automation/README.md b/tests/automation/README.md new file mode 100644 index 0000000000..5d1237b359 --- /dev/null +++ b/tests/automation/README.md @@ -0,0 +1,36 @@ +Automation +================= + +## Background + +The automation can replace existing automatic tests in node-webkit. + +This is fundmental framework enabling users to add mocha test cases in it. + +All test cases are running in seperated process so the whole test would not be blocked if some test case hang. + +Each test case would output separated report in file system. It becomes easier to check the test results. + +It allows to either run all test cases in batch or run each of them individually. + +## How to run automation: +Assume the current directory is in "automation" folder. + +1, At first time, install the npm modules: + + $npm install + +2, Run all test cases + + $node mocha_test.js + or simply call `run.sh` on Linux + +3, Run individual test case + + $nw + or + $quiet=false nw + +4, The test report will be generated under the `output` folder and sub folders named by current timestamp could be created. + + diff --git a/tests/automation/after_close_previous_window_then_open_new/index.html b/tests/automation/after_close_previous_window_then_open_new/index.html new file mode 100644 index 0000000000..bb4a76bccd --- /dev/null +++ b/tests/automation/after_close_previous_window_then_open_new/index.html @@ -0,0 +1,11 @@ + + + + +Test Window + + + + + + diff --git a/tests/automation/after_close_previous_window_then_open_new/internal/index.html b/tests/automation/after_close_previous_window_then_open_new/internal/index.html new file mode 100644 index 0000000000..d9480feeaa --- /dev/null +++ b/tests/automation/after_close_previous_window_then_open_new/internal/index.html @@ -0,0 +1,18 @@ + + + + +hi + + + diff --git a/tests/automation/after_close_previous_window_then_open_new/internal/package.json b/tests/automation/after_close_previous_window_then_open_new/internal/package.json new file mode 100644 index 0000000000..8748842da0 --- /dev/null +++ b/tests/automation/after_close_previous_window_then_open_new/internal/package.json @@ -0,0 +1,4 @@ +{ +"name": "nw-#1236", +"main": "index.html" +} diff --git a/tests/automation/after_close_previous_window_then_open_new/mocha_test.js b/tests/automation/after_close_previous_window_then_open_new/mocha_test.js new file mode 100644 index 0000000000..77540361cf --- /dev/null +++ b/tests/automation/after_close_previous_window_then_open_new/mocha_test.js @@ -0,0 +1,39 @@ +var path = require('path'); +var app_test = require('../../nw_test_app'); +var assert =require('assert'); +var fs = require('fs'); +var fs_extra = require('fs-extra'); +var spawn = require('child_process').spawn; +var global = require('../globals'); +var result; + +var dumpDir = path.join(global.tests_dir, 'internal', 'tmp'); +console.log('dumpDir:' + dumpDir); + +describe('after close previous window then open new', function() { + after(function () { + fs_extra.remove(dumpDir, function (er) { + if (er) throw er; + }); + }); + + before(function(done) { + this.timeout(0); + if (!fs.existsSync(dumpDir)) + fs.mkdirSync(dumpDir); + + var appPath = path.join(global.tests_dir, 'internal'); + + var exec_argv = [appPath]; + app = spawn(process.execPath, exec_argv); + setTimeout(function() { + done(); + }, 2000); + }); + + it('should not crash', function() { + result = fs.readdirSync(dumpDir); + assert.equal(result.length, 0); + }); + +}); diff --git a/tests/automation/after_close_previous_window_then_open_new/package.json b/tests/automation/after_close_previous_window_then_open_new/package.json new file mode 100644 index 0000000000..2d5dd8b8e4 --- /dev/null +++ b/tests/automation/after_close_previous_window_then_open_new/package.json @@ -0,0 +1,4 @@ +{ +"name": "after_close_previous_window_then_open_new_wrapper", +"main": "index.html" +} diff --git a/tests/automation/app-single-instance/app/index.html b/tests/automation/app-single-instance/app/index.html new file mode 100644 index 0000000000..95bbaf1f1f --- /dev/null +++ b/tests/automation/app-single-instance/app/index.html @@ -0,0 +1,22 @@ + + + + APP Single Instance + + +
+ + + \ No newline at end of file diff --git a/tests/automation/app-single-instance/app/package.json b/tests/automation/app-single-instance/app/package.json new file mode 100644 index 0000000000..cb2c05e819 --- /dev/null +++ b/tests/automation/app-single-instance/app/package.json @@ -0,0 +1,5 @@ +{ + "name":"ASI", + "main":"index.html", + "single-instance":true +} \ No newline at end of file diff --git a/tests/automation/app-single-instance/index.html b/tests/automation/app-single-instance/index.html new file mode 100644 index 0000000000..e0a9858acc --- /dev/null +++ b/tests/automation/app-single-instance/index.html @@ -0,0 +1,11 @@ + + + + +Test app single instance + + + + + + diff --git a/tests/automation/app-single-instance/mocha_test.js b/tests/automation/app-single-instance/mocha_test.js new file mode 100644 index 0000000000..4b8bad82b0 --- /dev/null +++ b/tests/automation/app-single-instance/mocha_test.js @@ -0,0 +1,42 @@ +var assert = require('assert'); +var path = require('path'); +var global = require('../globals'); + +describe('Single Instance APP', function() { + + var count = 0; + var onConnection = function(socket) { + socket.on('data', function(data) { + count += 1; + }); + }; + + var server = undefined; + + before(function(done) { + server = createTCPServer(13013); + server.on('connection', onConnection); + + var appPath = path.join(global.tests_dir, 'app'); + var child_1 = spawnChildProcess(appPath); + var child_2 = spawnChildProcess(appPath); + var child_3 = spawnChildProcess(appPath); + setTimeout(function() { + child_1.kill(); + child_2.kill(); + child_3.kill(); + done(); + }, 1000); + }); + + after(function() { + if (server) { + server.removeListener('connection',onConnection); + server.close(); + } + }); + + it('app should be single instance', function() { + assert.equal(count,1); + }); +}); diff --git a/tests/automation/app-single-instance/package.json b/tests/automation/app-single-instance/package.json new file mode 100644 index 0000000000..9c8363e52c --- /dev/null +++ b/tests/automation/app-single-instance/package.json @@ -0,0 +1,4 @@ +{ +"name": "app_single_instance_wrapper", +"main": "index.html" +} diff --git a/tests/automation/app/index.html b/tests/automation/app/index.html new file mode 100644 index 0000000000..893dc667f5 --- /dev/null +++ b/tests/automation/app/index.html @@ -0,0 +1,16 @@ + + + + + app test + + +

app test

+ + + + + + + + diff --git a/tests/automation/app/mocha_test.js b/tests/automation/app/mocha_test.js new file mode 100644 index 0000000000..09ab7c488c --- /dev/null +++ b/tests/automation/app/mocha_test.js @@ -0,0 +1,74 @@ +var gui = require('nw.gui'); +var path = require('path'); +var assert = require('assert'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + +describe('gui.App', function() { + + + var server, child, result = false; + + before(function(done) { + this.timeout(0); + server = createTCPServer(13013); + child = spawnChildProcess(path.join(curDir, 'internal')); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + result = true; + child.kill(); + done(); + }); + }); + + setTimeout(function() { + if (!result) { + child.kill(); + done('timeout'); + } + }, 4500); + + }); + + after(function () { + server.close(); + }); + + + describe('manifest', function() { + it('`gui.App.manifest` should equle to value of package.json', function() { + assert.equal(gui.App.manifest.name, 'nw-gui.App-test.wrapper'); + }); + + it('have main', function() { + assert.equal(typeof gui.App.manifest.main, 'string'); + assert.equal(gui.App.manifest.main, 'index.html'); + }); + + + it('have window', function() { + console.log('====have window:' + typeof gui.App.manifest.window); + assert.equal(typeof gui.App.manifest.window, 'object'); + }); + + it('have dependencies', function() { + assert.equal(typeof gui.App.manifest.dependencies, 'object'); + }); + + }); + + describe('clearCache()', function(done) { + it('should clear the HTTP cache in memory and the one on disk', function() { + var res_save = global.local_server.res_save; + + assert.equal(res_save[1].status, 304); + assert.equal(res_save[1].pathname, 'img.jpg'); + assert.equal(res_save[2].status, 200); + assert.equal(res_save[2].pathname, 'img.jpg'); + }); + }); + +}); + + diff --git a/tests/automation/app/package.json b/tests/automation/app/package.json new file mode 100644 index 0000000000..b76d2e98ac --- /dev/null +++ b/tests/automation/app/package.json @@ -0,0 +1,5 @@ +{ + "name":"nw-gui.App-test.wrapper", + "main":"index.html" +} + diff --git a/tests/automation/app_path_escape/index.html b/tests/automation/app_path_escape/index.html new file mode 100644 index 0000000000..5d7d344211 --- /dev/null +++ b/tests/automation/app_path_escape/index.html @@ -0,0 +1,9 @@ + + + + +Test path escape + + + + diff --git a/tests/automation/app_path_escape/mocha_test.js b/tests/automation/app_path_escape/mocha_test.js new file mode 100644 index 0000000000..e418b3155a --- /dev/null +++ b/tests/automation/app_path_escape/mocha_test.js @@ -0,0 +1,25 @@ +var spawn = require('child_process').spawn; +var os = require('os'); +var path = require('path'); +var global = require('../globals'); + +describe('app_path_escaping',function(){ + it('app should start normally with directory named xdk',function(done){ + this.timeout(0); + var opened = false; + var app = spawn(process.execPath, [path.join(global.tests_dir, 'xdk')]); + app.on('close',function(){ + opened = true; + done(); + }); + + setTimeout(function() { + if(!opened){ + app.kill(); + done('the app is not executed'); + } + }, 5000); + + }); +}); + diff --git a/tests/automation/app_path_escape/package.json b/tests/automation/app_path_escape/package.json new file mode 100644 index 0000000000..83005264a3 --- /dev/null +++ b/tests/automation/app_path_escape/package.json @@ -0,0 +1,4 @@ +{ +"name": "app_path_escape_wrapper", +"main": "index.html" +} diff --git a/tests/automation/app_path_escape/xdk/index.html b/tests/automation/app_path_escape/xdk/index.html new file mode 100644 index 0000000000..1b39fbb659 --- /dev/null +++ b/tests/automation/app_path_escape/xdk/index.html @@ -0,0 +1,15 @@ + + + + + test xdk + + + +hello world + + + diff --git a/tests/automation/app_path_escape/xdk/package.json b/tests/automation/app_path_escape/xdk/package.json new file mode 100644 index 0000000000..afd0be80f7 --- /dev/null +++ b/tests/automation/app_path_escape/xdk/package.json @@ -0,0 +1,4 @@ +{ + "name":"app path", + "main":"index.html" +} diff --git a/tests/automation/buff_from_string/index.html b/tests/automation/buff_from_string/index.html new file mode 100644 index 0000000000..e05147115e --- /dev/null +++ b/tests/automation/buff_from_string/index.html @@ -0,0 +1,16 @@ + + + + + + Buffer from string + + +

Buffer from string

+ + + + + + + diff --git a/tests/automation/buff_from_string/internal/index.html b/tests/automation/buff_from_string/internal/index.html new file mode 100644 index 0000000000..4034f877ee --- /dev/null +++ b/tests/automation/buff_from_string/internal/index.html @@ -0,0 +1,37 @@ + + + + + + Test -- Buffer from string + + +

We are using node.js + . +

+

Message: + +

+

+ +

+ + + + + diff --git a/tests/automation/buff_from_string/internal/package.json b/tests/automation/buff_from_string/internal/package.json new file mode 100644 index 0000000000..61a9047ed4 --- /dev/null +++ b/tests/automation/buff_from_string/internal/package.json @@ -0,0 +1,5 @@ +{ + "name":"buff_from_string", + "main":"index.html", + "dependencies":{} +} diff --git a/tests/automation/buff_from_string/internal/test.py b/tests/automation/buff_from_string/internal/test.py new file mode 100755 index 0000000000..5c9f9d149d --- /dev/null +++ b/tests/automation/buff_from_string/internal/test.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python + +from selenium import webdriver +import os +import traceback +import time +from selenium import webdriver +from platform import platform +import socket +from sys import argv + +port = 13013 +if len(argv) >= 2: + port = int(argv[1]) + + +path = os.path +dirname = path.abspath(path.dirname(__file__)) +chromedriver_path = path.join(dirname,"chromedriver2_server") + +nw = webdriver.Chrome(chromedriver_path,service_args=[dirname]) +input_element = nw.find_element_by_id("message") +input_element.send_keys("hello world") + +send_button = nw.find_element_by_id("send-message-btn") +send_button.click() + +time.sleep(1) +results = nw.execute_script('return JSON.stringify(results);') + +connection = socket.create_connection(("localhost",port)) +connection.sendall(results) +connection.close() + +def kill_process_tree(pid): + machine_type = platform() + if "Linux" in machine_type or "Darwin" in machine_type: + import psutil + parent = psutil.Process(spid) + for child in parent.get_children(recursive=True): + child.kill() + parent.kill() + return + elif 'Windows' in machine_type: + import subprocess + dev_null = open(os.devnull,"wb") + subprocess.Popen(['taskkill', '/F', '/T', '/PID', str(pid)],stdout=dev_null,stderr=dev_null) + return + else: + # print "Unknow OS type" + return + +time.sleep(2) +spid = nw.service.process.pid +kill_process_tree(spid) \ No newline at end of file diff --git a/tests/automation/buff_from_string/mocha_test.js b/tests/automation/buff_from_string/mocha_test.js new file mode 100644 index 0000000000..28c71a2ba3 --- /dev/null +++ b/tests/automation/buff_from_string/mocha_test.js @@ -0,0 +1,107 @@ +var assert = require('assert'); +var spawn = require('child_process').spawn; +var fs = require('fs-extra'); +var path = require('path'); +var curDir = fs.realpathSync('.'); +var func = require(path.join(curDir, '..', 'start_app', 'script.js')); + +var is_chromedriver_exists = false; + +describe("buff_from_string", function() { + var results = []; + var onConnection = function(socket) { + socket.on('data', function(data) { + results = JSON.parse(data.toString()); + }); + }; + + var server = undefined; + var tmpnwPath = path.join(curDir, 'tmp-nw'); + + before(function(done) { + this.timeout(0); + + server = createTCPServer(13013); + server.on('connection', onConnection); + + if (is_chromedriver_exists) { + func.copyExecFiles(function() { + var src_files = [ + path.join(curDir, 'internal', 'index.html'), + path.join(curDir, 'internal', 'package.json'), + path.join(curDir, 'internal', 'test.py') + ]; + var dst_files = [ + path.join(curDir, 'tmp-nw', 'index.html'), + path.join(curDir, 'tmp-nw', 'package.json'), + path.join(curDir, 'tmp-nw', 'test.py') + ]; + + if (process.platform == 'win32'){ + src_files.push(path.join(curDir, 'chromedriver2_server.exe')); + dst_files.push(path.join(curDir, 'tmp-nw', 'chromedriver2_server.exe')); + } else { + src_files.push(path.join(curDir, 'chromedriver2_server')); + dst_files.push(path.join(curDir, 'tmp-nw', 'chromedriver2_server')); + } + + for (var i=0;i + + + +

Test require()

+ + + + + + + + diff --git a/tests/automation/call_require_with_node-main_set/index.js b/tests/automation/call_require_with_node-main_set/index.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/automation/call_require_with_node-main_set/node_modules/test/index.js b/tests/automation/call_require_with_node-main_set/node_modules/test/index.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/automation/call_require_with_node-main_set/node_modules/test/package.json b/tests/automation/call_require_with_node-main_set/node_modules/test/package.json new file mode 100644 index 0000000000..f50d7198fd --- /dev/null +++ b/tests/automation/call_require_with_node-main_set/node_modules/test/package.json @@ -0,0 +1,4 @@ +{ + "name": "test", + "main": "index.js" +} diff --git a/tests/automation/call_require_with_node-main_set/package.json b/tests/automation/call_require_with_node-main_set/package.json new file mode 100644 index 0000000000..2b598748f1 --- /dev/null +++ b/tests/automation/call_require_with_node-main_set/package.json @@ -0,0 +1,5 @@ +{ + "name": "node-main", + "main": "index.html", + "node-main": "index.js" +} diff --git a/tests/automation/calls_across_window/index.html b/tests/automation/calls_across_window/index.html new file mode 100644 index 0000000000..409ebdd38e --- /dev/null +++ b/tests/automation/calls_across_window/index.html @@ -0,0 +1,11 @@ + + +Test call across window + + +

Test call across window

+ + + + + diff --git a/tests/automation/calls_across_window/internal/index.html b/tests/automation/calls_across_window/internal/index.html new file mode 100644 index 0000000000..890ad63698 --- /dev/null +++ b/tests/automation/calls_across_window/internal/index.html @@ -0,0 +1,121 @@ + + + + + + + + +
+ + + + + diff --git a/tests/automation/calls_across_window/internal/package.json b/tests/automation/calls_across_window/internal/package.json new file mode 100644 index 0000000000..59f6f7a921 --- /dev/null +++ b/tests/automation/calls_across_window/internal/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw-test", + "main": "index.html" +} diff --git a/tests/automation/calls_across_window/internal/test.html b/tests/automation/calls_across_window/internal/test.html new file mode 100644 index 0000000000..9eb8ca8e97 --- /dev/null +++ b/tests/automation/calls_across_window/internal/test.html @@ -0,0 +1,32 @@ + + + + + + +
+ +

+ + diff --git a/tests/automation/calls_across_window/mocha_test.js b/tests/automation/calls_across_window/mocha_test.js new file mode 100644 index 0000000000..d4e536dc52 --- /dev/null +++ b/tests/automation/calls_across_window/mocha_test.js @@ -0,0 +1,59 @@ +var fs = require('fs'); +var path = require('path'); +var global = {tests_dir: fs.realpathSync('.')}; + +describe('AppTest', function(){ + describe('call across window', function(){ + var child; + var server; + var exec_argv; + var socket; + + before(function(done){ + this.timeout(0); + server = createTCPServer(13013); + server.on('connection', function(theSocket) { + socket = theSocket; + socket.setEncoding('utf8'); + done(); + }); + exec_argv = path.join(global.tests_dir, 'internal'); + child = spawnChildProcess(exec_argv); + }); + + after(function(done){ + this.timeout(0); + child.kill(); + server.close(); + done(); + }); + + afterEach(function(){ + socket.removeAllListeners('data'); + }); + + it ('nw window function call', function(done) { + socket.on('data', function(data) { + if (data == 'ok') { + done(); + } else { + done(data); + } + }); + socket.write('newWindow'); + }); + + it ('brower window function call', function(done) { + socket.on('data', function(data) { + if (data == 'ok') { + done(); + } else { + done(data); + } + }); + socket.write('newBrowserWindow'); + }); + }); +}); + + diff --git a/tests/automation/calls_across_window/package.json b/tests/automation/calls_across_window/package.json new file mode 100644 index 0000000000..de96b12e22 --- /dev/null +++ b/tests/automation/calls_across_window/package.json @@ -0,0 +1,4 @@ +{ +"name": "call_cross_window_wrapper", +"main": "index.html" +} diff --git a/tests/automation/chromedriver2_server/chromedriver2_server_test.py b/tests/automation/chromedriver2_server/chromedriver2_server_test.py new file mode 100644 index 0000000000..52eeb0be29 --- /dev/null +++ b/tests/automation/chromedriver2_server/chromedriver2_server_test.py @@ -0,0 +1,26 @@ +import traceback +import time +import os +from selenium import webdriver + +#path = os.getcwd(); +#path = os.path.join(path, 'tmp-nw', 'chromedriver2_server'); +path = '/home/owen-cloud/Desktop/kevin/node-webkit/tests/tmp-nw/chromedriver2_server'; + +try: + driver = webdriver.Chrome(path); + driver.get('http://www.google.com'); + assert driver.title == 'Google' + + time.sleep(5) # Let the user actually see something! + search_box = driver.find_element_by_name('q') + search_box.send_keys('ChromeDriver') + search_box.submit() + time.sleep(5) # Let the user actually see something! + assert driver.title[0:12] == 'ChromeDriver' + driver.quit() +except: + driver.quit(); + traceback.print_exc() +else: + print 'pass' diff --git a/tests/automation/chromedriver2_server/index.html b/tests/automation/chromedriver2_server/index.html new file mode 100644 index 0000000000..1a9a8cb41d --- /dev/null +++ b/tests/automation/chromedriver2_server/index.html @@ -0,0 +1,11 @@ + + +Test chrome driver server + + +

Test chrome driver server

+ + + + + diff --git a/tests/automation/chromedriver2_server/mocha_test.js b/tests/automation/chromedriver2_server/mocha_test.js new file mode 100644 index 0000000000..d778f71687 --- /dev/null +++ b/tests/automation/chromedriver2_server/mocha_test.js @@ -0,0 +1,61 @@ +var assert = require('assert'); +var exec = require('child_process').exec; +var fs = require('fs-extra'); +var path = require('path'); +var curDir = fs.realpathSync('.'); +var func = require(path.join(curDir, '..', 'start_app', 'script.js')); +var is_chromedriver_exists = false; +var result = []; +var tmpnwPath = path.join(curDir, 'tmp-nw'); + +describe('chromedriver2_server', function() { + this.timeout(0); + + before(function(done) { + var srcFile; + var dstFile; + if (process.platform == 'win32'){ + srcFile = path.join(curDir, 'chromedriver2_server.exe'); + dstFile = path.join(curDir, 'tmp-nw', 'chromedriver2_server.exe'); + } else { + srcFile = path.join(curDir, 'chromedriver2_server'); + dstFile = path.join(curDir, 'tmp-nw', 'chromedriver2_server'); + } + if (!fs.existsSync(srcFile)) { + is_chromedriver_exists = false; + done(); + } else { + func.copyExecFiles(function() { + fs.copySync(srcFile, dstFile); + exec('python ' + path.join(curDir, 'chromedriver2_server_test.py'), + function (error, stdout, stderr) { + result.push(error); + result.push(stdout); + result.push(stderr); + done(); + }); // exec + }); // copyExecfiles + } + + }); + + after(function () { + if (fs.existsSync(tmpnwPath)) { + fs.removeSync(tmpnwPath); + } + }); + + it('should work' ,function(done) { + if (!is_chromedriver_exists) { + done('chromedrier2_server does not exist'); + } else { + assert.equal(result[0], null); + assert.equal(result[1], 'pass\n'); + assert.equal(result[2], ''); + done(); + } + }); + +}); + + diff --git a/tests/automation/chromedriver2_server/package.json b/tests/automation/chromedriver2_server/package.json new file mode 100644 index 0000000000..8570c43eb9 --- /dev/null +++ b/tests/automation/chromedriver2_server/package.json @@ -0,0 +1,4 @@ +{ +"name": "chromedriver2_server_wrapper", +"main": "index.html" +} diff --git a/tests/automation/chromium-args/index.html b/tests/automation/chromium-args/index.html new file mode 100644 index 0000000000..2e5ba2f6f8 --- /dev/null +++ b/tests/automation/chromium-args/index.html @@ -0,0 +1,15 @@ + + + + + test chromium args + + +

Test chromium args

+ + + + + + + diff --git a/tests/automation/chromium-args/internal/index.html b/tests/automation/chromium-args/internal/index.html new file mode 100644 index 0000000000..e54e4e94b5 --- /dev/null +++ b/tests/automation/chromium-args/internal/index.html @@ -0,0 +1,22 @@ + + + + test case for chromium-args + + +

Hello World!

+ + + diff --git a/tests/automation/chromium-args/internal/package1.json b/tests/automation/chromium-args/internal/package1.json new file mode 100644 index 0000000000..ae0e1396da --- /dev/null +++ b/tests/automation/chromium-args/internal/package1.json @@ -0,0 +1,9 @@ +{ + "name": "nw-chromium-args", + "main": "index.html", + "window": { + "show": false + }, + "chromium-args": "--disable-javascript" + +} diff --git a/tests/automation/chromium-args/internal/package2.json b/tests/automation/chromium-args/internal/package2.json new file mode 100644 index 0000000000..d1f533f2a9 --- /dev/null +++ b/tests/automation/chromium-args/internal/package2.json @@ -0,0 +1,9 @@ +{ + "name": "nw-chromium-args", + "main": "index.html", + "window": { + "show": false + }, + "chromium-args": "--app=http://www.baidu.com" + +} diff --git a/tests/automation/chromium-args/mocha_test.js b/tests/automation/chromium-args/mocha_test.js new file mode 100644 index 0000000000..e0ae047d3e --- /dev/null +++ b/tests/automation/chromium-args/mocha_test.js @@ -0,0 +1,93 @@ +var assert = require('assert'); +var cp = require('child_process'); +var fs = require('fs'); +var fs_extra = require('fs-extra'); +var curDir = fs.realpathSync('.'); +var exec_path = process.execPath; +var app_path = path.join(curDir, 'internal'); + +describe('chromium-args', function() { + + + describe('--disable-javascript', function() { + + var child; + + before(function(done) { + this.timeout(0); + fs_extra.copy(path.join(app_path, 'package1.json'), path.join(app_path, 'package.json'), function (err) { + if (err) { + throw err; + } + child = spawnChildProcess(path.join(curDir, 'internal')); + }); + + setTimeout(function(){done();}, 2500); + }); + + after(function() { + if (child) + child.kill(); + fs.unlink(path.join(app_path, 'hi'), function(err) {if(err && err.code !== 'ENOENT') throw err}); + fs.unlink(path.join(app_path, 'package.json'), function(err) {if(err && err.code !== 'ENOENT') throw err}); + }); + + it('javascript should not be executed', function() { + var result = fs.existsSync(path.join(app_path, 'hi')); + assert.equal(result, false); + }); + + }); + + + describe('--app=url', function() { + var result = false, result2 = false; + + var child, server; + + before(function(done) { + this.timeout(0); + fs_extra.copy(path.join(app_path, 'package2.json'), path.join(app_path, 'package.json'), function (err) { + if (err) { + throw err; + } + + server = createTCPServer(13013); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + result2 = data; + result = true; + done(); + }); + }); + + child = spawnChildProcess(path.join(curDir, 'internal')); + + setTimeout(function() { + if (!result) { + child.kill(); + done('timeout'); + } + }, 4500); + + }); + }); + + after(function(done) { + this.timeout(0); + child.kill(); + server.close(); + fs.unlink(path.join(app_path, 'package.json'), function(err) {if(err && err.code !== 'ENOENT') throw err}); + fs.unlink(path.join(app_path, 'hi'), function(err) {if(err && err.code !== 'ENOENT') throw err}); + done(); + }); + + it('website should be the url', function() { + assert.equal(result2, true); + }); + + + }); + +}); diff --git a/tests/automation/chromium-args/package.json b/tests/automation/chromium-args/package.json new file mode 100644 index 0000000000..a556d2f655 --- /dev/null +++ b/tests/automation/chromium-args/package.json @@ -0,0 +1,4 @@ +{ + "name":"chromium-args_wrapper", + "main":"index.html" +} diff --git a/tests/automation/config.json b/tests/automation/config.json new file mode 100644 index 0000000000..03f8af248b --- /dev/null +++ b/tests/automation/config.json @@ -0,0 +1,10 @@ +{ + "format": "xunit-file", + "exclude": ["node_modules", "output", "internal", "res", "node-remote", "save_devtools_settings", "single_instance", + "call_require_with_node-main_set", "show_devtool_after_http_server_created_in_node_main", "reference-node-main"], + "single": "", + "timeout": "8000", + "slow": "200", + "quiet": false +} + diff --git a/tests/automation/console/index.html b/tests/automation/console/index.html new file mode 100644 index 0000000000..d22f7ba617 --- /dev/null +++ b/tests/automation/console/index.html @@ -0,0 +1,15 @@ + + + + + Console test + + +

Console test

+ + + + + + + diff --git a/tests/automation/console/internal/index.html b/tests/automation/console/internal/index.html new file mode 100644 index 0000000000..b7c3e037d4 --- /dev/null +++ b/tests/automation/console/internal/index.html @@ -0,0 +1,94 @@ + + + + + Test Case for 'console.log' + + +

Please wait to be closed.

+ + + diff --git a/tests/automation/console/internal/package.json b/tests/automation/console/internal/package.json new file mode 100644 index 0000000000..085e140394 --- /dev/null +++ b/tests/automation/console/internal/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw-console-test", + "main": "index.html" +} diff --git a/tests/automation/console/mocha_test.js b/tests/automation/console/mocha_test.js new file mode 100644 index 0000000000..9775adb7c5 --- /dev/null +++ b/tests/automation/console/mocha_test.js @@ -0,0 +1,136 @@ +var path = require('path'); +var assert = require('assert'); +var fs = require('fs'); +var curDir = fs.realpathSync('.'); + +var results; +var expected = new Array( + 'New York', + '18', + 'true', + 'false', + '
Array[3]
    ', + '
    Object
      ', + 'undefined', + 'null', + 'Node count: 2 Done', + 'Sam', + '100', + '2', + '12.23', + '
      1. <!DOCTYPE html>
      ', + '
      1. <!DOCTYPE html>
      ', + '
      <!DOCTYPE html>
        ', + ' Sam has 100 points and 2 pencils.He carrys 12.23 kg of water', + 'Sam has 100 points and 2 pencils.He carrys 12.23 kg of water.
        1. <!DOCTYPE html>
        <!DOCTYPE html>
          '); + + +describe('console.log', function() { + + var child, server; + + before(function(done) { + this.timeout(0); + + child = spawnChildProcess(path.join(curDir, 'internal')); + + server = createTCPServer(13013); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + results = JSON.parse(data); + done(); + }); + }); + + setTimeout(function() { + done('timeout'); + }, 4500); + + }); + + after(function(done){ + this.timeout(0); + child.kill(); + server.close(); + done(); + }); + + describe('output data types', function() { + it('should display string correctly', function() { + assert.equal(results[0], expected[0]); + }); + + it('should display number correctly', function() { + assert.equal(results[1], expected[1]); + }); + + it('should display boolean correctly', function() { + assert.equal(results[2], expected[2]); + assert.equal(results[3], expected[3]); + }); + + it('should display array correctly', function() { + assert.equal(results[4], expected[4]); + }); + + it('should display object correctly', function() { + assert.equal(results[5], expected[5]); + }); + + it('should display undefined correctly', function() { + assert.equal(results[6], expected[6]); + }); + + it('should display null correctly', function() { + assert.equal(results[7], expected[7]); + }); + }); + + + describe('concatenate expressions', function() { + it('should display string correctly', function() { + assert.equal(results[8], expected[8]); + }); + }); + + describe('format output', function() { + it('should display correctly with %s', function() { + assert.equal(results[9], expected[9]); + }); + + it('should display correctly with %d', function() { + assert.equal(results[10], expected[10]); + }); + + it('should display correcty with %i', function() { + assert.equal(results[11], expected[11]); + }); + + it('should display correcty with %f', function() { + assert.equal(results[12], expected[12]); + }); + + it('should display correcty object', function() { + assert.equal(results[13], expected[13]); + }); + + it('should display correcty with %o', function() { + assert.equal(results[14], expected[14]); + }); + + it('should display correcty with %O', function() { + assert.equal(results[15], expected[15]); + }); + + it('should display correcty with %c', function() { + assert.equal(results[16], expected[16]); + }); + + it('should display correcty with all formats', function() { + assert.equal(results[17], expected[17]); + }); + }) +}); + + diff --git a/tests/automation/console/package.json b/tests/automation/console/package.json new file mode 100644 index 0000000000..6c239d47d2 --- /dev/null +++ b/tests/automation/console/package.json @@ -0,0 +1,4 @@ +{ + "name":"console_wrapper", + "main":"index.html" +} diff --git a/tests/automation/cookies_api/index.html b/tests/automation/cookies_api/index.html new file mode 100644 index 0000000000..60894e6495 --- /dev/null +++ b/tests/automation/cookies_api/index.html @@ -0,0 +1,15 @@ + + + + + Cookie api test + + +

          cookie api test

          + + + + + + + diff --git a/tests/automation/cookies_api/internal/index.html b/tests/automation/cookies_api/internal/index.html new file mode 100644 index 0000000000..32a7c785ac --- /dev/null +++ b/tests/automation/cookies_api/internal/index.html @@ -0,0 +1,128 @@ + + + + TEST CASE FOR COOKIES API + + +

          Hello World!

          + We are using node.js + + + diff --git a/tests/automation/cookies_api/internal/package.json b/tests/automation/cookies_api/internal/package.json new file mode 100644 index 0000000000..7578686d40 --- /dev/null +++ b/tests/automation/cookies_api/internal/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw1", + "main": "index.html" +} diff --git a/tests/automation/cookies_api/mocha_test.js b/tests/automation/cookies_api/mocha_test.js new file mode 100644 index 0000000000..220ab5fab7 --- /dev/null +++ b/tests/automation/cookies_api/mocha_test.js @@ -0,0 +1,144 @@ +var path = require('path'); +var assert = require('assert'); +var spawn = require('child_process').spawn; +var fs = require('fs'); +var curDir = fs.realpathSync('.'); + +var changed; + +describe('Window.cookies', function() { + + var server; + + var spawnChild = function(action) { + return spawn(process.execPath, [path.join(curDir, 'internal'), action]); + }; + + before(function(done){ + server = createTCPServer(13013); + done(); + }); + + after(function(done) { + this.timeout(0); + server.close(); + done(); + }); + +//// 1 + describe("get, getAll", function() { + + var child; + var results; + + before(function(done) { + this.timeout(0); + child = spawnChild(1); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + results = JSON.parse(data); + child.kill(); + done(); + }); + }); + }); + + after(function() { + server.removeAllListeners('connection'); + }); + + it('should get cookies', function() { + assert.equal(results[0], true); + assert.equal(results[1], true); + }); + }); + + +/////////// 2 + describe("set", function() { + describe("set lang en", function() { + before(function(done) { + this.timeout(0); + child = spawnChild(2); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + results = JSON.parse(data); + changed = results[1]; + child.kill(); + done(); + }); + }); + }); + + after(function() { + server.removeAllListeners('connection'); + }); + + it('should get cookies', function() { + assert.equal(results[0], true); + }) + }); + + describe("set lang zh", function() { + before(function(done) { + this.timeout(0); + child = spawnChild(3); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + results = JSON.parse(data); + changed = results[1]; + child.kill(); + done(); + }); + }); + }); + + after(function() { + server.removeAllListeners('connection'); + }); + + it('should get cookies', function() { + assert.equal(results[0], true); + }) + }); + + }); + +//// 3, + describe('remove', function() { + before(function(done) { + this.timeout(0); + child = spawnChild(4); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + results = JSON.parse(data); + //changed = results[1]; + child.kill(); + done(); + }); + }); + }); + + after(function() { + server.removeAllListeners('connection'); + }); + + it('should remove lang', function() { + assert.equal(results[1], false); + }); + }); + +///////// 4, + describe('onChanged', function() { + it('should be called when changed', function() { + assert.equal(changed, true); + }) + }); + + +}); + diff --git a/tests/automation/cookies_api/package.json b/tests/automation/cookies_api/package.json new file mode 100644 index 0000000000..a4157547c7 --- /dev/null +++ b/tests/automation/cookies_api/package.json @@ -0,0 +1,4 @@ +{ + "name":"cookie_api_wrapper", + "main":"index.html" +} diff --git a/tests/automation/crash_dump/index.html b/tests/automation/crash_dump/index.html new file mode 100644 index 0000000000..84b14d54a0 --- /dev/null +++ b/tests/automation/crash_dump/index.html @@ -0,0 +1,15 @@ + + + + + Crash dump test + + +

          Crash dump test

          + + + + + + + diff --git a/tests/automation/crash_dump/internal/index.html b/tests/automation/crash_dump/internal/index.html new file mode 100644 index 0000000000..96f6403568 --- /dev/null +++ b/tests/automation/crash_dump/internal/index.html @@ -0,0 +1,29 @@ + + + + Test case for crash dump! + + +

          Hello World!

          + We are using node.js + + + diff --git a/tests/automation/crash_dump/internal/index1.html b/tests/automation/crash_dump/internal/index1.html new file mode 100644 index 0000000000..8ce42356ee --- /dev/null +++ b/tests/automation/crash_dump/internal/index1.html @@ -0,0 +1,19 @@ + + + + Test case for crash dump browser! + + +

          Hello World!

          + We are using node.js + + + diff --git a/tests/automation/crash_dump/internal/index2.html b/tests/automation/crash_dump/internal/index2.html new file mode 100644 index 0000000000..30ed9fa20a --- /dev/null +++ b/tests/automation/crash_dump/internal/index2.html @@ -0,0 +1,19 @@ + + + + Test case for crash dump render! + + +

          Hello World!

          + We are using node.js + + + diff --git a/tests/automation/crash_dump/internal/package.json b/tests/automation/crash_dump/internal/package.json new file mode 100644 index 0000000000..3842f972c4 --- /dev/null +++ b/tests/automation/crash_dump/internal/package.json @@ -0,0 +1,4 @@ +{ + "name": "crash_dump_test", + "main": "index.html" +} \ No newline at end of file diff --git a/tests/automation/crash_dump/mocha_test.js b/tests/automation/crash_dump/mocha_test.js new file mode 100644 index 0000000000..9136221a03 --- /dev/null +++ b/tests/automation/crash_dump/mocha_test.js @@ -0,0 +1,123 @@ +var path = require('path'); +var assert = require('assert'); +var fs = require('fs'); +var fs_extra = require('fs-extra'); +var curDir = fs.realpathSync('.'); +var tmpDir = path.join(curDir, 'internal', 'tmp'); + +var results; + +describe('crash dump', function() { + + var spawnChild = function(action) { + return spawn(process.execPath, [path.join(curDir, 'internal'), action]); + }; + + var server; + + before(function(done) { + + var ready = function() { + fs.mkdirSync(tmpDir); + server = createTCPServer(13013); + done(); + }; + + if (fs.existsSync(tmpDir)) { + fs_extra.remove(tmpDir, function() { + ready(); + }); + } else { + ready(); + } + + }); + + after(function () { + server.close(); + fs_extra.remove(tmpDir, function (er) { + if (er) throw er; + }); + }); + +////////// 1 + + describe('crashBrowser()', function() { + before(function(done) { + this.timeout(0); + var child = spawnChild(0); + + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + setTimeout(function() { + results = fs.readdirSync(tmpDir); + //child.kill(); + done(); + }, 2000); + }); + }); + + }); + + after(function() { + server.removeAllListeners('connection'); + }); + + it('should work fine', function() { + assert.equal(results.length, 1); + var r = results[0].indexOf("dmp"); + assert.notEqual(r, -1); + }); + + }); + + +///// 2 + + describe('crashRenderer()', function() { + before(function(done) { + this.timeout(0); + var child = spawnChild(1); + + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + setTimeout(function() { + results = fs.readdirSync(tmpDir); + //child.kill(); + done(); + }, 2000); + }); + }); + }); + + after(function() { + server.removeAllListeners('connection'); + }); + + it('should work fine', function() { + assert.equal(results.length, 2); + var r = results[1].indexOf("dmp"); + assert.notEqual(r, -1); + }); + }); + +//// 3, + describe('App.setCrashDumpDir(dir)', function() { + before(function(done) { + this.timeout(0); + results = fs.readdirSync(tmpDir); + done(); + }); + + it('should work fine', function() { + assert.equal(results.length, 2); + }); + }); + + +}); + + + diff --git a/tests/automation/crash_dump/package.json b/tests/automation/crash_dump/package.json new file mode 100644 index 0000000000..68be4da35c --- /dev/null +++ b/tests/automation/crash_dump/package.json @@ -0,0 +1,4 @@ +{ + "name":"crash_dump_wrapper", + "main":"index.html" +} diff --git a/tests/automation/crashonreload/index.html b/tests/automation/crashonreload/index.html new file mode 100644 index 0000000000..58c3cfb44f --- /dev/null +++ b/tests/automation/crashonreload/index.html @@ -0,0 +1,15 @@ + + + + + Crash on reload test + + +

          Crash on reload test

          + + + + + + + diff --git a/tests/automation/crashonreload/internal/index.html b/tests/automation/crashonreload/internal/index.html new file mode 100644 index 0000000000..97d5bd432b --- /dev/null +++ b/tests/automation/crashonreload/internal/index.html @@ -0,0 +1,21 @@ + + + +test dev tools + + +hello world + + + + diff --git a/tests/automation/crashonreload/internal/package.json b/tests/automation/crashonreload/internal/package.json new file mode 100644 index 0000000000..c04044da00 --- /dev/null +++ b/tests/automation/crashonreload/internal/package.json @@ -0,0 +1,4 @@ +{ + "name":"dev tools", + "main":"index.html" +} \ No newline at end of file diff --git a/tests/automation/crashonreload/internal/test.html b/tests/automation/crashonreload/internal/test.html new file mode 100644 index 0000000000..c72e1692d8 --- /dev/null +++ b/tests/automation/crashonreload/internal/test.html @@ -0,0 +1,9 @@ + + + +test dev tools + + +hello world + + \ No newline at end of file diff --git a/tests/automation/crashonreload/mocha_test.js b/tests/automation/crashonreload/mocha_test.js new file mode 100644 index 0000000000..a0c4e29b55 --- /dev/null +++ b/tests/automation/crashonreload/mocha_test.js @@ -0,0 +1,31 @@ +var spawn = require('child_process').spawn; +var path = require('path'); +var fs = require('fs'); +var curDir = fs.realpathSync('.'); + +describe('crash on reload',function(){ + it('nw should not crash when reloading with devtool opened',function(done){ + this.timeout(0); + var result = false; + + var child = spawnChildProcess(path.join(curDir, 'internal')); + child.on('exit', function (code){ + result = true; + if (code != 0) { + done('nw crashes'); + child.kill(); + } else { + done(); + } + + }); + + setTimeout(function(){ + if (!result) { + child.kill(); + done(); + } + + }, 4500); + }); +}); diff --git a/tests/automation/crashonreload/package.json b/tests/automation/crashonreload/package.json new file mode 100644 index 0000000000..afb89973c8 --- /dev/null +++ b/tests/automation/crashonreload/package.json @@ -0,0 +1,4 @@ +{ + "name":"crash_onreload_wrapper", + "main":"index.html" +} diff --git a/tests/automation/datapath/datacash-path/index.html b/tests/automation/datapath/datacash-path/index.html new file mode 100644 index 0000000000..3f67831e2a --- /dev/null +++ b/tests/automation/datapath/datacash-path/index.html @@ -0,0 +1,19 @@ + + +testing data path + + +

          Testing data path

          +the data path should be set as + + + diff --git a/tests/automation/datapath/datacash-path/package.json b/tests/automation/datapath/datacash-path/package.json new file mode 100644 index 0000000000..56305d8d64 --- /dev/null +++ b/tests/automation/datapath/datacash-path/package.json @@ -0,0 +1,5 @@ +{ + "name": "data path", + "main": "index.html", + "chromium-args": "--data-path='./data-cash/dataPath/'" +} diff --git a/tests/automation/datapath/datacash/index.html b/tests/automation/datapath/datacash/index.html new file mode 100644 index 0000000000..332964515e --- /dev/null +++ b/tests/automation/datapath/datacash/index.html @@ -0,0 +1,21 @@ + + +testing data path + + +

          Testing data path

          +the data path should be set as + + + diff --git a/tests/automation/datapath/datacash/package.json b/tests/automation/datapath/datacash/package.json new file mode 100644 index 0000000000..3551a65874 --- /dev/null +++ b/tests/automation/datapath/datacash/package.json @@ -0,0 +1,5 @@ +{ + "name": "data path", + "main": "index.html", + "chromium-args": "--data-path='./data-cash/'" +} diff --git a/tests/automation/datapath/datapath-cash/index.html b/tests/automation/datapath/datapath-cash/index.html new file mode 100644 index 0000000000..3f67831e2a --- /dev/null +++ b/tests/automation/datapath/datapath-cash/index.html @@ -0,0 +1,19 @@ + + +testing data path + + +

          Testing data path

          +the data path should be set as + + + diff --git a/tests/automation/datapath/datapath-cash/package.json b/tests/automation/datapath/datapath-cash/package.json new file mode 100644 index 0000000000..93f8b5f9d6 --- /dev/null +++ b/tests/automation/datapath/datapath-cash/package.json @@ -0,0 +1,5 @@ +{ + "name": "data path", + "main": "index.html", + "chromium-args": "--data-path='./dataPath/data-cash/'" +} diff --git a/tests/automation/datapath/datapath/index.html b/tests/automation/datapath/datapath/index.html new file mode 100644 index 0000000000..3f67831e2a --- /dev/null +++ b/tests/automation/datapath/datapath/index.html @@ -0,0 +1,19 @@ + + +testing data path + + +

          Testing data path

          +the data path should be set as + + + diff --git a/tests/automation/datapath/datapath/package.json b/tests/automation/datapath/datapath/package.json new file mode 100644 index 0000000000..7843ddcba3 --- /dev/null +++ b/tests/automation/datapath/datapath/package.json @@ -0,0 +1,5 @@ +{ + "name": "data path", + "main": "index.html", + "chromium-args": "--data-path='./dataPath/'" +} diff --git a/tests/automation/datapath/index.html b/tests/automation/datapath/index.html new file mode 100644 index 0000000000..2fbfa231bb --- /dev/null +++ b/tests/automation/datapath/index.html @@ -0,0 +1,15 @@ + + + + + Data path test + + +

          Data path test

          + + + + + + + diff --git a/tests/automation/datapath/mocha_test.js b/tests/automation/datapath/mocha_test.js new file mode 100644 index 0000000000..c367ff4abd --- /dev/null +++ b/tests/automation/datapath/mocha_test.js @@ -0,0 +1,97 @@ +var path = require('path'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + + +describe('data-path', function() { + + var server; + + before(function(done){ + this.timeout(0); + if (!fs.existsSync(path.join(curDir, 'data-cash'))) + fs.mkdirsSync(path.join(curDir, 'data-cash')); + if (!fs.existsSync(path.join(curDir, 'dataPath'))) + fs.mkdirsSync(path.join(curDir, 'dataPath')); + if (!fs.existsSync(path.join(curDir, 'dataPath', 'data-cash'))) + fs.mkdirsSync(path.join(curDir, 'dataPath', 'data-cash')); + if (!fs.existsSync(path.join(curDir, 'data-cash', 'dataPath'))) + fs.mkdirsSync(path.join(curDir, 'data-cash', 'dataPath')); + + server = createTCPServer(13013); + + done(); + }); + + after(function() { + setTimeout(function() { + server.close(); + fs.remove(path.join(curDir, 'data-cash')); + fs.remove(path.join(curDir, 'dataPath')); + fs.remove(path.join(curDir, 'dataPath', 'data-cash')); + fs.remove(path.join(curDir, 'data-cash', 'dataPath')); + }, 1000); + }); + + + it('setting datapath as ./data-cash/ should pass', + function(done) { + this.timeout(0); + var child = spawnChildProcess(path.join(curDir, 'datacash')); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + child.kill(); + server.removeAllListeners('connection'); + done(); + });}); + }); + + it('setting datapath as ./dataPath/ should pass', + function(done) { + this.timeout(0); + + var child = spawnChildProcess(path.join(curDir, 'datapath')); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + child.kill(); + server.removeAllListeners('connection'); + done(); + });}); + }); + + it('setting datapath as ./dataPath/data-cash/ should pass', + function(done) { + this.timeout(0); + var child = spawnChildProcess(path.join(curDir, 'datapath-cash')); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + child.kill(); + server.removeAllListeners('connection'); + done(); + });}); + + }); + + it('setting datapath as ./data-cash/dataPath/ should pass', + function(done) { + this.timeout(0); + + var child = spawnChildProcess(path.join(curDir, 'datacash-path')); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + child.kill(); + server.removeAllListeners('connection'); + done(); + });}); + + }); + + +}); + + + diff --git a/tests/automation/datapath/package.json b/tests/automation/datapath/package.json new file mode 100644 index 0000000000..6351f0b9fe --- /dev/null +++ b/tests/automation/datapath/package.json @@ -0,0 +1,4 @@ +{ + "name":"datapath_wrapper", + "main":"index.html" +} diff --git a/tests/automation/document_cookies/app/index.html b/tests/automation/document_cookies/app/index.html new file mode 100644 index 0000000000..3efd0d79e6 --- /dev/null +++ b/tests/automation/document_cookies/app/index.html @@ -0,0 +1,54 @@ + + + + + + + + diff --git a/tests/automation/document_cookies/app/package.json b/tests/automation/document_cookies/app/package.json new file mode 100644 index 0000000000..4a73bfc451 --- /dev/null +++ b/tests/automation/document_cookies/app/package.json @@ -0,0 +1,7 @@ +{ + "name": "nw", + "main": "app://whatever/index.html", + "window": { + "show": false + } +} diff --git a/tests/automation/document_cookies/file/index.html b/tests/automation/document_cookies/file/index.html new file mode 100644 index 0000000000..3efd0d79e6 --- /dev/null +++ b/tests/automation/document_cookies/file/index.html @@ -0,0 +1,54 @@ + + + + + + + + diff --git a/tests/automation/document_cookies/file/package.json b/tests/automation/document_cookies/file/package.json new file mode 100644 index 0000000000..18d312afa6 --- /dev/null +++ b/tests/automation/document_cookies/file/package.json @@ -0,0 +1,7 @@ +{ + "name": "nw", + "main": "index.html", + "window": { + "show": false + } +} diff --git a/tests/automation/document_cookies/index.html b/tests/automation/document_cookies/index.html new file mode 100644 index 0000000000..12d7d8c6c2 --- /dev/null +++ b/tests/automation/document_cookies/index.html @@ -0,0 +1,15 @@ + + + + + Document cookie test + + +

          Document cookie test

          + + + + + + + diff --git a/tests/automation/document_cookies/mocha_test.js b/tests/automation/document_cookies/mocha_test.js new file mode 100644 index 0000000000..7764443c0d --- /dev/null +++ b/tests/automation/document_cookies/mocha_test.js @@ -0,0 +1,95 @@ +var path = require('path'); +var assert = require('assert'); +var gui = require('nw.gui'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); +var results = new Array(); +describe('document.cookies', function() { + +var server; + + before(function(done) { + server = createTCPServer(13013); + results.push('dump'); // should be remove when the first test case is ready + done(); + }); + + after(function () { + server.close(); + }); + +/* + describe('http', function() { + before(function(done) { + this.timeout(0); + var url = "http://127.0.0.1:8123/document_cookies.html"; + var win = gui.Window.open(url); + + setTimeout(function() { + results.push(win.window.msg.textContent); + done(); + win.window.close(); + }, 1000); + }); + it('should be set', function() { + assert.equal(results[0], '123'); + }); + }); +*/ + + describe('file', function() { + before(function(done) { + this.timeout(0); + var child = spawnChildProcess(path.join(curDir, 'file')); + + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + setTimeout(function() { + results.push(data); + child.kill(); + done(); + }, 1000); + }); + }); + setTimeout(done, 10000); + }); + + after(function() { + server.removeAllListeners('connection'); + }); + + it ('should be set', function() { + assert.equal(results[1], '123'); + }); + }); + + describe('app', function() { + before(function(done) { + this.timeout(0); + var child = spawnChildProcess(path.join(curDir, 'app')); + + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + setTimeout(function() { + results.push(data); + child.kill(); + done(); + }, 1000); + }); + }); + + setTimeout(done, 10000); + }); + + after(function() { + server.removeAllListeners('connection'); + }); + + it ('should be set', function() { + assert.equal(results[2], '123'); + }); + }); + +}); diff --git a/tests/automation/document_cookies/package.json b/tests/automation/document_cookies/package.json new file mode 100644 index 0000000000..196d53596c --- /dev/null +++ b/tests/automation/document_cookies/package.json @@ -0,0 +1,4 @@ +{ + "name":"documentcookie_wrapper", + "main":"index.html" +} diff --git a/tests/automation/failures.md b/tests/automation/failures.md new file mode 100644 index 0000000000..71d49ff7d2 --- /dev/null +++ b/tests/automation/failures.md @@ -0,0 +1,41 @@ +Failed test cases +======================= + +* app + +* chromedriver2_server + +* chromium-args + +* console + +* cookies_api (bad bad bad) + +* document_cookies + +* node + +* node-remote + +* process + +* reload_application + +* save_devtools_settings + +* single_instance + +* source-maps + +* temp_dir + +* user-agent + +* user-agent-app + +* website + + + + + diff --git a/tests/automation/fast_open_and_close_devtools/index.html b/tests/automation/fast_open_and_close_devtools/index.html new file mode 100644 index 0000000000..7fac4bbbd3 --- /dev/null +++ b/tests/automation/fast_open_and_close_devtools/index.html @@ -0,0 +1,16 @@ + + + + + Fast open and close devtools test + + +

          Fast open and close devtools test

          + + + + + + + + diff --git a/tests/automation/fast_open_and_close_devtools/internal/index.html b/tests/automation/fast_open_and_close_devtools/internal/index.html new file mode 100644 index 0000000000..8a54574ce4 --- /dev/null +++ b/tests/automation/fast_open_and_close_devtools/internal/index.html @@ -0,0 +1,17 @@ + + + + + Test Case For Fast Open&Close Devtools Crashes + + + + + diff --git a/tests/automation/fast_open_and_close_devtools/internal/package.json b/tests/automation/fast_open_and_close_devtools/internal/package.json new file mode 100644 index 0000000000..fedeac13b7 --- /dev/null +++ b/tests/automation/fast_open_and_close_devtools/internal/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw", + "main": "index.html" +} diff --git a/tests/automation/fast_open_and_close_devtools/mocha_test.js b/tests/automation/fast_open_and_close_devtools/mocha_test.js new file mode 100644 index 0000000000..2b57063cea --- /dev/null +++ b/tests/automation/fast_open_and_close_devtools/mocha_test.js @@ -0,0 +1,21 @@ +var path = require('path'); +var assert = require('assert'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + +var result = false; +describe('Fast open&close devtools from #1391', function() { + before(function(done) { + this.timeout(0); + var app = spawnChildProcess(path.join(curDir, 'internal')); + app.on('exit', function(code) { + if (code != null) + result = true; + done(); + }); + }); + + it('should not crash', function() { + assert.equal(result, true); + }); +}); diff --git a/tests/automation/fast_open_and_close_devtools/package.json b/tests/automation/fast_open_and_close_devtools/package.json new file mode 100644 index 0000000000..09772c3904 --- /dev/null +++ b/tests/automation/fast_open_and_close_devtools/package.json @@ -0,0 +1,4 @@ +{ + "name":"fast_open_and_close_devtools_wrapper", + "main":"index.html" +} diff --git a/tests/automation/fd-limit/.gitignore b/tests/automation/fd-limit/.gitignore new file mode 100644 index 0000000000..ca38f71d85 --- /dev/null +++ b/tests/automation/fd-limit/.gitignore @@ -0,0 +1,2 @@ +testfiles +fd-limit.xml diff --git a/tests/automation/fd-limit/index.html b/tests/automation/fd-limit/index.html new file mode 100644 index 0000000000..11eb04113e --- /dev/null +++ b/tests/automation/fd-limit/index.html @@ -0,0 +1,13 @@ + + + + + + fd-limit + + + + + + + diff --git a/tests/automation/fd-limit/mocha_test.js b/tests/automation/fd-limit/mocha_test.js new file mode 100644 index 0000000000..7ee726290f --- /dev/null +++ b/tests/automation/fd-limit/mocha_test.js @@ -0,0 +1,82 @@ +var assert = require('assert'); +var fs = require('fs'); +var path = require('path'); +var nw = require('./nw'); + +describe('fd-limit', function() { + + describe('file descriptor limit', function() { + + before(function(done) { + this.timeout(5000); + fs.mkdir('testfiles', function(err) { + if (err) throw err; + var n = 0; + (function next() { + fs.writeFile('testfiles/' + n + '.json', JSON.stringify({i: n}), function(err) { + if (err) throw err; + + if (++n === 2000) { + done(); + } else { + next(); + } + }); + }()); + }); + }); + + after(function(done) { + this.timeout(5000); + var n = 0; + (function next() { + fs.unlink('testfiles/' + n + '.json', function(err) { + if (err) throw err; + + if (++n === 2000) { + fs.rmdir('testfiles', function(err){ + if (err) throw err; + done(); + }); + } else { + next(); + } + }); + }()); + }); + + it('fetches 2k files via XHR should succeed with --file-descriptor-limit=8192', function(done) { + if (process.platform === 'win32') { + assert.equal(true, true); + done(); + } + + this.timeout(5000); + + nw.spawn(['test_app', '--file-descriptor-limit=8192'], function(err, data) { + if (err) throw err; + assert.equal(data.ok, true); + done(); + }); + + }); + + it('fetches 2k files via XHR should fail with --file-descriptor-limit=100', function(done) { + if (process.platform === 'win32') { + assert.equal(true, true); + done(); + } + + this.timeout(5000); + + nw.spawn(['test_app', '--file-descriptor-limit=100'], function(err, data) { + if (err) throw err; + assert.equal(data.ok, false); + done(); + }); + + }); + + }); + +}); diff --git a/tests/automation/fd-limit/nw.js b/tests/automation/fd-limit/nw.js new file mode 100644 index 0000000000..8b8650dab0 --- /dev/null +++ b/tests/automation/fd-limit/nw.js @@ -0,0 +1,40 @@ +var spawn = require('child_process').spawn; +var net = require('net'); + +exports.port = 13030; + +exports.spawn = function(args, cb) { + var shutdown = function (err, data) { + shutdown = function(){}; + server.close(function(){ + if (err) { + cb(err); + } else { + cb(null, JSON.parse(data)); + } + }); + }; + + var server = net.createServer(); + server.on('connection', function(con) { + con.on('data', function(data) { + shutdown(null, data); + }); + con.on('error', function(err){ + shutdown(err); + }); + con.on('close', function(){ + shutdown(new Error('not-receive')); + }); + }); + server.listen(exports.port); + + return spawn(process.execPath, args); + +}; + +exports.send = function(data) { + var client = net.connect(exports.port); + client.end(JSON.stringify(data)); + require('nw.gui').App.quit(); +}; diff --git a/tests/automation/fd-limit/package.json b/tests/automation/fd-limit/package.json new file mode 100644 index 0000000000..7a55b028dd --- /dev/null +++ b/tests/automation/fd-limit/package.json @@ -0,0 +1,5 @@ +{ + "name": "fd-limit", + "main": "index.html", + "single-instance": false +} diff --git a/tests/automation/fd-limit/test_app/index.html b/tests/automation/fd-limit/test_app/index.html new file mode 100644 index 0000000000..7e1056bd45 --- /dev/null +++ b/tests/automation/fd-limit/test_app/index.html @@ -0,0 +1,47 @@ + + + + + + fd-limit + + + + + diff --git a/tests/automation/fd-limit/test_app/package.json b/tests/automation/fd-limit/test_app/package.json new file mode 100644 index 0000000000..b3bf1b62e6 --- /dev/null +++ b/tests/automation/fd-limit/test_app/package.json @@ -0,0 +1,5 @@ +{ + "name": "fd-limit-test-app", + "main": "index.html", + "single-instance": false +} diff --git a/tests/automation/filevalue/index.html b/tests/automation/filevalue/index.html new file mode 100644 index 0000000000..e6c61c7b2a --- /dev/null +++ b/tests/automation/filevalue/index.html @@ -0,0 +1,16 @@ + + + + + file value test + + +

          File value test

          + + + + + + + + diff --git a/tests/automation/filevalue/internal/index.html b/tests/automation/filevalue/internal/index.html new file mode 100644 index 0000000000..f13e4c01a2 --- /dev/null +++ b/tests/automation/filevalue/internal/index.html @@ -0,0 +1,36 @@ + + + + + test setting file input value + + + + + + + diff --git a/tests/automation/filevalue/internal/package.json b/tests/automation/filevalue/internal/package.json new file mode 100644 index 0000000000..602dd5d3e4 --- /dev/null +++ b/tests/automation/filevalue/internal/package.json @@ -0,0 +1,4 @@ +{ + "name":"nw-file-input-value", + "main":"index.html" +} \ No newline at end of file diff --git a/tests/automation/filevalue/internal/testfile b/tests/automation/filevalue/internal/testfile new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/automation/filevalue/mocha_test.js b/tests/automation/filevalue/mocha_test.js new file mode 100644 index 0000000000..9994fa8aed --- /dev/null +++ b/tests/automation/filevalue/mocha_test.js @@ -0,0 +1,43 @@ + +var path = require('path'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + +describe('file input',function(){ + describe('set value',function(){ + + var server; + + before(function(done) { + server = createTCPServer(13013); + done(); + }); + + after(function () { + server.close(); + }); + + it('the value should be set without exception',function(done){ + this.timeout(0); + var child = spawnChildProcess(path.join(curDir, 'internal')); + + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + setTimeout(function() { + child.kill(); + if (data == 'mytestfile') + done(); + else + done('the value of the file input is not set correctly'); + }, 2000); + }); + }); + + }); + + }); + +}); + + diff --git a/tests/automation/filevalue/package.json b/tests/automation/filevalue/package.json new file mode 100644 index 0000000000..88c0905e3c --- /dev/null +++ b/tests/automation/filevalue/package.json @@ -0,0 +1,4 @@ +{ + "name":"filevalue_wrapper", + "main":"index.html" +} diff --git a/tests/automation/globals.js b/tests/automation/globals.js new file mode 100644 index 0000000000..24ac6fbad1 --- /dev/null +++ b/tests/automation/globals.js @@ -0,0 +1,5 @@ +var fs = require('fs'); + +exports.tests_dir = fs.realpathSync('.'); + + diff --git a/tests/automation/inject-js/index.html b/tests/automation/inject-js/index.html new file mode 100644 index 0000000000..d2395f274d --- /dev/null +++ b/tests/automation/inject-js/index.html @@ -0,0 +1,16 @@ + + + + + Inject js test + + +

          Inject js test

          + + + + + + + + diff --git a/tests/automation/inject-js/internal/end.js b/tests/automation/inject-js/internal/end.js new file mode 100644 index 0000000000..613f42bcdb --- /dev/null +++ b/tests/automation/inject-js/internal/end.js @@ -0,0 +1,2 @@ +var result = result ||[]; +result.push('inject-js-end'); \ No newline at end of file diff --git a/tests/automation/inject-js/internal/index.html b/tests/automation/inject-js/internal/index.html new file mode 100644 index 0000000000..354175ced9 --- /dev/null +++ b/tests/automation/inject-js/internal/index.html @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/tests/automation/inject-js/internal/package.json b/tests/automation/inject-js/internal/package.json new file mode 100644 index 0000000000..d36df79c46 --- /dev/null +++ b/tests/automation/inject-js/internal/package.json @@ -0,0 +1,6 @@ +{ + "name":"nw_1403514049", + "main":"index.html", + "inject-js-end":"./end.js", + "inject-js-start":"./start.js" +} \ No newline at end of file diff --git a/tests/automation/inject-js/internal/start.js b/tests/automation/inject-js/internal/start.js new file mode 100644 index 0000000000..5dd62d1045 --- /dev/null +++ b/tests/automation/inject-js/internal/start.js @@ -0,0 +1,6 @@ +var result = result ||[]; +result.push('inject-js-start'); + +window.onload = function(){ + result.push('onload') +}; \ No newline at end of file diff --git a/tests/automation/inject-js/mocha_test.js b/tests/automation/inject-js/mocha_test.js new file mode 100644 index 0000000000..025ea1377b --- /dev/null +++ b/tests/automation/inject-js/mocha_test.js @@ -0,0 +1,54 @@ +var path = require('path'); +var assert = require('assert'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + + +describe('inject-js',function(){ + + var server, child, result; + + before(function(done) { + this.timeout(0); + server = createTCPServer(13013); + child = spawnChildProcess(path.join(curDir, 'internal')); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + result = JSON.parse(data); + child.kill(); + done(); + }); + }); + + }); + + after(function () { + server.close(); + }); + + + it('result.length should equal 4',function(done){ + assert.notEqual(result,null); + assert.equal(result.length,4); + done(); + }); + it('inject-js-start should run first',function(done){ + assert.equal(result[0],'inject-js-start'); + done(); + }); + it('document script should run after inject-js-start',function(done){ + assert.equal(result[1],'script-start'); + done(); + }); + it('inject-js-end should before window.onload',function(done){ + assert.equal(result[2],'inject-js-end'); + done(); + }); + it('inject-js-start should run last',function(done){ + assert.equal(result[3],'onload'); + done(); + }); +}); + + diff --git a/tests/automation/inject-js/package.json b/tests/automation/inject-js/package.json new file mode 100644 index 0000000000..2d328d1b95 --- /dev/null +++ b/tests/automation/inject-js/package.json @@ -0,0 +1,4 @@ +{ + "name":"injectjs_wrapper", + "main":"index.html" +} diff --git a/tests/automation/loaded_event/index.html b/tests/automation/loaded_event/index.html new file mode 100644 index 0000000000..d31b76187a --- /dev/null +++ b/tests/automation/loaded_event/index.html @@ -0,0 +1,16 @@ + + + + + loaded_event test + + +

          Loaded event test

          + + + + + + + + diff --git a/tests/automation/loaded_event/internal/index.html b/tests/automation/loaded_event/internal/index.html new file mode 100644 index 0000000000..132bd7454e --- /dev/null +++ b/tests/automation/loaded_event/internal/index.html @@ -0,0 +1,44 @@ + + + + + + + + + + diff --git a/tests/automation/loaded_event/internal/package.json b/tests/automation/loaded_event/internal/package.json new file mode 100644 index 0000000000..7cd4843f99 --- /dev/null +++ b/tests/automation/loaded_event/internal/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw-demo", + "main": "index.html" +} diff --git a/tests/automation/loaded_event/internal/popup.html b/tests/automation/loaded_event/internal/popup.html new file mode 100644 index 0000000000..c032c597e7 --- /dev/null +++ b/tests/automation/loaded_event/internal/popup.html @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/tests/automation/loaded_event/mocha_test.js b/tests/automation/loaded_event/mocha_test.js new file mode 100644 index 0000000000..571fa4c71d --- /dev/null +++ b/tests/automation/loaded_event/mocha_test.js @@ -0,0 +1,47 @@ +var path = require('path'); +var assert = require('assert'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + +describe('loaded event', function() { + + var server, child, result = false; + + before(function(done) { + server = createTCPServer(13013); + done(); + }); + + after(function () { + server.close(); + }); + + it('loaded event can been fired', + function(done) { + this.timeout(0); + + child = spawnChildProcess(path.join(curDir, 'internal')); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + result = true; + child.kill(); + done(); + }); + }); + + setTimeout(function(){ + if (!result) { + child.close(); + //child.app.kill(); + //child.removeConnection(); + done('loaded evenet does not been fired'); + } + }, 3000); + //child.app.stderr.on('data', function(d){ console.log ('app' + d);}); + + }) + + +}) + diff --git a/tests/automation/loaded_event/package.json b/tests/automation/loaded_event/package.json new file mode 100644 index 0000000000..beccf58ec0 --- /dev/null +++ b/tests/automation/loaded_event/package.json @@ -0,0 +1,4 @@ +{ + "name":"loaded_event_wrapper", + "main":"index.html" +} diff --git a/tests/automation/menu/index.html b/tests/automation/menu/index.html new file mode 100644 index 0000000000..121999a48b --- /dev/null +++ b/tests/automation/menu/index.html @@ -0,0 +1,16 @@ + + + + + Menu test + + +

          Menu test

          + + + + + + + + diff --git a/tests/automation/menu/mocha_test.js b/tests/automation/menu/mocha_test.js new file mode 100644 index 0000000000..780846e5ad --- /dev/null +++ b/tests/automation/menu/mocha_test.js @@ -0,0 +1,83 @@ +var gui = require('nw.gui'); +var assert = require('assert'); + + +describe('Menu', function(){ + + describe('#append()', function(){ + it('should append a value', function(){ + var menu = new gui.Menu(); + menu.append(new gui.MenuItem({ label:'Item 1'})); + assert.equal(menu.items[0].label, 'Item 1'); + }) + + }) + + describe('#.length', function(){ + it('should return correct value', function(){ + var menu = new gui.Menu(); + menu.append(new gui.MenuItem({label : 'Item 1'})); + menu.append(new gui.MenuItem({label : 'Item 2'})); + assert.equal(menu.items.length, 2); + }) + }) + + describe('#insert()', function(){ + var menu = new gui.Menu(); + + it('should adject .length', function(){ + menu.append(new gui.MenuItem({label : 'Item 1'})); + menu.append(new gui.MenuItem({label : 'Item 2'})); + menu.insert(new gui.MenuItem({label : 'Item 0'}), 1); + assert.equal(menu.items.length, 3); + }) + + it('new value should be added in', function(){ + assert.equal(menu.items[1].label, 'Item 0'); + }) + + it('the origin value', function(){ + assert.equal(menu.items[2].label, 'Item 2'); + }) + }) + + describe('#removeAt()', function(){ + var menu = new gui.Menu(); + it('should adject .length', function(){ + menu.append(new gui.MenuItem({label : 'Item 0'})); + menu.append(new gui.MenuItem({label : 'Item 1'})); + menu.append(new gui.MenuItem({label : 'Item 2'})); + menu.removeAt(1); + assert.equal(menu.items.length, 2); + }); + + it('the next value', function(){ + assert.equal(menu.items[1].label, 'Item 2'); + }) + }) + + describe('#remove()', function(){ + var menu = new gui.Menu(); + menu.append(new gui.MenuItem({label : 'Item 0'})); + menu.append(new gui.MenuItem({label : 'Item 1'})); + var removedItem = new gui.MenuItem({label : 'Item Deleted'}); + menu.insert(removedItem, 1); + it('before delete', function(){ + assert.equal(menu.items.length, 3); + assert.equal(menu.items[1].label, 'Item Deleted'); + }) + + it('after delete', function(){ + menu.remove(removedItem); + assert.equal(menu.items.length, 2); + assert.equal(menu.items[1].label, 'Item 1'); + + }) + }) + + describe('#popup(), not implentment',function(){ + }) + +}) + + diff --git a/tests/automation/menu/package.json b/tests/automation/menu/package.json new file mode 100644 index 0000000000..899ddeef90 --- /dev/null +++ b/tests/automation/menu/package.json @@ -0,0 +1,4 @@ +{ + "name":"Menu_wrapper", + "main":"index.html" +} diff --git a/tests/automation/menu_item/icon1.png b/tests/automation/menu_item/icon1.png new file mode 100644 index 0000000000..010fc2dd2b Binary files /dev/null and b/tests/automation/menu_item/icon1.png differ diff --git a/tests/automation/menu_item/index.html b/tests/automation/menu_item/index.html new file mode 100644 index 0000000000..afa4493aa7 --- /dev/null +++ b/tests/automation/menu_item/index.html @@ -0,0 +1,16 @@ + + + + + Menu item test + + +

          Menu item test

          + + + + + + + + diff --git a/tests/automation/menu_item/mocha_test.js b/tests/automation/menu_item/mocha_test.js new file mode 100644 index 0000000000..fcac27477d --- /dev/null +++ b/tests/automation/menu_item/mocha_test.js @@ -0,0 +1,148 @@ +var gui = require('nw.gui'); +var assert = require('assert'); + +describe('MenuItem', function(){ + var menu; + beforeEach(function(){ + menu = new gui.Menu(); + }) + + describe('.type', function(){ + + it('should be separator', function(){ + menu.append(new gui.MenuItem({ label : 'Item 1', type : 'separator' })); + assert.equal(menu.items.length, 1); + assert.equal(menu.items[0].type, 'separator'); + }) + + it('should be normal', function(){ + menu.append(new gui.MenuItem({ label : 'Item 1' })); + assert.equal(menu.items.length, 1); + assert.equal(menu.items[0].type, 'normal'); + }) + + it('should be checkbox', function(){ + menu.append(new gui.MenuItem({ label : 'Item 1', type : 'checkbox' })); + assert.equal(menu.items.length, 1); + assert.equal(menu.items[0].type, 'checkbox'); + }) + + }) + + describe('.label', function(){ + it('set label', function(){ + menu.append(new gui.MenuItem({ label : 'Item 1'})); + assert.equal(menu.items.length, 1); + assert.equal(menu.items[0].label, 'Item 1'); + + }) + + it('after change',function(){ + menu.append(new gui.MenuItem({ label : 'Item 1'})); + assert.equal(menu.items.length, 1); + menu.items[0].label = 'Item Mama'; + assert.equal(menu.items[0].label, 'Item Mama'); + }) + + }) + + describe('.icon', function(){ + it('set menu icon', function(){ + menu.append(new gui.MenuItem({ label : 'Item icon' })); + assert.equal(menu.items.length, 1); + menu.items[0].icon = 'icon1.png'; + assert.equal(menu.items[0].icon, 'icon1.png'); + + }) + + it('clear menu icon', function(){ + menu.append(new gui.MenuItem({ label : 'Item icon' })); + menu.items[0].icon = 'icon1.png'; + menu.items[0].icon = ''; + assert.equal(menu.items[0].icon, ''); + }) + }) + + describe('.tooltip', function(){ + it('set tooltip', function(){ + menu.append(new gui.MenuItem({ label : 'Item 0', tooltip : 'tooltip' })); + assert.equal(menu.items[0].tooltip, 'tooltip'); + }) + + it('change tooltip', function(){ + menu.append(new gui.MenuItem({ label : 'Item 0', tooltip : 'tooltip' })); + menu.items[0].tooltip = ''; + assert.equal(menu.items[0].tooltip, ''); + }) + }) + + + describe('.checked', function(){ + it('set enabled', function(){ + menu.append(new gui.MenuItem({ type : 'checkbox', label : 'Item 0', checked : true })); + assert.equal(menu.items[0].checked, true); + }) + + it('change checked', function(){ + menu.append(new gui.MenuItem({ type : 'checkbox', label : 'Item 0', checked: true })); + menu.items[0].checked = false; + assert.equal(menu.items[0].checked, false); + + }) + }) + + + describe('.enabled', function(){ + it('set enabled', function(){ + menu.append(new gui.MenuItem({ type : 'checkbox', label : 'Item 0', enabled : true })); + assert.equal(menu.items[0].enabled, true); + }) + + it('change enabled', function(){ + menu.append(new gui.MenuItem({ type : 'checkbox', label : 'Item 0', enabled : true })); + menu.items[0].enabled = false; + assert.equal(menu.items[0].enabled, false); + + }) + }) + + describe('.submenu', function(){ + it('set submenu', function(){ + var submenu = new gui.Menu(); + submenu.append(new gui.MenuItem({ type: 'checkbox', label: 'Sub 1', checked: true, enabled: false })); + submenu.append(new gui.MenuItem({ type: 'checkbox', label: 'Sub 2', checked: true, enabled: false })); + submenu.append(new gui.MenuItem({ type: 'checkbox', label: 'Sub 3', checked: true, enabled: false })); + submenu.append(new gui.MenuItem({ type: 'checkbox', label: 'Sub 4', checked: true, enabled: false })); + menu.append(new gui.MenuItem({ label: 'I have submenu', submenu: submenu })); + + assert.equal(menu.items.length, 1); + assert.equal(menu.items[0].label, 'I have submenu'); + assert.equal(menu.items[0].submenu, submenu); + assert.equal(submenu.items.length, 4); + }) + }) + + describe('#click()', function(){ + + var menu_item = new gui.MenuItem({label : 'item 1'}); + + + it('before click',function(){ + assert.equal(menu_item.label, 'item 1'); + }) + + it('after click',function(done){ + menu_item.on('click', function(){ + menu_item.label = '2'; + assert.equal(menu_item.label, '2'); + done(); + }) + menu_item.emit('click'); + + }) + + + + }) + +}) diff --git a/tests/automation/menu_item/package.json b/tests/automation/menu_item/package.json new file mode 100644 index 0000000000..b4e9a364b2 --- /dev/null +++ b/tests/automation/menu_item/package.json @@ -0,0 +1,4 @@ +{ + "name":"Menu_item_wrapper", + "main":"index.html" +} diff --git a/tests/automation/mocha_test.js b/tests/automation/mocha_test.js new file mode 100644 index 0000000000..db7f86f779 --- /dev/null +++ b/tests/automation/mocha_test.js @@ -0,0 +1,193 @@ +var fs = require('fs'); +var path = require('path'); +var child_process = require('child_process'); +var colors = require('colors'); +//var toMarkdown = require('to-markdown').toMarkdown; + +var g_quiet = false; +var g_outputDir; +var g_outputLog; +var g_format; + +function print(str, err) { + if (!g_quiet) { + if (err) { + console.error(str); + } else { + console.log(str); + } + } + if (g_outputLog) { + fs.appendFile(g_outputLog, str + '\n'); + } +} + +function preTest() { + print(colors.green('===============')); + print(colors.green('Begin testing')); + print(colors.green('===============')); +} + +function postTest() { + print(colors.green('===============')); + print(colors.green('End testing')); + print(colors.green('===============')); + + if (g_format === 'markdown' && fs.existsSync(g_outputDir)) { + // convert xml to md for better reading + var subFiles = fs.readdirSync(g_outputDir); + for (var i = 0, len = subFiles.length; i < len; i++) { + var filePath = subFiles[i]; + if (path.extname(filePath) === '.xml') { + var conent = fs.readFileSync(path.join(g_outputDir, filePath)); + content = encodeURIComponent(conent); + var mdData = toMarkdown(content); + fs.writeFileSync(path.join(g_outputDir, path.basename(filePath)+'.md'), mdData); + } + } + } +} + +function performTests(config) { + + g_quiet = config.quiet; + + var curPath = fs.realpathSync('.'); + if (!fs.existsSync('output')) + fs.mkdirSync('output'); + g_outputDir = path.join(curPath, 'output', (new Date()).toUTCString()); + if (!fs.existsSync(g_outputDir)) + fs.mkdirSync(g_outputDir); + g_outputLog = path.join(g_outputDir, 'summary.log'); + print('The test results will output to : ' + colors.underline(g_outputDir) + '\n'); + + var subDirs = fs.readdirSync(curPath); + var testSuites = []; + var isExcluded = function(testsuite) { + for (var i = 0, len = config.exclude.length; i < len; i++) { + if (config.exclude[i] === testsuite) + return true; + } + return false; + }; + + if (config.single && config.single !== '' && fs.existsSync(path.join(curPath, config.single))) { + testSuites.push(config.single); + } else { + for (var i = 0, len = subDirs.length; i < len; i++) { + var subDir = subDirs[i]; + var status = fs.statSync(path.join(curPath, subDir)); + if (status && status.isDirectory() && !isExcluded(subDir)) { + testSuites.push(subDir); + } + } + } + + /* ============ running test cases ================ */ + if (testSuites.length === 0) { + print('No test cases are found in ' + curPath); + return; + } + preTest(); + g_format = config.format; + var arg_timeout = config.timeout ? ['--timeout', config.timeout] : []; + var arg_slow = config.slow ? ['--slow', config.slow] : []; + var argv = undefined; + var cmd = undefined; + var child = undefined; + var env = process.env; + var timerId = undefined; + var children = []; + + var timeout = parseInt(config.timeout); + if (Number.isNaN(timeout) || timeout <= 0) + timeout = 5000; + + env['out_dir'] = g_outputDir; + + var spawTestProcessByNW = function(testcase) { + cmd = 'nw'; + argv = [testcase]; + print('Running: ' + colors.underline(testcase)); + child = child_process.spawn(cmd, argv, {env: env}); + child.testcase = testcase; + return child; + }; + + var spawTestProcessByMocha = function(testcase) { + cmd = 'mocha'; + argv = [testcase + path.sep + 'mocha_test'].concat(['-R', 'xunit-file']) + .concat(arg_timeout).concat(arg_slow); + env['XUNIT_FILE'] = path.join(g_outputDir, testcase + '.xml'); + print('Running:' + colors.underline(testcase)); + child = child_process.spawn(cmd, argv, {env: env}); + child.testcase = testcase; + return child; + }; + + var curIdx = -1; + var curChildProcess = undefined; + var prefix; + +// for (var i =0, len = testSuites.length; i < len; i++ ) { +// console.log('Test suite ' + i + ':' + testSuites[i]); +// } + + var runNextTestByNW = function() { + curIdx += 1; + if (curIdx < testSuites.length) { + resetTimeout(curIdx); + curChildProcess = spawTestProcessByNW(testSuites[curIdx]); + children.push(curChildProcess); + curChildProcess.on('exit', function(code, signal) { + (function(p, c) { + prefix = (code === 0 ? colors.green('[Success]') : colors.red('[Failure]')); + print(prefix + ' : ' + colors.underline(p.testcase) + ' with exit code ' + c); + clearTimeout(timerId); + p.removeAllListeners('exit'); + p.kill('SIGKILL'); + runNextTestByNW(); + })(curChildProcess, code); + }); + } else { + if (timerId) { + clearTimeout(timerId); + timerId = undefined; + } + postTest(); + } + }; + + var resetTimeout = function(idx) { + if (timerId) + clearTimeout(timerId); + (function(idx){ + + timerId = setTimeout(function() { + + timerId = undefined; + if (idx || idx >= 0) { + var p = children[idx]; + print(colors.yellow('[Timeout]') + ' : ' + colors.underline(p.testcase)); + p.removeAllListeners('exit'); + p.kill('SIGKILL'); // 'SIGKILL', 'SIGTERM' + p = undefined; + runNextTestByNW(); + } + }, timeout); + + })(idx); + + }; + + runNextTestByNW(); + +} // end of performTests() + +var content = fs.readFileSync('./config.json'); +if (content) + performTests(JSON.parse(content)); +else + print('Failed to read configuration.', true); + + diff --git a/tests/automation/new-instance/index.html b/tests/automation/new-instance/index.html new file mode 100644 index 0000000000..a3fe74b68f --- /dev/null +++ b/tests/automation/new-instance/index.html @@ -0,0 +1,16 @@ + + + + + New Instance test + + +

          New Instance test

          + + + + + + + + diff --git a/tests/automation/new-instance/internal/index.html b/tests/automation/new-instance/internal/index.html new file mode 100644 index 0000000000..6ee8e7dec4 --- /dev/null +++ b/tests/automation/new-instance/internal/index.html @@ -0,0 +1,33 @@ + + + + +The pid of main page is: + +
          +The pid of new-instance page is: + + + + + + diff --git a/tests/automation/new-instance/internal/package.json b/tests/automation/new-instance/internal/package.json new file mode 100644 index 0000000000..7cd4843f99 --- /dev/null +++ b/tests/automation/new-instance/internal/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw-demo", + "main": "index.html" +} diff --git a/tests/automation/new-instance/internal/popup.html b/tests/automation/new-instance/internal/popup.html new file mode 100644 index 0000000000..b76e8af321 --- /dev/null +++ b/tests/automation/new-instance/internal/popup.html @@ -0,0 +1,21 @@ + + + + +The pid of page is: + + + + + + diff --git a/tests/automation/new-instance/mocha_test.js b/tests/automation/new-instance/mocha_test.js new file mode 100644 index 0000000000..efc6a1783f --- /dev/null +++ b/tests/automation/new-instance/mocha_test.js @@ -0,0 +1,51 @@ +var path = require('path'); +var assert = require('assert'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + +describe('new-instance', function() { + var server, child, result = false; + + before(function(done) { + server = createTCPServer(13013); + done(); + }); + + after(function () { + server.close(); + }); + + it('new window has different pid', function(done) { + this.timeout(0); + var result = false; + var times = 0; + var pid1, pid2; + + child = spawnChildProcess(path.join(curDir, 'internal')); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + if (times == 0) { + pid1 = data; + times += 1; + } else { + pid2 = data; + + if (pid1 != pid2) { + done(); + } else { + done('they are in the same process'); + } + child.kill(); + } + + }); + }); + + + }); + + +}); + + diff --git a/tests/automation/new-instance/package.json b/tests/automation/new-instance/package.json new file mode 100644 index 0000000000..da9799222d --- /dev/null +++ b/tests/automation/new-instance/package.json @@ -0,0 +1,4 @@ +{ + "name":"New_instance_wrapper", + "main":"index.html" +} diff --git a/tests/automation/node-main/index.html b/tests/automation/node-main/index.html new file mode 100644 index 0000000000..8b479f5b7d --- /dev/null +++ b/tests/automation/node-main/index.html @@ -0,0 +1,16 @@ + + + + + node main test + + +

          Node main test

          + + + + + + + + diff --git a/tests/automation/node-main/mocha_test.js b/tests/automation/node-main/mocha_test.js new file mode 100644 index 0000000000..befaedcf6e --- /dev/null +++ b/tests/automation/node-main/mocha_test.js @@ -0,0 +1,98 @@ +var path = require('path'); +var assert = require('assert'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + +describe('node-main', function() { + + var server; + + before(function(done) { + server = createTCPServer(13013); + done(); + }); + + after(function () { + server.close(); + }); + + describe('create http server in node-main', function() { + it('nw should not close by itself after show devtool', + function(done) { + this.timeout(0); + var result = false; + + var child = spawnChildProcess(path.join(curDir, '..', 'show_devtool_after_http_server_created_in_node_main')); + + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + result = JSON.parse(data); + child.kill(); + if (result.success) done(); + else done('error'); + result = true; + server.removeAllListeners('connection'); + }); + }); + + + setTimeout(function(){ + if (!result) { + done('nw close by itself') + } + }, 3000); + //child.app.stderr.on('data', function(d){ console.log ('app' + d);}); + + }); + }); + + + + describe('call require() in app', function() { + it('nw should can require modules', function(done){ + this.timeout(0); + var result = false; + var child = spawnChildProcess(path.join(curDir, '..', 'call_require_with_node-main_set')); + + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + result = JSON.parse(data); + child.kill(); + if (result.ok) done(); + else done('error:'+result.error); + result = true; + server.removeAllListeners('connection'); + }); + }); + + }); + }); + + describe('reference node-main module',function(){ + it('nw should be able to reference node-main module',function(done){ + this.timeout(0); + + var result = false; + var child = spawnChildProcess(path.join(curDir, '..', 'reference-node-main')); + + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + result = JSON.parse(data); + child.kill(); + assert.equal(result,true); + done(); + server.removeAllListeners('connection'); + }); + }); + + }); + }); + + + +}); + + diff --git a/tests/automation/node-main/package.json b/tests/automation/node-main/package.json new file mode 100644 index 0000000000..918bb25bc8 --- /dev/null +++ b/tests/automation/node-main/package.json @@ -0,0 +1,4 @@ +{ + "name":"Node_main_wrapper", + "main":"index.html" +} diff --git a/tests/automation/node-remote/index.html b/tests/automation/node-remote/index.html new file mode 100644 index 0000000000..4ec84c0284 --- /dev/null +++ b/tests/automation/node-remote/index.html @@ -0,0 +1,93 @@ + + + + + + + +
          + Can remote page + http://127.0.0.1:8123/node_remote_test.html + call Node: + +
          + +
          + Can remote page + http://127.0.0.1:8124/node_remote_test.html + call Node: + +
          + + + diff --git a/tests/automation/node-remote/mocha_test.js b/tests/automation/node-remote/mocha_test.js new file mode 100644 index 0000000000..737c129899 --- /dev/null +++ b/tests/automation/node-remote/mocha_test.js @@ -0,0 +1,70 @@ +var spawn = require('child_process').spawn; +var path = require('path'); +var net = require('net'); +var server = global.server; +var cb; + +describe('node-remote', function() { + describe('enable remote site http://127.0.0.1:8123/', function() { + var app; + var exec_argv; + var socket; + before(function(done) { + //this.timeout(0); + exec_argv = [path.join(global.tests_dir, 'node-remote'), + '--port', + global.port]; + + if (global.auto) exec_argv.push('--auto'); + + server.on('connection', cb = function(s){ + socket = s; + s.setEncoding('utf8'); + done(); + socket.on('error', function(e) { + console.log(e); + }); + }); + app = spawn(process.execPath, exec_argv); + }) + + after(function(done) { + this.timeout(0); + app.kill(); + server.removeListener('connection', cb); + done(); + }) + + afterEach(function(){ + socket.removeAllListeners('data'); + }) + + it('http://127.0.0.1:8123/node_remote_test.html should be able call Node', + function(done) { + this.timeout(0); + socket.on('data', function(data) { + if (data == 'ok'){ + done(); + } else { + done(data); + } + + }); + socket.write('8123'); + }) + + it('http://127.0.0.1:8124/node_remote_test.html should not be able call Node', + function(done) { + this.timeout(0); + socket.on('data', function(data) { + if (data == 'ok'){ + done('should not call node'); + } else { + done(); + } + + }); + socket.write('8124'); + }) + }) +}) diff --git a/tests/automation/node-remote/package.json b/tests/automation/node-remote/package.json new file mode 100644 index 0000000000..e79fa87ac3 --- /dev/null +++ b/tests/automation/node-remote/package.json @@ -0,0 +1,8 @@ +{ + "name": "nw-demo", + "main": "index.html", + "user-agent": "%name-a-b", + "node-remote": "127.0.0.1:8123", + "version": "1.0", + "window": { "show" : false } +} diff --git a/tests/automation/node/index.html b/tests/automation/node/index.html new file mode 100644 index 0000000000..a454c5e92f --- /dev/null +++ b/tests/automation/node/index.html @@ -0,0 +1,16 @@ + + + + + node test + + +

          Node test

          + + + + + + + + diff --git a/tests/automation/node/message_channel_test.js b/tests/automation/node/message_channel_test.js new file mode 100644 index 0000000000..2aac0a92a1 --- /dev/null +++ b/tests/automation/node/message_channel_test.js @@ -0,0 +1,7 @@ +exports.run = function(done) { + var mc = new window.MessageChannel() + mc.port1.onmessage = function(m) { + done(); + } + mc.port2.postMessage("HELLO"); +} diff --git a/tests/automation/node/mocha_test.js b/tests/automation/node/mocha_test.js new file mode 100644 index 0000000000..f853a5218f --- /dev/null +++ b/tests/automation/node/mocha_test.js @@ -0,0 +1,258 @@ +var path = require('path'); +var assert = require('assert'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + + +/// 1 +describe('require', function() { + describe('type', function() { + it('is function', function() { + assert.equal(typeof global.require, 'function'); + }); + }); + + describe('members', function() { + it('have resolve', function() { + assert.equal(typeof global.require.resolve, 'function'); + }); + + it('have main', function() { + assert.equal(typeof global.require.main, 'object'); + }); + + it('have extensions', function() { + assert.equal(typeof global.require.extensions, 'object'); + }); + + it('have registerExtension', function() { + assert.equal(typeof global.require.registerExtension, 'function'); + }); + + it('have cache', function() { + assert.equal(typeof global.require.cache, 'object'); + }); + }); +}); + +////////////////// 2 +/* +describe('node', function() { + + var mocha_callback = null; + before(function() { + mocha_callback = process.listeners('uncaughtException')[1]; + process.removeListener('uncaughtException', mocha_callback); + }); + after(function(){ + process.on('uncaughtException',mocha_callback); + }) + + describe('process', function() { + it('uncaughtException should have a default listener', function() { + assert.equal(process.listeners('uncaughtException').length, 1); + }); + + it('throwing uncaughtException should not show error page when having listener', function(done) { + var empty = function(e) { + process.removeListener('uncaughtException', empty); + done(); + }; + process.on('uncaughtException', empty); + process.nextTick(function() { + throw new String('If you see this then uncaughtException test failed'); + }); + }); + }); +}); +*/ +////////////// 3 + +describe('module', function() { + describe('javascript modules', function() { + var path = require('path'); + + it('require by path', function() { + var module1_path = path.join(curDir, 'module1.js'); + assert.equal(require(module1_path).test(), 'module1'); + }); + + it('simple file module', function() { + var module3_path = path.join(curDir, 'node_modules', 'module3'); + assert.equal(require(module3_path).test(), 'module3'); + }); + + it('space in path', function() { + var module4_path = path.join(curDir, 'node_modules', 'module4 space'); + assert.equal(require(module4_path).test(), 'module4'); + }); + }); + + describe('built-in modules', function() { + var fs = require('fs'); + var http = require('http'); + var path = require('path'); + var exec = require('child_process').exec; + + it('execute child process', function(done) { + var child = exec('echo echo back', function (error, stdout, stderr) { + assert.equal(error, null); + assert.equal(stdout, process.platform == 'win32' ? 'echo back\r\n' : 'echo back\n'); + assert.equal(stderr, ''); + done(); + }); + }); + + it('Buffer', function() { + assert.equal(String(Buffer('test')), 'test'); + }); +/* + it('http server and client', function(done) { + var server = http.createServer(function (req, res) { + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end('hello'); + }).listen(10086); + process.nextTick(function() { + var count = 0; + for (var i = 0; i < 5; ++i) { + http.get("http://127.0.0.1:10086", function(res) { + var data = ''; + res.on('end', function(e) { + if (data == 'hello') + ++count; + }).on('data', function(chunk) { + data += chunk; + }); + }).on('error', function() { + assert.equal(true, false); + }); + } + setTimeout(function() { + assert.equal(count, 5); + server.close(); + done(); + }, 50); + }); + }); +*/ + }); + +/* + describe('native modules (long-to-run)', function() { + it('bignum should work on posix environment', function() { + if (process.platform != 'win32') { + var bignum = require('./node_modules/bignum'); + assert.equal(bignum('782910138827292261791972728324982').sub('182373273283402171237474774728373').div(8), '75067108192986261319312244199576'); + } + }); + + it('native modules without handle scope', function() { + require('./node_modules/nw_test_loop_without_handle'); + }); + + it('native modules should work', function() { + var nativeModules = new Array("dtrace-provider", "ref", "lame"); + for (var i = 0; i < nativeModules.length; i++) + assert.equal((typeof require(nativeModules[i])), "object"); + }); + }); +*/ + +}); + +////////////////////// 4 + +describe('performance', function() { + describe('timers', function() { + it('setTimeout in webkit and node.js should be accurate', function(done) { + var flag1, flag2; + global.setTimeout(function() { flag1 = true; }, 10); + window.setTimeout(function() { flag2 = true; }, 10); + + setTimeout(function() { + assert.equal(flag1, true); + assert.equal(flag2, true); + done(); + }, 50); + }); + + it('setInterval int webkit and node.js should be accurate', function(done) { + var count1 = 0; + var count2 = 0;; + global.setInterval(function() { ++count1; }, 10); + window.setInterval(function() { ++count2; }, 10); + + setTimeout(function() { + assert.equal(count1 > 3, true); + assert.equal(count2 > 3, true); + done(); + }, 50); + }); + }); + + + describe('IO', function() { + var fs = require('fs'); + var path = require('path'); + var exec = require('child_process').exec; + var tar = require('tar'); + var result = false; + + it('file reading should be quick even when we have a child process running', function(done) { + this.timeout(0); + var mochaPath = path.join(curDir, 'mocha_test.js'); + fs.readFile(mochaPath, 'utf8', function(err, data) { + result = true; + done(); + }); + + setTimeout(function() { + if (!result) done('timeout'); + }, 1000); + }); + + it('untar a file should be quick', function(done) { + this.timeout(100); + var testPath = path.join(curDir, 'test.tar'); + fs.createReadStream(testPath) + .pipe(tar.Extract({ path: 'tmp' })) + .on('error', function (er) { + assert.equal(false, true); + }) + .on('end', function () { + var content = fs.readFileSync('tmp/text', 'utf8'); + assert.equal(content, 'I love luyao\n'); // who is luyao ? + fs.unlinkSync('tmp/text'); + fs.rmdirSync('tmp'); + done(); + }); + }); + + }); + + +}); + +///////////////////////////// 5 + +describe('browser', function() { + describe('security', function() { + it('cross domain call in file:// should succeed', function(done) { + window.$.get('http://baidu.com', function(data, status) { + assert.equal(status, 'success'); + assert.notEqual(data, null); + done(); + }); + }); + }); + + describe('render crashes', function() { + it('MessageChannel should not crash', function(done) { + var testPath = path.join(curDir, 'message_channel_test'); + var test = require(testPath); + test.run(done); // force to run in node context. + }); + }); +}); + + diff --git a/tests/automation/node/module1.js b/tests/automation/node/module1.js new file mode 100644 index 0000000000..fe1099f65c --- /dev/null +++ b/tests/automation/node/module1.js @@ -0,0 +1,3 @@ +exports.test = function() { + return "module1"; +} diff --git a/tests/automation/node/package.json b/tests/automation/node/package.json new file mode 100644 index 0000000000..9244205ca5 --- /dev/null +++ b/tests/automation/node/package.json @@ -0,0 +1,4 @@ +{ + "name":"Node_wrapper", + "main":"index.html" +} diff --git a/tests/automation/node/test.tar b/tests/automation/node/test.tar new file mode 100644 index 0000000000..e087c9cff2 Binary files /dev/null and b/tests/automation/node/test.tar differ diff --git a/tests/automation/nw-in-mem/index.html b/tests/automation/nw-in-mem/index.html new file mode 100644 index 0000000000..a894d0e6c6 --- /dev/null +++ b/tests/automation/nw-in-mem/index.html @@ -0,0 +1,16 @@ + + + + + nw_in_mem test + + +

          nw in men test

          + + + + + + + + diff --git a/tests/automation/nw-in-mem/mocha_test.js b/tests/automation/nw-in-mem/mocha_test.js new file mode 100644 index 0000000000..91c87bd3ef --- /dev/null +++ b/tests/automation/nw-in-mem/mocha_test.js @@ -0,0 +1,42 @@ +var os = require('os'); +var path = require('path'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + +var exec = require('child_process').exec; +var spawn = require('child_process').spawn; + +describe('nw in memory after quiting',function(){ + + it('nw.exe should not be in memory after gui.App.quit is called on windows',function(done){ + this.timeout(0); + if(os.platform() == "win32") { + var app = spawn(process.execPath, [path.join(curDir,'package')]) + app.on('close',function(){ + var nwname = "nw.exe"; + var nwcount = 0; + setTimeout(function() { + exec("tasklist", function(err, stdout, stderr) { + if(err){ return console.log(err); } + stdout.split('\n').filter(function(line){ + var p=line.trim().split(/\s+/),pname=p[0]; + if(pname.toLowerCase().indexOf(nwname)>=0) + nwcount++; + }); + + if(nwcount == 2) + done(); + else if(nwcount > 2) + done('nw.exe is still in memory after the quit of app'); + }) + }, 500); + }); + } + else + done(); + }); + + +}); + + diff --git a/tests/automation/nw-in-mem/package.json b/tests/automation/nw-in-mem/package.json new file mode 100644 index 0000000000..c0251d7820 --- /dev/null +++ b/tests/automation/nw-in-mem/package.json @@ -0,0 +1,4 @@ +{ + "name":"nw_in_mem_wrapper", + "main":"index.html" +} diff --git a/tests/automation/nw-in-mem/package/c2.png b/tests/automation/nw-in-mem/package/c2.png new file mode 100644 index 0000000000..ba48f98d2c Binary files /dev/null and b/tests/automation/nw-in-mem/package/c2.png differ diff --git a/tests/automation/nw-in-mem/package/c2runtime.js b/tests/automation/nw-in-mem/package/c2runtime.js new file mode 100644 index 0000000000..1ffdea04c2 --- /dev/null +++ b/tests/automation/nw-in-mem/package/c2runtime.js @@ -0,0 +1,2 @@ + +var cr={plugins_:{},behaviors:{}};"function"!==typeof Object.getPrototypeOf&&(Object.getPrototypeOf="object"===typeof"test".__proto__?function(a){return a.__proto__}:function(a){return a.constructor.prototype});(function(){function a(a,p){this.x=a;this.y=p;cr.seal(this)}function b(a,p,g,u){this.set(a,p,g,u);cr.seal(this)}function d(){this.bly=this.blx=this.bry=this.brx=this.try_=this.trx=this.tly=this.tlx=0;cr.seal(this)}function e(){this.items={};this.item_count=0;this.values_cache=[];this.cache_valid=!0;cr.seal(this)}function f(){this.sum=this.t=this.y=this.c=0;cr.seal(this)}function h(a){this.pts_cache=[];this.set_pts(a);cr.seal(this)}cr.logexport=function(a){window.console&&window.console.log&&window.console.log(a)};cr.seal=function(a){return a};cr.freeze=function(a){return a};cr.is_undefined=function(a){return"undefined"===typeof a};cr.is_number=function(a){return"number"===typeof a};cr.is_string=function(a){return"string"===typeof a};cr.isPOT=function(a){return 0a?-a:a};cr.max=function(a,p){return a>p?a:p};cr.min=function(a,p){return acr.max(c,s)||cr.max(p,u)cr.max(k,m))return!1;var f=c-a+s-g,b=k-p+m-u;a=g-a;p=u-p;c=s-c;k=m-k;m=cr.abs(p*c-k*a);a=a*b-p*f;return cr.abs(c*b-k*f)<=m&&cr.abs(a)<=m};b.prototype.set=function(a,p,g,u){this.left=a;this.top=p;this.right=g;this.bottom=u};b.prototype.width=function(){return this.right-this.left};b.prototype.height=function(){return this.bottom-this.top};b.prototype.offset=function(a,p){this.left+=a;this.top+=p;this.right+=a;this.bottom+=p;return this};b.prototype.intersects_rect=function(a){return!(a.rightthis.right||a.top>this.bottom)};b.prototype.contains_pt=function(a,p){return a>=this.left&&a<=this.right&&p>=this.top&&p<=this.bottom};cr.rect=b;d.prototype.set_from_rect=function(a){this.tlx=a.left;this.tly=a.top;this.trx=a.right;this.try_=a.top;this.brx=a.right;this.bry=a.bottom;this.blx=a.left;this.bly=a.bottom};d.prototype.set_from_rotated_rect=function(a,p){if(0===p)this.set_from_rect(a);else{var g=Math.sin(p),u=Math.cos(p),c=a.left*g,k=a.top*g,s=a.right*g,g=a.bottom*g,m=a.left*u,f=a.top*u,b=a.right*u,u=a.bottom*u;this.tlx=m-k;this.tly=f+c;this.trx=b-k;this.try_=f+s;this.brx=b-g;this.bry=u+s;this.blx=m-g;this.bly=u+c}};d.prototype.offset=function(a,p){this.tlx+=a;this.tly+=p;this.trx+=a;this.try_+=p;this.brx+=a;this.bry+=p;this.blx+=a;this.bly+=p;return this};d.prototype.bounding_box=function(a){a.left=cr.min(cr.min(this.tlx,this.trx),cr.min(this.brx,this.blx));a.top=cr.min(cr.min(this.tly,this.try_),cr.min(this.bry,this.bly));a.right=cr.max(cr.max(this.tlx,this.trx),cr.max(this.brx,this.blx));a.bottom=cr.max(cr.max(this.tly,this.try_),cr.max(this.bry,this.bly))};d.prototype.contains_pt=function(a,p){var g=this.trx-this.tlx,u=this.try_-this.tly,c=this.brx-this.tlx,k=this.bry-this.tly,s=a-this.tlx,m=p-this.tly,f=g*g+u*u,b=g*c+u*k,u=g*s+u*m,l=c*c+k*k,d=c*s+k*m,e=1/(f*l-b*b),g=(l*u-b*d)*e,f=(f*d-b*u)*e;if(0<=g&&0g+f)return!0;g=this.blx-this.tlx;u=this.bly-this.tly;f=g*g+u*u;b=g*c+u*k;u=g*s+u*m;e=1/(f*l-b*b);g=(l*u-b*d)*e;f=(f*d-b*u)*e;return 0<=g&&0g+f};d.prototype.at=function(a,p){switch(a){case 0:return p?this.tlx:this.tly;case 1:return p?this.trx:this.try_;case 2:return p?this.brx:this.bry;case 3:return p?this.blx:this.bly;case 4:return p?this.tlx:this.tly;default:return p?this.tlx:this.tly}};d.prototype.midX=function(){return(this.tlx+this.trx+this.brx+this.blx)/4};d.prototype.midY=function(){return(this.tly+this.try_+this.bry+this.bly)/4};d.prototype.intersects_segment=function(a,p,g,u){if(this.contains_pt(a,p)||this.contains_pt(g,u))return!0;var c,k,s,m,f;for(f=0;4>f;f++)if(c=this.at(f,!0),k=this.at(f,!1),s=this.at(f+1,!0),m=this.at(f+1,!1),cr.segments_intersect(a,p,g,u,c,k,s,m))return!0;return!1};d.prototype.intersects_quad=function(a){var p=a.midX(),g=a.midY();if(this.contains_pt(p,g))return!0;p=this.midX();g=this.midY();if(a.contains_pt(p,g))return!0;var u,c,k,s,m,f,b,l;for(b=0;4>b;b++)for(l=0;4>l;l++)if(p=this.at(b,!0),g=this.at(b,!1),u=this.at(b+1,!0),c=this.at(b+1,!1),k=a.at(l,!0),s=a.at(l,!1),m=a.at(l+1,!0),f=a.at(l+1,!1),cr.segments_intersect(p,g,u,c,k,s,m,f))return!0;return!1};cr.quad=d;cr.RGB=function(a,p,g){return Math.max(Math.min(a,255),0)|Math.max(Math.min(p,255),0)<<8|Math.max(Math.min(g,255),0)<<16};cr.GetRValue=function(a){return a&255};cr.GetGValue=function(a){return(a&65280)>>8};cr.GetBValue=function(a){return(a&16711680)>>16};cr.shallowCopy=function(a,p,g){for(var u in p)p.hasOwnProperty(u)&&(a[u]=p[u]);return a};cr.arrayRemove=function(a,p){var g,u;p=cr.floor(p);if(!(0>p||p>=a.length))if(0===p)a.shift();else if(p===a.length-1)a.pop();else{g=p;for(u=a.length-1;gg?g:a};cr.to_radians=function(a){return a/(180/cr.PI)};cr.to_degrees=function(a){return a*(180/cr.PI)};cr.clamp_angle_degrees=function(a){a%=360;0>a&&(a+=360);return a};cr.clamp_angle=function(a){a%=2*cr.PI;0>a&&(a+=2*cr.PI);return a};cr.to_clamped_degrees=function(a){return cr.clamp_angle_degrees(cr.to_degrees(a))};cr.to_clamped_radians=function(a){return cr.clamp_angle(cr.to_radians(a))};cr.angleTo=function(a,p,g,u){return Math.atan2(u-p,g-a)};cr.angleDiff=function(a,p){if(a===p)return 0;var g=Math.sin(a),u=Math.cos(a),c=Math.sin(p),k=Math.cos(p),g=g*c+u*k;return 1<=g?0:-1>=g?cr.PI:Math.acos(g)};cr.angleRotate=function(a,p,g){var u=Math.sin(a),c=Math.cos(a),k=Math.sin(p),s=Math.cos(p);return Math.acos(u*k+c*s)>g?0=u*c-g*k};cr.rotatePtAround=function(a,p,g,u,c,k){if(0===g)return k?a:p;var s=Math.sin(g);g=Math.cos(g);a-=u;p-=c;var m=a*s;a=a*g-p*s;p=p*g+m;return k?a+u:p+c};cr.distanceTo=function(a,p,g,u){a=g-a;p=u-p;return Math.sqrt(a*a+p*p)};cr.xor=function(a,p){return!a!==!p};cr.lerp=function(a,p,g){return a+(p-a)*g};cr.hasAnyOwnProperty=function(a){for(var p in a)if(a.hasOwnProperty(p))return!0;return!1};cr.wipe=function(a){for(var p in a)a.hasOwnProperty(p)&&delete a[p]};var k=+new Date;cr.performance_now=function(){if("undefined"!==typeof window.performance){var a=window.performance;if("undefined"!==typeof a.now)return a.now();if("undefined"!==typeof a.webkitNow)return a.webkitNow();if("undefined"!==typeof a.msNow)return a.msNow()}return Date.now()-k};e.prototype.contains=function(a){return this.items.hasOwnProperty(a.toString())};e.prototype.add=function(a){var p=a.toString();this.items.hasOwnProperty(p)||(this.items[p]=a,this.item_count++,this.cache_valid=!1);return this};e.prototype.remove=function(a){a=a.toString();this.items.hasOwnProperty(a)&&(delete this.items[a],this.item_count--,this.cache_valid=!1);return this};e.prototype.clear=function(){cr.wipe(this.items);this.item_count=0;this.values_cache.length=0;this.cache_valid=!0;return this};e.prototype.isEmpty=function(){return 0===this.item_count};e.prototype.count=function(){return this.item_count};e.prototype.update_cache=function(){if(!this.cache_valid){this.values_cache.length=this.item_count;var a,p=0;for(a in this.items)this.items.hasOwnProperty(a)&&(this.values_cache[p++]=this.items[a]);this.cache_valid=!0}};e.prototype.values=function(){this.update_cache();return this.values_cache.slice(0)};e.prototype.valuesRef=function(){this.update_cache();return this.values_cache};cr.ObjectSet=e;f.prototype.add=function(a){this.y=a-this.c;this.t=this.sum+this.y;this.c=this.t-this.sum-this.y;this.sum=this.t};f.prototype.reset=function(){this.sum=this.t=this.y=this.c=0};cr.KahanAdder=f;cr.regexp_escape=function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&")};h.prototype.set_pts=function(a){this.pts_array=a;this.pts_count=a.length/2;this.pts_cache.length=a.length;this.cache_height=this.cache_width=-1;this.cache_angle=0};h.prototype.is_empty=function(){return!this.pts_array.length};h.prototype.set_from_quad=function(a,p,g,u,c){this.pts_cache.length=8;this.pts_count=4;var k=this.pts_cache;k[0]=a.tlx-p;k[1]=a.tly-g;k[2]=a.trx-p;k[3]=a.try_-g;k[4]=a.brx-p;k[5]=a.bry-g;k[6]=a.blx-p;k[7]=a.bly-g;this.cache_width=u;this.cache_height=c};h.prototype.set_from_poly=function(a){this.pts_count=a.pts_count;cr.shallowAssignArray(this.pts_cache,a.pts_cache)};h.prototype.cache_poly=function(a,p,g){if(this.cache_width!==a||this.cache_height!==p||this.cache_angle!==g){this.cache_width=a;this.cache_height=p;this.cache_angle=g;var u,c,k,s=0,m=1,f=this.pts_array,b=this.pts_cache;0!==g&&(s=Math.sin(g),m=Math.cos(g));g=0;for(u=this.pts_count;gf&&(f=c),kl&&(l=k);c=m-110;var b=b-101,f=f+131,l=l+120,d,e,r=0,h=0;for(u=0;u=a||11<=a?"source-over":c[a-1]};cr.setGLBlend=function(a,p,g){if(g)switch(a.srcBlend=g.ONE,a.destBlend=g.ONE_MINUS_SRC_ALPHA,p){case 1:a.srcBlend=g.ONE;a.destBlend=g.ONE;break;case 3:a.srcBlend=g.ONE;a.destBlend=g.ZERO;break;case 4:a.srcBlend=g.ONE_MINUS_DST_ALPHA;a.destBlend=g.ONE;break;case 5:a.srcBlend=g.DST_ALPHA;a.destBlend=g.ZERO;break;case 6:a.srcBlend=g.ZERO;a.destBlend=g.SRC_ALPHA;break;case 7:a.srcBlend=g.ONE_MINUS_DST_ALPHA;a.destBlend=g.ZERO;break;case 8:a.srcBlend=g.ZERO;a.destBlend=g.ONE_MINUS_SRC_ALPHA;break;case 9:a.srcBlend=g.DST_ALPHA;a.destBlend=g.ONE_MINUS_SRC_ALPHA;break;case 10:a.srcBlend=g.ONE_MINUS_DST_ALPHA,a.destBlend=g.SRC_ALPHA}};cr.round6dp=function(a){return Math.round(1E6*a)/1E6};var r={usage:"search",sensitivity:"accent"},n=!!"a".localeCompare,l=n&&0==="a".localeCompare("A",void 0,r),q=n&&0!=="a".localeCompare("\u00e1",void 0,r),v=n&&l&&q;cr.equals_nocase=function(a,p){return"string"!==typeof a||"string"!==typeof p||a.length!==p.length?!1:a===p?!0:v?0===a.localeCompare(p,void 0,r):a.toLowerCase()===p.toLowerCase()}})();var MatrixArray="undefined"!==typeof Float32Array?Float32Array:Array,glMatrixArrayType=MatrixArray,vec3={},mat3={},mat4={},quat4={};vec3.create=function(a){var b=new MatrixArray(3);a&&(b[0]=a[0],b[1]=a[1],b[2]=a[2]);return b};vec3.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];return b};vec3.add=function(a,b,d){if(!d||a===d)return a[0]+=b[0],a[1]+=b[1],a[2]+=b[2],a;d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];return d};vec3.subtract=function(a,b,d){if(!d||a===d)return a[0]-=b[0],a[1]-=b[1],a[2]-=b[2],a;d[0]=a[0]-b[0];d[1]=a[1]-b[1];d[2]=a[2]-b[2];return d};vec3.negate=function(a,b){b||(b=a);b[0]=-a[0];b[1]=-a[1];b[2]=-a[2];return b};vec3.scale=function(a,b,d){if(!d||a===d)return a[0]*=b,a[1]*=b,a[2]*=b,a;d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;return d};vec3.normalize=function(a,b){b||(b=a);var d=a[0],e=a[1],f=a[2],h=Math.sqrt(d*d+e*e+f*f);if(h){if(1===h)return b[0]=d,b[1]=e,b[2]=f,b}else return b[0]=0,b[1]=0,b[2]=0,b;h=1/h;b[0]=d*h;b[1]=e*h;b[2]=f*h;return b};vec3.cross=function(a,b,d){d||(d=a);var e=a[0],f=a[1];a=a[2];var h=b[0],k=b[1];b=b[2];d[0]=f*b-a*k;d[1]=a*h-e*b;d[2]=e*k-f*h;return d};vec3.length=function(a){var b=a[0],d=a[1];a=a[2];return Math.sqrt(b*b+d*d+a*a)};vec3.dot=function(a,b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]};vec3.direction=function(a,b,d){d||(d=a);var e=a[0]-b[0],f=a[1]-b[1];a=a[2]-b[2];b=Math.sqrt(e*e+f*f+a*a);if(!b)return d[0]=0,d[1]=0,d[2]=0,d;b=1/b;d[0]=e*b;d[1]=f*b;d[2]=a*b;return d};vec3.lerp=function(a,b,d,e){e||(e=a);e[0]=a[0]+d*(b[0]-a[0]);e[1]=a[1]+d*(b[1]-a[1]);e[2]=a[2]+d*(b[2]-a[2]);return e};vec3.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+"]"};mat3.create=function(a){var b=new MatrixArray(9);a&&(b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b[4]=a[4],b[5]=a[5],b[6]=a[6],b[7]=a[7],b[8]=a[8]);return b};mat3.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];return b};mat3.identity=function(a){a[0]=1;a[1]=0;a[2]=0;a[3]=0;a[4]=1;a[5]=0;a[6]=0;a[7]=0;a[8]=1;return a};mat3.transpose=function(a,b){if(!b||a===b){var d=a[1],e=a[2],f=a[5];a[1]=a[3];a[2]=a[6];a[3]=d;a[5]=a[7];a[6]=e;a[7]=f;return a}b[0]=a[0];b[1]=a[3];b[2]=a[6];b[3]=a[1];b[4]=a[4];b[5]=a[7];b[6]=a[2];b[7]=a[5];b[8]=a[8];return b};mat3.toMat4=function(a,b){b||(b=mat4.create());b[15]=1;b[14]=0;b[13]=0;b[12]=0;b[11]=0;b[10]=a[8];b[9]=a[7];b[8]=a[6];b[7]=0;b[6]=a[5];b[5]=a[4];b[4]=a[3];b[3]=0;b[2]=a[2];b[1]=a[1];b[0]=a[0];return b};mat3.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+", "+a[4]+", "+a[5]+", "+a[6]+", "+a[7]+", "+a[8]+"]"};mat4.create=function(a){var b=new MatrixArray(16);a&&(b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b[4]=a[4],b[5]=a[5],b[6]=a[6],b[7]=a[7],b[8]=a[8],b[9]=a[9],b[10]=a[10],b[11]=a[11],b[12]=a[12],b[13]=a[13],b[14]=a[14],b[15]=a[15]);return b};mat4.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=a[12];b[13]=a[13];b[14]=a[14];b[15]=a[15];return b};mat4.identity=function(a){a[0]=1;a[1]=0;a[2]=0;a[3]=0;a[4]=0;a[5]=1;a[6]=0;a[7]=0;a[8]=0;a[9]=0;a[10]=1;a[11]=0;a[12]=0;a[13]=0;a[14]=0;a[15]=1;return a};mat4.transpose=function(a,b){if(!b||a===b){var d=a[1],e=a[2],f=a[3],h=a[6],k=a[7],c=a[11];a[1]=a[4];a[2]=a[8];a[3]=a[12];a[4]=d;a[6]=a[9];a[7]=a[13];a[8]=e;a[9]=h;a[11]=a[14];a[12]=f;a[13]=k;a[14]=c;return a}b[0]=a[0];b[1]=a[4];b[2]=a[8];b[3]=a[12];b[4]=a[1];b[5]=a[5];b[6]=a[9];b[7]=a[13];b[8]=a[2];b[9]=a[6];b[10]=a[10];b[11]=a[14];b[12]=a[3];b[13]=a[7];b[14]=a[11];b[15]=a[15];return b};mat4.determinant=function(a){var b=a[0],d=a[1],e=a[2],f=a[3],h=a[4],k=a[5],c=a[6],r=a[7],n=a[8],l=a[9],q=a[10],v=a[11],t=a[12],p=a[13],g=a[14];a=a[15];return t*l*c*f-n*p*c*f-t*k*q*f+h*p*q*f+n*k*g*f-h*l*g*f-t*l*e*r+n*p*e*r+t*d*q*r-b*p*q*r-n*d*g*r+b*l*g*r+t*k*e*v-h*p*e*v-t*d*c*v+b*p*c*v+h*d*g*v-b*k*g*v-n*k*e*a+h*l*e*a+n*d*c*a-b*l*c*a-h*d*q*a+b*k*q*a};mat4.inverse=function(a,b){b||(b=a);var d=a[0],e=a[1],f=a[2],h=a[3],k=a[4],c=a[5],r=a[6],n=a[7],l=a[8],q=a[9],v=a[10],t=a[11],p=a[12],g=a[13],u=a[14],D=a[15],B=d*c-e*k,s=d*r-f*k,m=d*n-h*k,P=e*r-f*c,x=e*n-h*c,z=f*n-h*r,G=l*g-q*p,C=l*u-v*p,F=l*D-t*p,y=q*u-v*g,H=q*D-t*g,Q=v*D-t*u,w=1/(B*Q-s*H+m*y+P*F-x*C+z*G);b[0]=(c*Q-r*H+n*y)*w;b[1]=(-e*Q+f*H-h*y)*w;b[2]=(g*z-u*x+D*P)*w;b[3]=(-q*z+v*x-t*P)*w;b[4]=(-k*Q+r*F-n*C)*w;b[5]=(d*Q-f*F+h*C)*w;b[6]=(-p*z+u*m-D*s)*w;b[7]=(l*z-v*m+t*s)*w;b[8]=(k*H-c*F+n*G)*w;b[9]=(-d*H+e*F-h*G)*w;b[10]=(p*x-g*m+D*B)*w;b[11]=(-l*x+q*m-t*B)*w;b[12]=(-k*y+c*C-r*G)*w;b[13]=(d*y-e*C+f*G)*w;b[14]=(-p*P+g*s-u*B)*w;b[15]=(l*P-q*s+v*B)*w;return b};mat4.toRotationMat=function(a,b){b||(b=mat4.create());b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=0;b[13]=0;b[14]=0;b[15]=1;return b};mat4.toMat3=function(a,b){b||(b=mat3.create());b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[4];b[4]=a[5];b[5]=a[6];b[6]=a[8];b[7]=a[9];b[8]=a[10];return b};mat4.toInverseMat3=function(a,b){var d=a[0],e=a[1],f=a[2],h=a[4],k=a[5],c=a[6],r=a[8],n=a[9],l=a[10],q=l*k-c*n,v=-l*h+c*r,t=n*h-k*r,p=d*q+e*v+f*t;if(!p)return null;p=1/p;b||(b=mat3.create());b[0]=q*p;b[1]=(-l*e+f*n)*p;b[2]=(c*e-f*k)*p;b[3]=v*p;b[4]=(l*d-f*r)*p;b[5]=(-c*d+f*h)*p;b[6]=t*p;b[7]=(-n*d+e*r)*p;b[8]=(k*d-e*h)*p;return b};mat4.multiply=function(a,b,d){d||(d=a);var e=a[0],f=a[1],h=a[2],k=a[3],c=a[4],r=a[5],n=a[6],l=a[7],q=a[8],v=a[9],t=a[10],p=a[11],g=a[12],u=a[13],D=a[14];a=a[15];var B=b[0],s=b[1],m=b[2],P=b[3],x=b[4],z=b[5],G=b[6],C=b[7],F=b[8],y=b[9],H=b[10],Q=b[11],w=b[12],N=b[13],R=b[14];b=b[15];d[0]=B*e+s*c+m*q+P*g;d[1]=B*f+s*r+m*v+P*u;d[2]=B*h+s*n+m*t+P*D;d[3]=B*k+s*l+m*p+P*a;d[4]=x*e+z*c+G*q+C*g;d[5]=x*f+z*r+G*v+C*u;d[6]=x*h+z*n+G*t+C*D;d[7]=x*k+z*l+G*p+C*a;d[8]=F*e+y*c+H*q+Q*g;d[9]=F*f+y*r+H*v+Q*u;d[10]=F*h+y*n+H*t+Q*D;d[11]=F*k+y*l+H*p+Q*a;d[12]=w*e+N*c+R*q+b*g;d[13]=w*f+N*r+R*v+b*u;d[14]=w*h+N*n+R*t+b*D;d[15]=w*k+N*l+R*p+b*a;return d};mat4.multiplyVec3=function(a,b,d){d||(d=b);var e=b[0],f=b[1];b=b[2];d[0]=a[0]*e+a[4]*f+a[8]*b+a[12];d[1]=a[1]*e+a[5]*f+a[9]*b+a[13];d[2]=a[2]*e+a[6]*f+a[10]*b+a[14];return d};mat4.multiplyVec4=function(a,b,d){d||(d=b);var e=b[0],f=b[1],h=b[2];b=b[3];d[0]=a[0]*e+a[4]*f+a[8]*h+a[12]*b;d[1]=a[1]*e+a[5]*f+a[9]*h+a[13]*b;d[2]=a[2]*e+a[6]*f+a[10]*h+a[14]*b;d[3]=a[3]*e+a[7]*f+a[11]*h+a[15]*b;return d};mat4.translate=function(a,b,d){var e=b[0],f=b[1];b=b[2];var h,k,c,r,n,l,q,v,t,p,g,u;if(!d||a===d)return a[12]=a[0]*e+a[4]*f+a[8]*b+a[12],a[13]=a[1]*e+a[5]*f+a[9]*b+a[13],a[14]=a[2]*e+a[6]*f+a[10]*b+a[14],a[15]=a[3]*e+a[7]*f+a[11]*b+a[15],a;h=a[0];k=a[1];c=a[2];r=a[3];n=a[4];l=a[5];q=a[6];v=a[7];t=a[8];p=a[9];g=a[10];u=a[11];d[0]=h;d[1]=k;d[2]=c;d[3]=r;d[4]=n;d[5]=l;d[6]=q;d[7]=v;d[8]=t;d[9]=p;d[10]=g;d[11]=u;d[12]=h*e+n*f+t*b+a[12];d[13]=k*e+l*f+p*b+a[13];d[14]=c*e+q*f+g*b+a[14];d[15]=r*e+v*f+u*b+a[15];return d};mat4.scale=function(a,b,d){var e=b[0],f=b[1];b=b[2];if(!d||a===d)return a[0]*=e,a[1]*=e,a[2]*=e,a[3]*=e,a[4]*=f,a[5]*=f,a[6]*=f,a[7]*=f,a[8]*=b,a[9]*=b,a[10]*=b,a[11]*=b,a;d[0]=a[0]*e;d[1]=a[1]*e;d[2]=a[2]*e;d[3]=a[3]*e;d[4]=a[4]*f;d[5]=a[5]*f;d[6]=a[6]*f;d[7]=a[7]*f;d[8]=a[8]*b;d[9]=a[9]*b;d[10]=a[10]*b;d[11]=a[11]*b;d[12]=a[12];d[13]=a[13];d[14]=a[14];d[15]=a[15];return d};mat4.rotate=function(a,b,d,e){var f=d[0],h=d[1];d=d[2];var k=Math.sqrt(f*f+h*h+d*d),c,r,n,l,q,v,t,p,g,u,D,B,s,m,P,x,z,G,C,F;if(!k)return null;1!==k&&(k=1/k,f*=k,h*=k,d*=k);c=Math.sin(b);r=Math.cos(b);n=1-r;b=a[0];k=a[1];l=a[2];q=a[3];v=a[4];t=a[5];p=a[6];g=a[7];u=a[8];D=a[9];B=a[10];s=a[11];m=f*f*n+r;P=h*f*n+d*c;x=d*f*n-h*c;z=f*h*n-d*c;G=h*h*n+r;C=d*h*n+f*c;F=f*d*n+h*c;f=h*d*n-f*c;h=d*d*n+r;e?a!==e&&(e[12]=a[12],e[13]=a[13],e[14]=a[14],e[15]=a[15]):e=a;e[0]=b*m+v*P+u*x;e[1]=k*m+t*P+D*x;e[2]=l*m+p*P+B*x;e[3]=q*m+g*P+s*x;e[4]=b*z+v*G+u*C;e[5]=k*z+t*G+D*C;e[6]=l*z+p*G+B*C;e[7]=q*z+g*G+s*C;e[8]=b*F+v*f+u*h;e[9]=k*F+t*f+D*h;e[10]=l*F+p*f+B*h;e[11]=q*F+g*f+s*h;return e};mat4.rotateX=function(a,b,d){var e=Math.sin(b);b=Math.cos(b);var f=a[4],h=a[5],k=a[6],c=a[7],r=a[8],n=a[9],l=a[10],q=a[11];d?a!==d&&(d[0]=a[0],d[1]=a[1],d[2]=a[2],d[3]=a[3],d[12]=a[12],d[13]=a[13],d[14]=a[14],d[15]=a[15]):d=a;d[4]=f*b+r*e;d[5]=h*b+n*e;d[6]=k*b+l*e;d[7]=c*b+q*e;d[8]=f*-e+r*b;d[9]=h*-e+n*b;d[10]=k*-e+l*b;d[11]=c*-e+q*b;return d};mat4.rotateY=function(a,b,d){var e=Math.sin(b);b=Math.cos(b);var f=a[0],h=a[1],k=a[2],c=a[3],r=a[8],n=a[9],l=a[10],q=a[11];d?a!==d&&(d[4]=a[4],d[5]=a[5],d[6]=a[6],d[7]=a[7],d[12]=a[12],d[13]=a[13],d[14]=a[14],d[15]=a[15]):d=a;d[0]=f*b+r*-e;d[1]=h*b+n*-e;d[2]=k*b+l*-e;d[3]=c*b+q*-e;d[8]=f*e+r*b;d[9]=h*e+n*b;d[10]=k*e+l*b;d[11]=c*e+q*b;return d};mat4.rotateZ=function(a,b,d){var e=Math.sin(b);b=Math.cos(b);var f=a[0],h=a[1],k=a[2],c=a[3],r=a[4],n=a[5],l=a[6],q=a[7];d?a!==d&&(d[8]=a[8],d[9]=a[9],d[10]=a[10],d[11]=a[11],d[12]=a[12],d[13]=a[13],d[14]=a[14],d[15]=a[15]):d=a;d[0]=f*b+r*e;d[1]=h*b+n*e;d[2]=k*b+l*e;d[3]=c*b+q*e;d[4]=f*-e+r*b;d[5]=h*-e+n*b;d[6]=k*-e+l*b;d[7]=c*-e+q*b;return d};mat4.frustum=function(a,b,d,e,f,h,k){k||(k=mat4.create());var c=b-a,r=e-d,n=h-f;k[0]=2*f/c;k[1]=0;k[2]=0;k[3]=0;k[4]=0;k[5]=2*f/r;k[6]=0;k[7]=0;k[8]=(b+a)/c;k[9]=(e+d)/r;k[10]=-(h+f)/n;k[11]=-1;k[12]=0;k[13]=0;k[14]=-(2*h*f)/n;k[15]=0;return k};mat4.perspective=function(a,b,d,e,f){a=d*Math.tan(a*Math.PI/360);b*=a;return mat4.frustum(-b,b,-a,a,d,e,f)};mat4.ortho=function(a,b,d,e,f,h,k){k||(k=mat4.create());var c=b-a,r=e-d,n=h-f;k[0]=2/c;k[1]=0;k[2]=0;k[3]=0;k[4]=0;k[5]=2/r;k[6]=0;k[7]=0;k[8]=0;k[9]=0;k[10]=-2/n;k[11]=0;k[12]=-(a+b)/c;k[13]=-(e+d)/r;k[14]=-(h+f)/n;k[15]=1;return k};mat4.lookAt=function(a,b,d,e){e||(e=mat4.create());var f,h,k,c,r,n,l,q,v=a[0],t=a[1];a=a[2];h=d[0];k=d[1];f=d[2];d=b[1];n=b[2];if(v===b[0]&&t===d&&a===n)return mat4.identity(e);d=v-b[0];n=t-b[1];l=a-b[2];q=1/Math.sqrt(d*d+n*n+l*l);d*=q;n*=q;l*=q;b=k*l-f*n;f=f*d-h*l;h=h*n-k*d;(q=Math.sqrt(b*b+f*f+h*h))?(q=1/q,b*=q,f*=q,h*=q):h=f=b=0;k=n*h-l*f;c=l*b-d*h;r=d*f-n*b;(q=Math.sqrt(k*k+c*c+r*r))?(q=1/q,k*=q,c*=q,r*=q):r=c=k=0;e[0]=b;e[1]=k;e[2]=d;e[3]=0;e[4]=f;e[5]=c;e[6]=n;e[7]=0;e[8]=h;e[9]=r;e[10]=l;e[11]=0;e[12]=-(b*v+f*t+h*a);e[13]=-(k*v+c*t+r*a);e[14]=-(d*v+n*t+l*a);e[15]=1;return e};mat4.fromRotationTranslation=function(a,b,d){d||(d=mat4.create());var e=a[0],f=a[1],h=a[2],k=a[3],c=e+e,r=f+f,n=h+h;a=e*c;var l=e*r,e=e*n,q=f*r,f=f*n,h=h*n,c=c*k,r=r*k,k=k*n;d[0]=1-(q+h);d[1]=l+k;d[2]=e-r;d[3]=0;d[4]=l-k;d[5]=1-(a+h);d[6]=f+c;d[7]=0;d[8]=e+r;d[9]=f-c;d[10]=1-(a+q);d[11]=0;d[12]=b[0];d[13]=b[1];d[14]=b[2];d[15]=1;return d};mat4.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+", "+a[4]+", "+a[5]+", "+a[6]+", "+a[7]+", "+a[8]+", "+a[9]+", "+a[10]+", "+a[11]+", "+a[12]+", "+a[13]+", "+a[14]+", "+a[15]+"]"};quat4.create=function(a){var b=new MatrixArray(4);a&&(b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3]);return b};quat4.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];return b};quat4.calculateW=function(a,b){var d=a[0],e=a[1],f=a[2];if(!b||a===b)return a[3]=-Math.sqrt(Math.abs(1-d*d-e*e-f*f)),a;b[0]=d;b[1]=e;b[2]=f;b[3]=-Math.sqrt(Math.abs(1-d*d-e*e-f*f));return b};quat4.inverse=function(a,b){if(!b||a===b)return a[0]*=-1,a[1]*=-1,a[2]*=-1,a;b[0]=-a[0];b[1]=-a[1];b[2]=-a[2];b[3]=a[3];return b};quat4.length=function(a){var b=a[0],d=a[1],e=a[2];a=a[3];return Math.sqrt(b*b+d*d+e*e+a*a)};quat4.normalize=function(a,b){b||(b=a);var d=a[0],e=a[1],f=a[2],h=a[3],k=Math.sqrt(d*d+e*e+f*f+h*h);if(0===k)return b[0]=0,b[1]=0,b[2]=0,b[3]=0,b;k=1/k;b[0]=d*k;b[1]=e*k;b[2]=f*k;b[3]=h*k;return b};quat4.multiply=function(a,b,d){d||(d=a);var e=a[0],f=a[1],h=a[2];a=a[3];var k=b[0],c=b[1],r=b[2];b=b[3];d[0]=e*b+a*k+f*r-h*c;d[1]=f*b+a*c+h*k-e*r;d[2]=h*b+a*r+e*c-f*k;d[3]=a*b-e*k-f*c-h*r;return d};quat4.multiplyVec3=function(a,b,d){d||(d=b);var e=b[0],f=b[1],h=b[2];b=a[0];var k=a[1],c=a[2];a=a[3];var r=a*e+k*h-c*f,n=a*f+c*e-b*h,l=a*h+b*f-k*e,e=-b*e-k*f-c*h;d[0]=r*a+e*-b+n*-c-l*-k;d[1]=n*a+e*-k+l*-b-r*-c;d[2]=l*a+e*-c+r*-k-n*-b;return d};quat4.toMat3=function(a,b){b||(b=mat3.create());var d=a[0],e=a[1],f=a[2],h=a[3],k=d+d,c=e+e,r=f+f,n=d*k,l=d*c,d=d*r,q=e*c,e=e*r,f=f*r,k=k*h,c=c*h,h=h*r;b[0]=1-(q+f);b[1]=l+h;b[2]=d-c;b[3]=l-h;b[4]=1-(n+f);b[5]=e+k;b[6]=d+c;b[7]=e-k;b[8]=1-(n+q);return b};quat4.toMat4=function(a,b){b||(b=mat4.create());var d=a[0],e=a[1],f=a[2],h=a[3],k=d+d,c=e+e,r=f+f,n=d*k,l=d*c,d=d*r,q=e*c,e=e*r,f=f*r,k=k*h,c=c*h,h=h*r;b[0]=1-(q+f);b[1]=l+h;b[2]=d-c;b[3]=0;b[4]=l-h;b[5]=1-(n+f);b[6]=e+k;b[7]=0;b[8]=d+c;b[9]=e-k;b[10]=1-(n+q);b[11]=0;b[12]=0;b[13]=0;b[14]=0;b[15]=1;return b};quat4.slerp=function(a,b,d,e){e||(e=a);var f=a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3],h,k;if(1<=Math.abs(f))return e!==a&&(e[0]=a[0],e[1]=a[1],e[2]=a[2],e[3]=a[3]),e;h=Math.acos(f);k=Math.sqrt(1-f*f);if(0.001>Math.abs(k))return e[0]=0.5*a[0]+0.5*b[0],e[1]=0.5*a[1]+0.5*b[1],e[2]=0.5*a[2]+0.5*b[2],e[3]=0.5*a[3]+0.5*b[3],e;f=Math.sin((1-d)*h)/k;d=Math.sin(d*h)/k;e[0]=a[0]*f+b[0]*d;e[1]=a[1]*f+b[1]*d;e[2]=a[2]*f+b[2]*d;e[3]=a[3]*f+b[3]*d;return e};quat4.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+"]"};(function(){function a(a,c){this.height=this.width=0;this.cam=vec3.create([0,0,100]);this.look=vec3.create([0,0,0]);this.up=vec3.create([0,1,0]);this.worldScale=vec3.create([1,1,1]);this.matP=mat4.create();this.matMV=mat4.create();this.lastMV=mat4.create();this.currentMV=mat4.create();this.gl=a;this.initState()}function b(a,c,f){this.gl=a;this.shaderProgram=c;this.name=f;this.locAPos=a.getAttribLocation(c,"aPos");this.locATex=a.getAttribLocation(c,"aTex");this.locMatP=a.getUniformLocation(c,"matP");this.locMatMV=a.getUniformLocation(c,"matMV");this.locOpacity=a.getUniformLocation(c,"opacity");this.locSamplerFront=a.getUniformLocation(c,"samplerFront");this.locSamplerBack=a.getUniformLocation(c,"samplerBack");this.locDestStart=a.getUniformLocation(c,"destStart");this.locDestEnd=a.getUniformLocation(c,"destEnd");this.locSeconds=a.getUniformLocation(c,"seconds");this.locPixelWidth=a.getUniformLocation(c,"pixelWidth");this.locPixelHeight=a.getUniformLocation(c,"pixelHeight");this.locLayerScale=a.getUniformLocation(c,"layerScale");this.locOpacity&&a.uniform1f(this.locOpacity,1);this.locSamplerFront&&a.uniform1i(this.locSamplerFront,0);this.locSamplerBack&&a.uniform1i(this.locSamplerBack,1);this.locDestStart&&a.uniform2f(this.locDestStart,0,0);this.locDestEnd&&a.uniform2f(this.locDestEnd,1,1);this.hasCurrentMatMV=!1}function d(a,c){this.type=a;this.glwrap=c;this.gl=c.gl;this.indexCount=this.startIndex=this.opacityParam=0;this.mat4param=this.texParam=null;this.shaderParams=[];cr.seal(this)}function e(a){--a;for(var c=1;32>c;c<<=1)a|=a>>c;return a+1}a.prototype.initState=function(){var a=this.gl,c;this.lastOpacity=1;this.lastTexture=null;this.currentOpacity=1;a.clearColor(0,0,0,0);a.clear(a.COLOR_BUFFER_BIT);a.enable(a.BLEND);a.blendFunc(a.ONE,a.ONE_MINUS_SRC_ALPHA);a.disable(a.CULL_FACE);a.disable(a.DEPTH_TEST);this.maxTextureSize=a.getParameter(a.MAX_TEXTURE_SIZE);this.lastSrcBlend=a.ONE;this.lastDestBlend=a.ONE_MINUS_SRC_ALPHA;this.pointBuffer=a.createBuffer();a.bindBuffer(a.ARRAY_BUFFER,this.pointBuffer);this.vertexBuffers=Array(4);this.texcoordBuffers=Array(4);for(c=0;4>c;c++)this.vertexBuffers[c]=a.createBuffer(),a.bindBuffer(a.ARRAY_BUFFER,this.vertexBuffers[c]),this.texcoordBuffers[c]=a.createBuffer(),a.bindBuffer(a.ARRAY_BUFFER,this.texcoordBuffers[c]);this.curBuffer=0;this.indexBuffer=a.createBuffer();a.bindBuffer(a.ELEMENT_ARRAY_BUFFER,this.indexBuffer);this.vertexData=new Float32Array(16E3);this.texcoordData=new Float32Array(16E3);this.pointData=new Float32Array(32E3);for(var f=new Uint16Array(12E3),b=c=0;12E3>c;)f[c++]=b,f[c++]=b+1,f[c++]=b+2,f[c++]=b,f[c++]=b+2,f[c++]=b+3,b+=4;a.bufferData(a.ELEMENT_ARRAY_BUFFER,f,a.STATIC_DRAW);this.pointPtr=this.vertexPtr=0;this.shaderPrograms=[];c=this.createShaderProgram({src:"varying mediump vec2 vTex;\nuniform lowp float opacity;\nuniform lowp sampler2D samplerFront;\nvoid main(void) {\n\tgl_FragColor = texture2D(samplerFront, vTex);\n\tgl_FragColor *= opacity;\n}"},"attribute highp vec2 aPos;\nattribute mediump vec2 aTex;\nvarying mediump vec2 vTex;\nuniform highp mat4 matP;\nuniform highp mat4 matMV;\nvoid main(void) {\n\tgl_Position = matP * matMV * vec4(aPos.x, aPos.y, 0.0, 1.0);\n\tvTex = aTex;\n}","");this.shaderPrograms.push(c);c=this.createShaderProgram({src:"uniform mediump sampler2D samplerFront;\nvarying lowp float opacity;\nvoid main(void) {\n\tgl_FragColor = texture2D(samplerFront, gl_PointCoord);\n\tgl_FragColor *= opacity;\n}"},"attribute vec4 aPos;\nvarying float opacity;\nuniform mat4 matP;\nuniform mat4 matMV;\nvoid main(void) {\n\tgl_Position = matP * matMV * vec4(aPos.x, aPos.y, 0.0, 1.0);\n\tgl_PointSize = aPos.z;\n\topacity = aPos.w;\n}","");this.shaderPrograms.push(c);for(var l in cr.shaders)cr.shaders.hasOwnProperty(l)&&this.shaderPrograms.push(this.createShaderProgram(cr.shaders[l],"attribute highp vec2 aPos;\nattribute mediump vec2 aTex;\nvarying mediump vec2 vTex;\nuniform highp mat4 matP;\nuniform highp mat4 matMV;\nvoid main(void) {\n\tgl_Position = matP * matMV * vec4(aPos.x, aPos.y, 0.0, 1.0);\n\tvTex = aTex;\n}",l));a.activeTexture(a.TEXTURE0);a.bindTexture(a.TEXTURE_2D,null);this.batch=[];this.batchPtr=0;this.hasPointBatchTop=this.hasQuadBatchTop=!1;this.currentProgram=this.lastProgram=-1;this.currentShader=null;this.fbo=a.createFramebuffer();this.renderToTex=null;this.tmpVec3=vec3.create([0,0,0]);a=a.getParameter(a.ALIASED_POINT_SIZE_RANGE);this.minPointSize=a[0];this.maxPointSize=a[1];this.switchProgram(0);cr.seal(this)};a.prototype.createShaderProgram=function(a,c,f){var d=this.gl,l=d.createShader(d.FRAGMENT_SHADER);d.shaderSource(l,a.src);d.compileShader(l);if(!d.getShaderParameter(l,d.COMPILE_STATUS))return d.deleteShader(l),null;var e=d.createShader(d.VERTEX_SHADER);d.shaderSource(e,c);d.compileShader(e);if(!d.getShaderParameter(e,d.COMPILE_STATUS))return d.deleteShader(l),d.deleteShader(e),null;c=d.createProgram();d.attachShader(c,l);d.attachShader(c,e);d.linkProgram(c);if(!d.getProgramParameter(c,d.LINK_STATUS))return d.deleteShader(l),d.deleteShader(e),d.deleteProgram(c),null;d.useProgram(c);d.validateProgram(c);d.deleteShader(l);d.deleteShader(e);f=new b(d,c,f);f.extendBoxHorizontal=a.extendBoxHorizontal||0;f.extendBoxVertical=a.extendBoxVertical||0;f.crossSampling=!!a.crossSampling;f.animated=!!a.animated;f.parameters=a.parameters||[];a=0;for(l=f.parameters.length;ac;c++)if(this.lastMV[c]!==this.matMV[c]){a=!0;break}a&&(a=this.pushBatch(),a.type=5,a.mat4param?mat4.set(this.matMV,a.mat4param):a.mat4param=mat4.create(this.matMV),mat4.set(this.matMV,this.lastMV),this.hasPointBatchTop=this.hasQuadBatchTop=!1)};d.prototype.doSetTexture=function(){this.gl.bindTexture(this.gl.TEXTURE_2D,this.texParam)};d.prototype.doSetOpacity=function(){var a=this.opacityParam,c=this.glwrap;c.currentOpacity=a;c=c.currentShader;c.locOpacity&&this.gl.uniform1f(c.locOpacity,a)};d.prototype.doQuad=function(){this.gl.drawElements(this.gl.TRIANGLES,this.indexCount,this.gl.UNSIGNED_SHORT,2*this.startIndex)};d.prototype.doSetBlend=function(){this.gl.blendFunc(this.startIndex,this.indexCount)};d.prototype.doUpdateModelView=function(){var a,c,f,b=this.glwrap.shaderPrograms,l=this.glwrap.currentProgram;a=0;for(c=b.length;a"===c.name)&&a.vertexAttribPointer(c.locAPos,4,a.FLOAT,!1,0,0));if(0"!==c.name)&&a.vertexAttribPointer(c.locAPos,2,a.FLOAT,!1,0,0);a.bindBuffer(a.ARRAY_BUFFER,this.texcoordBuffers[this.curBuffer]);a.bufferData(a.ARRAY_BUFFER,this.texcoordData.subarray(0,this.vertexPtr),a.STREAM_DRAW);c&&(0<=c.locATex&&""!==c.name)&&a.vertexAttribPointer(c.locATex,2,a.FLOAT,!1,0,0)}for(var f,a=0,c=this.batchPtr;am?(m*=g,5===e?(e=m/this.original_width,1e&&(e=1/Math.ceil(1/e)),m=this.original_width*e,d=this.original_height*e,b=(a-m)/2,s=(g-d)/2,a=m,g=d):(b=(a-m)/2,a=m)):(d=a/m,5===e?(e=d/this.original_height,1e&&(e=1/Math.ceil(1/e)),m=this.original_width*e,d=this.original_height*e,b=(a-m)/2,s=(g-d)/2,a=m):s=(g-d)/2,g=d),l&&!this.isNodeWebkit&&(s=b=0),b=Math.floor(b),s=Math.floor(s),a=Math.floor(a),g=Math.floor(g)):this.isNodeWebkit&&(this.isNodeFullscreen&&0===this.fullscreen_mode_set)&&(b=Math.floor((a-this.original_width)/2),s=Math.floor((g-this.original_height)/2),a=this.original_width,g=this.original_height);this.isRetina&&(this.isiPad&&1=s)s=5E4;a+=s;this.wait_for_textures[c].complete||this.wait_for_textures[c].loaded?g+=s:f=!1}this.progress=0==a?0:g/a;return f};a.prototype.go=function(){if(this.ctx||this.glwrap){var a=this.ctx||this.overlay_ctx;this.overlay_canvas&&this.positionOverlayCanvas();this.progress=0;this.last_progress=-1;if(this.areAllTexturesLoaded())this.go_textures_done();else{var g=Date.now()-this.start_time,c=1;this.isTizen&&(c=this.devicePixelRatio);if(a){if(3!==this.loaderstyle&&500<=g&&this.last_progress!=this.progress){a.clearRect(0,0,this.width,this.height);var g=this.width/(2*c),c=this.height/(2*c),f=0===this.loaderstyle&&this.loaderlogo.complete,b=40,s=0,m=80;f&&(m=this.loaderlogo.width,b=m/2,s=this.loaderlogo.height/2,a.drawImage(this.loaderlogo,cr.floor(g-b),cr.floor(c-s)));1>=this.loaderstyle?(c+=s+(f?12:0),g=cr.floor(g-b)+0.5,c=cr.floor(c)+0.5,a.fillStyle="DodgerBlue",a.fillRect(g,c,Math.floor(m*this.progress),6),a.strokeStyle="black",a.strokeRect(g,c,m,6),a.strokeStyle="white",a.strokeRect(g-1,c-1,m+2,8)):2===this.loaderstyle&&(a.font="12pt Arial",a.fillStyle="#999",a.textBaseLine="middle",f=Math.round(100*this.progress)+"%",b=a.measureText?a.measureText(f):null,a.fillText(f,g-(b?b.width:0)/2,c))}this.last_progress=this.progress}setTimeout(function(a){return function(){a.go()}}(this),100)}}};a.prototype.go_textures_done=function(){this.overlay_canvas&&(this.canvas.parentNode.removeChild(this.overlay_canvas),this.overlay_canvas=this.overlay_ctx=null);this.start_time=Date.now();this.last_fps_time=cr.performance_now();var a,g,c;if(this.uses_loader_layout)for(a=0,g=this.types_by_index.length;aa||2===this.fullscreen_mode&&gg||2===f&&ca++;)this.doChangeLayout(this.changelayout);a=0;for(g=this.eventsheets_by_index.length;ag.deadCache.length&&g.deadCache.push(a);g.stale_iids=!0}this.deathRow.isEmpty()||(this.redraw=!0);this.deathRow.clear();this.isInClearDeathRow=!1};a.prototype.createInstance=function(a,g,c,f){if(a.is_family){var b=cr.floor(Math.random()*a.members.length);return this.createInstance(a.members[b],g,c,f)}return a.default_instance?this.createInstanceFromInit(a.default_instance,g,!1,c,f,!1):null};var r=[];a.prototype.createInstanceFromInit=function(a,g,c,f,b,s){var m,d,e,l;if(!a)return null;var h=this.types_by_index[a[1]],k=h.plugin.is_world;if(this.isloading&&k&&!h.isOnLoaderLayout||k&&!this.glwrap&&11===a[0][11])return null;var t=g;k||(g=null);var q;h.deadCache.length?(q=h.deadCache.pop(),q.recycled=!0,h.plugin.Instance.call(q,h)):(q=new h.plugin.Instance(h),q.recycled=!1);q.uid=c&&!s?a[2]:this.next_uid++;this.objectsByUid[q.uid.toString()]=q;q.puid=this.next_puid++;q.iid=0;q.get_iid=cr.inst_get_iid;h.stale_iids=!0;e=a[3];if(q.recycled)cr.wipe(q.extra);else{q.extra={};if("undefined"!==typeof cr_is_preview)for(q.instance_var_names=[],q.instance_var_names.length=e.length,m=0,d=e.length;ma&&(a=0);a>=this.running_layout.layers.length&&(a=this.running_layout.layers.length-1);return this.running_layout.layers[a]};a.prototype.getLayer=function(a){return cr.is_number(a)?this.getLayerByNumber(a):this.getLayerByName(a.toString())};a.prototype.clearSol=function(a){var g,c;g=0;for(c=a.length;gk;k++)if(t=e-k*n,a.x=c+Math.cos(t)*m,a.y=f+Math.sin(t)*m,a.set_bbox_changed(),!this.testOverlap(a,h)&&(h=b?null:this.testOverlapSolid(a),!h)){q=t;break}36===k&&(q=cr.clamp_angle(e+cr.PI));h=l;for(k=1;36>k;k++)if(t=e+k*n,a.x=c+Math.cos(t)*m,a.y=f+Math.sin(t)*m,a.set_bbox_changed(),!this.testOverlap(a,h)&&(h=b?null:this.testOverlapSolid(a),!h)){r=t;break}36===k&&(r=cr.clamp_angle(e+cr.PI));a.x=d;a.y=s;a.set_bbox_changed();if(r===q)return r;a=cr.angleDiff(r,q)/2;a=cr.angleClockwise(r,q)?cr.clamp_angle(q+a+cr.PI):cr.clamp_angle(r+a);q=Math.cos(e);e=Math.sin(e);r=Math.cos(a);a=Math.sin(a);c=q*r+e*a;return cr.angleTo(0,0,q-2*c*r,e-2*c*a)};var v=[],t=-1;a.prototype.trigger=function(a,c,f){if(!this.running_layout)return!1;var b=this.running_layout.event_sheet;if(!b)return!1;t++;t===v.length?v.push(new cr.ObjectSet):v[t].clear();a=this.triggerOnSheet(a,c,b,f);t--;return a};a.prototype.triggerOnSheet=function(a,c,f,b){var d=v[t];if(d.contains(f))return!1;d.add(f);var d=f.includes.valuesRef(),s=!1,m,e,l;m=0;for(e=d.length;m=this.localvar_stack.length&&this.localvar_stack.push([])};a.prototype.popLocalVarStack=function(){this.localvar_stack_index--};a.prototype.getCurrentLocalVarStack=function(){return this.localvar_stack[this.localvar_stack_index]};a.prototype.pushEventStack=function(a){this.event_stack_index++;this.event_stack_index>=this.event_stack.length&&this.event_stack.push(new cr.eventStackFrame);var c=this.getCurrentEventStack();c.reset(a);return c};a.prototype.popEventStack=function(){this.event_stack_index--};a.prototype.getCurrentEventStack=function(){return this.event_stack[this.event_stack_index]};a.prototype.pushLoopStack=function(a){this.loop_stack_index++;this.loop_stack_index>=this.loop_stack.length&&this.loop_stack.push(cr.seal({name:a,index:0,stopped:!1}));var c=this.getCurrentLoop();c.name=a;c.index=0;c.stopped=!1;return c};a.prototype.popLoopStack=function(){this.loop_stack_index--};a.prototype.getCurrentLoop=function(){return this.loop_stack[this.loop_stack_index]};a.prototype.getEventVariableByName=function(a,c){for(var f,b,d,s,m,e;c;){f=0;for(b=c.subevents.length;fe||e>=a.instance_vars.length||(a.instance_vars[e]=b[f]));if(d.is_world){e=c.w;a.layer.sid!==e.l&&(b=a.layer,a.layer=this.running_layout.getLayerBySid(e.l),a.layer?(a.layer.instances.push(a),a.layer.zindices_stale=!0,cr.arrayFindRemove(b.instances,a),b.zindices_stale=!0):(a.layer=b,this.DestroyInstance(a)));a.x=e.x;a.y=e.y;a.width=e.w;a.height=e.h;a.zindex=e.zi;a.angle=e.hasOwnProperty("a")?e.a:0;a.opacity=e.hasOwnProperty("o")?e.o:1;a.hotspotX=e.hasOwnProperty("hX")?e.hX:0.5;a.hotspotY=e.hasOwnProperty("hY")?e.hY:0.5;a.visible=e.hasOwnProperty("v")?e.v:!0;a.collisionsEnabled=e.hasOwnProperty("ce")?e.ce:!0;a.my_timescale=e.hasOwnProperty("mts")?e.mts:-1;a.blend_mode=e.hasOwnProperty("bm")?e.bm:0;a.compositeOp=cr.effectToCompositeOp(a.blend_mode);this.gl&&cr.setGLBlend(a,a.blend_mode,this.gl);a.set_bbox_changed();if(e.hasOwnProperty("fx"))for(b=0,d=e.fx.length;bm||(a.active_effect_flags[m]=e.fx[b].active,a.effect_params[m]=e.fx[b].params);a.updateActiveEffects()}if(l=c.behs)for(f in l)l.hasOwnProperty(f)&&(e=this.getBehaviorIndexBySid(a,parseInt(f,10)),0>e||a.behavior_insts[e].loadFromJSON(l[f]));c.data&&a.loadFromJSON(c.data)};cr.runtime=a;cr.createRuntime=function(c){return new a(document.getElementById(c))};cr.createDCRuntime=function(c,f){return new a({dc:!0,width:c,height:f})};window.cr_createRuntime=cr.createRuntime;window.cr_createDCRuntime=cr.createDCRuntime;window.createCocoonJSRuntime=function(){window.c2cocoonjs=!0;var c=document.createElement("screencanvas")||document.createElement("canvas");document.body.appendChild(c);c=new a(c);window.c2runtime=c;window.addEventListener("orientationchange",function(){window.c2runtime.setSize(window.innerWidth,window.innerHeight)});window.c2runtime.setSize(window.innerWidth,window.innerHeight);return c}})();window.cr_getC2Runtime=function(){var a=document.getElementById("c2canvas");return a?a.c2runtime:window.c2runtime?window.c2runtime:null};window.cr_sizeCanvas=function(a,b){if(0!==a&&0!==b){var d=window.cr_getC2Runtime();d&&d.setSize(a,b)}};window.cr_setSuspended=function(a){var b=window.cr_getC2Runtime();b&&b.setSuspended(a)};(function(){function a(a,b){this.runtime=a;this.event_sheet=null;this.scrollX=this.runtime.original_width/2;this.scrollY=this.runtime.original_height/2;this.scale=1;this.angle=0;this.first_visit=!0;this.name=b[0];this.width=b[1];this.height=b[2];this.unbounded_scrolling=b[3];this.sheetname=b[4];this.sid=b[5];var d=b[6],c,e;this.layers=[];this.initial_types=[];c=0;for(e=d.length;c=this.layers.length&&(q=this.layers.length-1);n.layer=this.layers[q];n.layer.instances.push(n);n.layer.zindices_stale=!0}e.length=0;this.boundScrolling();a=0;for(k=this.layers.length;ak?n.siblings.push(l.instances[k]):l.default_instance&&(r=this.runtime.createInstanceFromInit(l.default_instance,n.layer,!0,n.x,n.y,!0),this.runtime.ClearDeathRow(),l.updateIIDs(),n.siblings.push(r),e.push(r)));a=0;for(k=this.initial_nonworld.length;a=this.active_effect_types.length?(1===this.active_effect_types.length?(b=this.active_effect_types[0].index,a.switchProgram(this.active_effect_types[0].shaderindex),a.setProgramParameters(null,1/this.runtime.width,1/this.runtime.height,0,0,1,1,this.scale,this.effect_params[b]),a.programIsAnimated(this.active_effect_types[0].shaderindex)&&(this.runtime.redraw=!0)):a.switchProgram(0),a.setRenderingToTexture(null),a.setOpacity(1),a.setTexture(this.runtime.layout_tex),a.setAlphaBlend(),a.resetModelView(),a.updateModelView(),b=this.runtime.width/2,d=this.runtime.height/2,a.quad(-b,d,b,d,b,-d,-b,-d),a.setTexture(null)):this.renderEffectChain(a,null,null,null));a.present()};a.prototype.getRenderTarget=function(){return 0this.width-b&&(a=this.width-b);athis.height-b&&(a=this.height-b);aG&&(G=0);0>F&&(F=0);H>B&&(H=B);w>s&&(w=s);0>C&&(C=0);0>y&&(y=0);Q>B&&(Q=B);N>s&&(N=s);x.left=G/B;x.top=1-F/s;x.right=H/B;x.bottom=1-w/s}else x.left=z.left=0,x.top=z.top=0,x.right=z.right=1,x.bottom=z.bottom=1;R=d&&((d.angle||W)&&a.programUsesDest(e[0].shaderindex)||0!==t||0!==R||1!==d.opacity||d.type.plugin.must_predraw)||b&&!d&&1!==b.opacity;a.setAlphaBlend();if(R){l[g]||(l[g]=a.createEmptyTexture(B,s,this.runtime.linearSampling));if(l[g].c2width!==B||l[g].c2height!==s)a.deleteTexture(l[g]),l[g]=a.createEmptyTexture(B,s,this.runtime.linearSampling);a.switchProgram(0);a.setRenderingToTexture(l[g]);D=N-y;a.clearRect(C,s-y-D,Q-C,D);d?d.drawGL(a):(a.setTexture(this.runtime.layer_tex),a.setOpacity(b.opacity),a.resetModelView(),a.translate(-m,-P),a.updateModelView(),a.quadTex(G,w,H,w,H,F,G,F,x));z.left=z.top=0;z.right=z.bottom=1;d&&(p=x.top,x.top=x.bottom,x.bottom=p);g=1;u=0}a.setOpacity(1);t=e.length-1;var W=a.programUsesCrossSampling(e[t].shaderindex),U=0;q=0;for(v=e.length;qthis.viewRight||l.top>this.viewBottom)||(d.globalCompositeOperation=n.compositeOp,n.draw(d)));d.restore();this.render_offscreen&&(a.globalCompositeOperation=this.compositeOp,a.globalAlpha=this.opacity,a.drawImage(b,0,0))};b.prototype.rotateViewport=function(a,b,d){var c=this.getScale();this.viewLeft=a;this.viewTop=b;this.viewRight=a+this.runtime.width*(1/c);this.viewBottom=b+this.runtime.height*(1/c);a=this.getAngle();0!==a&&(d&&(d.translate(this.runtime.width/2,this.runtime.height/2),d.rotate(-a),d.translate(this.runtime.width/-2,this.runtime.height/-2)),this.tmprect.set(this.viewLeft,this.viewTop,this.viewRight,this.viewBottom),this.tmprect.offset((this.viewLeft+this.viewRight)/-2,(this.viewTop+this.viewBottom)/-2),this.tmpquad.set_from_rotated_rect(this.tmprect,a),this.tmpquad.bounding_box(this.tmprect),this.tmprect.offset((this.viewLeft+this.viewRight)/2,(this.viewTop+this.viewBottom)/2),this.viewLeft=this.tmprect.left,this.viewTop=this.tmprect.top,this.viewRight=this.tmprect.right,this.viewBottom=this.tmprect.bottom)};b.prototype.drawGL=function(a){var b=this.runtime.width,d=this.runtime.height,c=0,e=0;if(this.render_offscreen=this.forceOwnTexture||1!==this.opacity||0this.viewRight||c.top>this.viewBottom)))if(v.uses_shaders)if(c=v.active_effect_types[0].shaderindex,e=v.active_effect_types[0].index,1!==v.active_effect_types.length||a.programUsesCrossSampling(c)||a.programExtendsBox(c)||(v.angle||v.layer.getAngle())&&a.programUsesDest(c)||1!==v.opacity||v.type.plugin.must_predraw)this.layout.renderEffectChain(a,this,v,this.render_offscreen?this.runtime.layer_tex:this.layout.getRenderTarget()),a.resetModelView(),a.scale(n,n),a.rotateZ(-this.getAngle()),a.translate((this.viewLeft+this.viewRight)/-2,(this.viewTop+this.viewBottom)/-2),a.updateModelView();else{a.switchProgram(c);a.setBlend(v.srcBlend,v.destBlend);a.programIsAnimated(c)&&(this.runtime.redraw=!0);var t=0,p=0,g=0,u=0;a.programUsesDest(c)&&(c=v.bbox,t=this.layerToCanvas(c.left,c.top,!0),p=this.layerToCanvas(c.left,c.top,!1),g=this.layerToCanvas(c.right,c.bottom,!0),c=this.layerToCanvas(c.right,c.bottom,!1),t/=b,p=1-p/d,g/=b,u=1-c/d);a.setProgramParameters(this.render_offscreen?this.runtime.layer_tex:this.layout.getRenderTarget(),1/v.width,1/v.height,t,p,g,u,this.getScale(),v.effect_params[e]);v.drawGL(a)}else a.switchProgram(0),a.setBlend(v.srcBlend,v.destBlend),v.drawGL(a);this.render_offscreen&&(c=this.active_effect_types.length?this.active_effect_types[0].shaderindex:0,e=this.active_effect_types.length?this.active_effect_types[0].index:0,0===this.active_effect_types.length||1===this.active_effect_types.length&&!a.programUsesCrossSampling(c)&&1===this.opacity?(1===this.active_effect_types.length?(a.switchProgram(c),a.setProgramParameters(this.layout.getRenderTarget(),1/this.runtime.width,1/this.runtime.height,0,0,1,1,this.getScale(),this.effect_params[e]),a.programIsAnimated(c)&&(this.runtime.redraw=!0)):a.switchProgram(0),a.setRenderingToTexture(this.layout.getRenderTarget()),a.setOpacity(this.opacity),a.setTexture(this.runtime.layer_tex),a.setBlend(this.srcBlend,this.destBlend),a.resetModelView(),a.updateModelView(),b=this.runtime.width/2,d=this.runtime.height/2,a.quad(-b,d,b,d,b,-d,-b,-d),a.setTexture(null)):this.layout.renderEffectChain(a,this,null,this.layout.getRenderTarget()))};b.prototype.canvasToLayer=function(a,b,d){var c=this.runtime.devicePixelRatio;this.runtime.isRetina&&0c[1].index&&(d=c[0],c[0]=c[1],c[1]=d):2=t.length&&(t.length=c.length+1);t[c.length]||(t[c.length]=[]);m=t[c.length];d=0;for(e=m.length;d=c.length&&(c.length=this.localIndex+1),c[this.localIndex]=a):this.data=a};l.prototype.getValue=function(){var a=this.runtime.getCurrentLocalVarStack();return!this.parent||this.is_static||!a||this.is_constant?this.data:this.localIndex>=a.length||"undefined"===typeof a[this.localIndex]?this.initial:a[this.localIndex]};l.prototype.run=function(){!this.parent||(this.is_static||this.is_constant)||this.setValue(this.initial)};cr.eventvariable=l;q.prototype.toString=function(){return"include:"+this.include_sheet.toString()};q.prototype.postInit=function(){this.include_sheet=this.runtime.eventsheets[this.include_sheet_name];this.sheet.includes.add(this);this.solModifiers=d(this.solModifiers)};q.prototype.run=function(){this.parent&&this.runtime.pushCleanSol(this.runtime.types_by_index);this.include_sheet.hasRun||this.include_sheet.run();this.parent&&this.runtime.popSol(this.runtime.types_by_index)};q.prototype.isActive=function(){for(var a=this.parent;a;){if(a.group&&!this.runtime.activeGroups[a.group_name.toLowerCase()])return!1;a=a.parent}return!0};cr.eventinclude=q;v.prototype.reset=function(a){this.current_event=a;this.actindex=this.cndindex=0;this.temp_parents_arr.length=0;this.else_branch_ran=this.last_event_true=!1};v.prototype.isModifierAfterCnds=function(){return this.current_event.solWriterAfterCnds?!0:this.cndindex=this.type&&(this.first=new cr.expNode(a,b[1]),this.second=new cr.expNode(a,b[2]));if(f){var h,k;h=0;for(k=f.length;hb&&(b+=f.length);f=f[b];-1b&&(b+=f.length);f=f[b].instance_vars[this.varindex];cr.is_string(f)?a.set_string(f):a.set_float(f);this.owner.popTempValue();return}this.owner.popTempValue()}b%=f.length;0>b&&(b+=f.length);f=f[b];b=0;this.object_type.is_family&&(b=f.type.family_var_map[this.object_type.family_index]);f=f.instance_vars[this.varindex+b];cr.is_string(f)?a.set_string(f):a.set_float(f)};a.prototype.eval_int=function(a){a.type=cr.exptype.Integer;a.data=this.value};a.prototype.eval_float=function(a){a.type=cr.exptype.Float;a.data=this.value};a.prototype.eval_string=function(a){a.type=cr.exptype.String;a.data=this.value};a.prototype.eval_unaryminus=function(a){this.first.get(a);a.is_number()&&(a.data=-a.data)};a.prototype.eval_add=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.is_number()&&b.is_number()&&(a.data+=b.data,b.is_float()&&a.make_float());this.owner.popTempValue()};a.prototype.eval_subtract=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.is_number()&&b.is_number()&&(a.data-=b.data,b.is_float()&&a.make_float());this.owner.popTempValue()};a.prototype.eval_multiply=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.is_number()&&b.is_number()&&(a.data*=b.data,b.is_float()&&a.make_float());this.owner.popTempValue()};a.prototype.eval_divide=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.is_number()&&b.is_number()&&(a.data/=b.data,a.make_float());this.owner.popTempValue()};a.prototype.eval_mod=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.is_number()&&b.is_number()&&(a.data%=b.data,b.is_float()&&a.make_float());this.owner.popTempValue()};a.prototype.eval_power=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.is_number()&&b.is_number()&&(a.data=Math.pow(a.data,b.data),b.is_float()&&a.make_float());this.owner.popTempValue()};a.prototype.eval_and=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.is_number()?b.is_string()?a.set_string(a.data.toString()+b.data):a.data&&b.data?a.set_int(1):a.set_int(0):a.is_string()&&(b.is_string()?a.data+=b.data:a.data+=(Math.round(1E10*b.data)/1E10).toString());this.owner.popTempValue()};a.prototype.eval_or=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.is_number()&&b.is_number()&&(a.data||b.data?a.set_int(1):a.set_int(0));this.owner.popTempValue()};a.prototype.eval_conditional=function(a){this.first.get(a);a.data?this.second.get(a):this.third.get(a)};a.prototype.eval_equal=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.set_int(a.data===b.data?1:0);this.owner.popTempValue()};a.prototype.eval_notequal=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.set_int(a.data!==b.data?1:0);this.owner.popTempValue()};a.prototype.eval_less=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.set_int(a.datab.data?1:0);this.owner.popTempValue()};a.prototype.eval_greaterequal=function(a){this.first.get(a);var b=this.owner.pushTempValue();this.second.get(b);a.set_int(a.data>=b.data?1:0);this.owner.popTempValue()};a.prototype.eval_eventvar_exp=function(a){var b=this.eventvar.getValue();cr.is_number(b)?a.set_float(b):a.set_string(b)};cr.expNode=a;b.prototype.is_int=function(){return this.type===cr.exptype.Integer};b.prototype.is_float=function(){return this.type===cr.exptype.Float};b.prototype.is_number=function(){return this.type===cr.exptype.Integer||this.type===cr.exptype.Float};b.prototype.is_string=function(){return this.type===cr.exptype.String};b.prototype.make_int=function(){this.is_int()||(this.is_float()?this.data=Math.floor(this.data):this.is_string()&&(this.data=parseInt(this.data,10)),this.type=cr.exptype.Integer)};b.prototype.make_float=function(){this.is_float()||(this.is_string()&&(this.data=parseFloat(this.data)),this.type=cr.exptype.Float)};b.prototype.make_string=function(){this.is_string()||(this.data=this.data.toString(),this.type=cr.exptype.String)};b.prototype.set_int=function(a){this.type=cr.exptype.Integer;this.data=Math.floor(a)};b.prototype.set_float=function(a){this.type=cr.exptype.Float;this.data=a};b.prototype.set_string=function(a){this.type=cr.exptype.String;this.data=a};b.prototype.set_any=function(a){cr.is_number(a)?(this.type=cr.exptype.Float,this.data=a):cr.is_string(a)?(this.type=cr.exptype.String,this.data=a.toString()):(this.type=cr.exptype.Integer,this.data=0)};cr.expvalue=b;cr.exptype={Integer:0,Float:1,String:2}})();cr.system_object=function(a){this.runtime=a;this.waits=[]};cr.system_object.prototype.saveToJSON=function(){var a={},b,d,e,f,h,k,c,r;a.waits=[];var n=a.waits,l;b=0;for(d=this.waits.length;bd?1:0}function d(a,b){n&&a===l&&b===q||(n=RegExp(a,b),l=a,q=b);n.lastIndex=0;return n}function e(){}function f(){}function h(a,b,c){if(a!==u||b!==D||c!==B){var e=d(b,c);g=a.match(e);u=a;D=b;B=c}}var k=cr.system_object.prototype;a.prototype.EveryTick=function(){return!0};a.prototype.OnLayoutStart=function(){return!0};a.prototype.OnLayoutEnd=function(){return!0};a.prototype.Compare=function(a,b,c){return cr.do_cmp(a,b,c)};a.prototype.CompareTime=function(a,b){var c=this.runtime.kahanTime.sum;if(0===a){var d=this.runtime.getCurrentCondition();return!d.extra.CompareTime_executed&&c>=b?d.extra.CompareTime_executed=!0:!1}return cr.do_cmp(c,a,b)};a.prototype.LayerVisible=function(a){return a?a.visible:!1};a.prototype.LayerCmpOpacity=function(a,b,c){return a?cr.do_cmp(100*a.opacity,b,c):!1};a.prototype.Repeat=function(a){var b=this.runtime.getCurrentEventStack(),c=b.current_event,d=b.isModifierAfterCnds(),b=this.runtime.pushLoopStack();if(d)for(d=0;d=c&&!a.stopped;--b)this.runtime.pushCopySol(e.solModifiers),a.index=b,e.retrigger(),this.runtime.popSol(e.solModifiers);else for(;b>=c&&!a.stopped;--b)a.index=b,e.retrigger();else if(d)for(;b<=c&&!a.stopped;++b)this.runtime.pushCopySol(e.solModifiers),a.index=b,e.retrigger(),this.runtime.popSol(e.solModifiers);else for(;b<=c&&!a.stopped;++b)a.index=b,e.retrigger();this.runtime.popLoopStack();return!1};var c=[],r=-1;a.prototype.ForEach=function(a){var b=a.getCurrentSol();r++;c.length===r&&c.push([]);var d=c[r];cr.shallowAssignArray(d,b.getObjects());var e=this.runtime.getCurrentEventStack(),f=e.current_event,g=e.isModifierAfterCnds(),e=this.runtime.pushLoopStack(),l,t,p,q,h,k,n=a.is_contained;if(g)for(g=0,l=d.length;g=c+e?(b.extra.Every_lastTime=c+e,d>=b.extra.Every_lastTime+e&&(b.extra.Every_lastTime=d),b.extra.Every_seconds=a,!0):!1};a.prototype.PickNth=function(a,b){if(!a)return!1;var c=a.getCurrentSol(),d=c.getObjects();b=cr.floor(b);if(0>b||b>=d.length)return!1;c.pick_one(d[b]);a.applySolToContainer();return!0};a.prototype.PickRandom=function(a){if(!a)return!1;var b=a.getCurrentSol(),c=b.getObjects(),d=cr.floor(Math.random()*c.length);if(d>=c.length)return!1;b.pick_one(c[d]);a.applySolToContainer();return!0};a.prototype.CompareVar=function(a,b,c){return cr.do_cmp(a.getValue(),b,c)};a.prototype.IsGroupActive=function(a){return this.runtime.activeGroups[a.toLowerCase()]};a.prototype.IsPreview=function(){return"undefined"!==typeof cr_is_preview};a.prototype.PickAll=function(a){if(!a||!a.instances.length)return!1;a.getCurrentSol().select_all=!0;a.applySolToContainer();return!0};a.prototype.IsMobile=function(){return this.runtime.isMobile};a.prototype.CompareBetween=function(a,b,c){return a>=b&&a<=c};a.prototype.Else=function(){var a=this.runtime.getCurrentEventStack();return a.else_branch_ran?!1:!a.last_event_true};a.prototype.OnLoadFinished=function(){return!0};a.prototype.OnCanvasSnapshot=function(){return!0};a.prototype.EffectsSupported=function(){return!!this.runtime.glwrap};a.prototype.OnSaveComplete=function(){return!0};a.prototype.OnLoadComplete=function(){return!0};a.prototype.OnLoadFailed=function(){return!0};a.prototype.ObjectUIDExists=function(a){return!!this.runtime.getObjectByUID(a)};a.prototype.IsOnPlatform=function(a){var b=this.runtime;switch(a){case 0:return!b.isDomFree&&!b.isNodeWebkit&&!b.isPhoneGap&&!b.isWindows8App&&!b.isWindowsPhone8&&!b.isBlackberry10;case 1:return b.isiOS;case 2:return b.isAndroid;case 3:return b.isWindows8App;case 4:return b.isWindowsPhone8;case 5:return b.isBlackberry10;case 6:return b.isTizen;case 7:return b.isNodeWebkit;case 8:return b.isCocoonJs;case 9:return b.isPhoneGap;case 10:return b.isArcade;case 11:return b.isNodeWebkit;default:return!1}};var n=null,l="",q="";a.prototype.RegexTest=function(a,b,c){return d(b,c).test(a)};var v=[];a.prototype.PickOverlappingPoint=function(a,b,c){if(!a)return!1;var d=a.getCurrentSol(),e=d.getObjects(),f=this.runtime.getCurrentEventStack().current_event.orblock,g=this.runtime.getCurrentCondition(),l,t;d.select_all?(cr.shallowAssignArray(v,e),d.else_instances.length=0,d.select_all=!1,d.instances.length=0):f?(cr.shallowAssignArray(v,d.else_instances),d.else_instances.length=0):(cr.shallowAssignArray(v,e),d.instances.length=0);e=0;for(f=v.length;ea&&(a=0);this.runtime.timescale=a};e.prototype.SetObjectTimescale=function(a,b){var c=b;0>c&&(c=0);if(a){var d=a.getCurrentSol().getObjects(),e,f;e=0;for(f=d.length;ea)){var b,c,d,e=this.runtime.getCurrentEventStack(),f;f=t.length?t.pop():{sols:{},solModifiers:[]};f.deleteme=!1;f.time=this.runtime.kahanTime.sum+a;f.ev=e.current_event;f.actindex=e.actindex+1;a=0;for(b=this.runtime.types_by_index.length;athis.runtime.loop_stack_index||(this.runtime.getCurrentLoop().stopped=!0)};e.prototype.GoToLayoutByName=function(a){if(!this.runtime.isloading&&!this.runtime.changelayout)for(var b in this.runtime.layouts)if(this.runtime.layouts.hasOwnProperty(b)&&cr.equals_nocase(b,a)){this.runtime.changelayout=this.runtime.layouts[b];break}};e.prototype.RestartLayout=function(a){if(!this.runtime.isloading&&!this.runtime.changelayout&&this.runtime.running_layout){this.runtime.changelayout=this.runtime.running_layout;var b,c;a=0;for(b=this.runtime.allGroups.length;a=a||0>=b||this.runtime.setSize(a,b)};e.prototype.SetLayoutEffectEnabled=function(a,b){if(this.runtime.running_layout&&this.runtime.glwrap){var c=this.runtime.running_layout.getEffectByName(b);if(c){var d=1===a;c.active!=d&&(c.active=d,this.runtime.running_layout.updateActiveEffects(),this.runtime.redraw=!0)}}};e.prototype.SetLayerEffectEnabled=function(a,b,c){a&&this.runtime.glwrap&&(c=a.getEffectByName(c))&&(b=1===b,c.active!=b&&(c.active=b,a.updateActiveEffects(),this.runtime.redraw=!0))};e.prototype.SetLayoutEffectParam=function(a,b,c){if(this.runtime.running_layout&&this.runtime.glwrap&&(a=this.runtime.running_layout.getEffectByName(a))){var d=this.runtime.running_layout.effect_params[a.index];b=Math.floor(b);0>b||b>=d.length||(1===this.runtime.glwrap.getProgramParameterType(a.shaderindex,b)&&(c/=100),d[b]!==c&&(d[b]=c,a.active&&(this.runtime.redraw=!0)))}};e.prototype.SetLayerEffectParam=function(a,b,c,d){a&&this.runtime.glwrap&&(b=a.getEffectByName(b))&&(a=a.effect_params[b.index],c=Math.floor(c),0>c||c>=a.length||(1===this.runtime.glwrap.getProgramParameterType(b.shaderindex,c)&&(d/=100),a[c]!==d&&(a[c]=d,b.active&&(this.runtime.redraw=!0))))};e.prototype.SaveState=function(a){this.runtime.saveToSlot=a};e.prototype.LoadState=function(a){this.runtime.loadFromSlot=a};e.prototype.LoadStateJSON=function(a){this.runtime.loadFromJson=a};k.acts=new e;f.prototype["int"]=function(a,b){cr.is_string(b)?(a.set_int(parseInt(b,10)),isNaN(a.data)&&(a.data=0)):a.set_int(b)};f.prototype["float"]=function(a,b){cr.is_string(b)?(a.set_float(parseFloat(b)),isNaN(a.data)&&(a.data=0)):a.set_float(b)};f.prototype.str=function(a,b){cr.is_string(b)?a.set_string(b):a.set_string(b.toString())};f.prototype.len=function(a,b){a.set_int(b.length||0)};f.prototype.random=function(a,b,c){void 0===c?a.set_float(Math.random()*b):a.set_float(Math.random()*(c-b)+b)};f.prototype.sqrt=function(a,b){a.set_float(Math.sqrt(b))};f.prototype.abs=function(a,b){a.set_float(Math.abs(b))};f.prototype.round=function(a,b){a.set_int(Math.round(b))};f.prototype.floor=function(a,b){a.set_int(Math.floor(b))};f.prototype.ceil=function(a,b){a.set_int(Math.ceil(b))};f.prototype.sin=function(a,b){a.set_float(Math.sin(cr.to_radians(b)))};f.prototype.cos=function(a,b){a.set_float(Math.cos(cr.to_radians(b)))};f.prototype.tan=function(a,b){a.set_float(Math.tan(cr.to_radians(b)))};f.prototype.asin=function(a,b){a.set_float(cr.to_degrees(Math.asin(b)))};f.prototype.acos=function(a,b){a.set_float(cr.to_degrees(Math.acos(b)))};f.prototype.atan=function(a,b){a.set_float(cr.to_degrees(Math.atan(b)))};f.prototype.exp=function(a,b){a.set_float(Math.exp(b))};f.prototype.ln=function(a,b){a.set_float(Math.log(b))};f.prototype.log10=function(a,b){a.set_float(Math.log(b)/Math.LN10)};f.prototype.max=function(a){var b=arguments[1],c,d;c=2;for(d=arguments.length;carguments[c]&&(b=arguments[c]);a.set_float(b)};f.prototype.dt=function(a){a.set_float(this.runtime.dt)};f.prototype.timescale=function(a){a.set_float(this.runtime.timescale)};f.prototype.wallclocktime=function(a){a.set_float((Date.now()-this.runtime.start_time)/1E3)};f.prototype.time=function(a){a.set_float(this.runtime.kahanTime.sum)};f.prototype.tickcount=function(a){a.set_int(this.runtime.tickcount)};f.prototype.objectcount=function(a){a.set_int(this.runtime.objectcount)};f.prototype.fps=function(a){a.set_int(this.runtime.fps)};f.prototype.loopindex=function(a,b){var c,d,e;if(this.runtime.loop_stack.length)if(b){d=0;for(e=this.runtime.loop_stack.length;dd?a.set_float(d):a.set_float(b)};f.prototype.layerscale=function(a,b){var c=this.runtime.getLayer(b);c?a.set_float(c.scale):a.set_float(0)};f.prototype.layeropacity=function(a,b){var c=this.runtime.getLayer(b);c?a.set_float(100*c.opacity):a.set_float(0)};f.prototype.layerscalerate=function(a,b){var c=this.runtime.getLayer(b);c?a.set_float(c.zoomRate):a.set_float(0)};f.prototype.layerparallaxx=function(a,b){var c=this.runtime.getLayer(b);c?a.set_float(100*c.parallaxX):a.set_float(0)};f.prototype.layerparallaxy=function(a,b){var c=this.runtime.getLayer(b);c?a.set_float(100*c.parallaxY):a.set_float(0)};f.prototype.layoutscale=function(a){this.runtime.running_layout?a.set_float(this.runtime.running_layout.scale):a.set_float(0)};f.prototype.layoutangle=function(a){a.set_float(cr.to_degrees(this.runtime.running_layout.angle))};f.prototype.layerangle=function(a,b){var c=this.runtime.getLayer(b);c?a.set_float(cr.to_degrees(c.angle)):a.set_float(0)};f.prototype.layoutwidth=function(a){a.set_int(this.runtime.running_layout.width)};f.prototype.layoutheight=function(a){a.set_int(this.runtime.running_layout.height)};f.prototype.find=function(a,b,c){cr.is_string(b)&&cr.is_string(c)?a.set_int(b.search(RegExp(cr.regexp_escape(c),"i"))):a.set_int(-1)};f.prototype.left=function(a,b,c){a.set_string(cr.is_string(b)?b.substr(0,c):"")};f.prototype.right=function(a,b,c){a.set_string(cr.is_string(b)?b.substr(b.length-c):"")};f.prototype.mid=function(a,b,c,d){a.set_string(cr.is_string(b)?b.substr(c,d):"")};f.prototype.tokenat=function(a,b,c,d){cr.is_string(b)&&cr.is_string(d)?(b=b.split(d),c=cr.floor(c),0>c||c>=b.length?a.set_string(""):a.set_string(b[c])):a.set_string("")};f.prototype.tokencount=function(a,b,c){cr.is_string(b)&&b.length?a.set_int(b.split(c).length):a.set_int(0)};f.prototype.replace=function(a,b,c,d){cr.is_string(b)&&cr.is_string(c)&&cr.is_string(d)?a.set_string(b.replace(RegExp(cr.regexp_escape(c),"gi"),d)):a.set_string(cr.is_string(b)?b:"")};f.prototype.trim=function(a,b){a.set_string(cr.is_string(b)?b.trim():"")};f.prototype.pi=function(a){a.set_float(cr.PI)};f.prototype.layoutname=function(a){this.runtime.running_layout?a.set_string(this.runtime.running_layout.name):a.set_string("")};f.prototype.renderer=function(a){a.set_string(this.runtime.gl?"webgl":"canvas2d")};f.prototype.anglediff=function(a,b,c){a.set_float(cr.to_degrees(cr.angleDiff(cr.to_radians(b),cr.to_radians(c))))};f.prototype.choose=function(a){var b=cr.floor(Math.random()*(arguments.length-1));a.set_any(arguments[b+1])};f.prototype.rgb=function(a,b,c,d){a.set_int(cr.RGB(b,c,d))};f.prototype.projectversion=function(a){a.set_string(this.runtime.versionstr)};f.prototype.anglelerp=function(a,b,c,d){b=cr.to_radians(b);c=cr.to_radians(c);var e=cr.angleDiff(b,c);cr.angleClockwise(c,b)?a.set_float(cr.to_clamped_degrees(b+e*d)):a.set_float(cr.to_clamped_degrees(b-e*d))};f.prototype.anglerotate=function(a,b,c,d){b=cr.to_radians(b);c=cr.to_radians(c);d=cr.to_radians(d);a.set_float(cr.to_clamped_degrees(cr.angleRotate(b,c,d)))};f.prototype.zeropad=function(a,b,c){var d=0>b?"-":"";0>b&&(b=-b);c-=b.toString().length;for(var e=0;ef||f>=g.length?a.set_string(""):a.set_string(g[f])};f.prototype.infinity=function(a){a.set_float(Infinity)};k.exps=new f;k.runWaits=function(){var a,b,c,d,e,f,g=this.runtime.getCurrentEventStack();a=0;for(c=this.waits.length;athis.runtime.kahanTime.sum)){g.current_event=d.ev;g.actindex=d.actindex;g.cndindex=0;for(b in d.sols)d.sols.hasOwnProperty(b)&&(e=this.runtime.types_by_index[parseInt(b,10)].getCurrentSol(),f=d.sols[b],e.select_all=f.sa,cr.shallowAssignArray(e.instances,f.insts),e=f,e.insts.length=0,p.push(e));d.ev.resume_actions_and_subevents();this.runtime.clearSol(d.solModifiers);d.deleteme=!0}b=a=0;for(c=this.waits.length;aa.viewRight||b.top>a.viewBottom)},r.IsOutsideLayout=function(){this.update_bbox();var a=this.bbox,b=this.runtime.running_layout;return 0>a.right||0>a.bottom||a.left>b.width||a.top>b.height},r.PickDistance=function(a,b,c){var d=this.getCurrentSol(),e=d.getObjects();if(!e.length)return!1;var f=e[0],h=f,k=cr.distanceTo(f.x,f.y,b,c),n,r,m;n=1;for(r=e.length;nk)k=m,h=f;d.pick_one(h);return!0},n.SetX=function(a){this.x!==a&&(this.x=a,this.set_bbox_changed())},n.SetY=function(a){this.y!==a&&(this.y=a,this.set_bbox_changed())},n.SetPos=function(a,b){if(this.x!==a||this.y!==b)this.x=a,this.y=b,this.set_bbox_changed()},n.SetPosToObject=function(a,b){var c=a.getPairedInstance(this);if(c){var d;c.getImagePoint?(d=c.getImagePoint(b,!0),c=c.getImagePoint(b,!1)):(d=c.x,c=c.y);if(this.x!==d||this.y!==c)this.x=d,this.y=c,this.set_bbox_changed()}},n.MoveForward=function(a){0!==a&&(this.x+=Math.cos(this.angle)*a,this.y+=Math.sin(this.angle)*a,this.set_bbox_changed())},n.MoveAtAngle=function(a,b){0!==b&&(this.x+=Math.cos(cr.to_radians(a))*b,this.y+=Math.sin(cr.to_radians(a))*b,this.set_bbox_changed())},b.X=function(a){a.set_float(this.x)},b.Y=function(a){a.set_float(this.y)},b.dt=function(a){a.set_float(this.runtime.getDt(this))});f&&(r.CompareWidth=function(a,b){return cr.do_cmp(this.width,a,b)},r.CompareHeight=function(a,b){return cr.do_cmp(this.height,a,b)},n.SetWidth=function(a){this.width!==a&&(this.width=a,this.set_bbox_changed())},n.SetHeight=function(a){this.height!==a&&(this.height=a,this.set_bbox_changed())},n.SetSize=function(a,b){if(this.width!==a||this.height!==b)this.width=a,this.height=b,this.set_bbox_changed()},b.Width=function(a){a.set_float(this.width)},b.Height=function(a){a.set_float(this.height)},b.BBoxLeft=function(a){this.update_bbox();a.set_float(this.bbox.left)},b.BBoxTop=function(a){this.update_bbox();a.set_float(this.bbox.top)},b.BBoxRight=function(a){this.update_bbox();a.set_float(this.bbox.right)},b.BBoxBottom=function(a){this.update_bbox();a.set_float(this.bbox.bottom)});h&&(r.AngleWithin=function(a,b){return cr.angleDiff(this.angle,cr.to_radians(b))<=cr.to_radians(a)},r.IsClockwiseFrom=function(a){return cr.angleClockwise(this.angle,cr.to_radians(a))},r.IsBetweenAngles=function(a,b){var c=cr.to_clamped_radians(a),d=cr.to_clamped_radians(b),e=cr.clamp_angle(this.angle);return cr.angleClockwise(d,c)?cr.angleClockwise(e,c)&&!cr.angleClockwise(e,d):!(!cr.angleClockwise(e,c)&&cr.angleClockwise(e,d))},n.SetAngle=function(a){a=cr.to_radians(cr.clamp_angle_degrees(a));isNaN(a)||this.angle===a||(this.angle=a,this.set_bbox_changed())},n.RotateClockwise=function(a){0===a||isNaN(a)||(this.angle+=cr.to_radians(a),this.angle=cr.clamp_angle(this.angle),this.set_bbox_changed())},n.RotateCounterclockwise=function(a){0===a||isNaN(a)||(this.angle-=cr.to_radians(a),this.angle=cr.clamp_angle(this.angle),this.set_bbox_changed())},n.RotateTowardAngle=function(a,b){var c=cr.angleRotate(this.angle,cr.to_radians(b),cr.to_radians(a));isNaN(c)||this.angle===c||(this.angle=c,this.set_bbox_changed())},n.RotateTowardPosition=function(a,b,c){b=Math.atan2(c-this.y,b-this.x);a=cr.angleRotate(this.angle,b,cr.to_radians(a));isNaN(a)||this.angle===a||(this.angle=a,this.set_bbox_changed())},n.SetTowardPosition=function(a,b){var c=Math.atan2(b-this.y,a-this.x);isNaN(c)||this.angle===c||(this.angle=c,this.set_bbox_changed())},b.Angle=function(a){a.set_float(cr.to_clamped_degrees(this.angle))});d||(r.CompareInstanceVar=function(a,b,c){return cr.do_cmp(this.instance_vars[a],b,c)},r.IsBoolInstanceVarSet=function(a){return this.instance_vars[a]},r.PickInstVarHiLow=function(a,b){var c=this.getCurrentSol(),d=c.getObjects();if(!d.length)return!1;var e=d[0],f=e,h=e.instance_vars[b],k,n,r;k=1;for(n=d.length;kh)h=r,f=e;c.pick_one(f);return!0},r.PickByUID=function(a){var b,c,d,e,f;if(this.runtime.getCurrentCondition().inverted){f=this.getCurrentSol();if(f.select_all)for(f.select_all=!1,f.instances.length=0,f.else_instances.length=0,d=this.instances,b=0,c=d.length;ba?a=0:1e.layer.index||d.layer.index===e.layer.index&&d.get_zindex()>e.get_zindex())e=d}else if(d.layer.indexc)){var d=1===a;this.active_effect_flags[c]!==d&&(this.active_effect_flags[c]=d,this.updateActiveEffects(),this.runtime.redraw=!0)}}},n.SetEffectParam=function(a,b,c){if(this.runtime.glwrap){var d=this.type.getEffectIndexByName(a);0>d||(a=this.type.effect_types[d],d=this.effect_params[d],b=Math.floor(b),0>b||b>=d.length||(1===this.runtime.glwrap.getProgramParameterType(a.shaderindex,b)&&(c/=100),d[b]!==c&&(d[b]=c,a.active&&(this.runtime.redraw=!0))))}})};cr.set_bbox_changed=function(){this.bbox_changed=!0;this.runtime.redraw=!0;var a,b;a=0;for(b=this.bbox_changed_callbacks.length;athis.bbox.right&&(a=this.bbox.left,this.bbox.left=this.bbox.right,this.bbox.right=a);this.bbox.top>this.bbox.bottom&&(a=this.bbox.top,this.bbox.top=this.bbox.bottom,this.bbox.bottom=a);this.bbox_changed=!1}};cr.inst_contains_pt=function(a,b){return this.bbox.contains_pt(a,b)&&this.bquad.contains_pt(a,b)?this.collision_poly&&!this.collision_poly.is_empty()?(this.collision_poly.cache_poly(this.width,this.height,this.angle),this.collision_poly.contains_pt(a-this.x,b-this.y)):!0:!1};cr.inst_get_iid=function(){this.type.updateIIDs();return this.iid};cr.inst_get_zindex=function(){this.layer.updateZIndices();return this.zindex};cr.inst_updateActiveEffects=function(){this.active_effect_types.length=0;var a,b;a=0;for(b=this.active_effect_flags.length;ad;case 5:return a>=d;default:return!1}};cr.shaders={};cr.shaders.warpripple={src:"varying mediump vec2 vTex;\nuniform lowp sampler2D samplerFront;\nuniform mediump float seconds;\nuniform mediump float pixelWidth;\nuniform mediump float layerScale;\nuniform mediump float freq;\nuniform mediump float amp;\nuniform mediump float speed;\nconst mediump float PI = 3.1415926;\nvoid main(void)\n{\nmediump vec2 p = vTex;\nmediump vec2 tex = vTex * 2.0 - 1.0;\nmediump float d = length(tex);\nmediump float a = atan(tex.y, tex.x);\nd += sin((d * 2.0 * PI) * freq / layerScale / (pixelWidth * 750.0) + (seconds * speed)) * amp * (pixelWidth * 750.0) * layerScale;\ntex.x = cos(a) * d;\ntex.y = sin(a) * d;\ntex = (tex + 1.0) / 2.0;\ngl_FragColor = texture2D(samplerFront, tex);\n}",extendBoxHorizontal:50,extendBoxVertical:50,crossSampling:!1,animated:!0,parameters:[["freq",0,0],["amp",0,1],["speed",0,0]]};cr.plugins_.Audio=function(a){this.runtime=a};(function(){function a(a){a=Math.pow(10,a/20);0>a&&(a=0);1a&&(a=0);1b&&(b=0);1c&&(c=0);1b&&(b=0),1b&&(b=0);1b&&(b=0);1b&&(b=0);1b&&(b=0);1a&&(a=0.01);this.preGain.gain.value=a;this.postGain.gain.value=Math.pow(1/a,0.6)*b};D.prototype.shape=function(a,b,c){var d=1.05*c*b-b;c=0>a?-1:1;a=0>a?-a:a;b=af;++f)e=f/32768,e=this.shape(e,c,d),this.curve[32768+f]=e,this.curve[32768-f-1]=-e};D.prototype.connectTo=function(a){this.wetNode.disconnect();this.wetNode.connect(a);this.dryNode.disconnect();this.dryNode.connect(a)};D.prototype.remove=function(){this.inputNode.disconnect();this.preGain.disconnect();this.waveShaper.disconnect();this.postGain.disconnect();this.wetNode.disconnect();this.dryNode.disconnect()};D.prototype.getInputNode=function(){return this.inputNode};D.prototype.setParam=function(a,b,c,d){switch(a){case 0:b/=100,0>b&&(b=0),1e&&(e=-e),this.peakthis.speeds.length||this.speeds.shift(),this.speeds.push(a),this.lastX=this.obj.x,this.lastY=this.obj.y)};m.prototype.getSpeed=function(){if(!this.speeds.length)return 0;var a,b,c=0;a=0;for(b=this.speeds.length;athis.buffer.bufferObject.duration:this.instanceObject.ended;case V:return this.pgended;case aa:!0}return!0};x.prototype.canBeRecycled=function(){return this.fresh||this.stopped?!0:this.hasEnded()};x.prototype.setPannerEnabled=function(a){J===E&&(!this.pannerEnabled&&a?(this.pannerNode||(this.pannerNode=I.createPanner(),this.pannerNode.panningModel="number"===typeof this.pannerNode.panningModel?ha:["equalpower","HRTF","soundfield"][ha],this.pannerNode.distanceModel="number"===typeof this.pannerNode.distanceModel?ia:["linear","inverse","exponential"][ia],this.pannerNode.refDistance=ka,this.pannerNode.maxDistance=la,this.pannerNode.rolloffFactor=ma),this.gainNode.disconnect(),this.gainNode.connect(this.pannerNode),this.pannerNode.connect(d(this.tag)),this.pannerEnabled=!0):this.pannerEnabled&&!a&&(this.pannerNode.disconnect(),this.gainNode.disconnect(),this.gainNode.connect(d(this.tag)),this.pannerEnabled=!1))};x.prototype.setPan=function(a,b,c,d,e,f){this.pannerEnabled&&J===E&&(this.pannerNode.setPosition(a,b,0),this.pannerNode.setOrientation(Math.cos(cr.to_radians(c)),Math.sin(cr.to_radians(c)),0),this.pannerNode.coneInnerAngle=d,this.pannerNode.coneOuterAngle=e,this.pannerNode.coneOuterGain=f,this.panX=a,this.panY=b,this.panAngle=c,this.panConeInner=d,this.panConeOuter=e,this.panConeOuterGain=f)};x.prototype.setObject=function(a){this.pannerEnabled&&J===E&&(this.objectTracker||(this.objectTracker=new m),this.objectTracker.setObject(a))};x.prototype.tick=function(a){if(this.pannerEnabled&&J===E&&this.objectTracker&&this.objectTracker.hasObject()&&this.isPlaying()){this.objectTracker.tick(a);a=this.objectTracker.obj;var b=cr.rotatePtAround(a.x,a.y,-a.layer.getAngle(),Z,$,!0),c=cr.rotatePtAround(a.x,a.y,-a.layer.getAngle(),Z,$,!1);this.pannerNode.setPosition(b,c,0);b=0;"undefined"!==typeof this.objectTracker.obj.angle&&(b=a.angle-a.layer.getAngle(),this.pannerNode.setOrientation(Math.cos(b),Math.sin(b),0));this.pannerNode.setVelocity(this.objectTracker.getVelocityX(),this.objectTracker.getVelocityY(),0)}};x.prototype.play=function(a,b,c){var d=this.instanceObject;this.looping=a;this.volume=b;c=c||0;switch(this.myapi){case S:1!==d.playbackRate&&(d.playbackRate=1);d.volume!==b*Y&&(d.volume=b*Y);d.loop!==a&&(d.loop=a);d.muted&&(d.muted=!1);if(d.currentTime!==c)try{d.currentTime=c}catch(e){}this.instanceObject.play();break;case E:this.muted=!1;this.mutevol=1;if(this.buffer.myapi===E)this.fresh||(this.instanceObject=I.createBufferSource(),this.instanceObject.buffer=this.buffer.bufferObject,this.instanceObject.connect(this.gainNode)),this.instanceObject.loop=a,this.gainNode.gain.value=b*Y,0===c?h(this.instanceObject):k(this.instanceObject,c,this.getDuration());else{1!==d.playbackRate&&(d.playbackRate=1);d.loop!==a&&(d.loop=a);this.gainNode.gain.value=b*Y;if(d.currentTime!==c)try{d.currentTime=c}catch(f){}d.play()}break;case V:(!this.fresh&&this.stopped||0!==c)&&d.seekTo(c);d.play();this.pgended=!1;break;case aa:N.isDirectCanvas?AppMobi.context.playSound(this.src):AppMobi.player.playSound(this.src)}this.playbackRate=1;this.startTime=N.kahanTime.sum-c;this.is_paused=this.stopped=this.fresh=!1};x.prototype.stop=function(){switch(this.myapi){case S:this.instanceObject.paused||this.instanceObject.pause();break;case E:this.buffer.myapi===E?c(this.instanceObject):this.instanceObject.paused||this.instanceObject.pause();break;case V:this.instanceObject.stop()}this.stopped=!0;this.is_paused=!1};x.prototype.pause=function(){if(!(this.fresh||this.stopped||this.hasEnded()||this.is_paused)){switch(this.myapi){case S:this.instanceObject.paused||this.instanceObject.pause();break;case E:this.buffer.myapi===E?(this.resume_position=this.getPlaybackTime(),this.looping&&(this.resume_position%=this.getDuration()),c(this.instanceObject)):this.instanceObject.paused||this.instanceObject.pause();break;case V:this.instanceObject.pause()}this.is_paused=!0}};x.prototype.resume=function(){if(!this.fresh&&!this.stopped&&!this.hasEnded()&&this.is_paused){switch(this.myapi){case S:this.instanceObject.play();break;case E:this.buffer.myapi===E?(this.instanceObject=I.createBufferSource(),this.instanceObject.buffer=this.buffer.bufferObject,this.instanceObject.connect(this.gainNode),this.instanceObject.loop=this.looping,this.gainNode.gain.value=Y*this.volume*this.mutevol,this.startTime=N.kahanTime.sum-this.resume_position,k(this.instanceObject,this.resume_position,this.getDuration())):this.instanceObject.play();break;case V:this.instanceObject.play()}this.is_paused=!1}};x.prototype.seek=function(a){if(!(this.fresh||this.stopped||this.hasEnded()))switch(this.myapi){case S:try{this.instanceObject.currentTime=a}catch(b){}break;case E:if(this.buffer.myapi===E)this.is_paused?this.resume_position=a:(this.pause(),this.resume_position=a,this.resume());else try{this.instanceObject.currentTime=a}catch(c){}}};x.prototype.reconnect=function(a){this.myapi===E&&(this.pannerEnabled?(this.pannerNode.disconnect(),this.pannerNode.connect(a)):(this.gainNode.disconnect(),this.gainNode.connect(a)))};x.prototype.getDuration=function(){switch(this.myapi){case S:if("undefined"!==typeof this.instanceObject.duration)return this.instanceObject.duration;break;case E:return this.buffer.bufferObject.duration;case V:return this.instanceObject.getDuration()}return 0};x.prototype.getPlaybackTime=function(){var a=this.getDuration(),b=0;switch(this.myapi){case S:"undefined"!==typeof this.instanceObject.currentTime&&(b=this.instanceObject.currentTime);break;case E:if(this.buffer.myapi===E){if(this.is_paused)return this.resume_position;b=N.kahanTime.sum-this.startTime}else"undefined"!==typeof this.instanceObject.currentTime&&(b=this.instanceObject.currentTime)}!this.looping&&b>a&&(b=a);return b};x.prototype.isPlaying=function(){return!this.is_paused&&!this.fresh&&!this.stopped&&!this.hasEnded()};x.prototype.setVolume=function(a){this.volume=a;this.updateVolume()};x.prototype.updateVolume=function(){var a=this.volume*Y;switch(this.myapi){case S:this.instanceObject.volume&&this.instanceObject.volume!==a&&(this.instanceObject.volume=a);break;case E:this.gainNode.gain.value=a*this.mutevol}};x.prototype.getVolume=function(){return this.volume};x.prototype.doSetMuted=function(a){switch(this.myapi){case S:this.instanceObject.muted!==!!a&&(this.instanceObject.muted=!!a);break;case E:this.mutevol=a?0:1,this.gainNode.gain.value=Y*this.volume*this.mutevol}};x.prototype.setMuted=function(a){this.is_muted=!!a;this.doSetMuted(this.is_muted||this.is_silent)};x.prototype.setSilent=function(a){this.is_silent=!!a;this.doSetMuted(this.is_muted||this.is_silent)};x.prototype.setLooping=function(a){this.looping=a;switch(this.myapi){case S:this.instanceObject.loop!==!!a&&(this.instanceObject.loop=!!a);break;case E:this.instanceObject.loop!==!!a&&(this.instanceObject.loop=!!a)}};x.prototype.setPlaybackRate=function(a){this.playbackRate=a;this.updatePlaybackRate()};x.prototype.updatePlaybackRate=function(){var a=this.playbackRate;if(1===fa&&!this.is_music||2===fa)a*=N.timescale;switch(this.myapi){case S:this.instanceObject.playbackRate!==a&&(this.instanceObject.playbackRate=a);break;case E:this.buffer.myapi===E?this.instanceObject.playbackRate.value!==a&&(this.instanceObject.playbackRate.value=a):this.instanceObject.playbackRate!==a&&(this.instanceObject.playbackRate=a)}};x.prototype.setSuspended=function(a){switch(this.myapi){case S:a?this.isPlaying()?(this.instanceObject.pause(),this.resume_me=!0):this.resume_me=!1:this.resume_me&&this.instanceObject.play();break;case E:a?this.isPlaying()?(this.buffer.myapi===E?(this.resume_position=this.getPlaybackTime(),this.looping&&(this.resume_position%=this.getDuration()),c(this.instanceObject)):this.instanceObject.pause(),this.resume_me=!0):this.resume_me=!1:this.resume_me&&(this.buffer.myapi===E?(this.instanceObject=I.createBufferSource(),this.instanceObject.buffer=this.buffer.bufferObject,this.instanceObject.connect(this.gainNode),this.instanceObject.loop=this.looping,this.gainNode.gain.value=Y*this.volume*this.mutevol,this.startTime=N.kahanTime.sum-this.resume_position,k(this.instanceObject,this.resume_position,this.getDuration())):this.instanceObject.play());break;case V:a?this.isPlaying()?(this.instanceObject.pause(),this.resume_me=!0):this.resume_me=!1:this.resume_me&&this.instanceObject.play()}};w.Instance=function(a){this.type=a;N=this.runtime=a.runtime;R=this;this.listenerTracker=null;this.listenerZ=-600;I=null;"undefined"!==typeof AudioContext?(J=E,I=new AudioContext):"undefined"!==typeof webkitAudioContext&&(J=E,I=new webkitAudioContext);this.runtime.isiOS&&J===E&&document.addEventListener("touchstart",function(){if(!na){var a=I.createBuffer(1,1,22050),b=I.createBufferSource();b.buffer=a;b.connect(I.destination);h(b);na=!0}},!0);J!==E&&(this.runtime.isPhoneGap?J=V:this.runtime.isAppMobi&&(J=aa));J===V&&(U=location.href,a=U.lastIndexOf("/"),-1"!==b&&(a.playTagWhenReady=b,a.loopWhenReady=d,a.volumeWhenReady=e),null;l=new x(a,b);K.push(l);return l};var M=[];F.prototype.OnEnded=function(a){return cr.equals_nocase(W,a)};F.prototype.PreloadsComplete=function(){var a,b;a=0;for(b=da.length;a",b,!1)}};y.prototype.PreloadByName=function(a,b){if(!T){var c=1===a,d=this.runtime.files_subfolder+b.toLowerCase()+(X?".ogg":".m4a");J===aa?this.runtime.isDirectCanvas?AppMobi.context.loadSound(d):AppMobi.player.loadSound(d):J!==V&&this.getAudioInstance(d,"",c,!1)}};y.prototype.SetPlaybackRate=function(a,b){z(a);0>b&&(b=0);var c,d;c=0;for(d=M.length;cb||b>=ja.length)||(a=a.toLowerCase(),g/=100,0>g&&(g=0),1e&&(e=0),1f&&(f=0),1g&&(g=0),1d&&(d=0);1c&&(c=0),1c&&(c=0),1f&&(f=0),1b||b>=a.length||a[b].setParam(c,d,e,f)))};y.prototype.SetListenerObject=function(a){a&&J===E&&(a=a.getFirstPicked())&&(this.listenerTracker.setObject(a),Z=a.x,$=a.y)};y.prototype.SetListenerZ=function(a){this.listenerZ=a};w.acts=new y;H.prototype.Duration=function(a,b){z(b);M.length?a.set_float(M[0].getDuration()):a.set_float(0)};H.prototype.PlaybackTime=function(a,b){z(b);M.length?a.set_float(M[0].getPlaybackTime()):a.set_float(0)};H.prototype.Volume=function(a,c){z(c);if(M.length){var d=M[0].getVolume();a.set_float(b(d))}else a.set_float(0)};H.prototype.MasterVolume=function(a){a.set_float(Y)};H.prototype.EffectCount=function(a,b){b=b.toLowerCase();var c=null;L.hasOwnProperty(b)&&(c=L[b]);a.set_int(c?c.length:0)};H.prototype.AnalyserFreqBinCount=function(a,b,c){b=b.toLowerCase();c=Math.floor(c);b=Q(b,c);a.set_int(b?b.node.frequencyBinCount:0)};H.prototype.AnalyserFreqBinAt=function(a,b,c,d){b=b.toLowerCase();c=Math.floor(c);d=Math.floor(d);(b=Q(b,c))?0>d||d>=b.node.frequencyBinCount?a.set_float(0):a.set_float(b.freqBins[d]):a.set_float(0)};H.prototype.AnalyserPeakLevel=function(a,b,c){b=b.toLowerCase();c=Math.floor(c);(b=Q(b,c))?a.set_float(b.peak):a.set_float(0)};H.prototype.AnalyserRMSLevel=function(a,b,c){b=b.toLowerCase();c=Math.floor(c);(b=Q(b,c))?a.set_float(b.rms):a.set_float(0)};w.exps=new H})();cr.plugins_.Browser=function(a){this.runtime=a};(function(){function a(){}function b(){}function d(){"undefined"!==typeof jQuery&&k.setSize(jQuery(window).width(),jQuery(window).height())}function e(){}var f=cr.plugins_.Browser.prototype;f.Type=function(a){this.plugin=a;this.runtime=a.runtime};f.Type.prototype.onCreate=function(){};f.Instance=function(a){this.type=a;this.runtime=a.runtime};f.Instance.prototype.onCreate=function(){var a=this;window.addEventListener("resize",function(){a.runtime.trigger(cr.plugins_.Browser.prototype.cnds.OnResize,a)});"undefined"!==typeof navigator.onLine&&(window.addEventListener("online",function(){a.runtime.trigger(cr.plugins_.Browser.prototype.cnds.OnOnline,a)}),window.addEventListener("offline",function(){a.runtime.trigger(cr.plugins_.Browser.prototype.cnds.OnOffline,a)}));"undefined"!==typeof window.applicationCache&&(window.applicationCache.addEventListener("updateready",function(){a.runtime.loadingprogress=1;a.runtime.trigger(cr.plugins_.Browser.prototype.cnds.OnUpdateReady,a)}),window.applicationCache.addEventListener("progress",function(b){a.runtime.loadingprogress=b.loaded/b.total}));this.runtime.isDirectCanvas||(document.addEventListener("appMobi.device.update.available",function(){a.runtime.trigger(cr.plugins_.Browser.prototype.cnds.OnUpdateReady,a)}),document.addEventListener("menubutton",function(){a.runtime.trigger(cr.plugins_.Browser.prototype.cnds.OnMenuButton,a)}),document.addEventListener("searchbutton",function(){a.runtime.trigger(cr.plugins_.Browser.prototype.cnds.OnSearchButton,a)}));this.runtime.addSuspendCallback(function(b){b?a.runtime.trigger(cr.plugins_.Browser.prototype.cnds.OnPageHidden,a):a.runtime.trigger(cr.plugins_.Browser.prototype.cnds.OnPageVisible,a)});this.is_arcade="undefined"!==typeof window.is_scirra_arcade;this.fullscreenOldMarginCss=""};a.prototype.CookiesEnabled=function(){return navigator?navigator.cookieEnabled:!1};a.prototype.IsOnline=function(){return navigator?navigator.onLine:!1};a.prototype.HasJava=function(){return navigator?navigator.javaEnabled():!1};a.prototype.OnOnline=function(){return!0};a.prototype.OnOffline=function(){return!0};a.prototype.IsDownloadingUpdate=function(){return"undefined"===typeof window.applicationCache?!1:window.applicationCache.status===window.applicationCache.DOWNLOADING};a.prototype.OnUpdateReady=function(){return!0};a.prototype.PageVisible=function(){return!this.runtime.isSuspended};a.prototype.OnPageVisible=function(){return!0};a.prototype.OnPageHidden=function(){return!0};a.prototype.OnResize=function(){return!0};a.prototype.IsFullscreen=function(){return!!(document.mozFullScreen||document.webkitIsFullScreen||document.fullScreen||this.runtime.isNodeFullscreen)};a.prototype.OnMenuButton=function(){return!0};a.prototype.OnSearchButton=function(){return!0};a.prototype.IsMetered=function(){var a=navigator.connection||navigator.mozConnection||navigator.webkitConnection;return a?a.metered:!1};a.prototype.IsCharging=function(){var a=navigator.battery||navigator.mozBattery||navigator.webkitBattery;return a?a.charging:!0};f.cnds=new a;b.prototype.Alert=function(a){this.runtime.isDomFree||alert(a.toString())};b.prototype.Close=function(){this.runtime.isCocoonJs?CocoonJS.App.forceToFinish():this.runtime.isNodeWebkit?window.nwgui.App.quit():this.is_arcade||this.runtime.isDomFree||window.close()};b.prototype.Focus=function(){this.runtime.isNodeWebkit?window.nwgui.Window.get().focus():this.is_arcade||this.runtime.isDomFree||window.focus()};b.prototype.Blur=function(){this.runtime.isNodeWebkit?window.nwgui.Window.get().blur():this.is_arcade||this.runtime.isDomFree||window.blur()};b.prototype.GoBack=function(){this.is_arcade||(this.runtime.isDomFree||!window.back)||window.back()};b.prototype.GoForward=function(){this.is_arcade||(this.runtime.isDomFree||!window.forward)||window.forward()};b.prototype.GoHome=function(){this.is_arcade||(this.runtime.isDomFree||!window.home)||window.home()};b.prototype.GoToURL=function(a){this.runtime.isCocoonJs?CocoonJS.App.openURL(a):this.is_arcade||this.runtime.isDomFree||(window.location=a)};b.prototype.GoToURLWindow=function(a,b){this.runtime.isCocoonJs?CocoonJS.App.openURL(a):this.is_arcade||this.runtime.isDomFree||window.open(a,b)};b.prototype.Reload=function(){this.is_arcade||this.runtime.isDomFree||window.location.reload()};var h=!0,k=null;b.prototype.RequestFullScreen=function(a){this.runtime.isDomFree?cr.logexport("[Construct 2] Requesting fullscreen is not supported on this platform - the request has been ignored"):(2<=a&&(a+=1),6===a&&(a=2),this.runtime.isNodeWebkit?this.runtime.isNodeFullscreen||(window.nwgui.Window.get().enterFullscreen(),this.runtime.isNodeFullscreen=!0):document.mozFullScreen||(document.webkitIsFullScreen||document.fullScreen)||(this.fullscreenOldMarginCss=jQuery(this.runtime.canvasdiv).css("margin"),jQuery(this.runtime.canvasdiv).css("margin","0"),window.c2resizestretchmode=0a||a>=this.keyMap.length?!1:this.keyMap[a]};a.prototype.OnKeyCode=function(a){return a===this.triggerKey};a.prototype.OnKeyCodeReleased=function(a){return a===this.triggerKey};e.cnds=new a;e.acts=new function(){};b.prototype.LastKeyCode=function(a){a.set_int(this.triggerKey)};b.prototype.StringFromKeyCode=function(a,b){a.set_string(d(b))};e.exps=new b})();cr.plugins_.NodeWebkit=function(a){this.runtime=a};(function(){function a(){}function b(){}function d(){}var e=!1,f=null,h=null,k=null,c="",r="",n="\\",l=[],q="",v="",t=cr.plugins_.NodeWebkit.prototype;t.Type=function(a){this.plugin=a;this.runtime=a.runtime};t.Type.prototype.onCreate=function(){};t.Instance=function(a){this.type=a;this.runtime=a.runtime};var p=t.Instance.prototype;p.onCreate=function(){e=this.runtime.isNodeWebkit;var a=this;e&&(f=require("path"),h=require("fs"),k=require("child_process"),"win32"!==process.platform&&(n="/"),c=f.dirname(process.execPath)+n,r=(process.env.HOME||process.env.HOMEPATH||process.env.USERPROFILE)+n,window.ondrop=function(b){b.preventDefault();for(var c=0;cb||b>=l.length?a.set_string(""):a.set_string(l[b])};d.prototype.DroppedFile=function(a){a.set_string(q)};d.prototype.ChosenPath=function(a){a.set_string(v)};t.exps=new d})();cr.plugins_.Particles=function(a){this.runtime=a};(function(){function a(a){this.owner=a;this.active=!1;this.angle=this.speed=this.y=this.x=0;this.opacity=1;this.age=this.gs=this.size=this.grow=0;cr.seal(this)}function b(){}function d(){}function e(){}var f=cr.plugins_.Particles.prototype;f.Type=function(a){this.plugin=a;this.runtime=a.runtime};var h=f.Type.prototype;h.onCreate=function(){this.is_family||(this.texture_img=new Image,this.texture_img.src=this.texture_file,this.texture_img.cr_filesize=this.texture_filesize,this.webGL_texture=null,this.runtime.wait_for_textures.push(this.texture_img))};h.onLostWebGLContext=function(){this.is_family||(this.webGL_texture=null)};h.onRestoreWebGLContext=function(){this.is_family||!this.instances.length||this.webGL_texture||(this.webGL_texture=this.runtime.glwrap.loadTexture(this.texture_img,!0,this.runtime.linearSampling,this.texture_pixelformat))};h.loadTextures=function(){this.is_family||(this.webGL_texture||!this.runtime.glwrap)||(this.webGL_texture=this.runtime.glwrap.loadTexture(this.texture_img,!0,this.runtime.linearSampling,this.texture_pixelformat))};h.unloadTextures=function(){this.is_family||(this.instances.length||!this.webGL_texture)||(this.runtime.glwrap.deleteTexture(this.webGL_texture),this.webGL_texture=null)};h.preloadCanvas2D=function(a){a.drawImage(this.texture_img,0,0)};a.prototype.init=function(){var a=this.owner;this.x=a.x-a.xrandom/2+Math.random()*a.xrandom;this.y=a.y-a.yrandom/2+Math.random()*a.yrandom;this.speed=a.initspeed-a.speedrandom/2+Math.random()*a.speedrandom;this.angle=a.angle-a.spraycone/2+Math.random()*a.spraycone;this.opacity=a.initopacity;this.size=a.initsize-a.sizerandom/2+Math.random()*a.sizerandom;this.grow=a.growrate-a.growrandom/2+Math.random()*a.growrandom;this.age=this.gs=0};a.prototype.tick=function(a){var b=this.owner;this.x+=Math.cos(this.angle)*this.speed*a;this.y+=Math.sin(this.angle)*this.speed*a;this.y+=this.gs*a;this.speed+=b.acc*a;this.size+=this.grow*a;this.gs+=b.g*a;this.age+=a;1>this.size?this.active=!1:(0!==b.lifeanglerandom&&(this.angle+=Math.random()*b.lifeanglerandom*a-b.lifeanglerandom*a/2),0!==b.lifespeedrandom&&(this.speed+=Math.random()*b.lifespeedrandom*a-b.lifespeedrandom*a/2),0!==b.lifeopacityrandom&&(this.opacity+=Math.random()*b.lifeopacityrandom*a-b.lifeopacityrandom*a/2,0>this.opacity?this.opacity=0:1=b.destroymode&&this.age>=b.timeout&&(this.active=!1),2===b.destroymode&&0>=this.speed&&(this.active=!1))};a.prototype.draw=function(a){var b=this.owner.opacity*this.opacity;if(0!==b){0===this.owner.destroymode&&(b*=1-this.age/this.owner.timeout);a.globalAlpha=b;var b=this.x-this.size/2,d=this.y-this.size/2;this.owner.runtime.pixel_rounding&&(b=b+0.5|0,d=d+0.5|0);a.drawImage(this.owner.type.texture_img,b,d,this.size,this.size)}};a.prototype.drawGL=function(a){var b=this.owner.opacity*this.opacity;0===this.owner.destroymode&&(b*=1-this.age/this.owner.timeout);var d=this.size,e=d*this.owner.particlescale,f=this.x-d/2,h=this.y-d/2;this.owner.runtime.pixel_rounding&&(f=f+0.5|0,h=h+0.5|0);1>e||0===b||(ea.maxPointSize?(a.setOpacity(b),a.quad(f,h,f+d,h,f+d,h+d,f,h+d)):a.point(this.x,this.y,e,b))};a.prototype.left=function(){return this.x-this.size/2};a.prototype.right=function(){return this.x+this.size/2};a.prototype.top=function(){return this.y-this.size/2};a.prototype.bottom=function(){return this.y+this.size/2};f.Instance=function(a){this.type=a;this.runtime=a.runtime};var h=f.Instance.prototype,k=[];h.onCreate=function(){var a=this.properties;this.rate=a[0];this.spraycone=cr.to_radians(a[1]);this.spraytype=a[2];this.spraying=!0;this.initspeed=a[3];this.initsize=a[4];this.initopacity=a[5]/100;this.growrate=a[6];this.xrandom=a[7];this.yrandom=a[8];this.speedrandom=a[9];this.sizerandom=a[10];this.growrandom=a[11];this.acc=a[12];this.g=a[13];this.lifeanglerandom=a[14];this.lifespeedrandom=a[15];this.lifeopacityrandom=a[16];this.destroymode=a[17];this.timeout=a[18];this.particleCreateCounter=0;this.particlescale=1;this.particleBoxLeft=this.x;this.particleBoxTop=this.y;this.particleBoxRight=this.x;this.particleBoxBottom=this.y;this.add_bbox_changed_callback(function(a){a.bbox.set(a.particleBoxLeft,a.particleBoxTop,a.particleBoxRight,a.particleBoxBottom);a.bquad.set_from_rect(a.bbox);a.bbox_changed=!1});this.recycled||(this.particles=[]);this.runtime.tickMe(this);this.type.loadTextures();if(1===this.spraytype)for(a=0;athis.particleBoxRight&&(this.particleBoxRight=e.right()),e.top()this.particleBoxBottom&&(this.particleBoxBottom=e.bottom()),f++):k.push(e);this.particles.length=f;this.set_bbox_changed();this.first_tick=!1;1===this.spraytype&&0===this.particles.length&&this.runtime.DestroyInstance(this)};h.draw=function(a){var b,d,e,f=this.layer;b=0;for(d=this.particles.length;b=f.viewLeft&&(e.bottom()>=f.viewTop&&e.left()<=f.viewRight&&e.top()<=f.viewBottom)&&e.draw(a)};h.drawGL=function(a){this.particlescale=this.layer.getScale();a.setTexture(this.type.webGL_texture);var b,d,e,f=this.layer;b=0;for(d=this.particles.length;b=f.viewLeft&&(e.bottom()>=f.viewTop&&e.left()<=f.viewRight&&e.top()<=f.viewBottom)&&e.drawGL(a)};b.prototype.IsSpraying=function(){return this.spraying};f.cnds=new b;d.prototype.SetSpraying=function(a){this.spraying=0!==a};d.prototype.SetEffect=function(a){this.compositeOp=cr.effectToCompositeOp(a);cr.setGLBlend(this,a,this.runtime.gl);this.runtime.redraw=!0};d.prototype.SetRate=function(a){this.rate=a;var b;if(1===this.spraytype&&this.first_tick)if(athis.particles.length)for(a-=this.particles.length,b=0;ba.viewRight||b.top>a.viewBottom)this.runtime.glwrap.deleteTexture(this.mytex),this.mycanvas=this.myctx=this.mytex=null}};k.onDestroy=function(){this.mycanvas=this.myctx=null;this.runtime.glwrap&&this.mytex&&this.runtime.glwrap.deleteTexture(this.mytex);this.mytex=null};k.updateFont=function(){this.font=this.fontstyle+" "+this.ptSize.toString()+"pt "+this.facename;this.text_changed=!0;this.runtime.redraw=!0};k.draw=function(a,b){a.font=this.font;a.textBaseline="top";a.fillStyle=this.color;a.globalAlpha=b?1:this.opacity;var c=1;b&&(c=this.layer.getScale(),a.save(),a.scale(c,c));if(this.text_changed||this.width!==this.lastwrapwidth)this.type.plugin.WordWrap(this.text,this.lines,a,this.width,this.wrapbyword),this.text_changed=!1,this.lastwrapwidth=this.width;this.update_bbox();var c=b?0:this.bquad.tlx,d=b?0:this.bquad.tly;this.runtime.pixel_rounding&&(c=c+0.5|0,d=d+0.5|0);0===this.angle||b||(a.save(),a.translate(c,d),a.rotate(this.angle),d=c=0);var e=d+this.height,f=this.pxHeight,f=f+this.line_height_offset*this.runtime.devicePixelRatio,h,k;1===this.valign?d+=Math.max(this.height/2-this.lines.length*f/2,0):2===this.valign&&(d+=Math.max(this.height-this.lines.length*f-2,0));for(k=0;k=e-f);k++);(0!==this.angle||b)&&a.restore();this.last_render_tick=this.runtime.tickcount};k.drawGL=function(a){if(!(1>this.width||1>this.height)){var b=this.text_changed||this.need_text_redraw;this.need_text_redraw=!1;var c=this.layer.getScale(),d=this.layer.getAngle(),e=this.rcTex,f=c*this.width,h=c*this.height,k=Math.ceil(f),n=Math.ceil(h),s=this.runtime.width/2,m=this.runtime.height/2;this.myctx||(this.mycanvas=document.createElement("canvas"),this.mycanvas.width=k,this.mycanvas.height=n,this.lastwidth=k,this.lastheight=n,b=!0,this.myctx=this.mycanvas.getContext("2d"));if(k!==this.lastwidth||n!==this.lastheight)this.mycanvas.width=k,this.mycanvas.height=n,this.mytex&&(a.deleteTexture(this.mytex),this.mytex=null),b=!0;b&&(this.myctx.clearRect(0,0,k,n),this.draw(this.myctx,!0),this.mytex||(this.mytex=a.createEmptyTexture(k,n,this.runtime.linearSampling,this.runtime.isMobile)),a.videoToTexture(this.mycanvas,this.mytex,this.runtime.isMobile));this.lastwidth=k;this.lastheight=n;a.setTexture(this.mytex);a.setOpacity(this.opacity);a.resetModelView();a.translate(-s,-m);a.updateModelView();var r=this.bquad,b=this.layer.layerToCanvas(r.tlx,r.tly,!0),s=this.layer.layerToCanvas(r.tlx,r.tly,!1),m=this.layer.layerToCanvas(r.trx,r.try_,!0),x=this.layer.layerToCanvas(r.trx,r.try_,!1),z=this.layer.layerToCanvas(r.brx,r.bry,!0),G=this.layer.layerToCanvas(r.brx,r.bry,!1),C=this.layer.layerToCanvas(r.blx,r.bly,!0),r=this.layer.layerToCanvas(r.blx,r.bly,!1);if(this.runtime.pixel_rounding||0===this.angle&&0===d)var F=(b+0.5|0)-b,y=(s+0.5|0)-s,b=b+F,s=s+y,m=m+F,x=x+y,z=z+F,G=G+y,C=C+F,r=r+y;0===this.angle&&0===d?(m=b+k,x=s,z=m,G=s+n,C=b,r=G,e.right=1,e.bottom=1):(e.right=f/k,e.bottom=h/n);a.quadTex(b,s,m,x,z,G,C,r,e);a.resetModelView();a.scale(c,c);a.rotateZ(-this.layer.getAngle());a.translate((this.layer.viewLeft+this.layer.viewRight)/-2,(this.layer.viewTop+this.layer.viewBottom)/-2);a.updateModelView();this.last_render_tick=this.runtime.tickcount}};var r=[];h.TokeniseWords=function(a){r.length=0;for(var b="",c,d=0;d=f)b(d);else{if(100>=c.length&&-1===c.indexOf("\n")){var g=e.measureText(c).width;if(g<=f){b(d);d.push(a());d[0].text=c;d[0].width=g;return}}this.WrapText(c,d,e,f,h)}else b(d)};h.WrapText=function(b,c,d,e,f){f&&(this.TokeniseWords(b),b=r);var g="",h,k,B,s=0;for(B=0;B=c.length&&c.push(a()),k=c[s],k.text=g,k.width=d.measureText(g).width,s++,g=""):(h=g,g+=b[B],k=d.measureText(g).width,k>=e&&(s>=c.length&&c.push(a()),k=c[s],k.text=h,k.width=d.measureText(h).width,s++,g=b[B],f||" "!==g||(g="")));g.length&&(s>=c.length&&c.push(a()),k=c[s],k.text=g,k.width=d.measureText(g).width,s++);for(B=s;Ba&&(a=Math.round(1E10*a)/1E10);a=a.toString();this.text!==a&&(this.text=a,this.text_changed=!0,this.runtime.redraw=!0)};e.prototype.AppendText=function(a){cr.is_number(a)&&(a=Math.round(1E10*a)/1E10);if(a=a.toString())this.text+=a,this.text_changed=!0,this.runtime.redraw=!0};e.prototype.SetFontFace=function(a,b){var c="";switch(b){case 1:c="bold";break;case 2:c="italic";break;case 3:c="bold italic"}if(a!==this.facename||c!==this.fontstyle)this.facename=a,this.fontstyle=c,this.updateFont()};e.prototype.SetFontSize=function(a){this.ptSize!==a&&(this.ptSize=a,this.pxHeight=Math.ceil(96*(this.ptSize/72))+4,this.updateFont())};e.prototype.SetFontColor=function(a){a="rgb("+cr.GetRValue(a).toString()+","+cr.GetGValue(a).toString()+","+cr.GetBValue(a).toString()+")";a!==this.color&&(this.color=a,this.need_text_redraw=!0,this.runtime.redraw=!0)};e.prototype.SetWebFont=function(a,b){if(this.runtime.isDomFree)cr.logexport("[Construct 2] Text plugin: 'Set web font' not supported on this platform - the action has been ignored");else{var d=this,e=function(){d.runtime.redraw=!0;d.text_changed=!0};if(c.hasOwnProperty(b)){var f="'"+a+"'";if(this.facename!==f)for(this.facename=f,this.updateFont(),f=1;10>f;f++)setTimeout(e,100*f),setTimeout(e,1E3*f)}else for(f=document.createElement("link"),f.href=b,f.rel="stylesheet",f.type="text/css",f.onload=e,document.getElementsByTagName("head")[0].appendChild(f),c[b]=!0,this.facename="'"+a+"'",this.updateFont(),f=1;10>f;f++)setTimeout(e,100*f),setTimeout(e,1E3*f)}};e.prototype.SetEffect=function(a){this.compositeOp=cr.effectToCompositeOp(a);cr.setGLBlend(this,a,this.runtime.gl);this.runtime.redraw=!0};h.acts=new e;f.prototype.Text=function(a){a.set_string(this.text)};f.prototype.FaceName=function(a){a.set_string(this.facename)};f.prototype.FaceSize=function(a){a.set_int(this.ptSize)};f.prototype.TextWidth=function(a){var b=0,c,d,e;c=0;for(d=this.lines.length;cc-b.time||(b.lasttime=b.time,b.lastx=b.x,b.lasty=b.y,b.time=c,b.x=a.pageX-d.left,b.y=a.pageY-d.top)}}};h.onPointerStart=function(a){if(a.pointerType!==a.MSPOINTER_TYPE_MOUSE){a.preventDefault&&a.preventDefault();var b=this.runtime.isDomFree?k:jQuery(this.runtime.canvas).offset(),c=a.pageX-b.left,b=a.pageY-b.top,d=cr.performance_now();this.trigger_index=this.touches.length;this.trigger_id=a.pointerId;this.touches.push({time:d,x:c,y:b,lasttime:d,lastx:c,lasty:b,id:a.pointerId,startindex:this.trigger_index});this.runtime.trigger(cr.plugins_.Touch.prototype.cnds.OnNthTouchStart,this);this.runtime.trigger(cr.plugins_.Touch.prototype.cnds.OnTouchStart,this);this.curTouchX=c;this.curTouchY=b;this.runtime.trigger(cr.plugins_.Touch.prototype.cnds.OnTouchObject,this)}};h.onPointerEnd=function(a){a.pointerType!==a.MSPOINTER_TYPE_MOUSE&&(a.preventDefault&&a.preventDefault(),a=this.findTouch(a.pointerId),this.trigger_index=0<=a?this.touches[a].startindex:-1,this.trigger_id=0<=a?this.touches[a].id:-1,this.runtime.trigger(cr.plugins_.Touch.prototype.cnds.OnNthTouchEnd,this),this.runtime.trigger(cr.plugins_.Touch.prototype.cnds.OnTouchEnd,this),0<=a&&this.touches.splice(a,1))};h.onTouchMove=function(a){a.preventDefault&&a.preventDefault();var b=cr.performance_now(),c,d,e,f;c=0;for(d=a.changedTouches.length;cb-f.time||(f.lasttime=f.time,f.lastx=f.x,f.lasty=f.y,f.time=b,f.x=e.pageX-h.left,f.y=e.pageY-h.top)}};h.onTouchStart=function(a){a.preventDefault&&a.preventDefault();var b=this.runtime.isDomFree?k:jQuery(this.runtime.canvas).offset(),c=cr.performance_now(),d,e,f,h;d=0;for(e=a.changedTouches.length;da||a>=this.touches.length)return!1;var d=this.touches[a];a=cr.distanceTo(d.x,d.y,d.lastx,d.lasty);var d=(d.time-d.lasttime)/1E3,e=0;0=a+1};f.cnds=new d;e.prototype.TouchCount=function(a){a.set_int(this.touches.length)};e.prototype.X=function(a,b){if(this.touches.length){var c,d,e,f,h;cr.is_undefined(b)?(c=this.runtime.getLayerByNumber(0),d=c.scale,e=c.zoomRate,f=c.parallaxX,h=c.angle,c.scale=this.runtime.running_layout.scale,c.zoomRate=1,c.parallaxX=1,c.angle=this.runtime.running_layout.angle,a.set_float(c.canvasToLayer(this.touches[0].x,this.touches[0].y,!0)),c.scale=d,c.zoomRate=e,c.parallaxX=f,c.angle=h):(c=cr.is_number(b)?this.runtime.getLayerByNumber(b):this.runtime.getLayerByName(b))?a.set_float(c.canvasToLayer(this.touches[0].x,this.touches[0].y,!0)):a.set_float(0)}else a.set_float(0)};e.prototype.XAt=function(a,b,c){b=Math.floor(b);if(0>b||b>=this.touches.length)a.set_float(0);else{var d,e,f,h;cr.is_undefined(c)?(c=this.runtime.getLayerByNumber(0),d=c.scale,e=c.zoomRate,f=c.parallaxX,h=c.angle,c.scale=this.runtime.running_layout.scale,c.zoomRate=1,c.parallaxX=1,c.angle=this.runtime.running_layout.angle,a.set_float(c.canvasToLayer(this.touches[b].x,this.touches[b].y,!0)),c.scale=d,c.zoomRate=e,c.parallaxX=f,c.angle=h):(c=cr.is_number(c)?this.runtime.getLayerByNumber(c):this.runtime.getLayerByName(c))?a.set_float(c.canvasToLayer(this.touches[b].x,this.touches[b].y,!0)):a.set_float(0)}};e.prototype.XForID=function(a,b,c){b=this.findTouch(b);if(0>b)a.set_float(0);else{b=this.touches[b];var d,e,f,h;cr.is_undefined(c)?(c=this.runtime.getLayerByNumber(0),d=c.scale,e=c.zoomRate,f=c.parallaxX,h=c.angle,c.scale=this.runtime.running_layout.scale,c.zoomRate=1,c.parallaxX=1,c.angle=this.runtime.running_layout.angle,a.set_float(c.canvasToLayer(b.x,b.y,!0)),c.scale=d,c.zoomRate=e,c.parallaxX=f,c.angle=h):(c=cr.is_number(c)?this.runtime.getLayerByNumber(c):this.runtime.getLayerByName(c))?a.set_float(c.canvasToLayer(b.x,b.y,!0)):a.set_float(0)}};e.prototype.Y=function(a,b){if(this.touches.length){var c,d,e,f,h;cr.is_undefined(b)?(c=this.runtime.getLayerByNumber(0),d=c.scale,e=c.zoomRate,f=c.parallaxY,h=c.angle,c.scale=this.runtime.running_layout.scale,c.zoomRate=1,c.parallaxY=1,c.angle=this.runtime.running_layout.angle,a.set_float(c.canvasToLayer(this.touches[0].x,this.touches[0].y,!1)),c.scale=d,c.zoomRate=e,c.parallaxY=f,c.angle=h):(c=cr.is_number(b)?this.runtime.getLayerByNumber(b):this.runtime.getLayerByName(b))?a.set_float(c.canvasToLayer(this.touches[0].x,this.touches[0].y,!1)):a.set_float(0)}else a.set_float(0)};e.prototype.YAt=function(a,b,c){b=Math.floor(b);if(0>b||b>=this.touches.length)a.set_float(0);else{var d,e,f,h;cr.is_undefined(c)?(c=this.runtime.getLayerByNumber(0),d=c.scale,e=c.zoomRate,f=c.parallaxY,h=c.angle,c.scale=this.runtime.running_layout.scale,c.zoomRate=1,c.parallaxY=1,c.angle=this.runtime.running_layout.angle,a.set_float(c.canvasToLayer(this.touches[b].x,this.touches[b].y,!1)),c.scale=d,c.zoomRate=e,c.parallaxY=f,c.angle=h):(c=cr.is_number(c)?this.runtime.getLayerByNumber(c):this.runtime.getLayerByName(c))?a.set_float(c.canvasToLayer(this.touches[b].x,this.touches[b].y,!1)):a.set_float(0)}};e.prototype.YForID=function(a,b,c){b=this.findTouch(b);if(0>b)a.set_float(0);else{b=this.touches[b];var d,e,f,h;cr.is_undefined(c)?(c=this.runtime.getLayerByNumber(0),d=c.scale,e=c.zoomRate,f=c.parallaxY,h=c.angle,c.scale=this.runtime.running_layout.scale,c.zoomRate=1,c.parallaxY=1,c.angle=this.runtime.running_layout.angle,a.set_float(c.canvasToLayer(b.x,b.y,!1)),c.scale=d,c.zoomRate=e,c.parallaxY=f,c.angle=h):(c=cr.is_number(c)?this.runtime.getLayerByNumber(c):this.runtime.getLayerByName(c))?a.set_float(c.canvasToLayer(b.x,b.y,!1)):a.set_float(0)}};e.prototype.AbsoluteX=function(a){this.touches.length?a.set_float(this.touches[0].x):a.set_float(0)};e.prototype.AbsoluteXAt=function(a,b){b=Math.floor(b);0>b||b>=this.touches.length?a.set_float(0):a.set_float(this.touches[b].x)};e.prototype.AbsoluteXForID=function(a,b){var c=this.findTouch(b);0>c?a.set_float(0):a.set_float(this.touches[c].x)};e.prototype.AbsoluteY=function(a){this.touches.length?a.set_float(this.touches[0].y):a.set_float(0)};e.prototype.AbsoluteYAt=function(a,b){b=Math.floor(b);0>b||b>=this.touches.length?a.set_float(0):a.set_float(this.touches[b].y)};e.prototype.AbsoluteYForID=function(a,b){var c=this.findTouch(b);0>c?a.set_float(0):a.set_float(this.touches[c].y)};e.prototype.SpeedAt=function(a,b){b=Math.floor(b);if(0>b||b>=this.touches.length)a.set_float(0);else{var c=this.touches[b],d=cr.distanceTo(c.x,c.y,c.lastx,c.lasty),c=(c.time-c.lasttime)/1E3;0===c?a.set_float(0):a.set_float(d/c)}};e.prototype.SpeedForID=function(a,b){var c=this.findTouch(b);if(0>c)a.set_float(0);else{var d=this.touches[c],c=cr.distanceTo(d.x,d.y,d.lastx,d.lasty),d=(d.time-d.lasttime)/1E3;0===d?a.set_float(0):a.set_float(c/d)}};e.prototype.AngleAt=function(a,b){b=Math.floor(b);if(0>b||b>=this.touches.length)a.set_float(0);else{var c=this.touches[b];a.set_float(cr.to_degrees(cr.angleTo(c.lastx,c.lasty,c.x,c.y)))}};e.prototype.AngleForID=function(a,b){var c=this.findTouch(b);0>c?a.set_float(0):(c=this.touches[c],a.set_float(cr.to_degrees(cr.angleTo(c.lastx,c.lasty,c.x,c.y))))};e.prototype.Alpha=function(a){a.set_float(this.getAlpha())};e.prototype.Beta=function(a){a.set_float(this.getBeta())};e.prototype.Gamma=function(a){a.set_float(this.getGamma())};e.prototype.AccelerationXWithG=function(a){a.set_float(this.acc_g_x)};e.prototype.AccelerationYWithG=function(a){a.set_float(this.acc_g_y)};e.prototype.AccelerationZWithG=function(a){a.set_float(this.acc_g_z)};e.prototype.AccelerationX=function(a){a.set_float(this.acc_x)};e.prototype.AccelerationY=function(a){a.set_float(this.acc_y)};e.prototype.AccelerationZ=function(a){a.set_float(this.acc_z)};e.prototype.TouchIndex=function(a){a.set_int(this.trigger_index)};e.prototype.TouchID=function(a){a.set_float(this.trigger_id)};f.exps=new e})();cr.getProjectModel=function(){return[null,"Loader",[[cr.plugins_.Browser,!0,!1,!1,!1,!1,!1,!1,!1,!1],[cr.plugins_.Audio,!0,!1,!1,!1,!1,!1,!1,!1,!1],[cr.plugins_.Keyboard,!0,!1,!1,!1,!1,!1,!1,!1,!1],[cr.plugins_.NodeWebkit,!0,!1,!1,!1,!1,!1,!1,!1,!1],[cr.plugins_.Particles,!1,!0,!0,!1,!0,!0,!0,!0,!0],[cr.plugins_.Text,!1,!0,!0,!0,!0,!0,!0,!0,!1],[cr.plugins_.Touch,!0,!1,!1,!1,!1,!1,!1,!1,!1]],[["t0",cr.plugins_.Browser,!1,[],0,0,null,null,[],!1,!1,0xe7508259f1049,[],[]],["t1",cr.plugins_.Keyboard,!1,[],0,0,null,null,[],!1,!1,8268562079040081,[],[]],["t2",cr.plugins_.Particles,!1,[],0,0,["c2.png",29833,0],null,[],!1,!1,723074460476516,[]],["t3",cr.plugins_.Audio,!1,[],0,0,null,null,[],!1,!1,0x87d7a273e6e76,[],[0,1,1,600,600,1E4,1,5E3,1]],["t4",cr.plugins_.NodeWebkit,!1,[],0,0,null,null,[],!1,!1,0x418c17d077bfa,[],[]],["t5",cr.plugins_.Particles,!1,[],0,0,["nw.png",2403,0],null,[],!1,!1,7413958693205721,[]],["t6",cr.plugins_.Text,!1,[],0,0,null,null,[],!1,!1,0xadbe7ef820faf,[]],["t7",cr.plugins_.Touch,!1,[],0,0,null,null,[],!1,!1,4854103537132793,[],[1]]],[],[["Loader",800,600,!1,"Loader_events",9256725879576544,[["Loader",0,8771806647801429,!0,[0,0,0],!1,1,1,1,!1,1,0,0,[],[]]],[],[]],["Veille",800,600,!1,"Veille_events",306290045656015,[["Veille",0,0x9c56068fd6acc,!0,[0,0,0],!1,1,1,1,!1,1,1,0,[[[400,-75,0,32,320,0,1.5708,1,0,0.5,0,0,[]],2,1,[],[],[5,120,0,250,40,0,-2,40,0,0,8,0,-150,100,2,800,2,0,5]],[[400,700,0,32,320,0,-1.5708,1,0,0.5,0,0,[]],5,7,[],[],[5,120,0,250,40,0,-2,40,0,0,8,0,-150,-100,2,800,2,0,5]],[[579,30,0,200,540,0,0,0.5,0,0,1,0,[]],6,4,[],[],["",1,"bold 12pt Arial","rgb(255,255,255)",1,1,0,0,0]]],[["warpripple","WarpRipple",[1,0.01,-5]]]]],[],[]],["Exit",800,600,!1,"Exit_events",6113943557441396,[["Exit",0,0xb124a9f08ea67,!0,[0,0,0],!1,1,1,1,!1,1,0,0,[],[]]],[],[]]],[["Loader_events",[[1,"NumVeille",0,-2,!1,!1,8964089995488567],[1,"NWfile",1,"",!1,!1,0xe4fbfc76f13ab],[0,null,!1,0x4dfd65888a214,[[-1,cr.system_object.prototype.cnds.OnLayoutStart,null,1,!1,!1,!1,9184290607372780]],[[0,cr.plugins_.Browser.prototype.acts.RequestFullScreen,null,7636186985065621,[[3,5]]]]],[0,null,!1,0x5bb13aa690f60,[[-1,cr.system_object.prototype.cnds.OnLoadFinished,null,1,!1,!1,!1,0x82e0442152500]],[[-1,cr.system_object.prototype.acts.Wait,null,9840344659686878,[[0,[1,0.01]]]],[-1,cr.system_object.prototype.acts.GoToLayout,null,0x5200955dfe05a,[[6,"Veille"]]]]]]],["Veille_events",[[1,"RMS",0,0,!1,!1,8272711222301935],[1,"Restart",0,0,!1,!1,5338948241480027],[0,null,!1,9883295321069700,[[-1,cr.system_object.prototype.cnds.OnLayoutStart,null,1,!1,!1,!1,0xd39cedc82dd33]],[[3,cr.plugins_.Audio.prototype.acts.PlayByName,null,5202980257560176,[[3,1],[1,[2,"Evening_Fall_Harp"]],[3,0],[0,[0,0]],[1,[2,""]]]],[2,cr.plugins_.Particles.prototype.acts.SetRate,null,0xc15240e82c978,[[0,[0,5]]]],[5,cr.plugins_.Particles.prototype.acts.SetRate,null,0x6ff8687e80572,[[0,[0,5]]]],[2,cr.plugins_.Particles.prototype.acts.SetSpraying,null,5500294466648475,[[3,1]]],[5,cr.plugins_.Particles.prototype.acts.SetSpraying,null,4830535051445549,[[3,1]]]]],[0,[!1,"Start"],!1,5670715518694284,[[-1,cr.system_object.prototype.cnds.IsGroupActive,null,0,!1,!1,!1,5670715518694284,[[1,[2,"Start"]]]]],[],[[0,null,!1,8365481790965971,[[-1,cr.system_object.prototype.cnds.Compare,null,0,!1,!1,!1,681938087295472,[[7,[19,cr.system_object.prototype.exps.layeropacity,[[2,"Veille"]]]],[8,0],[7,[0,100]]]]],[[-1,cr.system_object.prototype.acts.SetGroupActive,null,0x939cfc17f135c,[[1,[2,"Start"]],[3,0]]]]],[0,null,!1,0xaccce4249a717,[[-1,cr.system_object.prototype.cnds.Else,null,0,!1,!1,!1,9645145373693224],[-1,cr.system_object.prototype.cnds.EveryTick,null,0,!1,!1,!1,0xc1f6300af58e8]],[[-1,cr.system_object.prototype.acts.SetLayerOpacity,null,0x50fa048c371f6,[[5,[2,"Veille"]],[0,[4,[19,cr.system_object.prototype.exps.layeropacity,[[2,"Veille"]]],[0,1]]]]],[2,cr.plugins_.Particles.prototype.acts.SetInitOpacity,null,0xa82f69b81a210,[[0,[5,[19,cr.system_object.prototype.exps.layeropacity,[[2,"Veille"]]],[0,10]]]]],[5,cr.plugins_.Particles.prototype.acts.SetInitOpacity,null,9087421245893464,[[0,[5,[19,cr.system_object.prototype.exps.layeropacity,[[2,"Veille"]]],[0,10]]]]]]]]],[0,[!1,"Body"],!1,7028360535295121,[[-1,cr.system_object.prototype.cnds.IsGroupActive,null,0,!1,!1,!1,7028360535295121,[[1,[2,"Body"]]]]],[],[[0,null,!1,0x51e8a6e11ca45,[[1,cr.plugins_.Keyboard.prototype.cnds.OnKey,null,1,!1,!1,!1,0xaf5a5a8d1a494,[[9,13]]]],[],[[0,null,!1,6545261610976676,[[6,cr.plugins_.Text.prototype.cnds.IsVisible,null,0,!1,!0,!1,57733252997832]],[[6,cr.plugins_.Text.prototype.acts.SetVisible,null,0x8bbeaf0a9536a,[[3,1]]]]],[0,null,!1,7903978118133453,[[-1,cr.system_object.prototype.cnds.Else,null,0,!1,!1,!1,7093124615072028]],[[6,cr.plugins_.Text.prototype.acts.SetVisible,null,9016231935903996,[[3,0]]]]]]],[0,null,!1,0xc3a65bf4f2a22,[[-1,cr.system_object.prototype.cnds.Every,null,0,!1,!1,!1,7317966506217073,[[0,[1,1]]]]],[[2,cr.plugins_.Particles.prototype.acts.SetRate,null,8632633260751814,[[0,[19,cr.system_object.prototype.exps.clamp,[[18,[16,[7,[4,[20,2,cr.plugins_.Particles.prototype.exps.Rate,!1,null],[20,2,cr.plugins_.Particles.prototype.exps.ParticleCount,!1,null]],[7,[19,cr.system_object.prototype.exps.fps],[0,25]]],[19,cr.system_object.prototype.exps.fps]],[5,[20,2,cr.plugins_.Particles.prototype.exps.Rate,!1,null],[19,cr.system_object.prototype.exps.random,[[0,1],[0,2]]]],[4,[20,2,cr.plugins_.Particles.prototype.exps.Rate,!1,null],[19,cr.system_object.prototype.exps.random,[[0,1],[0,2]]]]],[0,5],[0,25]]]]]],[5,cr.plugins_.Particles.prototype.acts.SetRate,null,6062215411365679,[[0,[19,cr.system_object.prototype.exps.clamp,[[18,[16,[7,[4,[20,5,cr.plugins_.Particles.prototype.exps.Rate,!1,null],[20,5,cr.plugins_.Particles.prototype.exps.ParticleCount,!1,null]],[7,[19,cr.system_object.prototype.exps.fps],[0,25]]],[19,cr.system_object.prototype.exps.fps]],[5,[20,5,cr.plugins_.Particles.prototype.exps.Rate,!1,null],[19,cr.system_object.prototype.exps.random,[[0,1],[0,2]]]],[4,[20,5,cr.plugins_.Particles.prototype.exps.Rate,!1,null],[19,cr.system_object.prototype.exps.random,[[0,1],[0,2]]]]],[0,5],[0,25]]]]]],[6,cr.plugins_.Text.prototype.acts.SetText,null,9405966255769602,[[7,[10,[10,[10,[10,[10,[10,[10,[10,[10,[10,[10,[10,[10,[10,[10,[10,[10,[2,"#"],[20,2,cr.plugins_.Particles.prototype.exps.ParticleCount,!1,null]],[2," +"]],[19,cr.system_object.prototype.exps["int"],[[20,2,cr.plugins_.Particles.prototype.exps.Rate,!1,null]]]],[2,"/sec"]],[19,cr.system_object.prototype.exps.newline]],[19,cr.system_object.prototype.exps.newline]],[2,"FPS:"]],[19,cr.system_object.prototype.exps.fps]],[2," RMS:"]],[7,[19,cr.system_object.prototype.exps.floor,[[6,[23,"RMS"],[0,100]]]],[0,100]]],[19,cr.system_object.prototype.exps.newline]],[19,cr.system_object.prototype.exps.newline]],[2,"#"]],[20,5,cr.plugins_.Particles.prototype.exps.ParticleCount,!1,null]],[2," +"]],[19,cr.system_object.prototype.exps["int"],[[20,5,cr.plugins_.Particles.prototype.exps.Rate,!1,null]]]],[2,"/sec"]]]]]]],[0,null,!1,0x928915e9cd3bb,[[-1,cr.system_object.prototype.cnds.EveryTick,null,0,!1,!1,!1,6917607399799475],[3,cr.plugins_.Audio.prototype.cnds.AdvancedAudioSupported,null,0,!1,!1,!1,9849462572130356]],[[-1,cr.system_object.prototype.acts.SetVar,null,9168531653053006,[[11,"RMS"],[7,[20,3,cr.plugins_.Audio.prototype.exps.AnalyserRMSLevel,!1,null,[[2,"a"],[0,0]]]]]],[2,cr.plugins_.Particles.prototype.acts.SetGravity,null,0xcf6eb9b13cfb,[[0,[6,[23,"RMS"],[1,-2.1]]]]],[5,cr.plugins_.Particles.prototype.acts.SetGravity,null,8351761715539176,[[0,[6,[23,"RMS"],[1,2.1]]]]]]],[0,null,!1,8242976808981609,[[-1,cr.system_object.prototype.cnds.Compare,null,0,!1,!1,!1,5987721542830278,[[7,[19,cr.system_object.prototype.exps.time]],[8,5],[7,[0,140]]]],[-1,cr.system_object.prototype.cnds.CompareVar,null,0,!1,!1,!1,8881592830623784,[[11,"Restart"],[8,0],[7,[0,0]]]]],[[-1,cr.system_object.prototype.acts.SetVar,null,8871831640987063,[[11,"Restart"],[7,[0,1]]]]]],[0,null,!0,0xf980861b0c5a4,[[1,cr.plugins_.Keyboard.prototype.cnds.OnKey,null,1,!1,!1,!1,7240018894178457,[[9,27]]],[7,cr.plugins_.Touch.prototype.cnds.IsInTouch,null,0,!1,!1,!1,0xe41159773516],[-1,cr.system_object.prototype.cnds.CompareVar,null,0,!1,!1,!1,0xa522af7e93f2e,[[11,"Restart"],[8,0],[7,[0,1]]]]],[[-1,cr.system_object.prototype.acts.SetVar,null,0x9f9a93e787142,[[11,"Restart"],[7,[3,[23,"Restart"]]]]],[-1,cr.system_object.prototype.acts.SetGroupActive,null,4593935824136996,[[1,[2,"Start"]],[3,0]]],[-1,cr.system_object.prototype.acts.SetGroupActive,null,7749099240835881,[[1,[2,"Body"]],[3,0]]],[2,cr.plugins_.Particles.prototype.acts.SetSpraying,null,6865126891265061,[[3,0]]],[5,cr.plugins_.Particles.prototype.acts.SetSpraying,null,0x44b2c5ab55ee5,[[3,0]]],[-1,cr.system_object.prototype.acts.SetGroupActive,null,9864700388762392,[[1,[2,"End"]],[3,1]]]]]]],[0,[!1,"End"],!1,0x7309bde3e92e,[[-1,cr.system_object.prototype.cnds.IsGroupActive,null,0,!1,!1,!1,0x7309bde3e92e,[[1,[2,"End"]]]]],[],[[0,null,!1,0x7321b49aa09e1,[[-1,cr.system_object.prototype.cnds.Compare,null,0,!1,!1,!1,4511261893892535,[[7,[19,cr.system_object.prototype.exps.layeropacity,[[2,"Veille"]]]],[8,0],[7,[0,0]]]]],[[-1,cr.system_object.prototype.acts.SetGroupActive,null,4772013783097554,[[1,[2,"End"]],[3,0]]],[3,cr.plugins_.Audio.prototype.acts.StopAll,null,832829439830883]],[[0,null,!1,7259102832799663,[[-1,cr.system_object.prototype.cnds.CompareVar,null,0,!1,!1,!1,0x5012013f72140,[[11,"Restart"],[8,0],[7,[0,0]]]]],[[-1,cr.system_object.prototype.acts.GoToLayoutByName,null,0xe7d6f7283c74b,[[1,[2,"exit"]]]]]],[0,null,!1,8363975973112161,[[-1,cr.system_object.prototype.cnds.Else,null,0,!1,!1,!1,0x60cf0d437e8dc]],[[3,cr.plugins_.Audio.prototype.acts.StopAll,null,5893738282654444],[0,cr.plugins_.Browser.prototype.acts.Reload,null,0x62cf4bced8610]]]]],[0,null,!1,7218792687155007,[[-1,cr.system_object.prototype.cnds.Else,null,0,!1,!1,!1,7503399522701357],[-1,cr.system_object.prototype.cnds.EveryTick,null,0,!1,!1,!1,5378603673816071]],[[-1,cr.system_object.prototype.acts.SetLayerOpacity,null,0xccdd38cda519a,[[5,[2,"Veille"]],[0,[5,[19,cr.system_object.prototype.exps.layeropacity,[[2,"Veille"]]],[0,1]]]]],[3,cr.plugins_.Audio.prototype.acts.SetVolume,null,5245460560361351,[[1,[2,"a"]],[0,[5,[20,3,cr.plugins_.Audio.prototype.exps.Volume,!1,null,[[2,"a"]]],[1,0.25]]]]]]]]],[0,null,!1,0xf82f8373efea5,[[7,cr.plugins_.Touch.prototype.cnds.IsInTouch,null,0,!1,!1,!1,0xb4decc1bcddd3]],[[-1,cr.system_object.prototype.acts.GoToLayoutByName,null,0x59bbef9fef967,[[1,[2,"exit"]]]]]]]],["Exit_events",[[0,null,!1,0xe7e976c27bdd5,[[-1,cr.system_object.prototype.cnds.OnLayoutStart,null,1,!1,!1,!1,7777797351584432]],[[-1,cr.system_object.prototype.acts.Wait,null,5810891606874927,[[0,[1,0.01]]]],[0,cr.plugins_.Browser.prototype.acts.Close,null,82255096705349]]]]]],"",!0,800,600,2,!0,!0,!1,"1.1",0,!0,3,!0,8,!1,[]]}; \ No newline at end of file diff --git a/tests/automation/nw-in-mem/package/evening_fall_harp.ogg b/tests/automation/nw-in-mem/package/evening_fall_harp.ogg new file mode 100644 index 0000000000..003803ce2e Binary files /dev/null and b/tests/automation/nw-in-mem/package/evening_fall_harp.ogg differ diff --git a/tests/automation/nw-in-mem/package/icon-114.png b/tests/automation/nw-in-mem/package/icon-114.png new file mode 100644 index 0000000000..bf56364da4 Binary files /dev/null and b/tests/automation/nw-in-mem/package/icon-114.png differ diff --git a/tests/automation/nw-in-mem/package/icon-128.png b/tests/automation/nw-in-mem/package/icon-128.png new file mode 100644 index 0000000000..523ffd0ff7 Binary files /dev/null and b/tests/automation/nw-in-mem/package/icon-128.png differ diff --git a/tests/automation/nw-in-mem/package/icon-16.png b/tests/automation/nw-in-mem/package/icon-16.png new file mode 100644 index 0000000000..e1b785cc71 Binary files /dev/null and b/tests/automation/nw-in-mem/package/icon-16.png differ diff --git a/tests/automation/nw-in-mem/package/icon-32.png b/tests/automation/nw-in-mem/package/icon-32.png new file mode 100644 index 0000000000..02edf8c2b4 Binary files /dev/null and b/tests/automation/nw-in-mem/package/icon-32.png differ diff --git a/tests/automation/nw-in-mem/package/index.html b/tests/automation/nw-in-mem/package/index.html new file mode 100644 index 0000000000..ea89fa8452 --- /dev/null +++ b/tests/automation/nw-in-mem/package/index.html @@ -0,0 +1,107 @@ + + + + + + Veille + + + + + + + + + + + + + + +
          + + +
          + + + + +
          + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/automation/nw-in-mem/package/jquery-2.0.0.min.js b/tests/automation/nw-in-mem/package/jquery-2.0.0.min.js new file mode 100644 index 0000000000..fbb594e8f3 --- /dev/null +++ b/tests/automation/nw-in-mem/package/jquery-2.0.0.min.js @@ -0,0 +1,6 @@ +/*! jQuery v2.0.0 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license +//@ sourceMappingURL=jquery.min.map +*/ +(function(e,undefined){var t,n,r=typeof undefined,i=e.location,o=e.document,s=o.documentElement,a=e.jQuery,u=e.$,l={},c=[],f="2.0.0",p=c.concat,h=c.push,d=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=f.trim,x=function(e,n){return new x.fn.init(e,n,t)},b=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^-ms-/,N=/-([\da-z])/gi,E=function(e,t){return t.toUpperCase()},S=function(){o.removeEventListener("DOMContentLoaded",S,!1),e.removeEventListener("load",S,!1),x.ready()};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,t,n){var r,i;if(!e)return this;if("string"==typeof e){if(r="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:T.exec(e),!r||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof x?t[0]:t,x.merge(this,x.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:o,!0)),C.test(r[1])&&x.isPlainObject(t))for(r in t)x.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return i=o.getElementById(r[2]),i&&i.parentNode&&(this.length=1,this[0]=i),this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?n.ready(e):(e.selector!==undefined&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return d.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,t,n,r,i,o,s=arguments[0]||{},a=1,u=arguments.length,l=!1;for("boolean"==typeof s&&(l=s,s=arguments[1]||{},a=2),"object"==typeof s||x.isFunction(s)||(s={}),u===a&&(s=this,--a);u>a;a++)if(null!=(e=arguments[a]))for(t in e)n=s[t],r=e[t],s!==r&&(l&&r&&(x.isPlainObject(r)||(i=x.isArray(r)))?(i?(i=!1,o=n&&x.isArray(n)?n:[]):o=n&&x.isPlainObject(n)?n:{},s[t]=x.extend(l,o,r)):r!==undefined&&(s[t]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=a),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){(e===!0?--x.readyWait:x.isReady)||(x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(o,[x]),x.fn.trigger&&x(o).trigger("ready").off("ready")))},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray,isWindow:function(e){return null!=e&&e===e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if("object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(t){return!1}return!0},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:JSON.parse,parseXML:function(e){var t,n;if(!e||"string"!=typeof e)return null;try{n=new DOMParser,t=n.parseFromString(e,"text/xml")}catch(r){t=undefined}return(!t||t.getElementsByTagName("parsererror").length)&&x.error("Invalid XML: "+e),t},noop:function(){},globalEval:function(e){var t,n=eval;e=x.trim(e),e&&(1===e.indexOf("use strict")?(t=o.createElement("script"),t.text=e,o.head.appendChild(t).parentNode.removeChild(t)):n(e))},camelCase:function(e){return e.replace(k,"ms-").replace(N,E)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,s=j(e);if(n){if(s){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(s){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:function(e){return null==e?"":v.call(e)},makeArray:function(e,t){var n=t||[];return null!=e&&(j(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:g.call(t,e,n)},merge:function(e,t){var n=t.length,r=e.length,i=0;if("number"==typeof n)for(;n>i;i++)e[r++]=t[i];else while(t[i]!==undefined)e[r++]=t[i++];return e.length=r,e},grep:function(e,t,n){var r,i=[],o=0,s=e.length;for(n=!!n;s>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,s=j(e),a=[];if(s)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(a[a.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(a[a.length]=r);return p.apply([],a)},guid:1,proxy:function(e,t){var n,r,i;return"string"==typeof t&&(n=e[t],t=e,e=n),x.isFunction(e)?(r=d.call(arguments,2),i=function(){return e.apply(t||this,r.concat(d.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):undefined},access:function(e,t,n,r,i,o,s){var a=0,u=e.length,l=null==n;if("object"===x.type(n)){i=!0;for(a in n)x.access(e,t,a,n[a],!0,o,s)}else if(r!==undefined&&(i=!0,x.isFunction(r)||(s=!0),l&&(s?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(x(e),n)})),t))for(;u>a;a++)t(e[a],n,s?r:r.call(e[a],a,t(e[a],n)));return i?e:l?t.call(e):u?t(e[0],n):o},now:Date.now,swap:function(e,t,n,r){var i,o,s={};for(o in t)s[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=s[o];return i}}),x.ready.promise=function(t){return n||(n=x.Deferred(),"complete"===o.readyState?setTimeout(x.ready):(o.addEventListener("DOMContentLoaded",S,!1),e.addEventListener("load",S,!1))),n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function j(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}t=x(o),function(e,undefined){var t,n,r,i,o,s,a,u,l,c,f,p,h,d,g,m,y="sizzle"+-new Date,v=e.document,b={},w=0,T=0,C=ot(),k=ot(),N=ot(),E=!1,S=function(){return 0},j=typeof undefined,D=1<<31,A=[],L=A.pop,q=A.push,H=A.push,O=A.slice,F=A.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},P="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",R="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=M.replace("w","w#"),$="\\["+R+"*("+M+")"+R+"*(?:([*^$|!~]?=)"+R+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+R+"*\\]",B=":("+M+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",I=RegExp("^"+R+"+|((?:^|[^\\\\])(?:\\\\.)*)"+R+"+$","g"),z=RegExp("^"+R+"*,"+R+"*"),_=RegExp("^"+R+"*([>+~]|"+R+")"+R+"*"),X=RegExp(R+"*[+~]"),U=RegExp("="+R+"*([^\\]'\"]*)"+R+"*\\]","g"),Y=RegExp(B),V=RegExp("^"+W+"$"),G={ID:RegExp("^#("+M+")"),CLASS:RegExp("^\\.("+M+")"),TAG:RegExp("^("+M.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+B),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+R+"*(even|odd|(([+-]|)(\\d*)n|)"+R+"*(?:([+-]|)"+R+"*(\\d+)|))"+R+"*\\)|)","i"),"boolean":RegExp("^(?:"+P+")$","i"),needsContext:RegExp("^"+R+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+R+"*((?:-\\d)?\\d*)"+R+"*\\)|)(?=[^-]|$)","i")},J=/^[^{]+\{\s*\[native \w/,Q=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,et=/'|\\/g,tt=/\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,nt=function(e,t){var n="0x"+t-65536;return n!==n?t:0>n?String.fromCharCode(n+65536):String.fromCharCode(55296|n>>10,56320|1023&n)};try{H.apply(A=O.call(v.childNodes),v.childNodes),A[v.childNodes.length].nodeType}catch(rt){H={apply:A.length?function(e,t){q.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function it(e){return J.test(e+"")}function ot(){var e,t=[];return e=function(n,i){return t.push(n+=" ")>r.cacheLength&&delete e[t.shift()],e[n]=i}}function st(e){return e[y]=!0,e}function at(e){var t=c.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ut(e,t,n,r){var i,o,s,a,u,f,d,g,x,w;if((t?t.ownerDocument||t:v)!==c&&l(t),t=t||c,n=n||[],!e||"string"!=typeof e)return n;if(1!==(a=t.nodeType)&&9!==a)return[];if(p&&!r){if(i=Q.exec(e))if(s=i[1]){if(9===a){if(o=t.getElementById(s),!o||!o.parentNode)return n;if(o.id===s)return n.push(o),n}else if(t.ownerDocument&&(o=t.ownerDocument.getElementById(s))&&m(t,o)&&o.id===s)return n.push(o),n}else{if(i[2])return H.apply(n,t.getElementsByTagName(e)),n;if((s=i[3])&&b.getElementsByClassName&&t.getElementsByClassName)return H.apply(n,t.getElementsByClassName(s)),n}if(b.qsa&&(!h||!h.test(e))){if(g=d=y,x=t,w=9===a&&e,1===a&&"object"!==t.nodeName.toLowerCase()){f=gt(e),(d=t.getAttribute("id"))?g=d.replace(et,"\\$&"):t.setAttribute("id",g),g="[id='"+g+"'] ",u=f.length;while(u--)f[u]=g+mt(f[u]);x=X.test(e)&&t.parentNode||t,w=f.join(",")}if(w)try{return H.apply(n,x.querySelectorAll(w)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(I,"$1"),t,n,r)}o=ut.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},l=ut.setDocument=function(e){var t=e?e.ownerDocument||e:v;return t!==c&&9===t.nodeType&&t.documentElement?(c=t,f=t.documentElement,p=!o(t),b.getElementsByTagName=at(function(e){return e.appendChild(t.createComment("")),!e.getElementsByTagName("*").length}),b.attributes=at(function(e){return e.className="i",!e.getAttribute("className")}),b.getElementsByClassName=at(function(e){return e.innerHTML="
          ",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),b.sortDetached=at(function(e){return 1&e.compareDocumentPosition(c.createElement("div"))}),b.getById=at(function(e){return f.appendChild(e).id=y,!t.getElementsByName||!t.getElementsByName(y).length}),b.getById?(r.find.ID=function(e,t){if(typeof t.getElementById!==j&&p){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},r.filter.ID=function(e){var t=e.replace(tt,nt);return function(e){return e.getAttribute("id")===t}}):(r.find.ID=function(e,t){if(typeof t.getElementById!==j&&p){var n=t.getElementById(e);return n?n.id===e||typeof n.getAttributeNode!==j&&n.getAttributeNode("id").value===e?[n]:undefined:[]}},r.filter.ID=function(e){var t=e.replace(tt,nt);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),r.find.TAG=b.getElementsByTagName?function(e,t){return typeof t.getElementsByTagName!==j?t.getElementsByTagName(e):undefined}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=b.getElementsByClassName&&function(e,t){return typeof t.getElementsByClassName!==j&&p?t.getElementsByClassName(e):undefined},d=[],h=[],(b.qsa=it(t.querySelectorAll))&&(at(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||h.push("\\["+R+"*(?:value|"+P+")"),e.querySelectorAll(":checked").length||h.push(":checked")}),at(function(e){var t=c.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&h.push("[*^$]="+R+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||h.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),h.push(",.*:")})),(b.matchesSelector=it(g=f.webkitMatchesSelector||f.mozMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&at(function(e){b.disconnectedMatch=g.call(e,"div"),g.call(e,"[s!='']:x"),d.push("!=",B)}),h=h.length&&RegExp(h.join("|")),d=d.length&&RegExp(d.join("|")),m=it(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},S=f.compareDocumentPosition?function(e,n){if(e===n)return E=!0,0;var r=n.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(n);return r?1&r||!b.sortDetached&&n.compareDocumentPosition(e)===r?e===t||m(v,e)?-1:n===t||m(v,n)?1:u?F.call(u,e)-F.call(u,n):0:4&r?-1:1:e.compareDocumentPosition?-1:1}:function(e,n){var r,i=0,o=e.parentNode,s=n.parentNode,a=[e],l=[n];if(e===n)return E=!0,0;if(!o||!s)return e===t?-1:n===t?1:o?-1:s?1:u?F.call(u,e)-F.call(u,n):0;if(o===s)return lt(e,n);r=e;while(r=r.parentNode)a.unshift(r);r=n;while(r=r.parentNode)l.unshift(r);while(a[i]===l[i])i++;return i?lt(a[i],l[i]):a[i]===v?-1:l[i]===v?1:0},c):c},ut.matches=function(e,t){return ut(e,null,null,t)},ut.matchesSelector=function(e,t){if((e.ownerDocument||e)!==c&&l(e),t=t.replace(U,"='$1']"),!(!b.matchesSelector||!p||d&&d.test(t)||h&&h.test(t)))try{var n=g.call(e,t);if(n||b.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(r){}return ut(t,c,null,[e]).length>0},ut.contains=function(e,t){return(e.ownerDocument||e)!==c&&l(e),m(e,t)},ut.attr=function(e,t){(e.ownerDocument||e)!==c&&l(e);var n=r.attrHandle[t.toLowerCase()],i=n&&n(e,t,!p);return i===undefined?b.attributes||!p?e.getAttribute(t):(i=e.getAttributeNode(t))&&i.specified?i.value:null:i},ut.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},ut.uniqueSort=function(e){var t,n=[],r=0,i=0;if(E=!b.detectDuplicates,u=!b.sortStable&&e.slice(0),e.sort(S),E){while(t=e[i++])t===e[i]&&(r=n.push(i));while(r--)e.splice(n[r],1)}return e};function lt(e,t){var n=t&&e,r=n&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ct(e,t,n){var r;return n?undefined:(r=e.getAttributeNode(t))&&r.specified?r.value:e[t]===!0?t.toLowerCase():null}function ft(e,t,n){var r;return n?undefined:r=e.getAttribute(t,"type"===t.toLowerCase()?1:2)}function pt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ht(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function dt(e){return st(function(t){return t=+t,st(function(n,r){var i,o=e([],n.length,t),s=o.length;while(s--)n[i=o[s]]&&(n[i]=!(r[i]=n[i]))})})}i=ut.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else for(;t=e[r];r++)n+=i(t);return n},r=ut.selectors={cacheLength:50,createPseudo:st,match:G,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(tt,nt),e[3]=(e[4]||e[5]||"").replace(tt,nt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||ut.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&ut.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return G.CHILD.test(e[0])?null:(e[4]?e[2]=e[4]:n&&Y.test(n)&&(t=gt(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(tt,nt).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=C[e+" "];return t||(t=RegExp("(^|"+R+")"+e+"("+R+"|$)"))&&C(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=ut.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),s="last"!==e.slice(-4),a="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,h,d,g=o!==s?"nextSibling":"previousSibling",m=t.parentNode,v=a&&t.nodeName.toLowerCase(),x=!u&&!a;if(m){if(o){while(g){f=t;while(f=f[g])if(a?f.nodeName.toLowerCase()===v:1===f.nodeType)return!1;d=g="only"===e&&!d&&"nextSibling"}return!0}if(d=[s?m.firstChild:m.lastChild],s&&x){c=m[y]||(m[y]={}),l=c[e]||[],h=l[0]===w&&l[1],p=l[0]===w&&l[2],f=h&&m.childNodes[h];while(f=++h&&f&&f[g]||(p=h=0)||d.pop())if(1===f.nodeType&&++p&&f===t){c[e]=[w,h,p];break}}else if(x&&(l=(t[y]||(t[y]={}))[e])&&l[0]===w)p=l[1];else while(f=++h&&f&&f[g]||(p=h=0)||d.pop())if((a?f.nodeName.toLowerCase()===v:1===f.nodeType)&&++p&&(x&&((f[y]||(f[y]={}))[e]=[w,p]),f===t))break;return p-=i,p===r||0===p%r&&p/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||ut.error("unsupported pseudo: "+e);return i[y]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?st(function(e,n){var r,o=i(e,t),s=o.length;while(s--)r=F.call(e,o[s]),e[r]=!(n[r]=o[s])}):function(e){return i(e,0,n)}):i}},pseudos:{not:st(function(e){var t=[],n=[],r=s(e.replace(I,"$1"));return r[y]?st(function(e,t,n,i){var o,s=r(e,null,i,[]),a=e.length;while(a--)(o=s[a])&&(e[a]=!(t[a]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:st(function(e){return function(t){return ut(e,t).length>0}}),contains:st(function(e){return function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:st(function(e){return V.test(e||"")||ut.error("unsupported lang: "+e),e=e.replace(tt,nt).toLowerCase(),function(t){var n;do if(n=p?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===c.activeElement&&(!c.hasFocus||c.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Z.test(e.nodeName)},input:function(e){return K.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:dt(function(){return[0]}),last:dt(function(e,t){return[t-1]}),eq:dt(function(e,t,n){return[0>n?n+t:n]}),even:dt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:dt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:dt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:dt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(t in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})r.pseudos[t]=pt(t);for(t in{submit:!0,reset:!0})r.pseudos[t]=ht(t);function gt(e,t){var n,i,o,s,a,u,l,c=k[e+" "];if(c)return t?0:c.slice(0);a=e,u=[],l=r.preFilter;while(a){(!n||(i=z.exec(a)))&&(i&&(a=a.slice(i[0].length)||a),u.push(o=[])),n=!1,(i=_.exec(a))&&(n=i.shift(),o.push({value:n,type:i[0].replace(I," ")}),a=a.slice(n.length));for(s in r.filter)!(i=G[s].exec(a))||l[s]&&!(i=l[s](i))||(n=i.shift(),o.push({value:n,type:s,matches:i}),a=a.slice(n.length));if(!n)break}return t?a.length:a?ut.error(e):k(e,u).slice(0)}function mt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function yt(e,t,r){var i=t.dir,o=r&&"parentNode"===i,s=T++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,r,a){var u,l,c,f=w+" "+s;if(a){while(t=t[i])if((1===t.nodeType||o)&&e(t,r,a))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[y]||(t[y]={}),(l=c[i])&&l[0]===f){if((u=l[1])===!0||u===n)return u===!0}else if(l=c[i]=[f],l[1]=e(t,r,a)||n,l[1]===!0)return!0}}function vt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,s=[],a=0,u=e.length,l=null!=t;for(;u>a;a++)(o=e[a])&&(!n||n(o,r,i))&&(s.push(o),l&&t.push(a));return s}function bt(e,t,n,r,i,o){return r&&!r[y]&&(r=bt(r)),i&&!i[y]&&(i=bt(i,o)),st(function(o,s,a,u){var l,c,f,p=[],h=[],d=s.length,g=o||Ct(t||"*",a.nodeType?[a]:a,[]),m=!e||!o&&t?g:xt(g,p,e,a,u),y=n?i||(o?e:d||r)?[]:s:m;if(n&&n(m,y,a,u),r){l=xt(y,h),r(l,[],a,u),c=l.length;while(c--)(f=l[c])&&(y[h[c]]=!(m[h[c]]=f))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(f=y[c])&&l.push(m[c]=f);i(null,y=[],l,u)}c=y.length;while(c--)(f=y[c])&&(l=i?F.call(o,f):p[c])>-1&&(o[l]=!(s[l]=f))}}else y=xt(y===s?y.splice(d,y.length):y),i?i(null,s,y,u):H.apply(s,y)})}function wt(e){var t,n,i,o=e.length,s=r.relative[e[0].type],u=s||r.relative[" "],l=s?1:0,c=yt(function(e){return e===t},u,!0),f=yt(function(e){return F.call(t,e)>-1},u,!0),p=[function(e,n,r){return!s&&(r||n!==a)||((t=n).nodeType?c(e,n,r):f(e,n,r))}];for(;o>l;l++)if(n=r.relative[e[l].type])p=[yt(vt(p),n)];else{if(n=r.filter[e[l].type].apply(null,e[l].matches),n[y]){for(i=++l;o>i;i++)if(r.relative[e[i].type])break;return bt(l>1&&vt(p),l>1&&mt(e.slice(0,l-1)).replace(I,"$1"),n,i>l&&wt(e.slice(l,i)),o>i&&wt(e=e.slice(i)),o>i&&mt(e))}p.push(n)}return vt(p)}function Tt(e,t){var i=0,o=t.length>0,s=e.length>0,u=function(u,l,f,p,h){var d,g,m,y=[],v=0,x="0",b=u&&[],T=null!=h,C=a,k=u||s&&r.find.TAG("*",h&&l.parentNode||l),N=w+=null==C?1:Math.random()||.1;for(T&&(a=l!==c&&l,n=i);null!=(d=k[x]);x++){if(s&&d){g=0;while(m=e[g++])if(m(d,l,f)){p.push(d);break}T&&(w=N,n=++i)}o&&((d=!m&&d)&&v--,u&&b.push(d))}if(v+=x,o&&x!==v){g=0;while(m=t[g++])m(b,y,l,f);if(u){if(v>0)while(x--)b[x]||y[x]||(y[x]=L.call(p));y=xt(y)}H.apply(p,y),T&&!u&&y.length>0&&v+t.length>1&&ut.uniqueSort(p)}return T&&(w=N,a=C),b};return o?st(u):u}s=ut.compile=function(e,t){var n,r=[],i=[],o=N[e+" "];if(!o){t||(t=gt(e)),n=t.length;while(n--)o=wt(t[n]),o[y]?r.push(o):i.push(o);o=N(e,Tt(i,r))}return o};function Ct(e,t,n){var r=0,i=t.length;for(;i>r;r++)ut(e,t[r],n);return n}function kt(e,t,n,i){var o,a,u,l,c,f=gt(e);if(!i&&1===f.length){if(a=f[0]=f[0].slice(0),a.length>2&&"ID"===(u=a[0]).type&&9===t.nodeType&&p&&r.relative[a[1].type]){if(t=(r.find.ID(u.matches[0].replace(tt,nt),t)||[])[0],!t)return n;e=e.slice(a.shift().value.length)}o=G.needsContext.test(e)?0:a.length;while(o--){if(u=a[o],r.relative[l=u.type])break;if((c=r.find[l])&&(i=c(u.matches[0].replace(tt,nt),X.test(a[0].type)&&t.parentNode||t))){if(a.splice(o,1),e=i.length&&mt(a),!e)return H.apply(n,i),n;break}}}return s(e,f)(i,t,!p,n,X.test(e)),n}r.pseudos.nth=r.pseudos.eq;function Nt(){}Nt.prototype=r.filters=r.pseudos,r.setFilters=new Nt,b.sortStable=y.split("").sort(S).join("")===y,l(),[0,0].sort(S),b.detectDuplicates=E,at(function(e){if(e.innerHTML="","#"!==e.firstChild.getAttribute("href")){var t="type|href|height|width".split("|"),n=t.length;while(n--)r.attrHandle[t[n]]=ft}}),at(function(e){if(null!=e.getAttribute("disabled")){var t=P.split("|"),n=t.length;while(n--)r.attrHandle[t[n]]=ct}}),x.find=ut,x.expr=ut.selectors,x.expr[":"]=x.expr.pseudos,x.unique=ut.uniqueSort,x.text=ut.getText,x.isXMLDoc=ut.isXML,x.contains=ut.contains}(e);var D={};function A(e){var t=D[e]={};return x.each(e.match(w)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?D[e]||A(e):x.extend({},e);var t,n,r,i,o,s,a=[],u=!e.once&&[],l=function(f){for(t=e.memory&&f,n=!0,s=i||0,i=0,o=a.length,r=!0;a&&o>s;s++)if(a[s].apply(f[0],f[1])===!1&&e.stopOnFalse){t=!1;break}r=!1,a&&(u?u.length&&l(u.shift()):t?a=[]:c.disable())},c={add:function(){if(a){var n=a.length;(function s(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&c.has(n)||a.push(n):n&&n.length&&"string"!==r&&s(n)})})(arguments),r?o=a.length:t&&(i=n,l(t))}return this},remove:function(){return a&&x.each(arguments,function(e,t){var n;while((n=x.inArray(t,a,n))>-1)a.splice(n,1),r&&(o>=n&&o--,s>=n&&s--)}),this},has:function(e){return e?x.inArray(e,a)>-1:!(!a||!a.length)},empty:function(){return a=[],o=0,this},disable:function(){return a=u=t=undefined,this},disabled:function(){return!a},lock:function(){return u=undefined,t||c.disable(),this},locked:function(){return!u},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!a||n&&!u||(r?u.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!n}};return c},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var s=o[0],a=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=a&&a.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===r?n.promise():this,a?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var s=o[2],a=o[3];r[o[1]]=s.add,a&&s.add(function(){n=a},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=s.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=d.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),s=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?d.call(arguments):r,n===a?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},a,u,l;if(r>1)for(a=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(s(t,l,n)).fail(o.reject).progress(s(t,u,a)):--i;return i||o.resolveWith(l,n),o.promise()}}),x.support=function(t){var n=o.createElement("input"),r=o.createDocumentFragment(),i=o.createElement("div"),s=o.createElement("select"),a=s.appendChild(o.createElement("option"));return n.type?(n.type="checkbox",t.checkOn=""!==n.value,t.optSelected=a.selected,t.reliableMarginRight=!0,t.boxSizingReliable=!0,t.pixelPosition=!1,n.checked=!0,t.noCloneChecked=n.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!a.disabled,n=o.createElement("input"),n.value="t",n.type="radio",t.radioValue="t"===n.value,n.setAttribute("checked","t"),n.setAttribute("name","t"),r.appendChild(n),t.checkClone=r.cloneNode(!0).cloneNode(!0).lastChild.checked,t.focusinBubbles="onfocusin"in e,i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===i.style.backgroundClip,x(function(){var n,r,s="padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box",a=o.getElementsByTagName("body")[0];a&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",a.appendChild(n).appendChild(i),i.innerHTML="",i.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%",x.swap(a,null!=a.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===i.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(i,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(i,null)||{width:"4px"}).width,r=i.appendChild(o.createElement("div")),r.style.cssText=i.style.cssText=s,r.style.marginRight=r.style.width="0",i.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),a.removeChild(n))}),t):t}({});var L,q,H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,O=/([A-Z])/g;function F(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=x.expando+Math.random()}F.uid=1,F.accepts=function(e){return e.nodeType?1===e.nodeType||9===e.nodeType:!0},F.prototype={key:function(e){if(!F.accepts(e))return 0;var t={},n=e[this.expando];if(!n){n=F.uid++;try{t[this.expando]={value:n},Object.defineProperties(e,t)}catch(r){t[this.expando]=n,x.extend(e,t)}}return this.cache[n]||(this.cache[n]={}),n},set:function(e,t,n){var r,i=this.key(e),o=this.cache[i];if("string"==typeof t)o[t]=n;else if(x.isEmptyObject(o))this.cache[i]=t;else for(r in t)o[r]=t[r]},get:function(e,t){var n=this.cache[this.key(e)];return t===undefined?n:n[t]},access:function(e,t,n){return t===undefined||t&&"string"==typeof t&&n===undefined?this.get(e,t):(this.set(e,t,n),n!==undefined?n:t)},remove:function(e,t){var n,r,i=this.key(e),o=this.cache[i];if(t===undefined)this.cache[i]={};else{x.isArray(t)?r=t.concat(t.map(x.camelCase)):t in o?r=[t]:(r=x.camelCase(t),r=r in o?[r]:r.match(w)||[]),n=r.length;while(n--)delete o[r[n]]}},hasData:function(e){return!x.isEmptyObject(this.cache[e[this.expando]]||{})},discard:function(e){delete this.cache[this.key(e)]}},L=new F,q=new F,x.extend({acceptData:F.accepts,hasData:function(e){return L.hasData(e)||q.hasData(e)},data:function(e,t,n){return L.access(e,t,n)},removeData:function(e,t){L.remove(e,t)},_data:function(e,t,n){return q.access(e,t,n)},_removeData:function(e,t){q.remove(e,t)}}),x.fn.extend({data:function(e,t){var n,r,i=this[0],o=0,s=null;if(e===undefined){if(this.length&&(s=L.get(i),1===i.nodeType&&!q.get(i,"hasDataAttrs"))){for(n=i.attributes;n.length>o;o++)r=n[o].name,0===r.indexOf("data-")&&(r=x.camelCase(r.substring(5)),P(i,r,s[r]));q.set(i,"hasDataAttrs",!0)}return s}return"object"==typeof e?this.each(function(){L.set(this,e)}):x.access(this,function(t){var n,r=x.camelCase(e);if(i&&t===undefined){if(n=L.get(i,e),n!==undefined)return n;if(n=L.get(i,r),n!==undefined)return n;if(n=P(i,r,undefined),n!==undefined)return n}else this.each(function(){var n=L.get(this,r);L.set(this,r,t),-1!==e.indexOf("-")&&n!==undefined&&L.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){L.remove(this,e)})}});function P(e,t,n){var r;if(n===undefined&&1===e.nodeType)if(r="data-"+t.replace(O,"-$1").toLowerCase(),n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:H.test(n)?JSON.parse(n):n}catch(i){}L.set(e,t,n)}else n=undefined;return n}x.extend({queue:function(e,t,n){var r;return e?(t=(t||"fx")+"queue",r=q.get(e,t),n&&(!r||x.isArray(n)?r=q.access(e,t,x.makeArray(n)):r.push(n)),r||[]):undefined},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),s=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),o.cur=i,i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,s,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return q.get(e,n)||q.access(e,n,{empty:x.Callbacks("once memory").add(function(){q.remove(e,[t+"queue",n])})})}}),x.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),n>arguments.length?x.queue(this[0],e):t===undefined?this:this.each(function(){var n=x.queue(this,e,t); +x._queueHooks(this,e),"fx"===e&&"inprogress"!==n[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=x.Deferred(),o=this,s=this.length,a=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=undefined),e=e||"fx";while(s--)n=q.get(o[s],e+"queueHooks"),n&&n.empty&&(r++,n.empty.add(a));return a(),i.promise(t)}});var R,M,W=/[\t\r\n]/g,$=/\r/g,B=/^(?:input|select|textarea|button)$/i;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[x.propFix[e]||e]})},addClass:function(e){var t,n,r,i,o,s=0,a=this.length,u="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,s=0,a=this.length,u=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,i="boolean"==typeof t;return x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var o,s=0,a=x(this),u=t,l=e.match(w)||[];while(o=l[s++])u=i?u:!a.hasClass(o),a[u?"addClass":"removeClass"](o)}else(n===r||"boolean"===n)&&(this.className&&q.set(this,"__className__",this.className),this.className=this.className||e===!1?"":q.get(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(W," ").indexOf(t)>=0)return!0;return!1},val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=x.isFunction(e),this.each(function(n){var i,o=x(this);1===this.nodeType&&(i=r?e.call(this,n,o.val()):e,null==i?i="":"number"==typeof i?i+="":x.isArray(i)&&(i=x.map(i,function(e){return null==e?"":e+""})),t=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],t&&"set"in t&&t.set(this,i,"value")!==undefined||(this.value=i))});if(i)return t=x.valHooks[i.type]||x.valHooks[i.nodeName.toLowerCase()],t&&"get"in t&&(n=t.get(i,"value"))!==undefined?n:(n=i.value,"string"==typeof n?n.replace($,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,s=o?null:[],a=o?i+1:r.length,u=0>i?a:o?i:0;for(;a>u;u++)if(n=r[u],!(!n.selected&&u!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),s=i.length;while(s--)r=i[s],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,t,n){var i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===r?x.prop(e,t,n):(1===s&&x.isXMLDoc(e)||(t=t.toLowerCase(),i=x.attrHooks[t]||(x.expr.match.boolean.test(t)?M:R)),n===undefined?i&&"get"in i&&null!==(o=i.get(e,t))?o:(o=x.find.attr(e,t),null==o?undefined:o):null!==n?i&&"set"in i&&(o=i.set(e,n,t))!==undefined?o:(e.setAttribute(t,n+""),n):(x.removeAttr(e,t),undefined))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.boolean.test(n)&&(e[r]=!1),e.removeAttribute(n)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,t,n){var r,i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return o=1!==s||!x.isXMLDoc(e),o&&(t=x.propFix[t]||t,i=x.propHooks[t]),n!==undefined?i&&"set"in i&&(r=i.set(e,n,t))!==undefined?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){return e.hasAttribute("tabindex")||B.test(e.nodeName)||e.href?e.tabIndex:-1}}}}),M={set:function(e,t,n){return t===!1?x.removeAttr(e,n):e.setAttribute(n,n),n}},x.each(x.expr.match.boolean.source.match(/\w+/g),function(e,t){var n=x.expr.attrHandle[t]||x.find.attr;x.expr.attrHandle[t]=function(e,t,r){var i=x.expr.attrHandle[t],o=r?undefined:(x.expr.attrHandle[t]=undefined)!=n(e,t,r)?t.toLowerCase():null;return x.expr.attrHandle[t]=i,o}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,t){return x.isArray(t)?e.checked=x.inArray(x(e).val(),t)>=0:undefined}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var I=/^key/,z=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,X=/^([^.]*)(?:\.(.+)|)$/;function U(){return!0}function Y(){return!1}function V(){try{return o.activeElement}catch(e){}}x.event={global:{},add:function(e,t,n,i,o){var s,a,u,l,c,f,p,h,d,g,m,y=q.get(e);if(y){n.handler&&(s=n,n=s.handler,o=s.selector),n.guid||(n.guid=x.guid++),(l=y.events)||(l=y.events={}),(a=y.handle)||(a=y.handle=function(e){return typeof x===r||e&&x.event.triggered===e.type?undefined:x.event.dispatch.apply(a.elem,arguments)},a.elem=e),t=(t||"").match(w)||[""],c=t.length;while(c--)u=X.exec(t[c])||[],d=m=u[1],g=(u[2]||"").split(".").sort(),d&&(p=x.event.special[d]||{},d=(o?p.delegateType:p.bindType)||d,p=x.event.special[d]||{},f=x.extend({type:d,origType:m,data:i,handler:n,guid:n.guid,selector:o,needsContext:o&&x.expr.match.needsContext.test(o),namespace:g.join(".")},s),(h=l[d])||(h=l[d]=[],h.delegateCount=0,p.setup&&p.setup.call(e,i,g,a)!==!1||e.addEventListener&&e.addEventListener(d,a,!1)),p.add&&(p.add.call(e,f),f.handler.guid||(f.handler.guid=n.guid)),o?h.splice(h.delegateCount++,0,f):h.push(f),x.event.global[d]=!0);e=null}},remove:function(e,t,n,r,i){var o,s,a,u,l,c,f,p,h,d,g,m=q.hasData(e)&&q.get(e);if(m&&(u=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(a=X.exec(t[l])||[],h=g=a[1],d=(a[2]||"").split(".").sort(),h){f=x.event.special[h]||{},h=(r?f.delegateType:f.bindType)||h,p=u[h]||[],a=a[2]&&RegExp("(^|\\.)"+d.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||a&&!a.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));s&&!p.length&&(f.teardown&&f.teardown.call(e,d,m.handle)!==!1||x.removeEvent(e,h,m.handle),delete u[h])}else for(h in u)x.event.remove(e,h+t[l],n,r,!0);x.isEmptyObject(u)&&(delete m.handle,q.remove(e,"events"))}},trigger:function(t,n,r,i){var s,a,u,l,c,f,p,h=[r||o],d=y.call(t,"type")?t.type:t,g=y.call(t,"namespace")?t.namespace.split("."):[];if(a=u=r=r||o,3!==r.nodeType&&8!==r.nodeType&&!_.test(d+x.event.triggered)&&(d.indexOf(".")>=0&&(g=d.split("."),d=g.shift(),g.sort()),c=0>d.indexOf(":")&&"on"+d,t=t[x.expando]?t:new x.Event(d,"object"==typeof t&&t),t.isTrigger=i?2:3,t.namespace=g.join("."),t.namespace_re=t.namespace?RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=undefined,t.target||(t.target=r),n=null==n?[t]:x.makeArray(n,[t]),p=x.event.special[d]||{},i||!p.trigger||p.trigger.apply(r,n)!==!1)){if(!i&&!p.noBubble&&!x.isWindow(r)){for(l=p.delegateType||d,_.test(l+d)||(a=a.parentNode);a;a=a.parentNode)h.push(a),u=a;u===(r.ownerDocument||o)&&h.push(u.defaultView||u.parentWindow||e)}s=0;while((a=h[s++])&&!t.isPropagationStopped())t.type=s>1?l:p.bindType||d,f=(q.get(a,"events")||{})[t.type]&&q.get(a,"handle"),f&&f.apply(a,n),f=c&&a[c],f&&x.acceptData(a)&&f.apply&&f.apply(a,n)===!1&&t.preventDefault();return t.type=d,i||t.isDefaultPrevented()||p._default&&p._default.apply(h.pop(),n)!==!1||!x.acceptData(r)||c&&x.isFunction(r[d])&&!x.isWindow(r)&&(u=r[c],u&&(r[c]=null),x.event.triggered=d,r[d](),x.event.triggered=undefined,u&&(r[c]=u)),t.result}},dispatch:function(e){e=x.event.fix(e);var t,n,r,i,o,s=[],a=d.call(arguments),u=(q.get(this,"events")||{})[e.type]||[],l=x.event.special[e.type]||{};if(a[0]=e,e.delegateTarget=this,!l.preDispatch||l.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),t=0;while((i=s[t++])&&!e.isPropagationStopped()){e.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(o.namespace))&&(e.handleObj=o,e.data=o.data,r=((x.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,a),r!==undefined&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return l.postDispatch&&l.postDispatch.call(this,e),e.result}},handlers:function(e,t){var n,r,i,o,s=[],a=t.delegateCount,u=e.target;if(a&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!==this;u=u.parentNode||this)if(u.disabled!==!0||"click"!==e.type){for(r=[],n=0;a>n;n++)o=t[n],i=o.selector+" ",r[i]===undefined&&(r[i]=o.needsContext?x(i,this).index(u)>=0:x.find(i,this,null,[u]).length),r[i]&&r.push(o);r.length&&s.push({elem:u,handlers:r})}return t.length>a&&s.push({elem:this,handlers:t.slice(a)}),s},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,t){var n,r,i,s=t.button;return null==e.pageX&&null!=t.clientX&&(n=e.target.ownerDocument||o,r=n.documentElement,i=n.body,e.pageX=t.clientX+(r&&r.scrollLeft||i&&i.scrollLeft||0)-(r&&r.clientLeft||i&&i.clientLeft||0),e.pageY=t.clientY+(r&&r.scrollTop||i&&i.scrollTop||0)-(r&&r.clientTop||i&&i.clientTop||0)),e.which||s===undefined||(e.which=1&s?1:2&s?3:4&s?2:0),e}},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=z.test(i)?this.mouseHooks:I.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return 3===e.target.nodeType&&(e.target=e.target.parentNode),s.filter?s.filter(e,o):e},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==V()&&this.focus?(this.focus(),!1):undefined},delegateType:"focusin"},blur:{trigger:function(){return this===V()&&this.blur?(this.blur(),!1):undefined},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&x.nodeName(this,"input")?(this.click(),!1):undefined},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==undefined&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)},x.Event=function(e,t){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.getPreventDefault&&e.getPreventDefault()?U:Y):this.type=e,t&&x.extend(this,t),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,undefined):new x.Event(e,t)},x.Event.prototype={isDefaultPrevented:Y,isPropagationStopped:Y,isImmediatePropagationStopped:Y,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=U,e&&e.preventDefault&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=U,e&&e.stopPropagation&&e.stopPropagation()},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=U,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,t,n,r,i){var o,s;if("object"==typeof e){"string"!=typeof t&&(n=n||t,t=undefined);for(s in e)this.on(s,t,n,e[s],i);return this}if(null==n&&null==r?(r=t,n=t=undefined):null==r&&("string"==typeof t?(r=n,n=undefined):(r=n,n=t,t=undefined)),r===!1)r=Y;else if(!r)return this;return 1===i&&(o=r,r=function(e){return x().off(e),o.apply(this,arguments)},r.guid=o.guid||(o.guid=x.guid++)),this.each(function(){x.event.add(this,e,r,n,t)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,x(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return(t===!1||"function"==typeof t)&&(n=t,t=undefined),n===!1&&(n=Y),this.each(function(){x.event.remove(this,e,n,t)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];return n?x.event.trigger(e,t,n,!0):undefined}});var G=/^.[^:#\[\.,]*$/,J=x.expr.match.needsContext,Q={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n,r,i=this.length;if("string"!=typeof e)return t=this,this.pushStack(x(e).filter(function(){for(r=0;i>r;r++)if(x.contains(t[r],this))return!0}));for(n=[],r=0;i>r;r++)x.find(e,this[r],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=(this.selector?this.selector+" ":"")+e,n},has:function(e){var t=x(e,this),n=t.length;return this.filter(function(){var e=0;for(;n>e;e++)if(x.contains(this,t[e]))return!0})},not:function(e){return this.pushStack(Z(this,e||[],!0))},filter:function(e){return this.pushStack(Z(this,e||[],!1))},is:function(e){return!!e&&("string"==typeof e?J.test(e)?x(e,this.context).index(this[0])>=0:x.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,o=[],s=J.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(s?s.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?g.call(x(e),this[0]):g.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function K(e,t){while((e=e[t])&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return K(e,"nextSibling")},prev:function(e){return K(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(Q[e]||x.unique(i),"p"===e[0]&&i.reverse()),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,t,n){var r=[],i=n!==undefined;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&x(e).is(n))break;r.push(e)}return r},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function Z(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(G.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return g.call(t,e)>=0!==n})}var et=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,tt=/<([\w:]+)/,nt=/<|&#?\w+;/,rt=/<(?:script|style|link)/i,it=/^(?:checkbox|radio)$/i,ot=/checked\s*(?:[^=]|=\s*.checked.)/i,st=/^$|\/(?:java|ecma)script/i,at=/^true\/(.*)/,ut=/^\s*\s*$/g,lt={option:[1,""],thead:[1,"","
          "],tr:[2,"","
          "],td:[3,"","
          "],_default:[0,"",""]};lt.optgroup=lt.option,lt.tbody=lt.tfoot=lt.colgroup=lt.caption=lt.col=lt.thead,lt.th=lt.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===undefined?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=ct(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=ct(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(gt(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&ht(gt(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++)1===e.nodeType&&(x.cleanData(gt(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var t=this[0]||{},n=0,r=this.length;if(e===undefined&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!rt.test(e)&&!lt[(tt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(et,"<$1>");try{for(;r>n;n++)t=this[n]||{},1===t.nodeType&&(x.cleanData(gt(t,!1)),t.innerHTML=e);t=0}catch(i){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=p.apply([],e);var r,i,o,s,a,u,l=0,c=this.length,f=this,h=c-1,d=e[0],g=x.isFunction(d);if(g||!(1>=c||"string"!=typeof d||x.support.checkClone)&&ot.test(d))return this.each(function(r){var i=f.eq(r);g&&(e[0]=d.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(r=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),i=r.firstChild,1===r.childNodes.length&&(r=i),i)){for(o=x.map(gt(r,"script"),ft),s=o.length;c>l;l++)a=r,l!==h&&(a=x.clone(a,!0,!0),s&&x.merge(o,gt(a,"script"))),t.call(this[l],a,l);if(s)for(u=o[o.length-1].ownerDocument,x.map(o,pt),l=0;s>l;l++)a=o[l],st.test(a.type||"")&&!q.access(a,"globalEval")&&x.contains(u,a)&&(a.src?x._evalUrl(a.src):x.globalEval(a.textContent.replace(ut,"")))}return this}}),x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=[],i=x(e),o=i.length-1,s=0;for(;o>=s;s++)n=s===o?this:this.clone(!0),x(i[s])[t](n),h.apply(r,n.get());return this.pushStack(r)}}),x.extend({clone:function(e,t,n){var r,i,o,s,a=e.cloneNode(!0),u=x.contains(e.ownerDocument,e);if(!(x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(s=gt(a),o=gt(e),r=0,i=o.length;i>r;r++)mt(o[r],s[r]);if(t)if(n)for(o=o||gt(e),s=s||gt(a),r=0,i=o.length;i>r;r++)dt(o[r],s[r]);else dt(e,a);return s=gt(a,"script"),s.length>0&&ht(s,!u&>(e,"script")),a},buildFragment:function(e,t,n,r){var i,o,s,a,u,l,c=0,f=e.length,p=t.createDocumentFragment(),h=[];for(;f>c;c++)if(i=e[c],i||0===i)if("object"===x.type(i))x.merge(h,i.nodeType?[i]:i);else if(nt.test(i)){o=o||p.appendChild(t.createElement("div")),s=(tt.exec(i)||["",""])[1].toLowerCase(),a=lt[s]||lt._default,o.innerHTML=a[1]+i.replace(et,"<$1>")+a[2],l=a[0];while(l--)o=o.firstChild;x.merge(h,o.childNodes),o=p.firstChild,o.textContent=""}else h.push(t.createTextNode(i));p.textContent="",c=0;while(i=h[c++])if((!r||-1===x.inArray(i,r))&&(u=x.contains(i.ownerDocument,i),o=gt(p.appendChild(i),"script"),u&&ht(o),n)){l=0;while(i=o[l++])st.test(i.type||"")&&n.push(i)}return p},cleanData:function(e){var t,n,r,i=e.length,o=0,s=x.event.special;for(;i>o;o++){if(n=e[o],x.acceptData(n)&&(t=q.access(n)))for(r in t.events)s[r]?x.event.remove(n,r):x.removeEvent(n,r,t.handle);L.discard(n),q.discard(n)}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"text",async:!1,global:!1,success:x.globalEval})}});function ct(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function ft(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function pt(e){var t=at.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function ht(e,t){var n=e.length,r=0;for(;n>r;r++)q.set(e[r],"globalEval",!t||q.get(t[r],"globalEval"))}function dt(e,t){var n,r,i,o,s,a,u,l;if(1===t.nodeType){if(q.hasData(e)&&(o=q.access(e),s=x.extend({},o),l=o.events,q.set(t,s),l)){delete s.handle,s.events={};for(i in l)for(n=0,r=l[i].length;r>n;n++)x.event.add(t,i,l[i][n])}L.hasData(e)&&(a=L.access(e),u=x.extend({},a),L.set(t,u))}}function gt(e,t){var n=e.getElementsByTagName?e.getElementsByTagName(t||"*"):e.querySelectorAll?e.querySelectorAll(t||"*"):[];return t===undefined||t&&x.nodeName(e,t)?x.merge([e],n):n}function mt(e,t){var n=t.nodeName.toLowerCase();"input"===n&&it.test(e.type)?t.checked=e.checked:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}x.fn.extend({wrapAll:function(e){var t;return x.isFunction(e)?this.each(function(t){x(this).wrapAll(e.call(this,t))}):(this[0]&&(t=x(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this)},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var yt,vt,xt=/^(none|table(?!-c[ea]).+)/,bt=/^margin/,wt=RegExp("^("+b+")(.*)$","i"),Tt=RegExp("^("+b+")(?!px)[a-z%]+$","i"),Ct=RegExp("^([+-])=("+b+")","i"),kt={BODY:"block"},Nt={position:"absolute",visibility:"hidden",display:"block"},Et={letterSpacing:0,fontWeight:400},St=["Top","Right","Bottom","Left"],jt=["Webkit","O","Moz","ms"];function Dt(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=jt.length;while(i--)if(t=jt[i]+n,t in e)return t;return r}function At(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function Lt(t){return e.getComputedStyle(t,null)}function qt(e,t){var n,r,i,o=[],s=0,a=e.length;for(;a>s;s++)r=e[s],r.style&&(o[s]=q.get(r,"olddisplay"),n=r.style.display,t?(o[s]||"none"!==n||(r.style.display=""),""===r.style.display&&At(r)&&(o[s]=q.access(r,"olddisplay",Pt(r.nodeName)))):o[s]||(i=At(r),(n&&"none"!==n||!i)&&q.set(r,"olddisplay",i?n:x.css(r,"display"))));for(s=0;a>s;s++)r=e[s],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[s]||"":"none"));return e}x.fn.extend({css:function(e,t){return x.access(this,function(e,t,n){var r,i,o={},s=0;if(x.isArray(t)){for(r=Lt(e),i=t.length;i>s;s++)o[t[s]]=x.css(e,t[s],!1,r);return o}return n!==undefined?x.style(e,t,n):x.css(e,t)},e,t,arguments.length>1)},show:function(){return qt(this,!0)},hide:function(){return qt(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:At(this))?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=yt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,s,a=x.camelCase(t),u=e.style;return t=x.cssProps[a]||(x.cssProps[a]=Dt(u,a)),s=x.cssHooks[t]||x.cssHooks[a],n===undefined?s&&"get"in s&&(i=s.get(e,!1,r))!==undefined?i:u[t]:(o=typeof n,"string"===o&&(i=Ct.exec(n))&&(n=(i[1]+1)*i[2]+parseFloat(x.css(e,t)),o="number"),null==n||"number"===o&&isNaN(n)||("number"!==o||x.cssNumber[a]||(n+="px"),x.support.clearCloneStyle||""!==n||0!==t.indexOf("background")||(u[t]="inherit"),s&&"set"in s&&(n=s.set(e,n,r))===undefined||(u[t]=n)),undefined)}},css:function(e,t,n,r){var i,o,s,a=x.camelCase(t);return t=x.cssProps[a]||(x.cssProps[a]=Dt(e.style,a)),s=x.cssHooks[t]||x.cssHooks[a],s&&"get"in s&&(i=s.get(e,!0,n)),i===undefined&&(i=yt(e,t,r)),"normal"===i&&t in Et&&(i=Et[t]),""===n||n?(o=parseFloat(i),n===!0||x.isNumeric(o)?o||0:i):i}}),yt=function(e,t,n){var r,i,o,s=n||Lt(e),a=s?s.getPropertyValue(t)||s[t]:undefined,u=e.style;return s&&(""!==a||x.contains(e.ownerDocument,e)||(a=x.style(e,t)),Tt.test(a)&&bt.test(t)&&(r=u.width,i=u.minWidth,o=u.maxWidth,u.minWidth=u.maxWidth=u.width=a,a=s.width,u.width=r,u.minWidth=i,u.maxWidth=o)),a};function Ht(e,t,n){var r=wt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function Ot(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,s=0;for(;4>o;o+=2)"margin"===n&&(s+=x.css(e,n+St[o],!0,i)),r?("content"===n&&(s-=x.css(e,"padding"+St[o],!0,i)),"margin"!==n&&(s-=x.css(e,"border"+St[o]+"Width",!0,i))):(s+=x.css(e,"padding"+St[o],!0,i),"padding"!==n&&(s+=x.css(e,"border"+St[o]+"Width",!0,i)));return s}function Ft(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Lt(e),s=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=yt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Tt.test(i))return i;r=s&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+Ot(e,t,n||(s?"border":"content"),r,o)+"px"}function Pt(e){var t=o,n=kt[e];return n||(n=Rt(e,t),"none"!==n&&n||(vt=(vt||x(" + + + + + diff --git a/tests/automation/nwfaketop/internal/package.json b/tests/automation/nwfaketop/internal/package.json new file mode 100644 index 0000000000..b2bc1566ae --- /dev/null +++ b/tests/automation/nwfaketop/internal/package.json @@ -0,0 +1,11 @@ +{ + "name": "nwfaketop", + "main": "index.html", + "window": { + "width": 600, + "height": 400, + "position": "center", + "toolbar": true, + "resizable": true + } +} \ No newline at end of file diff --git a/tests/automation/nwfaketop/mocha_test.js b/tests/automation/nwfaketop/mocha_test.js new file mode 100644 index 0000000000..fb32f30701 --- /dev/null +++ b/tests/automation/nwfaketop/mocha_test.js @@ -0,0 +1,44 @@ +var path = require('path'); +var assert = require('assert'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + + +describe('nwfaketop',function(){ + + var child, server, result; + + before(function(done){ + this.timeout(0); + server = createTCPServer(13013); + child = spawnChildProcess(path.join(curDir, 'internal')); + + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + result = JSON.parse(data+''); + done(); + }); + }); + + }); + + after(function(done){ + server.close(); + child.kill(); + done(); + }); + + it("nwfaketop attribute set",function(done){ + assert(result[0],true); + done(); + }); + it("nwfaketop attribute default",function(done){ + assert(result[1],true); + done(); + }); + + +}); + + diff --git a/tests/automation/nwfaketop/package.json b/tests/automation/nwfaketop/package.json new file mode 100644 index 0000000000..0877473e9c --- /dev/null +++ b/tests/automation/nwfaketop/package.json @@ -0,0 +1,4 @@ +{ + "name":"nwfaketop_wrapper", + "main":"index.html" +} diff --git a/tests/automation/package.json b/tests/automation/package.json new file mode 100644 index 0000000000..03957725b8 --- /dev/null +++ b/tests/automation/package.json @@ -0,0 +1,13 @@ +{ + "name": "automation", + "version": "0.1.0", + "main": "index.html", + "window": { + "position": "center", + "show": false + }, + "dependencies": { + "xunit-file": "*", + "colors": "*" + } +} diff --git a/tests/automation/plugin/flash/index.html b/tests/automation/plugin/flash/index.html new file mode 100644 index 0000000000..26b23a9fb1 --- /dev/null +++ b/tests/automation/plugin/flash/index.html @@ -0,0 +1,35 @@ + + + + SWFObject - step 1 + + + +
          + + + + + + +

          Alternative content

          + +
          + + + + + +
          +
          + + diff --git a/tests/automation/plugin/flash/package.json b/tests/automation/plugin/flash/package.json new file mode 100644 index 0000000000..9dede79d65 --- /dev/null +++ b/tests/automation/plugin/flash/package.json @@ -0,0 +1,20 @@ +{ + "main": "index.html", + "name": "nw-demo", + "description": "demo app of node-webkit", + "version": "0.1", + "keywords": [ "demo", "node-webkit" ], + "window": { + "toolbar": true, + "width": 800, + "height": 500, + "position": "mouse", + "min_width": 400, + "min_height": 200, + "max_width": 800, + "max_height": 600 + }, + "webkit": { + "plugin": true + } +} diff --git a/tests/automation/plugin/flash/relog.swf b/tests/automation/plugin/flash/relog.swf new file mode 100644 index 0000000000..db63f88062 Binary files /dev/null and b/tests/automation/plugin/flash/relog.swf differ diff --git a/tests/automation/plugin/index.html b/tests/automation/plugin/index.html new file mode 100644 index 0000000000..655f1d6f72 --- /dev/null +++ b/tests/automation/plugin/index.html @@ -0,0 +1,16 @@ + + + + + plugin test + + +

          plugin test

          + + + + + + + + diff --git a/tests/automation/plugin/mocha_test.js b/tests/automation/plugin/mocha_test.js new file mode 100644 index 0000000000..ac053a7e11 --- /dev/null +++ b/tests/automation/plugin/mocha_test.js @@ -0,0 +1,40 @@ +var path = require('path'); +var assert = require('assert'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + +describe('Plugin', function() { + + describe('flash', function(){ + + var server, child, result = false; + + before(function(done) { + server = createTCPServer(13013); + child = spawnChildProcess(path.join(curDir, 'flash')); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + result = true; + child.kill(); + done(); + }); + }); + + }); + + after(function () { + server.close(); + }); + + it('should not crash', function() { + + assert.equal(result, true); + + }); + + }); + +}); + + diff --git a/tests/automation/plugin/package.json b/tests/automation/plugin/package.json new file mode 100644 index 0000000000..4313392294 --- /dev/null +++ b/tests/automation/plugin/package.json @@ -0,0 +1,5 @@ +{ + "name":"plugin_wrapper", + "main":"index.html" +} + diff --git a/tests/automation/process/exit/index.html b/tests/automation/process/exit/index.html new file mode 100644 index 0000000000..769749b1a4 --- /dev/null +++ b/tests/automation/process/exit/index.html @@ -0,0 +1,20 @@ + + + + + +sdew + + diff --git a/tests/automation/process/exit/package.json b/tests/automation/process/exit/package.json new file mode 100644 index 0000000000..0631d3a337 --- /dev/null +++ b/tests/automation/process/exit/package.json @@ -0,0 +1,5 @@ +{ + "name": "nw-demo", + "main": "index.html", + "window": { "show" : true } +} diff --git a/tests/automation/process/index.html b/tests/automation/process/index.html new file mode 100644 index 0000000000..94e29b0cd3 --- /dev/null +++ b/tests/automation/process/index.html @@ -0,0 +1,16 @@ + + + + + process test + + +

          Process test

          + + + + + + + + diff --git a/tests/automation/process/mocha_test.js b/tests/automation/process/mocha_test.js new file mode 100644 index 0000000000..22c1388226 --- /dev/null +++ b/tests/automation/process/mocha_test.js @@ -0,0 +1,32 @@ +var path = require('path'); +var assert = require('assert'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + +describe('process', function() { + describe('exit', function(){ + it('event process.exit should be fired after calling win.close(true)', function(done) { + + + var child = spawnChildProcess(path.join(curDir, 'exit')); + + setTimeout(function() { + var tmpFilePath = path.join(curDir, 'exit', 'a.txt'); + fs.exists(tmpFilePath, function (exists) { + if (exists) { + fs.unlink(tmpFilePath); + done(); + } else { + done('the event `exit` does not been called.'); + } + }); + + }, 1500); + + + }); + + }); + +}); + diff --git a/tests/automation/process/package.json b/tests/automation/process/package.json new file mode 100644 index 0000000000..c83e304e6e --- /dev/null +++ b/tests/automation/process/package.json @@ -0,0 +1,5 @@ +{ + "name":"process_wrapper", + "main":"index.html" +} + diff --git a/tests/automation/proxy/export.bat b/tests/automation/proxy/export.bat new file mode 100644 index 0000000000..fe25b8b4be --- /dev/null +++ b/tests/automation/proxy/export.bat @@ -0,0 +1 @@ +reg export "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" c:\test.reg \ No newline at end of file diff --git a/tests/automation/proxy/index.html b/tests/automation/proxy/index.html new file mode 100644 index 0000000000..1708c25344 --- /dev/null +++ b/tests/automation/proxy/index.html @@ -0,0 +1,16 @@ + + + + + Proxy test + + +

          Proxy test

          + + + + + + + + diff --git a/tests/automation/proxy/mocha_test.js b/tests/automation/proxy/mocha_test.js new file mode 100644 index 0000000000..f2b1fda63c --- /dev/null +++ b/tests/automation/proxy/mocha_test.js @@ -0,0 +1,51 @@ +var cp = require('child_process'); +var os = require('os'); +var path = require('path'); +var assert = require('assert'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + +describe('proxy', function(){ + before(function(done){ + if (os.platform() == "win32"){ + this.timeout(0); + var ex = cp.exec(path.join(curDir, 'export.bat'), + function(error, stdout, stderr){ + console.log(stdout); + done(); + }); + } + else + done(); + }) + after(function(){ + this.timeout(0); + if (os.platform() == "win32"){ + fs.unlink('c:/test.reg', function (err) { + if (err) console.log(err); + }); + } + }) + it('the get proxyfor url should work fine', function(done) { + if (os.platform() == "win32"){ + this.timeout(0); + setTimeout(function(){ + var data = fs.readFileSync("c:/test.reg",'utf16le'); + var index = data.indexOf('ProxyServer'); + var right = data.substring(index+14); + var array = right.split('"'); + var gui = require('nw.gui'); + var re = gui.App.getProxyForURL("https://www.google.com.hk"); + if (re == "PROXY "+array[0]) + done(); + else + done('the method getProxyForURL is not right'); + },1000); + } + else { + done(); + } + }); + +}); + diff --git a/tests/automation/proxy/package.json b/tests/automation/proxy/package.json new file mode 100644 index 0000000000..b395473dc3 --- /dev/null +++ b/tests/automation/proxy/package.json @@ -0,0 +1,5 @@ +{ + "name":"proxy_wrapper", + "main":"index.html" +} + diff --git a/tests/automation/quit_with_secondary_window_on_top/index.html b/tests/automation/quit_with_secondary_window_on_top/index.html new file mode 100644 index 0000000000..70522fdc0c --- /dev/null +++ b/tests/automation/quit_with_secondary_window_on_top/index.html @@ -0,0 +1,16 @@ + + + + + quit with secondary window on top test + + +

          quit with secondary window on top test

          + + + + + + + + diff --git a/tests/automation/quit_with_secondary_window_on_top/internal/index.html b/tests/automation/quit_with_secondary_window_on_top/internal/index.html new file mode 100644 index 0000000000..9ce8558db6 --- /dev/null +++ b/tests/automation/quit_with_secondary_window_on_top/internal/index.html @@ -0,0 +1,17 @@ + + + + quit_with_secondary_window_on_top! + + +

          quit_with_secondary_window_on_top!

          + with the created new window on top, hit CMD-Q or select Menu > Quit + . + + diff --git a/tests/automation/quit_with_secondary_window_on_top/internal/index1.html b/tests/automation/quit_with_secondary_window_on_top/internal/index1.html new file mode 100644 index 0000000000..a73f7b5476 --- /dev/null +++ b/tests/automation/quit_with_secondary_window_on_top/internal/index1.html @@ -0,0 +1,18 @@ + + + + New Window! + + +

          New Window!

          + We are using node.js + + + diff --git a/tests/automation/quit_with_secondary_window_on_top/internal/package.json b/tests/automation/quit_with_secondary_window_on_top/internal/package.json new file mode 100644 index 0000000000..9b415e218a --- /dev/null +++ b/tests/automation/quit_with_secondary_window_on_top/internal/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw-quit_with_secondary_window_on_top", + "main": "index.html" +} diff --git a/tests/automation/quit_with_secondary_window_on_top/mocha_test.js b/tests/automation/quit_with_secondary_window_on_top/mocha_test.js new file mode 100644 index 0000000000..0bc93039a6 --- /dev/null +++ b/tests/automation/quit_with_secondary_window_on_top/mocha_test.js @@ -0,0 +1,40 @@ +var assert = require('assert'); +var path = require('path'); +var exec = require('child_process').exec; +var spawn = require('child_process').spawn; +var os = require('os'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + +var result; + +if (os.platform() === 'darwin') { + +describe('quit_with_secondary_window_on_top', function() { + before(function(done) { + this.timeout(0); + var app_path = path.join(curDir, 'internal'); + var exec_path = process.execPath; + exec_path = exec_path.replace(/ /g, '\\ '); + exec(exec_path + ' ' + app_path, function(error, stdout, stderr) { + result = error; + done(); + }); + }) + it('should quit with secondary window on top without error', function() { + assert.equal(result, null); + }); +}); + +} else { + console.log('This test need to run under darwin'); +describe('quit_with_secondary_window_on_top', function() { + + it('should quit with secondary window on top without error', function() { + // todo + }); +}); + +} + + diff --git a/tests/automation/quit_with_secondary_window_on_top/package.json b/tests/automation/quit_with_secondary_window_on_top/package.json new file mode 100644 index 0000000000..cdb919062f --- /dev/null +++ b/tests/automation/quit_with_secondary_window_on_top/package.json @@ -0,0 +1,5 @@ +{ + "name":"quit_with_secondary_window_on_top_wrapper", + "main":"index.html" +} + diff --git a/tests/automation/reference-node-main/index.html b/tests/automation/reference-node-main/index.html new file mode 100644 index 0000000000..e8fedb870d --- /dev/null +++ b/tests/automation/reference-node-main/index.html @@ -0,0 +1,53 @@ + + + + + test + + + + + + diff --git a/tests/automation/reference-node-main/index.js b/tests/automation/reference-node-main/index.js new file mode 100644 index 0000000000..04b94f07a4 --- /dev/null +++ b/tests/automation/reference-node-main/index.js @@ -0,0 +1,24 @@ +exports.message = "hello world"; +exports.port = 10000; +exports.ready = false; + +var server = require('net').createServer(); +server.on('connection',function(socket){ + socket.on('data',function(data){ + socket.write(data); + }); +}); + +server.on('error',function(){ + try{ + server.close(); + }catch(e){ + exports.port += 1; + setTimeout(function(){ + server.listen(exports.port); + },0); + } +}); +server.listen(exports.port,function(){ + exports.ready = true; +}); diff --git a/tests/automation/reference-node-main/package.json b/tests/automation/reference-node-main/package.json new file mode 100644 index 0000000000..486c567b08 --- /dev/null +++ b/tests/automation/reference-node-main/package.json @@ -0,0 +1,6 @@ +{ + "name":"nw_1403596464", + "main":"index.html", + "dependencies":{}, + "node-main":"./index.js" +} \ No newline at end of file diff --git a/tests/automation/reference_xhr_in_node_context/index.html b/tests/automation/reference_xhr_in_node_context/index.html new file mode 100644 index 0000000000..a7d063e37a --- /dev/null +++ b/tests/automation/reference_xhr_in_node_context/index.html @@ -0,0 +1,16 @@ + + + + + reference xhr in node context test + + +

          reference xhr in node context test

          + + + + + + + + diff --git a/tests/automation/reference_xhr_in_node_context/internal/index.html b/tests/automation/reference_xhr_in_node_context/internal/index.html new file mode 100644 index 0000000000..848b42c98f --- /dev/null +++ b/tests/automation/reference_xhr_in_node_context/internal/index.html @@ -0,0 +1,32 @@ + + + + + test + + +

          it works!

          + + + + diff --git a/tests/automation/reference_xhr_in_node_context/internal/package.json b/tests/automation/reference_xhr_in_node_context/internal/package.json new file mode 100644 index 0000000000..1f456a50f6 --- /dev/null +++ b/tests/automation/reference_xhr_in_node_context/internal/package.json @@ -0,0 +1,5 @@ +{ + "name":"nw_1404278514", + "main":"index.html", + "dependencies":{} +} \ No newline at end of file diff --git a/tests/automation/reference_xhr_in_node_context/internal/test.js b/tests/automation/reference_xhr_in_node_context/internal/test.js new file mode 100644 index 0000000000..e935f938c6 --- /dev/null +++ b/tests/automation/reference_xhr_in_node_context/internal/test.js @@ -0,0 +1,11 @@ +exports.xhr = function () { + global.xhr.onreadystatechange = function() { + if (global.xhr.readyState === 4 && global.xhr.status === 200) { + } + } + global.xhr.onload = function(e) { + var uInt8Array = new Uint8Array(this.response); // this.response == uInt8Array.buffer + // var byte3 = uInt8Array[4]; // byte at offset 4 + }; + +} \ No newline at end of file diff --git a/tests/automation/reference_xhr_in_node_context/mocha_test.js b/tests/automation/reference_xhr_in_node_context/mocha_test.js new file mode 100644 index 0000000000..c96eb6c7a5 --- /dev/null +++ b/tests/automation/reference_xhr_in_node_context/mocha_test.js @@ -0,0 +1,34 @@ +var path = require('path'); +var assert = require('assert'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + +describe('reference xhr from node context',function(){ + + var server, child, crash = true; + + before(function(done) { + this.timeout(0); + server = createTCPServer(13013); + child = spawnChildProcess(path.join(curDir, 'internal')); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + crash = false; + child.kill(); + done(); + }); + }); + + }); + + after(function () { + server.close(); + }); + + it("reference xhr from node context should not crash",function(done){ + assert.equal(crash,false); + done(); + }); +}); + diff --git a/tests/automation/reference_xhr_in_node_context/package.json b/tests/automation/reference_xhr_in_node_context/package.json new file mode 100644 index 0000000000..ca5ad75924 --- /dev/null +++ b/tests/automation/reference_xhr_in_node_context/package.json @@ -0,0 +1,5 @@ +{ + "name":"reference_xhr_in_node_context_wrapper", + "main":"index.html" +} + diff --git a/tests/automation/reload_application/index.html b/tests/automation/reload_application/index.html new file mode 100644 index 0000000000..52d2674005 --- /dev/null +++ b/tests/automation/reload_application/index.html @@ -0,0 +1,16 @@ + + + + + reload application test + + +

          reload application test

          + + + + + + + + diff --git a/tests/automation/reload_application/internal/index.html b/tests/automation/reload_application/internal/index.html new file mode 100644 index 0000000000..5144305aa3 --- /dev/null +++ b/tests/automation/reload_application/internal/index.html @@ -0,0 +1,96 @@ + + + + + + + + + +
          + +
          + +
          + + + + diff --git a/tests/automation/reload_application/internal/package.json b/tests/automation/reload_application/internal/package.json new file mode 100644 index 0000000000..3323c9b893 --- /dev/null +++ b/tests/automation/reload_application/internal/package.json @@ -0,0 +1,7 @@ +{ + "name": "reload application", + "main": "index.html", + "window": { + "show": false + } +} diff --git a/tests/automation/reload_application/mocha_test.js b/tests/automation/reload_application/mocha_test.js new file mode 100644 index 0000000000..9f0a3c7546 --- /dev/null +++ b/tests/automation/reload_application/mocha_test.js @@ -0,0 +1,189 @@ +var assert = require('assert'); +var spawn = require('child_process').spawn; +var path = require('path'); +var net = require('net'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + +var cb; + +describe('AppTest', function(){ + + describe('reload app (long-to-run)', function(){ + + + var server; + + before(function(done) { + server = createTCPServer(13013); + done(); + }); + + after(function () { + server.close(); + }); + + afterEach(function(){ + try { + server.removeAllListeners('connection'); + } catch (e) { + } + + }); + + + + var spawnChild = function(type) { + return spawn(process.execPath, [path.join(curDir, 'internal'), type]); + }; + + + it('close window after reload', function(done){ + this.timeout(0); + var result = false; + + var app = spawnChild(0); + app.on('exit', function (code){ + if (code != 0) return done('error'); + result = true; + done(); + }); + + setTimeout(function(){ + if (!result) { + app.kill(); + done("Timeout, Can not close window."); + } + }, 30000); + + }); + + + + it('quit app after reload', function(done){ + this.timeout(0); + var result = false; + var app = spawnChild(1); + app.on('exit', function (code){ + if (code) { + done('error: the error code is: ' + code); + } else { + result = true; + done(); + } + }); + + setTimeout(function(){ + if (!result) { + app.kill(); + done("Timeout, Can not quit App."); + } + }, 30000); + + + }); + + + it('close window after reload dev', function(done){ + this.timeout(0); + var times = 0; + var result = false; + + server.on('connection', cb = function(socket){ + + //console.log('client connect in 1'); + socket.setEncoding('utf8'); + socket.on('error', function(er) { + //console.log(er); + }); + socket.on('data', function(data){ + if (data == 'open'){ + if (times == 0){ + times += 1; + socket.write('reload'); + } else if (times == 1){ + socket.write('quit'); + } + + }// if(data == 'open') + + }); + }); + + var app = spawnChild(2); + + app.on('exit', function (code){ + if (code) { + done('error: the error code is: ' + code); + } else { + result = true; + done(); + } + }); + + + setTimeout(function(){ + if (!result) { + app.kill(); + done("Timeout, Can not close window."); + } + }, 30000); + + + }); + + + it('quit app after reload dev', function(done){ + this.timeout(0); + var times = 0; + var result = false; + + server.on('connection', cb = function(socket){ + socket.on('error', function(er) { + //console.log(er); + }); + //console.log('client connect'); + socket.setEncoding('utf8'); + socket.on('data', function(data){ + if (data == 'open'){ + + if (times == 0){ + times += 1; + socket.write('reload'); + } else if (times == 1){ + socket.write('quit'); + } + + }// if(data == 'open') + + }); + }); + + var app = spawnChild(3); + + app.on('exit', function (code){ + if (code != 0) return done('error'); + result = true; + done(); + }); + + + setTimeout(function(){ + if (!result) { + app.kill(); + done("Timeout, Can not quit App."); + } + }, 30000); + + + }); + + + + + }); + +}); + + + diff --git a/tests/automation/reload_application/package.json b/tests/automation/reload_application/package.json new file mode 100644 index 0000000000..37488a02a1 --- /dev/null +++ b/tests/automation/reload_application/package.json @@ -0,0 +1,5 @@ +{ + "name":"reload_application_wrapper", + "main":"index.html" +} + diff --git a/tests/automation/remote-img/imgnotshown/package.json b/tests/automation/remote-img/imgnotshown/package.json new file mode 100644 index 0000000000..d1242b6d18 --- /dev/null +++ b/tests/automation/remote-img/imgnotshown/package.json @@ -0,0 +1,5 @@ +{ + "name": "remote file access", + "main": "http://localhost:8123/index.html", + "node-remote": "localhost" +} \ No newline at end of file diff --git a/tests/automation/remote-img/imgshown/index.html b/tests/automation/remote-img/imgshown/index.html new file mode 100644 index 0000000000..a424b015c2 --- /dev/null +++ b/tests/automation/remote-img/imgshown/index.html @@ -0,0 +1,21 @@ + + + + + remote file access test + + + +
          + + + \ No newline at end of file diff --git a/tests/automation/remote-img/imgshown/package.json b/tests/automation/remote-img/imgshown/package.json new file mode 100644 index 0000000000..d9d91badba --- /dev/null +++ b/tests/automation/remote-img/imgshown/package.json @@ -0,0 +1,4 @@ +{ + "name": "remote file access", + "main": "index.html" +} \ No newline at end of file diff --git a/tests/automation/remote-img/index.html b/tests/automation/remote-img/index.html new file mode 100644 index 0000000000..1800fbd5d1 --- /dev/null +++ b/tests/automation/remote-img/index.html @@ -0,0 +1,16 @@ + + + + + remote img test + + +

          remote img test

          + + + + + + + + diff --git a/tests/automation/remote-img/mocha_test.js b/tests/automation/remote-img/mocha_test.js new file mode 100644 index 0000000000..aa5582afba --- /dev/null +++ b/tests/automation/remote-img/mocha_test.js @@ -0,0 +1,93 @@ +var path = require('path'); +var spawn = require('child_process').spawn; +var fs = require('fs-extra'); +var os = require('os'); +var platform = os.platform(); +var curDir = fs.realpathSync('.'); + +var result, app, exec_argv, timerId; + +process.on('exit', function() { + if (app) { + app.kill('SIGKILL'); + } +}); + +describe('remote-file-access', function(){ + + before(function(done){ + this.timeout(0); + if(platform == "win32"){ + fs.copySync(path.join(curDir, 'star.jpg'),'/star.jpg'); + } + else { + fs.copySync(path.join(curDir, 'star.jpg'),'/tmp/star.jpg'); + } + done(); + }); + + after(function(done) { + this.timeout(0); + if (platform == "win32"){ + fs.unlinkSync('/star.jpg'); + } + else { + fs.unlinkSync('/tmp/star.jpg'); + } + done(); + }); + + + it ('remote img should work', function(done){ + this.timeout(0); + exec_argv = [path.join(curDir, 'imgnotshown')]; + result =false; + app = spawn(process.execPath, exec_argv); + app.on('exit', function(code){ + + clearTimeout(timerId); + result = true; + app.kill('SIGKILL'); + app = undefined; + done(); + }); + + timerId = setTimeout(function(){ + if(!result){ + app.removeAllListeners('exit'); + app.kill('SIGKILL'); + app = undefined; + done(); + } + }, 2000); + }); + + + it ('local img should work', function(done){ + this.timeout(0); + exec_argv = [path.join(curDir, 'imgshown')]; + result = false; + app = spawn(process.execPath, exec_argv); + app.on('exit', function(code){ + clearTimeout(timerId); + result = true; + app.kill('SIGKILL'); + app = undefined; + done(); + }); + + timerId=setTimeout(function(){ + if(!result){ + app.removeAllListeners('exit'); + app.kill('SIGKILL'); + app = undefined; + done(); + } + }, 2000); + + }); + + +}); + + diff --git a/tests/automation/remote-img/package.json b/tests/automation/remote-img/package.json new file mode 100644 index 0000000000..849ae2d40b --- /dev/null +++ b/tests/automation/remote-img/package.json @@ -0,0 +1,5 @@ +{ + "name":"remote_img_wrapper", + "main":"index.html" +} + diff --git a/tests/automation/remote-img/star.jpg b/tests/automation/remote-img/star.jpg new file mode 100644 index 0000000000..9d634e2c27 Binary files /dev/null and b/tests/automation/remote-img/star.jpg differ diff --git a/tests/automation/res/assert.js b/tests/automation/res/assert.js new file mode 100644 index 0000000000..73c9fa55ce --- /dev/null +++ b/tests/automation/res/assert.js @@ -0,0 +1,431 @@ +// http://wiki.commonjs.org/wiki/Unit_Testing/1.0 +// +// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! +// +// Copyright (c) 2011 Jxck +// +// Originally from node.js (http://nodejs.org) +// Copyright Joyent, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the 'Software'), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +(function(module) { + +if (typeof module.exports === 'undefined') { + module.exports = module; // this case must be browser +} + +// UTILITY + +// Object.create compatible in IE +var create = Object.create || function(p) { + if (!p) throw Error('no type'); + function f() {}; + f.prototype = p; + return new f(); +}; + +// UTILITY +var util = { + inherits: function(ctor, superCtor) { + ctor.super_ = superCtor; + ctor.prototype = create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }, + isArray: function(ar) { + return Array.isArray(ar); + }, + isBoolean: function(arg) { + return typeof arg === 'boolean'; + }, + isNull: function(arg) { + return arg === null; + }, + isNullOrUndefined: function(arg) { + return arg == null; + }, + isNumber: function(arg) { + return typeof arg === 'number'; + }, + isString: function(arg) { + return typeof arg === 'string'; + }, + isSymbol: function(arg) { + return typeof arg === 'symbol'; + }, + isUndefined: function(arg) { + return arg === void 0; + }, + isRegExp: function(re) { + return util.isObject(re) && util.objectToString(re) === '[object RegExp]'; + }, + isObject: function(arg) { + return typeof arg === 'object' && arg !== null; + }, + isDate: function(d) { + return util.isObject(d) && util.objectToString(d) === '[object Date]'; + }, + isError: function(e) { + return isObject(e) && + (objectToString(e) === '[object Error]' || e instanceof Error); + }, + isFunction: function(arg) { + return typeof arg === 'function'; + }, + isPrimitive: function(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; + }, + objectToString: function(o) { + return Object.prototype.toString.call(o); + } +}; + +var pSlice = Array.prototype.slice; + +// from https://github.com/substack/node-deep-equal +var Object_keys = typeof Object.keys === 'function' + ? Object.keys + : function (obj) { + var keys = []; + for (var key in obj) keys.push(key); + return keys; + } +; + +// 1. The assert module provides functions that throw +// AssertionError's when particular conditions are not met. The +// assert module must conform to the following interface. + +var assert = module.exports = ok; + +// 2. The AssertionError is defined in assert. +// new assert.AssertionError({ message: message, +// actual: actual, +// expected: expected }) + +assert.AssertionError = function AssertionError(options) { + this.name = 'AssertionError'; + this.actual = options.actual; + this.expected = options.expected; + this.operator = options.operator; + if (options.message) { + this.message = options.message; + this.generatedMessage = false; + } else { + this.message = getMessage(this); + this.generatedMessage = true; + } + var stackStartFunction = options.stackStartFunction || fail; + if (Error.captureStackTrace) { + Error.captureStackTrace(this, stackStartFunction); + } else { + // try to throw an error now, and from the stack property + // work out the line that called in to assert.js. + try { + this.stack = (new Error).stack.toString(); + } catch (e) {} + } +}; + +// assert.AssertionError instanceof Error +util.inherits(assert.AssertionError, Error); + +function replacer(key, value) { + if (util.isUndefined(value)) { + return '' + value; + } + if (util.isNumber(value) && (isNaN(value) || !isFinite(value))) { + return value.toString(); + } + if (util.isFunction(value) || util.isRegExp(value)) { + return value.toString(); + } + return value; +} + +function truncate(s, n) { + if (util.isString(s)) { + return s.length < n ? s : s.slice(0, n); + } else { + return s; + } +} + +function getMessage(self) { + return truncate(JSON.stringify(self.actual, replacer), 128) + ' ' + + self.operator + ' ' + + truncate(JSON.stringify(self.expected, replacer), 128); +} + +// At present only the three keys mentioned above are used and +// understood by the spec. Implementations or sub modules can pass +// other keys to the AssertionError's constructor - they will be +// ignored. + +// 3. All of the following functions must throw an AssertionError +// when a corresponding condition is not met, with a message that +// may be undefined if not provided. All assertion methods provide +// both the actual and expected values to the assertion error for +// display purposes. + +function fail(actual, expected, message, operator, stackStartFunction) { + throw new assert.AssertionError({ + message: message, + actual: actual, + expected: expected, + operator: operator, + stackStartFunction: stackStartFunction + }); +} + +// EXTENSION! allows for well behaved errors defined elsewhere. +assert.fail = fail; + +// 4. Pure assertion tests whether a value is truthy, as determined +// by !!guard. +// assert.ok(guard, message_opt); +// This statement is equivalent to assert.equal(true, !!guard, +// message_opt);. To test strictly for the value true, use +// assert.strictEqual(true, guard, message_opt);. + +function ok(value, message) { + if (!value) fail(value, true, message, '==', assert.ok); +} +assert.ok = ok; + +// 5. The equality assertion tests shallow, coercive equality with +// ==. +// assert.equal(actual, expected, message_opt); + +assert.equal = function equal(actual, expected, message) { + if (actual != expected) fail(actual, expected, message, '==', assert.equal); +}; + +// 6. The non-equality assertion tests for whether two objects are not equal +// with != assert.notEqual(actual, expected, message_opt); + +assert.notEqual = function notEqual(actual, expected, message) { + if (actual == expected) { + fail(actual, expected, message, '!=', assert.notEqual); + } +}; + +// 7. The equivalence assertion tests a deep equality relation. +// assert.deepEqual(actual, expected, message_opt); + +assert.deepEqual = function deepEqual(actual, expected, message) { + if (!_deepEqual(actual, expected)) { + fail(actual, expected, message, 'deepEqual', assert.deepEqual); + } +}; + +function _deepEqual(actual, expected) { + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + + // } else if (util.isBuffer(actual) && util.isBuffer(expected)) { + // if (actual.length != expected.length) return false; + // + // for (var i = 0; i < actual.length; i++) { + // if (actual[i] !== expected[i]) return false; + // } + // + // return true; + // + // 7.2. If the expected value is a Date object, the actual value is + // equivalent if it is also a Date object that refers to the same time. + } else if (util.isDate(actual) && util.isDate(expected)) { + return actual.getTime() === expected.getTime(); + + // 7.3 If the expected value is a RegExp object, the actual value is + // equivalent if it is also a RegExp object with the same source and + // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`). + } else if (util.isRegExp(actual) && util.isRegExp(expected)) { + return actual.source === expected.source && + actual.global === expected.global && + actual.multiline === expected.multiline && + actual.lastIndex === expected.lastIndex && + actual.ignoreCase === expected.ignoreCase; + + // 7.4. Other pairs that do not both pass typeof value == 'object', + // equivalence is determined by ==. + } else if (!util.isObject(actual) && !util.isObject(expected)) { + return actual == expected; + + // 7.5 For all other Object pairs, including Array objects, equivalence is + // determined by having the same number of owned properties (as verified + // with Object.prototype.hasOwnProperty.call), the same set of keys + // (although not necessarily the same order), equivalent values for every + // corresponding key, and an identical 'prototype' property. Note: this + // accounts for both named and indexed properties on Arrays. + } else { + return objEquiv(actual, expected); + } +} + +function isArguments(object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; +} + +function objEquiv(a, b) { + if (util.isNullOrUndefined(a) || util.isNullOrUndefined(b)) + return false; + // an identical 'prototype' property. + if (a.prototype !== b.prototype) return false; + //~~~I've managed to break Object.keys through screwy arguments passing. + // Converting to array solves the problem. + var aIsArgs = isArguments(a), + bIsArgs = isArguments(b); + if ((aIsArgs && !bIsArgs) || (!aIsArgs && bIsArgs)) + return false; + if (aIsArgs) { + a = pSlice.call(a); + b = pSlice.call(b); + return _deepEqual(a, b); + } + try { + var ka = Object.keys(a), + kb = Object.keys(b), + key, i; + } catch (e) {//happens when one is a string literal and the other isn't + return false; + } + // having the same number of owned properties (keys incorporates + // hasOwnProperty) + if (ka.length != kb.length) + return false; + //the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + //~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] != kb[i]) + return false; + } + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!_deepEqual(a[key], b[key])) return false; + } + return true; +} + +// 8. The non-equivalence assertion tests for any deep inequality. +// assert.notDeepEqual(actual, expected, message_opt); + +assert.notDeepEqual = function notDeepEqual(actual, expected, message) { + if (_deepEqual(actual, expected)) { + fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual); + } +}; + +// 9. The strict equality assertion tests strict equality, as determined by ===. +// assert.strictEqual(actual, expected, message_opt); + +assert.strictEqual = function strictEqual(actual, expected, message) { + if (actual !== expected) { + fail(actual, expected, message, '===', assert.strictEqual); + } +}; + +// 10. The strict non-equality assertion tests for strict inequality, as +// determined by !==. assert.notStrictEqual(actual, expected, message_opt); + +assert.notStrictEqual = function notStrictEqual(actual, expected, message) { + if (actual === expected) { + fail(actual, expected, message, '!==', assert.notStrictEqual); + } +}; + +function expectedException(actual, expected) { + if (!actual || !expected) { + return false; + } + + if (Object.prototype.toString.call(expected) == '[object RegExp]') { + return expected.test(actual); + } else if (actual instanceof expected) { + return true; + } else if (expected.call({}, actual) === true) { + return true; + } + + return false; +} + +function _throws(shouldThrow, block, expected, message) { + var actual; + + if (util.isString(expected)) { + message = expected; + expected = null; + } + + try { + block(); + } catch (e) { + actual = e; + } + + message = (expected && expected.name ? ' (' + expected.name + ').' : '.') + + (message ? ' ' + message : '.'); + + if (shouldThrow && !actual) { + fail(actual, expected, 'Missing expected exception' + message); + } + + if (!shouldThrow && expectedException(actual, expected)) { + fail(actual, expected, 'Got unwanted exception' + message); + } + + if ((shouldThrow && actual && expected && + !expectedException(actual, expected)) || (!shouldThrow && actual)) { + throw actual; + } +} + +// 11. Expected to throw an error: +// assert.throws(block, Error_opt, message_opt); + +assert.throws = function(block, /*optional*/error, /*optional*/message) { + _throws.apply(this, [true].concat(pSlice.call(arguments))); +}; + +// EXTENSION! This is annoying to write outside this module. +assert.doesNotThrow = function(block, /*optional*/message) { + _throws.apply(this, [false].concat(pSlice.call(arguments))); +}; + +assert.ifError = function(err) { if (err) {throw err;}}; + +module.assert = module.exports; +delete module.exports; +})(this); diff --git a/tests/automation/res/build.sh b/tests/automation/res/build.sh new file mode 100755 index 0000000000..484afb91fd --- /dev/null +++ b/tests/automation/res/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +uglifyjs mocha.js chai.js assert.js util.js -c -m -o mocha_util.js + diff --git a/tests/automation/res/chai.js b/tests/automation/res/chai.js new file mode 100644 index 0000000000..75eaecbb65 --- /dev/null +++ b/tests/automation/res/chai.js @@ -0,0 +1,4800 @@ + +;(function(){ + +/** + * Require the module at `name`. + * + * @param {String} name + * @return {Object} exports + * @api public + */ + +function require(name) { + var module = require.modules[name]; + if (!module) throw new Error('failed to require "' + name + '"'); + + if (!('exports' in module) && typeof module.definition === 'function') { + module.client = module.component = true; + module.definition.call(this, module.exports = {}, module); + delete module.definition; + } + + return module.exports; +} + +/** + * Meta info, accessible in the global scope unless you use AMD option. + */ + +require.loader = 'component'; + +/** + * Internal helper object, contains a sorting function for semantiv versioning + */ +require.helper = {}; +require.helper.semVerSort = function(a, b) { + var aArray = a.version.split('.'); + var bArray = b.version.split('.'); + for (var i=0; i bLex ? 1 : -1; + continue; + } else if (aInt > bInt) { + return 1; + } else { + return -1; + } + } + return 0; +} + +/** + * Find and require a module which name starts with the provided name. + * If multiple modules exists, the highest semver is used. + * This function can only be used for remote dependencies. + + * @param {String} name - module name: `user~repo` + * @param {Boolean} returnPath - returns the canonical require path if true, + * otherwise it returns the epxorted module + */ +require.latest = function (name, returnPath) { + function showError(name) { + throw new Error('failed to find latest module of "' + name + '"'); + } + // only remotes with semvers, ignore local files conataining a '/' + var versionRegexp = /(.*)~(.*)@v?(\d+\.\d+\.\d+[^\/]*)$/; + var remoteRegexp = /(.*)~(.*)/; + if (!remoteRegexp.test(name)) showError(name); + var moduleNames = Object.keys(require.modules); + var semVerCandidates = []; + var otherCandidates = []; // for instance: name of the git branch + for (var i=0; i 0) { + var module = semVerCandidates.sort(require.helper.semVerSort).pop().name; + if (returnPath === true) { + return module; + } + return require(module); + } + // if the build contains more than one branch of the same module + // you should not use this funciton + var module = otherCandidates.pop().name; + if (returnPath === true) { + return module; + } + return require(module); +} + +/** + * Registered modules. + */ + +require.modules = {}; + +/** + * Register module at `name` with callback `definition`. + * + * @param {String} name + * @param {Function} definition + * @api private + */ + +require.register = function (name, definition) { + require.modules[name] = { + definition: definition + }; +}; + +/** + * Define a module's exports immediately with `exports`. + * + * @param {String} name + * @param {Generic} exports + * @api private + */ + +require.define = function (name, exports) { + require.modules[name] = { + exports: exports + }; +}; +require.register("chaijs~assertion-error@1.0.0", function (exports, module) { +/*! + * assertion-error + * Copyright(c) 2013 Jake Luer + * MIT Licensed + */ + +/*! + * Return a function that will copy properties from + * one object to another excluding any originally + * listed. Returned function will create a new `{}`. + * + * @param {String} excluded properties ... + * @return {Function} + */ + +function exclude () { + var excludes = [].slice.call(arguments); + + function excludeProps (res, obj) { + Object.keys(obj).forEach(function (key) { + if (!~excludes.indexOf(key)) res[key] = obj[key]; + }); + } + + return function extendExclude () { + var args = [].slice.call(arguments) + , i = 0 + , res = {}; + + for (; i < args.length; i++) { + excludeProps(res, args[i]); + } + + return res; + }; +}; + +/*! + * Primary Exports + */ + +module.exports = AssertionError; + +/** + * ### AssertionError + * + * An extension of the JavaScript `Error` constructor for + * assertion and validation scenarios. + * + * @param {String} message + * @param {Object} properties to include (optional) + * @param {callee} start stack function (optional) + */ + +function AssertionError (message, _props, ssf) { + var extend = exclude('name', 'message', 'stack', 'constructor', 'toJSON') + , props = extend(_props || {}); + + // default values + this.message = message || 'Unspecified AssertionError'; + this.showDiff = false; + + // copy from properties + for (var key in props) { + this[key] = props[key]; + } + + // capture stack trace + ssf = ssf || arguments.callee; + if (ssf && Error.captureStackTrace) { + Error.captureStackTrace(this, ssf); + } +} + +/*! + * Inherit from Error.prototype + */ + +AssertionError.prototype = Object.create(Error.prototype); + +/*! + * Statically set name + */ + +AssertionError.prototype.name = 'AssertionError'; + +/*! + * Ensure correct constructor + */ + +AssertionError.prototype.constructor = AssertionError; + +/** + * Allow errors to be converted to JSON for static transfer. + * + * @param {Boolean} include stack (default: `true`) + * @return {Object} object that can be `JSON.stringify` + */ + +AssertionError.prototype.toJSON = function (stack) { + var extend = exclude('constructor', 'toJSON', 'stack') + , props = extend({ name: this.name }, this); + + // include stack if exists and not turned off + if (false !== stack && this.stack) { + props.stack = this.stack; + } + + return props; +}; + +}); + +require.register("chaijs~type-detect@0.1.1", function (exports, module) { +/*! + * type-detect + * Copyright(c) 2013 jake luer + * MIT Licensed + */ + +/*! + * Primary Exports + */ + +var exports = module.exports = getType; + +/*! + * Detectable javascript natives + */ + +var natives = { + '[object Array]': 'array' + , '[object RegExp]': 'regexp' + , '[object Function]': 'function' + , '[object Arguments]': 'arguments' + , '[object Date]': 'date' +}; + +/** + * ### typeOf (obj) + * + * Use several different techniques to determine + * the type of object being tested. + * + * + * @param {Mixed} object + * @return {String} object type + * @api public + */ + +function getType (obj) { + var str = Object.prototype.toString.call(obj); + if (natives[str]) return natives[str]; + if (obj === null) return 'null'; + if (obj === undefined) return 'undefined'; + if (obj === Object(obj)) return 'object'; + return typeof obj; +} + +exports.Library = Library; + +/** + * ### Library + * + * Create a repository for custom type detection. + * + * ```js + * var lib = new type.Library; + * ``` + * + */ + +function Library () { + this.tests = {}; +} + +/** + * #### .of (obj) + * + * Expose replacement `typeof` detection to the library. + * + * ```js + * if ('string' === lib.of('hello world')) { + * // ... + * } + * ``` + * + * @param {Mixed} object to test + * @return {String} type + */ + +Library.prototype.of = getType; + +/** + * #### .define (type, test) + * + * Add a test to for the `.test()` assertion. + * + * Can be defined as a regular expression: + * + * ```js + * lib.define('int', /^[0-9]+$/); + * ``` + * + * ... or as a function: + * + * ```js + * lib.define('bln', function (obj) { + * if ('boolean' === lib.of(obj)) return true; + * var blns = [ 'yes', 'no', 'true', 'false', 1, 0 ]; + * if ('string' === lib.of(obj)) obj = obj.toLowerCase(); + * return !! ~blns.indexOf(obj); + * }); + * ``` + * + * @param {String} type + * @param {RegExp|Function} test + * @api public + */ + +Library.prototype.define = function (type, test) { + if (arguments.length === 1) return this.tests[type]; + this.tests[type] = test; + return this; +}; + +/** + * #### .test (obj, test) + * + * Assert that an object is of type. Will first + * check natives, and if that does not pass it will + * use the user defined custom tests. + * + * ```js + * assert(lib.test('1', 'int')); + * assert(lib.test('yes', 'bln')); + * ``` + * + * @param {Mixed} object + * @param {String} type + * @return {Boolean} result + * @api public + */ + +Library.prototype.test = function (obj, type) { + if (type === getType(obj)) return true; + var test = this.tests[type]; + + if (test && 'regexp' === getType(test)) { + return test.test(obj); + } else if (test && 'function' === getType(test)) { + return test(obj); + } else { + throw new ReferenceError('Type test "' + type + '" not defined or invalid.'); + } +}; + +}); + +require.register("chaijs~deep-eql@0.1.3", function (exports, module) { +/*! + * deep-eql + * Copyright(c) 2013 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependencies + */ + +var type = require('chaijs~type-detect@0.1.1'); + +/*! + * Buffer.isBuffer browser shim + */ + +var Buffer; +try { Buffer = require('buffer').Buffer; } +catch(ex) { + Buffer = {}; + Buffer.isBuffer = function() { return false; } +} + +/*! + * Primary Export + */ + +module.exports = deepEqual; + +/** + * Assert super-strict (egal) equality between + * two objects of any type. + * + * @param {Mixed} a + * @param {Mixed} b + * @param {Array} memoised (optional) + * @return {Boolean} equal match + */ + +function deepEqual(a, b, m) { + if (sameValue(a, b)) { + return true; + } else if ('date' === type(a)) { + return dateEqual(a, b); + } else if ('regexp' === type(a)) { + return regexpEqual(a, b); + } else if (Buffer.isBuffer(a)) { + return bufferEqual(a, b); + } else if ('arguments' === type(a)) { + return argumentsEqual(a, b, m); + } else if (!typeEqual(a, b)) { + return false; + } else if (('object' !== type(a) && 'object' !== type(b)) + && ('array' !== type(a) && 'array' !== type(b))) { + return sameValue(a, b); + } else { + return objectEqual(a, b, m); + } +} + +/*! + * Strict (egal) equality test. Ensures that NaN always + * equals NaN and `-0` does not equal `+0`. + * + * @param {Mixed} a + * @param {Mixed} b + * @return {Boolean} equal match + */ + +function sameValue(a, b) { + if (a === b) return a !== 0 || 1 / a === 1 / b; + return a !== a && b !== b; +} + +/*! + * Compare the types of two given objects and + * return if they are equal. Note that an Array + * has a type of `array` (not `object`) and arguments + * have a type of `arguments` (not `array`/`object`). + * + * @param {Mixed} a + * @param {Mixed} b + * @return {Boolean} result + */ + +function typeEqual(a, b) { + return type(a) === type(b); +} + +/*! + * Compare two Date objects by asserting that + * the time values are equal using `saveValue`. + * + * @param {Date} a + * @param {Date} b + * @return {Boolean} result + */ + +function dateEqual(a, b) { + if ('date' !== type(b)) return false; + return sameValue(a.getTime(), b.getTime()); +} + +/*! + * Compare two regular expressions by converting them + * to string and checking for `sameValue`. + * + * @param {RegExp} a + * @param {RegExp} b + * @return {Boolean} result + */ + +function regexpEqual(a, b) { + if ('regexp' !== type(b)) return false; + return sameValue(a.toString(), b.toString()); +} + +/*! + * Assert deep equality of two `arguments` objects. + * Unfortunately, these must be sliced to arrays + * prior to test to ensure no bad behavior. + * + * @param {Arguments} a + * @param {Arguments} b + * @param {Array} memoize (optional) + * @return {Boolean} result + */ + +function argumentsEqual(a, b, m) { + if ('arguments' !== type(b)) return false; + a = [].slice.call(a); + b = [].slice.call(b); + return deepEqual(a, b, m); +} + +/*! + * Get enumerable properties of a given object. + * + * @param {Object} a + * @return {Array} property names + */ + +function enumerable(a) { + var res = []; + for (var key in a) res.push(key); + return res; +} + +/*! + * Simple equality for flat iterable objects + * such as Arrays or Node.js buffers. + * + * @param {Iterable} a + * @param {Iterable} b + * @return {Boolean} result + */ + +function iterableEqual(a, b) { + if (a.length !== b.length) return false; + + var i = 0; + var match = true; + + for (; i < a.length; i++) { + if (a[i] !== b[i]) { + match = false; + break; + } + } + + return match; +} + +/*! + * Extension to `iterableEqual` specifically + * for Node.js Buffers. + * + * @param {Buffer} a + * @param {Mixed} b + * @return {Boolean} result + */ + +function bufferEqual(a, b) { + if (!Buffer.isBuffer(b)) return false; + return iterableEqual(a, b); +} + +/*! + * Block for `objectEqual` ensuring non-existing + * values don't get in. + * + * @param {Mixed} object + * @return {Boolean} result + */ + +function isValue(a) { + return a !== null && a !== undefined; +} + +/*! + * Recursively check the equality of two objects. + * Once basic sameness has been established it will + * defer to `deepEqual` for each enumerable key + * in the object. + * + * @param {Mixed} a + * @param {Mixed} b + * @return {Boolean} result + */ + +function objectEqual(a, b, m) { + if (!isValue(a) || !isValue(b)) { + return false; + } + + if (a.prototype !== b.prototype) { + return false; + } + + var i; + if (m) { + for (i = 0; i < m.length; i++) { + if ((m[i][0] === a && m[i][1] === b) + || (m[i][0] === b && m[i][1] === a)) { + return true; + } + } + } else { + m = []; + } + + try { + var ka = enumerable(a); + var kb = enumerable(b); + } catch (ex) { + return false; + } + + ka.sort(); + kb.sort(); + + if (!iterableEqual(ka, kb)) { + return false; + } + + m.push([ a, b ]); + + var key; + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!deepEqual(a[key], b[key], m)) { + return false; + } + } + + return true; +} + +}); + +require.register("chai", function (exports, module) { +module.exports = require('chai/lib/chai.js'); + +}); + +require.register("chai/lib/chai.js", function (exports, module) { +/*! + * chai + * Copyright(c) 2011-2014 Jake Luer + * MIT Licensed + */ + +var used = [] + , exports = module.exports = {}; + +/*! + * Chai version + */ + +exports.version = '1.10.0'; + +/*! + * Assertion Error + */ + +exports.AssertionError = require('chaijs~assertion-error@1.0.0'); + +/*! + * Utils for plugins (not exported) + */ + +var util = require('chai/lib/chai/utils/index.js'); + +/** + * # .use(function) + * + * Provides a way to extend the internals of Chai + * + * @param {Function} + * @returns {this} for chaining + * @api public + */ + +exports.use = function (fn) { + if (!~used.indexOf(fn)) { + fn(this, util); + used.push(fn); + } + + return this; +}; + +/*! + * Configuration + */ + +var config = require('chai/lib/chai/config.js'); +exports.config = config; + +/*! + * Primary `Assertion` prototype + */ + +var assertion = require('chai/lib/chai/assertion.js'); +exports.use(assertion); + +/*! + * Core Assertions + */ + +var core = require('chai/lib/chai/core/assertions.js'); +exports.use(core); + +/*! + * Expect interface + */ + +var expect = require('chai/lib/chai/interface/expect.js'); +exports.use(expect); + +/*! + * Should interface + */ + +var should = require('chai/lib/chai/interface/should.js'); +exports.use(should); + +/*! + * Assert interface + */ + +var assert = require('chai/lib/chai/interface/assert.js'); +exports.use(assert); + +}); + +require.register("chai/lib/chai/assertion.js", function (exports, module) { +/*! + * chai + * http://chaijs.com + * Copyright(c) 2011-2014 Jake Luer + * MIT Licensed + */ + +var config = require('chai/lib/chai/config.js'); +var NOOP = function() { }; + +module.exports = function (_chai, util) { + /*! + * Module dependencies. + */ + + var AssertionError = _chai.AssertionError + , flag = util.flag; + + /*! + * Module export. + */ + + _chai.Assertion = Assertion; + + /*! + * Assertion Constructor + * + * Creates object for chaining. + * + * @api private + */ + + function Assertion (obj, msg, stack) { + flag(this, 'ssfi', stack || arguments.callee); + flag(this, 'object', obj); + flag(this, 'message', msg); + } + + Object.defineProperty(Assertion, 'includeStack', { + get: function() { + console.warn('Assertion.includeStack is deprecated, use chai.config.includeStack instead.'); + return config.includeStack; + }, + set: function(value) { + console.warn('Assertion.includeStack is deprecated, use chai.config.includeStack instead.'); + config.includeStack = value; + } + }); + + Object.defineProperty(Assertion, 'showDiff', { + get: function() { + console.warn('Assertion.showDiff is deprecated, use chai.config.showDiff instead.'); + return config.showDiff; + }, + set: function(value) { + console.warn('Assertion.showDiff is deprecated, use chai.config.showDiff instead.'); + config.showDiff = value; + } + }); + + Assertion.addProperty = function (name, fn) { + util.addProperty(this.prototype, name, fn); + }; + + Assertion.addMethod = function (name, fn) { + util.addMethod(this.prototype, name, fn); + }; + + Assertion.addChainableMethod = function (name, fn, chainingBehavior) { + util.addChainableMethod(this.prototype, name, fn, chainingBehavior); + }; + + Assertion.addChainableNoop = function(name, fn) { + util.addChainableMethod(this.prototype, name, NOOP, fn); + }; + + Assertion.overwriteProperty = function (name, fn) { + util.overwriteProperty(this.prototype, name, fn); + }; + + Assertion.overwriteMethod = function (name, fn) { + util.overwriteMethod(this.prototype, name, fn); + }; + + Assertion.overwriteChainableMethod = function (name, fn, chainingBehavior) { + util.overwriteChainableMethod(this.prototype, name, fn, chainingBehavior); + }; + + /*! + * ### .assert(expression, message, negateMessage, expected, actual) + * + * Executes an expression and check expectations. Throws AssertionError for reporting if test doesn't pass. + * + * @name assert + * @param {Philosophical} expression to be tested + * @param {String or Function} message or function that returns message to display if fails + * @param {String or Function} negatedMessage or function that returns negatedMessage to display if negated expression fails + * @param {Mixed} expected value (remember to check for negation) + * @param {Mixed} actual (optional) will default to `this.obj` + * @api private + */ + + Assertion.prototype.assert = function (expr, msg, negateMsg, expected, _actual, showDiff) { + var ok = util.test(this, arguments); + if (true !== showDiff) showDiff = false; + if (true !== config.showDiff) showDiff = false; + + if (!ok) { + var msg = util.getMessage(this, arguments) + , actual = util.getActual(this, arguments); + throw new AssertionError(msg, { + actual: actual + , expected: expected + , showDiff: showDiff + }, (config.includeStack) ? this.assert : flag(this, 'ssfi')); + } + }; + + /*! + * ### ._obj + * + * Quick reference to stored `actual` value for plugin developers. + * + * @api private + */ + + Object.defineProperty(Assertion.prototype, '_obj', + { get: function () { + return flag(this, 'object'); + } + , set: function (val) { + flag(this, 'object', val); + } + }); +}; + +}); + +require.register("chai/lib/chai/config.js", function (exports, module) { +module.exports = { + + /** + * ### config.includeStack + * + * User configurable property, influences whether stack trace + * is included in Assertion error message. Default of false + * suppresses stack trace in the error message. + * + * chai.config.includeStack = true; // enable stack on error + * + * @param {Boolean} + * @api public + */ + + includeStack: false, + + /** + * ### config.showDiff + * + * User configurable property, influences whether or not + * the `showDiff` flag should be included in the thrown + * AssertionErrors. `false` will always be `false`; `true` + * will be true when the assertion has requested a diff + * be shown. + * + * @param {Boolean} + * @api public + */ + + showDiff: true, + + /** + * ### config.truncateThreshold + * + * User configurable property, sets length threshold for actual and + * expected values in assertion errors. If this threshold is exceeded, + * the value is truncated. + * + * Set it to zero if you want to disable truncating altogether. + * + * chai.config.truncateThreshold = 0; // disable truncating + * + * @param {Number} + * @api public + */ + + truncateThreshold: 40 + +}; + +}); + +require.register("chai/lib/chai/core/assertions.js", function (exports, module) { +/*! + * chai + * http://chaijs.com + * Copyright(c) 2011-2014 Jake Luer + * MIT Licensed + */ + +module.exports = function (chai, _) { + var Assertion = chai.Assertion + , toString = Object.prototype.toString + , flag = _.flag; + + /** + * ### Language Chains + * + * The following are provided as chainable getters to + * improve the readability of your assertions. They + * do not provide testing capabilities unless they + * have been overwritten by a plugin. + * + * **Chains** + * + * - to + * - be + * - been + * - is + * - that + * - and + * - has + * - have + * - with + * - at + * - of + * - same + * + * @name language chains + * @api public + */ + + [ 'to', 'be', 'been' + , 'is', 'and', 'has', 'have' + , 'with', 'that', 'at' + , 'of', 'same' ].forEach(function (chain) { + Assertion.addProperty(chain, function () { + return this; + }); + }); + + /** + * ### .not + * + * Negates any of assertions following in the chain. + * + * expect(foo).to.not.equal('bar'); + * expect(goodFn).to.not.throw(Error); + * expect({ foo: 'baz' }).to.have.property('foo') + * .and.not.equal('bar'); + * + * @name not + * @api public + */ + + Assertion.addProperty('not', function () { + flag(this, 'negate', true); + }); + + /** + * ### .deep + * + * Sets the `deep` flag, later used by the `equal` and + * `property` assertions. + * + * expect(foo).to.deep.equal({ bar: 'baz' }); + * expect({ foo: { bar: { baz: 'quux' } } }) + * .to.have.deep.property('foo.bar.baz', 'quux'); + * + * @name deep + * @api public + */ + + Assertion.addProperty('deep', function () { + flag(this, 'deep', true); + }); + + /** + * ### .a(type) + * + * The `a` and `an` assertions are aliases that can be + * used either as language chains or to assert a value's + * type. + * + * // typeof + * expect('test').to.be.a('string'); + * expect({ foo: 'bar' }).to.be.an('object'); + * expect(null).to.be.a('null'); + * expect(undefined).to.be.an('undefined'); + * + * // language chain + * expect(foo).to.be.an.instanceof(Foo); + * + * @name a + * @alias an + * @param {String} type + * @param {String} message _optional_ + * @api public + */ + + function an (type, msg) { + if (msg) flag(this, 'message', msg); + type = type.toLowerCase(); + var obj = flag(this, 'object') + , article = ~[ 'a', 'e', 'i', 'o', 'u' ].indexOf(type.charAt(0)) ? 'an ' : 'a '; + + this.assert( + type === _.type(obj) + , 'expected #{this} to be ' + article + type + , 'expected #{this} not to be ' + article + type + ); + } + + Assertion.addChainableMethod('an', an); + Assertion.addChainableMethod('a', an); + + /** + * ### .include(value) + * + * The `include` and `contain` assertions can be used as either property + * based language chains or as methods to assert the inclusion of an object + * in an array or a substring in a string. When used as language chains, + * they toggle the `contain` flag for the `keys` assertion. + * + * expect([1,2,3]).to.include(2); + * expect('foobar').to.contain('foo'); + * expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo'); + * + * @name include + * @alias contain + * @param {Object|String|Number} obj + * @param {String} message _optional_ + * @api public + */ + + function includeChainingBehavior () { + flag(this, 'contains', true); + } + + function include (val, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + var expected = false; + if (_.type(obj) === 'array' && _.type(val) === 'object') { + for (var i in obj) { + if (_.eql(obj[i], val)) { + expected = true; + break; + } + } + } else if (_.type(val) === 'object') { + if (!flag(this, 'negate')) { + for (var k in val) new Assertion(obj).property(k, val[k]); + return; + } + var subset = {} + for (var k in val) subset[k] = obj[k] + expected = _.eql(subset, val); + } else { + expected = obj && ~obj.indexOf(val) + } + this.assert( + expected + , 'expected #{this} to include ' + _.inspect(val) + , 'expected #{this} to not include ' + _.inspect(val)); + } + + Assertion.addChainableMethod('include', include, includeChainingBehavior); + Assertion.addChainableMethod('contain', include, includeChainingBehavior); + + /** + * ### .ok + * + * Asserts that the target is truthy. + * + * expect('everthing').to.be.ok; + * expect(1).to.be.ok; + * expect(false).to.not.be.ok; + * expect(undefined).to.not.be.ok; + * expect(null).to.not.be.ok; + * + * Can also be used as a function, which prevents some linter errors. + * + * expect('everthing').to.be.ok(); + * + * @name ok + * @api public + */ + + Assertion.addChainableNoop('ok', function () { + this.assert( + flag(this, 'object') + , 'expected #{this} to be truthy' + , 'expected #{this} to be falsy'); + }); + + /** + * ### .true + * + * Asserts that the target is `true`. + * + * expect(true).to.be.true; + * expect(1).to.not.be.true; + * + * Can also be used as a function, which prevents some linter errors. + * + * expect(true).to.be.true(); + * + * @name true + * @api public + */ + + Assertion.addChainableNoop('true', function () { + this.assert( + true === flag(this, 'object') + , 'expected #{this} to be true' + , 'expected #{this} to be false' + , this.negate ? false : true + ); + }); + + /** + * ### .false + * + * Asserts that the target is `false`. + * + * expect(false).to.be.false; + * expect(0).to.not.be.false; + * + * Can also be used as a function, which prevents some linter errors. + * + * expect(false).to.be.false(); + * + * @name false + * @api public + */ + + Assertion.addChainableNoop('false', function () { + this.assert( + false === flag(this, 'object') + , 'expected #{this} to be false' + , 'expected #{this} to be true' + , this.negate ? true : false + ); + }); + + /** + * ### .null + * + * Asserts that the target is `null`. + * + * expect(null).to.be.null; + * expect(undefined).not.to.be.null; + * + * Can also be used as a function, which prevents some linter errors. + * + * expect(null).to.be.null(); + * + * @name null + * @api public + */ + + Assertion.addChainableNoop('null', function () { + this.assert( + null === flag(this, 'object') + , 'expected #{this} to be null' + , 'expected #{this} not to be null' + ); + }); + + /** + * ### .undefined + * + * Asserts that the target is `undefined`. + * + * expect(undefined).to.be.undefined; + * expect(null).to.not.be.undefined; + * + * Can also be used as a function, which prevents some linter errors. + * + * expect(undefined).to.be.undefined(); + * + * @name undefined + * @api public + */ + + Assertion.addChainableNoop('undefined', function () { + this.assert( + undefined === flag(this, 'object') + , 'expected #{this} to be undefined' + , 'expected #{this} not to be undefined' + ); + }); + + /** + * ### .exist + * + * Asserts that the target is neither `null` nor `undefined`. + * + * var foo = 'hi' + * , bar = null + * , baz; + * + * expect(foo).to.exist; + * expect(bar).to.not.exist; + * expect(baz).to.not.exist; + * + * Can also be used as a function, which prevents some linter errors. + * + * expect(foo).to.exist(); + * + * @name exist + * @api public + */ + + Assertion.addChainableNoop('exist', function () { + this.assert( + null != flag(this, 'object') + , 'expected #{this} to exist' + , 'expected #{this} to not exist' + ); + }); + + + /** + * ### .empty + * + * Asserts that the target's length is `0`. For arrays, it checks + * the `length` property. For objects, it gets the count of + * enumerable keys. + * + * expect([]).to.be.empty; + * expect('').to.be.empty; + * expect({}).to.be.empty; + * + * Can also be used as a function, which prevents some linter errors. + * + * expect([]).to.be.empty(); + * + * @name empty + * @api public + */ + + Assertion.addChainableNoop('empty', function () { + var obj = flag(this, 'object') + , expected = obj; + + if (Array.isArray(obj) || 'string' === typeof object) { + expected = obj.length; + } else if (typeof obj === 'object') { + expected = Object.keys(obj).length; + } + + this.assert( + !expected + , 'expected #{this} to be empty' + , 'expected #{this} not to be empty' + ); + }); + + /** + * ### .arguments + * + * Asserts that the target is an arguments object. + * + * function test () { + * expect(arguments).to.be.arguments; + * } + * + * Can also be used as a function, which prevents some linter errors. + * + * function test () { + * expect(arguments).to.be.arguments(); + * } + * + * @name arguments + * @alias Arguments + * @api public + */ + + function checkArguments () { + var obj = flag(this, 'object') + , type = Object.prototype.toString.call(obj); + this.assert( + '[object Arguments]' === type + , 'expected #{this} to be arguments but got ' + type + , 'expected #{this} to not be arguments' + ); + } + + Assertion.addChainableNoop('arguments', checkArguments); + Assertion.addChainableNoop('Arguments', checkArguments); + + /** + * ### .equal(value) + * + * Asserts that the target is strictly equal (`===`) to `value`. + * Alternately, if the `deep` flag is set, asserts that + * the target is deeply equal to `value`. + * + * expect('hello').to.equal('hello'); + * expect(42).to.equal(42); + * expect(1).to.not.equal(true); + * expect({ foo: 'bar' }).to.not.equal({ foo: 'bar' }); + * expect({ foo: 'bar' }).to.deep.equal({ foo: 'bar' }); + * + * @name equal + * @alias equals + * @alias eq + * @alias deep.equal + * @param {Mixed} value + * @param {String} message _optional_ + * @api public + */ + + function assertEqual (val, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + if (flag(this, 'deep')) { + return this.eql(val); + } else { + this.assert( + val === obj + , 'expected #{this} to equal #{exp}' + , 'expected #{this} to not equal #{exp}' + , val + , this._obj + , true + ); + } + } + + Assertion.addMethod('equal', assertEqual); + Assertion.addMethod('equals', assertEqual); + Assertion.addMethod('eq', assertEqual); + + /** + * ### .eql(value) + * + * Asserts that the target is deeply equal to `value`. + * + * expect({ foo: 'bar' }).to.eql({ foo: 'bar' }); + * expect([ 1, 2, 3 ]).to.eql([ 1, 2, 3 ]); + * + * @name eql + * @alias eqls + * @param {Mixed} value + * @param {String} message _optional_ + * @api public + */ + + function assertEql(obj, msg) { + if (msg) flag(this, 'message', msg); + this.assert( + _.eql(obj, flag(this, 'object')) + , 'expected #{this} to deeply equal #{exp}' + , 'expected #{this} to not deeply equal #{exp}' + , obj + , this._obj + , true + ); + } + + Assertion.addMethod('eql', assertEql); + Assertion.addMethod('eqls', assertEql); + + /** + * ### .above(value) + * + * Asserts that the target is greater than `value`. + * + * expect(10).to.be.above(5); + * + * Can also be used in conjunction with `length` to + * assert a minimum length. The benefit being a + * more informative error message than if the length + * was supplied directly. + * + * expect('foo').to.have.length.above(2); + * expect([ 1, 2, 3 ]).to.have.length.above(2); + * + * @name above + * @alias gt + * @alias greaterThan + * @param {Number} value + * @param {String} message _optional_ + * @api public + */ + + function assertAbove (n, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + if (flag(this, 'doLength')) { + new Assertion(obj, msg).to.have.property('length'); + var len = obj.length; + this.assert( + len > n + , 'expected #{this} to have a length above #{exp} but got #{act}' + , 'expected #{this} to not have a length above #{exp}' + , n + , len + ); + } else { + this.assert( + obj > n + , 'expected #{this} to be above ' + n + , 'expected #{this} to be at most ' + n + ); + } + } + + Assertion.addMethod('above', assertAbove); + Assertion.addMethod('gt', assertAbove); + Assertion.addMethod('greaterThan', assertAbove); + + /** + * ### .least(value) + * + * Asserts that the target is greater than or equal to `value`. + * + * expect(10).to.be.at.least(10); + * + * Can also be used in conjunction with `length` to + * assert a minimum length. The benefit being a + * more informative error message than if the length + * was supplied directly. + * + * expect('foo').to.have.length.of.at.least(2); + * expect([ 1, 2, 3 ]).to.have.length.of.at.least(3); + * + * @name least + * @alias gte + * @param {Number} value + * @param {String} message _optional_ + * @api public + */ + + function assertLeast (n, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + if (flag(this, 'doLength')) { + new Assertion(obj, msg).to.have.property('length'); + var len = obj.length; + this.assert( + len >= n + , 'expected #{this} to have a length at least #{exp} but got #{act}' + , 'expected #{this} to have a length below #{exp}' + , n + , len + ); + } else { + this.assert( + obj >= n + , 'expected #{this} to be at least ' + n + , 'expected #{this} to be below ' + n + ); + } + } + + Assertion.addMethod('least', assertLeast); + Assertion.addMethod('gte', assertLeast); + + /** + * ### .below(value) + * + * Asserts that the target is less than `value`. + * + * expect(5).to.be.below(10); + * + * Can also be used in conjunction with `length` to + * assert a maximum length. The benefit being a + * more informative error message than if the length + * was supplied directly. + * + * expect('foo').to.have.length.below(4); + * expect([ 1, 2, 3 ]).to.have.length.below(4); + * + * @name below + * @alias lt + * @alias lessThan + * @param {Number} value + * @param {String} message _optional_ + * @api public + */ + + function assertBelow (n, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + if (flag(this, 'doLength')) { + new Assertion(obj, msg).to.have.property('length'); + var len = obj.length; + this.assert( + len < n + , 'expected #{this} to have a length below #{exp} but got #{act}' + , 'expected #{this} to not have a length below #{exp}' + , n + , len + ); + } else { + this.assert( + obj < n + , 'expected #{this} to be below ' + n + , 'expected #{this} to be at least ' + n + ); + } + } + + Assertion.addMethod('below', assertBelow); + Assertion.addMethod('lt', assertBelow); + Assertion.addMethod('lessThan', assertBelow); + + /** + * ### .most(value) + * + * Asserts that the target is less than or equal to `value`. + * + * expect(5).to.be.at.most(5); + * + * Can also be used in conjunction with `length` to + * assert a maximum length. The benefit being a + * more informative error message than if the length + * was supplied directly. + * + * expect('foo').to.have.length.of.at.most(4); + * expect([ 1, 2, 3 ]).to.have.length.of.at.most(3); + * + * @name most + * @alias lte + * @param {Number} value + * @param {String} message _optional_ + * @api public + */ + + function assertMost (n, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + if (flag(this, 'doLength')) { + new Assertion(obj, msg).to.have.property('length'); + var len = obj.length; + this.assert( + len <= n + , 'expected #{this} to have a length at most #{exp} but got #{act}' + , 'expected #{this} to have a length above #{exp}' + , n + , len + ); + } else { + this.assert( + obj <= n + , 'expected #{this} to be at most ' + n + , 'expected #{this} to be above ' + n + ); + } + } + + Assertion.addMethod('most', assertMost); + Assertion.addMethod('lte', assertMost); + + /** + * ### .within(start, finish) + * + * Asserts that the target is within a range. + * + * expect(7).to.be.within(5,10); + * + * Can also be used in conjunction with `length` to + * assert a length range. The benefit being a + * more informative error message than if the length + * was supplied directly. + * + * expect('foo').to.have.length.within(2,4); + * expect([ 1, 2, 3 ]).to.have.length.within(2,4); + * + * @name within + * @param {Number} start lowerbound inclusive + * @param {Number} finish upperbound inclusive + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('within', function (start, finish, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object') + , range = start + '..' + finish; + if (flag(this, 'doLength')) { + new Assertion(obj, msg).to.have.property('length'); + var len = obj.length; + this.assert( + len >= start && len <= finish + , 'expected #{this} to have a length within ' + range + , 'expected #{this} to not have a length within ' + range + ); + } else { + this.assert( + obj >= start && obj <= finish + , 'expected #{this} to be within ' + range + , 'expected #{this} to not be within ' + range + ); + } + }); + + /** + * ### .instanceof(constructor) + * + * Asserts that the target is an instance of `constructor`. + * + * var Tea = function (name) { this.name = name; } + * , Chai = new Tea('chai'); + * + * expect(Chai).to.be.an.instanceof(Tea); + * expect([ 1, 2, 3 ]).to.be.instanceof(Array); + * + * @name instanceof + * @param {Constructor} constructor + * @param {String} message _optional_ + * @alias instanceOf + * @api public + */ + + function assertInstanceOf (constructor, msg) { + if (msg) flag(this, 'message', msg); + var name = _.getName(constructor); + this.assert( + flag(this, 'object') instanceof constructor + , 'expected #{this} to be an instance of ' + name + , 'expected #{this} to not be an instance of ' + name + ); + }; + + Assertion.addMethod('instanceof', assertInstanceOf); + Assertion.addMethod('instanceOf', assertInstanceOf); + + /** + * ### .property(name, [value]) + * + * Asserts that the target has a property `name`, optionally asserting that + * the value of that property is strictly equal to `value`. + * If the `deep` flag is set, you can use dot- and bracket-notation for deep + * references into objects and arrays. + * + * // simple referencing + * var obj = { foo: 'bar' }; + * expect(obj).to.have.property('foo'); + * expect(obj).to.have.property('foo', 'bar'); + * + * // deep referencing + * var deepObj = { + * green: { tea: 'matcha' } + * , teas: [ 'chai', 'matcha', { tea: 'konacha' } ] + * }; + + * expect(deepObj).to.have.deep.property('green.tea', 'matcha'); + * expect(deepObj).to.have.deep.property('teas[1]', 'matcha'); + * expect(deepObj).to.have.deep.property('teas[2].tea', 'konacha'); + * + * You can also use an array as the starting point of a `deep.property` + * assertion, or traverse nested arrays. + * + * var arr = [ + * [ 'chai', 'matcha', 'konacha' ] + * , [ { tea: 'chai' } + * , { tea: 'matcha' } + * , { tea: 'konacha' } ] + * ]; + * + * expect(arr).to.have.deep.property('[0][1]', 'matcha'); + * expect(arr).to.have.deep.property('[1][2].tea', 'konacha'); + * + * Furthermore, `property` changes the subject of the assertion + * to be the value of that property from the original object. This + * permits for further chainable assertions on that property. + * + * expect(obj).to.have.property('foo') + * .that.is.a('string'); + * expect(deepObj).to.have.property('green') + * .that.is.an('object') + * .that.deep.equals({ tea: 'matcha' }); + * expect(deepObj).to.have.property('teas') + * .that.is.an('array') + * .with.deep.property('[2]') + * .that.deep.equals({ tea: 'konacha' }); + * + * @name property + * @alias deep.property + * @param {String} name + * @param {Mixed} value (optional) + * @param {String} message _optional_ + * @returns value of property for chaining + * @api public + */ + + Assertion.addMethod('property', function (name, val, msg) { + if (msg) flag(this, 'message', msg); + + var descriptor = flag(this, 'deep') ? 'deep property ' : 'property ' + , negate = flag(this, 'negate') + , obj = flag(this, 'object') + , value = flag(this, 'deep') + ? _.getPathValue(name, obj) + : obj[name]; + + if (negate && undefined !== val) { + if (undefined === value) { + msg = (msg != null) ? msg + ': ' : ''; + throw new Error(msg + _.inspect(obj) + ' has no ' + descriptor + _.inspect(name)); + } + } else { + this.assert( + undefined !== value + , 'expected #{this} to have a ' + descriptor + _.inspect(name) + , 'expected #{this} to not have ' + descriptor + _.inspect(name)); + } + + if (undefined !== val) { + this.assert( + val === value + , 'expected #{this} to have a ' + descriptor + _.inspect(name) + ' of #{exp}, but got #{act}' + , 'expected #{this} to not have a ' + descriptor + _.inspect(name) + ' of #{act}' + , val + , value + ); + } + + flag(this, 'object', value); + }); + + + /** + * ### .ownProperty(name) + * + * Asserts that the target has an own property `name`. + * + * expect('test').to.have.ownProperty('length'); + * + * @name ownProperty + * @alias haveOwnProperty + * @param {String} name + * @param {String} message _optional_ + * @api public + */ + + function assertOwnProperty (name, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + this.assert( + obj.hasOwnProperty(name) + , 'expected #{this} to have own property ' + _.inspect(name) + , 'expected #{this} to not have own property ' + _.inspect(name) + ); + } + + Assertion.addMethod('ownProperty', assertOwnProperty); + Assertion.addMethod('haveOwnProperty', assertOwnProperty); + + /** + * ### .length(value) + * + * Asserts that the target's `length` property has + * the expected value. + * + * expect([ 1, 2, 3]).to.have.length(3); + * expect('foobar').to.have.length(6); + * + * Can also be used as a chain precursor to a value + * comparison for the length property. + * + * expect('foo').to.have.length.above(2); + * expect([ 1, 2, 3 ]).to.have.length.above(2); + * expect('foo').to.have.length.below(4); + * expect([ 1, 2, 3 ]).to.have.length.below(4); + * expect('foo').to.have.length.within(2,4); + * expect([ 1, 2, 3 ]).to.have.length.within(2,4); + * + * @name length + * @alias lengthOf + * @param {Number} length + * @param {String} message _optional_ + * @api public + */ + + function assertLengthChain () { + flag(this, 'doLength', true); + } + + function assertLength (n, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + new Assertion(obj, msg).to.have.property('length'); + var len = obj.length; + + this.assert( + len == n + , 'expected #{this} to have a length of #{exp} but got #{act}' + , 'expected #{this} to not have a length of #{act}' + , n + , len + ); + } + + Assertion.addChainableMethod('length', assertLength, assertLengthChain); + Assertion.addMethod('lengthOf', assertLength); + + /** + * ### .match(regexp) + * + * Asserts that the target matches a regular expression. + * + * expect('foobar').to.match(/^foo/); + * + * @name match + * @param {RegExp} RegularExpression + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('match', function (re, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + this.assert( + re.exec(obj) + , 'expected #{this} to match ' + re + , 'expected #{this} not to match ' + re + ); + }); + + /** + * ### .string(string) + * + * Asserts that the string target contains another string. + * + * expect('foobar').to.have.string('bar'); + * + * @name string + * @param {String} string + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('string', function (str, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + new Assertion(obj, msg).is.a('string'); + + this.assert( + ~obj.indexOf(str) + , 'expected #{this} to contain ' + _.inspect(str) + , 'expected #{this} to not contain ' + _.inspect(str) + ); + }); + + + /** + * ### .keys(key1, [key2], [...]) + * + * Asserts that the target has exactly the given keys, or + * asserts the inclusion of some keys when using the + * `include` or `contain` modifiers. + * + * expect({ foo: 1, bar: 2 }).to.have.keys(['foo', 'bar']); + * expect({ foo: 1, bar: 2, baz: 3 }).to.contain.keys('foo', 'bar'); + * + * @name keys + * @alias key + * @param {String...|Array} keys + * @api public + */ + + function assertKeys (keys) { + var obj = flag(this, 'object') + , str + , ok = true; + + keys = keys instanceof Array + ? keys + : Array.prototype.slice.call(arguments); + + if (!keys.length) throw new Error('keys required'); + + var actual = Object.keys(obj) + , expected = keys + , len = keys.length; + + // Inclusion + ok = keys.every(function(key){ + return ~actual.indexOf(key); + }); + + // Strict + if (!flag(this, 'negate') && !flag(this, 'contains')) { + ok = ok && keys.length == actual.length; + } + + // Key string + if (len > 1) { + keys = keys.map(function(key){ + return _.inspect(key); + }); + var last = keys.pop(); + str = keys.join(', ') + ', and ' + last; + } else { + str = _.inspect(keys[0]); + } + + // Form + str = (len > 1 ? 'keys ' : 'key ') + str; + + // Have / include + str = (flag(this, 'contains') ? 'contain ' : 'have ') + str; + + // Assertion + this.assert( + ok + , 'expected #{this} to ' + str + , 'expected #{this} to not ' + str + , expected.sort() + , actual.sort() + , true + ); + } + + Assertion.addMethod('keys', assertKeys); + Assertion.addMethod('key', assertKeys); + + /** + * ### .throw(constructor) + * + * Asserts that the function target will throw a specific error, or specific type of error + * (as determined using `instanceof`), optionally with a RegExp or string inclusion test + * for the error's message. + * + * var err = new ReferenceError('This is a bad function.'); + * var fn = function () { throw err; } + * expect(fn).to.throw(ReferenceError); + * expect(fn).to.throw(Error); + * expect(fn).to.throw(/bad function/); + * expect(fn).to.not.throw('good function'); + * expect(fn).to.throw(ReferenceError, /bad function/); + * expect(fn).to.throw(err); + * expect(fn).to.not.throw(new RangeError('Out of range.')); + * + * Please note that when a throw expectation is negated, it will check each + * parameter independently, starting with error constructor type. The appropriate way + * to check for the existence of a type of error but for a message that does not match + * is to use `and`. + * + * expect(fn).to.throw(ReferenceError) + * .and.not.throw(/good function/); + * + * @name throw + * @alias throws + * @alias Throw + * @param {ErrorConstructor} constructor + * @param {String|RegExp} expected error message + * @param {String} message _optional_ + * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types + * @returns error for chaining (null if no error) + * @api public + */ + + function assertThrows (constructor, errMsg, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + new Assertion(obj, msg).is.a('function'); + + var thrown = false + , desiredError = null + , name = null + , thrownError = null; + + if (arguments.length === 0) { + errMsg = null; + constructor = null; + } else if (constructor && (constructor instanceof RegExp || 'string' === typeof constructor)) { + errMsg = constructor; + constructor = null; + } else if (constructor && constructor instanceof Error) { + desiredError = constructor; + constructor = null; + errMsg = null; + } else if (typeof constructor === 'function') { + name = constructor.prototype.name || constructor.name; + if (name === 'Error' && constructor !== Error) { + name = (new constructor()).name; + } + } else { + constructor = null; + } + + try { + obj(); + } catch (err) { + // first, check desired error + if (desiredError) { + this.assert( + err === desiredError + , 'expected #{this} to throw #{exp} but #{act} was thrown' + , 'expected #{this} to not throw #{exp}' + , (desiredError instanceof Error ? desiredError.toString() : desiredError) + , (err instanceof Error ? err.toString() : err) + ); + + flag(this, 'object', err); + return this; + } + + // next, check constructor + if (constructor) { + this.assert( + err instanceof constructor + , 'expected #{this} to throw #{exp} but #{act} was thrown' + , 'expected #{this} to not throw #{exp} but #{act} was thrown' + , name + , (err instanceof Error ? err.toString() : err) + ); + + if (!errMsg) { + flag(this, 'object', err); + return this; + } + } + + // next, check message + var message = 'object' === _.type(err) && "message" in err + ? err.message + : '' + err; + + if ((message != null) && errMsg && errMsg instanceof RegExp) { + this.assert( + errMsg.exec(message) + , 'expected #{this} to throw error matching #{exp} but got #{act}' + , 'expected #{this} to throw error not matching #{exp}' + , errMsg + , message + ); + + flag(this, 'object', err); + return this; + } else if ((message != null) && errMsg && 'string' === typeof errMsg) { + this.assert( + ~message.indexOf(errMsg) + , 'expected #{this} to throw error including #{exp} but got #{act}' + , 'expected #{this} to throw error not including #{act}' + , errMsg + , message + ); + + flag(this, 'object', err); + return this; + } else { + thrown = true; + thrownError = err; + } + } + + var actuallyGot = '' + , expectedThrown = name !== null + ? name + : desiredError + ? '#{exp}' //_.inspect(desiredError) + : 'an error'; + + if (thrown) { + actuallyGot = ' but #{act} was thrown' + } + + this.assert( + thrown === true + , 'expected #{this} to throw ' + expectedThrown + actuallyGot + , 'expected #{this} to not throw ' + expectedThrown + actuallyGot + , (desiredError instanceof Error ? desiredError.toString() : desiredError) + , (thrownError instanceof Error ? thrownError.toString() : thrownError) + ); + + flag(this, 'object', thrownError); + }; + + Assertion.addMethod('throw', assertThrows); + Assertion.addMethod('throws', assertThrows); + Assertion.addMethod('Throw', assertThrows); + + /** + * ### .respondTo(method) + * + * Asserts that the object or class target will respond to a method. + * + * Klass.prototype.bar = function(){}; + * expect(Klass).to.respondTo('bar'); + * expect(obj).to.respondTo('bar'); + * + * To check if a constructor will respond to a static function, + * set the `itself` flag. + * + * Klass.baz = function(){}; + * expect(Klass).itself.to.respondTo('baz'); + * + * @name respondTo + * @param {String} method + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('respondTo', function (method, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object') + , itself = flag(this, 'itself') + , context = ('function' === _.type(obj) && !itself) + ? obj.prototype[method] + : obj[method]; + + this.assert( + 'function' === typeof context + , 'expected #{this} to respond to ' + _.inspect(method) + , 'expected #{this} to not respond to ' + _.inspect(method) + ); + }); + + /** + * ### .itself + * + * Sets the `itself` flag, later used by the `respondTo` assertion. + * + * function Foo() {} + * Foo.bar = function() {} + * Foo.prototype.baz = function() {} + * + * expect(Foo).itself.to.respondTo('bar'); + * expect(Foo).itself.not.to.respondTo('baz'); + * + * @name itself + * @api public + */ + + Assertion.addProperty('itself', function () { + flag(this, 'itself', true); + }); + + /** + * ### .satisfy(method) + * + * Asserts that the target passes a given truth test. + * + * expect(1).to.satisfy(function(num) { return num > 0; }); + * + * @name satisfy + * @param {Function} matcher + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('satisfy', function (matcher, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + var result = matcher(obj); + this.assert( + result + , 'expected #{this} to satisfy ' + _.objDisplay(matcher) + , 'expected #{this} to not satisfy' + _.objDisplay(matcher) + , this.negate ? false : true + , result + ); + }); + + /** + * ### .closeTo(expected, delta) + * + * Asserts that the target is equal `expected`, to within a +/- `delta` range. + * + * expect(1.5).to.be.closeTo(1, 0.5); + * + * @name closeTo + * @param {Number} expected + * @param {Number} delta + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('closeTo', function (expected, delta, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + + new Assertion(obj, msg).is.a('number'); + if (_.type(expected) !== 'number' || _.type(delta) !== 'number') { + throw new Error('the arguments to closeTo must be numbers'); + } + + this.assert( + Math.abs(obj - expected) <= delta + , 'expected #{this} to be close to ' + expected + ' +/- ' + delta + , 'expected #{this} not to be close to ' + expected + ' +/- ' + delta + ); + }); + + function isSubsetOf(subset, superset, cmp) { + return subset.every(function(elem) { + if (!cmp) return superset.indexOf(elem) !== -1; + + return superset.some(function(elem2) { + return cmp(elem, elem2); + }); + }) + } + + /** + * ### .members(set) + * + * Asserts that the target is a superset of `set`, + * or that the target and `set` have the same strictly-equal (===) members. + * Alternately, if the `deep` flag is set, set members are compared for deep + * equality. + * + * expect([1, 2, 3]).to.include.members([3, 2]); + * expect([1, 2, 3]).to.not.include.members([3, 2, 8]); + * + * expect([4, 2]).to.have.members([2, 4]); + * expect([5, 2]).to.not.have.members([5, 2, 1]); + * + * expect([{ id: 1 }]).to.deep.include.members([{ id: 1 }]); + * + * @name members + * @param {Array} set + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('members', function (subset, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + + new Assertion(obj).to.be.an('array'); + new Assertion(subset).to.be.an('array'); + + var cmp = flag(this, 'deep') ? _.eql : undefined; + + if (flag(this, 'contains')) { + return this.assert( + isSubsetOf(subset, obj, cmp) + , 'expected #{this} to be a superset of #{act}' + , 'expected #{this} to not be a superset of #{act}' + , obj + , subset + ); + } + + this.assert( + isSubsetOf(obj, subset, cmp) && isSubsetOf(subset, obj, cmp) + , 'expected #{this} to have the same members as #{act}' + , 'expected #{this} to not have the same members as #{act}' + , obj + , subset + ); + }); +}; + +}); + +require.register("chai/lib/chai/interface/assert.js", function (exports, module) { +/*! + * chai + * Copyright(c) 2011-2014 Jake Luer + * MIT Licensed + */ + + +module.exports = function (chai, util) { + + /*! + * Chai dependencies. + */ + + var Assertion = chai.Assertion + , flag = util.flag; + + /*! + * Module export. + */ + + /** + * ### assert(expression, message) + * + * Write your own test expressions. + * + * assert('foo' !== 'bar', 'foo is not bar'); + * assert(Array.isArray([]), 'empty arrays are arrays'); + * + * @param {Mixed} expression to test for truthiness + * @param {String} message to display on error + * @name assert + * @api public + */ + + var assert = chai.assert = function (express, errmsg) { + var test = new Assertion(null, null, chai.assert); + test.assert( + express + , errmsg + , '[ negation message unavailable ]' + ); + }; + + /** + * ### .fail(actual, expected, [message], [operator]) + * + * Throw a failure. Node.js `assert` module-compatible. + * + * @name fail + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @param {String} operator + * @api public + */ + + assert.fail = function (actual, expected, message, operator) { + message = message || 'assert.fail()'; + throw new chai.AssertionError(message, { + actual: actual + , expected: expected + , operator: operator + }, assert.fail); + }; + + /** + * ### .ok(object, [message]) + * + * Asserts that `object` is truthy. + * + * assert.ok('everything', 'everything is ok'); + * assert.ok(false, 'this will fail'); + * + * @name ok + * @param {Mixed} object to test + * @param {String} message + * @api public + */ + + assert.ok = function (val, msg) { + new Assertion(val, msg).is.ok; + }; + + /** + * ### .notOk(object, [message]) + * + * Asserts that `object` is falsy. + * + * assert.notOk('everything', 'this will fail'); + * assert.notOk(false, 'this will pass'); + * + * @name notOk + * @param {Mixed} object to test + * @param {String} message + * @api public + */ + + assert.notOk = function (val, msg) { + new Assertion(val, msg).is.not.ok; + }; + + /** + * ### .equal(actual, expected, [message]) + * + * Asserts non-strict equality (`==`) of `actual` and `expected`. + * + * assert.equal(3, '3', '== coerces values to strings'); + * + * @name equal + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @api public + */ + + assert.equal = function (act, exp, msg) { + var test = new Assertion(act, msg, assert.equal); + + test.assert( + exp == flag(test, 'object') + , 'expected #{this} to equal #{exp}' + , 'expected #{this} to not equal #{act}' + , exp + , act + ); + }; + + /** + * ### .notEqual(actual, expected, [message]) + * + * Asserts non-strict inequality (`!=`) of `actual` and `expected`. + * + * assert.notEqual(3, 4, 'these numbers are not equal'); + * + * @name notEqual + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @api public + */ + + assert.notEqual = function (act, exp, msg) { + var test = new Assertion(act, msg, assert.notEqual); + + test.assert( + exp != flag(test, 'object') + , 'expected #{this} to not equal #{exp}' + , 'expected #{this} to equal #{act}' + , exp + , act + ); + }; + + /** + * ### .strictEqual(actual, expected, [message]) + * + * Asserts strict equality (`===`) of `actual` and `expected`. + * + * assert.strictEqual(true, true, 'these booleans are strictly equal'); + * + * @name strictEqual + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @api public + */ + + assert.strictEqual = function (act, exp, msg) { + new Assertion(act, msg).to.equal(exp); + }; + + /** + * ### .notStrictEqual(actual, expected, [message]) + * + * Asserts strict inequality (`!==`) of `actual` and `expected`. + * + * assert.notStrictEqual(3, '3', 'no coercion for strict equality'); + * + * @name notStrictEqual + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @api public + */ + + assert.notStrictEqual = function (act, exp, msg) { + new Assertion(act, msg).to.not.equal(exp); + }; + + /** + * ### .deepEqual(actual, expected, [message]) + * + * Asserts that `actual` is deeply equal to `expected`. + * + * assert.deepEqual({ tea: 'green' }, { tea: 'green' }); + * + * @name deepEqual + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @api public + */ + + assert.deepEqual = function (act, exp, msg) { + new Assertion(act, msg).to.eql(exp); + }; + + /** + * ### .notDeepEqual(actual, expected, [message]) + * + * Assert that `actual` is not deeply equal to `expected`. + * + * assert.notDeepEqual({ tea: 'green' }, { tea: 'jasmine' }); + * + * @name notDeepEqual + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @api public + */ + + assert.notDeepEqual = function (act, exp, msg) { + new Assertion(act, msg).to.not.eql(exp); + }; + + /** + * ### .isTrue(value, [message]) + * + * Asserts that `value` is true. + * + * var teaServed = true; + * assert.isTrue(teaServed, 'the tea has been served'); + * + * @name isTrue + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isTrue = function (val, msg) { + new Assertion(val, msg).is['true']; + }; + + /** + * ### .isFalse(value, [message]) + * + * Asserts that `value` is false. + * + * var teaServed = false; + * assert.isFalse(teaServed, 'no tea yet? hmm...'); + * + * @name isFalse + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isFalse = function (val, msg) { + new Assertion(val, msg).is['false']; + }; + + /** + * ### .isNull(value, [message]) + * + * Asserts that `value` is null. + * + * assert.isNull(err, 'there was no error'); + * + * @name isNull + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNull = function (val, msg) { + new Assertion(val, msg).to.equal(null); + }; + + /** + * ### .isNotNull(value, [message]) + * + * Asserts that `value` is not null. + * + * var tea = 'tasty chai'; + * assert.isNotNull(tea, 'great, time for tea!'); + * + * @name isNotNull + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotNull = function (val, msg) { + new Assertion(val, msg).to.not.equal(null); + }; + + /** + * ### .isUndefined(value, [message]) + * + * Asserts that `value` is `undefined`. + * + * var tea; + * assert.isUndefined(tea, 'no tea defined'); + * + * @name isUndefined + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isUndefined = function (val, msg) { + new Assertion(val, msg).to.equal(undefined); + }; + + /** + * ### .isDefined(value, [message]) + * + * Asserts that `value` is not `undefined`. + * + * var tea = 'cup of chai'; + * assert.isDefined(tea, 'tea has been defined'); + * + * @name isDefined + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isDefined = function (val, msg) { + new Assertion(val, msg).to.not.equal(undefined); + }; + + /** + * ### .isFunction(value, [message]) + * + * Asserts that `value` is a function. + * + * function serveTea() { return 'cup of tea'; }; + * assert.isFunction(serveTea, 'great, we can have tea now'); + * + * @name isFunction + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isFunction = function (val, msg) { + new Assertion(val, msg).to.be.a('function'); + }; + + /** + * ### .isNotFunction(value, [message]) + * + * Asserts that `value` is _not_ a function. + * + * var serveTea = [ 'heat', 'pour', 'sip' ]; + * assert.isNotFunction(serveTea, 'great, we have listed the steps'); + * + * @name isNotFunction + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotFunction = function (val, msg) { + new Assertion(val, msg).to.not.be.a('function'); + }; + + /** + * ### .isObject(value, [message]) + * + * Asserts that `value` is an object (as revealed by + * `Object.prototype.toString`). + * + * var selection = { name: 'Chai', serve: 'with spices' }; + * assert.isObject(selection, 'tea selection is an object'); + * + * @name isObject + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isObject = function (val, msg) { + new Assertion(val, msg).to.be.a('object'); + }; + + /** + * ### .isNotObject(value, [message]) + * + * Asserts that `value` is _not_ an object. + * + * var selection = 'chai' + * assert.isNotObject(selection, 'tea selection is not an object'); + * assert.isNotObject(null, 'null is not an object'); + * + * @name isNotObject + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotObject = function (val, msg) { + new Assertion(val, msg).to.not.be.a('object'); + }; + + /** + * ### .isArray(value, [message]) + * + * Asserts that `value` is an array. + * + * var menu = [ 'green', 'chai', 'oolong' ]; + * assert.isArray(menu, 'what kind of tea do we want?'); + * + * @name isArray + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isArray = function (val, msg) { + new Assertion(val, msg).to.be.an('array'); + }; + + /** + * ### .isNotArray(value, [message]) + * + * Asserts that `value` is _not_ an array. + * + * var menu = 'green|chai|oolong'; + * assert.isNotArray(menu, 'what kind of tea do we want?'); + * + * @name isNotArray + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotArray = function (val, msg) { + new Assertion(val, msg).to.not.be.an('array'); + }; + + /** + * ### .isString(value, [message]) + * + * Asserts that `value` is a string. + * + * var teaOrder = 'chai'; + * assert.isString(teaOrder, 'order placed'); + * + * @name isString + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isString = function (val, msg) { + new Assertion(val, msg).to.be.a('string'); + }; + + /** + * ### .isNotString(value, [message]) + * + * Asserts that `value` is _not_ a string. + * + * var teaOrder = 4; + * assert.isNotString(teaOrder, 'order placed'); + * + * @name isNotString + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotString = function (val, msg) { + new Assertion(val, msg).to.not.be.a('string'); + }; + + /** + * ### .isNumber(value, [message]) + * + * Asserts that `value` is a number. + * + * var cups = 2; + * assert.isNumber(cups, 'how many cups'); + * + * @name isNumber + * @param {Number} value + * @param {String} message + * @api public + */ + + assert.isNumber = function (val, msg) { + new Assertion(val, msg).to.be.a('number'); + }; + + /** + * ### .isNotNumber(value, [message]) + * + * Asserts that `value` is _not_ a number. + * + * var cups = '2 cups please'; + * assert.isNotNumber(cups, 'how many cups'); + * + * @name isNotNumber + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotNumber = function (val, msg) { + new Assertion(val, msg).to.not.be.a('number'); + }; + + /** + * ### .isBoolean(value, [message]) + * + * Asserts that `value` is a boolean. + * + * var teaReady = true + * , teaServed = false; + * + * assert.isBoolean(teaReady, 'is the tea ready'); + * assert.isBoolean(teaServed, 'has tea been served'); + * + * @name isBoolean + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isBoolean = function (val, msg) { + new Assertion(val, msg).to.be.a('boolean'); + }; + + /** + * ### .isNotBoolean(value, [message]) + * + * Asserts that `value` is _not_ a boolean. + * + * var teaReady = 'yep' + * , teaServed = 'nope'; + * + * assert.isNotBoolean(teaReady, 'is the tea ready'); + * assert.isNotBoolean(teaServed, 'has tea been served'); + * + * @name isNotBoolean + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotBoolean = function (val, msg) { + new Assertion(val, msg).to.not.be.a('boolean'); + }; + + /** + * ### .typeOf(value, name, [message]) + * + * Asserts that `value`'s type is `name`, as determined by + * `Object.prototype.toString`. + * + * assert.typeOf({ tea: 'chai' }, 'object', 'we have an object'); + * assert.typeOf(['chai', 'jasmine'], 'array', 'we have an array'); + * assert.typeOf('tea', 'string', 'we have a string'); + * assert.typeOf(/tea/, 'regexp', 'we have a regular expression'); + * assert.typeOf(null, 'null', 'we have a null'); + * assert.typeOf(undefined, 'undefined', 'we have an undefined'); + * + * @name typeOf + * @param {Mixed} value + * @param {String} name + * @param {String} message + * @api public + */ + + assert.typeOf = function (val, type, msg) { + new Assertion(val, msg).to.be.a(type); + }; + + /** + * ### .notTypeOf(value, name, [message]) + * + * Asserts that `value`'s type is _not_ `name`, as determined by + * `Object.prototype.toString`. + * + * assert.notTypeOf('tea', 'number', 'strings are not numbers'); + * + * @name notTypeOf + * @param {Mixed} value + * @param {String} typeof name + * @param {String} message + * @api public + */ + + assert.notTypeOf = function (val, type, msg) { + new Assertion(val, msg).to.not.be.a(type); + }; + + /** + * ### .instanceOf(object, constructor, [message]) + * + * Asserts that `value` is an instance of `constructor`. + * + * var Tea = function (name) { this.name = name; } + * , chai = new Tea('chai'); + * + * assert.instanceOf(chai, Tea, 'chai is an instance of tea'); + * + * @name instanceOf + * @param {Object} object + * @param {Constructor} constructor + * @param {String} message + * @api public + */ + + assert.instanceOf = function (val, type, msg) { + new Assertion(val, msg).to.be.instanceOf(type); + }; + + /** + * ### .notInstanceOf(object, constructor, [message]) + * + * Asserts `value` is not an instance of `constructor`. + * + * var Tea = function (name) { this.name = name; } + * , chai = new String('chai'); + * + * assert.notInstanceOf(chai, Tea, 'chai is not an instance of tea'); + * + * @name notInstanceOf + * @param {Object} object + * @param {Constructor} constructor + * @param {String} message + * @api public + */ + + assert.notInstanceOf = function (val, type, msg) { + new Assertion(val, msg).to.not.be.instanceOf(type); + }; + + /** + * ### .include(haystack, needle, [message]) + * + * Asserts that `haystack` includes `needle`. Works + * for strings and arrays. + * + * assert.include('foobar', 'bar', 'foobar contains string "bar"'); + * assert.include([ 1, 2, 3 ], 3, 'array contains value'); + * + * @name include + * @param {Array|String} haystack + * @param {Mixed} needle + * @param {String} message + * @api public + */ + + assert.include = function (exp, inc, msg) { + new Assertion(exp, msg, assert.include).include(inc); + }; + + /** + * ### .notInclude(haystack, needle, [message]) + * + * Asserts that `haystack` does not include `needle`. Works + * for strings and arrays. + *i + * assert.notInclude('foobar', 'baz', 'string not include substring'); + * assert.notInclude([ 1, 2, 3 ], 4, 'array not include contain value'); + * + * @name notInclude + * @param {Array|String} haystack + * @param {Mixed} needle + * @param {String} message + * @api public + */ + + assert.notInclude = function (exp, inc, msg) { + new Assertion(exp, msg, assert.notInclude).not.include(inc); + }; + + /** + * ### .match(value, regexp, [message]) + * + * Asserts that `value` matches the regular expression `regexp`. + * + * assert.match('foobar', /^foo/, 'regexp matches'); + * + * @name match + * @param {Mixed} value + * @param {RegExp} regexp + * @param {String} message + * @api public + */ + + assert.match = function (exp, re, msg) { + new Assertion(exp, msg).to.match(re); + }; + + /** + * ### .notMatch(value, regexp, [message]) + * + * Asserts that `value` does not match the regular expression `regexp`. + * + * assert.notMatch('foobar', /^foo/, 'regexp does not match'); + * + * @name notMatch + * @param {Mixed} value + * @param {RegExp} regexp + * @param {String} message + * @api public + */ + + assert.notMatch = function (exp, re, msg) { + new Assertion(exp, msg).to.not.match(re); + }; + + /** + * ### .property(object, property, [message]) + * + * Asserts that `object` has a property named by `property`. + * + * assert.property({ tea: { green: 'matcha' }}, 'tea'); + * + * @name property + * @param {Object} object + * @param {String} property + * @param {String} message + * @api public + */ + + assert.property = function (obj, prop, msg) { + new Assertion(obj, msg).to.have.property(prop); + }; + + /** + * ### .notProperty(object, property, [message]) + * + * Asserts that `object` does _not_ have a property named by `property`. + * + * assert.notProperty({ tea: { green: 'matcha' }}, 'coffee'); + * + * @name notProperty + * @param {Object} object + * @param {String} property + * @param {String} message + * @api public + */ + + assert.notProperty = function (obj, prop, msg) { + new Assertion(obj, msg).to.not.have.property(prop); + }; + + /** + * ### .deepProperty(object, property, [message]) + * + * Asserts that `object` has a property named by `property`, which can be a + * string using dot- and bracket-notation for deep reference. + * + * assert.deepProperty({ tea: { green: 'matcha' }}, 'tea.green'); + * + * @name deepProperty + * @param {Object} object + * @param {String} property + * @param {String} message + * @api public + */ + + assert.deepProperty = function (obj, prop, msg) { + new Assertion(obj, msg).to.have.deep.property(prop); + }; + + /** + * ### .notDeepProperty(object, property, [message]) + * + * Asserts that `object` does _not_ have a property named by `property`, which + * can be a string using dot- and bracket-notation for deep reference. + * + * assert.notDeepProperty({ tea: { green: 'matcha' }}, 'tea.oolong'); + * + * @name notDeepProperty + * @param {Object} object + * @param {String} property + * @param {String} message + * @api public + */ + + assert.notDeepProperty = function (obj, prop, msg) { + new Assertion(obj, msg).to.not.have.deep.property(prop); + }; + + /** + * ### .propertyVal(object, property, value, [message]) + * + * Asserts that `object` has a property named by `property` with value given + * by `value`. + * + * assert.propertyVal({ tea: 'is good' }, 'tea', 'is good'); + * + * @name propertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.propertyVal = function (obj, prop, val, msg) { + new Assertion(obj, msg).to.have.property(prop, val); + }; + + /** + * ### .propertyNotVal(object, property, value, [message]) + * + * Asserts that `object` has a property named by `property`, but with a value + * different from that given by `value`. + * + * assert.propertyNotVal({ tea: 'is good' }, 'tea', 'is bad'); + * + * @name propertyNotVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.propertyNotVal = function (obj, prop, val, msg) { + new Assertion(obj, msg).to.not.have.property(prop, val); + }; + + /** + * ### .deepPropertyVal(object, property, value, [message]) + * + * Asserts that `object` has a property named by `property` with value given + * by `value`. `property` can use dot- and bracket-notation for deep + * reference. + * + * assert.deepPropertyVal({ tea: { green: 'matcha' }}, 'tea.green', 'matcha'); + * + * @name deepPropertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.deepPropertyVal = function (obj, prop, val, msg) { + new Assertion(obj, msg).to.have.deep.property(prop, val); + }; + + /** + * ### .deepPropertyNotVal(object, property, value, [message]) + * + * Asserts that `object` has a property named by `property`, but with a value + * different from that given by `value`. `property` can use dot- and + * bracket-notation for deep reference. + * + * assert.deepPropertyNotVal({ tea: { green: 'matcha' }}, 'tea.green', 'konacha'); + * + * @name deepPropertyNotVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.deepPropertyNotVal = function (obj, prop, val, msg) { + new Assertion(obj, msg).to.not.have.deep.property(prop, val); + }; + + /** + * ### .lengthOf(object, length, [message]) + * + * Asserts that `object` has a `length` property with the expected value. + * + * assert.lengthOf([1,2,3], 3, 'array has length of 3'); + * assert.lengthOf('foobar', 5, 'string has length of 6'); + * + * @name lengthOf + * @param {Mixed} object + * @param {Number} length + * @param {String} message + * @api public + */ + + assert.lengthOf = function (exp, len, msg) { + new Assertion(exp, msg).to.have.length(len); + }; + + /** + * ### .throws(function, [constructor/string/regexp], [string/regexp], [message]) + * + * Asserts that `function` will throw an error that is an instance of + * `constructor`, or alternately that it will throw an error with message + * matching `regexp`. + * + * assert.throw(fn, 'function throws a reference error'); + * assert.throw(fn, /function throws a reference error/); + * assert.throw(fn, ReferenceError); + * assert.throw(fn, ReferenceError, 'function throws a reference error'); + * assert.throw(fn, ReferenceError, /function throws a reference error/); + * + * @name throws + * @alias throw + * @alias Throw + * @param {Function} function + * @param {ErrorConstructor} constructor + * @param {RegExp} regexp + * @param {String} message + * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types + * @api public + */ + + assert.Throw = function (fn, errt, errs, msg) { + if ('string' === typeof errt || errt instanceof RegExp) { + errs = errt; + errt = null; + } + + var assertErr = new Assertion(fn, msg).to.Throw(errt, errs); + return flag(assertErr, 'object'); + }; + + /** + * ### .doesNotThrow(function, [constructor/regexp], [message]) + * + * Asserts that `function` will _not_ throw an error that is an instance of + * `constructor`, or alternately that it will not throw an error with message + * matching `regexp`. + * + * assert.doesNotThrow(fn, Error, 'function does not throw'); + * + * @name doesNotThrow + * @param {Function} function + * @param {ErrorConstructor} constructor + * @param {RegExp} regexp + * @param {String} message + * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types + * @api public + */ + + assert.doesNotThrow = function (fn, type, msg) { + if ('string' === typeof type) { + msg = type; + type = null; + } + + new Assertion(fn, msg).to.not.Throw(type); + }; + + /** + * ### .operator(val1, operator, val2, [message]) + * + * Compares two values using `operator`. + * + * assert.operator(1, '<', 2, 'everything is ok'); + * assert.operator(1, '>', 2, 'this will fail'); + * + * @name operator + * @param {Mixed} val1 + * @param {String} operator + * @param {Mixed} val2 + * @param {String} message + * @api public + */ + + assert.operator = function (val, operator, val2, msg) { + if (!~['==', '===', '>', '>=', '<', '<=', '!=', '!=='].indexOf(operator)) { + throw new Error('Invalid operator "' + operator + '"'); + } + var test = new Assertion(eval(val + operator + val2), msg); + test.assert( + true === flag(test, 'object') + , 'expected ' + util.inspect(val) + ' to be ' + operator + ' ' + util.inspect(val2) + , 'expected ' + util.inspect(val) + ' to not be ' + operator + ' ' + util.inspect(val2) ); + }; + + /** + * ### .closeTo(actual, expected, delta, [message]) + * + * Asserts that the target is equal `expected`, to within a +/- `delta` range. + * + * assert.closeTo(1.5, 1, 0.5, 'numbers are close'); + * + * @name closeTo + * @param {Number} actual + * @param {Number} expected + * @param {Number} delta + * @param {String} message + * @api public + */ + + assert.closeTo = function (act, exp, delta, msg) { + new Assertion(act, msg).to.be.closeTo(exp, delta); + }; + + /** + * ### .sameMembers(set1, set2, [message]) + * + * Asserts that `set1` and `set2` have the same members. + * Order is not taken into account. + * + * assert.sameMembers([ 1, 2, 3 ], [ 2, 1, 3 ], 'same members'); + * + * @name sameMembers + * @param {Array} set1 + * @param {Array} set2 + * @param {String} message + * @api public + */ + + assert.sameMembers = function (set1, set2, msg) { + new Assertion(set1, msg).to.have.same.members(set2); + } + + /** + * ### .includeMembers(superset, subset, [message]) + * + * Asserts that `subset` is included in `superset`. + * Order is not taken into account. + * + * assert.includeMembers([ 1, 2, 3 ], [ 2, 1 ], 'include members'); + * + * @name includeMembers + * @param {Array} superset + * @param {Array} subset + * @param {String} message + * @api public + */ + + assert.includeMembers = function (superset, subset, msg) { + new Assertion(superset, msg).to.include.members(subset); + } + + /*! + * Undocumented / untested + */ + + assert.ifError = function (val, msg) { + new Assertion(val, msg).to.not.be.ok; + }; + + /*! + * Aliases. + */ + + (function alias(name, as){ + assert[as] = assert[name]; + return alias; + }) + ('Throw', 'throw') + ('Throw', 'throws'); +}; + +}); + +require.register("chai/lib/chai/interface/expect.js", function (exports, module) { +/*! + * chai + * Copyright(c) 2011-2014 Jake Luer + * MIT Licensed + */ + +module.exports = function (chai, util) { + chai.expect = function (val, message) { + return new chai.Assertion(val, message); + }; +}; + + +}); + +require.register("chai/lib/chai/interface/should.js", function (exports, module) { +/*! + * chai + * Copyright(c) 2011-2014 Jake Luer + * MIT Licensed + */ + +module.exports = function (chai, util) { + var Assertion = chai.Assertion; + + function loadShould () { + // explicitly define this method as function as to have it's name to include as `ssfi` + function shouldGetter() { + if (this instanceof String || this instanceof Number) { + return new Assertion(this.constructor(this), null, shouldGetter); + } else if (this instanceof Boolean) { + return new Assertion(this == true, null, shouldGetter); + } + return new Assertion(this, null, shouldGetter); + } + function shouldSetter(value) { + // See https://github.com/chaijs/chai/issues/86: this makes + // `whatever.should = someValue` actually set `someValue`, which is + // especially useful for `global.should = require('chai').should()`. + // + // Note that we have to use [[DefineProperty]] instead of [[Put]] + // since otherwise we would trigger this very setter! + Object.defineProperty(this, 'should', { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } + // modify Object.prototype to have `should` + Object.defineProperty(Object.prototype, 'should', { + set: shouldSetter + , get: shouldGetter + , configurable: true + }); + + var should = {}; + + should.equal = function (val1, val2, msg) { + new Assertion(val1, msg).to.equal(val2); + }; + + should.Throw = function (fn, errt, errs, msg) { + new Assertion(fn, msg).to.Throw(errt, errs); + }; + + should.exist = function (val, msg) { + new Assertion(val, msg).to.exist; + } + + // negation + should.not = {} + + should.not.equal = function (val1, val2, msg) { + new Assertion(val1, msg).to.not.equal(val2); + }; + + should.not.Throw = function (fn, errt, errs, msg) { + new Assertion(fn, msg).to.not.Throw(errt, errs); + }; + + should.not.exist = function (val, msg) { + new Assertion(val, msg).to.not.exist; + } + + should['throw'] = should['Throw']; + should.not['throw'] = should.not['Throw']; + + return should; + }; + + chai.should = loadShould; + chai.Should = loadShould; +}; + +}); + +require.register("chai/lib/chai/utils/addChainableMethod.js", function (exports, module) { +/*! + * Chai - addChainingMethod utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependencies + */ + +var transferFlags = require('chai/lib/chai/utils/transferFlags.js'); +var flag = require('chai/lib/chai/utils/flag.js'); +var config = require('chai/lib/chai/config.js'); + +/*! + * Module variables + */ + +// Check whether `__proto__` is supported +var hasProtoSupport = '__proto__' in Object; + +// Without `__proto__` support, this module will need to add properties to a function. +// However, some Function.prototype methods cannot be overwritten, +// and there seems no easy cross-platform way to detect them (@see chaijs/chai/issues/69). +var excludeNames = /^(?:length|name|arguments|caller)$/; + +// Cache `Function` properties +var call = Function.prototype.call, + apply = Function.prototype.apply; + +/** + * ### addChainableMethod (ctx, name, method, chainingBehavior) + * + * Adds a method to an object, such that the method can also be chained. + * + * utils.addChainableMethod(chai.Assertion.prototype, 'foo', function (str) { + * var obj = utils.flag(this, 'object'); + * new chai.Assertion(obj).to.be.equal(str); + * }); + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.addChainableMethod('foo', fn, chainingBehavior); + * + * The result can then be used as both a method assertion, executing both `method` and + * `chainingBehavior`, or as a language chain, which only executes `chainingBehavior`. + * + * expect(fooStr).to.be.foo('bar'); + * expect(fooStr).to.be.foo.equal('foo'); + * + * @param {Object} ctx object to which the method is added + * @param {String} name of method to add + * @param {Function} method function to be used for `name`, when called + * @param {Function} chainingBehavior function to be called every time the property is accessed + * @name addChainableMethod + * @api public + */ + +module.exports = function (ctx, name, method, chainingBehavior) { + if (typeof chainingBehavior !== 'function') { + chainingBehavior = function () { }; + } + + var chainableBehavior = { + method: method + , chainingBehavior: chainingBehavior + }; + + // save the methods so we can overwrite them later, if we need to. + if (!ctx.__methods) { + ctx.__methods = {}; + } + ctx.__methods[name] = chainableBehavior; + + Object.defineProperty(ctx, name, + { get: function () { + chainableBehavior.chainingBehavior.call(this); + + var assert = function assert() { + var old_ssfi = flag(this, 'ssfi'); + if (old_ssfi && config.includeStack === false) + flag(this, 'ssfi', assert); + var result = chainableBehavior.method.apply(this, arguments); + return result === undefined ? this : result; + }; + + // Use `__proto__` if available + if (hasProtoSupport) { + // Inherit all properties from the object by replacing the `Function` prototype + var prototype = assert.__proto__ = Object.create(this); + // Restore the `call` and `apply` methods from `Function` + prototype.call = call; + prototype.apply = apply; + } + // Otherwise, redefine all properties (slow!) + else { + var asserterNames = Object.getOwnPropertyNames(ctx); + asserterNames.forEach(function (asserterName) { + if (!excludeNames.test(asserterName)) { + var pd = Object.getOwnPropertyDescriptor(ctx, asserterName); + Object.defineProperty(assert, asserterName, pd); + } + }); + } + + transferFlags(this, assert); + return assert; + } + , configurable: true + }); +}; + +}); + +require.register("chai/lib/chai/utils/addMethod.js", function (exports, module) { +/*! + * Chai - addMethod utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +var config = require('chai/lib/chai/config.js'); + +/** + * ### .addMethod (ctx, name, method) + * + * Adds a method to the prototype of an object. + * + * utils.addMethod(chai.Assertion.prototype, 'foo', function (str) { + * var obj = utils.flag(this, 'object'); + * new chai.Assertion(obj).to.be.equal(str); + * }); + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.addMethod('foo', fn); + * + * Then can be used as any other assertion. + * + * expect(fooStr).to.be.foo('bar'); + * + * @param {Object} ctx object to which the method is added + * @param {String} name of method to add + * @param {Function} method function to be used for name + * @name addMethod + * @api public + */ +var flag = require('chai/lib/chai/utils/flag.js'); + +module.exports = function (ctx, name, method) { + ctx[name] = function () { + var old_ssfi = flag(this, 'ssfi'); + if (old_ssfi && config.includeStack === false) + flag(this, 'ssfi', ctx[name]); + var result = method.apply(this, arguments); + return result === undefined ? this : result; + }; +}; + +}); + +require.register("chai/lib/chai/utils/addProperty.js", function (exports, module) { +/*! + * Chai - addProperty utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### addProperty (ctx, name, getter) + * + * Adds a property to the prototype of an object. + * + * utils.addProperty(chai.Assertion.prototype, 'foo', function () { + * var obj = utils.flag(this, 'object'); + * new chai.Assertion(obj).to.be.instanceof(Foo); + * }); + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.addProperty('foo', fn); + * + * Then can be used as any other assertion. + * + * expect(myFoo).to.be.foo; + * + * @param {Object} ctx object to which the property is added + * @param {String} name of property to add + * @param {Function} getter function to be used for name + * @name addProperty + * @api public + */ + +module.exports = function (ctx, name, getter) { + Object.defineProperty(ctx, name, + { get: function () { + var result = getter.call(this); + return result === undefined ? this : result; + } + , configurable: true + }); +}; + +}); + +require.register("chai/lib/chai/utils/flag.js", function (exports, module) { +/*! + * Chai - flag utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### flag(object ,key, [value]) + * + * Get or set a flag value on an object. If a + * value is provided it will be set, else it will + * return the currently set value or `undefined` if + * the value is not set. + * + * utils.flag(this, 'foo', 'bar'); // setter + * utils.flag(this, 'foo'); // getter, returns `bar` + * + * @param {Object} object (constructed Assertion + * @param {String} key + * @param {Mixed} value (optional) + * @name flag + * @api private + */ + +module.exports = function (obj, key, value) { + var flags = obj.__flags || (obj.__flags = Object.create(null)); + if (arguments.length === 3) { + flags[key] = value; + } else { + return flags[key]; + } +}; + +}); + +require.register("chai/lib/chai/utils/getActual.js", function (exports, module) { +/*! + * Chai - getActual utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * # getActual(object, [actual]) + * + * Returns the `actual` value for an Assertion + * + * @param {Object} object (constructed Assertion) + * @param {Arguments} chai.Assertion.prototype.assert arguments + */ + +module.exports = function (obj, args) { + return args.length > 4 ? args[4] : obj._obj; +}; + +}); + +require.register("chai/lib/chai/utils/getEnumerableProperties.js", function (exports, module) { +/*! + * Chai - getEnumerableProperties utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### .getEnumerableProperties(object) + * + * This allows the retrieval of enumerable property names of an object, + * inherited or not. + * + * @param {Object} object + * @returns {Array} + * @name getEnumerableProperties + * @api public + */ + +module.exports = function getEnumerableProperties(object) { + var result = []; + for (var name in object) { + result.push(name); + } + return result; +}; + +}); + +require.register("chai/lib/chai/utils/getMessage.js", function (exports, module) { +/*! + * Chai - message composition utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependancies + */ + +var flag = require('chai/lib/chai/utils/flag.js') + , getActual = require('chai/lib/chai/utils/getActual.js') + , inspect = require('chai/lib/chai/utils/inspect.js') + , objDisplay = require('chai/lib/chai/utils/objDisplay.js'); + +/** + * ### .getMessage(object, message, negateMessage) + * + * Construct the error message based on flags + * and template tags. Template tags will return + * a stringified inspection of the object referenced. + * + * Message template tags: + * - `#{this}` current asserted object + * - `#{act}` actual value + * - `#{exp}` expected value + * + * @param {Object} object (constructed Assertion) + * @param {Arguments} chai.Assertion.prototype.assert arguments + * @name getMessage + * @api public + */ + +module.exports = function (obj, args) { + var negate = flag(obj, 'negate') + , val = flag(obj, 'object') + , expected = args[3] + , actual = getActual(obj, args) + , msg = negate ? args[2] : args[1] + , flagMsg = flag(obj, 'message'); + + if(typeof msg === "function") msg = msg(); + msg = msg || ''; + msg = msg + .replace(/#{this}/g, objDisplay(val)) + .replace(/#{act}/g, objDisplay(actual)) + .replace(/#{exp}/g, objDisplay(expected)); + + return flagMsg ? flagMsg + ': ' + msg : msg; +}; + +}); + +require.register("chai/lib/chai/utils/getName.js", function (exports, module) { +/*! + * Chai - getName utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * # getName(func) + * + * Gets the name of a function, in a cross-browser way. + * + * @param {Function} a function (usually a constructor) + */ + +module.exports = function (func) { + if (func.name) return func.name; + + var match = /^\s?function ([^(]*)\(/.exec(func); + return match && match[1] ? match[1] : ""; +}; + +}); + +require.register("chai/lib/chai/utils/getPathValue.js", function (exports, module) { +/*! + * Chai - getPathValue utility + * Copyright(c) 2012-2014 Jake Luer + * @see https://github.com/logicalparadox/filtr + * MIT Licensed + */ + +/** + * ### .getPathValue(path, object) + * + * This allows the retrieval of values in an + * object given a string path. + * + * var obj = { + * prop1: { + * arr: ['a', 'b', 'c'] + * , str: 'Hello' + * } + * , prop2: { + * arr: [ { nested: 'Universe' } ] + * , str: 'Hello again!' + * } + * } + * + * The following would be the results. + * + * getPathValue('prop1.str', obj); // Hello + * getPathValue('prop1.att[2]', obj); // b + * getPathValue('prop2.arr[0].nested', obj); // Universe + * + * @param {String} path + * @param {Object} object + * @returns {Object} value or `undefined` + * @name getPathValue + * @api public + */ + +var getPathValue = module.exports = function (path, obj) { + var parsed = parsePath(path); + return _getPathValue(parsed, obj); +}; + +/*! + * ## parsePath(path) + * + * Helper function used to parse string object + * paths. Use in conjunction with `_getPathValue`. + * + * var parsed = parsePath('myobject.property.subprop'); + * + * ### Paths: + * + * * Can be as near infinitely deep and nested + * * Arrays are also valid using the formal `myobject.document[3].property`. + * + * @param {String} path + * @returns {Object} parsed + * @api private + */ + +function parsePath (path) { + var str = path.replace(/\[/g, '.[') + , parts = str.match(/(\\\.|[^.]+?)+/g); + return parts.map(function (value) { + var re = /\[(\d+)\]$/ + , mArr = re.exec(value) + if (mArr) return { i: parseFloat(mArr[1]) }; + else return { p: value }; + }); +}; + +/*! + * ## _getPathValue(parsed, obj) + * + * Helper companion function for `.parsePath` that returns + * the value located at the parsed address. + * + * var value = getPathValue(parsed, obj); + * + * @param {Object} parsed definition from `parsePath`. + * @param {Object} object to search against + * @returns {Object|Undefined} value + * @api private + */ + +function _getPathValue (parsed, obj) { + var tmp = obj + , res; + for (var i = 0, l = parsed.length; i < l; i++) { + var part = parsed[i]; + if (tmp) { + if ('undefined' !== typeof part.p) + tmp = tmp[part.p]; + else if ('undefined' !== typeof part.i) + tmp = tmp[part.i]; + if (i == (l - 1)) res = tmp; + } else { + res = undefined; + } + } + return res; +}; + +}); + +require.register("chai/lib/chai/utils/getProperties.js", function (exports, module) { +/*! + * Chai - getProperties utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### .getProperties(object) + * + * This allows the retrieval of property names of an object, enumerable or not, + * inherited or not. + * + * @param {Object} object + * @returns {Array} + * @name getProperties + * @api public + */ + +module.exports = function getProperties(object) { + var result = Object.getOwnPropertyNames(subject); + + function addProperty(property) { + if (result.indexOf(property) === -1) { + result.push(property); + } + } + + var proto = Object.getPrototypeOf(subject); + while (proto !== null) { + Object.getOwnPropertyNames(proto).forEach(addProperty); + proto = Object.getPrototypeOf(proto); + } + + return result; +}; + +}); + +require.register("chai/lib/chai/utils/index.js", function (exports, module) { +/*! + * chai + * Copyright(c) 2011 Jake Luer + * MIT Licensed + */ + +/*! + * Main exports + */ + +var exports = module.exports = {}; + +/*! + * test utility + */ + +exports.test = require('chai/lib/chai/utils/test.js'); + +/*! + * type utility + */ + +exports.type = require('chai/lib/chai/utils/type.js'); + +/*! + * message utility + */ + +exports.getMessage = require('chai/lib/chai/utils/getMessage.js'); + +/*! + * actual utility + */ + +exports.getActual = require('chai/lib/chai/utils/getActual.js'); + +/*! + * Inspect util + */ + +exports.inspect = require('chai/lib/chai/utils/inspect.js'); + +/*! + * Object Display util + */ + +exports.objDisplay = require('chai/lib/chai/utils/objDisplay.js'); + +/*! + * Flag utility + */ + +exports.flag = require('chai/lib/chai/utils/flag.js'); + +/*! + * Flag transferring utility + */ + +exports.transferFlags = require('chai/lib/chai/utils/transferFlags.js'); + +/*! + * Deep equal utility + */ + +exports.eql = require('chaijs~deep-eql@0.1.3'); + +/*! + * Deep path value + */ + +exports.getPathValue = require('chai/lib/chai/utils/getPathValue.js'); + +/*! + * Function name + */ + +exports.getName = require('chai/lib/chai/utils/getName.js'); + +/*! + * add Property + */ + +exports.addProperty = require('chai/lib/chai/utils/addProperty.js'); + +/*! + * add Method + */ + +exports.addMethod = require('chai/lib/chai/utils/addMethod.js'); + +/*! + * overwrite Property + */ + +exports.overwriteProperty = require('chai/lib/chai/utils/overwriteProperty.js'); + +/*! + * overwrite Method + */ + +exports.overwriteMethod = require('chai/lib/chai/utils/overwriteMethod.js'); + +/*! + * Add a chainable method + */ + +exports.addChainableMethod = require('chai/lib/chai/utils/addChainableMethod.js'); + +/*! + * Overwrite chainable method + */ + +exports.overwriteChainableMethod = require('chai/lib/chai/utils/overwriteChainableMethod.js'); + + +}); + +require.register("chai/lib/chai/utils/inspect.js", function (exports, module) { +// This is (almost) directly from Node.js utils +// https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/util.js + +var getName = require('chai/lib/chai/utils/getName.js'); +var getProperties = require('chai/lib/chai/utils/getProperties.js'); +var getEnumerableProperties = require('chai/lib/chai/utils/getEnumerableProperties.js'); + +module.exports = inspect; + +/** + * Echos the value of a value. Trys to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Boolean} showHidden Flag that shows hidden (not enumerable) + * properties of objects. + * @param {Number} depth Depth in which to descend in object. Default is 2. + * @param {Boolean} colors Flag to turn on ANSI escape codes to color the + * output. Default is false (no coloring). + */ +function inspect(obj, showHidden, depth, colors) { + var ctx = { + showHidden: showHidden, + seen: [], + stylize: function (str) { return str; } + }; + return formatValue(ctx, obj, (typeof depth === 'undefined' ? 2 : depth)); +} + +// Returns true if object is a DOM element. +var isDOMElement = function (object) { + if (typeof HTMLElement === 'object') { + return object instanceof HTMLElement; + } else { + return object && + typeof object === 'object' && + object.nodeType === 1 && + typeof object.nodeName === 'string'; + } +}; + +function formatValue(ctx, value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (value && typeof value.inspect === 'function' && + // Filter out the util module, it's inspect function is special + value.inspect !== exports.inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + var ret = value.inspect(recurseTimes); + if (typeof ret !== 'string') { + ret = formatValue(ctx, ret, recurseTimes); + } + return ret; + } + + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } + + // If this is a DOM element, try to get the outer HTML. + if (isDOMElement(value)) { + if ('outerHTML' in value) { + return value.outerHTML; + // This value does not have an outerHTML attribute, + // it could still be an XML element + } else { + // Attempt to serialize it + try { + if (document.xmlVersion) { + var xmlSerializer = new XMLSerializer(); + return xmlSerializer.serializeToString(value); + } else { + // Firefox 11- do not support outerHTML + // It does, however, support innerHTML + // Use the following to render the element + var ns = "http://www.w3.org/1999/xhtml"; + var container = document.createElementNS(ns, '_'); + + container.appendChild(value.cloneNode(false)); + html = container.innerHTML + .replace('><', '>' + value.innerHTML + '<'); + container.innerHTML = ''; + return html; + } + } catch (err) { + // This could be a non-native DOM implementation, + // continue with the normal flow: + // printing the element as if it is an object. + } + } + } + + // Look up the keys of the object. + var visibleKeys = getEnumerableProperties(value); + var keys = ctx.showHidden ? getProperties(value) : visibleKeys; + + // Some type of object without properties can be shortcutted. + // In IE, errors have a single `stack` property, or if they are vanilla `Error`, + // a `stack` plus `description` property; ignore those for consistency. + if (keys.length === 0 || (isError(value) && ( + (keys.length === 1 && keys[0] === 'stack') || + (keys.length === 2 && keys[0] === 'description' && keys[1] === 'stack') + ))) { + if (typeof value === 'function') { + var name = getName(value); + var nameSuffix = name ? ': ' + name : ''; + return ctx.stylize('[Function' + nameSuffix + ']', 'special'); + } + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toUTCString.call(value), 'date'); + } + if (isError(value)) { + return formatError(value); + } + } + + var base = '', array = false, braces = ['{', '}']; + + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } + + // Make functions say that they are functions + if (typeof value === 'function') { + var name = getName(value); + var nameSuffix = name ? ': ' + name : ''; + base = ' [Function' + nameSuffix + ']'; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + return formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } else { + return ctx.stylize('[Object]', 'special'); + } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function(key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } + + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); +} + + +function formatPrimitive(ctx, value) { + switch (typeof value) { + case 'undefined': + return ctx.stylize('undefined', 'undefined'); + + case 'string': + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + + case 'number': + if (value === 0 && (1/value) === -Infinity) { + return ctx.stylize('-0', 'number'); + } + return ctx.stylize('' + value, 'number'); + + case 'boolean': + return ctx.stylize('' + value, 'boolean'); + } + // For some reason typeof null is "object", so special case here. + if (value === null) { + return ctx.stylize('null', 'null'); + } +} + + +function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; +} + + +function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (Object.prototype.hasOwnProperty.call(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + String(i), true)); + } else { + output.push(''); + } + } + keys.forEach(function(key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + key, true)); + } + }); + return output; +} + + +function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str; + if (value.__lookupGetter__) { + if (value.__lookupGetter__(key)) { + if (value.__lookupSetter__(key)) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (value.__lookupSetter__(key)) { + str = ctx.stylize('[Setter]', 'special'); + } + } + } + if (visibleKeys.indexOf(key) < 0) { + name = '[' + key + ']'; + } + if (!str) { + if (ctx.seen.indexOf(value[key]) < 0) { + if (recurseTimes === null) { + str = formatValue(ctx, value[key], null); + } else { + str = formatValue(ctx, value[key], recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = ctx.stylize('[Circular]', 'special'); + } + } + if (typeof name === 'undefined') { + if (array && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } + + return name + ': ' + str; +} + + +function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = output.reduce(function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.length + 1; + }, 0); + + if (length > 60) { + return braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +} + +function isArray(ar) { + return Array.isArray(ar) || + (typeof ar === 'object' && objectToString(ar) === '[object Array]'); +} + +function isRegExp(re) { + return typeof re === 'object' && objectToString(re) === '[object RegExp]'; +} + +function isDate(d) { + return typeof d === 'object' && objectToString(d) === '[object Date]'; +} + +function isError(e) { + return typeof e === 'object' && objectToString(e) === '[object Error]'; +} + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + +}); + +require.register("chai/lib/chai/utils/objDisplay.js", function (exports, module) { +/*! + * Chai - flag utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependancies + */ + +var inspect = require('chai/lib/chai/utils/inspect.js'); +var config = require('chai/lib/chai/config.js'); + +/** + * ### .objDisplay (object) + * + * Determines if an object or an array matches + * criteria to be inspected in-line for error + * messages or should be truncated. + * + * @param {Mixed} javascript object to inspect + * @name objDisplay + * @api public + */ + +module.exports = function (obj) { + var str = inspect(obj) + , type = Object.prototype.toString.call(obj); + + if (config.truncateThreshold && str.length >= config.truncateThreshold) { + if (type === '[object Function]') { + return !obj.name || obj.name === '' + ? '[Function]' + : '[Function: ' + obj.name + ']'; + } else if (type === '[object Array]') { + return '[ Array(' + obj.length + ') ]'; + } else if (type === '[object Object]') { + var keys = Object.keys(obj) + , kstr = keys.length > 2 + ? keys.splice(0, 2).join(', ') + ', ...' + : keys.join(', '); + return '{ Object (' + kstr + ') }'; + } else { + return str; + } + } else { + return str; + } +}; + +}); + +require.register("chai/lib/chai/utils/overwriteMethod.js", function (exports, module) { +/*! + * Chai - overwriteMethod utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### overwriteMethod (ctx, name, fn) + * + * Overwites an already existing method and provides + * access to previous function. Must return function + * to be used for name. + * + * utils.overwriteMethod(chai.Assertion.prototype, 'equal', function (_super) { + * return function (str) { + * var obj = utils.flag(this, 'object'); + * if (obj instanceof Foo) { + * new chai.Assertion(obj.value).to.equal(str); + * } else { + * _super.apply(this, arguments); + * } + * } + * }); + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.overwriteMethod('foo', fn); + * + * Then can be used as any other assertion. + * + * expect(myFoo).to.equal('bar'); + * + * @param {Object} ctx object whose method is to be overwritten + * @param {String} name of method to overwrite + * @param {Function} method function that returns a function to be used for name + * @name overwriteMethod + * @api public + */ + +module.exports = function (ctx, name, method) { + var _method = ctx[name] + , _super = function () { return this; }; + + if (_method && 'function' === typeof _method) + _super = _method; + + ctx[name] = function () { + var result = method(_super).apply(this, arguments); + return result === undefined ? this : result; + } +}; + +}); + +require.register("chai/lib/chai/utils/overwriteProperty.js", function (exports, module) { +/*! + * Chai - overwriteProperty utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### overwriteProperty (ctx, name, fn) + * + * Overwites an already existing property getter and provides + * access to previous value. Must return function to use as getter. + * + * utils.overwriteProperty(chai.Assertion.prototype, 'ok', function (_super) { + * return function () { + * var obj = utils.flag(this, 'object'); + * if (obj instanceof Foo) { + * new chai.Assertion(obj.name).to.equal('bar'); + * } else { + * _super.call(this); + * } + * } + * }); + * + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.overwriteProperty('foo', fn); + * + * Then can be used as any other assertion. + * + * expect(myFoo).to.be.ok; + * + * @param {Object} ctx object whose property is to be overwritten + * @param {String} name of property to overwrite + * @param {Function} getter function that returns a getter function to be used for name + * @name overwriteProperty + * @api public + */ + +module.exports = function (ctx, name, getter) { + var _get = Object.getOwnPropertyDescriptor(ctx, name) + , _super = function () {}; + + if (_get && 'function' === typeof _get.get) + _super = _get.get + + Object.defineProperty(ctx, name, + { get: function () { + var result = getter(_super).call(this); + return result === undefined ? this : result; + } + , configurable: true + }); +}; + +}); + +require.register("chai/lib/chai/utils/overwriteChainableMethod.js", function (exports, module) { +/*! + * Chai - overwriteChainableMethod utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### overwriteChainableMethod (ctx, name, fn) + * + * Overwites an already existing chainable method + * and provides access to the previous function or + * property. Must return functions to be used for + * name. + * + * utils.overwriteChainableMethod(chai.Assertion.prototype, 'length', + * function (_super) { + * } + * , function (_super) { + * } + * ); + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.overwriteChainableMethod('foo', fn, fn); + * + * Then can be used as any other assertion. + * + * expect(myFoo).to.have.length(3); + * expect(myFoo).to.have.length.above(3); + * + * @param {Object} ctx object whose method / property is to be overwritten + * @param {String} name of method / property to overwrite + * @param {Function} method function that returns a function to be used for name + * @param {Function} chainingBehavior function that returns a function to be used for property + * @name overwriteChainableMethod + * @api public + */ + +module.exports = function (ctx, name, method, chainingBehavior) { + var chainableBehavior = ctx.__methods[name]; + + var _chainingBehavior = chainableBehavior.chainingBehavior; + chainableBehavior.chainingBehavior = function () { + var result = chainingBehavior(_chainingBehavior).call(this); + return result === undefined ? this : result; + }; + + var _method = chainableBehavior.method; + chainableBehavior.method = function () { + var result = method(_method).apply(this, arguments); + return result === undefined ? this : result; + }; +}; + +}); + +require.register("chai/lib/chai/utils/test.js", function (exports, module) { +/*! + * Chai - test utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependancies + */ + +var flag = require('chai/lib/chai/utils/flag.js'); + +/** + * # test(object, expression) + * + * Test and object for expression. + * + * @param {Object} object (constructed Assertion) + * @param {Arguments} chai.Assertion.prototype.assert arguments + */ + +module.exports = function (obj, args) { + var negate = flag(obj, 'negate') + , expr = args[0]; + return negate ? !expr : expr; +}; + +}); + +require.register("chai/lib/chai/utils/transferFlags.js", function (exports, module) { +/*! + * Chai - transferFlags utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### transferFlags(assertion, object, includeAll = true) + * + * Transfer all the flags for `assertion` to `object`. If + * `includeAll` is set to `false`, then the base Chai + * assertion flags (namely `object`, `ssfi`, and `message`) + * will not be transferred. + * + * + * var newAssertion = new Assertion(); + * utils.transferFlags(assertion, newAssertion); + * + * var anotherAsseriton = new Assertion(myObj); + * utils.transferFlags(assertion, anotherAssertion, false); + * + * @param {Assertion} assertion the assertion to transfer the flags from + * @param {Object} object the object to transfer the flags too; usually a new assertion + * @param {Boolean} includeAll + * @name getAllFlags + * @api private + */ + +module.exports = function (assertion, object, includeAll) { + var flags = assertion.__flags || (assertion.__flags = Object.create(null)); + + if (!object.__flags) { + object.__flags = Object.create(null); + } + + includeAll = arguments.length === 3 ? includeAll : true; + + for (var flag in flags) { + if (includeAll || + (flag !== 'object' && flag !== 'ssfi' && flag != 'message')) { + object.__flags[flag] = flags[flag]; + } + } +}; + +}); + +require.register("chai/lib/chai/utils/type.js", function (exports, module) { +/*! + * Chai - type utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Detectable javascript natives + */ + +var natives = { + '[object Arguments]': 'arguments' + , '[object Array]': 'array' + , '[object Date]': 'date' + , '[object Function]': 'function' + , '[object Number]': 'number' + , '[object RegExp]': 'regexp' + , '[object String]': 'string' +}; + +/** + * ### type(object) + * + * Better implementation of `typeof` detection that can + * be used cross-browser. Handles the inconsistencies of + * Array, `null`, and `undefined` detection. + * + * utils.type({}) // 'object' + * utils.type(null) // `null' + * utils.type(undefined) // `undefined` + * utils.type([]) // `array` + * + * @param {Mixed} object to detect type of + * @name type + * @api private + */ + +module.exports = function (obj) { + var str = Object.prototype.toString.call(obj); + if (natives[str]) return natives[str]; + if (obj === null) return 'null'; + if (obj === undefined) return 'undefined'; + if (obj === Object(obj)) return 'object'; + return typeof obj; +}; + +}); + +if (typeof exports == "object") { + module.exports = require("chai"); +} else if (typeof define == "function" && define.amd) { + define("chai", [], function(){ return require("chai"); }); +} else { + (this || window)["chai"] = require("chai"); +} +})() diff --git a/tests/automation/res/mocha.css b/tests/automation/res/mocha.css new file mode 100644 index 0000000000..42b9798fa4 --- /dev/null +++ b/tests/automation/res/mocha.css @@ -0,0 +1,270 @@ +@charset "utf-8"; + +body { + margin:0; +} + +#mocha { + font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; + margin: 60px 50px; +} + +#mocha ul, +#mocha li { + margin: 0; + padding: 0; +} + +#mocha ul { + list-style: none; +} + +#mocha h1, +#mocha h2 { + margin: 0; +} + +#mocha h1 { + margin-top: 15px; + font-size: 1em; + font-weight: 200; +} + +#mocha h1 a { + text-decoration: none; + color: inherit; +} + +#mocha h1 a:hover { + text-decoration: underline; +} + +#mocha .suite .suite h1 { + margin-top: 0; + font-size: .8em; +} + +#mocha .hidden { + display: none; +} + +#mocha h2 { + font-size: 12px; + font-weight: normal; + cursor: pointer; +} + +#mocha .suite { + margin-left: 15px; +} + +#mocha .test { + margin-left: 15px; + overflow: hidden; +} + +#mocha .test.pending:hover h2::after { + content: '(pending)'; + font-family: arial, sans-serif; +} + +#mocha .test.pass.medium .duration { + background: #c09853; +} + +#mocha .test.pass.slow .duration { + background: #b94a48; +} + +#mocha .test.pass::before { + content: '✓'; + font-size: 12px; + display: block; + float: left; + margin-right: 5px; + color: #00d6b2; +} + +#mocha .test.pass .duration { + font-size: 9px; + margin-left: 5px; + padding: 2px 5px; + color: #fff; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + -ms-border-radius: 5px; + -o-border-radius: 5px; + border-radius: 5px; +} + +#mocha .test.pass.fast .duration { + display: none; +} + +#mocha .test.pending { + color: #0b97c4; +} + +#mocha .test.pending::before { + content: '◦'; + color: #0b97c4; +} + +#mocha .test.fail { + color: #c00; +} + +#mocha .test.fail pre { + color: black; +} + +#mocha .test.fail::before { + content: '✖'; + font-size: 12px; + display: block; + float: left; + margin-right: 5px; + color: #c00; +} + +#mocha .test pre.error { + color: #c00; + max-height: 300px; + overflow: auto; +} + +/** + * (1): approximate for browsers not supporting calc + * (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border) + * ^^ seriously + */ +#mocha .test pre { + display: block; + float: left; + clear: left; + font: 12px/1.5 monaco, monospace; + margin: 5px; + padding: 15px; + border: 1px solid #eee; + max-width: 85%; /*(1)*/ + max-width: calc(100% - 42px); /*(2)*/ + word-wrap: break-word; + border-bottom-color: #ddd; + -webkit-border-radius: 3px; + -webkit-box-shadow: 0 1px 3px #eee; + -moz-border-radius: 3px; + -moz-box-shadow: 0 1px 3px #eee; + border-radius: 3px; +} + +#mocha .test h2 { + position: relative; +} + +#mocha .test a.replay { + position: absolute; + top: 3px; + right: 0; + text-decoration: none; + vertical-align: middle; + display: block; + width: 15px; + height: 15px; + line-height: 15px; + text-align: center; + background: #eee; + font-size: 15px; + -moz-border-radius: 15px; + border-radius: 15px; + -webkit-transition: opacity 200ms; + -moz-transition: opacity 200ms; + transition: opacity 200ms; + opacity: 0.3; + color: #888; +} + +#mocha .test:hover a.replay { + opacity: 1; +} + +#mocha-report.pass .test.fail { + display: none; +} + +#mocha-report.fail .test.pass { + display: none; +} + +#mocha-report.pending .test.pass, +#mocha-report.pending .test.fail { + display: none; +} +#mocha-report.pending .test.pass.pending { + display: block; +} + +#mocha-error { + color: #c00; + font-size: 1.5em; + font-weight: 100; + letter-spacing: 1px; +} + +#mocha-stats { + position: fixed; + top: 15px; + right: 10px; + font-size: 12px; + margin: 0; + color: #888; + z-index: 1; +} + +#mocha-stats .progress { + float: right; + padding-top: 0; +} + +#mocha-stats em { + color: black; +} + +#mocha-stats a { + text-decoration: none; + color: inherit; +} + +#mocha-stats a:hover { + border-bottom: 1px solid #eee; +} + +#mocha-stats li { + display: inline-block; + margin: 0 5px; + list-style: none; + padding-top: 11px; +} + +#mocha-stats canvas { + width: 40px; + height: 40px; +} + +#mocha code .comment { color: #ddd; } +#mocha code .init { color: #2f6fad; } +#mocha code .string { color: #5890ad; } +#mocha code .keyword { color: #8a6343; } +#mocha code .number { color: #2f6fad; } + +@media screen and (max-device-width: 480px) { + #mocha { + margin: 60px 0px; + } + + #mocha #stats { + position: absolute; + } +} diff --git a/tests/automation/res/mocha.js b/tests/automation/res/mocha.js new file mode 100644 index 0000000000..564a4f3184 --- /dev/null +++ b/tests/automation/res/mocha.js @@ -0,0 +1,6069 @@ +;(function(){ + +// CommonJS require() + +function require(p){ + var path = require.resolve(p) + , mod = require.modules[path]; + if (!mod) throw new Error('failed to require "' + p + '"'); + if (!mod.exports) { + mod.exports = {}; + mod.call(mod.exports, mod, mod.exports, require.relative(path)); + } + return mod.exports; + } + +require.modules = {}; + +require.resolve = function (path){ + var orig = path + , reg = path + '.js' + , index = path + '/index.js'; + return require.modules[reg] && reg + || require.modules[index] && index + || orig; + }; + +require.register = function (path, fn){ + require.modules[path] = fn; + }; + +require.relative = function (parent) { + return function(p){ + if ('.' != p.charAt(0)) return require(p); + + var path = parent.split('/') + , segs = p.split('/'); + path.pop(); + + for (var i = 0; i < segs.length; i++) { + var seg = segs[i]; + if ('..' == seg) path.pop(); + else if ('.' != seg) path.push(seg); + } + + return require(path.join('/')); + }; + }; + + +require.register("browser/debug.js", function(module, exports, require){ +module.exports = function(type){ + return function(){ + } +}; + +}); // module: browser/debug.js + +require.register("browser/diff.js", function(module, exports, require){ +/* See LICENSE file for terms of use */ + +/* + * Text diff implementation. + * + * This library supports the following APIS: + * JsDiff.diffChars: Character by character diff + * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace + * JsDiff.diffLines: Line based diff + * + * JsDiff.diffCss: Diff targeted at CSS content + * + * These methods are based on the implementation proposed in + * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). + * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 + */ +var JsDiff = (function() { + /*jshint maxparams: 5*/ + function clonePath(path) { + return { newPos: path.newPos, components: path.components.slice(0) }; + } + function removeEmpty(array) { + var ret = []; + for (var i = 0; i < array.length; i++) { + if (array[i]) { + ret.push(array[i]); + } + } + return ret; + } + function escapeHTML(s) { + var n = s; + n = n.replace(/&/g, '&'); + n = n.replace(//g, '>'); + n = n.replace(/"/g, '"'); + + return n; + } + + var Diff = function(ignoreWhitespace) { + this.ignoreWhitespace = ignoreWhitespace; + }; + Diff.prototype = { + diff: function(oldString, newString) { + // Handle the identity case (this is due to unrolling editLength == 0 + if (newString === oldString) { + return [{ value: newString }]; + } + if (!newString) { + return [{ value: oldString, removed: true }]; + } + if (!oldString) { + return [{ value: newString, added: true }]; + } + + newString = this.tokenize(newString); + oldString = this.tokenize(oldString); + + var newLen = newString.length, oldLen = oldString.length; + var maxEditLength = newLen + oldLen; + var bestPath = [{ newPos: -1, components: [] }]; + + // Seed editLength = 0 + var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); + if (bestPath[0].newPos+1 >= newLen && oldPos+1 >= oldLen) { + return bestPath[0].components; + } + + for (var editLength = 1; editLength <= maxEditLength; editLength++) { + for (var diagonalPath = -1*editLength; diagonalPath <= editLength; diagonalPath+=2) { + var basePath; + var addPath = bestPath[diagonalPath-1], + removePath = bestPath[diagonalPath+1]; + oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; + if (addPath) { + // No one else is going to attempt to use this value, clear it + bestPath[diagonalPath-1] = undefined; + } + + var canAdd = addPath && addPath.newPos+1 < newLen; + var canRemove = removePath && 0 <= oldPos && oldPos < oldLen; + if (!canAdd && !canRemove) { + bestPath[diagonalPath] = undefined; + continue; + } + + // Select the diagonal that we want to branch from. We select the prior + // path whose position in the new string is the farthest from the origin + // and does not pass the bounds of the diff graph + if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) { + basePath = clonePath(removePath); + this.pushComponent(basePath.components, oldString[oldPos], undefined, true); + } else { + basePath = clonePath(addPath); + basePath.newPos++; + this.pushComponent(basePath.components, newString[basePath.newPos], true, undefined); + } + + var oldPos = this.extractCommon(basePath, newString, oldString, diagonalPath); + + if (basePath.newPos+1 >= newLen && oldPos+1 >= oldLen) { + return basePath.components; + } else { + bestPath[diagonalPath] = basePath; + } + } + } + }, + + pushComponent: function(components, value, added, removed) { + var last = components[components.length-1]; + if (last && last.added === added && last.removed === removed) { + // We need to clone here as the component clone operation is just + // as shallow array clone + components[components.length-1] = + {value: this.join(last.value, value), added: added, removed: removed }; + } else { + components.push({value: value, added: added, removed: removed }); + } + }, + extractCommon: function(basePath, newString, oldString, diagonalPath) { + var newLen = newString.length, + oldLen = oldString.length, + newPos = basePath.newPos, + oldPos = newPos - diagonalPath; + while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) { + newPos++; + oldPos++; + + this.pushComponent(basePath.components, newString[newPos], undefined, undefined); + } + basePath.newPos = newPos; + return oldPos; + }, + + equals: function(left, right) { + var reWhitespace = /\S/; + if (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)) { + return true; + } else { + return left === right; + } + }, + join: function(left, right) { + return left + right; + }, + tokenize: function(value) { + return value; + } + }; + + var CharDiff = new Diff(); + + var WordDiff = new Diff(true); + var WordWithSpaceDiff = new Diff(); + WordDiff.tokenize = WordWithSpaceDiff.tokenize = function(value) { + return removeEmpty(value.split(/(\s+|\b)/)); + }; + + var CssDiff = new Diff(true); + CssDiff.tokenize = function(value) { + return removeEmpty(value.split(/([{}:;,]|\s+)/)); + }; + + var LineDiff = new Diff(); + LineDiff.tokenize = function(value) { + return value.split(/^/m); + }; + + return { + Diff: Diff, + + diffChars: function(oldStr, newStr) { return CharDiff.diff(oldStr, newStr); }, + diffWords: function(oldStr, newStr) { return WordDiff.diff(oldStr, newStr); }, + diffWordsWithSpace: function(oldStr, newStr) { return WordWithSpaceDiff.diff(oldStr, newStr); }, + diffLines: function(oldStr, newStr) { return LineDiff.diff(oldStr, newStr); }, + + diffCss: function(oldStr, newStr) { return CssDiff.diff(oldStr, newStr); }, + + createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) { + var ret = []; + + ret.push('Index: ' + fileName); + ret.push('==================================================================='); + ret.push('--- ' + fileName + (typeof oldHeader === 'undefined' ? '' : '\t' + oldHeader)); + ret.push('+++ ' + fileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader)); + + var diff = LineDiff.diff(oldStr, newStr); + if (!diff[diff.length-1].value) { + diff.pop(); // Remove trailing newline add + } + diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier + + function contextLines(lines) { + return lines.map(function(entry) { return ' ' + entry; }); + } + function eofNL(curRange, i, current) { + var last = diff[diff.length-2], + isLast = i === diff.length-2, + isLastOfType = i === diff.length-3 && (current.added !== last.added || current.removed !== last.removed); + + // Figure out if this is the last line for the given file and missing NL + if (!/\n$/.test(current.value) && (isLast || isLastOfType)) { + curRange.push('\\ No newline at end of file'); + } + } + + var oldRangeStart = 0, newRangeStart = 0, curRange = [], + oldLine = 1, newLine = 1; + for (var i = 0; i < diff.length; i++) { + var current = diff[i], + lines = current.lines || current.value.replace(/\n$/, '').split('\n'); + current.lines = lines; + + if (current.added || current.removed) { + if (!oldRangeStart) { + var prev = diff[i-1]; + oldRangeStart = oldLine; + newRangeStart = newLine; + + if (prev) { + curRange = contextLines(prev.lines.slice(-4)); + oldRangeStart -= curRange.length; + newRangeStart -= curRange.length; + } + } + curRange.push.apply(curRange, lines.map(function(entry) { return (current.added?'+':'-') + entry; })); + eofNL(curRange, i, current); + + if (current.added) { + newLine += lines.length; + } else { + oldLine += lines.length; + } + } else { + if (oldRangeStart) { + // Close out any changes that have been output (or join overlapping) + if (lines.length <= 8 && i < diff.length-2) { + // Overlapping + curRange.push.apply(curRange, contextLines(lines)); + } else { + // end the range and output + var contextSize = Math.min(lines.length, 4); + ret.push( + '@@ -' + oldRangeStart + ',' + (oldLine-oldRangeStart+contextSize) + + ' +' + newRangeStart + ',' + (newLine-newRangeStart+contextSize) + + ' @@'); + ret.push.apply(ret, curRange); + ret.push.apply(ret, contextLines(lines.slice(0, contextSize))); + if (lines.length <= 4) { + eofNL(ret, i, current); + } + + oldRangeStart = 0; newRangeStart = 0; curRange = []; + } + } + oldLine += lines.length; + newLine += lines.length; + } + } + + return ret.join('\n') + '\n'; + }, + + applyPatch: function(oldStr, uniDiff) { + var diffstr = uniDiff.split('\n'); + var diff = []; + var remEOFNL = false, + addEOFNL = false; + + for (var i = (diffstr[0][0]==='I'?4:0); i < diffstr.length; i++) { + if(diffstr[i][0] === '@') { + var meh = diffstr[i].split(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/); + diff.unshift({ + start:meh[3], + oldlength:meh[2], + oldlines:[], + newlength:meh[4], + newlines:[] + }); + } else if(diffstr[i][0] === '+') { + diff[0].newlines.push(diffstr[i].substr(1)); + } else if(diffstr[i][0] === '-') { + diff[0].oldlines.push(diffstr[i].substr(1)); + } else if(diffstr[i][0] === ' ') { + diff[0].newlines.push(diffstr[i].substr(1)); + diff[0].oldlines.push(diffstr[i].substr(1)); + } else if(diffstr[i][0] === '\\') { + if (diffstr[i-1][0] === '+') { + remEOFNL = true; + } else if(diffstr[i-1][0] === '-') { + addEOFNL = true; + } + } + } + + var str = oldStr.split('\n'); + for (var i = diff.length - 1; i >= 0; i--) { + var d = diff[i]; + for (var j = 0; j < d.oldlength; j++) { + if(str[d.start-1+j] !== d.oldlines[j]) { + return false; + } + } + Array.prototype.splice.apply(str,[d.start-1,+d.oldlength].concat(d.newlines)); + } + + if (remEOFNL) { + while (!str[str.length-1]) { + str.pop(); + } + } else if (addEOFNL) { + str.push(''); + } + return str.join('\n'); + }, + + convertChangesToXML: function(changes){ + var ret = []; + for ( var i = 0; i < changes.length; i++) { + var change = changes[i]; + if (change.added) { + ret.push(''); + } else if (change.removed) { + ret.push(''); + } + + ret.push(escapeHTML(change.value)); + + if (change.added) { + ret.push(''); + } else if (change.removed) { + ret.push(''); + } + } + return ret.join(''); + }, + + // See: http://code.google.com/p/google-diff-match-patch/wiki/API + convertChangesToDMP: function(changes){ + var ret = [], change; + for ( var i = 0; i < changes.length; i++) { + change = changes[i]; + ret.push([(change.added ? 1 : change.removed ? -1 : 0), change.value]); + } + return ret; + } + }; +})(); + +if (typeof module !== 'undefined') { + module.exports = JsDiff; +} + +}); // module: browser/diff.js + +require.register("browser/escape-string-regexp.js", function(module, exports, require){ +'use strict'; + +var matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g; + +module.exports = function (str) { + if (typeof str !== 'string') { + throw new TypeError('Expected a string'); + } + + return str.replace(matchOperatorsRe, '\\$&'); +}; + +}); // module: browser/escape-string-regexp.js + +require.register("browser/events.js", function(module, exports, require){ +/** + * Module exports. + */ + +exports.EventEmitter = EventEmitter; + +/** + * Check if `obj` is an array. + */ + +function isArray(obj) { + return '[object Array]' == {}.toString.call(obj); +} + +/** + * Event emitter constructor. + * + * @api public + */ + +function EventEmitter(){}; + +/** + * Adds a listener. + * + * @api public + */ + +EventEmitter.prototype.on = function (name, fn) { + if (!this.$events) { + this.$events = {}; + } + + if (!this.$events[name]) { + this.$events[name] = fn; + } else if (isArray(this.$events[name])) { + this.$events[name].push(fn); + } else { + this.$events[name] = [this.$events[name], fn]; + } + + return this; +}; + +EventEmitter.prototype.addListener = EventEmitter.prototype.on; + +/** + * Adds a volatile listener. + * + * @api public + */ + +EventEmitter.prototype.once = function (name, fn) { + var self = this; + + function on () { + self.removeListener(name, on); + fn.apply(this, arguments); + }; + + on.listener = fn; + this.on(name, on); + + return this; +}; + +/** + * Removes a listener. + * + * @api public + */ + +EventEmitter.prototype.removeListener = function (name, fn) { + if (this.$events && this.$events[name]) { + var list = this.$events[name]; + + if (isArray(list)) { + var pos = -1; + + for (var i = 0, l = list.length; i < l; i++) { + if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { + pos = i; + break; + } + } + + if (pos < 0) { + return this; + } + + list.splice(pos, 1); + + if (!list.length) { + delete this.$events[name]; + } + } else if (list === fn || (list.listener && list.listener === fn)) { + delete this.$events[name]; + } + } + + return this; +}; + +/** + * Removes all listeners for an event. + * + * @api public + */ + +EventEmitter.prototype.removeAllListeners = function (name) { + if (name === undefined) { + this.$events = {}; + return this; + } + + if (this.$events && this.$events[name]) { + this.$events[name] = null; + } + + return this; +}; + +/** + * Gets all listeners for a certain event. + * + * @api public + */ + +EventEmitter.prototype.listeners = function (name) { + if (!this.$events) { + this.$events = {}; + } + + if (!this.$events[name]) { + this.$events[name] = []; + } + + if (!isArray(this.$events[name])) { + this.$events[name] = [this.$events[name]]; + } + + return this.$events[name]; +}; + +/** + * Emits an event. + * + * @api public + */ + +EventEmitter.prototype.emit = function (name) { + if (!this.$events) { + return false; + } + + var handler = this.$events[name]; + + if (!handler) { + return false; + } + + var args = [].slice.call(arguments, 1); + + if ('function' == typeof handler) { + handler.apply(this, args); + } else if (isArray(handler)) { + var listeners = handler.slice(); + + for (var i = 0, l = listeners.length; i < l; i++) { + listeners[i].apply(this, args); + } + } else { + return false; + } + + return true; +}; + +}); // module: browser/events.js + +require.register("browser/fs.js", function(module, exports, require){ + +}); // module: browser/fs.js + +require.register("browser/glob.js", function(module, exports, require){ + +}); // module: browser/glob.js + +require.register("browser/path.js", function(module, exports, require){ + +}); // module: browser/path.js + +require.register("browser/progress.js", function(module, exports, require){ +/** + * Expose `Progress`. + */ + +module.exports = Progress; + +/** + * Initialize a new `Progress` indicator. + */ + +function Progress() { + this.percent = 0; + this.size(0); + this.fontSize(11); + this.font('helvetica, arial, sans-serif'); +} + +/** + * Set progress size to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.size = function(n){ + this._size = n; + return this; +}; + +/** + * Set text to `str`. + * + * @param {String} str + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.text = function(str){ + this._text = str; + return this; +}; + +/** + * Set font size to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.fontSize = function(n){ + this._fontSize = n; + return this; +}; + +/** + * Set font `family`. + * + * @param {String} family + * @return {Progress} for chaining + */ + +Progress.prototype.font = function(family){ + this._font = family; + return this; +}; + +/** + * Update percentage to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + */ + +Progress.prototype.update = function(n){ + this.percent = n; + return this; +}; + +/** + * Draw on `ctx`. + * + * @param {CanvasRenderingContext2d} ctx + * @return {Progress} for chaining + */ + +Progress.prototype.draw = function(ctx){ + try { + var percent = Math.min(this.percent, 100) + , size = this._size + , half = size / 2 + , x = half + , y = half + , rad = half - 1 + , fontSize = this._fontSize; + + ctx.font = fontSize + 'px ' + this._font; + + var angle = Math.PI * 2 * (percent / 100); + ctx.clearRect(0, 0, size, size); + + // outer circle + ctx.strokeStyle = '#9f9f9f'; + ctx.beginPath(); + ctx.arc(x, y, rad, 0, angle, false); + ctx.stroke(); + + // inner circle + ctx.strokeStyle = '#eee'; + ctx.beginPath(); + ctx.arc(x, y, rad - 1, 0, angle, true); + ctx.stroke(); + + // text + var text = this._text || (percent | 0) + '%' + , w = ctx.measureText(text).width; + + ctx.fillText( + text + , x - w / 2 + 1 + , y + fontSize / 2 - 1); + } catch (ex) {} //don't fail if we can't render progress + return this; +}; + +}); // module: browser/progress.js + +require.register("browser/tty.js", function(module, exports, require){ +exports.isatty = function(){ + return true; +}; + +exports.getWindowSize = function(){ + if ('innerHeight' in global) { + return [global.innerHeight, global.innerWidth]; + } else { + // In a Web Worker, the DOM Window is not available. + return [640, 480]; + } +}; + +}); // module: browser/tty.js + +require.register("context.js", function(module, exports, require){ +/** + * Expose `Context`. + */ + +module.exports = Context; + +/** + * Initialize a new `Context`. + * + * @api private + */ + +function Context(){} + +/** + * Set or get the context `Runnable` to `runnable`. + * + * @param {Runnable} runnable + * @return {Context} + * @api private + */ + +Context.prototype.runnable = function(runnable){ + if (0 == arguments.length) return this._runnable; + this.test = this._runnable = runnable; + return this; +}; + +/** + * Set test timeout `ms`. + * + * @param {Number} ms + * @return {Context} self + * @api private + */ + +Context.prototype.timeout = function(ms){ + if (arguments.length === 0) return this.runnable().timeout(); + this.runnable().timeout(ms); + return this; +}; + +/** + * Set test timeout `enabled`. + * + * @param {Boolean} enabled + * @return {Context} self + * @api private + */ + +Context.prototype.enableTimeouts = function (enabled) { + this.runnable().enableTimeouts(enabled); + return this; +}; + + +/** + * Set test slowness threshold `ms`. + * + * @param {Number} ms + * @return {Context} self + * @api private + */ + +Context.prototype.slow = function(ms){ + this.runnable().slow(ms); + return this; +}; + +/** + * Inspect the context void of `._runnable`. + * + * @return {String} + * @api private + */ + +Context.prototype.inspect = function(){ + return JSON.stringify(this, function(key, val){ + if ('_runnable' == key) return; + if ('test' == key) return; + return val; + }, 2); +}; + +}); // module: context.js + +require.register("hook.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Runnable = require('./runnable'); + +/** + * Expose `Hook`. + */ + +module.exports = Hook; + +/** + * Initialize a new `Hook` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Hook(title, fn) { + Runnable.call(this, title, fn); + this.type = 'hook'; +} + +/** + * Inherit from `Runnable.prototype`. + */ + +function F(){}; +F.prototype = Runnable.prototype; +Hook.prototype = new F; +Hook.prototype.constructor = Hook; + + +/** + * Get or set the test `err`. + * + * @param {Error} err + * @return {Error} + * @api public + */ + +Hook.prototype.error = function(err){ + if (0 == arguments.length) { + var err = this._error; + this._error = null; + return err; + } + + this._error = err; +}; + +}); // module: hook.js + +require.register("interfaces/bdd.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test') + , utils = require('../utils') + , escapeRe = require('browser/escape-string-regexp'); + +/** + * BDD-style interface: + * + * describe('Array', function(){ + * describe('#indexOf()', function(){ + * it('should return -1 when not present', function(){ + * + * }); + * + * it('should return the index when present', function(){ + * + * }); + * }); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context, file, mocha){ + + /** + * Execute before running tests. + */ + + context.before = function(name, fn){ + suites[0].beforeAll(name, fn); + }; + + /** + * Execute after running tests. + */ + + context.after = function(name, fn){ + suites[0].afterAll(name, fn); + }; + + /** + * Execute before each test case. + */ + + context.beforeEach = function(name, fn){ + suites[0].beforeEach(name, fn); + }; + + /** + * Execute after each test case. + */ + + context.afterEach = function(name, fn){ + suites[0].afterEach(name, fn); + }; + + /** + * Describe a "suite" with the given `title` + * and callback `fn` containing nested suites + * and/or tests. + */ + + context.describe = context.context = function(title, fn){ + var suite = Suite.create(suites[0], title); + suite.file = file; + suites.unshift(suite); + fn.call(suite); + suites.shift(); + return suite; + }; + + /** + * Pending describe. + */ + + context.xdescribe = + context.xcontext = + context.describe.skip = function(title, fn){ + var suite = Suite.create(suites[0], title); + suite.pending = true; + suites.unshift(suite); + fn.call(suite); + suites.shift(); + }; + + /** + * Exclusive suite. + */ + + context.describe.only = function(title, fn){ + var suite = context.describe(title, fn); + mocha.grep(suite.fullTitle()); + return suite; + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.it = context.specify = function(title, fn){ + var suite = suites[0]; + if (suite.pending) fn = null; + var test = new Test(title, fn); + test.file = file; + suite.addTest(test); + return test; + }; + + /** + * Exclusive test-case. + */ + + context.it.only = function(title, fn){ + var test = context.it(title, fn); + var reString = '^' + escapeRe(test.fullTitle()) + '$'; + mocha.grep(new RegExp(reString)); + return test; + }; + + /** + * Pending test case. + */ + + context.xit = + context.xspecify = + context.it.skip = function(title){ + context.it(title); + }; + }); +}; + +}); // module: interfaces/bdd.js + +require.register("interfaces/exports.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * TDD-style interface: + * + * exports.Array = { + * '#indexOf()': { + * 'should return -1 when the value is not present': function(){ + * + * }, + * + * 'should return the correct index when the value is present': function(){ + * + * } + * } + * }; + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('require', visit); + + function visit(obj, file) { + var suite; + for (var key in obj) { + if ('function' == typeof obj[key]) { + var fn = obj[key]; + switch (key) { + case 'before': + suites[0].beforeAll(fn); + break; + case 'after': + suites[0].afterAll(fn); + break; + case 'beforeEach': + suites[0].beforeEach(fn); + break; + case 'afterEach': + suites[0].afterEach(fn); + break; + default: + var test = new Test(key, fn); + test.file = file; + suites[0].addTest(test); + } + } else { + suite = Suite.create(suites[0], key); + suites.unshift(suite); + visit(obj[key]); + suites.shift(); + } + } + } +}; + +}); // module: interfaces/exports.js + +require.register("interfaces/index.js", function(module, exports, require){ +exports.bdd = require('./bdd'); +exports.tdd = require('./tdd'); +exports.qunit = require('./qunit'); +exports.exports = require('./exports'); + +}); // module: interfaces/index.js + +require.register("interfaces/qunit.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test') + , escapeRe = require('browser/escape-string-regexp') + , utils = require('../utils'); + +/** + * QUnit-style interface: + * + * suite('Array'); + * + * test('#length', function(){ + * var arr = [1,2,3]; + * ok(arr.length == 3); + * }); + * + * test('#indexOf()', function(){ + * var arr = [1,2,3]; + * ok(arr.indexOf(1) == 0); + * ok(arr.indexOf(2) == 1); + * ok(arr.indexOf(3) == 2); + * }); + * + * suite('String'); + * + * test('#length', function(){ + * ok('foo'.length == 3); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context, file, mocha){ + + /** + * Execute before running tests. + */ + + context.before = function(name, fn){ + suites[0].beforeAll(name, fn); + }; + + /** + * Execute after running tests. + */ + + context.after = function(name, fn){ + suites[0].afterAll(name, fn); + }; + + /** + * Execute before each test case. + */ + + context.beforeEach = function(name, fn){ + suites[0].beforeEach(name, fn); + }; + + /** + * Execute after each test case. + */ + + context.afterEach = function(name, fn){ + suites[0].afterEach(name, fn); + }; + + /** + * Describe a "suite" with the given `title`. + */ + + context.suite = function(title){ + if (suites.length > 1) suites.shift(); + var suite = Suite.create(suites[0], title); + suite.file = file; + suites.unshift(suite); + return suite; + }; + + /** + * Exclusive test-case. + */ + + context.suite.only = function(title, fn){ + var suite = context.suite(title, fn); + mocha.grep(suite.fullTitle()); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.test = function(title, fn){ + var test = new Test(title, fn); + test.file = file; + suites[0].addTest(test); + return test; + }; + + /** + * Exclusive test-case. + */ + + context.test.only = function(title, fn){ + var test = context.test(title, fn); + var reString = '^' + escapeRe(test.fullTitle()) + '$'; + mocha.grep(new RegExp(reString)); + }; + + /** + * Pending test case. + */ + + context.test.skip = function(title){ + context.test(title); + }; + }); +}; + +}); // module: interfaces/qunit.js + +require.register("interfaces/tdd.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test') + , escapeRe = require('browser/escape-string-regexp') + , utils = require('../utils'); + +/** + * TDD-style interface: + * + * suite('Array', function(){ + * suite('#indexOf()', function(){ + * suiteSetup(function(){ + * + * }); + * + * test('should return -1 when not present', function(){ + * + * }); + * + * test('should return the index when present', function(){ + * + * }); + * + * suiteTeardown(function(){ + * + * }); + * }); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context, file, mocha){ + + /** + * Execute before each test case. + */ + + context.setup = function(name, fn){ + suites[0].beforeEach(name, fn); + }; + + /** + * Execute after each test case. + */ + + context.teardown = function(name, fn){ + suites[0].afterEach(name, fn); + }; + + /** + * Execute before the suite. + */ + + context.suiteSetup = function(name, fn){ + suites[0].beforeAll(name, fn); + }; + + /** + * Execute after the suite. + */ + + context.suiteTeardown = function(name, fn){ + suites[0].afterAll(name, fn); + }; + + /** + * Describe a "suite" with the given `title` + * and callback `fn` containing nested suites + * and/or tests. + */ + + context.suite = function(title, fn){ + var suite = Suite.create(suites[0], title); + suite.file = file; + suites.unshift(suite); + fn.call(suite); + suites.shift(); + return suite; + }; + + /** + * Pending suite. + */ + context.suite.skip = function(title, fn) { + var suite = Suite.create(suites[0], title); + suite.pending = true; + suites.unshift(suite); + fn.call(suite); + suites.shift(); + }; + + /** + * Exclusive test-case. + */ + + context.suite.only = function(title, fn){ + var suite = context.suite(title, fn); + mocha.grep(suite.fullTitle()); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.test = function(title, fn){ + var suite = suites[0]; + if (suite.pending) fn = null; + var test = new Test(title, fn); + test.file = file; + suite.addTest(test); + return test; + }; + + /** + * Exclusive test-case. + */ + + context.test.only = function(title, fn){ + var test = context.test(title, fn); + var reString = '^' + escapeRe(test.fullTitle()) + '$'; + mocha.grep(new RegExp(reString)); + }; + + /** + * Pending test case. + */ + + context.test.skip = function(title){ + context.test(title); + }; + }); +}; + +}); // module: interfaces/tdd.js + +require.register("mocha.js", function(module, exports, require){ +/*! + * mocha + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var path = require('browser/path') + , escapeRe = require('browser/escape-string-regexp') + , utils = require('./utils'); + +/** + * Expose `Mocha`. + */ + +exports = module.exports = Mocha; + +/** + * To require local UIs and reporters when running in node. + */ + +if (typeof process !== 'undefined' && typeof process.cwd === 'function') { + var join = path.join + , cwd = process.cwd(); + module.paths.push(cwd, join(cwd, 'node_modules')); +} + +/** + * Expose internals. + */ + +exports.utils = utils; +exports.interfaces = require('./interfaces'); +exports.reporters = require('./reporters'); +exports.Runnable = require('./runnable'); +exports.Context = require('./context'); +exports.Runner = require('./runner'); +exports.Suite = require('./suite'); +exports.Hook = require('./hook'); +exports.Test = require('./test'); + +/** + * Return image `name` path. + * + * @param {String} name + * @return {String} + * @api private + */ + +function image(name) { + return __dirname + '/../images/' + name + '.png'; +} + +/** + * Setup mocha with `options`. + * + * Options: + * + * - `ui` name "bdd", "tdd", "exports" etc + * - `reporter` reporter instance, defaults to `mocha.reporters.spec` + * - `globals` array of accepted globals + * - `timeout` timeout in milliseconds + * - `bail` bail on the first test failure + * - `slow` milliseconds to wait before considering a test slow + * - `ignoreLeaks` ignore global leaks + * - `grep` string or regexp to filter tests with + * + * @param {Object} options + * @api public + */ + +function Mocha(options) { + options = options || {}; + this.files = []; + this.options = options; + this.grep(options.grep); + this.suite = new exports.Suite('', new exports.Context); + this.ui(options.ui); + this.bail(options.bail); + this.reporter(options.reporter); + if (null != options.timeout) this.timeout(options.timeout); + this.useColors(options.useColors) + if (options.enableTimeouts !== null) this.enableTimeouts(options.enableTimeouts); + if (options.slow) this.slow(options.slow); + + this.suite.on('pre-require', function (context) { + exports.afterEach = context.afterEach || context.teardown; + exports.after = context.after || context.suiteTeardown; + exports.beforeEach = context.beforeEach || context.setup; + exports.before = context.before || context.suiteSetup; + exports.describe = context.describe || context.suite; + exports.it = context.it || context.test; + exports.setup = context.setup || context.beforeEach; + exports.suiteSetup = context.suiteSetup || context.before; + exports.suiteTeardown = context.suiteTeardown || context.after; + exports.suite = context.suite || context.describe; + exports.teardown = context.teardown || context.afterEach; + exports.test = context.test || context.it; + }); +} + +/** + * Enable or disable bailing on the first failure. + * + * @param {Boolean} [bail] + * @api public + */ + +Mocha.prototype.bail = function(bail){ + if (0 == arguments.length) bail = true; + this.suite.bail(bail); + return this; +}; + +/** + * Add test `file`. + * + * @param {String} file + * @api public + */ + +Mocha.prototype.addFile = function(file){ + this.files.push(file); + return this; +}; + +/** + * Set reporter to `reporter`, defaults to "spec". + * + * @param {String|Function} reporter name or constructor + * @api public + */ + +Mocha.prototype.reporter = function(reporter){ + if ('function' == typeof reporter) { + this._reporter = reporter; + } else { + reporter = reporter || 'spec'; + var _reporter; + try { _reporter = require('./reporters/' + reporter); } catch (err) {}; + if (!_reporter) try { _reporter = require(reporter); } catch (err) {}; + if (!_reporter && reporter === 'teamcity') + console.warn('The Teamcity reporter was moved to a package named ' + + 'mocha-teamcity-reporter ' + + '(https://npmjs.org/package/mocha-teamcity-reporter).'); + if (!_reporter) throw new Error('invalid reporter "' + reporter + '"'); + this._reporter = _reporter; + } + return this; +}; + +/** + * Set test UI `name`, defaults to "bdd". + * + * @param {String} bdd + * @api public + */ + +Mocha.prototype.ui = function(name){ + name = name || 'bdd'; + this._ui = exports.interfaces[name]; + if (!this._ui) try { this._ui = require(name); } catch (err) {}; + if (!this._ui) throw new Error('invalid interface "' + name + '"'); + this._ui = this._ui(this.suite); + return this; +}; + +/** + * Load registered files. + * + * @api private + */ + +Mocha.prototype.loadFiles = function(fn){ + var self = this; + var suite = this.suite; + var pending = this.files.length; + this.files.forEach(function(file){ + file = path.resolve(file); + suite.emit('pre-require', global, file, self); + suite.emit('require', require(file), file, self); + suite.emit('post-require', global, file, self); + --pending || (fn && fn()); + }); +}; + +/** + * Enable growl support. + * + * @api private + */ + +Mocha.prototype._growl = function(runner, reporter) { + var notify = require('growl'); + + runner.on('end', function(){ + var stats = reporter.stats; + if (stats.failures) { + var msg = stats.failures + ' of ' + runner.total + ' tests failed'; + notify(msg, { name: 'mocha', title: 'Failed', image: image('error') }); + } else { + notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { + name: 'mocha' + , title: 'Passed' + , image: image('ok') + }); + } + }); +}; + +/** + * Add regexp to grep, if `re` is a string it is escaped. + * + * @param {RegExp|String} re + * @return {Mocha} + * @api public + */ + +Mocha.prototype.grep = function(re){ + this.options.grep = 'string' == typeof re + ? new RegExp(escapeRe(re)) + : re; + return this; +}; + +/** + * Invert `.grep()` matches. + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.invert = function(){ + this.options.invert = true; + return this; +}; + +/** + * Ignore global leaks. + * + * @param {Boolean} ignore + * @return {Mocha} + * @api public + */ + +Mocha.prototype.ignoreLeaks = function(ignore){ + this.options.ignoreLeaks = !!ignore; + return this; +}; + +/** + * Enable global leak checking. + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.checkLeaks = function(){ + this.options.ignoreLeaks = false; + return this; +}; + +/** + * Enable growl support. + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.growl = function(){ + this.options.growl = true; + return this; +}; + +/** + * Ignore `globals` array or string. + * + * @param {Array|String} globals + * @return {Mocha} + * @api public + */ + +Mocha.prototype.globals = function(globals){ + this.options.globals = (this.options.globals || []).concat(globals); + return this; +}; + +/** + * Emit color output. + * + * @param {Boolean} colors + * @return {Mocha} + * @api public + */ + +Mocha.prototype.useColors = function(colors){ + this.options.useColors = arguments.length && colors != undefined + ? colors + : true; + return this; +}; + +/** + * Use inline diffs rather than +/-. + * + * @param {Boolean} inlineDiffs + * @return {Mocha} + * @api public + */ + +Mocha.prototype.useInlineDiffs = function(inlineDiffs) { + this.options.useInlineDiffs = arguments.length && inlineDiffs != undefined + ? inlineDiffs + : false; + return this; +}; + +/** + * Set the timeout in milliseconds. + * + * @param {Number} timeout + * @return {Mocha} + * @api public + */ + +Mocha.prototype.timeout = function(timeout){ + this.suite.timeout(timeout); + return this; +}; + +/** + * Set slowness threshold in milliseconds. + * + * @param {Number} slow + * @return {Mocha} + * @api public + */ + +Mocha.prototype.slow = function(slow){ + this.suite.slow(slow); + return this; +}; + +/** + * Enable timeouts. + * + * @param {Boolean} enabled + * @return {Mocha} + * @api public + */ + +Mocha.prototype.enableTimeouts = function(enabled) { + this.suite.enableTimeouts(arguments.length && enabled !== undefined + ? enabled + : true); + return this +}; + +/** + * Makes all tests async (accepting a callback) + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.asyncOnly = function(){ + this.options.asyncOnly = true; + return this; +}; + +/** + * Disable syntax highlighting (in browser). + * @returns {Mocha} + * @api public + */ +Mocha.prototype.noHighlighting = function() { + this.options.noHighlighting = true; + return this; +}; + +/** + * Run tests and invoke `fn()` when complete. + * + * @param {Function} fn + * @return {Runner} + * @api public + */ + +Mocha.prototype.run = function(fn){ + if (this.files.length) this.loadFiles(); + var suite = this.suite; + var options = this.options; + options.files = this.files; + var runner = new exports.Runner(suite); + var reporter = new this._reporter(runner, options); + runner.ignoreLeaks = false !== options.ignoreLeaks; + runner.asyncOnly = options.asyncOnly; + if (options.grep) runner.grep(options.grep, options.invert); + if (options.globals) runner.globals(options.globals); + if (options.growl) this._growl(runner, reporter); + exports.reporters.Base.useColors = options.useColors; + exports.reporters.Base.inlineDiffs = options.useInlineDiffs; + return runner.run(fn); +}; + +}); // module: mocha.js + +require.register("ms.js", function(module, exports, require){ +/** + * Helpers. + */ + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; +var y = d * 365.25; + +/** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} options + * @return {String|Number} + * @api public + */ + +module.exports = function(val, options){ + options = options || {}; + if ('string' == typeof val) return parse(val); + return options['long'] ? longFormat(val) : shortFormat(val); +}; + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + var match = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str); + if (!match) return; + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'y': + return n * y; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 's': + return n * s; + case 'ms': + return n; + } +} + +/** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function shortFormat(ms) { + if (ms >= d) return Math.round(ms / d) + 'd'; + if (ms >= h) return Math.round(ms / h) + 'h'; + if (ms >= m) return Math.round(ms / m) + 'm'; + if (ms >= s) return Math.round(ms / s) + 's'; + return ms + 'ms'; +} + +/** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function longFormat(ms) { + return plural(ms, d, 'day') + || plural(ms, h, 'hour') + || plural(ms, m, 'minute') + || plural(ms, s, 'second') + || ms + ' ms'; +} + +/** + * Pluralization helper. + */ + +function plural(ms, n, name) { + if (ms < n) return; + if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name; + return Math.ceil(ms / n) + ' ' + name + 's'; +} + +}); // module: ms.js + +require.register("reporters/base.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var tty = require('browser/tty') + , diff = require('browser/diff') + , ms = require('../ms') + , utils = require('../utils'); + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date + , setTimeout = global.setTimeout + , setInterval = global.setInterval + , clearTimeout = global.clearTimeout + , clearInterval = global.clearInterval; + +/** + * Check if both stdio streams are associated with a tty. + */ + +var isatty = tty.isatty(1) && tty.isatty(2); + +/** + * Expose `Base`. + */ + +exports = module.exports = Base; + +/** + * Enable coloring by default. + */ + +exports.useColors = isatty || (process.env.MOCHA_COLORS !== undefined); + +/** + * Inline diffs instead of +/- + */ + +exports.inlineDiffs = false; + +/** + * Default color map. + */ + +exports.colors = { + 'pass': 90 + , 'fail': 31 + , 'bright pass': 92 + , 'bright fail': 91 + , 'bright yellow': 93 + , 'pending': 36 + , 'suite': 0 + , 'error title': 0 + , 'error message': 31 + , 'error stack': 90 + , 'checkmark': 32 + , 'fast': 90 + , 'medium': 33 + , 'slow': 31 + , 'green': 32 + , 'light': 90 + , 'diff gutter': 90 + , 'diff added': 42 + , 'diff removed': 41 +}; + +/** + * Default symbol map. + */ + +exports.symbols = { + ok: '✓', + err: '✖', + dot: '․' +}; + +// With node.js on Windows: use symbols available in terminal default fonts +if ('win32' == process.platform) { + exports.symbols.ok = '\u221A'; + exports.symbols.err = '\u00D7'; + exports.symbols.dot = '.'; +} + +/** + * Color `str` with the given `type`, + * allowing colors to be disabled, + * as well as user-defined color + * schemes. + * + * @param {String} type + * @param {String} str + * @return {String} + * @api private + */ + +var color = exports.color = function(type, str) { + if (!exports.useColors) return str; + return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; +}; + +/** + * Expose term window size, with some + * defaults for when stderr is not a tty. + */ + +exports.window = { + width: isatty + ? process.stdout.getWindowSize + ? process.stdout.getWindowSize(1)[0] + : tty.getWindowSize()[1] + : 75 +}; + +/** + * Expose some basic cursor interactions + * that are common among reporters. + */ + +exports.cursor = { + hide: function(){ + isatty && process.stdout.write('\u001b[?25l'); + }, + + show: function(){ + isatty && process.stdout.write('\u001b[?25h'); + }, + + deleteLine: function(){ + isatty && process.stdout.write('\u001b[2K'); + }, + + beginningOfLine: function(){ + isatty && process.stdout.write('\u001b[0G'); + }, + + CR: function(){ + if (isatty) { + exports.cursor.deleteLine(); + exports.cursor.beginningOfLine(); + } else { + process.stdout.write('\r'); + } + } +}; + +/** + * Outut the given `failures` as a list. + * + * @param {Array} failures + * @api public + */ + +exports.list = function(failures){ + console.error(); + failures.forEach(function(test, i){ + // format + var fmt = color('error title', ' %s) %s:\n') + + color('error message', ' %s') + + color('error stack', '\n%s\n'); + + // msg + var err = test.err + , message = err.message || '' + , stack = err.stack || message + , index = stack.indexOf(message) + message.length + , msg = stack.slice(0, index) + , actual = err.actual + , expected = err.expected + , escape = true; + + // uncaught + if (err.uncaught) { + msg = 'Uncaught ' + msg; + } + + // explicitly show diff + if (err.showDiff && sameType(actual, expected)) { + escape = false; + err.actual = actual = utils.stringify(actual); + err.expected = expected = utils.stringify(expected); + } + + // actual / expected diff + if (err.showDiff && 'string' == typeof actual && 'string' == typeof expected) { + fmt = color('error title', ' %s) %s:\n%s') + color('error stack', '\n%s\n'); + var match = message.match(/^([^:]+): expected/); + msg = '\n ' + color('error message', match ? match[1] : msg); + + if (exports.inlineDiffs) { + msg += inlineDiff(err, escape); + } else { + msg += unifiedDiff(err, escape); + } + } + + // indent stack trace without msg + stack = stack.slice(index ? index + 1 : index) + .replace(/^/gm, ' '); + + console.error(fmt, (i + 1), test.fullTitle(), msg, stack); + }); +}; + +/** + * Initialize a new `Base` reporter. + * + * All other reporters generally + * inherit from this reporter, providing + * stats such as test duration, number + * of tests passed / failed etc. + * + * @param {Runner} runner + * @api public + */ + +function Base(runner) { + var self = this + , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 } + , failures = this.failures = []; + + if (!runner) return; + this.runner = runner; + + runner.stats = stats; + + runner.on('start', function(){ + stats.start = new Date; + }); + + runner.on('suite', function(suite){ + stats.suites = stats.suites || 0; + suite.root || stats.suites++; + }); + + runner.on('test end', function(test){ + stats.tests = stats.tests || 0; + stats.tests++; + }); + + runner.on('pass', function(test){ + stats.passes = stats.passes || 0; + + var medium = test.slow() / 2; + test.speed = test.duration > test.slow() + ? 'slow' + : test.duration > medium + ? 'medium' + : 'fast'; + + stats.passes++; + }); + + runner.on('fail', function(test, err){ + stats.failures = stats.failures || 0; + stats.failures++; + test.err = err; + failures.push(test); + }); + + runner.on('end', function(){ + stats.end = new Date; + stats.duration = new Date - stats.start; + }); + + runner.on('pending', function(){ + stats.pending++; + }); +} + +/** + * Output common epilogue used by many of + * the bundled reporters. + * + * @api public + */ + +Base.prototype.epilogue = function(){ + var stats = this.stats; + var tests; + var fmt; + + console.log(); + + // passes + fmt = color('bright pass', ' ') + + color('green', ' %d passing') + + color('light', ' (%s)'); + + console.log(fmt, + stats.passes || 0, + ms(stats.duration)); + + // pending + if (stats.pending) { + fmt = color('pending', ' ') + + color('pending', ' %d pending'); + + console.log(fmt, stats.pending); + } + + // failures + if (stats.failures) { + fmt = color('fail', ' %d failing'); + + console.error(fmt, + stats.failures); + + Base.list(this.failures); + console.error(); + } + + console.log(); +}; + +/** + * Pad the given `str` to `len`. + * + * @param {String} str + * @param {String} len + * @return {String} + * @api private + */ + +function pad(str, len) { + str = String(str); + return Array(len - str.length + 1).join(' ') + str; +} + + +/** + * Returns an inline diff between 2 strings with coloured ANSI output + * + * @param {Error} Error with actual/expected + * @return {String} Diff + * @api private + */ + +function inlineDiff(err, escape) { + var msg = errorDiff(err, 'WordsWithSpace', escape); + + // linenos + var lines = msg.split('\n'); + if (lines.length > 4) { + var width = String(lines.length).length; + msg = lines.map(function(str, i){ + return pad(++i, width) + ' |' + ' ' + str; + }).join('\n'); + } + + // legend + msg = '\n' + + color('diff removed', 'actual') + + ' ' + + color('diff added', 'expected') + + '\n\n' + + msg + + '\n'; + + // indent + msg = msg.replace(/^/gm, ' '); + return msg; +} + +/** + * Returns a unified diff between 2 strings + * + * @param {Error} Error with actual/expected + * @return {String} Diff + * @api private + */ + +function unifiedDiff(err, escape) { + var indent = ' '; + function cleanUp(line) { + if (escape) { + line = escapeInvisibles(line); + } + if (line[0] === '+') return indent + colorLines('diff added', line); + if (line[0] === '-') return indent + colorLines('diff removed', line); + if (line.match(/\@\@/)) return null; + if (line.match(/\\ No newline/)) return null; + else return indent + line; + } + function notBlank(line) { + return line != null; + } + msg = diff.createPatch('string', err.actual, err.expected); + var lines = msg.split('\n').splice(4); + return '\n ' + + colorLines('diff added', '+ expected') + ' ' + + colorLines('diff removed', '- actual') + + '\n\n' + + lines.map(cleanUp).filter(notBlank).join('\n'); +} + +/** + * Return a character diff for `err`. + * + * @param {Error} err + * @return {String} + * @api private + */ + +function errorDiff(err, type, escape) { + var actual = escape ? escapeInvisibles(err.actual) : err.actual; + var expected = escape ? escapeInvisibles(err.expected) : err.expected; + return diff['diff' + type](actual, expected).map(function(str){ + if (str.added) return colorLines('diff added', str.value); + if (str.removed) return colorLines('diff removed', str.value); + return str.value; + }).join(''); +} + +/** + * Returns a string with all invisible characters in plain text + * + * @param {String} line + * @return {String} + * @api private + */ +function escapeInvisibles(line) { + return line.replace(/\t/g, '') + .replace(/\r/g, '') + .replace(/\n/g, '\n'); +} + +/** + * Color lines for `str`, using the color `name`. + * + * @param {String} name + * @param {String} str + * @return {String} + * @api private + */ + +function colorLines(name, str) { + return str.split('\n').map(function(str){ + return color(name, str); + }).join('\n'); +} + +/** + * Check that a / b have the same type. + * + * @param {Object} a + * @param {Object} b + * @return {Boolean} + * @api private + */ + +function sameType(a, b) { + a = Object.prototype.toString.call(a); + b = Object.prototype.toString.call(b); + return a == b; +} + +}); // module: reporters/base.js + +require.register("reporters/doc.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils'); + +/** + * Expose `Doc`. + */ + +exports = module.exports = Doc; + +/** + * Initialize a new `Doc` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Doc(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , indents = 2; + + function indent() { + return Array(indents).join(' '); + } + + runner.on('suite', function(suite){ + if (suite.root) return; + ++indents; + console.log('%s
          ', indent()); + ++indents; + console.log('%s

          %s

          ', indent(), utils.escape(suite.title)); + console.log('%s
          ', indent()); + }); + + runner.on('suite end', function(suite){ + if (suite.root) return; + console.log('%s
          ', indent()); + --indents; + console.log('%s
          ', indent()); + --indents; + }); + + runner.on('pass', function(test){ + console.log('%s
          %s
          ', indent(), utils.escape(test.title)); + var code = utils.escape(utils.clean(test.fn.toString())); + console.log('%s
          %s
          ', indent(), code); + }); + + runner.on('fail', function(test, err){ + console.log('%s
          %s
          ', indent(), utils.escape(test.title)); + var code = utils.escape(utils.clean(test.fn.toString())); + console.log('%s
          %s
          ', indent(), code); + console.log('%s
          %s
          ', indent(), utils.escape(err)); + }); +} + +}); // module: reporters/doc.js + +require.register("reporters/dot.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `Dot`. + */ + +exports = module.exports = Dot; + +/** + * Initialize a new `Dot` matrix test reporter. + * + * @param {Runner} runner + * @api public + */ + +function Dot(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , n = -1; + + runner.on('start', function(){ + process.stdout.write('\n '); + }); + + runner.on('pending', function(test){ + if (++n % width == 0) process.stdout.write('\n '); + process.stdout.write(color('pending', Base.symbols.dot)); + }); + + runner.on('pass', function(test){ + if (++n % width == 0) process.stdout.write('\n '); + if ('slow' == test.speed) { + process.stdout.write(color('bright yellow', Base.symbols.dot)); + } else { + process.stdout.write(color(test.speed, Base.symbols.dot)); + } + }); + + runner.on('fail', function(test, err){ + if (++n % width == 0) process.stdout.write('\n '); + process.stdout.write(color('fail', Base.symbols.dot)); + }); + + runner.on('end', function(){ + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +Dot.prototype = new F; +Dot.prototype.constructor = Dot; + + +}); // module: reporters/dot.js + +require.register("reporters/html-cov.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var JSONCov = require('./json-cov') + , fs = require('browser/fs'); + +/** + * Expose `HTMLCov`. + */ + +exports = module.exports = HTMLCov; + +/** + * Initialize a new `JsCoverage` reporter. + * + * @param {Runner} runner + * @api public + */ + +function HTMLCov(runner) { + var jade = require('jade') + , file = __dirname + '/templates/coverage.jade' + , str = fs.readFileSync(file, 'utf8') + , fn = jade.compile(str, { filename: file }) + , self = this; + + JSONCov.call(this, runner, false); + + runner.on('end', function(){ + process.stdout.write(fn({ + cov: self.cov + , coverageClass: coverageClass + })); + }); +} + +/** + * Return coverage class for `n`. + * + * @return {String} + * @api private + */ + +function coverageClass(n) { + if (n >= 75) return 'high'; + if (n >= 50) return 'medium'; + if (n >= 25) return 'low'; + return 'terrible'; +} + +}); // module: reporters/html-cov.js + +require.register("reporters/html.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils') + , Progress = require('../browser/progress') + , escape = utils.escape; + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date + , setTimeout = global.setTimeout + , setInterval = global.setInterval + , clearTimeout = global.clearTimeout + , clearInterval = global.clearInterval; + +/** + * Expose `HTML`. + */ + +exports = module.exports = HTML; + +/** + * Stats template. + */ + +var statsTemplate = ''; + +/** + * Initialize a new `HTML` reporter. + * + * @param {Runner} runner + * @api public + */ + +function HTML(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , stat = fragment(statsTemplate) + , items = stat.getElementsByTagName('li') + , passes = items[1].getElementsByTagName('em')[0] + , passesLink = items[1].getElementsByTagName('a')[0] + , failures = items[2].getElementsByTagName('em')[0] + , failuresLink = items[2].getElementsByTagName('a')[0] + , duration = items[3].getElementsByTagName('em')[0] + , canvas = stat.getElementsByTagName('canvas')[0] + , report = fragment('
            ') + , stack = [report] + , progress + , ctx + , root = document.getElementById('mocha'); + + if (canvas.getContext) { + var ratio = window.devicePixelRatio || 1; + canvas.style.width = canvas.width; + canvas.style.height = canvas.height; + canvas.width *= ratio; + canvas.height *= ratio; + ctx = canvas.getContext('2d'); + ctx.scale(ratio, ratio); + progress = new Progress; + } + + if (!root) return error('#mocha div missing, add it to your document'); + + // pass toggle + on(passesLink, 'click', function(){ + unhide(); + var name = /pass/.test(report.className) ? '' : ' pass'; + report.className = report.className.replace(/fail|pass/g, '') + name; + if (report.className.trim()) hideSuitesWithout('test pass'); + }); + + // failure toggle + on(failuresLink, 'click', function(){ + unhide(); + var name = /fail/.test(report.className) ? '' : ' fail'; + report.className = report.className.replace(/fail|pass/g, '') + name; + if (report.className.trim()) hideSuitesWithout('test fail'); + }); + + root.appendChild(stat); + root.appendChild(report); + + if (progress) progress.size(40); + + runner.on('suite', function(suite){ + if (suite.root) return; + + // suite + var url = self.suiteURL(suite); + var el = fragment('
          • %s

          • ', url, escape(suite.title)); + + // container + stack[0].appendChild(el); + stack.unshift(document.createElement('ul')); + el.appendChild(stack[0]); + }); + + runner.on('suite end', function(suite){ + if (suite.root) return; + stack.shift(); + }); + + runner.on('fail', function(test, err){ + if ('hook' == test.type) runner.emit('test end', test); + }); + + runner.on('test end', function(test){ + // TODO: add to stats + var percent = stats.tests / this.total * 100 | 0; + if (progress) progress.update(percent).draw(ctx); + + // update stats + var ms = new Date - stats.start; + text(passes, stats.passes); + text(failures, stats.failures); + text(duration, (ms / 1000).toFixed(2)); + + // test + if ('passed' == test.state) { + var url = self.testURL(test); + var el = fragment('
          • %e%ems

          • ', test.speed, test.title, test.duration, url); + } else if (test.pending) { + var el = fragment('
          • %e

          • ', test.title); + } else { + var el = fragment('
          • %e

          • ', test.title, encodeURIComponent(test.fullTitle())); + var str = test.err.stack || test.err.toString(); + + // FF / Opera do not add the message + if (!~str.indexOf(test.err.message)) { + str = test.err.message + '\n' + str; + } + + // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we + // check for the result of the stringifying. + if ('[object Error]' == str) str = test.err.message; + + // Safari doesn't give you a stack. Let's at least provide a source line. + if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) { + str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")"; + } + + el.appendChild(fragment('
            %e
            ', str)); + } + + // toggle code + // TODO: defer + if (!test.pending) { + var h2 = el.getElementsByTagName('h2')[0]; + + on(h2, 'click', function(){ + pre.style.display = 'none' == pre.style.display + ? 'block' + : 'none'; + }); + + var pre = fragment('
            %e
            ', utils.clean(test.fn.toString())); + el.appendChild(pre); + pre.style.display = 'none'; + } + + // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack. + if (stack[0]) stack[0].appendChild(el); + }); +} + +/** + * Makes a URL, preserving querystring ("search") parameters. + * @param {string} s + * @returns {string} your new URL + */ +var makeUrl = function makeUrl(s) { + var search = window.location.search; + return (search ? search + '&' : '?' ) + 'grep=' + encodeURIComponent(s); +}; + +/** + * Provide suite URL + * + * @param {Object} [suite] + */ +HTML.prototype.suiteURL = function(suite){ + return makeUrl(suite.fullTitle()); +}; + +/** + * Provide test URL + * + * @param {Object} [test] + */ + +HTML.prototype.testURL = function(test){ + return makeUrl(test.fullTitle()); +}; + +/** + * Display error `msg`. + */ + +function error(msg) { + document.body.appendChild(fragment('
            %s
            ', msg)); +} + +/** + * Return a DOM fragment from `html`. + */ + +function fragment(html) { + var args = arguments + , div = document.createElement('div') + , i = 1; + + div.innerHTML = html.replace(/%([se])/g, function(_, type){ + switch (type) { + case 's': return String(args[i++]); + case 'e': return escape(args[i++]); + } + }); + + return div.firstChild; +} + +/** + * Check for suites that do not have elements + * with `classname`, and hide them. + */ + +function hideSuitesWithout(classname) { + var suites = document.getElementsByClassName('suite'); + for (var i = 0; i < suites.length; i++) { + var els = suites[i].getElementsByClassName(classname); + if (0 == els.length) suites[i].className += ' hidden'; + } +} + +/** + * Unhide .hidden suites. + */ + +function unhide() { + var els = document.getElementsByClassName('suite hidden'); + for (var i = 0; i < els.length; ++i) { + els[i].className = els[i].className.replace('suite hidden', 'suite'); + } +} + +/** + * Set `el` text to `str`. + */ + +function text(el, str) { + if (el.textContent) { + el.textContent = str; + } else { + el.innerText = str; + } +} + +/** + * Listen on `event` with callback `fn`. + */ + +function on(el, event, fn) { + if (el.addEventListener) { + el.addEventListener(event, fn, false); + } else { + el.attachEvent('on' + event, fn); + } +} + +}); // module: reporters/html.js + +require.register("reporters/index.js", function(module, exports, require){ +exports.Base = require('./base'); +exports.Dot = require('./dot'); +exports.Doc = require('./doc'); +exports.TAP = require('./tap'); +exports.JSON = require('./json'); +exports.HTML = require('./html'); +exports.List = require('./list'); +exports.Min = require('./min'); +exports.Spec = require('./spec'); +exports.Nyan = require('./nyan'); +exports.XUnit = require('./xunit'); +exports.Markdown = require('./markdown'); +exports.Progress = require('./progress'); +exports.Landing = require('./landing'); +exports.JSONCov = require('./json-cov'); +exports.HTMLCov = require('./html-cov'); +exports.JSONStream = require('./json-stream'); + +}); // module: reporters/index.js + +require.register("reporters/json-cov.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `JSONCov`. + */ + +exports = module.exports = JSONCov; + +/** + * Initialize a new `JsCoverage` reporter. + * + * @param {Runner} runner + * @param {Boolean} output + * @api public + */ + +function JSONCov(runner, output) { + var self = this + , output = 1 == arguments.length ? true : output; + + Base.call(this, runner); + + var tests = [] + , failures = [] + , passes = []; + + runner.on('test end', function(test){ + tests.push(test); + }); + + runner.on('pass', function(test){ + passes.push(test); + }); + + runner.on('fail', function(test){ + failures.push(test); + }); + + runner.on('end', function(){ + var cov = global._$jscoverage || {}; + var result = self.cov = map(cov); + result.stats = self.stats; + result.tests = tests.map(clean); + result.failures = failures.map(clean); + result.passes = passes.map(clean); + if (!output) return; + process.stdout.write(JSON.stringify(result, null, 2 )); + }); +} + +/** + * Map jscoverage data to a JSON structure + * suitable for reporting. + * + * @param {Object} cov + * @return {Object} + * @api private + */ + +function map(cov) { + var ret = { + instrumentation: 'node-jscoverage' + , sloc: 0 + , hits: 0 + , misses: 0 + , coverage: 0 + , files: [] + }; + + for (var filename in cov) { + var data = coverage(filename, cov[filename]); + ret.files.push(data); + ret.hits += data.hits; + ret.misses += data.misses; + ret.sloc += data.sloc; + } + + ret.files.sort(function(a, b) { + return a.filename.localeCompare(b.filename); + }); + + if (ret.sloc > 0) { + ret.coverage = (ret.hits / ret.sloc) * 100; + } + + return ret; +} + +/** + * Map jscoverage data for a single source file + * to a JSON structure suitable for reporting. + * + * @param {String} filename name of the source file + * @param {Object} data jscoverage coverage data + * @return {Object} + * @api private + */ + +function coverage(filename, data) { + var ret = { + filename: filename, + coverage: 0, + hits: 0, + misses: 0, + sloc: 0, + source: {} + }; + + data.source.forEach(function(line, num){ + num++; + + if (data[num] === 0) { + ret.misses++; + ret.sloc++; + } else if (data[num] !== undefined) { + ret.hits++; + ret.sloc++; + } + + ret.source[num] = { + source: line + , coverage: data[num] === undefined + ? '' + : data[num] + }; + }); + + ret.coverage = ret.hits / ret.sloc * 100; + + return ret; +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} + +}); // module: reporters/json-cov.js + +require.register("reporters/json-stream.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `List`. + */ + +exports = module.exports = List; + +/** + * Initialize a new `List` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function List(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total; + + runner.on('start', function(){ + console.log(JSON.stringify(['start', { total: total }])); + }); + + runner.on('pass', function(test){ + console.log(JSON.stringify(['pass', clean(test)])); + }); + + runner.on('fail', function(test, err){ + test = clean(test); + test.err = err.message; + console.log(JSON.stringify(['fail', test])); + }); + + runner.on('end', function(){ + process.stdout.write(JSON.stringify(['end', self.stats])); + }); +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} + +}); // module: reporters/json-stream.js + +require.register("reporters/json.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `JSON`. + */ + +exports = module.exports = JSONReporter; + +/** + * Initialize a new `JSON` reporter. + * + * @param {Runner} runner + * @api public + */ + +function JSONReporter(runner) { + var self = this; + Base.call(this, runner); + + var tests = [] + , pending = [] + , failures = [] + , passes = []; + + runner.on('test end', function(test){ + tests.push(test); + }); + + runner.on('pass', function(test){ + passes.push(test); + }); + + runner.on('fail', function(test){ + failures.push(test); + }); + + runner.on('pending', function(test){ + pending.push(test); + }); + + runner.on('end', function(){ + var obj = { + stats: self.stats, + tests: tests.map(clean), + pending: pending.map(clean), + failures: failures.map(clean), + passes: passes.map(clean) + }; + + runner.testResults = obj; + + process.stdout.write(JSON.stringify(obj, null, 2)); + }); +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title, + fullTitle: test.fullTitle(), + duration: test.duration, + err: errorJSON(test.err || {}) + } +} + +/** + * Transform `error` into a JSON object. + * @param {Error} err + * @return {Object} + */ + +function errorJSON(err) { + var res = {}; + Object.getOwnPropertyNames(err).forEach(function(key) { + res[key] = err[key]; + }, err); + return res; +} + +}); // module: reporters/json.js + +require.register("reporters/landing.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Landing`. + */ + +exports = module.exports = Landing; + +/** + * Airplane color. + */ + +Base.colors.plane = 0; + +/** + * Airplane crash color. + */ + +Base.colors['plane crash'] = 31; + +/** + * Runway color. + */ + +Base.colors.runway = 90; + +/** + * Initialize a new `Landing` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Landing(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , total = runner.total + , stream = process.stdout + , plane = color('plane', '✈') + , crashed = -1 + , n = 0; + + function runway() { + var buf = Array(width).join('-'); + return ' ' + color('runway', buf); + } + + runner.on('start', function(){ + stream.write('\n\n\n '); + cursor.hide(); + }); + + runner.on('test end', function(test){ + // check if the plane crashed + var col = -1 == crashed + ? width * ++n / total | 0 + : crashed; + + // show the crash + if ('failed' == test.state) { + plane = color('plane crash', '✈'); + crashed = col; + } + + // render landing strip + stream.write('\u001b['+(width+1)+'D\u001b[2A'); + stream.write(runway()); + stream.write('\n '); + stream.write(color('runway', Array(col).join('⋅'))); + stream.write(plane) + stream.write(color('runway', Array(width - col).join('⋅') + '\n')); + stream.write(runway()); + stream.write('\u001b[0m'); + }); + + runner.on('end', function(){ + cursor.show(); + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +Landing.prototype = new F; +Landing.prototype.constructor = Landing; + + +}); // module: reporters/landing.js + +require.register("reporters/list.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `List`. + */ + +exports = module.exports = List; + +/** + * Initialize a new `List` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function List(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , n = 0; + + runner.on('start', function(){ + console.log(); + }); + + runner.on('test', function(test){ + process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); + }); + + runner.on('pending', function(test){ + var fmt = color('checkmark', ' -') + + color('pending', ' %s'); + console.log(fmt, test.fullTitle()); + }); + + runner.on('pass', function(test){ + var fmt = color('checkmark', ' '+Base.symbols.dot) + + color('pass', ' %s: ') + + color(test.speed, '%dms'); + cursor.CR(); + console.log(fmt, test.fullTitle(), test.duration); + }); + + runner.on('fail', function(test, err){ + cursor.CR(); + console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); + }); + + runner.on('end', self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +List.prototype = new F; +List.prototype.constructor = List; + + +}); // module: reporters/list.js + +require.register("reporters/markdown.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils'); + +/** + * Expose `Markdown`. + */ + +exports = module.exports = Markdown; + +/** + * Initialize a new `Markdown` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Markdown(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , level = 0 + , buf = ''; + + function title(str) { + return Array(level).join('#') + ' ' + str; + } + + function indent() { + return Array(level).join(' '); + } + + function mapTOC(suite, obj) { + var ret = obj; + obj = obj[suite.title] = obj[suite.title] || { suite: suite }; + suite.suites.forEach(function(suite){ + mapTOC(suite, obj); + }); + return ret; + } + + function stringifyTOC(obj, level) { + ++level; + var buf = ''; + var link; + for (var key in obj) { + if ('suite' == key) continue; + if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; + if (key) buf += Array(level).join(' ') + link; + buf += stringifyTOC(obj[key], level); + } + --level; + return buf; + } + + function generateTOC(suite) { + var obj = mapTOC(suite, {}); + return stringifyTOC(obj, 0); + } + + generateTOC(runner.suite); + + runner.on('suite', function(suite){ + ++level; + var slug = utils.slug(suite.fullTitle()); + buf += '' + '\n'; + buf += title(suite.title) + '\n'; + }); + + runner.on('suite end', function(suite){ + --level; + }); + + runner.on('pass', function(test){ + var code = utils.clean(test.fn.toString()); + buf += test.title + '.\n'; + buf += '\n```js\n'; + buf += code + '\n'; + buf += '```\n\n'; + }); + + runner.on('end', function(){ + process.stdout.write('# TOC\n'); + process.stdout.write(generateTOC(runner.suite)); + process.stdout.write(buf); + }); +} + +}); // module: reporters/markdown.js + +require.register("reporters/min.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `Min`. + */ + +exports = module.exports = Min; + +/** + * Initialize a new `Min` minimal test reporter (best used with --watch). + * + * @param {Runner} runner + * @api public + */ + +function Min(runner) { + Base.call(this, runner); + + runner.on('start', function(){ + // clear screen + process.stdout.write('\u001b[2J'); + // set cursor position + process.stdout.write('\u001b[1;3H'); + }); + + runner.on('end', this.epilogue.bind(this)); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +Min.prototype = new F; +Min.prototype.constructor = Min; + + +}); // module: reporters/min.js + +require.register("reporters/nyan.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `Dot`. + */ + +exports = module.exports = NyanCat; + +/** + * Initialize a new `Dot` matrix test reporter. + * + * @param {Runner} runner + * @api public + */ + +function NyanCat(runner) { + Base.call(this, runner); + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , rainbowColors = this.rainbowColors = self.generateColors() + , colorIndex = this.colorIndex = 0 + , numerOfLines = this.numberOfLines = 4 + , trajectories = this.trajectories = [[], [], [], []] + , nyanCatWidth = this.nyanCatWidth = 11 + , trajectoryWidthMax = this.trajectoryWidthMax = (width - nyanCatWidth) + , scoreboardWidth = this.scoreboardWidth = 5 + , tick = this.tick = 0 + , n = 0; + + runner.on('start', function(){ + Base.cursor.hide(); + self.draw(); + }); + + runner.on('pending', function(test){ + self.draw(); + }); + + runner.on('pass', function(test){ + self.draw(); + }); + + runner.on('fail', function(test, err){ + self.draw(); + }); + + runner.on('end', function(){ + Base.cursor.show(); + for (var i = 0; i < self.numberOfLines; i++) write('\n'); + self.epilogue(); + }); +} + +/** + * Draw the nyan cat + * + * @api private + */ + +NyanCat.prototype.draw = function(){ + this.appendRainbow(); + this.drawScoreboard(); + this.drawRainbow(); + this.drawNyanCat(); + this.tick = !this.tick; +}; + +/** + * Draw the "scoreboard" showing the number + * of passes, failures and pending tests. + * + * @api private + */ + +NyanCat.prototype.drawScoreboard = function(){ + var stats = this.stats; + var colors = Base.colors; + + function draw(color, n) { + write(' '); + write('\u001b[' + color + 'm' + n + '\u001b[0m'); + write('\n'); + } + + draw(colors.green, stats.passes); + draw(colors.fail, stats.failures); + draw(colors.pending, stats.pending); + write('\n'); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Append the rainbow. + * + * @api private + */ + +NyanCat.prototype.appendRainbow = function(){ + var segment = this.tick ? '_' : '-'; + var rainbowified = this.rainbowify(segment); + + for (var index = 0; index < this.numberOfLines; index++) { + var trajectory = this.trajectories[index]; + if (trajectory.length >= this.trajectoryWidthMax) trajectory.shift(); + trajectory.push(rainbowified); + } +}; + +/** + * Draw the rainbow. + * + * @api private + */ + +NyanCat.prototype.drawRainbow = function(){ + var self = this; + + this.trajectories.forEach(function(line, index) { + write('\u001b[' + self.scoreboardWidth + 'C'); + write(line.join('')); + write('\n'); + }); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Draw the nyan cat + * + * @api private + */ + +NyanCat.prototype.drawNyanCat = function() { + var self = this; + var startWidth = this.scoreboardWidth + this.trajectories[0].length; + var color = '\u001b[' + startWidth + 'C'; + var padding = ''; + + write(color); + write('_,------,'); + write('\n'); + + write(color); + padding = self.tick ? ' ' : ' '; + write('_|' + padding + '/\\_/\\ '); + write('\n'); + + write(color); + padding = self.tick ? '_' : '__'; + var tail = self.tick ? '~' : '^'; + var face; + write(tail + '|' + padding + this.face() + ' '); + write('\n'); + + write(color); + padding = self.tick ? ' ' : ' '; + write(padding + '"" "" '); + write('\n'); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Draw nyan cat face. + * + * @return {String} + * @api private + */ + +NyanCat.prototype.face = function() { + var stats = this.stats; + if (stats.failures) { + return '( x .x)'; + } else if (stats.pending) { + return '( o .o)'; + } else if(stats.passes) { + return '( ^ .^)'; + } else { + return '( - .-)'; + } +}; + +/** + * Move cursor up `n`. + * + * @param {Number} n + * @api private + */ + +NyanCat.prototype.cursorUp = function(n) { + write('\u001b[' + n + 'A'); +}; + +/** + * Move cursor down `n`. + * + * @param {Number} n + * @api private + */ + +NyanCat.prototype.cursorDown = function(n) { + write('\u001b[' + n + 'B'); +}; + +/** + * Generate rainbow colors. + * + * @return {Array} + * @api private + */ + +NyanCat.prototype.generateColors = function(){ + var colors = []; + + for (var i = 0; i < (6 * 7); i++) { + var pi3 = Math.floor(Math.PI / 3); + var n = (i * (1.0 / 6)); + var r = Math.floor(3 * Math.sin(n) + 3); + var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3); + var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3); + colors.push(36 * r + 6 * g + b + 16); + } + + return colors; +}; + +/** + * Apply rainbow to the given `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +NyanCat.prototype.rainbowify = function(str){ + var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; + this.colorIndex += 1; + return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; +}; + +/** + * Stdout helper. + */ + +function write(string) { + process.stdout.write(string); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +NyanCat.prototype = new F; +NyanCat.prototype.constructor = NyanCat; + + +}); // module: reporters/nyan.js + +require.register("reporters/progress.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Progress`. + */ + +exports = module.exports = Progress; + +/** + * General progress bar color. + */ + +Base.colors.progress = 90; + +/** + * Initialize a new `Progress` bar test reporter. + * + * @param {Runner} runner + * @param {Object} options + * @api public + */ + +function Progress(runner, options) { + Base.call(this, runner); + + var self = this + , options = options || {} + , stats = this.stats + , width = Base.window.width * .50 | 0 + , total = runner.total + , complete = 0 + , max = Math.max + , lastN = -1; + + // default chars + options.open = options.open || '['; + options.complete = options.complete || '▬'; + options.incomplete = options.incomplete || Base.symbols.dot; + options.close = options.close || ']'; + options.verbose = false; + + // tests started + runner.on('start', function(){ + console.log(); + cursor.hide(); + }); + + // tests complete + runner.on('test end', function(){ + complete++; + var incomplete = total - complete + , percent = complete / total + , n = width * percent | 0 + , i = width - n; + + if (lastN === n && !options.verbose) { + // Don't re-render the line if it hasn't changed + return; + } + lastN = n; + + cursor.CR(); + process.stdout.write('\u001b[J'); + process.stdout.write(color('progress', ' ' + options.open)); + process.stdout.write(Array(n).join(options.complete)); + process.stdout.write(Array(i).join(options.incomplete)); + process.stdout.write(color('progress', options.close)); + if (options.verbose) { + process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); + } + }); + + // tests are complete, output some stats + // and the failures if any + runner.on('end', function(){ + cursor.show(); + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +Progress.prototype = new F; +Progress.prototype.constructor = Progress; + + +}); // module: reporters/progress.js + +require.register("reporters/spec.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Spec`. + */ + +exports = module.exports = Spec; + +/** + * Initialize a new `Spec` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function Spec(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , indents = 0 + , n = 0; + + function indent() { + return Array(indents).join(' ') + } + + runner.on('start', function(){ + console.log(); + }); + + runner.on('suite', function(suite){ + ++indents; + console.log(color('suite', '%s%s'), indent(), suite.title); + }); + + runner.on('suite end', function(suite){ + --indents; + if (1 == indents) console.log(); + }); + + runner.on('pending', function(test){ + var fmt = indent() + color('pending', ' - %s'); + console.log(fmt, test.title); + }); + + runner.on('pass', function(test){ + if ('fast' == test.speed) { + var fmt = indent() + + color('checkmark', ' ' + Base.symbols.ok) + + color('pass', ' %s '); + cursor.CR(); + console.log(fmt, test.title); + } else { + var fmt = indent() + + color('checkmark', ' ' + Base.symbols.ok) + + color('pass', ' %s ') + + color(test.speed, '(%dms)'); + cursor.CR(); + console.log(fmt, test.title, test.duration); + } + }); + + runner.on('fail', function(test, err){ + cursor.CR(); + console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); + }); + + runner.on('end', self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +Spec.prototype = new F; +Spec.prototype.constructor = Spec; + + +}); // module: reporters/spec.js + +require.register("reporters/tap.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `TAP`. + */ + +exports = module.exports = TAP; + +/** + * Initialize a new `TAP` reporter. + * + * @param {Runner} runner + * @api public + */ + +function TAP(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , n = 1 + , passes = 0 + , failures = 0; + + runner.on('start', function(){ + var total = runner.grepTotal(runner.suite); + console.log('%d..%d', 1, total); + }); + + runner.on('test end', function(){ + ++n; + }); + + runner.on('pending', function(test){ + console.log('ok %d %s # SKIP -', n, title(test)); + }); + + runner.on('pass', function(test){ + passes++; + console.log('ok %d %s', n, title(test)); + }); + + runner.on('fail', function(test, err){ + failures++; + console.log('not ok %d %s', n, title(test)); + if (err.stack) console.log(err.stack.replace(/^/gm, ' ')); + }); + + runner.on('end', function(){ + console.log('# tests ' + (passes + failures)); + console.log('# pass ' + passes); + console.log('# fail ' + failures); + }); +} + +/** + * Return a TAP-safe title of `test` + * + * @param {Object} test + * @return {String} + * @api private + */ + +function title(test) { + return test.fullTitle().replace(/#/g, ''); +} + +}); // module: reporters/tap.js + +require.register("reporters/xunit.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils') + , escape = utils.escape; + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date + , setTimeout = global.setTimeout + , setInterval = global.setInterval + , clearTimeout = global.clearTimeout + , clearInterval = global.clearInterval; + +/** + * Expose `XUnit`. + */ + +exports = module.exports = XUnit; + +/** + * Initialize a new `XUnit` reporter. + * + * @param {Runner} runner + * @api public + */ + +function XUnit(runner) { + Base.call(this, runner); + var stats = this.stats + , tests = [] + , self = this; + + runner.on('pending', function(test){ + tests.push(test); + }); + + runner.on('pass', function(test){ + tests.push(test); + }); + + runner.on('fail', function(test){ + tests.push(test); + }); + + runner.on('end', function(){ + console.log(tag('testsuite', { + name: 'Mocha Tests' + , tests: stats.tests + , failures: stats.failures + , errors: stats.failures + , skipped: stats.tests - stats.failures - stats.passes + , timestamp: (new Date).toUTCString() + , time: (stats.duration / 1000) || 0 + }, false)); + + tests.forEach(test); + console.log(''); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +XUnit.prototype = new F; +XUnit.prototype.constructor = XUnit; + + +/** + * Output tag for the given `test.` + */ + +function test(test) { + var attrs = { + classname: test.parent.fullTitle() + , name: test.title + , time: (test.duration / 1000) || 0 + }; + + if ('failed' == test.state) { + var err = test.err; + console.log(tag('testcase', attrs, false, tag('failure', {}, false, cdata(escape(err.message) + "\n" + err.stack)))); + } else if (test.pending) { + console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); + } else { + console.log(tag('testcase', attrs, true) ); + } +} + +/** + * HTML tag helper. + */ + +function tag(name, attrs, close, content) { + var end = close ? '/>' : '>' + , pairs = [] + , tag; + + for (var key in attrs) { + pairs.push(key + '="' + escape(attrs[key]) + '"'); + } + + tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; + if (content) tag += content + ''; +} + +}); // module: reporters/xunit.js + +require.register("runnable.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('mocha:runnable') + , milliseconds = require('./ms'); + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date + , setTimeout = global.setTimeout + , setInterval = global.setInterval + , clearTimeout = global.clearTimeout + , clearInterval = global.clearInterval; + +/** + * Object#toString(). + */ + +var toString = Object.prototype.toString; + +/** + * Expose `Runnable`. + */ + +module.exports = Runnable; + +/** + * Initialize a new `Runnable` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Runnable(title, fn) { + this.title = title; + this.fn = fn; + this.async = fn && fn.length; + this.sync = ! this.async; + this._timeout = 2000; + this._slow = 75; + this._enableTimeouts = true; + this.timedOut = false; + this._trace = new Error('done() called multiple times') +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +function F(){}; +F.prototype = EventEmitter.prototype; +Runnable.prototype = new F; +Runnable.prototype.constructor = Runnable; + + +/** + * Set & get timeout `ms`. + * + * @param {Number|String} ms + * @return {Runnable|Number} ms or self + * @api private + */ + +Runnable.prototype.timeout = function(ms){ + if (0 == arguments.length) return this._timeout; + if (ms === 0) this._enableTimeouts = false; + if ('string' == typeof ms) ms = milliseconds(ms); + debug('timeout %d', ms); + this._timeout = ms; + if (this.timer) this.resetTimeout(); + return this; +}; + +/** + * Set & get slow `ms`. + * + * @param {Number|String} ms + * @return {Runnable|Number} ms or self + * @api private + */ + +Runnable.prototype.slow = function(ms){ + if (0 === arguments.length) return this._slow; + if ('string' == typeof ms) ms = milliseconds(ms); + debug('timeout %d', ms); + this._slow = ms; + return this; +}; + +/** + * Set and & get timeout `enabled`. + * + * @param {Boolean} enabled + * @return {Runnable|Boolean} enabled or self + * @api private + */ + +Runnable.prototype.enableTimeouts = function(enabled){ + if (arguments.length === 0) return this._enableTimeouts; + debug('enableTimeouts %s', enabled); + this._enableTimeouts = enabled; + return this; +}; + +/** + * Return the full title generated by recursively + * concatenating the parent's full title. + * + * @return {String} + * @api public + */ + +Runnable.prototype.fullTitle = function(){ + return this.parent.fullTitle() + ' ' + this.title; +}; + +/** + * Clear the timeout. + * + * @api private + */ + +Runnable.prototype.clearTimeout = function(){ + clearTimeout(this.timer); +}; + +/** + * Inspect the runnable void of private properties. + * + * @return {String} + * @api private + */ + +Runnable.prototype.inspect = function(){ + return JSON.stringify(this, function(key, val){ + if ('_' == key[0]) return; + if ('parent' == key) return '#'; + if ('ctx' == key) return '#'; + return val; + }, 2); +}; + +/** + * Reset the timeout. + * + * @api private + */ + +Runnable.prototype.resetTimeout = function(){ + var self = this; + var ms = this.timeout() || 1e9; + + if (!this._enableTimeouts) return; + this.clearTimeout(); + this.timer = setTimeout(function(){ + if (!self._enableTimeouts) return; + self.callback(new Error('timeout of ' + ms + 'ms exceeded')); + self.timedOut = true; + }, ms); +}; + +/** + * Whitelist these globals for this test run + * + * @api private + */ +Runnable.prototype.globals = function(arr){ + var self = this; + this._allowedGlobals = arr; +}; + +/** + * Run the test and invoke `fn(err)`. + * + * @param {Function} fn + * @api private + */ + +Runnable.prototype.run = function(fn){ + var self = this + , start = new Date + , ctx = this.ctx + , finished + , emitted; + + // Some times the ctx exists but it is not runnable + if (ctx && ctx.runnable) ctx.runnable(this); + + // called multiple times + function multiple(err) { + if (emitted) return; + emitted = true; + self.emit('error', err || new Error('done() called multiple times; stacktrace may be inaccurate')); + } + + // finished + function done(err) { + var ms = self.timeout(); + if (self.timedOut) return; + if (finished) return multiple(err || self._trace); + self.clearTimeout(); + self.duration = new Date - start; + finished = true; + if (!err && self.duration > ms && self._enableTimeouts) err = new Error('timeout of ' + ms + 'ms exceeded'); + fn(err); + } + + // for .resetTimeout() + this.callback = done; + + // explicit async with `done` argument + if (this.async) { + this.resetTimeout(); + + try { + this.fn.call(ctx, function(err){ + if (err instanceof Error || toString.call(err) === "[object Error]") return done(err); + if (null != err) { + if (Object.prototype.toString.call(err) === '[object Object]') { + return done(new Error('done() invoked with non-Error: ' + JSON.stringify(err))); + } else { + return done(new Error('done() invoked with non-Error: ' + err)); + } + } + done(); + }); + } catch (err) { + done(err); + } + return; + } + + if (this.asyncOnly) { + return done(new Error('--async-only option in use without declaring `done()`')); + } + + // sync or promise-returning + try { + if (this.pending) { + done(); + } else { + callFn(this.fn); + } + } catch (err) { + done(err); + } + + function callFn(fn) { + var result = fn.call(ctx); + if (result && typeof result.then === 'function') { + self.resetTimeout(); + result + .then(function() { + done() + }, + function(reason) { + done(reason || new Error('Promise rejected with no or falsy reason')) + }); + } else { + done(); + } + } +}; + +}); // module: runnable.js + +require.register("runner.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('mocha:runner') + , Test = require('./test') + , utils = require('./utils') + , filter = utils.filter + , keys = utils.keys; + +/** + * Non-enumerable globals. + */ + +var globals = [ + 'setTimeout', + 'clearTimeout', + 'setInterval', + 'clearInterval', + 'XMLHttpRequest', + 'Date' +]; + +/** + * Expose `Runner`. + */ + +module.exports = Runner; + +/** + * Initialize a `Runner` for the given `suite`. + * + * Events: + * + * - `start` execution started + * - `end` execution complete + * - `suite` (suite) test suite execution started + * - `suite end` (suite) all tests (and sub-suites) have finished + * - `test` (test) test execution started + * - `test end` (test) test completed + * - `hook` (hook) hook execution started + * - `hook end` (hook) hook complete + * - `pass` (test) test passed + * - `fail` (test, err) test failed + * - `pending` (test) test pending + * + * @api public + */ + +function Runner(suite) { + var self = this; + this._globals = []; + this._abort = false; + this.suite = suite; + this.total = suite.total(); + this.failures = 0; + this.on('test end', function(test){ self.checkGlobals(test); }); + this.on('hook end', function(hook){ self.checkGlobals(hook); }); + this.grep(/.*/); + this.globals(this.globalProps().concat(extraGlobals())); +} + +/** + * Wrapper for setImmediate, process.nextTick, or browser polyfill. + * + * @param {Function} fn + * @api private + */ + +Runner.immediately = global.setImmediate || process.nextTick; + +/** + * Inherit from `EventEmitter.prototype`. + */ + +function F(){}; +F.prototype = EventEmitter.prototype; +Runner.prototype = new F; +Runner.prototype.constructor = Runner; + + +/** + * Run tests with full titles matching `re`. Updates runner.total + * with number of tests matched. + * + * @param {RegExp} re + * @param {Boolean} invert + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.grep = function(re, invert){ + debug('grep %s', re); + this._grep = re; + this._invert = invert; + this.total = this.grepTotal(this.suite); + return this; +}; + +/** + * Returns the number of tests matching the grep search for the + * given suite. + * + * @param {Suite} suite + * @return {Number} + * @api public + */ + +Runner.prototype.grepTotal = function(suite) { + var self = this; + var total = 0; + + suite.eachTest(function(test){ + var match = self._grep.test(test.fullTitle()); + if (self._invert) match = !match; + if (match) total++; + }); + + return total; +}; + +/** + * Return a list of global properties. + * + * @return {Array} + * @api private + */ + +Runner.prototype.globalProps = function() { + var props = utils.keys(global); + + // non-enumerables + for (var i = 0; i < globals.length; ++i) { + if (~utils.indexOf(props, globals[i])) continue; + props.push(globals[i]); + } + + return props; +}; + +/** + * Allow the given `arr` of globals. + * + * @param {Array} arr + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.globals = function(arr){ + if (0 == arguments.length) return this._globals; + debug('globals %j', arr); + this._globals = this._globals.concat(arr); + return this; +}; + +/** + * Check for global variable leaks. + * + * @api private + */ + +Runner.prototype.checkGlobals = function(test){ + if (this.ignoreLeaks) return; + var ok = this._globals; + + var globals = this.globalProps(); + var leaks; + + if (test) { + ok = ok.concat(test._allowedGlobals || []); + } + + if(this.prevGlobalsLength == globals.length) return; + this.prevGlobalsLength = globals.length; + + leaks = filterLeaks(ok, globals); + this._globals = this._globals.concat(leaks); + + if (leaks.length > 1) { + this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); + } else if (leaks.length) { + this.fail(test, new Error('global leak detected: ' + leaks[0])); + } +}; + +/** + * Fail the given `test`. + * + * @param {Test} test + * @param {Error} err + * @api private + */ + +Runner.prototype.fail = function(test, err){ + ++this.failures; + test.state = 'failed'; + + if ('string' == typeof err) { + err = new Error('the string "' + err + '" was thrown, throw an Error :)'); + } + + this.emit('fail', test, err); +}; + +/** + * Fail the given `hook` with `err`. + * + * Hook failures work in the following pattern: + * - If bail, then exit + * - Failed `before` hook skips all tests in a suite and subsuites, + * but jumps to corresponding `after` hook + * - Failed `before each` hook skips remaining tests in a + * suite and jumps to corresponding `after each` hook, + * which is run only once + * - Failed `after` hook does not alter + * execution order + * - Failed `after each` hook skips remaining tests in a + * suite and subsuites, but executes other `after each` + * hooks + * + * @param {Hook} hook + * @param {Error} err + * @api private + */ + +Runner.prototype.failHook = function(hook, err){ + this.fail(hook, err); + if (this.suite.bail()) { + this.emit('end'); + } +}; + +/** + * Run hook `name` callbacks and then invoke `fn()`. + * + * @param {String} name + * @param {Function} function + * @api private + */ + +Runner.prototype.hook = function(name, fn){ + var suite = this.suite + , hooks = suite['_' + name] + , self = this + , timer; + + function next(i) { + var hook = hooks[i]; + if (!hook) return fn(); + if (self.failures && suite.bail()) return fn(); + self.currentRunnable = hook; + + hook.ctx.currentTest = self.test; + + self.emit('hook', hook); + + hook.on('error', function(err){ + self.failHook(hook, err); + }); + + hook.run(function(err){ + hook.removeAllListeners('error'); + var testError = hook.error(); + if (testError) self.fail(self.test, testError); + if (err) { + self.failHook(hook, err); + + // stop executing hooks, notify callee of hook err + return fn(err); + } + self.emit('hook end', hook); + delete hook.ctx.currentTest; + next(++i); + }); + } + + Runner.immediately(function(){ + next(0); + }); +}; + +/** + * Run hook `name` for the given array of `suites` + * in order, and callback `fn(err, errSuite)`. + * + * @param {String} name + * @param {Array} suites + * @param {Function} fn + * @api private + */ + +Runner.prototype.hooks = function(name, suites, fn){ + var self = this + , orig = this.suite; + + function next(suite) { + self.suite = suite; + + if (!suite) { + self.suite = orig; + return fn(); + } + + self.hook(name, function(err){ + if (err) { + var errSuite = self.suite; + self.suite = orig; + return fn(err, errSuite); + } + + next(suites.pop()); + }); + } + + next(suites.pop()); +}; + +/** + * Run hooks from the top level down. + * + * @param {String} name + * @param {Function} fn + * @api private + */ + +Runner.prototype.hookUp = function(name, fn){ + var suites = [this.suite].concat(this.parents()).reverse(); + this.hooks(name, suites, fn); +}; + +/** + * Run hooks from the bottom up. + * + * @param {String} name + * @param {Function} fn + * @api private + */ + +Runner.prototype.hookDown = function(name, fn){ + var suites = [this.suite].concat(this.parents()); + this.hooks(name, suites, fn); +}; + +/** + * Return an array of parent Suites from + * closest to furthest. + * + * @return {Array} + * @api private + */ + +Runner.prototype.parents = function(){ + var suite = this.suite + , suites = []; + while (suite = suite.parent) suites.push(suite); + return suites; +}; + +/** + * Run the current test and callback `fn(err)`. + * + * @param {Function} fn + * @api private + */ + +Runner.prototype.runTest = function(fn){ + var test = this.test + , self = this; + + if (this.asyncOnly) test.asyncOnly = true; + + try { + test.on('error', function(err){ + self.fail(test, err); + }); + test.run(fn); + } catch (err) { + fn(err); + } +}; + +/** + * Run tests in the given `suite` and invoke + * the callback `fn()` when complete. + * + * @param {Suite} suite + * @param {Function} fn + * @api private + */ + +Runner.prototype.runTests = function(suite, fn){ + var self = this + , tests = suite.tests.slice() + , test; + + + function hookErr(err, errSuite, after) { + // before/after Each hook for errSuite failed: + var orig = self.suite; + + // for failed 'after each' hook start from errSuite parent, + // otherwise start from errSuite itself + self.suite = after ? errSuite.parent : errSuite; + + if (self.suite) { + // call hookUp afterEach + self.hookUp('afterEach', function(err2, errSuite2) { + self.suite = orig; + // some hooks may fail even now + if (err2) return hookErr(err2, errSuite2, true); + // report error suite + fn(errSuite); + }); + } else { + // there is no need calling other 'after each' hooks + self.suite = orig; + fn(errSuite); + } + } + + function next(err, errSuite) { + // if we bail after first err + if (self.failures && suite._bail) return fn(); + + if (self._abort) return fn(); + + if (err) return hookErr(err, errSuite, true); + + // next test + test = tests.shift(); + + // all done + if (!test) return fn(); + + // grep + var match = self._grep.test(test.fullTitle()); + if (self._invert) match = !match; + if (!match) return next(); + + // pending + if (test.pending) { + self.emit('pending', test); + self.emit('test end', test); + return next(); + } + + // execute test and hook(s) + self.emit('test', self.test = test); + self.hookDown('beforeEach', function(err, errSuite){ + + if (err) return hookErr(err, errSuite, false); + + self.currentRunnable = self.test; + self.runTest(function(err){ + test = self.test; + + if (err) { + self.fail(test, err); + self.emit('test end', test); + return self.hookUp('afterEach', next); + } + + test.state = 'passed'; + self.emit('pass', test); + self.emit('test end', test); + self.hookUp('afterEach', next); + }); + }); + } + + this.next = next; + next(); +}; + +/** + * Run the given `suite` and invoke the + * callback `fn()` when complete. + * + * @param {Suite} suite + * @param {Function} fn + * @api private + */ + +Runner.prototype.runSuite = function(suite, fn){ + var total = this.grepTotal(suite) + , self = this + , i = 0; + + debug('run suite %s', suite.fullTitle()); + + if (!total) return fn(); + + this.emit('suite', this.suite = suite); + + function next(errSuite) { + if (errSuite) { + // current suite failed on a hook from errSuite + if (errSuite == suite) { + // if errSuite is current suite + // continue to the next sibling suite + return done(); + } else { + // errSuite is among the parents of current suite + // stop execution of errSuite and all sub-suites + return done(errSuite); + } + } + + if (self._abort) return done(); + + var curr = suite.suites[i++]; + if (!curr) return done(); + self.runSuite(curr, next); + } + + function done(errSuite) { + self.suite = suite; + self.hook('afterAll', function(){ + self.emit('suite end', suite); + fn(errSuite); + }); + } + + this.hook('beforeAll', function(err){ + if (err) return done(); + self.runTests(suite, next); + }); +}; + +/** + * Handle uncaught exceptions. + * + * @param {Error} err + * @api private + */ + +Runner.prototype.uncaught = function(err){ + if (err) { + debug('uncaught exception %s', err !== function () { + return this; + }.call(err) ? err : ( err.message || err )); + } else { + debug('uncaught undefined exception'); + err = new Error('Caught undefined error, did you throw without specifying what?'); + } + err.uncaught = true; + + var runnable = this.currentRunnable; + if (!runnable) return; + + var wasAlreadyDone = runnable.state; + this.fail(runnable, err); + + runnable.clearTimeout(); + + if (wasAlreadyDone) return; + + // recover from test + if ('test' == runnable.type) { + this.emit('test end', runnable); + this.hookUp('afterEach', this.next); + return; + } + + // bail on hooks + this.emit('end'); +}; + +/** + * Run the root suite and invoke `fn(failures)` + * on completion. + * + * @param {Function} fn + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.run = function(fn){ + var self = this + , fn = fn || function(){}; + + function uncaught(err){ + self.uncaught(err); + } + + debug('start'); + + // callback + this.on('end', function(){ + debug('end'); + process.removeListener('uncaughtException', uncaught); + fn(self.failures); + }); + + // run suites + this.emit('start'); + this.runSuite(this.suite, function(){ + debug('finished running'); + self.emit('end'); + }); + + // uncaught exception + process.on('uncaughtException', uncaught); + + return this; +}; + +/** + * Cleanly abort execution + * + * @return {Runner} for chaining + * @api public + */ +Runner.prototype.abort = function(){ + debug('aborting'); + this._abort = true; +}; + +/** + * Filter leaks with the given globals flagged as `ok`. + * + * @param {Array} ok + * @param {Array} globals + * @return {Array} + * @api private + */ + +function filterLeaks(ok, globals) { + return filter(globals, function(key){ + // Firefox and Chrome exposes iframes as index inside the window object + if (/^d+/.test(key)) return false; + + // in firefox + // if runner runs in an iframe, this iframe's window.getInterface method not init at first + // it is assigned in some seconds + if (global.navigator && /^getInterface/.test(key)) return false; + + // an iframe could be approached by window[iframeIndex] + // in ie6,7,8 and opera, iframeIndex is enumerable, this could cause leak + if (global.navigator && /^\d+/.test(key)) return false; + + // Opera and IE expose global variables for HTML element IDs (issue #243) + if (/^mocha-/.test(key)) return false; + + var matched = filter(ok, function(ok){ + if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]); + return key == ok; + }); + return matched.length == 0 && (!global.navigator || 'onerror' !== key); + }); +} + +/** + * Array of globals dependent on the environment. + * + * @return {Array} + * @api private + */ + + function extraGlobals() { + if (typeof(process) === 'object' && + typeof(process.version) === 'string') { + + var nodeVersion = process.version.split('.').reduce(function(a, v) { + return a << 8 | v; + }); + + // 'errno' was renamed to process._errno in v0.9.11. + + if (nodeVersion < 0x00090B) { + return ['errno']; + } + } + + return []; + } + +}); // module: runner.js + +require.register("suite.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('mocha:suite') + , milliseconds = require('./ms') + , utils = require('./utils') + , Hook = require('./hook'); + +/** + * Expose `Suite`. + */ + +exports = module.exports = Suite; + +/** + * Create a new `Suite` with the given `title` + * and parent `Suite`. When a suite with the + * same title is already present, that suite + * is returned to provide nicer reporter + * and more flexible meta-testing. + * + * @param {Suite} parent + * @param {String} title + * @return {Suite} + * @api public + */ + +exports.create = function(parent, title){ + var suite = new Suite(title, parent.ctx); + suite.parent = parent; + if (parent.pending) suite.pending = true; + title = suite.fullTitle(); + parent.addSuite(suite); + return suite; +}; + +/** + * Initialize a new `Suite` with the given + * `title` and `ctx`. + * + * @param {String} title + * @param {Context} ctx + * @api private + */ + +function Suite(title, parentContext) { + this.title = title; + var context = function() {}; + context.prototype = parentContext; + this.ctx = new context(); + this.suites = []; + this.tests = []; + this.pending = false; + this._beforeEach = []; + this._beforeAll = []; + this._afterEach = []; + this._afterAll = []; + this.root = !title; + this._timeout = 2000; + this._enableTimeouts = true; + this._slow = 75; + this._bail = false; +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +function F(){}; +F.prototype = EventEmitter.prototype; +Suite.prototype = new F; +Suite.prototype.constructor = Suite; + + +/** + * Return a clone of this `Suite`. + * + * @return {Suite} + * @api private + */ + +Suite.prototype.clone = function(){ + var suite = new Suite(this.title); + debug('clone'); + suite.ctx = this.ctx; + suite.timeout(this.timeout()); + suite.enableTimeouts(this.enableTimeouts()); + suite.slow(this.slow()); + suite.bail(this.bail()); + return suite; +}; + +/** + * Set timeout `ms` or short-hand such as "2s". + * + * @param {Number|String} ms + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.timeout = function(ms){ + if (0 == arguments.length) return this._timeout; + if (ms === 0) this._enableTimeouts = false; + if ('string' == typeof ms) ms = milliseconds(ms); + debug('timeout %d', ms); + this._timeout = parseInt(ms, 10); + return this; +}; + +/** + * Set timeout `enabled`. + * + * @param {Boolean} enabled + * @return {Suite|Boolean} self or enabled + * @api private + */ + +Suite.prototype.enableTimeouts = function(enabled){ + if (arguments.length === 0) return this._enableTimeouts; + debug('enableTimeouts %s', enabled); + this._enableTimeouts = enabled; + return this; +}; + +/** + * Set slow `ms` or short-hand such as "2s". + * + * @param {Number|String} ms + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.slow = function(ms){ + if (0 === arguments.length) return this._slow; + if ('string' == typeof ms) ms = milliseconds(ms); + debug('slow %d', ms); + this._slow = ms; + return this; +}; + +/** + * Sets whether to bail after first error. + * + * @parma {Boolean} bail + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.bail = function(bail){ + if (0 == arguments.length) return this._bail; + debug('bail %s', bail); + this._bail = bail; + return this; +}; + +/** + * Run `fn(test[, done])` before running tests. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.beforeAll = function(title, fn){ + if (this.pending) return this; + if ('function' === typeof title) { + fn = title; + title = fn.name; + } + title = '"before all" hook' + (title ? ': ' + title : ''); + + var hook = new Hook(title, fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.enableTimeouts(this.enableTimeouts()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._beforeAll.push(hook); + this.emit('beforeAll', hook); + return this; +}; + +/** + * Run `fn(test[, done])` after running tests. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.afterAll = function(title, fn){ + if (this.pending) return this; + if ('function' === typeof title) { + fn = title; + title = fn.name; + } + title = '"after all" hook' + (title ? ': ' + title : ''); + + var hook = new Hook(title, fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.enableTimeouts(this.enableTimeouts()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._afterAll.push(hook); + this.emit('afterAll', hook); + return this; +}; + +/** + * Run `fn(test[, done])` before each test case. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.beforeEach = function(title, fn){ + if (this.pending) return this; + if ('function' === typeof title) { + fn = title; + title = fn.name; + } + title = '"before each" hook' + (title ? ': ' + title : ''); + + var hook = new Hook(title, fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.enableTimeouts(this.enableTimeouts()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._beforeEach.push(hook); + this.emit('beforeEach', hook); + return this; +}; + +/** + * Run `fn(test[, done])` after each test case. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.afterEach = function(title, fn){ + if (this.pending) return this; + if ('function' === typeof title) { + fn = title; + title = fn.name; + } + title = '"after each" hook' + (title ? ': ' + title : ''); + + var hook = new Hook(title, fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.enableTimeouts(this.enableTimeouts()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._afterEach.push(hook); + this.emit('afterEach', hook); + return this; +}; + +/** + * Add a test `suite`. + * + * @param {Suite} suite + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.addSuite = function(suite){ + suite.parent = this; + suite.timeout(this.timeout()); + suite.enableTimeouts(this.enableTimeouts()); + suite.slow(this.slow()); + suite.bail(this.bail()); + this.suites.push(suite); + this.emit('suite', suite); + return this; +}; + +/** + * Add a `test` to this suite. + * + * @param {Test} test + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.addTest = function(test){ + test.parent = this; + test.timeout(this.timeout()); + test.enableTimeouts(this.enableTimeouts()); + test.slow(this.slow()); + test.ctx = this.ctx; + this.tests.push(test); + this.emit('test', test); + return this; +}; + +/** + * Return the full title generated by recursively + * concatenating the parent's full title. + * + * @return {String} + * @api public + */ + +Suite.prototype.fullTitle = function(){ + if (this.parent) { + var full = this.parent.fullTitle(); + if (full) return full + ' ' + this.title; + } + return this.title; +}; + +/** + * Return the total number of tests. + * + * @return {Number} + * @api public + */ + +Suite.prototype.total = function(){ + return utils.reduce(this.suites, function(sum, suite){ + return sum + suite.total(); + }, 0) + this.tests.length; +}; + +/** + * Iterates through each suite recursively to find + * all tests. Applies a function in the format + * `fn(test)`. + * + * @param {Function} fn + * @return {Suite} + * @api private + */ + +Suite.prototype.eachTest = function(fn){ + utils.forEach(this.tests, fn); + utils.forEach(this.suites, function(suite){ + suite.eachTest(fn); + }); + return this; +}; + +}); // module: suite.js + +require.register("test.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Runnable = require('./runnable'); + +/** + * Expose `Test`. + */ + +module.exports = Test; + +/** + * Initialize a new `Test` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Test(title, fn) { + Runnable.call(this, title, fn); + this.pending = !fn; + this.type = 'test'; +} + +/** + * Inherit from `Runnable.prototype`. + */ + +function F(){}; +F.prototype = Runnable.prototype; +Test.prototype = new F; +Test.prototype.constructor = Test; + + +}); // module: test.js + +require.register("utils.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var fs = require('browser/fs') + , path = require('browser/path') + , basename = path.basename + , exists = fs.existsSync || path.existsSync + , glob = require('browser/glob') + , join = path.join + , debug = require('browser/debug')('mocha:watch'); + +/** + * Ignored directories. + */ + +var ignore = ['node_modules', '.git']; + +/** + * Escape special characters in the given string of html. + * + * @param {String} html + * @return {String} + * @api private + */ + +exports.escape = function(html){ + return String(html) + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(//g, '>'); +}; + +/** + * Array#forEach (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} scope + * @api private + */ + +exports.forEach = function(arr, fn, scope){ + for (var i = 0, l = arr.length; i < l; i++) + fn.call(scope, arr[i], i); +}; + +/** + * Array#map (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} scope + * @api private + */ + +exports.map = function(arr, fn, scope){ + var result = []; + for (var i = 0, l = arr.length; i < l; i++) + result.push(fn.call(scope, arr[i], i)); + return result; +}; + +/** + * Array#indexOf (<=IE8) + * + * @parma {Array} arr + * @param {Object} obj to find index of + * @param {Number} start + * @api private + */ + +exports.indexOf = function(arr, obj, start){ + for (var i = start || 0, l = arr.length; i < l; i++) { + if (arr[i] === obj) + return i; + } + return -1; +}; + +/** + * Array#reduce (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} initial value + * @api private + */ + +exports.reduce = function(arr, fn, val){ + var rval = val; + + for (var i = 0, l = arr.length; i < l; i++) { + rval = fn(rval, arr[i], i, arr); + } + + return rval; +}; + +/** + * Array#filter (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @api private + */ + +exports.filter = function(arr, fn){ + var ret = []; + + for (var i = 0, l = arr.length; i < l; i++) { + var val = arr[i]; + if (fn(val, i, arr)) ret.push(val); + } + + return ret; +}; + +/** + * Object.keys (<=IE8) + * + * @param {Object} obj + * @return {Array} keys + * @api private + */ + +exports.keys = Object.keys || function(obj) { + var keys = [] + , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 + + for (var key in obj) { + if (has.call(obj, key)) { + keys.push(key); + } + } + + return keys; +}; + +/** + * Watch the given `files` for changes + * and invoke `fn(file)` on modification. + * + * @param {Array} files + * @param {Function} fn + * @api private + */ + +exports.watch = function(files, fn){ + var options = { interval: 100 }; + files.forEach(function(file){ + debug('file %s', file); + fs.watchFile(file, options, function(curr, prev){ + if (prev.mtime < curr.mtime) fn(file); + }); + }); +}; + +/** + * Ignored files. + */ + +function ignored(path){ + return !~ignore.indexOf(path); +} + +/** + * Lookup files in the given `dir`. + * + * @return {Array} + * @api private + */ + +exports.files = function(dir, ext, ret){ + ret = ret || []; + ext = ext || ['js']; + + var re = new RegExp('\\.(' + ext.join('|') + ')$'); + + fs.readdirSync(dir) + .filter(ignored) + .forEach(function(path){ + path = join(dir, path); + if (fs.statSync(path).isDirectory()) { + exports.files(path, ext, ret); + } else if (path.match(re)) { + ret.push(path); + } + }); + + return ret; +}; + +/** + * Compute a slug from the given `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +exports.slug = function(str){ + return str + .toLowerCase() + .replace(/ +/g, '-') + .replace(/[^-\w]/g, ''); +}; + +/** + * Strip the function definition from `str`, + * and re-indent for pre whitespace. + */ + +exports.clean = function(str) { + str = str + .replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, '') + .replace(/^function *\(.*\) *{|\(.*\) *=> *{?/, '') + .replace(/\s+\}$/, ''); + + var spaces = str.match(/^\n?( *)/)[1].length + , tabs = str.match(/^\n?(\t*)/)[1].length + , re = new RegExp('^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs ? tabs : spaces) + '}', 'gm'); + + str = str.replace(re, ''); + + return exports.trim(str); +}; + +/** + * Trim the given `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +exports.trim = function(str){ + return str.replace(/^\s+|\s+$/g, ''); +}; + +/** + * Parse the given `qs`. + * + * @param {String} qs + * @return {Object} + * @api private + */ + +exports.parseQuery = function(qs){ + return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){ + var i = pair.indexOf('=') + , key = pair.slice(0, i) + , val = pair.slice(++i); + + obj[key] = decodeURIComponent(val); + return obj; + }, {}); +}; + +/** + * Highlight the given string of `js`. + * + * @param {String} js + * @return {String} + * @api private + */ + +function highlight(js) { + return js + .replace(//g, '>') + .replace(/\/\/(.*)/gm, '//$1') + .replace(/('.*?')/gm, '$1') + .replace(/(\d+\.\d+)/gm, '$1') + .replace(/(\d+)/gm, '$1') + .replace(/\bnew[ \t]+(\w+)/gm, 'new $1') + .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '$1') +} + +/** + * Highlight the contents of tag `name`. + * + * @param {String} name + * @api private + */ + +exports.highlightTags = function(name) { + var code = document.getElementById('mocha').getElementsByTagName(name); + for (var i = 0, len = code.length; i < len; ++i) { + code[i].innerHTML = highlight(code[i].innerHTML); + } +}; + + +/** + * Stringify `obj`. + * + * @param {Object} obj + * @return {String} + * @api private + */ + +exports.stringify = function(obj) { + if (obj instanceof RegExp) return obj.toString(); + return JSON.stringify(exports.canonicalize(obj), null, 2).replace(/,(\n|$)/g, '$1'); +}; + +/** + * Return a new object that has the keys in sorted order. + * @param {Object} obj + * @param {Array} [stack] + * @return {Object} + * @api private + */ + +exports.canonicalize = function(obj, stack) { + stack = stack || []; + + if (exports.indexOf(stack, obj) !== -1) return '[Circular]'; + + var canonicalizedObj; + + if ({}.toString.call(obj) === '[object Array]') { + stack.push(obj); + canonicalizedObj = exports.map(obj, function (item) { + return exports.canonicalize(item, stack); + }); + stack.pop(); + } else if (typeof obj === 'object' && obj !== null) { + stack.push(obj); + canonicalizedObj = {}; + exports.forEach(exports.keys(obj).sort(), function (key) { + canonicalizedObj[key] = exports.canonicalize(obj[key], stack); + }); + stack.pop(); + } else { + canonicalizedObj = obj; + } + + return canonicalizedObj; + }; + +/** + * Lookup file names at the given `path`. + */ +exports.lookupFiles = function lookupFiles(path, extensions, recursive) { + var files = []; + var re = new RegExp('\\.(' + extensions.join('|') + ')$'); + + if (!exists(path)) { + if (exists(path + '.js')) { + path += '.js'; + } else { + files = glob.sync(path); + if (!files.length) throw new Error("cannot resolve path (or pattern) '" + path + "'"); + return files; + } + } + + try { + var stat = fs.statSync(path); + if (stat.isFile()) return path; + } + catch (ignored) { + return; + } + + fs.readdirSync(path).forEach(function(file){ + file = join(path, file); + try { + var stat = fs.statSync(file); + if (stat.isDirectory()) { + if (recursive) { + files = files.concat(lookupFiles(file, extensions, recursive)); + } + return; + } + } + catch (ignored) { + return; + } + if (!stat.isFile() || !re.test(file) || basename(file)[0] === '.') return; + files.push(file); + }); + + return files; +}; + +}); // module: utils.js +// The global object is "self" in Web Workers. +var global = (function() { return this; })(); + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date; +var setTimeout = global.setTimeout; +var setInterval = global.setInterval; +var clearTimeout = global.clearTimeout; +var clearInterval = global.clearInterval; + +/** + * Node shims. + * + * These are meant only to allow + * mocha.js to run untouched, not + * to allow running node code in + * the browser. + */ + +var process = {}; +process.exit = function(status){}; +process.stdout = {}; + +var uncaughtExceptionHandlers = []; + +var originalOnerrorHandler = global.onerror; + +/** + * Remove uncaughtException listener. + * Revert to original onerror handler if previously defined. + */ + +process.removeListener = function(e, fn){ + if ('uncaughtException' == e) { + if (originalOnerrorHandler) { + global.onerror = originalOnerrorHandler; + } else { + global.onerror = function() {}; + } + var i = Mocha.utils.indexOf(uncaughtExceptionHandlers, fn); + if (i != -1) { uncaughtExceptionHandlers.splice(i, 1); } + } +}; + +/** + * Implements uncaughtException listener. + */ + +process.on = function(e, fn){ + if ('uncaughtException' == e) { + global.onerror = function(err, url, line){ + fn(new Error(err + ' (' + url + ':' + line + ')')); + return true; + }; + uncaughtExceptionHandlers.push(fn); + } +}; + +/** + * Expose mocha. + */ + +var Mocha = global.Mocha = require('mocha'), + mocha = global.mocha = new Mocha({ reporter: 'html' }); + +// The BDD UI is registered by default, but no UI will be functional in the +// browser without an explicit call to the overridden `mocha.ui` (see below). +// Ensure that this default UI does not expose its methods to the global scope. +mocha.suite.removeAllListeners('pre-require'); + +var immediateQueue = [] + , immediateTimeout; + +function timeslice() { + var immediateStart = new Date().getTime(); + while (immediateQueue.length && (new Date().getTime() - immediateStart) < 100) { + immediateQueue.shift()(); + } + if (immediateQueue.length) { + immediateTimeout = setTimeout(timeslice, 0); + } else { + immediateTimeout = null; + } +} + +/** + * High-performance override of Runner.immediately. + */ + +Mocha.Runner.immediately = function(callback) { + immediateQueue.push(callback); + if (!immediateTimeout) { + immediateTimeout = setTimeout(timeslice, 0); + } +}; + +/** + * Function to allow assertion libraries to throw errors directly into mocha. + * This is useful when running tests in a browser because window.onerror will + * only receive the 'message' attribute of the Error. + */ +mocha.throwError = function(err) { + Mocha.utils.forEach(uncaughtExceptionHandlers, function (fn) { + fn(err); + }); + throw err; +}; + +/** + * Override ui to ensure that the ui functions are initialized. + * Normally this would happen in Mocha.prototype.loadFiles. + */ + +mocha.ui = function(ui){ + Mocha.prototype.ui.call(this, ui); + this.suite.emit('pre-require', global, null, this); + return this; +}; + +/** + * Setup mocha with the given setting options. + */ + +mocha.setup = function(opts){ + if ('string' == typeof opts) opts = { ui: opts }; + for (var opt in opts) this[opt](opts[opt]); + return this; +}; + +/** + * Run mocha, returning the Runner. + */ + +mocha.run = function(fn){ + var options = mocha.options; + mocha.globals('location'); + + var query = Mocha.utils.parseQuery(global.location.search || ''); + if (query.grep) mocha.grep(query.grep); + if (query.invert) mocha.invert(); + + return Mocha.prototype.run.call(mocha, function(err){ + // The DOM Document is not available in Web Workers. + var document = global.document; + if (document && document.getElementById('mocha') && options.noHighlighting !== true) { + Mocha.utils.highlightTags('code'); + } + if (fn) fn(err); + }); +}; + +/** + * Expose the process shim. + */ + +Mocha.process = process; +})(); diff --git a/tests/automation/res/mocha_util.js b/tests/automation/res/mocha_util.js new file mode 100644 index 0000000000..eab1459caa --- /dev/null +++ b/tests/automation/res/mocha_util.js @@ -0,0 +1,3 @@ +!function(){function t(e){var n=t.resolve(e),r=t.modules[n];if(!r)throw new Error('failed to require "'+e+'"');return r.exports||(r.exports={},r.call(r.exports,r,r.exports,t.relative(n))),r.exports}function e(){for(var t=(new r).getTime();h.length&&(new r).getTime()-t<100;)h.shift()();l=h.length?i(e,0):null}t.modules={},t.resolve=function(e){var n=e,r=e+".js",i=e+"/index.js";return t.modules[r]&&r||t.modules[i]&&i||n},t.register=function(e,n){t.modules[e]=n},t.relative=function(e){return function(n){if("."!=n.charAt(0))return t(n);var r=e.split("/"),i=n.split("/");r.pop();for(var o=0;o/g,">"),e=e.replace(/"/g,""")}var r=function(t){this.ignoreWhitespace=t};r.prototype={diff:function(e,n){if(n===e)return[{value:n}];if(!n)return[{value:e,removed:!0}];if(!e)return[{value:n,added:!0}];n=this.tokenize(n),e=this.tokenize(e);var r=n.length,i=e.length,o=r+i,s=[{newPos:-1,components:[]}],a=this.extractCommon(s[0],n,e,0);if(s[0].newPos+1>=r&&a+1>=i)return s[0].components;for(var u=1;o>=u;u++)for(var c=-1*u;u>=c;c+=2){var l,h=s[c-1],f=s[c+1];a=(f?f.newPos:0)-c,h&&(s[c-1]=void 0);var p=h&&h.newPos+1=0&&i>a;if(p||d){!p||d&&h.newPos=r&&a+1>=i)return l.components;s[c]=l}else s[c]=void 0}},pushComponent:function(t,e,n,r){var i=t[t.length-1];i&&i.added===n&&i.removed===r?t[t.length-1]={value:this.join(i.value,e),added:n,removed:r}:t.push({value:e,added:n,removed:r})},extractCommon:function(t,e,n,r){for(var i=e.length,o=n.length,s=t.newPos,a=s-r;i>s+1&&o>a+1&&this.equals(e[s+1],n[a+1]);)s++,a++,this.pushComponent(t.components,e[s],void 0,void 0);return t.newPos=s,a},equals:function(t,e){var n=/\S/;return!this.ignoreWhitespace||n.test(t)||n.test(e)?t===e:!0},join:function(t,e){return t+e},tokenize:function(t){return t}};var i=new r,o=new r(!0),s=new r;o.tokenize=s.tokenize=function(t){return e(t.split(/(\s+|\b)/))};var a=new r(!0);a.tokenize=function(t){return e(t.split(/([{}:;,]|\s+)/))};var u=new r;return u.tokenize=function(t){return t.split(/^/m)},{Diff:r,diffChars:function(t,e){return i.diff(t,e)},diffWords:function(t,e){return o.diff(t,e)},diffWordsWithSpace:function(t,e){return s.diff(t,e)},diffLines:function(t,e){return u.diff(t,e)},diffCss:function(t,e){return a.diff(t,e)},createPatch:function(t,e,n,r,i){function o(t){return t.map(function(t){return" "+t})}function s(t,e,n){var r=c[c.length-2],i=e===c.length-2,o=e===c.length-3&&(n.added!==r.added||n.removed!==r.removed);/\n$/.test(n.value)||!i&&!o||t.push("\\ No newline at end of file")}var a=[];a.push("Index: "+t),a.push("==================================================================="),a.push("--- "+t+("undefined"==typeof r?"":" "+r)),a.push("+++ "+t+("undefined"==typeof i?"":" "+i));var c=u.diff(e,n);c[c.length-1].value||c.pop(),c.push({value:"",lines:[]});for(var l=0,h=0,f=[],p=1,d=1,g=0;g=0;s--){for(var c=r[s],l=0;l"):i.removed&&e.push(""),e.push(n(i.value)),i.added?e.push(""):i.removed&&e.push("")}return e.join("")},convertChangesToDMP:function(t){for(var e,n=[],r=0;ro;o++)if(r[o]===e||r[o].listener&&r[o].listener===e){i=o;break}if(0>i)return this;r.splice(i,1),r.length||delete this.$events[t]}else(r===e||r.listener&&r.listener===e)&&delete this.$events[t]}return this},r.prototype.removeAllListeners=function(t){return void 0===t?(this.$events={},this):(this.$events&&this.$events[t]&&(this.$events[t]=null),this)},r.prototype.listeners=function(t){return this.$events||(this.$events={}),this.$events[t]||(this.$events[t]=[]),n(this.$events[t])||(this.$events[t]=[this.$events[t]]),this.$events[t]},r.prototype.emit=function(t){if(!this.$events)return!1;var e=this.$events[t];if(!e)return!1;var r=[].slice.call(arguments,1);if("function"==typeof e)e.apply(this,r);else{if(!n(e))return!1;for(var i=e.slice(),o=0,s=i.length;s>o;o++)i[o].apply(this,r)}return!0}}),t.register("browser/fs.js",function(){}),t.register("browser/glob.js",function(){}),t.register("browser/path.js",function(){}),t.register("browser/progress.js",function(t){function e(){this.percent=0,this.size(0),this.fontSize(11),this.font("helvetica, arial, sans-serif")}t.exports=e,e.prototype.size=function(t){return this._size=t,this},e.prototype.text=function(t){return this._text=t,this},e.prototype.fontSize=function(t){return this._fontSize=t,this},e.prototype.font=function(t){return this._font=t,this},e.prototype.update=function(t){return this.percent=t,this},e.prototype.draw=function(t){try{var e=Math.min(this.percent,100),n=this._size,r=n/2,i=r,o=r,s=r-1,a=this._fontSize;t.font=a+"px "+this._font;var u=2*Math.PI*(e/100);t.clearRect(0,0,n,n),t.strokeStyle="#9f9f9f",t.beginPath(),t.arc(i,o,s,0,u,!1),t.stroke(),t.strokeStyle="#eee",t.beginPath(),t.arc(i,o,s-1,0,u,!0),t.stroke();var c=this._text||(0|e)+"%",l=t.measureText(c).width;t.fillText(c,i-l/2+1,o+a/2-1)}catch(h){}return this}}),t.register("browser/tty.js",function(t,e){e.isatty=function(){return!0},e.getWindowSize=function(){return"innerHeight"in n?[n.innerHeight,n.innerWidth]:[640,480]}}),t.register("context.js",function(t){function e(){}t.exports=e,e.prototype.runnable=function(t){return 0==arguments.length?this._runnable:(this.test=this._runnable=t,this)},e.prototype.timeout=function(t){return 0===arguments.length?this.runnable().timeout():(this.runnable().timeout(t),this)},e.prototype.enableTimeouts=function(t){return this.runnable().enableTimeouts(t),this},e.prototype.slow=function(t){return this.runnable().slow(t),this},e.prototype.inspect=function(){return JSON.stringify(this,function(t,e){return"_runnable"!=t&&"test"!=t?e:void 0},2)}}),t.register("hook.js",function(t,e,n){function r(t,e){o.call(this,t,e),this.type="hook"}function i(){}var o=n("./runnable");t.exports=r,i.prototype=o.prototype,r.prototype=new i,r.prototype.constructor=r,r.prototype.error=function(t){if(0==arguments.length){var t=this._error;return this._error=null,t}this._error=t}}),t.register("interfaces/bdd.js",function(t,e,n){var r=n("../suite"),i=n("../test"),o=(n("../utils"),n("browser/escape-string-regexp"));t.exports=function(t){var e=[t];t.on("pre-require",function(t,n,s){t.before=function(t,n){e[0].beforeAll(t,n)},t.after=function(t,n){e[0].afterAll(t,n)},t.beforeEach=function(t,n){e[0].beforeEach(t,n)},t.afterEach=function(t,n){e[0].afterEach(t,n)},t.describe=t.context=function(t,i){var o=r.create(e[0],t);return o.file=n,e.unshift(o),i.call(o),e.shift(),o},t.xdescribe=t.xcontext=t.describe.skip=function(t,n){var i=r.create(e[0],t);i.pending=!0,e.unshift(i),n.call(i),e.shift()},t.describe.only=function(e,n){var r=t.describe(e,n);return s.grep(r.fullTitle()),r},t.it=t.specify=function(t,r){var o=e[0];o.pending&&(r=null);var s=new i(t,r);return s.file=n,o.addTest(s),s},t.it.only=function(e,n){var r=t.it(e,n),i="^"+o(r.fullTitle())+"$";return s.grep(new RegExp(i)),r},t.xit=t.xspecify=t.it.skip=function(e){t.it(e)}})}}),t.register("interfaces/exports.js",function(t,e,n){var r=n("../suite"),i=n("../test");t.exports=function(t){function e(t,o){var s;for(var a in t)if("function"==typeof t[a]){var u=t[a];switch(a){case"before":n[0].beforeAll(u);break;case"after":n[0].afterAll(u);break;case"beforeEach":n[0].beforeEach(u);break;case"afterEach":n[0].afterEach(u);break;default:var c=new i(a,u);c.file=o,n[0].addTest(c)}}else s=r.create(n[0],a),n.unshift(s),e(t[a]),n.shift()}var n=[t];t.on("require",e)}}),t.register("interfaces/index.js",function(t,e,n){e.bdd=n("./bdd"),e.tdd=n("./tdd"),e.qunit=n("./qunit"),e.exports=n("./exports")}),t.register("interfaces/qunit.js",function(t,e,n){{var r=n("../suite"),i=n("../test"),o=n("browser/escape-string-regexp");n("../utils")}t.exports=function(t){var e=[t];t.on("pre-require",function(t,n,s){t.before=function(t,n){e[0].beforeAll(t,n)},t.after=function(t,n){e[0].afterAll(t,n)},t.beforeEach=function(t,n){e[0].beforeEach(t,n)},t.afterEach=function(t,n){e[0].afterEach(t,n)},t.suite=function(t){e.length>1&&e.shift();var i=r.create(e[0],t);return i.file=n,e.unshift(i),i},t.suite.only=function(e,n){var r=t.suite(e,n);s.grep(r.fullTitle())},t.test=function(t,r){var o=new i(t,r);return o.file=n,e[0].addTest(o),o},t.test.only=function(e,n){var r=t.test(e,n),i="^"+o(r.fullTitle())+"$";s.grep(new RegExp(i))},t.test.skip=function(e){t.test(e)}})}}),t.register("interfaces/tdd.js",function(t,e,n){{var r=n("../suite"),i=n("../test"),o=n("browser/escape-string-regexp");n("../utils")}t.exports=function(t){var e=[t];t.on("pre-require",function(t,n,s){t.setup=function(t,n){e[0].beforeEach(t,n)},t.teardown=function(t,n){e[0].afterEach(t,n)},t.suiteSetup=function(t,n){e[0].beforeAll(t,n)},t.suiteTeardown=function(t,n){e[0].afterAll(t,n)},t.suite=function(t,i){var o=r.create(e[0],t);return o.file=n,e.unshift(o),i.call(o),e.shift(),o},t.suite.skip=function(t,n){var i=r.create(e[0],t);i.pending=!0,e.unshift(i),n.call(i),e.shift()},t.suite.only=function(e,n){var r=t.suite(e,n);s.grep(r.fullTitle())},t.test=function(t,r){var o=e[0];o.pending&&(r=null);var s=new i(t,r);return s.file=n,o.addTest(s),s},t.test.only=function(e,n){var r=t.test(e,n),i="^"+o(r.fullTitle())+"$";s.grep(new RegExp(i))},t.test.skip=function(e){t.test(e)}})}}),t.register("mocha.js",function(t,e,r){function i(t){return __dirname+"/../images/"+t+".png"}function s(t){t=t||{},this.files=[],this.options=t,this.grep(t.grep),this.suite=new e.Suite("",new e.Context),this.ui(t.ui),this.bail(t.bail),this.reporter(t.reporter),null!=t.timeout&&this.timeout(t.timeout),this.useColors(t.useColors),null!==t.enableTimeouts&&this.enableTimeouts(t.enableTimeouts),t.slow&&this.slow(t.slow),this.suite.on("pre-require",function(t){e.afterEach=t.afterEach||t.teardown,e.after=t.after||t.suiteTeardown,e.beforeEach=t.beforeEach||t.setup,e.before=t.before||t.suiteSetup,e.describe=t.describe||t.suite,e.it=t.it||t.test,e.setup=t.setup||t.beforeEach,e.suiteSetup=t.suiteSetup||t.before,e.suiteTeardown=t.suiteTeardown||t.after,e.suite=t.suite||t.describe,e.teardown=t.teardown||t.afterEach,e.test=t.test||t.it})}var a=r("browser/path"),u=r("browser/escape-string-regexp"),c=r("./utils");if(e=t.exports=s,"undefined"!=typeof o&&"function"==typeof o.cwd){var l=a.join,h=o.cwd();t.paths.push(h,l(h,"node_modules"))}e.utils=c,e.interfaces=r("./interfaces"),e.reporters=r("./reporters"),e.Runnable=r("./runnable"),e.Context=r("./context"),e.Runner=r("./runner"),e.Suite=r("./suite"),e.Hook=r("./hook"),e.Test=r("./test"),s.prototype.bail=function(t){return 0==arguments.length&&(t=!0),this.suite.bail(t),this},s.prototype.addFile=function(t){return this.files.push(t),this},s.prototype.reporter=function(t){if("function"==typeof t)this._reporter=t;else{t=t||"spec";var e;try{e=r("./reporters/"+t)}catch(n){}if(!e)try{e=r(t)}catch(n){}if(e||"teamcity"!==t||console.warn("The Teamcity reporter was moved to a package named mocha-teamcity-reporter (https://npmjs.org/package/mocha-teamcity-reporter)."),!e)throw new Error('invalid reporter "'+t+'"');this._reporter=e}return this},s.prototype.ui=function(t){if(t=t||"bdd",this._ui=e.interfaces[t],!this._ui)try{this._ui=r(t)}catch(n){}if(!this._ui)throw new Error('invalid interface "'+t+'"');return this._ui=this._ui(this.suite),this},s.prototype.loadFiles=function(t){var e=this,i=this.suite,o=this.files.length;this.files.forEach(function(s){s=a.resolve(s),i.emit("pre-require",n,s,e),i.emit("require",r(s),s,e),i.emit("post-require",n,s,e),--o||t&&t()})},s.prototype._growl=function(t,e){var n=r("growl");t.on("end",function(){var r=e.stats;if(r.failures){var o=r.failures+" of "+t.total+" tests failed";n(o,{name:"mocha",title:"Failed",image:i("error")})}else n(r.passes+" tests passed in "+r.duration+"ms",{name:"mocha",title:"Passed",image:i("ok")})})},s.prototype.grep=function(t){return this.options.grep="string"==typeof t?new RegExp(u(t)):t,this},s.prototype.invert=function(){return this.options.invert=!0,this},s.prototype.ignoreLeaks=function(t){return this.options.ignoreLeaks=!!t,this},s.prototype.checkLeaks=function(){return this.options.ignoreLeaks=!1,this},s.prototype.growl=function(){return this.options.growl=!0,this},s.prototype.globals=function(t){return this.options.globals=(this.options.globals||[]).concat(t),this},s.prototype.useColors=function(t){return this.options.useColors=arguments.length&&void 0!=t?t:!0,this},s.prototype.useInlineDiffs=function(t){return this.options.useInlineDiffs=arguments.length&&void 0!=t?t:!1,this},s.prototype.timeout=function(t){return this.suite.timeout(t),this},s.prototype.slow=function(t){return this.suite.slow(t),this},s.prototype.enableTimeouts=function(t){return this.suite.enableTimeouts(arguments.length&&void 0!==t?t:!0),this},s.prototype.asyncOnly=function(){return this.options.asyncOnly=!0,this},s.prototype.noHighlighting=function(){return this.options.noHighlighting=!0,this},s.prototype.run=function(t){this.files.length&&this.loadFiles();var n=this.suite,r=this.options;r.files=this.files;var i=new e.Runner(n),o=new this._reporter(i,r);return i.ignoreLeaks=!1!==r.ignoreLeaks,i.asyncOnly=r.asyncOnly,r.grep&&i.grep(r.grep,r.invert),r.globals&&i.globals(r.globals),r.growl&&this._growl(i,o),e.reporters.Base.useColors=r.useColors,e.reporters.Base.inlineDiffs=r.useInlineDiffs,i.run(t)}}),t.register("ms.js",function(t){function e(t){var e=/^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(t);if(e){var n=parseFloat(e[1]),r=(e[2]||"ms").toLowerCase();switch(r){case"years":case"year":case"y":return n*c;case"days":case"day":case"d":return n*u;case"hours":case"hour":case"h":return n*a;case"minutes":case"minute":case"m":return n*s;case"seconds":case"second":case"s":return n*o;case"ms":return n}}}function n(t){return t>=u?Math.round(t/u)+"d":t>=a?Math.round(t/a)+"h":t>=s?Math.round(t/s)+"m":t>=o?Math.round(t/o)+"s":t+"ms"}function r(t){return i(t,u,"day")||i(t,a,"hour")||i(t,s,"minute")||i(t,o,"second")||t+" ms"}function i(t,e,n){return e>t?void 0:1.5*e>t?Math.floor(t/e)+" "+n:Math.ceil(t/e)+" "+n+"s"}var o=1e3,s=60*o,a=60*s,u=24*a,c=365.25*u;t.exports=function(t,i){return i=i||{},"string"==typeof t?e(t):i["long"]?r(t):n(t)}}),t.register("reporters/base.js",function(t,e,r){function i(t){var e=this.stats={suites:0,tests:0,passes:0,pending:0,failures:0},n=this.failures=[];t&&(this.runner=t,t.stats=e,t.on("start",function(){e.start=new m}),t.on("suite",function(t){e.suites=e.suites||0,t.root||e.suites++}),t.on("test end",function(){e.tests=e.tests||0,e.tests++}),t.on("pass",function(t){e.passes=e.passes||0;var n=t.slow()/2;t.speed=t.duration>t.slow()?"slow":t.duration>n?"medium":"fast",e.passes++}),t.on("fail",function(t,r){e.failures=e.failures||0,e.failures++,t.err=r,n.push(t)}),t.on("end",function(){e.end=new m,e.duration=new m-e.start}),t.on("pending",function(){e.pending++}))}function s(t,e){return t=String(t),Array(e-t.length+1).join(" ")+t}function a(t,e){var n=c(t,"WordsWithSpace",e),r=n.split("\n");if(r.length>4){var i=String(r.length).length;n=r.map(function(t,e){return s(++e,i)+" | "+t}).join("\n")}return n="\n"+y("diff removed","actual")+" "+y("diff added","expected")+"\n\n"+n+"\n",n=n.replace(/^/gm," ")}function u(t,e){function n(t){return e&&(t=l(t)),"+"===t[0]?i+h("diff added",t):"-"===t[0]?i+h("diff removed",t):t.match(/\@\@/)?null:t.match(/\\ No newline/)?null:i+t}function r(t){return null!=t}var i=" ";msg=d.createPatch("string",t.actual,t.expected);var o=msg.split("\n").splice(4);return"\n "+h("diff added","+ expected")+" "+h("diff removed","- actual")+"\n\n"+o.map(n).filter(r).join("\n")}function c(t,e,n){var r=n?l(t.actual):t.actual,i=n?l(t.expected):t.expected;return d["diff"+e](r,i).map(function(t){return t.added?h("diff added",t.value):t.removed?h("diff removed",t.value):t.value}).join("")}function l(t){return t.replace(/\t/g,"").replace(/\r/g,"").replace(/\n/g,"\n")}function h(t,e){return e.split("\n").map(function(e){return y(t,e)}).join("\n")}function f(t,e){return t=Object.prototype.toString.call(t),e=Object.prototype.toString.call(e),t==e}var p=r("browser/tty"),d=r("browser/diff"),g=r("../ms"),v=r("../utils"),m=n.Date,b=(n.setTimeout,n.setInterval,n.clearTimeout,n.clearInterval,p.isatty(1)&&p.isatty(2));e=t.exports=i,e.useColors=b||void 0!==o.env.MOCHA_COLORS,e.inlineDiffs=!1,e.colors={pass:90,fail:31,"bright pass":92,"bright fail":91,"bright yellow":93,pending:36,suite:0,"error title":0,"error message":31,"error stack":90,checkmark:32,fast:90,medium:33,slow:31,green:32,light:90,"diff gutter":90,"diff added":42,"diff removed":41},e.symbols={ok:"✓",err:"✖",dot:"․"},"win32"==o.platform&&(e.symbols.ok="√",e.symbols.err="×",e.symbols.dot=".");var y=e.color=function(t,n){return e.useColors?"["+e.colors[t]+"m"+n+"":n};e.window={width:b?o.stdout.getWindowSize?o.stdout.getWindowSize(1)[0]:p.getWindowSize()[1]:75},e.cursor={hide:function(){b&&o.stdout.write("[?25l")},show:function(){b&&o.stdout.write("[?25h")},deleteLine:function(){b&&o.stdout.write("")},beginningOfLine:function(){b&&o.stdout.write("")},CR:function(){b?(e.cursor.deleteLine(),e.cursor.beginningOfLine()):o.stdout.write("\r")}},e.list=function(t){console.error(),t.forEach(function(t,n){var r=y("error title"," %s) %s:\n")+y("error message"," %s")+y("error stack","\n%s\n"),i=t.err,o=i.message||"",s=i.stack||o,c=s.indexOf(o)+o.length,l=s.slice(0,c),h=i.actual,p=i.expected,d=!0;if(i.uncaught&&(l="Uncaught "+l),i.showDiff&&f(h,p)&&(d=!1,i.actual=h=v.stringify(h),i.expected=p=v.stringify(p)),i.showDiff&&"string"==typeof h&&"string"==typeof p){r=y("error title"," %s) %s:\n%s")+y("error stack","\n%s\n");var g=o.match(/^([^:]+): expected/);l="\n "+y("error message",g?g[1]:l),l+=e.inlineDiffs?a(i,d):u(i,d)}s=s.slice(c?c+1:c).replace(/^/gm," "),console.error(r,n+1,t.fullTitle(),l,s)})},i.prototype.epilogue=function(){var t,e=this.stats;console.log(),t=y("bright pass"," ")+y("green"," %d passing")+y("light"," (%s)"),console.log(t,e.passes||0,g(e.duration)),e.pending&&(t=y("pending"," ")+y("pending"," %d pending"),console.log(t,e.pending)),e.failures&&(t=y("fail"," %d failing"),console.error(t,e.failures),i.list(this.failures),console.error()),console.log()}}),t.register("reporters/doc.js",function(t,e,n){function r(t){function e(){return Array(n).join(" ")}i.call(this,t);var n=(this.stats,t.total,2);t.on("suite",function(t){t.root||(++n,console.log('%s
            ',e()),++n,console.log("%s

            %s

            ",e(),o.escape(t.title)),console.log("%s
            ",e()))}),t.on("suite end",function(t){t.root||(console.log("%s
            ",e()),--n,console.log("%s
            ",e()),--n)}),t.on("pass",function(t){console.log("%s
            %s
            ",e(),o.escape(t.title));var n=o.escape(o.clean(t.fn.toString()));console.log("%s
            %s
            ",e(),n)}),t.on("fail",function(t,n){console.log('%s
            %s
            ',e(),o.escape(t.title));var r=o.escape(o.clean(t.fn.toString()));console.log('%s
            %s
            ',e(),r),console.log('%s
            %s
            ',e(),o.escape(n))})}var i=n("./base"),o=n("../utils");e=t.exports=r}),t.register("reporters/dot.js",function(t,e,n){function r(t){s.call(this,t);var e=this,n=(this.stats,.75*s.window.width|0),r=-1;t.on("start",function(){o.stdout.write("\n ")}),t.on("pending",function(){++r%n==0&&o.stdout.write("\n "),o.stdout.write(a("pending",s.symbols.dot))}),t.on("pass",function(t){++r%n==0&&o.stdout.write("\n "),o.stdout.write("slow"==t.speed?a("bright yellow",s.symbols.dot):a(t.speed,s.symbols.dot))}),t.on("fail",function(){++r%n==0&&o.stdout.write("\n "),o.stdout.write(a("fail",s.symbols.dot))}),t.on("end",function(){console.log(),e.epilogue()})}function i(){}var s=n("./base"),a=s.color;e=t.exports=r,i.prototype=s.prototype,r.prototype=new i,r.prototype.constructor=r}),t.register("reporters/html-cov.js",function(t,e,n){function r(t){var e=n("jade"),r=__dirname+"/templates/coverage.jade",u=a.readFileSync(r,"utf8"),c=e.compile(u,{filename:r}),l=this;s.call(this,t,!1),t.on("end",function(){o.stdout.write(c({cov:l.cov,coverageClass:i}))})}function i(t){return t>=75?"high":t>=50?"medium":t>=25?"low":"terrible"}var s=n("./json-cov"),a=n("browser/fs");e=t.exports=r}),t.register("reporters/html.js",function(t,e,r){function i(t){h.call(this,t);var e,n,r=this,i=this.stats,m=(t.total,s(v)),b=m.getElementsByTagName("li"),y=b[1].getElementsByTagName("em")[0],w=b[1].getElementsByTagName("a")[0],x=b[2].getElementsByTagName("em")[0],j=b[2].getElementsByTagName("a")[0],E=b[3].getElementsByTagName("em")[0],T=m.getElementsByTagName("canvas")[0],k=s('
              '),q=[k],_=document.getElementById("mocha");if(T.getContext){var O=window.devicePixelRatio||1;T.style.width=T.width,T.style.height=T.height,T.width*=O,T.height*=O,n=T.getContext("2d"),n.scale(O,O),e=new p}return _?(l(w,"click",function(){u();var t=/pass/.test(k.className)?"":" pass";k.className=k.className.replace(/fail|pass/g,"")+t,k.className.trim()&&a("test pass")}),l(j,"click",function(){u();var t=/fail/.test(k.className)?"":" fail";k.className=k.className.replace(/fail|pass/g,"")+t,k.className.trim()&&a("test fail")}),_.appendChild(m),_.appendChild(k),e&&e.size(40),t.on("suite",function(t){if(!t.root){var e=r.suiteURL(t),n=s('
            • %s

            • ',e,d(t.title));q[0].appendChild(n),q.unshift(document.createElement("ul")),n.appendChild(q[0])}}),t.on("suite end",function(t){t.root||q.shift()}),t.on("fail",function(e){"hook"==e.type&&t.emit("test end",e)}),void t.on("test end",function(t){var o=i.tests/this.total*100|0;e&&e.update(o).draw(n);var a=new g-i.start;if(c(y,i.passes),c(x,i.failures),c(E,(a/1e3).toFixed(2)),"passed"==t.state)var u=r.testURL(t),h=s('
            • %e%ems

            • ',t.speed,t.title,t.duration,u);else if(t.pending)var h=s('
            • %e

            • ',t.title);else{var h=s('
            • %e

            • ',t.title,encodeURIComponent(t.fullTitle())),p=t.err.stack||t.err.toString();~p.indexOf(t.err.message)||(p=t.err.message+"\n"+p),"[object Error]"==p&&(p=t.err.message),!t.err.stack&&t.err.sourceURL&&void 0!==t.err.line&&(p+="\n("+t.err.sourceURL+":"+t.err.line+")"),h.appendChild(s('
              %e
              ',p))}if(!t.pending){var d=h.getElementsByTagName("h2")[0];l(d,"click",function(){v.style.display="none"==v.style.display?"block":"none"});var v=s("
              %e
              ",f.clean(t.fn.toString()));h.appendChild(v),v.style.display="none"}q[0]&&q[0].appendChild(h)})):o("#mocha div missing, add it to your document")}function o(t){document.body.appendChild(s('
              %s
              ',t))}function s(t){var e=arguments,n=document.createElement("div"),r=1;return n.innerHTML=t.replace(/%([se])/g,function(t,n){switch(n){case"s":return String(e[r++]);case"e":return d(e[r++])}}),n.firstChild}function a(t){for(var e=document.getElementsByClassName("suite"),n=0;n0&&(e.coverage=e.hits/e.sloc*100),e}function a(t,e){var n={filename:t,coverage:0,hits:0,misses:0,sloc:0,source:{}};return e.source.forEach(function(t,r){r++,0===e[r]?(n.misses++,n.sloc++):void 0!==e[r]&&(n.hits++,n.sloc++),n.source[r]={source:t,coverage:void 0===e[r]?"":e[r]}}),n.coverage=n.hits/n.sloc*100,n}function u(t){return{title:t.title,fullTitle:t.fullTitle(),duration:t.duration}}var c=r("./base");e=t.exports=i}),t.register("reporters/json-stream.js",function(t,e,n){function r(t){s.call(this,t);var e=this,n=(this.stats,t.total);t.on("start",function(){console.log(JSON.stringify(["start",{total:n}]))}),t.on("pass",function(t){console.log(JSON.stringify(["pass",i(t)]))}),t.on("fail",function(t,e){t=i(t),t.err=e.message,console.log(JSON.stringify(["fail",t]))}),t.on("end",function(){o.stdout.write(JSON.stringify(["end",e.stats]))})}function i(t){return{title:t.title,fullTitle:t.fullTitle(),duration:t.duration}}{var s=n("./base");s.color}e=t.exports=r}),t.register("reporters/json.js",function(t,e,n){function r(t){var e=this;a.call(this,t);var n=[],r=[],s=[],u=[];t.on("test end",function(t){n.push(t)}),t.on("pass",function(t){u.push(t)}),t.on("fail",function(t){s.push(t)}),t.on("pending",function(t){r.push(t)}),t.on("end",function(){var a={stats:e.stats,tests:n.map(i),pending:r.map(i),failures:s.map(i),passes:u.map(i)};t.testResults=a,o.stdout.write(JSON.stringify(a,null,2))})}function i(t){return{title:t.title,fullTitle:t.fullTitle(),duration:t.duration,err:s(t.err||{})}}function s(t){var e={};return Object.getOwnPropertyNames(t).forEach(function(n){e[n]=t[n]},t),e}{var a=n("./base");a.cursor,a.color}e=t.exports=r}),t.register("reporters/landing.js",function(t,e,n){function r(t){function e(){var t=Array(r).join("-");return" "+u("runway",t)}s.call(this,t);var n=this,r=(this.stats,.75*s.window.width|0),i=t.total,c=o.stdout,l=u("plane","✈"),h=-1,f=0;t.on("start",function(){c.write("\n\n\n "),a.hide()}),t.on("test end",function(t){var n=-1==h?r*++f/i|0:h;"failed"==t.state&&(l=u("plane crash","✈"),h=n),c.write("["+(r+1)+"D"),c.write(e()),c.write("\n "),c.write(u("runway",Array(n).join("⋅"))),c.write(l),c.write(u("runway",Array(r-n).join("⋅")+"\n")),c.write(e()),c.write("")}),t.on("end",function(){a.show(),console.log(),n.epilogue()})}function i(){}var s=n("./base"),a=s.cursor,u=s.color;e=t.exports=r,s.colors.plane=0,s.colors["plane crash"]=31,s.colors.runway=90,i.prototype=s.prototype,r.prototype=new i,r.prototype.constructor=r}),t.register("reporters/list.js",function(t,e,n){function r(t){s.call(this,t);var e=this,n=(this.stats,0);t.on("start",function(){console.log()}),t.on("test",function(t){o.stdout.write(u("pass"," "+t.fullTitle()+": "))}),t.on("pending",function(t){var e=u("checkmark"," -")+u("pending"," %s");console.log(e,t.fullTitle())}),t.on("pass",function(t){var e=u("checkmark"," "+s.symbols.dot)+u("pass"," %s: ")+u(t.speed,"%dms");a.CR(),console.log(e,t.fullTitle(),t.duration)}),t.on("fail",function(t){a.CR(),console.log(u("fail"," %d) %s"),++n,t.fullTitle())}),t.on("end",e.epilogue.bind(e))}function i(){}var s=n("./base"),a=s.cursor,u=s.color;e=t.exports=r,i.prototype=s.prototype,r.prototype=new i,r.prototype.constructor=r}),t.register("reporters/markdown.js",function(t,e,n){function r(t){function e(t){return Array(u).join("#")+" "+t}function n(t,e){var r=e;return e=e[t.title]=e[t.title]||{suite:t},t.suites.forEach(function(t){n(t,e)}),r}function r(t,e){++e;var n,i="";for(var o in t)"suite"!=o&&(o&&(n=" - ["+o+"](#"+s.slug(t[o].suite.fullTitle())+")\n"),o&&(i+=Array(e).join(" ")+n),i+=r(t[o],e));return--e,i}function a(t){var e=n(t,{});return r(e,0)}i.call(this,t);var u=(this.stats,0),c="";a(t.suite),t.on("suite",function(t){++u;var n=s.slug(t.fullTitle());c+='\n',c+=e(t.title)+"\n"}),t.on("suite end",function(){--u}),t.on("pass",function(t){var e=s.clean(t.fn.toString());c+=t.title+".\n",c+="\n```js\n",c+=e+"\n",c+="```\n\n"}),t.on("end",function(){o.stdout.write("# TOC\n"),o.stdout.write(a(t.suite)),o.stdout.write(c) +})}var i=n("./base"),s=n("../utils");e=t.exports=r}),t.register("reporters/min.js",function(t,e,n){function r(t){s.call(this,t),t.on("start",function(){o.stdout.write(""),o.stdout.write("")}),t.on("end",this.epilogue.bind(this))}function i(){}var s=n("./base");e=t.exports=r,i.prototype=s.prototype,r.prototype=new i,r.prototype.constructor=r}),t.register("reporters/nyan.js",function(t,e,n){function r(t){a.call(this,t);{var e=this,n=(this.stats,.75*a.window.width|0),r=(this.rainbowColors=e.generateColors(),this.colorIndex=0,this.numberOfLines=4,this.trajectories=[[],[],[],[]],this.nyanCatWidth=11);this.trajectoryWidthMax=n-r,this.scoreboardWidth=5,this.tick=0}t.on("start",function(){a.cursor.hide(),e.draw()}),t.on("pending",function(){e.draw()}),t.on("pass",function(){e.draw()}),t.on("fail",function(){e.draw()}),t.on("end",function(){a.cursor.show();for(var t=0;t=this.trajectoryWidthMax&&r.shift(),r.push(e)}},r.prototype.drawRainbow=function(){var t=this;this.trajectories.forEach(function(e){i("["+t.scoreboardWidth+"C"),i(e.join("")),i("\n")}),this.cursorUp(this.numberOfLines)},r.prototype.drawNyanCat=function(){var t=this,e=this.scoreboardWidth+this.trajectories[0].length,n="["+e+"C",r="";i(n),i("_,------,"),i("\n"),i(n),r=t.tick?" ":" ",i("_|"+r+"/\\_/\\ "),i("\n"),i(n),r=t.tick?"_":"__";var o=t.tick?"~":"^";i(o+"|"+r+this.face()+" "),i("\n"),i(n),r=t.tick?" ":" ",i(r+'"" "" '),i("\n"),this.cursorUp(this.numberOfLines)},r.prototype.face=function(){var t=this.stats;return t.failures?"( x .x)":t.pending?"( o .o)":t.passes?"( ^ .^)":"( - .-)"},r.prototype.cursorUp=function(t){i("["+t+"A")},r.prototype.cursorDown=function(t){i("["+t+"B")},r.prototype.generateColors=function(){for(var t=[],e=0;42>e;e++){var n=Math.floor(Math.PI/3),r=e*(1/6),i=Math.floor(3*Math.sin(r)+3),o=Math.floor(3*Math.sin(r+2*n)+3),s=Math.floor(3*Math.sin(r+4*n)+3);t.push(36*i+6*o+s+16)}return t},r.prototype.rainbowify=function(t){var e=this.rainbowColors[this.colorIndex%this.rainbowColors.length];return this.colorIndex+=1,"[38;5;"+e+"m"+t+""},s.prototype=a.prototype,r.prototype=new s,r.prototype.constructor=r}),t.register("reporters/progress.js",function(t,e,n){function r(t,e){s.call(this,t);var n=this,e=e||{},r=(this.stats,.5*s.window.width|0),i=t.total,c=0,l=(Math.max,-1);e.open=e.open||"[",e.complete=e.complete||"▬",e.incomplete=e.incomplete||s.symbols.dot,e.close=e.close||"]",e.verbose=!1,t.on("start",function(){console.log(),a.hide()}),t.on("test end",function(){c++;var t=c/i,n=r*t|0,s=r-n;(l!==n||e.verbose)&&(l=n,a.CR(),o.stdout.write(""),o.stdout.write(u("progress"," "+e.open)),o.stdout.write(Array(n).join(e.complete)),o.stdout.write(Array(s).join(e.incomplete)),o.stdout.write(u("progress",e.close)),e.verbose&&o.stdout.write(u("progress"," "+c+" of "+i)))}),t.on("end",function(){a.show(),console.log(),n.epilogue()})}function i(){}var s=n("./base"),a=s.cursor,u=s.color;e=t.exports=r,s.colors.progress=90,i.prototype=s.prototype,r.prototype=new i,r.prototype.constructor=r}),t.register("reporters/spec.js",function(t,e,n){function r(t){function e(){return Array(r).join(" ")}o.call(this,t);var n=this,r=(this.stats,0),i=0;t.on("start",function(){console.log()}),t.on("suite",function(t){++r,console.log(a("suite","%s%s"),e(),t.title)}),t.on("suite end",function(){--r,1==r&&console.log()}),t.on("pending",function(t){var n=e()+a("pending"," - %s");console.log(n,t.title)}),t.on("pass",function(t){if("fast"==t.speed){var n=e()+a("checkmark"," "+o.symbols.ok)+a("pass"," %s ");s.CR(),console.log(n,t.title)}else{var n=e()+a("checkmark"," "+o.symbols.ok)+a("pass"," %s ")+a(t.speed,"(%dms)");s.CR(),console.log(n,t.title,t.duration)}}),t.on("fail",function(t){s.CR(),console.log(e()+a("fail"," %d) %s"),++i,t.title)}),t.on("end",n.epilogue.bind(n))}function i(){}var o=n("./base"),s=o.cursor,a=o.color;e=t.exports=r,i.prototype=o.prototype,r.prototype=new i,r.prototype.constructor=r}),t.register("reporters/tap.js",function(t,e,n){function r(t){o.call(this,t);var e=(this.stats,1),n=0,r=0;t.on("start",function(){var e=t.grepTotal(t.suite);console.log("%d..%d",1,e)}),t.on("test end",function(){++e}),t.on("pending",function(t){console.log("ok %d %s # SKIP -",e,i(t))}),t.on("pass",function(t){n++,console.log("ok %d %s",e,i(t))}),t.on("fail",function(t,n){r++,console.log("not ok %d %s",e,i(t)),n.stack&&console.log(n.stack.replace(/^/gm," "))}),t.on("end",function(){console.log("# tests "+(n+r)),console.log("# pass "+n),console.log("# fail "+r)})}function i(t){return t.fullTitle().replace(/#/g,"")}{var o=n("./base");o.cursor,o.color}e=t.exports=r}),t.register("reporters/xunit.js",function(t,e,r){function i(t){c.call(this,t);var e=this.stats,n=[];t.on("pending",function(t){n.push(t)}),t.on("pass",function(t){n.push(t)}),t.on("fail",function(t){n.push(t)}),t.on("end",function(){console.log(a("testsuite",{name:"Mocha Tests",tests:e.tests,failures:e.failures,errors:e.failures,skipped:e.tests-e.failures-e.passes,timestamp:(new f).toUTCString(),time:e.duration/1e3||0},!1)),n.forEach(s),console.log("")})}function o(){}function s(t){var e={classname:t.parent.fullTitle(),name:t.title,time:t.duration/1e3||0};if("failed"==t.state){var n=t.err;console.log(a("testcase",e,!1,a("failure",{},!1,u(h(n.message)+"\n"+n.stack))))}else console.log(t.pending?a("testcase",e,!1,a("skipped",{},!0)):a("testcase",e,!0))}function a(t,e,n,r){var i,o=n?"/>":">",s=[];for(var a in e)s.push(a+'="'+h(e[a])+'"');return i="<"+t+(s.length?" "+s.join(" "):"")+o,r&&(i+=r+""}{var c=r("./base"),l=r("../utils"),h=l.escape,f=n.Date;n.setTimeout,n.setInterval,n.clearTimeout,n.clearInterval}e=t.exports=i,o.prototype=c.prototype,i.prototype=new o,i.prototype.constructor=i}),t.register("runnable.js",function(t,e,r){function i(t,e){this.title=t,this.fn=e,this.async=e&&e.length,this.sync=!this.async,this._timeout=2e3,this._slow=75,this._enableTimeouts=!0,this.timedOut=!1,this._trace=new Error("done() called multiple times")}function o(){}var s=r("browser/events").EventEmitter,a=r("browser/debug")("mocha:runnable"),u=r("./ms"),c=n.Date,l=n.setTimeout,h=(n.setInterval,n.clearTimeout),f=(n.clearInterval,Object.prototype.toString);t.exports=i,o.prototype=s.prototype,i.prototype=new o,i.prototype.constructor=i,i.prototype.timeout=function(t){return 0==arguments.length?this._timeout:(0===t&&(this._enableTimeouts=!1),"string"==typeof t&&(t=u(t)),a("timeout %d",t),this._timeout=t,this.timer&&this.resetTimeout(),this)},i.prototype.slow=function(t){return 0===arguments.length?this._slow:("string"==typeof t&&(t=u(t)),a("timeout %d",t),this._slow=t,this)},i.prototype.enableTimeouts=function(t){return 0===arguments.length?this._enableTimeouts:(a("enableTimeouts %s",t),this._enableTimeouts=t,this)},i.prototype.fullTitle=function(){return this.parent.fullTitle()+" "+this.title},i.prototype.clearTimeout=function(){h(this.timer)},i.prototype.inspect=function(){return JSON.stringify(this,function(t,e){return"_"!=t[0]?"parent"==t?"#":"ctx"==t?"#":e:void 0},2)},i.prototype.resetTimeout=function(){var t=this,e=this.timeout()||1e9;this._enableTimeouts&&(this.clearTimeout(),this.timer=l(function(){t._enableTimeouts&&(t.callback(new Error("timeout of "+e+"ms exceeded")),t.timedOut=!0)},e))},i.prototype.globals=function(t){this._allowedGlobals=t},i.prototype.run=function(t){function e(t){o||(o=!0,s.emit("error",t||new Error("done() called multiple times; stacktrace may be inaccurate")))}function n(n){var r=s.timeout();if(!s.timedOut){if(i)return e(n||s._trace);s.clearTimeout(),s.duration=new c-a,i=!0,!n&&s.duration>r&&s._enableTimeouts&&(n=new Error("timeout of "+r+"ms exceeded")),t(n)}}function r(t){var e=t.call(u);e&&"function"==typeof e.then?(s.resetTimeout(),e.then(function(){n()},function(t){n(t||new Error("Promise rejected with no or falsy reason"))})):n()}var i,o,s=this,a=new c,u=this.ctx;if(u&&u.runnable&&u.runnable(this),this.callback=n,this.async){this.resetTimeout();try{this.fn.call(u,function(t){return t instanceof Error||"[object Error]"===f.call(t)?n(t):null!=t?n("[object Object]"===Object.prototype.toString.call(t)?new Error("done() invoked with non-Error: "+JSON.stringify(t)):new Error("done() invoked with non-Error: "+t)):void n()})}catch(l){n(l)}}else{if(this.asyncOnly)return n(new Error("--async-only option in use without declaring `done()`"));try{this.pending?n():r(this.fn)}catch(l){n(l)}}}}),t.register("runner.js",function(t,e,r){function i(t){var e=this;this._globals=[],this._abort=!1,this.suite=t,this.total=t.total(),this.failures=0,this.on("test end",function(t){e.checkGlobals(t)}),this.on("hook end",function(t){e.checkGlobals(t)}),this.grep(/.*/),this.globals(this.globalProps().concat(u()))}function s(){}function a(t,e){return f(e,function(e){if(/^d+/.test(e))return!1;if(n.navigator&&/^getInterface/.test(e))return!1;if(n.navigator&&/^\d+/.test(e))return!1;if(/^mocha-/.test(e))return!1;var r=f(t,function(t){return~t.indexOf("*")?0==e.indexOf(t.split("*")[0]):e==t});return 0==r.length&&(!n.navigator||"onerror"!==e)})}function u(){if("object"==typeof o&&"string"==typeof o.version){var t=o.version.split(".").reduce(function(t,e){return t<<8|e});if(2315>t)return["errno"]}return[]}var c=r("browser/events").EventEmitter,l=r("browser/debug")("mocha:runner"),h=(r("./test"),r("./utils")),f=h.filter,p=(h.keys,["setTimeout","clearTimeout","setInterval","clearInterval","XMLHttpRequest","Date"]);t.exports=i,i.immediately=n.setImmediate||o.nextTick,s.prototype=c.prototype,i.prototype=new s,i.prototype.constructor=i,i.prototype.grep=function(t,e){return l("grep %s",t),this._grep=t,this._invert=e,this.total=this.grepTotal(this.suite),this},i.prototype.grepTotal=function(t){var e=this,n=0;return t.eachTest(function(t){var r=e._grep.test(t.fullTitle());e._invert&&(r=!r),r&&n++}),n},i.prototype.globalProps=function(){for(var t=h.keys(n),e=0;e1?this.fail(t,new Error("global leaks detected: "+e.join(", "))):e.length&&this.fail(t,new Error("global leak detected: "+e[0])))}},i.prototype.fail=function(t,e){++this.failures,t.state="failed","string"==typeof e&&(e=new Error('the string "'+e+'" was thrown, throw an Error :)')),this.emit("fail",t,e)},i.prototype.failHook=function(t,e){this.fail(t,e),this.suite.bail()&&this.emit("end")},i.prototype.hook=function(t,e){function n(t){var i=o[t];return i?s.failures&&r.bail()?e():(s.currentRunnable=i,i.ctx.currentTest=s.test,s.emit("hook",i),i.on("error",function(t){s.failHook(i,t)}),void i.run(function(r){i.removeAllListeners("error");var o=i.error();return o&&s.fail(s.test,o),r?(s.failHook(i,r),e(r)):(s.emit("hook end",i),delete i.ctx.currentTest,void n(++t))})):e()}var r=this.suite,o=r["_"+t],s=this;i.immediately(function(){n(0)})},i.prototype.hooks=function(t,e,n){function r(s){return i.suite=s,s?void i.hook(t,function(t){if(t){var s=i.suite;return i.suite=o,n(t,s)}r(e.pop())}):(i.suite=o,n())}var i=this,o=this.suite;r(e.pop())},i.prototype.hookUp=function(t,e){var n=[this.suite].concat(this.parents()).reverse();this.hooks(t,n,e)},i.prototype.hookDown=function(t,e){var n=[this.suite].concat(this.parents());this.hooks(t,n,e)},i.prototype.parents=function(){for(var t=this.suite,e=[];t=t.parent;)e.push(t);return e},i.prototype.runTest=function(t){var e=this.test,n=this;this.asyncOnly&&(e.asyncOnly=!0);try{e.on("error",function(t){n.fail(e,t)}),e.run(t)}catch(r){t(r)}},i.prototype.runTests=function(t,e){function n(t,r,i){var s=o.suite;o.suite=i?r.parent:r,o.suite?o.hookUp("afterEach",function(t,i){return o.suite=s,t?n(t,i,!0):void e(r)}):(o.suite=s,e(r))}function r(a,u){if(o.failures&&t._bail)return e();if(o._abort)return e();if(a)return n(a,u,!0);if(i=s.shift(),!i)return e();var c=o._grep.test(i.fullTitle());return o._invert&&(c=!c),c?i.pending?(o.emit("pending",i),o.emit("test end",i),r()):(o.emit("test",o.test=i),void o.hookDown("beforeEach",function(t,e){return t?n(t,e,!1):(o.currentRunnable=o.test,void o.runTest(function(t){return i=o.test,t?(o.fail(i,t),o.emit("test end",i),o.hookUp("afterEach",r)):(i.state="passed",o.emit("pass",i),o.emit("test end",i),void o.hookUp("afterEach",r))}))})):r()}var i,o=this,s=t.tests.slice();this.next=r,r()},i.prototype.runSuite=function(t,e){function n(e){if(e)return e==t?r():r(e);if(o._abort)return r();var i=t.suites[s++];return i?void o.runSuite(i,n):r()}function r(n){o.suite=t,o.hook("afterAll",function(){o.emit("suite end",t),e(n)})}var i=this.grepTotal(t),o=this,s=0;return l("run suite %s",t.fullTitle()),i?(this.emit("suite",this.suite=t),void this.hook("beforeAll",function(e){return e?r():void o.runTests(t,n)})):e()},i.prototype.uncaught=function(t){t?l("uncaught exception %s",t!==function(){return this}.call(t)?t:t.message||t):(l("uncaught undefined exception"),t=new Error("Caught undefined error, did you throw without specifying what?")),t.uncaught=!0;var e=this.currentRunnable;if(e){var n=e.state;if(this.fail(e,t),e.clearTimeout(),!n)return"test"==e.type?(this.emit("test end",e),void this.hookUp("afterEach",this.next)):void this.emit("end")}},i.prototype.run=function(t){function e(t){n.uncaught(t)}var n=this,t=t||function(){};return l("start"),this.on("end",function(){l("end"),o.removeListener("uncaughtException",e),t(n.failures)}),this.emit("start"),this.runSuite(this.suite,function(){l("finished running"),n.emit("end")}),o.on("uncaughtException",e),this},i.prototype.abort=function(){l("aborting"),this._abort=!0}}),t.register("suite.js",function(t,e,n){function r(t,e){this.title=t;var n=function(){};n.prototype=e,this.ctx=new n,this.suites=[],this.tests=[],this.pending=!1,this._beforeEach=[],this._beforeAll=[],this._afterEach=[],this._afterAll=[],this.root=!t,this._timeout=2e3,this._enableTimeouts=!0,this._slow=75,this._bail=!1}function i(){}var o=n("browser/events").EventEmitter,s=n("browser/debug")("mocha:suite"),a=n("./ms"),u=n("./utils"),c=n("./hook");e=t.exports=r,e.create=function(t,e){var n=new r(e,t.ctx);return n.parent=t,t.pending&&(n.pending=!0),e=n.fullTitle(),t.addSuite(n),n},i.prototype=o.prototype,r.prototype=new i,r.prototype.constructor=r,r.prototype.clone=function(){var t=new r(this.title);return s("clone"),t.ctx=this.ctx,t.timeout(this.timeout()),t.enableTimeouts(this.enableTimeouts()),t.slow(this.slow()),t.bail(this.bail()),t},r.prototype.timeout=function(t){return 0==arguments.length?this._timeout:(0===t&&(this._enableTimeouts=!1),"string"==typeof t&&(t=a(t)),s("timeout %d",t),this._timeout=parseInt(t,10),this)},r.prototype.enableTimeouts=function(t){return 0===arguments.length?this._enableTimeouts:(s("enableTimeouts %s",t),this._enableTimeouts=t,this)},r.prototype.slow=function(t){return 0===arguments.length?this._slow:("string"==typeof t&&(t=a(t)),s("slow %d",t),this._slow=t,this)},r.prototype.bail=function(t){return 0==arguments.length?this._bail:(s("bail %s",t),this._bail=t,this)},r.prototype.beforeAll=function(t,e){if(this.pending)return this;"function"==typeof t&&(e=t,t=e.name),t='"before all" hook'+(t?": "+t:"");var n=new c(t,e);return n.parent=this,n.timeout(this.timeout()),n.enableTimeouts(this.enableTimeouts()),n.slow(this.slow()),n.ctx=this.ctx,this._beforeAll.push(n),this.emit("beforeAll",n),this},r.prototype.afterAll=function(t,e){if(this.pending)return this;"function"==typeof t&&(e=t,t=e.name),t='"after all" hook'+(t?": "+t:"");var n=new c(t,e);return n.parent=this,n.timeout(this.timeout()),n.enableTimeouts(this.enableTimeouts()),n.slow(this.slow()),n.ctx=this.ctx,this._afterAll.push(n),this.emit("afterAll",n),this},r.prototype.beforeEach=function(t,e){if(this.pending)return this;"function"==typeof t&&(e=t,t=e.name),t='"before each" hook'+(t?": "+t:"");var n=new c(t,e);return n.parent=this,n.timeout(this.timeout()),n.enableTimeouts(this.enableTimeouts()),n.slow(this.slow()),n.ctx=this.ctx,this._beforeEach.push(n),this.emit("beforeEach",n),this},r.prototype.afterEach=function(t,e){if(this.pending)return this;"function"==typeof t&&(e=t,t=e.name),t='"after each" hook'+(t?": "+t:"");var n=new c(t,e);return n.parent=this,n.timeout(this.timeout()),n.enableTimeouts(this.enableTimeouts()),n.slow(this.slow()),n.ctx=this.ctx,this._afterEach.push(n),this.emit("afterEach",n),this},r.prototype.addSuite=function(t){return t.parent=this,t.timeout(this.timeout()),t.enableTimeouts(this.enableTimeouts()),t.slow(this.slow()),t.bail(this.bail()),this.suites.push(t),this.emit("suite",t),this},r.prototype.addTest=function(t){return t.parent=this,t.timeout(this.timeout()),t.enableTimeouts(this.enableTimeouts()),t.slow(this.slow()),t.ctx=this.ctx,this.tests.push(t),this.emit("test",t),this},r.prototype.fullTitle=function(){if(this.parent){var t=this.parent.fullTitle();if(t)return t+" "+this.title}return this.title},r.prototype.total=function(){return u.reduce(this.suites,function(t,e){return t+e.total()},0)+this.tests.length},r.prototype.eachTest=function(t){return u.forEach(this.tests,t),u.forEach(this.suites,function(e){e.eachTest(t)}),this}}),t.register("test.js",function(t,e,n){function r(t,e){o.call(this,t,e),this.pending=!e,this.type="test"}function i(){}var o=n("./runnable");t.exports=r,i.prototype=o.prototype,r.prototype=new i,r.prototype.constructor=r}),t.register("utils.js",function(t,e,n){function r(t){return!~f.indexOf(t)}function i(t){return t.replace(//g,">").replace(/\/\/(.*)/gm,'//$1').replace(/('.*?')/gm,'$1').replace(/(\d+\.\d+)/gm,'$1').replace(/(\d+)/gm,'$1').replace(/\bnew[ \t]+(\w+)/gm,'new $1').replace(/\b(function|new|throw|return|var|if|else)\b/gm,'$1')}var o=n("browser/fs"),s=n("browser/path"),a=s.basename,u=o.existsSync||s.existsSync,c=n("browser/glob"),l=s.join,h=n("browser/debug")("mocha:watch"),f=["node_modules",".git"];e.escape=function(t){return String(t).replace(/&/g,"&").replace(/"/g,""").replace(//g,">")},e.forEach=function(t,e,n){for(var r=0,i=t.length;i>r;r++)e.call(n,t[r],r)},e.map=function(t,e,n){for(var r=[],i=0,o=t.length;o>i;i++)r.push(e.call(n,t[i],i));return r},e.indexOf=function(t,e,n){for(var r=n||0,i=t.length;i>r;r++)if(t[r]===e)return r;return-1},e.reduce=function(t,e,n){for(var r=n,i=0,o=t.length;o>i;i++)r=e(r,t[i],i,t);return r},e.filter=function(t,e){for(var n=[],r=0,i=t.length;i>r;r++){var o=t[r];e(o,r,t)&&n.push(o)}return n},e.keys=Object.keys||function(t){var e=[],n=Object.prototype.hasOwnProperty;for(var r in t)n.call(t,r)&&e.push(r);return e},e.watch=function(t,e){var n={interval:100};t.forEach(function(t){h("file %s",t),o.watchFile(t,n,function(n,r){r.mtime *{?/,"").replace(/\s+\}$/,"");var n=t.match(/^\n?( *)/)[1].length,r=t.match(/^\n?(\t*)/)[1].length,i=new RegExp("^\n?"+(r?" ":" ")+"{"+(r?r:n)+"}","gm");return t=t.replace(i,""),e.trim(t)},e.trim=function(t){return t.replace(/^\s+|\s+$/g,"")},e.parseQuery=function(t){return e.reduce(t.replace("?","").split("&"),function(t,e){var n=e.indexOf("="),r=e.slice(0,n),i=e.slice(++n);return t[r]=decodeURIComponent(i),t},{})},e.highlightTags=function(t){for(var e=document.getElementById("mocha").getElementsByTagName(t),n=0,r=e.length;r>n;++n)e[n].innerHTML=i(e[n].innerHTML)},e.stringify=function(t){return t instanceof RegExp?t.toString():JSON.stringify(e.canonicalize(t),null,2).replace(/,(\n|$)/g,"$1")},e.canonicalize=function(t,n){if(n=n||[],-1!==e.indexOf(n,t))return"[Circular]";var r;return"[object Array]"==={}.toString.call(t)?(n.push(t),r=e.map(t,function(t){return e.canonicalize(t,n)}),n.pop()):"object"==typeof t&&null!==t?(n.push(t),r={},e.forEach(e.keys(t).sort(),function(i){r[i]=e.canonicalize(t[i],n)}),n.pop()):r=t,r},e.lookupFiles=function p(t,e,n){var r=[],i=new RegExp("\\.("+e.join("|")+")$");if(!u(t)){if(!u(t+".js")){if(r=c.sync(t),!r.length)throw new Error("cannot resolve path (or pattern) '"+t+"'");return r}t+=".js"}try{var s=o.statSync(t);if(s.isFile())return t}catch(h){return}return o.readdirSync(t).forEach(function(s){s=l(t,s);try{var u=o.statSync(s);if(u.isDirectory())return void(n&&(r=r.concat(p(s,e,n))))}catch(c){return}u.isFile()&&i.test(s)&&"."!==a(s)[0]&&r.push(s)}),r}});var n=function(){return this}(),r=n.Date,i=n.setTimeout,o=(n.setInterval,n.clearTimeout,n.clearInterval,{});o.exit=function(){},o.stdout={};var s=[],a=n.onerror;o.removeListener=function(t,e){if("uncaughtException"==t){n.onerror=a?a:function(){};var r=u.utils.indexOf(s,e);-1!=r&&s.splice(r,1)}},o.on=function(t,e){"uncaughtException"==t&&(n.onerror=function(t,n,r){return e(new Error(t+" ("+n+":"+r+")")),!0},s.push(e))};var u=n.Mocha=t("mocha"),c=n.mocha=new u({reporter:"html"});c.suite.removeAllListeners("pre-require");var l,h=[];u.Runner.immediately=function(t){h.push(t),l||(l=i(e,0))},c.throwError=function(t){throw u.utils.forEach(s,function(e){e(t)}),t},c.ui=function(t){return u.prototype.ui.call(this,t),this.suite.emit("pre-require",n,null,this),this},c.setup=function(t){"string"==typeof t&&(t={ui:t});for(var e in t)this[e](t[e]);return this},c.run=function(t){var e=c.options;c.globals("location");var r=u.utils.parseQuery(n.location.search||"");return r.grep&&c.grep(r.grep),r.invert&&c.invert(),u.prototype.run.call(c,function(r){var i=n.document;i&&i.getElementById("mocha")&&e.noHighlighting!==!0&&u.utils.highlightTags("code"),t&&t(r)})},u.process=o}(),function(){function require(t){var e=require.modules[t];if(!e)throw new Error('failed to require "'+t+'"');return"exports"in e||"function"!=typeof e.definition||(e.client=e.component=!0,e.definition.call(this,e.exports={},e),delete e.definition),e.exports}require.loader="component",require.helper={},require.helper.semVerSort=function(t,e){for(var n=t.version.split("."),r=e.version.split("."),i=0;is?1:-1;var a=n[i].substr((""+o).length),u=r[i].substr((""+s).length);if(""===a&&""!==u)return 1;if(""!==a&&""===u)return-1;if(""!==a&&""!==u)return a>u?1:-1}return 0},require.latest=function(t,e){function n(t){throw new Error('failed to find latest module of "'+t+'"')}var r=/(.*)~(.*)@v?(\d+\.\d+\.\d+[^\/]*)$/,i=/(.*)~(.*)/;i.test(t)||n(t);for(var o=Object.keys(require.modules),s=[],a=[],u=0;u0){var f=s.sort(require.helper.semVerSort).pop().name;return e===!0?f:require(f)}var f=a.pop().name;return e===!0?f:require(f)},require.modules={},require.register=function(t,e){require.modules[t]={definition:e}},require.define=function(t,e){require.modules[t]={exports:e}},require.register("chaijs~assertion-error@1.0.0",function(t,e){function n(){function t(t,n){Object.keys(n).forEach(function(r){~e.indexOf(r)||(t[r]=n[r])})}var e=[].slice.call(arguments);return function(){for(var e=[].slice.call(arguments),n=0,r={};n=0;i--)if(l=o[i],!n(t[l],e[l],r))return!1;return!0}var p,d=require("chaijs~type-detect@0.1.1");try{p=require("buffer").Buffer}catch(g){p={},p.isBuffer=function(){return!1}}e.exports=n}),require.register("chai",function(t,e){e.exports=require("chai/lib/chai.js")}),require.register("chai/lib/chai.js",function(t,e){var n=[],t=e.exports={};t.version="1.10.0",t.AssertionError=require("chaijs~assertion-error@1.0.0");var r=require("chai/lib/chai/utils/index.js");t.use=function(t){return~n.indexOf(t)||(t(this,r),n.push(t)),this};var i=require("chai/lib/chai/config.js");t.config=i;var o=require("chai/lib/chai/assertion.js");t.use(o);var s=require("chai/lib/chai/core/assertions.js");t.use(s);var a=require("chai/lib/chai/interface/expect.js");t.use(a);var u=require("chai/lib/chai/interface/should.js");t.use(u);var c=require("chai/lib/chai/interface/assert.js");t.use(c)}),require.register("chai/lib/chai/assertion.js",function(t,e){var n=require("chai/lib/chai/config.js"),r=function(){};e.exports=function(t,e){function i(t,e,n){s(this,"ssfi",n||arguments.callee),s(this,"object",t),s(this,"message",e)}var o=t.AssertionError,s=e.flag;t.Assertion=i,Object.defineProperty(i,"includeStack",{get:function(){return console.warn("Assertion.includeStack is deprecated, use chai.config.includeStack instead."),n.includeStack},set:function(t){console.warn("Assertion.includeStack is deprecated, use chai.config.includeStack instead."),n.includeStack=t}}),Object.defineProperty(i,"showDiff",{get:function(){return console.warn("Assertion.showDiff is deprecated, use chai.config.showDiff instead."),n.showDiff},set:function(t){console.warn("Assertion.showDiff is deprecated, use chai.config.showDiff instead."),n.showDiff=t}}),i.addProperty=function(t,n){e.addProperty(this.prototype,t,n)},i.addMethod=function(t,n){e.addMethod(this.prototype,t,n)},i.addChainableMethod=function(t,n,r){e.addChainableMethod(this.prototype,t,n,r)},i.addChainableNoop=function(t,n){e.addChainableMethod(this.prototype,t,r,n)},i.overwriteProperty=function(t,n){e.overwriteProperty(this.prototype,t,n)},i.overwriteMethod=function(t,n){e.overwriteMethod(this.prototype,t,n)},i.overwriteChainableMethod=function(t,n,r){e.overwriteChainableMethod(this.prototype,t,n,r)},i.prototype.assert=function(t,r,i,a,u,c){var l=e.test(this,arguments);if(!0!==c&&(c=!1),!0!==n.showDiff&&(c=!1),!l){var r=e.getMessage(this,arguments),h=e.getActual(this,arguments);throw new o(r,{actual:h,expected:a,showDiff:c},n.includeStack?this.assert:s(this,"ssfi"))}},Object.defineProperty(i.prototype,"_obj",{get:function(){return s(this,"object")},set:function(t){s(this,"object",t)}})}}),require.register("chai/lib/chai/config.js",function(t,e){e.exports={includeStack:!1,showDiff:!0,truncateThreshold:40}}),require.register("chai/lib/chai/core/assertions.js",function(t,e){e.exports=function(t,e){function n(t,n){n&&w(this,"message",n),t=t.toLowerCase();var r=w(this,"object"),i=~["a","e","i","o","u"].indexOf(t.charAt(0))?"an ":"a ";this.assert(t===e.type(r),"expected #{this} to be "+i+t,"expected #{this} not to be "+i+t)}function r(){w(this,"contains",!0)}function i(t,n){n&&w(this,"message",n);var r=w(this,"object"),i=!1;if("array"===e.type(r)&&"object"===e.type(t)){for(var o in r)if(e.eql(r[o],t)){i=!0;break}}else if("object"===e.type(t)){if(!w(this,"negate")){for(var s in t)new y(r).property(s,t[s]);return}var a={};for(var s in t)a[s]=r[s];i=e.eql(a,t)}else i=r&&~r.indexOf(t);this.assert(i,"expected #{this} to include "+e.inspect(t),"expected #{this} to not include "+e.inspect(t))}function o(){var t=w(this,"object"),e=Object.prototype.toString.call(t);this.assert("[object Arguments]"===e,"expected #{this} to be arguments but got "+e,"expected #{this} to not be arguments")}function s(t,e){e&&w(this,"message",e);var n=w(this,"object");return w(this,"deep")?this.eql(t):void this.assert(t===n,"expected #{this} to equal #{exp}","expected #{this} to not equal #{exp}",t,this._obj,!0)}function a(t,n){n&&w(this,"message",n),this.assert(e.eql(t,w(this,"object")),"expected #{this} to deeply equal #{exp}","expected #{this} to not deeply equal #{exp}",t,this._obj,!0)}function u(t,e){e&&w(this,"message",e);var n=w(this,"object");if(w(this,"doLength")){new y(n,e).to.have.property("length");var r=n.length;this.assert(r>t,"expected #{this} to have a length above #{exp} but got #{act}","expected #{this} to not have a length above #{exp}",t,r)}else this.assert(n>t,"expected #{this} to be above "+t,"expected #{this} to be at most "+t)}function c(t,e){e&&w(this,"message",e);var n=w(this,"object");if(w(this,"doLength")){new y(n,e).to.have.property("length");var r=n.length;this.assert(r>=t,"expected #{this} to have a length at least #{exp} but got #{act}","expected #{this} to have a length below #{exp}",t,r)}else this.assert(n>=t,"expected #{this} to be at least "+t,"expected #{this} to be below "+t)}function l(t,e){e&&w(this,"message",e);var n=w(this,"object");if(w(this,"doLength")){new y(n,e).to.have.property("length");var r=n.length;this.assert(t>r,"expected #{this} to have a length below #{exp} but got #{act}","expected #{this} to not have a length below #{exp}",t,r) +}else this.assert(t>n,"expected #{this} to be below "+t,"expected #{this} to be at least "+t)}function h(t,e){e&&w(this,"message",e);var n=w(this,"object");if(w(this,"doLength")){new y(n,e).to.have.property("length");var r=n.length;this.assert(t>=r,"expected #{this} to have a length at most #{exp} but got #{act}","expected #{this} to have a length above #{exp}",t,r)}else this.assert(t>=n,"expected #{this} to be at most "+t,"expected #{this} to be above "+t)}function f(t,n){n&&w(this,"message",n);var r=e.getName(t);this.assert(w(this,"object")instanceof t,"expected #{this} to be an instance of "+r,"expected #{this} to not be an instance of "+r)}function p(t,n){n&&w(this,"message",n);var r=w(this,"object");this.assert(r.hasOwnProperty(t),"expected #{this} to have own property "+e.inspect(t),"expected #{this} to not have own property "+e.inspect(t))}function d(){w(this,"doLength",!0)}function g(t,e){e&&w(this,"message",e);var n=w(this,"object");new y(n,e).to.have.property("length");var r=n.length;this.assert(r==t,"expected #{this} to have a length of #{exp} but got #{act}","expected #{this} to not have a length of #{act}",t,r)}function v(t){var n,r=w(this,"object"),i=!0;if(t=t instanceof Array?t:Array.prototype.slice.call(arguments),!t.length)throw new Error("keys required");var o=Object.keys(r),s=t,a=t.length;if(i=t.every(function(t){return~o.indexOf(t)}),w(this,"negate")||w(this,"contains")||(i=i&&t.length==o.length),a>1){t=t.map(function(t){return e.inspect(t)});var u=t.pop();n=t.join(", ")+", and "+u}else n=e.inspect(t[0]);n=(a>1?"keys ":"key ")+n,n=(w(this,"contains")?"contain ":"have ")+n,this.assert(i,"expected #{this} to "+n,"expected #{this} to not "+n,s.sort(),o.sort(),!0)}function m(t,n,r){r&&w(this,"message",r);var i=w(this,"object");new y(i,r).is.a("function");var o=!1,s=null,a=null,u=null;0===arguments.length?(n=null,t=null):t&&(t instanceof RegExp||"string"==typeof t)?(n=t,t=null):t&&t instanceof Error?(s=t,t=null,n=null):"function"==typeof t?(a=t.prototype.name||t.name,"Error"===a&&t!==Error&&(a=(new t).name)):t=null;try{i()}catch(c){if(s)return this.assert(c===s,"expected #{this} to throw #{exp} but #{act} was thrown","expected #{this} to not throw #{exp}",s instanceof Error?s.toString():s,c instanceof Error?c.toString():c),w(this,"object",c),this;if(t&&(this.assert(c instanceof t,"expected #{this} to throw #{exp} but #{act} was thrown","expected #{this} to not throw #{exp} but #{act} was thrown",a,c instanceof Error?c.toString():c),!n))return w(this,"object",c),this;var l="object"===e.type(c)&&"message"in c?c.message:""+c;if(null!=l&&n&&n instanceof RegExp)return this.assert(n.exec(l),"expected #{this} to throw error matching #{exp} but got #{act}","expected #{this} to throw error not matching #{exp}",n,l),w(this,"object",c),this;if(null!=l&&n&&"string"==typeof n)return this.assert(~l.indexOf(n),"expected #{this} to throw error including #{exp} but got #{act}","expected #{this} to throw error not including #{act}",n,l),w(this,"object",c),this;o=!0,u=c}var h="",f=null!==a?a:s?"#{exp}":"an error";o&&(h=" but #{act} was thrown"),this.assert(o===!0,"expected #{this} to throw "+f+h,"expected #{this} to not throw "+f+h,s instanceof Error?s.toString():s,u instanceof Error?u.toString():u),w(this,"object",u)}function b(t,e,n){return t.every(function(t){return n?e.some(function(e){return n(t,e)}):-1!==e.indexOf(t)})}var y=t.Assertion,w=(Object.prototype.toString,e.flag);["to","be","been","is","and","has","have","with","that","at","of","same"].forEach(function(t){y.addProperty(t,function(){return this})}),y.addProperty("not",function(){w(this,"negate",!0)}),y.addProperty("deep",function(){w(this,"deep",!0)}),y.addChainableMethod("an",n),y.addChainableMethod("a",n),y.addChainableMethod("include",i,r),y.addChainableMethod("contain",i,r),y.addChainableNoop("ok",function(){this.assert(w(this,"object"),"expected #{this} to be truthy","expected #{this} to be falsy")}),y.addChainableNoop("true",function(){this.assert(!0===w(this,"object"),"expected #{this} to be true","expected #{this} to be false",this.negate?!1:!0)}),y.addChainableNoop("false",function(){this.assert(!1===w(this,"object"),"expected #{this} to be false","expected #{this} to be true",this.negate?!0:!1)}),y.addChainableNoop("null",function(){this.assert(null===w(this,"object"),"expected #{this} to be null","expected #{this} not to be null")}),y.addChainableNoop("undefined",function(){this.assert(void 0===w(this,"object"),"expected #{this} to be undefined","expected #{this} not to be undefined")}),y.addChainableNoop("exist",function(){this.assert(null!=w(this,"object"),"expected #{this} to exist","expected #{this} to not exist")}),y.addChainableNoop("empty",function(){var t=w(this,"object"),e=t;Array.isArray(t)||"string"==typeof object?e=t.length:"object"==typeof t&&(e=Object.keys(t).length),this.assert(!e,"expected #{this} to be empty","expected #{this} not to be empty")}),y.addChainableNoop("arguments",o),y.addChainableNoop("Arguments",o),y.addMethod("equal",s),y.addMethod("equals",s),y.addMethod("eq",s),y.addMethod("eql",a),y.addMethod("eqls",a),y.addMethod("above",u),y.addMethod("gt",u),y.addMethod("greaterThan",u),y.addMethod("least",c),y.addMethod("gte",c),y.addMethod("below",l),y.addMethod("lt",l),y.addMethod("lessThan",l),y.addMethod("most",h),y.addMethod("lte",h),y.addMethod("within",function(t,e,n){n&&w(this,"message",n);var r=w(this,"object"),i=t+".."+e;if(w(this,"doLength")){new y(r,n).to.have.property("length");var o=r.length;this.assert(o>=t&&e>=o,"expected #{this} to have a length within "+i,"expected #{this} to not have a length within "+i)}else this.assert(r>=t&&e>=r,"expected #{this} to be within "+i,"expected #{this} to not be within "+i)}),y.addMethod("instanceof",f),y.addMethod("instanceOf",f),y.addMethod("property",function(t,n,r){r&&w(this,"message",r);var i=w(this,"deep")?"deep property ":"property ",o=w(this,"negate"),s=w(this,"object"),a=w(this,"deep")?e.getPathValue(t,s):s[t];if(o&&void 0!==n){if(void 0===a)throw r=null!=r?r+": ":"",new Error(r+e.inspect(s)+" has no "+i+e.inspect(t))}else this.assert(void 0!==a,"expected #{this} to have a "+i+e.inspect(t),"expected #{this} to not have "+i+e.inspect(t));void 0!==n&&this.assert(n===a,"expected #{this} to have a "+i+e.inspect(t)+" of #{exp}, but got #{act}","expected #{this} to not have a "+i+e.inspect(t)+" of #{act}",n,a),w(this,"object",a)}),y.addMethod("ownProperty",p),y.addMethod("haveOwnProperty",p),y.addChainableMethod("length",g,d),y.addMethod("lengthOf",g),y.addMethod("match",function(t,e){e&&w(this,"message",e);var n=w(this,"object");this.assert(t.exec(n),"expected #{this} to match "+t,"expected #{this} not to match "+t)}),y.addMethod("string",function(t,n){n&&w(this,"message",n);var r=w(this,"object");new y(r,n).is.a("string"),this.assert(~r.indexOf(t),"expected #{this} to contain "+e.inspect(t),"expected #{this} to not contain "+e.inspect(t))}),y.addMethod("keys",v),y.addMethod("key",v),y.addMethod("throw",m),y.addMethod("throws",m),y.addMethod("Throw",m),y.addMethod("respondTo",function(t,n){n&&w(this,"message",n);var r=w(this,"object"),i=w(this,"itself"),o="function"!==e.type(r)||i?r[t]:r.prototype[t];this.assert("function"==typeof o,"expected #{this} to respond to "+e.inspect(t),"expected #{this} to not respond to "+e.inspect(t))}),y.addProperty("itself",function(){w(this,"itself",!0)}),y.addMethod("satisfy",function(t,n){n&&w(this,"message",n);var r=w(this,"object"),i=t(r);this.assert(i,"expected #{this} to satisfy "+e.objDisplay(t),"expected #{this} to not satisfy"+e.objDisplay(t),this.negate?!1:!0,i)}),y.addMethod("closeTo",function(t,n,r){r&&w(this,"message",r);var i=w(this,"object");if(new y(i,r).is.a("number"),"number"!==e.type(t)||"number"!==e.type(n))throw new Error("the arguments to closeTo must be numbers");this.assert(Math.abs(i-t)<=n,"expected #{this} to be close to "+t+" +/- "+n,"expected #{this} not to be close to "+t+" +/- "+n)}),y.addMethod("members",function(t,n){n&&w(this,"message",n);var r=w(this,"object");new y(r).to.be.an("array"),new y(t).to.be.an("array");var i=w(this,"deep")?e.eql:void 0;return w(this,"contains")?this.assert(b(t,r,i),"expected #{this} to be a superset of #{act}","expected #{this} to not be a superset of #{act}",r,t):void this.assert(b(r,t,i)&&b(t,r,i),"expected #{this} to have the same members as #{act}","expected #{this} to not have the same members as #{act}",r,t)})}}),require.register("chai/lib/chai/interface/assert.js",function(exports,module){module.exports=function(chai,util){var Assertion=chai.Assertion,flag=util.flag,assert=chai.assert=function(t,e){var n=new Assertion(null,null,chai.assert);n.assert(t,e,"[ negation message unavailable ]")};assert.fail=function(t,e,n,r){throw n=n||"assert.fail()",new chai.AssertionError(n,{actual:t,expected:e,operator:r},assert.fail)},assert.ok=function(t,e){new Assertion(t,e).is.ok},assert.notOk=function(t,e){new Assertion(t,e).is.not.ok},assert.equal=function(t,e,n){var r=new Assertion(t,n,assert.equal);r.assert(e==flag(r,"object"),"expected #{this} to equal #{exp}","expected #{this} to not equal #{act}",e,t)},assert.notEqual=function(t,e,n){var r=new Assertion(t,n,assert.notEqual);r.assert(e!=flag(r,"object"),"expected #{this} to not equal #{exp}","expected #{this} to equal #{act}",e,t)},assert.strictEqual=function(t,e,n){new Assertion(t,n).to.equal(e)},assert.notStrictEqual=function(t,e,n){new Assertion(t,n).to.not.equal(e)},assert.deepEqual=function(t,e,n){new Assertion(t,n).to.eql(e)},assert.notDeepEqual=function(t,e,n){new Assertion(t,n).to.not.eql(e)},assert.isTrue=function(t,e){new Assertion(t,e).is["true"]},assert.isFalse=function(t,e){new Assertion(t,e).is["false"]},assert.isNull=function(t,e){new Assertion(t,e).to.equal(null)},assert.isNotNull=function(t,e){new Assertion(t,e).to.not.equal(null)},assert.isUndefined=function(t,e){new Assertion(t,e).to.equal(void 0)},assert.isDefined=function(t,e){new Assertion(t,e).to.not.equal(void 0)},assert.isFunction=function(t,e){new Assertion(t,e).to.be.a("function")},assert.isNotFunction=function(t,e){new Assertion(t,e).to.not.be.a("function")},assert.isObject=function(t,e){new Assertion(t,e).to.be.a("object")},assert.isNotObject=function(t,e){new Assertion(t,e).to.not.be.a("object")},assert.isArray=function(t,e){new Assertion(t,e).to.be.an("array")},assert.isNotArray=function(t,e){new Assertion(t,e).to.not.be.an("array")},assert.isString=function(t,e){new Assertion(t,e).to.be.a("string")},assert.isNotString=function(t,e){new Assertion(t,e).to.not.be.a("string")},assert.isNumber=function(t,e){new Assertion(t,e).to.be.a("number")},assert.isNotNumber=function(t,e){new Assertion(t,e).to.not.be.a("number")},assert.isBoolean=function(t,e){new Assertion(t,e).to.be.a("boolean")},assert.isNotBoolean=function(t,e){new Assertion(t,e).to.not.be.a("boolean")},assert.typeOf=function(t,e,n){new Assertion(t,n).to.be.a(e)},assert.notTypeOf=function(t,e,n){new Assertion(t,n).to.not.be.a(e)},assert.instanceOf=function(t,e,n){new Assertion(t,n).to.be.instanceOf(e)},assert.notInstanceOf=function(t,e,n){new Assertion(t,n).to.not.be.instanceOf(e)},assert.include=function(t,e,n){new Assertion(t,n,assert.include).include(e)},assert.notInclude=function(t,e,n){new Assertion(t,n,assert.notInclude).not.include(e)},assert.match=function(t,e,n){new Assertion(t,n).to.match(e)},assert.notMatch=function(t,e,n){new Assertion(t,n).to.not.match(e)},assert.property=function(t,e,n){new Assertion(t,n).to.have.property(e)},assert.notProperty=function(t,e,n){new Assertion(t,n).to.not.have.property(e)},assert.deepProperty=function(t,e,n){new Assertion(t,n).to.have.deep.property(e)},assert.notDeepProperty=function(t,e,n){new Assertion(t,n).to.not.have.deep.property(e)},assert.propertyVal=function(t,e,n,r){new Assertion(t,r).to.have.property(e,n)},assert.propertyNotVal=function(t,e,n,r){new Assertion(t,r).to.not.have.property(e,n)},assert.deepPropertyVal=function(t,e,n,r){new Assertion(t,r).to.have.deep.property(e,n)},assert.deepPropertyNotVal=function(t,e,n,r){new Assertion(t,r).to.not.have.deep.property(e,n)},assert.lengthOf=function(t,e,n){new Assertion(t,n).to.have.length(e)},assert.Throw=function(t,e,n,r){("string"==typeof e||e instanceof RegExp)&&(n=e,e=null);var i=new Assertion(t,r).to.Throw(e,n);return flag(i,"object")},assert.doesNotThrow=function(t,e,n){"string"==typeof e&&(n=e,e=null),new Assertion(t,n).to.not.Throw(e)},assert.operator=function(val,operator,val2,msg){if(!~["==","===",">",">=","<","<=","!=","!=="].indexOf(operator))throw new Error('Invalid operator "'+operator+'"');var test=new Assertion(eval(val+operator+val2),msg);test.assert(!0===flag(test,"object"),"expected "+util.inspect(val)+" to be "+operator+" "+util.inspect(val2),"expected "+util.inspect(val)+" to not be "+operator+" "+util.inspect(val2))},assert.closeTo=function(t,e,n,r){new Assertion(t,r).to.be.closeTo(e,n)},assert.sameMembers=function(t,e,n){new Assertion(t,n).to.have.same.members(e)},assert.includeMembers=function(t,e,n){new Assertion(t,n).to.include.members(e)},assert.ifError=function(t,e){new Assertion(t,e).to.not.be.ok},function t(e,n){return assert[n]=assert[e],t}("Throw","throw")("Throw","throws")}}),require.register("chai/lib/chai/interface/expect.js",function(t,e){e.exports=function(t){t.expect=function(e,n){return new t.Assertion(e,n)}}}),require.register("chai/lib/chai/interface/should.js",function(t,e){e.exports=function(t){function e(){function t(){return this instanceof String||this instanceof Number?new n(this.constructor(this),null,t):this instanceof Boolean?new n(1==this,null,t):new n(this,null,t)}function e(t){Object.defineProperty(this,"should",{value:t,enumerable:!0,configurable:!0,writable:!0})}Object.defineProperty(Object.prototype,"should",{set:e,get:t,configurable:!0});var r={};return r.equal=function(t,e,r){new n(t,r).to.equal(e)},r.Throw=function(t,e,r,i){new n(t,i).to.Throw(e,r)},r.exist=function(t,e){new n(t,e).to.exist},r.not={},r.not.equal=function(t,e,r){new n(t,r).to.not.equal(e)},r.not.Throw=function(t,e,r,i){new n(t,i).to.not.Throw(e,r)},r.not.exist=function(t,e){new n(t,e).to.not.exist},r["throw"]=r.Throw,r.not["throw"]=r.not.Throw,r}var n=t.Assertion;t.should=e,t.Should=e}}),require.register("chai/lib/chai/utils/addChainableMethod.js",function(t,e){var n=require("chai/lib/chai/utils/transferFlags.js"),r=require("chai/lib/chai/utils/flag.js"),i=require("chai/lib/chai/config.js"),o="__proto__"in Object,s=/^(?:length|name|arguments|caller)$/,a=Function.prototype.call,u=Function.prototype.apply;e.exports=function(t,e,c,l){"function"!=typeof l&&(l=function(){});var h={method:c,chainingBehavior:l};t.__methods||(t.__methods={}),t.__methods[e]=h,Object.defineProperty(t,e,{get:function(){h.chainingBehavior.call(this);var e=function f(){var t=r(this,"ssfi");t&&i.includeStack===!1&&r(this,"ssfi",f);var e=h.method.apply(this,arguments);return void 0===e?this:e};if(o){var c=e.__proto__=Object.create(this);c.call=a,c.apply=u}else{var l=Object.getOwnPropertyNames(t);l.forEach(function(n){if(!s.test(n)){var r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r)}})}return n(this,e),e},configurable:!0})}}),require.register("chai/lib/chai/utils/addMethod.js",function(t,e){var n=require("chai/lib/chai/config.js"),r=require("chai/lib/chai/utils/flag.js");e.exports=function(t,e,i){t[e]=function(){var o=r(this,"ssfi");o&&n.includeStack===!1&&r(this,"ssfi",t[e]);var s=i.apply(this,arguments);return void 0===s?this:s}}}),require.register("chai/lib/chai/utils/addProperty.js",function(t,e){e.exports=function(t,e,n){Object.defineProperty(t,e,{get:function(){var t=n.call(this);return void 0===t?this:t},configurable:!0})}}),require.register("chai/lib/chai/utils/flag.js",function(t,e){e.exports=function(t,e,n){var r=t.__flags||(t.__flags=Object.create(null));return 3!==arguments.length?r[e]:void(r[e]=n)}}),require.register("chai/lib/chai/utils/getActual.js",function(t,e){e.exports=function(t,e){return e.length>4?e[4]:t._obj}}),require.register("chai/lib/chai/utils/getEnumerableProperties.js",function(t,e){e.exports=function(t){var e=[];for(var n in t)e.push(n);return e}}),require.register("chai/lib/chai/utils/getMessage.js",function(t,e){var n=require("chai/lib/chai/utils/flag.js"),r=require("chai/lib/chai/utils/getActual.js"),i=(require("chai/lib/chai/utils/inspect.js"),require("chai/lib/chai/utils/objDisplay.js"));e.exports=function(t,e){var o=n(t,"negate"),s=n(t,"object"),a=e[3],u=r(t,e),c=o?e[2]:e[1],l=n(t,"message");return"function"==typeof c&&(c=c()),c=c||"",c=c.replace(/#{this}/g,i(s)).replace(/#{act}/g,i(u)).replace(/#{exp}/g,i(a)),l?l+": "+c:c}}),require.register("chai/lib/chai/utils/getName.js",function(t,e){e.exports=function(t){if(t.name)return t.name;var e=/^\s?function ([^(]*)\(/.exec(t);return e&&e[1]?e[1]:""}}),require.register("chai/lib/chai/utils/getPathValue.js",function(t,e){function n(t){var e=t.replace(/\[/g,".["),n=e.match(/(\\\.|[^.]+?)+/g);return n.map(function(t){var e=/\[(\d+)\]$/,n=e.exec(t);return n?{i:parseFloat(n[1])}:{p:t}})}function r(t,e){for(var n,r=e,i=0,o=t.length;o>i;i++){var s=t[i];r?("undefined"!=typeof s.p?r=r[s.p]:"undefined"!=typeof s.i&&(r=r[s.i]),i==o-1&&(n=r)):n=void 0}return n}e.exports=function(t,e){var i=n(t);return r(i,e)}}),require.register("chai/lib/chai/utils/getProperties.js",function(t,e){e.exports=function(){function t(t){-1===e.indexOf(t)&&e.push(t)}for(var e=Object.getOwnPropertyNames(subject),n=Object.getPrototypeOf(subject);null!==n;)Object.getOwnPropertyNames(n).forEach(t),n=Object.getPrototypeOf(n);return e}}),require.register("chai/lib/chai/utils/index.js",function(t,e){var t=e.exports={};t.test=require("chai/lib/chai/utils/test.js"),t.type=require("chai/lib/chai/utils/type.js"),t.getMessage=require("chai/lib/chai/utils/getMessage.js"),t.getActual=require("chai/lib/chai/utils/getActual.js"),t.inspect=require("chai/lib/chai/utils/inspect.js"),t.objDisplay=require("chai/lib/chai/utils/objDisplay.js"),t.flag=require("chai/lib/chai/utils/flag.js"),t.transferFlags=require("chai/lib/chai/utils/transferFlags.js"),t.eql=require("chaijs~deep-eql@0.1.3"),t.getPathValue=require("chai/lib/chai/utils/getPathValue.js"),t.getName=require("chai/lib/chai/utils/getName.js"),t.addProperty=require("chai/lib/chai/utils/addProperty.js"),t.addMethod=require("chai/lib/chai/utils/addMethod.js"),t.overwriteProperty=require("chai/lib/chai/utils/overwriteProperty.js"),t.overwriteMethod=require("chai/lib/chai/utils/overwriteMethod.js"),t.addChainableMethod=require("chai/lib/chai/utils/addChainableMethod.js"),t.overwriteChainableMethod=require("chai/lib/chai/utils/overwriteChainableMethod.js")}),require.register("chai/lib/chai/utils/inspect.js",function(t,e){function n(t,e,n){var i={showHidden:e,seen:[],stylize:function(t){return t}};return r(i,t,"undefined"==typeof n?2:n)}function r(e,n,p){if(n&&"function"==typeof n.inspect&&n.inspect!==t.inspect&&(!n.constructor||n.constructor.prototype!==n)){var b=n.inspect(p);return"string"!=typeof b&&(b=r(e,b,p)),b}var y=i(e,n);if(y)return y;if(m(n)){if("outerHTML"in n)return n.outerHTML;try{if(document.xmlVersion){var w=new XMLSerializer;return w.serializeToString(n)}var x="http://www.w3.org/1999/xhtml",j=document.createElementNS(x,"_");return j.appendChild(n.cloneNode(!1)),html=j.innerHTML.replace("><",">"+n.innerHTML+"<"),j.innerHTML="",html}catch(E){}}var T=v(n),k=e.showHidden?g(n):T;if(0===k.length||f(n)&&(1===k.length&&"stack"===k[0]||2===k.length&&"description"===k[0]&&"stack"===k[1])){if("function"==typeof n){var q=d(n),_=q?": "+q:"";return e.stylize("[Function"+_+"]","special")}if(l(n))return e.stylize(RegExp.prototype.toString.call(n),"regexp");if(h(n))return e.stylize(Date.prototype.toUTCString.call(n),"date");if(f(n))return o(n)}var O="",S=!1,A=["{","}"];if(c(n)&&(S=!0,A=["[","]"]),"function"==typeof n){var q=d(n),_=q?": "+q:"";O=" [Function"+_+"]"}if(l(n)&&(O=" "+RegExp.prototype.toString.call(n)),h(n)&&(O=" "+Date.prototype.toUTCString.call(n)),f(n))return o(n);if(0===k.length&&(!S||0==n.length))return A[0]+O+A[1];if(0>p)return l(n)?e.stylize(RegExp.prototype.toString.call(n),"regexp"):e.stylize("[Object]","special");e.seen.push(n);var M;return M=S?s(e,n,p,T,k):k.map(function(t){return a(e,n,p,T,t,S)}),e.seen.pop(),u(M,O,A)}function i(t,e){switch(typeof e){case"undefined":return t.stylize("undefined","undefined");case"string":var n="'"+JSON.stringify(e).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return t.stylize(n,"string");case"number":return 0===e&&1/e===-1/0?t.stylize("-0","number"):t.stylize(""+e,"number");case"boolean":return t.stylize(""+e,"boolean")}return null===e?t.stylize("null","null"):void 0}function o(t){return"["+Error.prototype.toString.call(t)+"]"}function s(t,e,n,r,i){for(var o=[],s=0,u=e.length;u>s;++s)o.push(Object.prototype.hasOwnProperty.call(e,String(s))?a(t,e,n,r,String(s),!0):"");return i.forEach(function(i){i.match(/^\d+$/)||o.push(a(t,e,n,r,i,!0))}),o}function a(t,e,n,i,o,s){var a,u;if(e.__lookupGetter__&&(e.__lookupGetter__(o)?u=e.__lookupSetter__(o)?t.stylize("[Getter/Setter]","special"):t.stylize("[Getter]","special"):e.__lookupSetter__(o)&&(u=t.stylize("[Setter]","special"))),i.indexOf(o)<0&&(a="["+o+"]"),u||(t.seen.indexOf(e[o])<0?(u=null===n?r(t,e[o],null):r(t,e[o],n-1),u.indexOf("\n")>-1&&(u=s?u.split("\n").map(function(t){return" "+t}).join("\n").substr(2):"\n"+u.split("\n").map(function(t){return" "+t}).join("\n"))):u=t.stylize("[Circular]","special")),"undefined"==typeof a){if(s&&o.match(/^\d+$/))return u;a=JSON.stringify(""+o),a.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(a=a.substr(1,a.length-2),a=t.stylize(a,"name")):(a=a.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),a=t.stylize(a,"string"))}return a+": "+u}function u(t,e,n){var r=0,i=t.reduce(function(t,e){return r++,e.indexOf("\n")>=0&&r++,t+e.length+1},0);return i>60?n[0]+(""===e?"":e+"\n ")+" "+t.join(",\n ")+" "+n[1]:n[0]+e+" "+t.join(", ")+" "+n[1]}function c(t){return Array.isArray(t)||"object"==typeof t&&"[object Array]"===p(t)}function l(t){return"object"==typeof t&&"[object RegExp]"===p(t)}function h(t){return"object"==typeof t&&"[object Date]"===p(t)}function f(t){return"object"==typeof t&&"[object Error]"===p(t)}function p(t){return Object.prototype.toString.call(t)}var d=require("chai/lib/chai/utils/getName.js"),g=require("chai/lib/chai/utils/getProperties.js"),v=require("chai/lib/chai/utils/getEnumerableProperties.js");e.exports=n;var m=function(t){return"object"==typeof HTMLElement?t instanceof HTMLElement:t&&"object"==typeof t&&1===t.nodeType&&"string"==typeof t.nodeName}}),require.register("chai/lib/chai/utils/objDisplay.js",function(t,e){var n=require("chai/lib/chai/utils/inspect.js"),r=require("chai/lib/chai/config.js");e.exports=function(t){var e=n(t),i=Object.prototype.toString.call(t);if(r.truncateThreshold&&e.length>=r.truncateThreshold){if("[object Function]"===i)return t.name&&""!==t.name?"[Function: "+t.name+"]":"[Function]";if("[object Array]"===i)return"[ Array("+t.length+") ]";if("[object Object]"===i){var o=Object.keys(t),s=o.length>2?o.splice(0,2).join(", ")+", ...":o.join(", ");return"{ Object ("+s+") }"}return e}return e}}),require.register("chai/lib/chai/utils/overwriteMethod.js",function(t,e){e.exports=function(t,e,n){var r=t[e],i=function(){return this};r&&"function"==typeof r&&(i=r),t[e]=function(){var t=n(i).apply(this,arguments);return void 0===t?this:t}}}),require.register("chai/lib/chai/utils/overwriteProperty.js",function(t,e){e.exports=function(t,e,n){var r=Object.getOwnPropertyDescriptor(t,e),i=function(){};r&&"function"==typeof r.get&&(i=r.get),Object.defineProperty(t,e,{get:function(){var t=n(i).call(this);return void 0===t?this:t},configurable:!0})}}),require.register("chai/lib/chai/utils/overwriteChainableMethod.js",function(t,e){e.exports=function(t,e,n,r){var i=t.__methods[e],o=i.chainingBehavior;i.chainingBehavior=function(){var t=r(o).call(this);return void 0===t?this:t};var s=i.method;i.method=function(){var t=n(s).apply(this,arguments);return void 0===t?this:t}}}),require.register("chai/lib/chai/utils/test.js",function(t,e){var n=require("chai/lib/chai/utils/flag.js");e.exports=function(t,e){var r=n(t,"negate"),i=e[0];return r?!i:i}}),require.register("chai/lib/chai/utils/transferFlags.js",function(t,e){e.exports=function(t,e,n){var r=t.__flags||(t.__flags=Object.create(null));e.__flags||(e.__flags=Object.create(null)),n=3===arguments.length?n:!0;for(var i in r)(n||"object"!==i&&"ssfi"!==i&&"message"!=i)&&(e.__flags[i]=r[i])}}),require.register("chai/lib/chai/utils/type.js",function(t,e){var n={"[object Arguments]":"arguments","[object Array]":"array","[object Date]":"date","[object Function]":"function","[object Number]":"number","[object RegExp]":"regexp","[object String]":"string"};e.exports=function(t){var e=Object.prototype.toString.call(t);return n[e]?n[e]:null===t?"null":void 0===t?"undefined":t===Object(t)?"object":typeof t}}),"object"==typeof exports?module.exports=require("chai"):"function"==typeof define&&define.amd?define("chai",[],function(){return require("chai")}):(this||window).chai=require("chai")}(),function(t){function e(t,e){return f.isUndefined(e)?""+e:!f.isNumber(e)||!isNaN(e)&&isFinite(e)?f.isFunction(e)||f.isRegExp(e)?e.toString():e:e.toString()}function n(t,e){return f.isString(t)?t.length=0;o--)if(u[o]!=c[o])return!1;for(o=u.length-1;o>=0;o--)if(i=u[o],!s(t[i],e[i]))return!1;return!0}function c(t,e){return t&&e?"[object RegExp]"==Object.prototype.toString.call(e)?e.test(t):t instanceof e?!0:e.call({},t)===!0?!0:!1:!1}function l(t,e,n,r){var o;f.isString(n)&&(r=n,n=null);try{e()}catch(s){o=s}if(r=(n&&n.name?" ("+n.name+").":".")+(r?" "+r:"."),t&&!o&&i(o,n,"Missing expected exception"+r),!t&&c(o,n)&&i(o,n,"Got unwanted exception"+r),t&&o&&n&&!c(o,n)||!t&&o)throw o}"undefined"==typeof t.exports&&(t.exports=t);var h=Object.create||function(t){function e(){}if(!t)throw Error("no type");return e.prototype=t,new e},f={inherits:function(t,e){t.super_=e,t.prototype=h(e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}})},isArray:function(t){return Array.isArray(t)},isBoolean:function(t){return"boolean"==typeof t},isNull:function(t){return null===t},isNullOrUndefined:function(t){return null==t},isNumber:function(t){return"number"==typeof t},isString:function(t){return"string"==typeof t},isSymbol:function(t){return"symbol"==typeof t},isUndefined:function(t){return void 0===t},isRegExp:function(t){return f.isObject(t)&&"[object RegExp]"===f.objectToString(t)},isObject:function(t){return"object"==typeof t&&null!==t},isDate:function(t){return f.isObject(t)&&"[object Date]"===f.objectToString(t)},isError:function(t){return isObject(t)&&("[object Error]"===objectToString(t)||t instanceof Error)},isFunction:function(t){return"function"==typeof t},isPrimitive:function(t){return null===t||"boolean"==typeof t||"number"==typeof t||"string"==typeof t||"symbol"==typeof t||"undefined"==typeof t},objectToString:function(t){return Object.prototype.toString.call(t)}},p=Array.prototype.slice,d=("function"==typeof Object.keys?Object.keys:function(t){var e=[];for(var n in t)e.push(n);return e},t.exports=o);d.AssertionError=function(t){this.name="AssertionError",this.actual=t.actual,this.expected=t.expected,this.operator=t.operator,t.message?(this.message=t.message,this.generatedMessage=!1):(this.message=r(this),this.generatedMessage=!0);var e=t.stackStartFunction||i;if(Error.captureStackTrace)Error.captureStackTrace(this,e);else try{this.stack=(new Error).stack.toString()}catch(n){}},f.inherits(d.AssertionError,Error),d.fail=i,d.ok=o,d.equal=function(t,e,n){t!=e&&i(t,e,n,"==",d.equal)},d.notEqual=function(t,e,n){t==e&&i(t,e,n,"!=",d.notEqual)},d.deepEqual=function(t,e,n){s(t,e)||i(t,e,n,"deepEqual",d.deepEqual)},d.notDeepEqual=function(t,e,n){s(t,e)&&i(t,e,n,"notDeepEqual",d.notDeepEqual)},d.strictEqual=function(t,e,n){t!==e&&i(t,e,n,"===",d.strictEqual)},d.notStrictEqual=function(t,e,n){t===e&&i(t,e,n,"!==",d.notStrictEqual)},d.throws=function(){l.apply(this,[!0].concat(p.call(arguments)))},d.doesNotThrow=function(){l.apply(this,[!1].concat(p.call(arguments)))},d.ifError=function(t){if(t)throw t},t.assert=t.exports,delete t.exports}(this);var div=document.createElement("div");div.id="mocha",document.body.appendChild(div);var g_quiet=process.env.quiet||!0;if("false"===g_quiet&&(g_quiet=!1),!g_quiet){var link=document.createElement("link");link.setAttribute("rel","stylesheet"),link.setAttribute("type","text/css"),link.setAttribute("href","../res/mocha.css"),document.getElementsByTagName("head")[0].appendChild(link)}var fs=require("fs"),path=require("path"),net=require("net"),spawn=require("child_process").spawn,createTCPServer=function(t,e){var n=net.createServer();return e&&(process.exit=function(){n.close(),process.quit()}),t=t||13013,n.on("error",function(t){console.error("Failed to launch TCP server: "+t.code)}),n.listen(t),n},connectToTCPServer=function(t,e,n){t=t||13013;var r=net.connect({port:t});return r.setEncoding("utf8"),e&&r.write(JSON.stringify(e)),n||(r.end(),r=void 0),r},spawnChildProcess=function(t){var e=spawn(process.execPath,[t]);return e};mocha.setup({ui:"bdd"});var outputAsXML=function(){var t=process.env.out_dir;t||(t=fs.realpathSync("."),console.log("====Test results will be at: "+t));var e=0;if(window.XMLSerializer){var n=new XMLSerializer,r=process.cwd();r=r.substring(r.lastIndexOf(path.sep)+1);var i=path.join(t,r+".xml"),o=(document.getElementById("mocha-report"),document.getElementById("mocha")),s=n.serializeToString(o);s=decodeURIComponent(s),fs.writeFileSync(i,s)}var a=document.getElementById("mocha-stats"),u=a.getElementsByClassName("failures"),c=void 0,l="";return u&&u.length>0&&(c=u[0].getElementsByTagName("em"),c&&c.length>0&&(l=c[0].innerText,"0"!==l&&(e=1))),g_quiet&&process.exit(e),0};window.assertRunningResult=function(t,e){describe("Assert test running result",function(){it("the test should succeed",function(n){t?n():n("Assertion failed: "+e)})})},window.addEventListener("load",function(){mocha.run(),mocha.suite.afterAll(function(){outputAsXML()})}); \ No newline at end of file diff --git a/tests/automation/res/util.js b/tests/automation/res/util.js new file mode 100644 index 0000000000..c916bd17e4 --- /dev/null +++ b/tests/automation/res/util.js @@ -0,0 +1,124 @@ +var div = document.createElement('div'); +div.id = 'mocha'; +//div.style.visibility = 'hidden'; +document.body.appendChild(div); + +// useful when need to keep app stay on window: +// $ quiet=false nw +// +var g_quiet = process.env['quiet'] || true; +if (g_quiet === 'false') g_quiet = false; + +if (!g_quiet) { + var link = document.createElement('link'); + link.setAttribute('rel', 'stylesheet'); + link.setAttribute('type', 'text/css'); + link.setAttribute('href', '../res/mocha.css'); + document.getElementsByTagName('head')[0].appendChild(link); +} + +var fs = require('fs'); +var path = require('path'); +var net = require('net'); +var spawn = require('child_process').spawn; + +var createTCPServer = function(port, autoClose) { + var server = net.createServer(); + if (autoClose) { + process.exit = function() { + server.close(); + process.quit(); + }; + } + port = port || 13013; + server.on('error', function(e) { + console.error('Failed to launch TCP server: '+ e.code); + }); + server.listen(port); + return server; +}; + +var connectToTCPServer = function(port, data, keepOpen) { + port = port || 13013; + var socket = net.connect({port: port}); + socket.setEncoding('utf8'); + if (data) { + socket.write(JSON.stringify(data)); + } + if (!keepOpen) { + socket.end(); + socket = undefined; + } + return socket; +}; + +var spawnChildProcess = function(appPath) { + var child = spawn(process.execPath, [appPath]); + return child; +}; + +mocha.setup({ui:'bdd'}); + +var outputAsXML = function() { + var outputDir = process.env['out_dir']; + if (!outputDir) { + outputDir = fs.realpathSync('.'); + console.log('====Test results will be at: ' + outputDir); + //return; + } + var ret = 0; + if (window.XMLSerializer) { + var xmlParser = new XMLSerializer(); + var appName = process.cwd(); + appName = appName.substring(appName.lastIndexOf(path.sep) + 1); + var outputPath = path.join(outputDir, appName + '.xml'); + var reportNode = document.getElementById('mocha-report'); + var mochaNode = document.getElementById('mocha'); + var data = xmlParser.serializeToString(mochaNode); //(reportNode); + //data = data.replace('/', '//'); + //data = data.replace('%20', ' '); + data = decodeURIComponent(data); + fs.writeFileSync(outputPath, data); + } + + // check failures + var stats = document.getElementById('mocha-stats'); + var failureNodes = stats.getElementsByClassName('failures'); + var failureNumNode = undefined; + var failures = ''; + + if (failureNodes && failureNodes.length > 0) { + failureNumNode = failureNodes[0].getElementsByTagName('em'); + if (failureNumNode && failureNumNode.length > 0) { + failures = failureNumNode[0].innerText; + if (failures !== '0') { + ret = 1; + } + } + } + + if (g_quiet) + process.exit(ret); + + return 0; +}; + +window.assertRunningResult = function(result, errMsg) { + + describe('Assert test running result', function() { + it('the test should succeed', function(done){ + if (result) { + done(); + } else { + done('Assertion failed: ' + errMsg); + } + }); + }); +}; + +window.addEventListener('load', function(){ + mocha.run(); + mocha.suite.afterAll(function(){ + outputAsXML(); + }); +}); diff --git a/tests/automation/run.sh b/tests/automation/run.sh new file mode 100755 index 0000000000..1f39745cfa --- /dev/null +++ b/tests/automation/run.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +node mocha_test.js + diff --git a/tests/automation/sample/index.html b/tests/automation/sample/index.html new file mode 100644 index 0000000000..8f9c77e535 --- /dev/null +++ b/tests/automation/sample/index.html @@ -0,0 +1,18 @@ + + + + +hi + + + + + diff --git a/tests/automation/sample/mocha_test.js b/tests/automation/sample/mocha_test.js new file mode 100644 index 0000000000..2581ad091a --- /dev/null +++ b/tests/automation/sample/mocha_test.js @@ -0,0 +1,32 @@ +var assert = require('assert'); + +describe('sample-test', function() { + + beforeEach(function() { + console.log('before each'); + }); + + before(function() { + console.log('before'); + //assert.ok(false); + }); + + describe('#indexOf()', function() { // unit test group 1 + it('should return -1 when value is not presenttt', function() { // unit test 1.1 + assert.equal(-1, [1, 2, 3].indexOf(5)); + assert.equal(-1, [1, 2, 3].indexOf(6)); + assert.equal(1, [1, 2, 3].indexOf(2)); + }); + }); + + describe('#length', function() { // unit test group 2 + it('should return 3', function() { // unit test 2.1 + assert.equal(3, [1,2,3].length); + }); + it('should return 0', function() { // unit test 2.2 + assert.equal(0, ''.length); + }); + + }); + +}); diff --git a/tests/automation/sample/package.json b/tests/automation/sample/package.json new file mode 100644 index 0000000000..8748842da0 --- /dev/null +++ b/tests/automation/sample/package.json @@ -0,0 +1,4 @@ +{ +"name": "nw-#1236", +"main": "index.html" +} diff --git a/tests/automation/save_devtools_settings/index.html b/tests/automation/save_devtools_settings/index.html new file mode 100644 index 0000000000..ec903a852d --- /dev/null +++ b/tests/automation/save_devtools_settings/index.html @@ -0,0 +1,16 @@ + + + + + save devtools setting test + + +

              save devtools setting test

              + + + + + + + + diff --git a/tests/automation/save_devtools_settings/internal/index.html b/tests/automation/save_devtools_settings/internal/index.html new file mode 100644 index 0000000000..58a0bd79c6 --- /dev/null +++ b/tests/automation/save_devtools_settings/internal/index.html @@ -0,0 +1,183 @@ + + + + + Test case for save devtools settings + + +

              Please wait to be closed.

              + + + + diff --git a/tests/automation/save_devtools_settings/internal/package.json b/tests/automation/save_devtools_settings/internal/package.json new file mode 100644 index 0000000000..095e2476d4 --- /dev/null +++ b/tests/automation/save_devtools_settings/internal/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw-save-devtools-settings-test", + "main": "index.html" +} diff --git a/tests/automation/save_devtools_settings/mocha_test.js b/tests/automation/save_devtools_settings/mocha_test.js new file mode 100644 index 0000000000..e83a72cb4d --- /dev/null +++ b/tests/automation/save_devtools_settings/mocha_test.js @@ -0,0 +1,120 @@ +var path = require('path'); +var assert = require('assert'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + + +var original; +var changed; +var ok = false; +var server, child; + +var spawnChild = function(action) { + return spawn(process.execPath, [path.join(curDir, 'internal'), action]); +}; + +function read_changed(done) { + if (!ok) { + setTimeout(read_changed, 2000, done); + } else { + var tmpChild = spawnChild(0); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + changed = data; + tmpChild.kill(); + done(); + console.log("secnond"); + }); + }); + + } +} + +describe('save_devtools_settings', function() { + + before(function(done) { + this.timeout(0); + server = createTCPServer(13013); + child = spawnChild(1); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + result = JSON.parse(data); + original = data; + ok = true; + child.kill(); + done(); + console.log("first"); + }); + }); + + setTimeout(read_changed, 2000, done); + + }); + + after(function () { + server.close(); + }); + + + it("should save devtools' settings", function() { + var i = 0; + // general + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.equal((original[i] + 1) % 4, changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + + assert.equal((original[i] + 1) % 4, changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + if (!original[i - 1]) + assert.equal((original[i] + 1) % 10, changed[i]); + i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + + // overrides + assert.notEqual(original[i], changed[i]); i++; + if (!original[i - 1]) + // 0~21 + assert.equal((original[i] + 1) % 22, changed[i]); + i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + if (!original[i - 1]) { + assert.equal((original[i] + 1) % 10, changed[i]); i++; + assert.equal((original[i] + 1) % 10, changed[i]); i++; + } + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + if (!original[i - 1]) { + assert.equal((original[i] + 1) % 10, changed[i]); i++; + assert.equal((original[i] + 1) % 10, changed[i]); i++; + assert.equal((original[i] + 1) % 10, changed[i]); i++; + } + assert.notEqual(original[i], changed[i]); i++; + assert.notEqual(original[i], changed[i]); i++; + if (!original[i - 1]) + assert.equal((original[i] + 1) % 9, changed[i]); + i++; + + }); + +}); + + diff --git a/tests/automation/save_devtools_settings/package.json b/tests/automation/save_devtools_settings/package.json new file mode 100644 index 0000000000..9a89f16732 --- /dev/null +++ b/tests/automation/save_devtools_settings/package.json @@ -0,0 +1,5 @@ +{ + "name":"save_devtools_settings_wrapper", + "main":"index.html" +} + diff --git a/tests/automation/shortcut/index.html b/tests/automation/shortcut/index.html new file mode 100644 index 0000000000..168044ffc5 --- /dev/null +++ b/tests/automation/shortcut/index.html @@ -0,0 +1,16 @@ + + + + + shortcut test + + +

              shortcut test

              + + + + + + + + diff --git a/tests/automation/shortcut/mocha_test.js b/tests/automation/shortcut/mocha_test.js new file mode 100644 index 0000000000..57b1a6aa53 --- /dev/null +++ b/tests/automation/shortcut/mocha_test.js @@ -0,0 +1,110 @@ +var gui = require('nw.gui'); +var assert = require('assert'); +require('should'); + +describe('Shortcut', function() { + describe('.key', function() { + it('should be undefined if no key specified.', function() { + var shortcut; + try { + shortcut = new gui.Shortcut(); + } catch (err) {} + assert.equal(shortcut, undefined); + }); + + it('should be an object if key specified.', function() { + var shortcut; + try { + shortcut = new gui.Shortcut({"key": "Alt+Shift+S"}); + } catch (err) {} + + shortcut.should.be.a.Object; + assert.equal(shortcut.key, "Alt+Shift+S"); + }); + }); + + describe('.active', function() { + it('should be undefined if active is not an function object.', function() { + var shortcut; + try { + shortcut = new gui.Shortcut({key: 'Alt+Shift+S', active: "foo"}); + } catch(err) {} + + assert.equal(shortcut, undefined); + }); + + it('should be an object if "key" and "active" specified.', function() { + var onActive = function() {}; + var shortcut = new gui.Shortcut({key: 'Alt+Shift+S', active: onActive}); + + shortcut.should.be.a.Object; + assert.equal(shortcut.active, onActive); + }); + }); + + describe('.failed', function() { + it('should be undefined if "failed" is not a function object.', function() { + var shortcut; + try { + shortcut = new gui.Shortcut({key: 'Alt+Shift+S', failed: "foo"}); + } catch(err) {} + + assert.equal(shortcut, undefined); + }); + + it('should be an object if "key" and "failed" specified.', function() { + var onFailed = function() {}; + var shortcut = new gui.Shortcut({key: 'Alt+Shift+S', failed: onFailed}); + + shortcut.should.be.a.Object; + assert.equal(shortcut.failed, onFailed); + }); + }); +}); + +describe('App.registerGlobalHotKey', function() { + it('should register failed if the parameter is not an Shortcut object.', + function(done) { + var object = new Object(); + try { + gui.App.registerGlobalHotKey(object); + } catch(err) { + done(); + } + } + ); + + it('should register failed if the same key has been registered.', + function(done) { + var shortcut = new gui.Shortcut({key: "Alt+Shift+S"}); + shortcut.failed = function(msg) { + assert.equal(msg, "Register global desktop keyboard shortcut failed."); + done(); + }; + + gui.App.registerGlobalHotKey(shortcut); + gui.App.registerGlobalHotKey(shortcut); + } + ); + + it('should register failed for invalid key.', function(done) { + var shortcut = new gui.Shortcut({key: "foo"}); + shortcut.failed = function(msg) { + done(); + } + gui.App.registerGlobalHotKey(shortcut); + }); +}); + +describe('App.unregisterGlobalHotKey', function() { + it('should unregister failed if the parameter is not an Shortcut object.', + function(done) { + var object = new Object(); + try { + gui.App.unregisterGlobalHotKey(object); + } catch(err) { + done(); + } + } + ); +}); diff --git a/tests/automation/shortcut/package.json b/tests/automation/shortcut/package.json new file mode 100644 index 0000000000..a055ed9650 --- /dev/null +++ b/tests/automation/shortcut/package.json @@ -0,0 +1,5 @@ +{ + "name":"shortcut_wrapper", + "main":"index.html" +} + diff --git a/tests/automation/show_devtool_after_http_server_created_in_node_main/index.html b/tests/automation/show_devtool_after_http_server_created_in_node_main/index.html new file mode 100644 index 0000000000..46a598e9c2 --- /dev/null +++ b/tests/automation/show_devtool_after_http_server_created_in_node_main/index.html @@ -0,0 +1,49 @@ + + + + + +

              nw rocks !

              +

              after launch devtool, nw should not close by itself.

              + + + + diff --git a/tests/automation/show_devtool_after_http_server_created_in_node_main/main.js b/tests/automation/show_devtool_after_http_server_created_in_node_main/main.js new file mode 100644 index 0000000000..ce28ff5e7a --- /dev/null +++ b/tests/automation/show_devtool_after_http_server_created_in_node_main/main.js @@ -0,0 +1,7 @@ +var http = require('http'); +http.createServer(function (req, res) { + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end('Hello World\n'); +}).listen(11337, '127.0.0.1'); + +console.log('Server running at http://127.0.0.1:1337/'); diff --git a/tests/automation/show_devtool_after_http_server_created_in_node_main/package.json b/tests/automation/show_devtool_after_http_server_created_in_node_main/package.json new file mode 100644 index 0000000000..d88d875b8e --- /dev/null +++ b/tests/automation/show_devtool_after_http_server_created_in_node_main/package.json @@ -0,0 +1,14 @@ +{ + "name": "server", + "description": "server", + "version": "0.1", + "nodejs":true, + "node-main": "main.js", + "main": "index.html", + "window": { + "show": true, + "toolbar": true, + "fullscreen":false + + } +} diff --git a/tests/automation/single_instance/index.html b/tests/automation/single_instance/index.html new file mode 100644 index 0000000000..dec432a698 --- /dev/null +++ b/tests/automation/single_instance/index.html @@ -0,0 +1,16 @@ + + + + + single instance test + + +

              single instance test

              + + + + + + + + diff --git a/tests/automation/single_instance/mocha_test.js b/tests/automation/single_instance/mocha_test.js new file mode 100644 index 0000000000..fafc932280 --- /dev/null +++ b/tests/automation/single_instance/mocha_test.js @@ -0,0 +1,188 @@ +var path = require('path'); +var os = require('os'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + +var func = require(path.join(curDir, '..', 'start_app', 'script.js')); +var execPath = func.getExecPath(); + +var spawn = require('child_process').spawn; +var exec = require('child_process').exec; + +var app = new Array(); +var child1, child2; +var mac_app_path = path.join(curDir, 'tmp-nw', 'node-webkit.app'); + +function make_execuable_file(folder_path, done) { + func.copyExecFiles(function() { + func.copySourceFiles(folder_path); + func.zipSourceFiles(function() { + func.makeExecuableFile(); + if (os.platform() == 'darwin') { + var app_path = 'tmp-nw/node-webkit.app/Contents/Resources/app.nw'; + fs.mkdir(app_path, function(err) { + if(err && err.code !== 'EEXIST') throw err + fs.copy('tmp-nw/index.html', path.join(app_path, 'index.html')); + fs.copy('tmp-nw/package.html', path.join(app_path, 'package.html')); + }); + } + done(); + }); + }); +} + +function check_have(i, cmd, options, msg, last, done) { + var result = false; + app[i] = spawn(cmd, options); + app[i].on('exit', function() { + result = true; + }); + + if (last == 2) { + setTimeout(function() { + if (result) { + done(); + } else { + done(msg); + } + app[i].kill(); + app[i - 1].kill(); + }, 3000); + } else { + setTimeout(function() { + if (result) { + done(msg); + } else { + done(); + } + if (last == 1) { + app[i].kill(); + app[i - 1].kill(); + } + }, 3000); + } +} + +describe('single-instance', function() { + this.timeout(0); + +/////// 1 + describe('single-instance false', function() { + before(function(done) { + make_execuable_file(path.join(curDir, 'mul'), done); + }); + + after(function() { + fs.remove(path.join(curDir, 'tmp-nw'), function (er) { + if (er) throw er; + }); + }); + + it('should have a instance', function(done) { + check_have(0, execPath, "", 'not have a instance', 0, done); + }); + + it('should have a second instance', function(done) { + check_have(1, execPath, "", 'not have a second instance', 1, done); + }); + }); + +//////////////// 2 + describe('single-instance default', function() { + + before(function(done) { + setTimeout(function() { + make_execuable_file(path.join(curDir, 'single'), done); + }, 3000); + }); + + after(function() { + fs.remove(path.join(curDir, 'tmp-nw'), function (er) { + if (er) throw er; + }); + }); + + it('should have a instance', function(done) { + check_have(2, execPath, "", 'not have a instance', 0, done); + }); + + it('should not have a second instance', function(done) { + check_have(3, execPath, "", 'have a second instance', 2, done); + }); + }); + +/////////////////////// 3 + if (os.platform() == 'darwin') { + describe('single-instance false(open app)', function(){ + before(function(done) { + setTimeout(function() { + make_execuable_file('single_instance/open_mul', done); + }, 3000); + }); + + after(function() { + fs.remove('tmp-nw', function (er) { + if (er) throw er; + }); + }); + + it('should have a instance (open app)', function(done) { + child1 = exec('open ' + mac_app_path); + setTimeout(function () { + var content = fs.readFileSync('tmp-nw/msg'); + if (content + "" != "") + done(); + else + done("not have a instance"); + }, 6000); + }); + + it('should not have a second instance (open app)', function(done) { + child2 = exec('open ' + mac_app_path); + var content = fs.readFileSync('tmp-nw/msg'); + if (content + "" == "11") + done("have a second instance"); + else + done(); + }); + + }); + + describe('single-instance default(open app)', function(){ + before(function(done) { + setTimeout(function() { + make_execuable_file('single_instance/open_single', done); + }, 3000); + }); + + after(function() { + fs.remove('tmp-nw', function (er) { + if (er) throw er; + }); + }); + + it('should have a instance (open app)', function(done) { + child1 = exec('open ' + mac_app_path); + setTimeout(function () { + var content = fs.readFileSync('tmp-nw/msg_s'); + if (content + "" != "") + done(); + else + done("not have a instance"); + }, 6000); + }); + + it('should not have a second instance (open app)', function(done) { + child2 = exec('open ' + mac_app_path); + var content = fs.readFileSync('tmp-nw/msg_s'); + if (content + "" == "11") + done("have a second instance"); + else + done(); + + }); + }); + } +}); + + diff --git a/tests/automation/single_instance/mul/index.html b/tests/automation/single_instance/mul/index.html new file mode 100644 index 0000000000..c432c370d3 --- /dev/null +++ b/tests/automation/single_instance/mul/index.html @@ -0,0 +1,10 @@ + + + + Mul-instance + + +

              Mul-instance

              + We are using node.js + + diff --git a/tests/automation/single_instance/mul/package.json b/tests/automation/single_instance/mul/package.json new file mode 100644 index 0000000000..0a341d73b3 --- /dev/null +++ b/tests/automation/single_instance/mul/package.json @@ -0,0 +1,6 @@ +{ + "name": "nw-single-sinstance", + "main": "index.html", + "single-instance": false +} + diff --git a/tests/automation/single_instance/open_mul/index.html b/tests/automation/single_instance/open_mul/index.html new file mode 100644 index 0000000000..b365a2557b --- /dev/null +++ b/tests/automation/single_instance/open_mul/index.html @@ -0,0 +1,19 @@ + + + + open Mul-instance + + +

              open Mul-instance

              + We are using node.js + + + diff --git a/tests/automation/single_instance/open_mul/package.json b/tests/automation/single_instance/open_mul/package.json new file mode 100644 index 0000000000..d00691e29a --- /dev/null +++ b/tests/automation/single_instance/open_mul/package.json @@ -0,0 +1,5 @@ +{ + "name": "nw-single-sinstance", + "main": "index.html", + "single-instance": false +} diff --git a/tests/automation/single_instance/open_single/index.html b/tests/automation/single_instance/open_single/index.html new file mode 100644 index 0000000000..fdadd50e9d --- /dev/null +++ b/tests/automation/single_instance/open_single/index.html @@ -0,0 +1,18 @@ + + + + open Single-instance + + +

              open Single-instance

              + We are using node.js + + + diff --git a/tests/automation/single_instance/open_single/package.json b/tests/automation/single_instance/open_single/package.json new file mode 100644 index 0000000000..e5e3247df4 --- /dev/null +++ b/tests/automation/single_instance/open_single/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw-single-sinstance", + "main": "index.html" +} diff --git a/tests/automation/single_instance/package.json b/tests/automation/single_instance/package.json new file mode 100644 index 0000000000..546ab42270 --- /dev/null +++ b/tests/automation/single_instance/package.json @@ -0,0 +1,5 @@ +{ + "name":"single_instance_wrapper", + "main":"index.html" +} + diff --git a/tests/automation/single_instance/single/index.html b/tests/automation/single_instance/single/index.html new file mode 100644 index 0000000000..8281ed49db --- /dev/null +++ b/tests/automation/single_instance/single/index.html @@ -0,0 +1,10 @@ + + + + Single-instance + + +

              Single-instance

              + We are using node.js + + diff --git a/tests/automation/single_instance/single/package.json b/tests/automation/single_instance/single/package.json new file mode 100644 index 0000000000..0fb97dc071 --- /dev/null +++ b/tests/automation/single_instance/single/package.json @@ -0,0 +1,5 @@ +{ + "name": "nw-single-sinstance", + "main": "index.html" +} + diff --git a/tests/automation/snapshot/index.html b/tests/automation/snapshot/index.html new file mode 100644 index 0000000000..07632e0abd --- /dev/null +++ b/tests/automation/snapshot/index.html @@ -0,0 +1,16 @@ + + + + + snapshort test + + +

              snapshort test

              + + + + + + + + diff --git a/tests/automation/snapshot/internal/1266-snapshot-crash-start/file_to_snapshot_to_app.bin.js b/tests/automation/snapshot/internal/1266-snapshot-crash-start/file_to_snapshot_to_app.bin.js new file mode 100755 index 0000000000..cda251772f --- /dev/null +++ b/tests/automation/snapshot/internal/1266-snapshot-crash-start/file_to_snapshot_to_app.bin.js @@ -0,0 +1,11 @@ +var sampleFunction; + +(function() +{ + var privateVar = 'private'; + + sampleFunction = function() + { + return privateVar+'67868'; + }; +})(); diff --git a/tests/automation/snapshot/internal/1266-snapshot-crash-start/index.html b/tests/automation/snapshot/internal/1266-snapshot-crash-start/index.html new file mode 100755 index 0000000000..e6d9329a9e --- /dev/null +++ b/tests/automation/snapshot/internal/1266-snapshot-crash-start/index.html @@ -0,0 +1,26 @@ + + + + + snapshot test + + + +
              Data...
              + + + + + diff --git a/tests/automation/snapshot/internal/1266-snapshot-crash-start/package.json b/tests/automation/snapshot/internal/1266-snapshot-crash-start/package.json new file mode 100755 index 0000000000..10d863b9ea --- /dev/null +++ b/tests/automation/snapshot/internal/1266-snapshot-crash-start/package.json @@ -0,0 +1,5 @@ +{ + "name": "nw-demo", + "main": "index.html", + "snapshot" : "app.bin" +} diff --git a/tests/automation/snapshot/internal/1266-snapshot-crash-start/script.js b/tests/automation/snapshot/internal/1266-snapshot-crash-start/script.js new file mode 100755 index 0000000000..1b9218f143 --- /dev/null +++ b/tests/automation/snapshot/internal/1266-snapshot-crash-start/script.js @@ -0,0 +1,4 @@ +$(document).ready(function() +{ + $('#content').html(sampleFunction()); +}); \ No newline at end of file diff --git a/tests/automation/snapshot/internal/index.html b/tests/automation/snapshot/internal/index.html new file mode 100644 index 0000000000..155bfddea2 --- /dev/null +++ b/tests/automation/snapshot/internal/index.html @@ -0,0 +1,10 @@ + + snapshot demo + + + + + + diff --git a/tests/automation/snapshot/internal/mytest.js b/tests/automation/snapshot/internal/mytest.js new file mode 100644 index 0000000000..df8095f254 --- /dev/null +++ b/tests/automation/snapshot/internal/mytest.js @@ -0,0 +1,12 @@ +function mytest(a) { + document.write(a + 42); + + var gui = require('nw.gui'); + var result = { ok: true }; + + var socket = require('net').connect({port: 13013}); + socket.setEncoding('utf8'); + socket.end(JSON.stringify(result)); + + +} diff --git a/tests/automation/snapshot/internal/package.json b/tests/automation/snapshot/internal/package.json new file mode 100644 index 0000000000..694c4bf933 --- /dev/null +++ b/tests/automation/snapshot/internal/package.json @@ -0,0 +1,5 @@ +{ + "name": "nw-demo", + "main": "index.html", + "snapshot": "mytest.bin" +} \ No newline at end of file diff --git a/tests/automation/snapshot/mocha_test.js b/tests/automation/snapshot/mocha_test.js new file mode 100644 index 0000000000..dcf5cef1dc --- /dev/null +++ b/tests/automation/snapshot/mocha_test.js @@ -0,0 +1,124 @@ +var path = require('path'); +var os = require('os'); +var fs = require('fs-extra'); +var cp = require('child_process'); +var curDir = fs.realpathSync('.'); + +var myTestJSPath = path.join(curDir, 'internal', 'mytest.js'); +var myTestBinPath = path.join(curDir, 'internal', 'mytest.bin'); + +var snapshotPath; +var server; + +describe('snapshot', function() { + +/////////// 1 + + describe('demo should work fine', function() { + before(function(done) { + var snapshotExec; + if (os.platform() == 'darwin') { + snapshotExec = '../../../../../../nwsnapshot'; + } + if (os.platform() == 'linux') { + snapshotExec = 'nwsnapshot'; + } + if (os.platform() == 'win32') { + snapshotExec = 'nwsnapshot.exe'; + } + + snapshotPath = path.join(process.execPath, '..', snapshotExec); + console.log("snapshotPath: " + snapshotPath); + + cp.execFile(snapshotPath, + ['--extra_code', myTestJSPath, myTestBinPath], + {cwd: curDir}, + function (error, stdout, stderr) { + + server = createTCPServer(13013); + + done(); + } + ); + + }); + + after(function() { + fs.unlink(myTestBinPath, function(err) {if(err && err.code !== 'ENOENT') throw err}); + fs.unlink(path.join(curDir, 'internal', 'v8.log'), function(err) {if(err && err.code !== 'ENOENT') throw err}); + server.close(); + }); + + it('the native code could be exectuted', + function(done) { + this.timeout(0); + var result = false; + + + var child = spawnChildProcess(path.join(curDir, 'internal')); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + result = true; + child.kill(); + done(); + }); + }); + + + setTimeout(function(){ + if (!result) { + done('the native code does not been executed'); + child.close(); + //child.removeConnection(); + //child.app.kill(); + } + }, 3000); + //child.app.stderr.on('data', function(d){ console.log ('app' + d);}); + + }); + }); + + +////////////////// 2 + + + describe('1266-snapshot-crash-start', function() { + before(function(done) { + cp.execFile(snapshotPath, + ['--extra_code', 'file_to_snapshot_to_app.bin.js', 'app.bin'], + {cwd: path.join(curDir, 'internal', '1266-snapshot-crash-start')}, + function (error, stdout, stderr) { + done(); + } + + ); + }); + + after(function() { + fs.unlink(path.join(curDir, 'internal','1266-snapshot-crash-start', 'app.bin'), function(err) {if(err && err.code !== 'ENOENT') throw err}); + fs.unlink(path.join(curDir, 'internal','1266-snapshot-crash-start', 'v8.log'), function(err) {if(err && err.code !== 'ENOENT') throw err}); + fs.unlink(path.join(curDir, 'internal','1266-snapshot-crash-start','tmp'), function(err) {if(err && err.code !== 'ENOENT') throw err}); + }); + + it('another demo should close nomally', function(done) { + this.timeout(0); + var ppath = process.execPath + " " + path.join(curDir, 'internal', '1266-snapshot-crash-start'); + cp.exec(ppath, function(err, stdout, stderr) { + }); + setTimeout(function(){ + fs.exists(path.join(curDir, 'internal', '1266-snapshot-crash-start', 'tmp'), function(exists) { + if (exists) + done(); + else + done('another demo fails'); + }); + }, 3000); + }); + }); + + + +}); + + diff --git a/tests/automation/snapshot/package.json b/tests/automation/snapshot/package.json new file mode 100644 index 0000000000..7fcfad8073 --- /dev/null +++ b/tests/automation/snapshot/package.json @@ -0,0 +1,5 @@ +{ + "name":"snapshort_wrapper", + "main":"index.html" +} + diff --git a/tests/automation/source-maps/index.html b/tests/automation/source-maps/index.html new file mode 100644 index 0000000000..129df6e8bc --- /dev/null +++ b/tests/automation/source-maps/index.html @@ -0,0 +1,16 @@ + + + + + source map test + + +

              source map test

              + + + + + + + + diff --git a/tests/automation/source-maps/internal/index.html b/tests/automation/source-maps/internal/index.html new file mode 100644 index 0000000000..8d22e007a0 --- /dev/null +++ b/tests/automation/source-maps/internal/index.html @@ -0,0 +1,90 @@ + + + + + Test case for source maps + + + +

              Please wait to be closed.

              + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/automation/source-maps/internal/package.json b/tests/automation/source-maps/internal/package.json new file mode 100644 index 0000000000..862ea905fc --- /dev/null +++ b/tests/automation/source-maps/internal/package.json @@ -0,0 +1,5 @@ +{ + "name": "nw-test-source-maps", + "main": "index.html", + "node-remote": "127.0.0.1" +} diff --git a/tests/automation/source-maps/internal/scripts/compiler.jar b/tests/automation/source-maps/internal/scripts/compiler.jar new file mode 100644 index 0000000000..53037b0e8a Binary files /dev/null and b/tests/automation/source-maps/internal/scripts/compiler.jar differ diff --git a/tests/automation/source-maps/internal/scripts/jquery.d.ts b/tests/automation/source-maps/internal/scripts/jquery.d.ts new file mode 100644 index 0000000000..a7c4c53892 --- /dev/null +++ b/tests/automation/source-maps/internal/scripts/jquery.d.ts @@ -0,0 +1,702 @@ +// Typing for the jQuery library, version 1.7.x + +/* + Interface for the AJAX setting that will configure the AJAX request +*/ +interface JQueryAjaxSettings { + accepts?: any; + async?: bool; + beforeSend?(jqXHR: JQueryXHR, settings: JQueryAjaxSettings); + cache?: bool; + complete?(jqXHR: JQueryXHR, textStatus: string); + contents?: { [key: string]: any; }; + contentType?: string; + context?: any; + converters?: { [key: string]: any; }; + crossDomain?: bool; + data?: any; + dataFilter?(data: any, ty: any): any; + dataType?: string; + error?(jqXHR: JQueryXHR, textStatus: string, errorThrow: string): any; + global?: bool; + headers?: { [key: any]: any; }; + ifModified?: bool; + isLocal?: bool; + jsonp?: string; + jsonpCallback?: any; + mimeType?: string; + password?: string; + processData?: bool; + scriptCharset?: string; + statusCode?: { [key: any]: any; }; + success?(data: any, textStatus: string, jqXHR: JQueryXHR); + timeout?: number; + traditional?: bool; + type?: string; + url?: string; + username?: string; + xhr?: any; + xhrFields?: { [key: any]: any; }; +} + +/* + Interface for the jqXHR object +*/ +interface JQueryXHR extends XMLHttpRequest { + overrideMimeType(); +} + +/* + Interface for the JQuery callback +*/ +interface JQueryCallback { + add(...callbacks: any[]): any; + disable(): any; + empty(): any; + fire(...arguments: any[]): any; + fired(): bool; + fireWith(context: any, ...args: any[]): any; + has(callback: any): bool; + lock(): any; + locked(): bool; + removed(...callbacks: any[]): any; +} + +/* + Interface for the JQuery promise, part of callbacks +*/ +interface JQueryPromise { + always(...alwaysCallbacks: any[]): JQueryDeferred; + done(...doneCallbacks: any[]): JQueryDeferred; + fail(...failCallbacks: any[]): JQueryDeferred; + pipe(doneFilter?: (x: any) => any, failFilter?: (x: any) => any, progressFilter?: (x: any) => any): JQueryPromise; + then(doneCallbacks: any, failCallbacks: any, progressCallbacks?: any): JQueryDeferred; +} + +/* + Interface for the JQuery deferred, part of callbacks +*/ +interface JQueryDeferred extends JQueryPromise { + notify(...args: any[]): JQueryDeferred; + notifyWith(context: any, ...args: any[]): JQueryDeferred; + + pipe(doneFilter?: any, failFilter?: any, progressFilter?: any): JQueryPromise; + progress(...progressCallbacks: any[]): JQueryDeferred; + reject(...args: any[]): JQueryDeferred; + rejectWith(context:any, ...args: any[]): JQueryDeferred; + resolve(...args: any[]): JQueryDeferred; + resolveWith(context:any, ...args: any[]): JQueryDeferred; + state(): string; + then(doneCallbacks: any, failCallbacks: any, progressCallbacks?: any): JQueryDeferred; +} + +/* + Interface of the JQuery extension of the W3C event object +*/ +interface JQueryEventObject extends Event { + data: any; + delegateTarget: Element; + isDefaultPrevented(): bool; + isImmediatePropogationStopped(): bool; + isPropogationStopped(): bool; + namespace: string; + preventDefault(): any; + relatedTarget: Element; + result: any; + stopImmediatePropagation(); + stopPropagation(); + pageX: number; + pageY: number; + which: number; + metaKey: any; +} + +/* + Collection of properties of the current browser +*/ +interface JQueryBrowserInfo { + safari:bool; + opera:bool; + msie:bool; + mozilla:bool; + version:string; +} + +interface JQuerySupport { + ajax?: bool; + boxModel?: bool; + changeBubbles?: bool; + checkClone?: bool; + checkOn?: bool; + cors?: bool; + cssFloat?: bool; + hrefNormalized?: bool; + htmlSerialize?: bool; + leadingWhitespace?: bool; + noCloneChecked?: bool; + noCloneEvent?: bool; + opacity?: bool; + optDisabled?: bool; + optSelected?: bool; + scriptEval?(): bool; + style?: bool; + submitBubbles?: bool; + tbody?: bool; +} + +/* + Static members of jQuery (those on $ and jQuery themselves) +*/ +interface JQueryStatic { + + /**** + AJAX + *****/ + ajax(url: string, settings: JQueryAjaxSettings); + + ajaxPrefilter(dataTypes: string, handler: (opts: any, originalOpts: any, jqXHR: JQueryXHR) => any): any; + ajaxPrefilter(handler: (opts: any, originalOpts: any, jqXHR: JQueryXHR) => any): any; + + ajaxSetup(options: any); + + get(url: string, data?: any, success?: any, dataType?: any): JQueryXHR; + getJSON(url: string, data?: any, success?: any): JQueryXHR; + getScript(url: string, success?: any): JQueryXHR; + + param(obj: any): string; + param(obj: any, traditional: bool): string; + + post(url: string, data?: any, success?: any, dataType?: any): JQueryXHR; + + /********* + CALLBACKS + **********/ + Callbacks(flags: any): JQueryCallback; + + /**** + CORE + *****/ + holdReady(hold: bool): any; + + (selector: string, context?: any): JQuery; + (element: Element): JQuery; + (object: { }): JQuery; + (elementArray: Element[]): JQuery; + (object: JQuery): JQuery; + (func: Function): JQuery; + (): JQuery; + + noConflict(removeAll?: bool): Object; + + when(...deferreds: any[]): JQueryPromise; + + /*** + CSS + ****/ + css(e: any, propertyName: string, value?: any); + css(e: any, propertyName: any, value?: any); + cssHooks: { [key: any]: any; }; + + /**** + DATA + *****/ + data(element: Element, key: string, value: any): Object; + + dequeue(element: Element, queueName?: string): any; + + hasData(element: Element): bool; + + queue(element: Element, queueName?: string): any[]; + queue(element: Element, queueName: string, newQueueOrCallback: any): JQuery; + + removeData(element: Element, name?: string): JQuery; + + /******* + EFFECTS + ********/ + fx: { tick: () => void; interval: number; stop: () => void; speeds: { slow: number; fast: number; }; off: bool; step: any; }; + + /****** + EVENTS + *******/ + proxy(context: any, name: any): any; + + /********* + INTERNALS + **********/ + error(message: any); + + /************* + MISCELLANEOUS + **************/ + expr: any; + fn: any; //TODO: Decide how we want to type this + isReady: bool; + + /********** + PROPERTIES + ***********/ + browser: JQueryBrowserInfo; + support: JQuerySupport; + + /********* + UTILITIES + **********/ + contains(container: Element, contained: Element): bool; + + each(collection: any, callback: (indexInArray: any, valueOfElement: any) => any): any; + + extend(target: any, ...objs: any[]): Object; + extend(deep: bool, target: any, ...objs: any[]): Object; + + globalEval(code: string): any; + + grep(array: any[], func: any, invert: bool): any[]; + + inArray(value: any, array: any[], fromIndex?: number): number; + + isArray(obj: any): bool; + isEmptyObject(obj: any): bool; + isFunction(obj: any): bool; + isNumeric(value: any): bool; + isPlainObject(obj: any): bool; + isWindow(obj: any): bool; + isXMLDoc(node: Node): bool; + + makeArray(obj: any): any[]; + + map(array: any[], callback: (elementOfArray: any, indexInArray: any) =>any): JQuery; + + merge(first: any[], second: any[]): any[]; + + noop(): any; + + now(): number; + + parseJSON(json: string): Object; + + //FIXME: This should return an XMLDocument + parseXML(data: string): any; + + queue(element: Element, queueName: string, newQueue: any[]): JQuery; + + trim(str: string): string; + + type(obj: any): string; + + unique(arr: any[]): any[]; +} + +/* + The jQuery instance members +*/ +interface JQuery { + /**** + AJAX + *****/ + ajaxComplete(handler: any): JQuery; + ajaxError(handler: (evt: any, xhr: any, opts: any) => any): JQuery; + ajaxSend(handler: (evt: any, xhr: any, opts: any) => any): JQuery; + ajaxStart(handler: () => any): JQuery; + ajaxStop(handler: () => any): JQuery; + ajaxSuccess(handler: (evt: any, xml: any, opts: any) => any): JQuery; + + load(url: string, data?: any, complete?: any): JQuery; + + serialize(): string; + serializeArray(): any[]; + + /********** + ATTRIBUTES + ***********/ + addClass(classNames: string): JQuery; + addClass(func: (index: any, currentClass: any) => JQuery); + + attr(attributeName: string): string; + attr(attributeName: string, value: any): JQuery; + attr(map: { [key: any]: any; }): JQuery; + attr(attributeName: string, func: (index: any, attr: any) => any): JQuery; + + hasClass(className: string): bool; + + html(htmlString: string): JQuery; + html(): string; + + prop(propertyName: string): string; + prop(propertyName: string, value: any): JQuery; + prop(map: any): JQuery; + prop(propertyName: string, func: (index: any, oldPropertyValue: any) => any): JQuery; + + removeAttr(attributeName: any): JQuery; + + removeClass(className?: any): JQuery; + removeClass(func: (index: any, cls: any) => any): JQuery; + + removeProp(propertyName: any): JQuery; + + toggleClass(className: any, swtch?: bool): JQuery; + toggleClass(swtch?: bool): JQuery; + toggleClass(func: (index: any, cls: any, swtch: any) => any): JQuery; + + val(): any; + val(value: string[]): JQuery; + val(value: string): JQuery; + val(func: (index: any, value: any) => any): JQuery; + + /*** + CSS + ****/ + css(propertyName: string, value?: any); + css(propertyName: any, value?: any); + + height(): number; + height(value: number): JQuery; + height(func: (index: any, height: any) => any): JQuery; + + innerHeight(): number; + innerWidth(): number; + + offset(): Object; + offset(coordinates: any): JQuery; + offset(func: (index: any, coords: any) => any): JQuery; + + outerHeight(includeMargin?: bool): number; + outerWidth(includeMargin?: bool): number; + + position(): { top: number; left: number; }; + + scrollLeft(): number; + scrollLeft(value: number): JQuery; + + scrollTop(): number; + scrollTop(value: number): JQuery; + + width(): number; + width(value: number): JQuery; + width(func: (index: any, height: any) => any): JQuery; + + /**** + DATA + *****/ + clearQueue(queueName?: string): JQuery; + + data(key: string, value: any): JQuery; + data(obj: { [key: string]: any; }): JQuery; + data(key?: string): any; + + dequeue(queueName?: string): JQuery; + + removeData(nameOrList?: any): JQuery; + + /******** + DEFERRED + *********/ + promise(type?: any, target?: any): JQueryPromise; + + /******* + EFFECTS + ********/ + animate(properties: any, duration?: any, easing?: string, complete?: Function): JQuery; + animate(properties: any, options: { duration?: any; easing?: string; complete?: Function; step?: Function; queue?: bool; specialEasing?: any; }); + + delay(duration: number, queueName?: string): JQuery; + + fadeIn(duration?: any, callback?: any): JQuery; + fadeIn(duration?: any, easing?: string, callback?: any): JQuery; + + fadeOut(duration?: any, callback?: any): JQuery; + fadeOut(duration?: any, easing?: string, callback?: any): JQuery; + + fadeTo(duration: any, opacity: number, callback?: any): JQuery; + fadeTo(duration: any, opacity: number, easing?: string, callback?: any): JQuery; + + fadeToggle(duration?: any, easing?: string, callback?: any): JQuery; + + hide(duration?: any, callback?: any): JQuery; + hide(duration?: any, easing?: string, callback?: any): JQuery; + + show(duration?: any, callback?: any): JQuery; + show(duration?: any, easing?: string, callback?: any): JQuery; + + slideDown(duration?: any, callback?: any): JQuery; + slideDown(duration?: any, easing?: string, callback?: any): JQuery; + + slideToggle(duration?: any, callback?: any): JQuery; + slideToggle(duration?: any, easing?: string, callback?: any): JQuery; + + slideUp(duration?: any, callback?: any): JQuery; + slideUp(duration?: any, easing?: string, callback?: any): JQuery; + + stop(clearQueue?: bool, jumpToEnd?: bool): JQuery; + stop(queue?:any, clearQueue?: bool, jumpToEnd?: bool): JQuery; + + toggle(duration?: any, callback?: any): JQuery; + toggle(duration?: any, easing?: string, callback?: any): JQuery; + toggle(showOrHide: bool): JQuery; + + /****** + EVENTS + *******/ + bind(eventType: string, eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + bind(eventType: string, eventData: any, preventBubble:bool): JQuery; + bind(eventType: string, preventBubble:bool): JQuery; + bind(...events: any[]); + + blur(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + blur(handler: (eventObject: JQueryEventObject) => any): JQuery; + + change(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + change(handler: (eventObject: JQueryEventObject) => any): JQuery; + + click(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + click(handler: (eventObject: JQueryEventObject) => any): JQuery; + + dblclick(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + dblclick(handler: (eventObject: JQueryEventObject) => any): JQuery; + + delegate(selector: any, eventType: string, handler: (eventObject: JQueryEventObject) => any): JQuery; + + + focus(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + focus(handler: (eventObject: JQueryEventObject) => any): JQuery; + + focusin(eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + focusin(handler: (eventObject: JQueryEventObject) => any): JQuery; + + focusout(eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + focusout(handler: (eventObject: JQueryEventObject) => any): JQuery; + + hover(handlerIn: (eventObject: JQueryEventObject) => any, handlerOut: (eventObject: JQueryEventObject) => any): JQuery; + hover(handlerInOut: (eventObject: JQueryEventObject) => any): JQuery; + + keydown(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + keydown(handler: (eventObject: JQueryEventObject) => any): JQuery; + + keypress(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + keypress(handler: (eventObject: JQueryEventObject) => any): JQuery; + + keyup(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + keyup(handler: (eventObject: JQueryEventObject) => any): JQuery; + + mousedown(eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + mousedown(handler: (eventObject: JQueryEventObject) => any): JQuery; + + mouseevent(eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + mouseevent(handler: (eventObject: JQueryEventObject) => any): JQuery; + + mouseleave(eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + mouseleave(handler: (eventObject: JQueryEventObject) => any): JQuery; + + mousemove(eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + mousemove(handler: (eventObject: JQueryEventObject) => any): JQuery; + + mouseout(eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + mouseout(handler: (eventObject: JQueryEventObject) => any): JQuery; + + mouseover(eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + mouseover(handler: (eventObject: JQueryEventObject) => any): JQuery; + + mouseup(eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + mouseup(handler: (eventObject: JQueryEventObject) => any): JQuery; + + off(events?: string, selector?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + off(eventsMap: { [key: string]: any; }, selector?: any): JQuery; + + on(events: string, selector?: any, data?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + on(eventsMap: { [key: string]: any; }, selector?: any, data?: any): JQuery; + + one(events: string, selector?: any, data?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + one(eventsMap: { [key: string]: any; }, selector?: any, data?: any): JQuery; + + ready(handler: any): JQuery; + + resize(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + resize(handler: (eventObject: JQueryEventObject) => any): JQuery; + + scroll(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + scroll(handler: (eventObject: JQueryEventObject) => any): JQuery; + + select(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + select(handler: (eventObject: JQueryEventObject) => any): JQuery; + + submit(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + submit(handler: (eventObject: JQueryEventObject) => any): JQuery; + + trigger(eventType: string, ...extraParameters: any[]): JQuery; + trigger(event: JQueryEventObject): JQuery; + + triggerHandler(eventType: string, ...extraParameters: any[]): Object; + + unbind(eventType?: string, handler?: (eventObject: JQueryEventObject) => any): JQuery; + unbind(eventType: string, fls: bool): JQuery; + unbind(evt: any): JQuery; + + undelegate(): JQuery; + undelegate(selector: any, eventType: string, handler?: (eventObject: JQueryEventObject) => any): JQuery; + undelegate(selector: any, events: any): JQuery; + undelegate(namespace: string): JQuery; + + /********* + INTERNALS + **********/ + + context: Element; + jquery: string; + pushStack(elements: any[]): JQuery; + pushStack(elements: any[], name: any, arguments: any): JQuery; + + /************ + MANIPULATION + *************/ + after(...content: any[]): JQuery; + after(func: (index: any) => any); + + append(...content: any[]): JQuery; + append(func: (index: any, html: any) => any); + + appendTo(target: any): JQuery; + + before(...content: any[]): JQuery; + before(func: (index: any) => any); + + clone(withDataAndEvents?: bool, deepWithDataAndEvents?: bool): JQuery; + + detach(selector?: any): JQuery; + + empty(): JQuery; + + insertAfter(target: any): JQuery; + insertBefore(target: any): JQuery; + + prepend(...content: any[]): JQuery; + prepend(func: (index: any, html: any) =>any): JQuery; + + prependTo(target: any): JQuery; + + remove(selector?: any): JQuery; + + replaceAll(target: any): JQuery; + + replaceWith(func: any): JQuery; + + text(textString: string): JQuery; + text(): string; + + toArray(): any[]; + + unwrap(): JQuery; + + wrap(wrappingElement: any): JQuery; + wrap(func: (index: any) =>any): JQuery; + + wrapAll(wrappingElement: any): JQuery; + + wrapInner(wrappingElement: any): JQuery; + wrapInner(func: (index: any) =>any): JQuery; + + /************* + MISCELLANEOUS + **************/ + each(func: (index: any, elem: Element) => JQuery); + + get(index?: number): any; + + index(selectorOrElement?: any): number; + + /********** + PROPERTIES + ***********/ + length: number; + [x: string]: HTMLElement; + [x: number]: HTMLElement; + + /********** + TRAVERSING + ***********/ + add(selector: string, context?: any): JQuery; + add(...elements: any[]): JQuery; + add(html: string): JQuery; + add(obj: JQuery): JQuery; + + andSelf(): JQuery; + + children(selector?: any): JQuery; + + closest(selector: string): JQuery; + closest(selector: string, context?: Element): JQuery; + closest(obj: JQuery): JQuery; + closest(element: any): JQuery; + closest(selectors: any, context?: Element): any[]; + + contents(): JQuery; + + end(): JQuery; + + eq(index: number): JQuery; + + filter(selector: string): JQuery; + filter(func: (index: any) =>any): JQuery; + filter(element: any): JQuery; + filter(obj: JQuery): JQuery; + + find(selector: string): JQuery; + find(element: any): JQuery; + find(obj: JQuery): JQuery; + + first(): JQuery; + + has(selector: string): JQuery; + has(contained: Element): JQuery; + + is(selector: string): JQuery; + is(func: (index: any) =>any): JQuery; + is(element: any): JQuery; + is(obj: JQuery): JQuery; + + last(): JQuery; + + map(callback: (index: any, domElement: Element) =>any): JQuery; + + next(selector?: string): JQuery; + + nextAll(selector?: string): JQuery; + + nextUntil(selector?: string, filter?: string): JQuery; + nextUntil(element?: Element, filter?: string): JQuery; + + not(selector: string): JQuery; + not(func: (index: any) =>any): JQuery; + not(element: any): JQuery; + not(obj: JQuery): JQuery; + + offsetParent(): JQuery; + + parent(selector?: string): JQuery; + + parents(selector?: string): JQuery; + + parentsUntil(selector?: string, filter?: string): JQuery; + parentsUntil(element?: Element, filter?: string): JQuery; + + prev(selector?: string): JQuery; + + prevAll(selector?: string): JQuery; + + prevUntil(selector?: string, filter?:string): JQuery; + prevUntil(element?: Element, filter?:string): JQuery; + + siblings(selector?: string): JQuery; + + slice(start: number, end?: number): JQuery; + + /********* + UTILITIES + **********/ + + queue(queueName?: string): any[]; + queue(queueName: string, newQueueOrCallback: any): JQuery; + queue(newQueueOrCallback: any): JQuery; +} + +declare var jQuery: JQueryStatic; +declare var $: JQueryStatic; \ No newline at end of file diff --git a/tests/automation/source-maps/internal/scripts/script.closure.js b/tests/automation/source-maps/internal/scripts/script.closure.js new file mode 100644 index 0000000000..be7ba3131e --- /dev/null +++ b/tests/automation/source-maps/internal/scripts/script.closure.js @@ -0,0 +1,2 @@ +document.getElementById("color").focus();$(document).keydown(function(a){13==a.keyCode&&$("body").css("background-color",$("#color").val())}); +//@ sourceMappingURL=script.closure.js.map \ No newline at end of file diff --git a/tests/automation/source-maps/internal/scripts/script.closure.js.map b/tests/automation/source-maps/internal/scripts/script.closure.js.map new file mode 100644 index 0000000000..a3bb383db0 --- /dev/null +++ b/tests/automation/source-maps/internal/scripts/script.closure.js.map @@ -0,0 +1,8 @@ +{ + "version":3, + "file":"script.closure.js", + "lineCount":1, + "mappings":"AACAA,QAAAC,eAAA,CAAwB,OAAxB,CAAAC,MAAA,EAEAC,EAAA,CAAEH,QAAF,CAAAI,QAAA,CAAoB,QAAQ,CAACC,CAAD,CAAM,CAGb,EAAnB,EAAIA,CAAAC,QAAJ,EAEEH,CAAA,CAAE,MAAF,CAAAI,IAAA,CAAc,kBAAd,CAAkCJ,CAAA,CAAE,QAAF,CAAAK,IAAA,EAAlC,CAL8B,CAAlC;", + "sources":["script1.js"], + "names":["document","getElementById","focus","$","keydown","evt","keyCode","css","val"] +} diff --git a/tests/automation/source-maps/internal/scripts/script.coffee.coffee b/tests/automation/source-maps/internal/scripts/script.coffee.coffee new file mode 100644 index 0000000000..506a5f7a53 --- /dev/null +++ b/tests/automation/source-maps/internal/scripts/script.coffee.coffee @@ -0,0 +1,5 @@ +document.getElementById("color").focus() + +$(document).keydown (evt) -> + if evt.keyCode == 13 + $("body").css("background-color", $("#color").val()) diff --git a/tests/automation/source-maps/internal/scripts/script.coffee.js b/tests/automation/source-maps/internal/scripts/script.coffee.js new file mode 100644 index 0000000000..b7fa109dc5 --- /dev/null +++ b/tests/automation/source-maps/internal/scripts/script.coffee.js @@ -0,0 +1,13 @@ +// Generated by CoffeeScript 1.4.0 +(function() { + + document.getElementById("color").focus(); + + $(document).keydown(function(evt) { + if (evt.keyCode === 13) { + return $("body").css("background-color", $("#color").val()); + } + }); + +}).call(this); +//@ sourceMappingURL=script.coffee.js.map \ No newline at end of file diff --git a/tests/automation/source-maps/internal/scripts/script.coffee.js.map b/tests/automation/source-maps/internal/scripts/script.coffee.js.map new file mode 100644 index 0000000000..26883a4421 --- /dev/null +++ b/tests/automation/source-maps/internal/scripts/script.coffee.js.map @@ -0,0 +1 @@ +{"version":3,"file":"script.coffee.coffee","sources":["script.coffee.coffee"],"names":[],"mappings":"AAAC;AAAA,QAAQ,eAAe,CAAC,OAAD,CAAS,MAAM,CAAA,EAAtC;AAEA,CAAC,CAAC,QAAD,CAAU,QAAX,CAAoB,SAAA,CAAA,GAAA,CAAA;MACd,GAAG,QAAH,CAAA,GAAA,CAAe;WAChB,CAAC,CAAC,MAAD,CAAQ,IAAI,CAAC,kBAAD,EAAqB,CAAC,CAAC,QAAD,CAAU,IAAI,CAAA,CAApC;CAFlB"} diff --git a/tests/automation/source-maps/internal/scripts/script.coffee.min.js b/tests/automation/source-maps/internal/scripts/script.coffee.min.js new file mode 100644 index 0000000000..9c9e11d29e --- /dev/null +++ b/tests/automation/source-maps/internal/scripts/script.coffee.min.js @@ -0,0 +1,2 @@ +(function(){document.getElementById("color").focus();$(document).keydown(function(evt){if(evt.keyCode===13){return $("body").css("background-color",$("#color").val())}})}).call(this); +//@ sourceMappingURL=script.coffee.min.js.map \ No newline at end of file diff --git a/tests/automation/source-maps/internal/scripts/script.coffee.min.js.map b/tests/automation/source-maps/internal/scripts/script.coffee.min.js.map new file mode 100644 index 0000000000..2d70896e3f --- /dev/null +++ b/tests/automation/source-maps/internal/scripts/script.coffee.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"script.coffee.min.js","sources":["script.coffee.coffee"],"names":[],"mappings":"CAAC,WAEoB,SACX,eAAY,SAAA,OADrB,GAAA,UAAA,QAAA,SAAA,KAAA,GAAA,IAAA,UAAA,GAAA,CAAA,MAAA,GAAA,QAAA,IAAA,mBAAA,EAAA,UAAA,YAAA,KAAA"} \ No newline at end of file diff --git a/tests/automation/source-maps/internal/scripts/script.js b/tests/automation/source-maps/internal/scripts/script.js new file mode 100644 index 0000000000..a4aa1db970 --- /dev/null +++ b/tests/automation/source-maps/internal/scripts/script.js @@ -0,0 +1,12 @@ +// on load, put focus on the input +document.getElementById("color").focus(); + +$(document).keydown(function(evt) { + + // only if the user keypress is ENTER + if (evt.keyCode == 13) { + // css color names and hex code + $("body").css("background-color", $("#color").val()); + } + +}); \ No newline at end of file diff --git a/tests/automation/source-maps/internal/scripts/script.jsmin-grunt.js b/tests/automation/source-maps/internal/scripts/script.jsmin-grunt.js new file mode 100644 index 0000000000..e0877b3d38 --- /dev/null +++ b/tests/automation/source-maps/internal/scripts/script.jsmin-grunt.js @@ -0,0 +1,3 @@ + +document.getElementById("color").focus();$(document).keydown(function(evt){if(evt.keyCode==13){$("body").css("background-color",$("#color").val());}}); +//@ sourceMappingURL=script.jsmin-grunt.js.map \ No newline at end of file diff --git a/tests/automation/source-maps/internal/scripts/script.jsmin-grunt.js.map b/tests/automation/source-maps/internal/scripts/script.jsmin-grunt.js.map new file mode 100644 index 0000000000..7377de2425 --- /dev/null +++ b/tests/automation/source-maps/internal/scripts/script.jsmin-grunt.js.map @@ -0,0 +1 @@ +{"version":3,"file":"script.jsmin-grunt.js","sources":["script2.js"],"names":[],"mappings":"AAAkC;AAClC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAExC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAGhC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,CAAE,CAAC,CAAC,CAAE,CAErB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CACtD,CAEF,CAAC,CAAC"} diff --git a/tests/automation/source-maps/internal/scripts/script.typescript.js b/tests/automation/source-maps/internal/scripts/script.typescript.js new file mode 100644 index 0000000000..f4687533d8 --- /dev/null +++ b/tests/automation/source-maps/internal/scripts/script.typescript.js @@ -0,0 +1,7 @@ +document.getElementById("color").focus(); +$(document).keydown(function (evt) { + if(evt.keyCode == 13) { + $("body").css("background-color", $("#color").val()); + } +}); +//@ sourceMappingURL=script.typescript.js.map diff --git a/tests/automation/source-maps/internal/scripts/script.typescript.js.map b/tests/automation/source-maps/internal/scripts/script.typescript.js.map new file mode 100644 index 0000000000..034a30378e --- /dev/null +++ b/tests/automation/source-maps/internal/scripts/script.typescript.js.map @@ -0,0 +1 @@ +{"version":3,"file":"script.typescript.js","sources":["script.typescript.ts"],"names":[""],"mappings":"AAAA,QAGQ,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE;AAExC,CAAC,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,UAAS,GAAG;IAG9BA,GAAIA,GAAGA,CAACA,OAAOA,IAAIA,EAAEA,CAACA;QAEpBA,CAACA,CAACA,MAAMA,CAACA,CAACA,GAAGA,CAACA,kBAAkBA,EAAEA,CAACA,CAACA,QAAQA,CAACA,CAACA,GAAGA,EAAEA,CAACA;KACrDA;AAEHA,CAACA,CAAC;AAAC"} \ No newline at end of file diff --git a/tests/automation/source-maps/internal/scripts/script.typescript.min.js b/tests/automation/source-maps/internal/scripts/script.typescript.min.js new file mode 100644 index 0000000000..75ed335420 --- /dev/null +++ b/tests/automation/source-maps/internal/scripts/script.typescript.min.js @@ -0,0 +1,2 @@ +document.getElementById("color").focus();$(document).keydown(function(evt){if(evt.keyCode==13){$("body").css("background-color",$("#color").val())}}); +//@ sourceMappingURL=script.typescript.min.js.map \ No newline at end of file diff --git a/tests/automation/source-maps/internal/scripts/script.typescript.min.js.map b/tests/automation/source-maps/internal/scripts/script.typescript.min.js.map new file mode 100644 index 0000000000..7cd9eaa424 --- /dev/null +++ b/tests/automation/source-maps/internal/scripts/script.typescript.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"script.typescript.min.js","sources":["script.typescript.ts"],"names":[],"mappings":"AAAA,SAGS,eAAe,SAAS,OAEjC,GAAE,UAAU,QAAQ,SAAS,KAG3B,GAAI,IAAI,SAAW,GAAG,CAEpB,EAAE,QAAQ,IAAI,mBAAoB,EAAE,UAAU"} \ No newline at end of file diff --git a/tests/automation/source-maps/internal/scripts/script.typescript.ts b/tests/automation/source-maps/internal/scripts/script.typescript.ts new file mode 100644 index 0000000000..20481fb5fc --- /dev/null +++ b/tests/automation/source-maps/internal/scripts/script.typescript.ts @@ -0,0 +1,14 @@ +/// + +// on load, put focus on the input +document.getElementById("color").focus(); + +$(document).keydown(function(evt) { + + // only if the user keypress is ENTER + if (evt.keyCode == 13) { + // css color names and hex code + $("body").css("background-color", $("#color").val()); + } + +}); \ No newline at end of file diff --git a/tests/automation/source-maps/internal/scripts/script.uglify.js b/tests/automation/source-maps/internal/scripts/script.uglify.js new file mode 100644 index 0000000000..3392f6df36 --- /dev/null +++ b/tests/automation/source-maps/internal/scripts/script.uglify.js @@ -0,0 +1,2 @@ +document.getElementById("color").focus();$(document).keydown(function(evt){if(evt.keyCode==13){$("body").css("background-color",$("#color").val())}}); +//@ sourceMappingURL=script.uglify.js.map \ No newline at end of file diff --git a/tests/automation/source-maps/internal/scripts/script.uglify.js.map b/tests/automation/source-maps/internal/scripts/script.uglify.js.map new file mode 100644 index 0000000000..cd200f92b4 --- /dev/null +++ b/tests/automation/source-maps/internal/scripts/script.uglify.js.map @@ -0,0 +1 @@ +{"version":3,"file":"script.uglify.js","sources":["script.js"],"names":["document","getElementById","focus","$","keydown","evt","keyCode","css","val"],"mappings":"AACAA,SAASC,eAAe,SAASC,OAEjCC,GAAEH,UAAUI,QAAQ,SAASC,KAG3B,GAAIA,IAAIC,SAAW,GAAI,CAErBH,EAAE,QAAQI,IAAI,mBAAoBJ,EAAE,UAAUK"} \ No newline at end of file diff --git a/tests/automation/source-maps/internal/scripts/script1.js b/tests/automation/source-maps/internal/scripts/script1.js new file mode 100644 index 0000000000..a4aa1db970 --- /dev/null +++ b/tests/automation/source-maps/internal/scripts/script1.js @@ -0,0 +1,12 @@ +// on load, put focus on the input +document.getElementById("color").focus(); + +$(document).keydown(function(evt) { + + // only if the user keypress is ENTER + if (evt.keyCode == 13) { + // css color names and hex code + $("body").css("background-color", $("#color").val()); + } + +}); \ No newline at end of file diff --git a/tests/automation/source-maps/internal/scripts/script2.js b/tests/automation/source-maps/internal/scripts/script2.js new file mode 100644 index 0000000000..a4aa1db970 --- /dev/null +++ b/tests/automation/source-maps/internal/scripts/script2.js @@ -0,0 +1,12 @@ +// on load, put focus on the input +document.getElementById("color").focus(); + +$(document).keydown(function(evt) { + + // only if the user keypress is ENTER + if (evt.keyCode == 13) { + // css color names and hex code + $("body").css("background-color", $("#color").val()); + } + +}); \ No newline at end of file diff --git a/tests/automation/source-maps/internal/styles/style.css b/tests/automation/source-maps/internal/styles/style.css new file mode 100644 index 0000000000..ab7318910d --- /dev/null +++ b/tests/automation/source-maps/internal/styles/style.css @@ -0,0 +1,20 @@ +@media -sass-debug-info{filename{font-family:file\:\/start\/styles\/style\.sass}line{font-family:\000034}} +body { + background-color: #d9d9d9; } + +@media -sass-debug-info{filename{font-family:file\:\/start\/styles\/style\.sass}line{font-family:\000037}} +input { + background-color: rgba(255, 255, 255, 0.5); + border: 0; + display: block; + font-size: 3em; + margin: 15% auto; + outline: none; + padding: 0.4em; + width: 20%; } + +@media only screen and (max-width: 600px) { +@media -sass-debug-info{filename{font-family:file\:\/start\/styles\/style\.sass}line{font-family:\0000318}} + input { + font-size: 2em; + width: 40%; } } diff --git a/tests/automation/source-maps/internal/styles/style.sass b/tests/automation/source-maps/internal/styles/style.sass new file mode 100644 index 0000000000..3a024e308e --- /dev/null +++ b/tests/automation/source-maps/internal/styles/style.sass @@ -0,0 +1,20 @@ +$main-color: #000 +$secondary-color: #fff + +body + background-color: lighten($main-color, 85%) + +input + background-color: transparentize($secondary-color, 0.5) + border: 0 + display: block + font-size: 3em + margin: 15% auto + outline: none + padding: 0.4em + width: 20% + +@media only screen and (max-width: 600px) + input + font-size: 2em + width: 40% diff --git a/tests/automation/source-maps/internal/styles/style.scss b/tests/automation/source-maps/internal/styles/style.scss new file mode 100644 index 0000000000..21bf031a23 --- /dev/null +++ b/tests/automation/source-maps/internal/styles/style.scss @@ -0,0 +1,24 @@ +$main-color: #000; +$secondary-color: #fff; + +body{ + background-color: lighten($main-color, 85%); +} + +input{ + background-color: transparentize($secondary-color, 0.5); + border: 0; + display: block; + font-size: 3em; + margin: 15% auto; + outline: none; + padding: 0.4em; + width: 20%; +} + +@media only screen and (max-width: 600px){ + input{ + font-size: 2em; + width: 40%; + } +} diff --git a/tests/automation/source-maps/mocha_test.js b/tests/automation/source-maps/mocha_test.js new file mode 100644 index 0000000000..f7144463c3 --- /dev/null +++ b/tests/automation/source-maps/mocha_test.js @@ -0,0 +1,66 @@ +var path = require('path'); +var assert = require('assert'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + + +describe('source maps', function() { + + var server, child, results, ok = false; + + before(function(done) { + this.timeout(0); + server = createTCPServer(13013); + child = spawnChildProcess(path.join(curDir, 'internal')); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + ok = true; + results = JSON.parse(data); + child.kill(); + done(); + }); + }); + + setTimeout(function() { + if (!ok) { + child.kill(); + done('timeout'); + } + }, 4500); + + }); + + after(function () { + server.close(); + }); + + it('should support Closure Compile', function() { + assert.equal("script.closure.js", results[0]); + assert.equal("script1.js", results[8]); + }); + + it('should support JSMin and Grunt', function() { + assert.equal("script.jsmin-grunt.js", results[4]); + assert.equal("script2.js", results[9]); + }); + + it('should support Uglifyjs', function() { + assert.equal('script.uglify.js', results[7]); + assert.equal('script.js', results[3]); + }); + + it('should support CoffeeScript', function() { + assert.equal('script.coffee.min.js', results[2]); + assert.equal('script.coffee.coffee', results[1]); + }); + + it('should support TypeScript', function() { + assert.equal('script.typescript.min.js', results[5]); + assert.equal('script.typescript.ts', results[6]); + }); + + +}); + + diff --git a/tests/automation/source-maps/package.json b/tests/automation/source-maps/package.json new file mode 100644 index 0000000000..9c9bc79877 --- /dev/null +++ b/tests/automation/source-maps/package.json @@ -0,0 +1,5 @@ +{ + "name":"source_map_wrapper", + "main":"index.html" +} + diff --git a/tests/automation/start_app/index.html b/tests/automation/start_app/index.html new file mode 100644 index 0000000000..e7d0082e15 --- /dev/null +++ b/tests/automation/start_app/index.html @@ -0,0 +1,12 @@ + + + Hello World + + +

              Start app test

              + + + + + + diff --git a/tests/automation/start_app/internal/index.html b/tests/automation/start_app/internal/index.html new file mode 100644 index 0000000000..043908786a --- /dev/null +++ b/tests/automation/start_app/internal/index.html @@ -0,0 +1,16 @@ + + + Hello World + + + +

              this is node-webkit

              + + + diff --git a/tests/automation/start_app/internal/package.json b/tests/automation/start_app/internal/package.json new file mode 100644 index 0000000000..cc5dc359a1 --- /dev/null +++ b/tests/automation/start_app/internal/package.json @@ -0,0 +1,8 @@ +{ + "name": "nw-demo", + "version": "0.1.0", + "main": "index.html", + "window": { + "show": false + } +} diff --git a/tests/automation/start_app/mocha_test.js b/tests/automation/start_app/mocha_test.js new file mode 100644 index 0000000000..f8a0132516 --- /dev/null +++ b/tests/automation/start_app/mocha_test.js @@ -0,0 +1,138 @@ +var spawn = require('child_process').spawn; +var exec = require('child_process').exec; +var path = require('path'); +var net = require('net'); +var os = require('os'); +var fs = require('fs-extra'); +var curDir = require('fs').realpathSync('.'); +var func = require(path.join(curDir, 'script.js')); +var execPath = path.join(curDir, func.getExecPath()); + +describe('Startup', function() { +describe('different method of starting app (long-to-run)', function() { + + before(function(done){ + this.timeout(10000); + func.copyExecFiles(function() { + func.copySourceFiles('internal'); + func.zipSourceFiles(function() { + func.makeExecuableFile(); + done(); + }); + }); + + }); + + after(function(done) { + setTimeout(function() { + fs.remove(path.join(curDir, 'tmp-nw'), function (er) { + if (er) { + console.log('Failed to remove the temporary folder tmp-nw: ' + er); + throw er; + } + done(); + }) + }, 1000); + }) + + it('start from nw that package.json in the same folder', function(done) { + this.timeout(0); + var result = false; + + if (os.platform() == 'darwin') { + //mac don't have this method + done(); + return; + } + + var app = spawn(execPath); + app.on('exit', function() { + result = true; + done(); + }); + + setTimeout(function() { + if (!result) { + done('timeout'); + app.kill(); + } + }, 10000); + + }) + + it('start from app.nw', function(done) { + this.timeout(0); + var result = false; + + var app = spawn(execPath, [path.join(curDir, 'tmp-nw', 'app.nw')]); + app.on('exit', function() { + result = true; + done(); + }); + setTimeout(function() { + if (!result) { + done('timeout'); + app.kill(); + } + }, 10000); + + }) + + it('start from folder contains `../`', function(done) { + this.timeout(0); + var result = false; + var app = spawn(execPath, [path.join(curDir, '..', '..', 'tmp-nw')]); + + + app.on('exit', function() { + result = true; + done(); + }); + + setTimeout(function() { + if (!result) { + done('timeout'); + app.kill(); + } + }, 10000); + }) + + it('start from an executable file app.exe', function(done) { + this.timeout(0); + var result = false; + function launch(appPath) { + var app = spawn(appPath); + app.on('exit', function() { + result = true; + done(); + }); + + setTimeout(function() { + if (!result) { + done('timeout'); + app.kill(); + } + }, 10000); + } + + if (os.platform() == 'win32') { + launch(path.join(curDir, 'tmp-nw', 'app.exe')); + } + if (os.platform() == 'linux') { + launch(path.join(curDir, 'tmp-nw', 'app')); + } + if (os.platform() == 'darwin') { + var app_path = curDir + 'tmp-nw/node-webkit.app/Contents/Resources/app.nw'; + fs.mkdir(app_path, function(err) { + if(err && err.code !== 'EEXIST') throw err + fs.copy(path.join(curDir, 'tmp-nw', 'internal', 'index.html'), path.join(app_path, 'index.html')); + fs.copy(path.join(curDir, 'tmp-nw', 'internal', 'package.json'), path.join(app_path, 'package.json'), function() { + launch(app_path); + }); + }); + } + + }) + +}) +}) diff --git a/tests/automation/start_app/package.json b/tests/automation/start_app/package.json new file mode 100644 index 0000000000..15ea52e629 --- /dev/null +++ b/tests/automation/start_app/package.json @@ -0,0 +1,5 @@ +{ + "name": "start_app_wrapper", + "version": "0.1.0", + "main": "index.html" +} diff --git a/tests/automation/start_app/script.js b/tests/automation/start_app/script.js new file mode 100644 index 0000000000..9ad4d05d7f --- /dev/null +++ b/tests/automation/start_app/script.js @@ -0,0 +1,91 @@ +var path = require('path'); +var os = require('os'); +var fsextra = require('fs-extra'); +var cp = require('child_process'); +var exec = cp.exec; +var sqawn = cp.sqawn; +var global = {}; +global.tests_dir = fsextra.realpathSync('.'); + +var required_file_win_linux = fsextra.readdirSync(path.dirname(process.execPath)); +var required_file_win = required_file_win_linux; +var required_file_linux = required_file_win_linux; + +var required_file_macox = [ + 'node-webkit.app' +]; + +var source_file = ['index.html', 'package.json']; + +var exec_root = path.dirname(process.execPath); +var required_file; +if (os.platform() == 'win32') { + required_file = required_file_win; +} +if (os.platform() == 'linux') { + required_file = required_file_linux; +} +if (os.platform() == 'darwin') { + required_file = required_file_macox; + if (~exec_root.indexOf("Helper.app")) + exec_root = path.join(exec_root, '..', '..', '..') + exec_root = path.normalize( + path.join(exec_root, '..', '..', '..')); +} + + + +exports.getExecPath = function() { + if (os.platform() == 'win32') { + return path.join('tmp-nw', 'nw.exe'); + } + if (os.platform() == 'linux') { + return path.join('tmp-nw', 'nw'); + } + if (os.platform() == 'darwin') { + return path.join('tmp-nw', 'node-webkit.app', 'Contents', 'MacOS', 'node-webkit'); + } + +} + +function copyExecFiles(done) { + fsextra.mkdir('tmp-nw', function(err) { + if(err && err.code !== 'EEXIST') throw err; + var files_done = 0; + for (var i in required_file) { + var src_file = path.join(exec_root, required_file[i]); + var dst_file = path.join('tmp-nw', required_file[i]); + fsextra.copySync(src_file, dst_file); + } + done(); + }); + +} + +exports.copySourceFiles = function(folder) { + if (folder == undefined) + folder = 'start_app'; + fsextra.createReadStream(global.tests_dir + '/' + folder + '/index.html').pipe( + fsextra.createWriteStream('tmp-nw/index.html')); + fsextra.createReadStream(global.tests_dir + '/' + folder + '/package.json').pipe( + fsextra.createWriteStream('tmp-nw/package.json')); + +} + +exports.zipSourceFiles = function(callback) { + exec('python '+path.join(global.tests_dir, 'zip.py')); + setTimeout(callback, 2000); +} + +exports.makeExecuableFile = function() { + if (os.platform() == 'win32') { + cp.exec('copy /b nw.exe+app.nw app.exe', {cwd: './tmp-nw/'}); + } + if (os.platform() == 'linux') { + cp.exec('cat nw app.nw > app && chmod +x app', {cwd: './tmp-nw/'}); + } + +} + + +exports.copyExecFiles = copyExecFiles; diff --git a/tests/automation/start_app/zip.py b/tests/automation/start_app/zip.py new file mode 100644 index 0000000000..66f228f716 --- /dev/null +++ b/tests/automation/start_app/zip.py @@ -0,0 +1,14 @@ +import zipfile +import os +curDir = os.path.dirname(os.path.abspath(__file__)) +zip = zipfile.ZipFile(os.path.join(curDir, 'tmp-nw', 'app.nw'), 'w', + compression=zipfile.ZIP_DEFLATED) + +source_file = ['index.html', 'package.json'] + +for file in source_file: + path = os.path.join(curDir, 'tmp-nw', file) + zip.write(path, file) + +zip.close(); + diff --git a/tests/automation/temp_dir/index.html b/tests/automation/temp_dir/index.html new file mode 100644 index 0000000000..5146dd6241 --- /dev/null +++ b/tests/automation/temp_dir/index.html @@ -0,0 +1,16 @@ + + + + + temp dir test + + +

              temp dir test

              + + + + + + + + diff --git a/tests/automation/temp_dir/internal/index.html b/tests/automation/temp_dir/internal/index.html new file mode 100644 index 0000000000..4cddf36d08 --- /dev/null +++ b/tests/automation/temp_dir/internal/index.html @@ -0,0 +1,26 @@ + + + + Test CASE FOR TMP DIR SHOULD BE REMOVED AFTER APP EXIT + + +

              Hello World!

              + + + diff --git a/tests/automation/temp_dir/internal/package.json b/tests/automation/temp_dir/internal/package.json new file mode 100644 index 0000000000..b9b7eb06de --- /dev/null +++ b/tests/automation/temp_dir/internal/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw", + "main": "index.html" +} diff --git a/tests/automation/temp_dir/mocha_test.js b/tests/automation/temp_dir/mocha_test.js new file mode 100644 index 0000000000..271b7b55e2 --- /dev/null +++ b/tests/automation/temp_dir/mocha_test.js @@ -0,0 +1,70 @@ +var path = require('path'); +var os = require('os'); +var cp = require('child_process'); +var assert = require('assert'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + +var func = require(path.join(curDir, '..', 'start_app', 'script.js')); +var execPath = func.getExecPath(); + + +var mac_app_path = path.join(curDir, 'tmp-nw', 'node-webkit.app'); + +var temp_path; + +if (os.platform() == 'win32') { + execPath = path.join(curDir, 'tmp-nw', 'app.exe'); +} else if (os.platform() == 'linux') { + execPath = path.join(curDir, 'tmp-nw', 'app'); +} else if (os.platform() == 'darwin') { + execPath = path.join(curDir, 'tmp-nw', 'node-webkit.app', 'Contents', 'MacOS', 'node-webkit'); +} + +function make_execuable_file(folder_path, done) { + func.copyExecFiles(function() { + func.copySourceFiles(folder_path); + func.zipSourceFiles(function() { + func.makeExecuableFile(); + if (os.platform() == 'darwin') { + var app_path = 'tmp-nw/node-webkit.app/Contents/Resources/app.nw'; + fs.mkdir(app_path, function(err) { + if(err && err.code !== 'EEXIST') throw err + fs.copy('tmp-nw/index.html', path.join(app_path, 'index.html')); + fs.copy('tmp-nw/package.html', path.join(app_path, 'package.html')); + setTimeout(done, 3000); + + }); + } else { + setTimeout(function() { + var child = cp.spawn(execPath, curDir); // [path.join(curDir, 'internal')]); + child.on('exit', function() { + temp_path = path.dirname(fs.readFileSync(path.join(curDir, 'tmp-nw','path.org'))).substring(8); + done(); + }); + }, 3000); + } + }); + }); +} + +describe('temp_dir', function() { + this.timeout(0); + + before(function(done) { + make_execuable_file('internal', done); + }); + + after(function() { + fs.remove('tmp-nw', function (er) { + if (er) throw er; + }); + }); + + it('should be removed after app exit', function() { + var exist = fs.existsSync(temp_path); + assert.equal(exist, false); + }); +}); + + diff --git a/tests/automation/temp_dir/package.json b/tests/automation/temp_dir/package.json new file mode 100644 index 0000000000..1d0c39102d --- /dev/null +++ b/tests/automation/temp_dir/package.json @@ -0,0 +1,5 @@ +{ + "name":"temp_dir_wrapper", + "main":"index.html" +} + diff --git a/tests/automation/user-agent-app/index.html b/tests/automation/user-agent-app/index.html new file mode 100644 index 0000000000..31eb0c66d0 --- /dev/null +++ b/tests/automation/user-agent-app/index.html @@ -0,0 +1,16 @@ + + + + + user agent app test + + +

              user agent app test

              + + + + + + + + diff --git a/tests/automation/user-agent-app/internal/index.html b/tests/automation/user-agent-app/internal/index.html new file mode 100644 index 0000000000..517c1a0c48 --- /dev/null +++ b/tests/automation/user-agent-app/internal/index.html @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/tests/automation/user-agent-app/internal/package.json b/tests/automation/user-agent-app/internal/package.json new file mode 100644 index 0000000000..ec487cf9a5 --- /dev/null +++ b/tests/automation/user-agent-app/internal/package.json @@ -0,0 +1,5 @@ +{ + "name": "user agent app", + "main": "index.html", + "user-agent": "%name/%nwver/%ver/%webkit_ver/%osinfo" +} \ No newline at end of file diff --git a/tests/automation/user-agent-app/mocha_test.js b/tests/automation/user-agent-app/mocha_test.js new file mode 100644 index 0000000000..a395d0c7b9 --- /dev/null +++ b/tests/automation/user-agent-app/mocha_test.js @@ -0,0 +1,47 @@ +var path = require('path'); +var assert = require('assert'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + + +describe('user-agent-app', function() { + + var result = false, package_name, package_info; + + before(function(done) { + this.timeout(0); + server = createTCPServer(13013); + child = spawnChildProcess(path.join(curDir, 'internal')); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + result = true; + package_name = data; + package_info = JSON.parse(fs.readFileSync(path.join(curDir, 'internal', 'package.json'), 'utf8')); + child.kill(); + done(); + }); + }); + + setTimeout(function(){ + if (!result) { + child.kill(); + done('loaded event does not been fired'); + } + }, 7000); + + }); + + after(function () { + server.close(); + }); + + it('user agent shoud be the same when open a new window', + function() { + assert.equal(result, true); + assert.equal(package_name, package_info.name); + }); + + +}); + diff --git a/tests/automation/user-agent-app/package.json b/tests/automation/user-agent-app/package.json new file mode 100644 index 0000000000..9a59564273 --- /dev/null +++ b/tests/automation/user-agent-app/package.json @@ -0,0 +1,5 @@ +{ + "name":"user_agent_app_wrapper", + "main":"index.html" +} + diff --git a/tests/automation/user-agent/index.html b/tests/automation/user-agent/index.html new file mode 100644 index 0000000000..823bfc1920 --- /dev/null +++ b/tests/automation/user-agent/index.html @@ -0,0 +1,16 @@ + + + + + user agent test + + +

              user agent test

              + + + + + + + + diff --git a/tests/automation/user-agent/internal/index.html b/tests/automation/user-agent/internal/index.html new file mode 100644 index 0000000000..6a9e30c610 --- /dev/null +++ b/tests/automation/user-agent/internal/index.html @@ -0,0 +1,58 @@ + + + + Test Case For user-agent! + + +

              Hello User-Agent

              + + + diff --git a/tests/automation/user-agent/internal/index1.html b/tests/automation/user-agent/internal/index1.html new file mode 100644 index 0000000000..829a1c6904 --- /dev/null +++ b/tests/automation/user-agent/internal/index1.html @@ -0,0 +1,14 @@ + + + + Child Window For user-agent! + + +

              Hello Child User-Agent

              + + + diff --git a/tests/automation/user-agent/internal/package.json b/tests/automation/user-agent/internal/package.json new file mode 100644 index 0000000000..e106169070 --- /dev/null +++ b/tests/automation/user-agent/internal/package.json @@ -0,0 +1,5 @@ +{ + "name": "nw-user-agent-test", + "main": "index.html", + "user-agent": "%name/%nwver/%ver/%webkit_ver/%osinfo" +} diff --git a/tests/automation/user-agent/mocha_test.js b/tests/automation/user-agent/mocha_test.js new file mode 100644 index 0000000000..f8cec10e2a --- /dev/null +++ b/tests/automation/user-agent/mocha_test.js @@ -0,0 +1,64 @@ +var path = require('path'); +var assert = require('assert'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + +describe('user-agent', function() { + + var server, child, results; + + before(function(done) { + this.timeout(0); + server = createTCPServer(13013); + child = spawnChildProcess(path.join(curDir, 'internal')); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + results = JSON.parse(data); + for (var i = 0, len = results.length; i < len; i++) { + console.log('User agent['+i+']:' + results[i]); + } + child.kill(); + done(); + }); + }); + + }); + + after(function () { + server.close(); + }); + + + describe('user-agent with child window opened', function() { + it('should be same to the one in parent window', function() { + assert.equal(results[0], results[1]); + }); + }); + + describe('user-agent with reload', function() { + it('should be same with all reload method', function() { + assert.equal(results[0], results[2]); + assert.equal(results[0], results[3]); + assert.equal(results[0], results[4]); + assert.equal(results[0], results[5]); + }); + }); + + describe('user-agent with package.json', function() { + var user_agent = navigator.userAgent.split('/'); + var package_info = JSON.parse(fs.readFileSync(path.join(curDir, 'internal', 'package.json'), 'utf8')); + it('name should be same to the one in package.json', + function() { + assert.equal(user_agent[0], package_info.name); + }); + + it('version should be same to the one in package.json', + function() { + assert.equal(user_agent[2], package_info.version); + }); + }); + +}); + + diff --git a/tests/automation/user-agent/package.json b/tests/automation/user-agent/package.json new file mode 100644 index 0000000000..26c3b524a8 --- /dev/null +++ b/tests/automation/user-agent/package.json @@ -0,0 +1,5 @@ +{ + "name":"user_agent_wrapper", + "main":"index.html" +} + diff --git a/tests/automation/website/index.html b/tests/automation/website/index.html new file mode 100644 index 0000000000..c742b26e0a --- /dev/null +++ b/tests/automation/website/index.html @@ -0,0 +1,16 @@ + + + + + website test + + +

              website test

              + + + + + + + + diff --git a/tests/automation/website/mocha_test.js b/tests/automation/website/mocha_test.js new file mode 100644 index 0000000000..fcaca4cec5 --- /dev/null +++ b/tests/automation/website/mocha_test.js @@ -0,0 +1,51 @@ +var assert = require('assert'); + +describe('website', function() { + describe('scores', function() { + var gui = require('nw.gui'); + + it('html5test.com should score high (long-to-run)', function(done) { + this.timeout(0); + var win = gui.Window.open('http://html5test.com', { show: false }); + win.on('loaded', function() { + var results = win.window.document.getElementById('results'); + if (results == null){ + done('Can not connect to the web'); + } else { + try { + var score = results.childNodes[0].childNodes[1].innerHTML; + if (score >= 445) { + done(); + } else { + done('have a low score'); + } + } catch (e) { + done('failed to get score'); + } // try-catch + + } + win.close(); + }); + }); + + + it('should support WebGL at get.webgl.org', function(done) { + this.timeout(0); + var win = gui.Window.open('http://get.webgl.org', { show: false }); + win.on('loaded', function() { + var results = win.window.document.getElementById('webgl-yes'); + if (results.classList.contains('webgl-hidden')) { + done('do not support WebGL'); + } else { + done(); + } + win.close(); + }); + + }); + + + }); +}); + + diff --git a/tests/automation/website/package.json b/tests/automation/website/package.json new file mode 100644 index 0000000000..7c67f7e726 --- /dev/null +++ b/tests/automation/website/package.json @@ -0,0 +1,5 @@ +{ + "name":"website_wrapper", + "main":"index.html" +} + diff --git a/tests/automation/window-document-event/index.html b/tests/automation/window-document-event/index.html new file mode 100644 index 0000000000..4b8b8ab768 --- /dev/null +++ b/tests/automation/window-document-event/index.html @@ -0,0 +1,16 @@ + + + + + window document event test + + +

              window document event test

              + + + + + + + + diff --git a/tests/automation/window-document-event/internal/iframe.html b/tests/automation/window-document-event/internal/iframe.html new file mode 100644 index 0000000000..bdcdcbb44e --- /dev/null +++ b/tests/automation/window-document-event/internal/iframe.html @@ -0,0 +1,13 @@ + + + + + + + +

              Iframe

              + + + \ No newline at end of file diff --git a/tests/automation/window-document-event/internal/index.html b/tests/automation/window-document-event/internal/index.html new file mode 100644 index 0000000000..e5527cf9e5 --- /dev/null +++ b/tests/automation/window-document-event/internal/index.html @@ -0,0 +1,63 @@ + + + + + test + + +

              it works!

              + + + + diff --git a/tests/automation/window-document-event/internal/new_win.html b/tests/automation/window-document-event/internal/new_win.html new file mode 100644 index 0000000000..aa328ef904 --- /dev/null +++ b/tests/automation/window-document-event/internal/new_win.html @@ -0,0 +1,17 @@ + + + + + + + +

              New Window

              + + + + \ No newline at end of file diff --git a/tests/automation/window-document-event/internal/package.json b/tests/automation/window-document-event/internal/package.json new file mode 100644 index 0000000000..85ea1d2a75 --- /dev/null +++ b/tests/automation/window-document-event/internal/package.json @@ -0,0 +1,5 @@ +{ + "name":"nw_1403587578", + "main":"index.html", + "dependencies":{} +} \ No newline at end of file diff --git a/tests/automation/window-document-event/mocha_test.js b/tests/automation/window-document-event/mocha_test.js new file mode 100644 index 0000000000..7fa0d55613 --- /dev/null +++ b/tests/automation/window-document-event/mocha_test.js @@ -0,0 +1,74 @@ +var path = require('path'); +var assert = require('assert'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + +describe('document-start/end',function(){ + + var server, child, results; + + before(function(done) { + this.timeout(0); + server = createTCPServer(13013); + child = spawnChildProcess(path.join(curDir, 'internal')); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + results = JSON.parse(''+data); + child.kill(); + done(); + }); + }); + + }); + + after(function () { + server.close(); + }); + + + it('results should not equal undefined',function(done){ + assert.notEqual(results,undefined); + done(); + }) + + it('new window document-start run first',function(done){ + assert.equal(results[0]['flag'],true) + assert.equal(results[0]['name'],'top-window-document-start') + done(); + }); + it('new window script run between document-start and document-end',function(done){ + assert.equal(results[1],'new-window-script'); + done(); + }); + it('new window document-end should run before onload event',function(done){ + assert.equal(results[2]['flag'],true) + assert.equal(results[2]['name'],'top-window-document-end') + done(); + }); + it('new window onload should run last',function(done){ + assert.equal(results[3],'onload-from-new-window'); + done(); + }); + + it('iframe document-start should run first',function(done){ + assert.equal(results[4]['flag'],true) + assert.equal(results[4]['name'],'iframe-document-start') + done(); + }); + it('iframe script run between document-start and document-end',function(done){ + assert.equal(results[5],'iframe-script'); + done(); + }); + it('iframe document-end should run later',function(done){ + assert.equal(results[6]['flag'],true) + assert.equal(results[6]['name'],'iframe-document-end') + done(); + }); + it('iframe onload should run last',function(done){ + assert.equal(results[7],'onload-from-iframe'); + done(); + }); +}); + + diff --git a/tests/automation/window-document-event/package.json b/tests/automation/window-document-event/package.json new file mode 100644 index 0000000000..ca9df08169 --- /dev/null +++ b/tests/automation/window-document-event/package.json @@ -0,0 +1,5 @@ +{ + "name":"window_document_event_wrapper", + "main":"index.html" +} + diff --git a/tests/automation/window-eval/index.html b/tests/automation/window-eval/index.html new file mode 100644 index 0000000000..3548d2e5b5 --- /dev/null +++ b/tests/automation/window-eval/index.html @@ -0,0 +1,16 @@ + + + + + window eval test + + +

              window eval test

              + + + + + + + + diff --git a/tests/automation/window-eval/internal/iframe.html b/tests/automation/window-eval/internal/iframe.html new file mode 100644 index 0000000000..ab1d2c0ffa --- /dev/null +++ b/tests/automation/window-eval/internal/iframe.html @@ -0,0 +1,14 @@ + + + + + test + + +

              iframe

              + + + + \ No newline at end of file diff --git a/tests/automation/window-eval/internal/index.html b/tests/automation/window-eval/internal/index.html new file mode 100644 index 0000000000..4089aa8ba8 --- /dev/null +++ b/tests/automation/window-eval/internal/index.html @@ -0,0 +1,35 @@ + + + + + test + + +

              it works!

              + + + + + diff --git a/tests/automation/window-eval/internal/package.json b/tests/automation/window-eval/internal/package.json new file mode 100644 index 0000000000..a33ab28774 --- /dev/null +++ b/tests/automation/window-eval/internal/package.json @@ -0,0 +1,5 @@ +{ + "name":"nw_1403254112", + "main":"index.html", + "dependencies":{} +} \ No newline at end of file diff --git a/tests/automation/window-eval/mocha_test.js b/tests/automation/window-eval/mocha_test.js new file mode 100644 index 0000000000..de11d3e1e9 --- /dev/null +++ b/tests/automation/window-eval/mocha_test.js @@ -0,0 +1,35 @@ +var path = require('path'); +var assert = require('assert'); +var fs = require('fs-extra'); +var curDir = fs.realpathSync('.'); + + +describe('Window.eval',function(){ + var server, child, result; + + before(function(done) { + this.timeout(0); + server = createTCPServer(13013); + child = spawnChildProcess(path.join(curDir, 'internal')); + server.on('connection', function(socket) { + socket.setEncoding('utf8'); + socket.on('data', function(data) { + result = (data.toString() == "success"); + child.kill(); + done(); + }); + }); + + }); + + after(function () { + server.close(); + }); + + it("Window.eval should works",function(done){ + assert.equal(result,true); + done(); + }); +}); + + diff --git a/tests/automation/window-eval/package.json b/tests/automation/window-eval/package.json new file mode 100644 index 0000000000..3b31df6584 --- /dev/null +++ b/tests/automation/window-eval/package.json @@ -0,0 +1,5 @@ +{ + "name":"window_eval_wrapper", + "main":"index.html" +} + diff --git a/tests/automation/window/index.html b/tests/automation/window/index.html new file mode 100644 index 0000000000..f4cb6778bc --- /dev/null +++ b/tests/automation/window/index.html @@ -0,0 +1,59 @@ + + + + + Test Case for 'Window.focus' + + + +

              For now you should manually click the button to test the case

              + + + + + + + diff --git a/tests/automation/window/index1.html b/tests/automation/window/index1.html new file mode 100644 index 0000000000..0c6385bceb --- /dev/null +++ b/tests/automation/window/index1.html @@ -0,0 +1,19 @@ + + + + + new_win1 + + + +

              Please focus this before click the according button .

              + + diff --git a/tests/automation/window/index2.html b/tests/automation/window/index2.html new file mode 100644 index 0000000000..d4f09e42d1 --- /dev/null +++ b/tests/automation/window/index2.html @@ -0,0 +1,10 @@ + + + + + new_win2 + + +

              Please wait to be closed.

              + + diff --git a/tests/automation/window/mocha_test.js b/tests/automation/window/mocha_test.js new file mode 100644 index 0000000000..983cb6b705 --- /dev/null +++ b/tests/automation/window/mocha_test.js @@ -0,0 +1,30 @@ +var path = require('path'); +var assert = require('assert'); + +describe('window', function() { + this.timeout(0); + before(function(done) { + this.timeout(0); + + + var checkDone = function() { + if (test_done) { + done(); + } else { + setTimeout(function() {checkDone();}, 2000);; + } + }; + + checkDone(); + + }); + + describe('focus()', function() { + it('should focus on the window', function() { + for (var i = 0; i < 3; i++) { + console.log('win_c['+i+'] = '+ win_c[i]); + assert.notEqual(win_c[i], "0"); + } + }); + }); +}); diff --git a/tests/automation/window/package.json b/tests/automation/window/package.json new file mode 100644 index 0000000000..1d6d4d34f4 --- /dev/null +++ b/tests/automation/window/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw-window-test", + "main": "index.html" +} diff --git a/tests/index.html b/tests/index.html index 6e8a8b2365..8a83bc7673 100644 --- a/tests/index.html +++ b/tests/index.html @@ -32,7 +32,9 @@ var gui = require('nw.gui'); var path = require('path'); var program = require('commander'); - + global.local_server = require('./server/server'); + var local_server = global.local_server; + var list = new Array(); //socket server var net = require('net'); global.server = net.createServer(); @@ -61,6 +63,7 @@ .option('-A, --async-only', "force all tests to take a callback (async)") .option('-p, --port ', "set the port used by socket") .option('-v, --verbose', "show the test case which is running.") + .option('-l, --list', "list all test cases") .parse([ 'node-webkit', 'nw-test' ].concat(gui.App.argv)); // --silent @@ -106,6 +109,32 @@ if (fs.existsSync(test_file_path)){ var content = fs.readFileSync(test_file_path); + // Get all the test cases + var content_tmp = content + "", content_bet, n, n1 = 0, obj, obj1; + obj1 = new Object(); + obj1.depth = 1; + + while (program.list && content_tmp.length != 0) { + n = content_tmp.search('describe'); + if (n == -1) + break; + n1 = content_tmp.search('{'); + content_bet = (content_tmp + "").slice(n1, n); + var c = content_bet.split('{').length - content_bet.split('}').length; + + content_tmp = content_tmp.slice(n); + n1 = content_tmp.search('{'); + n1 = content_tmp.lastIndexOf(',' ,n1); + obj = new Object(); + obj.test_name = content_tmp.slice(10, n1 - 1); + content_tmp = content_tmp.slice(n1); + obj.depth = obj1.depth + c; + + list.push(obj); + obj1 = obj; + } + + // Simple wrapped 'eval' to setup tests, so: // 1) working directory is the directory of 'index.html'. // 2) tests run in window context. @@ -119,6 +148,18 @@ } } + if (program.list) { + for (var i = 0; i < list.length; i++) { + var split = '-'; + process.stdout.write(new Array(list[i].depth).join(split) + list[i].test_name + '\n'); + } + process.stdout.write("Report total: " + list.length + '\n'); + server.close = function(){} + server.listen = function() {} + process.exit(); + } + + server.on('listening', function(){ // Run! diff --git a/tests/json_reporter.js b/tests/json_reporter.js index ed24d13b39..5dea4f5583 100644 --- a/tests/json_reporter.js +++ b/tests/json_reporter.js @@ -42,9 +42,7 @@ function JSONReporter(runner) { runner.on('end', function(){ var obj = { stats: self.stats - , tests: tests.map(clean) , failures: failures.map(clean) - , passes: passes.map(clean) }; process.stdout.write(JSON.stringify(obj, null, 2)); @@ -65,5 +63,6 @@ function clean(test) { title: test.title , fullTitle: test.fullTitle() , duration: test.duration + , error_stack: test.err.stack.split('\n') } } diff --git a/tests/manual_tests/autocomplete/index.html b/tests/manual_tests/autocomplete/index.html new file mode 100644 index 0000000000..856e30ca42 --- /dev/null +++ b/tests/manual_tests/autocomplete/index.html @@ -0,0 +1,16 @@ + + +

              +

              Test case for GitHub issue #434
              (lack of autocomplete if "id" or "name" attribute is used for input tag)

              +

              +

              + Please type letter "b" in this input field, it should present an autocomplete list with "Bart" entry in it. + + + +

              + + diff --git a/tests/manual_tests/autocomplete/package.json b/tests/manual_tests/autocomplete/package.json new file mode 100644 index 0000000000..2ce54b96a9 --- /dev/null +++ b/tests/manual_tests/autocomplete/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw-test", + "main": "index.html" +} diff --git a/tests/manual_tests/capture_page/index.html b/tests/manual_tests/capture_page/index.html new file mode 100644 index 0000000000..3b9f43407b --- /dev/null +++ b/tests/manual_tests/capture_page/index.html @@ -0,0 +1,58 @@ + + + + + + +
              +

              Please wait 1s for nw to render page

              +

              If you see a capture in popup window, this case passes

              +
              + +
              +
              + + diff --git a/tests/manual_tests/capture_page/package.json b/tests/manual_tests/capture_page/package.json new file mode 100644 index 0000000000..a4b8075bcc --- /dev/null +++ b/tests/manual_tests/capture_page/package.json @@ -0,0 +1,5 @@ +{ + "name":"nw_1403500501", + "main":"index.html", + "dependencies":{} +} \ No newline at end of file diff --git a/tests/manual_tests/capture_page/popup.html b/tests/manual_tests/capture_page/popup.html new file mode 100644 index 0000000000..1b8fe22c83 --- /dev/null +++ b/tests/manual_tests/capture_page/popup.html @@ -0,0 +1,23 @@ + + + Popup window + + + +
              + + +
              +
              + + + + +
              + + \ No newline at end of file diff --git a/tests/manual_tests/custom_tray_menu/custom-tray-menu.html b/tests/manual_tests/custom_tray_menu/custom-tray-menu.html new file mode 100644 index 0000000000..8a122b0e6d --- /dev/null +++ b/tests/manual_tests/custom_tray_menu/custom-tray-menu.html @@ -0,0 +1,17 @@ + + + +

              A custom tray menu

              +

              +

              + + \ No newline at end of file diff --git a/tests/manual_tests/custom_tray_menu/icon.png b/tests/manual_tests/custom_tray_menu/icon.png new file mode 100644 index 0000000000..50d9c17541 Binary files /dev/null and b/tests/manual_tests/custom_tray_menu/icon.png differ diff --git a/tests/manual_tests/custom_tray_menu/index.html b/tests/manual_tests/custom_tray_menu/index.html new file mode 100644 index 0000000000..82441e8b7c --- /dev/null +++ b/tests/manual_tests/custom_tray_menu/index.html @@ -0,0 +1,48 @@ + + + + + \ No newline at end of file diff --git a/tests/manual_tests/custom_tray_menu/package.json b/tests/manual_tests/custom_tray_menu/package.json new file mode 100644 index 0000000000..a61be4902e --- /dev/null +++ b/tests/manual_tests/custom_tray_menu/package.json @@ -0,0 +1,24 @@ +{ + "main": "index.html", + "name": "nw-test", + "description": "test app", + "window": { + "title": "My Node-Webkit App", + "icon": "icon.png", + "toolbar": false, + "frame": true, + "width": 800, + "height": 500, + "always-on-top": false, + "kiosk": false, + "fullscreen": false, + "show": false, + "show_in_taskbar": false, + "resizable": true + }, + "webkit": { + "plugin": false, + "java": false, + "page-cache": false + } +} \ No newline at end of file diff --git a/tests/manual_tests/drag_window/index.html b/tests/manual_tests/drag_window/index.html new file mode 100644 index 0000000000..c5db2bb4f2 --- /dev/null +++ b/tests/manual_tests/drag_window/index.html @@ -0,0 +1,26 @@ + + + + + + + + + +
              +

              Drag me

              +

              Moviable -> Success

              +

              Otherwise -> Fail

              +
              + + + \ No newline at end of file diff --git a/tests/manual_tests/drag_window/package.json b/tests/manual_tests/drag_window/package.json new file mode 100644 index 0000000000..462d8ec405 --- /dev/null +++ b/tests/manual_tests/drag_window/package.json @@ -0,0 +1,8 @@ +{ + "name":"nw_drag_window", + "main":"index.html", + "window":{ + "toolbar":false, + "frame":false + } +} \ No newline at end of file diff --git a/tests/manual_tests/focus/1.html b/tests/manual_tests/focus/1.html new file mode 100644 index 0000000000..a4f3ad55fe --- /dev/null +++ b/tests/manual_tests/focus/1.html @@ -0,0 +1 @@ +hello 1 \ No newline at end of file diff --git a/tests/manual_tests/focus/2.html b/tests/manual_tests/focus/2.html new file mode 100644 index 0000000000..b20684db62 --- /dev/null +++ b/tests/manual_tests/focus/2.html @@ -0,0 +1 @@ +hello 2 \ No newline at end of file diff --git a/tests/manual_tests/focus/index.html b/tests/manual_tests/focus/index.html new file mode 100644 index 0000000000..a05a10155b --- /dev/null +++ b/tests/manual_tests/focus/index.html @@ -0,0 +1,29 @@ + + +Hello World! + + + +

              Hello World!

              + + \ No newline at end of file diff --git a/tests/manual_tests/focus/package.json b/tests/manual_tests/focus/package.json new file mode 100644 index 0000000000..b2a69d4145 --- /dev/null +++ b/tests/manual_tests/focus/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw-demo", + "main": "index.html" +} \ No newline at end of file diff --git a/tests/manual_tests/global_hotkey/index.html b/tests/manual_tests/global_hotkey/index.html new file mode 100644 index 0000000000..b13ad25b0e --- /dev/null +++ b/tests/manual_tests/global_hotkey/index.html @@ -0,0 +1,67 @@ + + + + HotKey Demo + + + + +
              Now you can try to press 'Alt+Shit+A', 'Ctrl+K', 'Alt+B', 'Ctrl+Alt+C', 'MediaPrevTrack', 'MediaNextTrack', 'MediaPlayPause', 'MediaStop'.
              + +
              
              +  
              +
              diff --git a/tests/manual_tests/global_hotkey/package.json b/tests/manual_tests/global_hotkey/package.json
              new file mode 100644
              index 0000000000..58e5f668ae
              --- /dev/null
              +++ b/tests/manual_tests/global_hotkey/package.json
              @@ -0,0 +1,7 @@
              +{
              +  "name" : "Hotkey-demo",
              +  "main" : "index.html",
              +  "window" : {
              +    "width": 850
              +  }
              +}
              diff --git a/tests/manual_tests/http_proxy_auth/index.html b/tests/manual_tests/http_proxy_auth/index.html
              new file mode 100644
              index 0000000000..6551a06818
              --- /dev/null
              +++ b/tests/manual_tests/http_proxy_auth/index.html
              @@ -0,0 +1,41 @@
              +
              +
              +  
              +    
              +    test
              +  
              +  
              +    

              Username: nw

              +

              Password: nw

              +

              If you get no error message in new window, it works fine!

              + + + + \ No newline at end of file diff --git a/tests/manual_tests/http_proxy_auth/package.json b/tests/manual_tests/http_proxy_auth/package.json new file mode 100644 index 0000000000..9c42efd736 --- /dev/null +++ b/tests/manual_tests/http_proxy_auth/package.json @@ -0,0 +1,5 @@ +{ + "name":"nw_1404797845", + "main":"index.html", + "dependencies":{} +} \ No newline at end of file diff --git a/tests/manual_tests/http_proxy_auth/users.htpasswd b/tests/manual_tests/http_proxy_auth/users.htpasswd new file mode 100644 index 0000000000..6c8aa1dd6f --- /dev/null +++ b/tests/manual_tests/http_proxy_auth/users.htpasswd @@ -0,0 +1 @@ +nw:nw \ No newline at end of file diff --git a/tests/manual_tests/menu/index.html b/tests/manual_tests/menu/index.html index 2ad4665f53..d67823ac98 100644 --- a/tests/manual_tests/menu/index.html +++ b/tests/manual_tests/menu/index.html @@ -193,10 +193,9 @@ } })); menubar.append(new gui.MenuItem({ label: 'Sub1', submenu: sub1})); - menubar.append(new gui.MenuItem({ label: 'Sub2', submenu: sub2})); + //menubar.append(new gui.MenuItem({ label: 'Sub2', submenu: sub2})); win.menu = menubar; - gc(); + + + diff --git a/tests/manual_tests/menu_shortcut/package.json b/tests/manual_tests/menu_shortcut/package.json new file mode 100644 index 0000000000..113d160a54 --- /dev/null +++ b/tests/manual_tests/menu_shortcut/package.json @@ -0,0 +1,5 @@ +{ + "name":"test", + "main":"index.html", + "dependencies":{} +} \ No newline at end of file diff --git a/tests/manual_tests/new_win_policy/app/iframe.html b/tests/manual_tests/new_win_policy/app/iframe.html new file mode 100644 index 0000000000..8e8dbe6dac --- /dev/null +++ b/tests/manual_tests/new_win_policy/app/iframe.html @@ -0,0 +1,17 @@ + + + + + test + + +

              iframe

              + IFRAME MANUAL CLICK LINK + + + + \ No newline at end of file diff --git a/tests/manual_tests/new_win_policy/app/index.html b/tests/manual_tests/new_win_policy/app/index.html new file mode 100644 index 0000000000..f8c9fe706b --- /dev/null +++ b/tests/manual_tests/new_win_policy/app/index.html @@ -0,0 +1,61 @@ + + + + + test + + + WINDOW MANUAL CLICK LINK + + + + + \ No newline at end of file diff --git a/tests/manual_tests/new_win_policy/app/package.json b/tests/manual_tests/new_win_policy/app/package.json new file mode 100644 index 0000000000..4a1a3c79a7 --- /dev/null +++ b/tests/manual_tests/new_win_policy/app/package.json @@ -0,0 +1,7 @@ +{ + "name":"nw_1403592293", + "main":"index.html", + "dependencies":{}, + "window":{ + } +} \ No newline at end of file diff --git a/tests/manual_tests/new_win_policy/index.html b/tests/manual_tests/new_win_policy/index.html new file mode 100644 index 0000000000..7bafa87bc3 --- /dev/null +++ b/tests/manual_tests/new_win_policy/index.html @@ -0,0 +1,74 @@ + + + + + test + + + + + +
              +
                +
              • ignore() => ignore the request, navigation won't happen.
              • +
              • forceCurrent() => force the link to be opened in the same frame.
              • +
              • forceDownload() => force the link to be a downloadable, or open by external program.
              • +
              • forceNewWindow() => force the link to be opened in a new window.
              • +
              • forceNewPopup() => force the link to be opened in a new popup window
              • +
              • Please make sure there are no overlapping windows when running this case
              • +
              +
              + +
              +

              From Top Window

              +
              +
              +
              +
              +
              +
              +
              + +
              +

              From Iframe

              +
              +
              +
              +
              +
              +
              +
              + + + + \ No newline at end of file diff --git a/tests/manual_tests/new_win_policy/package.json b/tests/manual_tests/new_win_policy/package.json new file mode 100644 index 0000000000..2dd65fd458 --- /dev/null +++ b/tests/manual_tests/new_win_policy/package.json @@ -0,0 +1,5 @@ +{ + "name":"nw_1403590714", + "main":"index.html", + "dependencies":{} +} \ No newline at end of file diff --git a/tests/manual_tests/notification/index.html b/tests/manual_tests/notification/index.html new file mode 100644 index 0000000000..35434a4cd6 --- /dev/null +++ b/tests/manual_tests/notification/index.html @@ -0,0 +1,68 @@ + + + Notification + + +

              + + + diff --git a/tests/manual_tests/notification/package.json b/tests/manual_tests/notification/package.json new file mode 100644 index 0000000000..7efd8aac98 --- /dev/null +++ b/tests/manual_tests/notification/package.json @@ -0,0 +1,5 @@ +{ + "name": "nw-notification-test", + "main": "index.html", + "app-id": "com.node.webkit.notification.test" +} diff --git a/tests/manual_tests/quit/app/close-handler.js b/tests/manual_tests/quit/app/close-handler.js new file mode 100644 index 0000000000..45be750277 --- /dev/null +++ b/tests/manual_tests/quit/app/close-handler.js @@ -0,0 +1,13 @@ +exports.init = function(win, nwGui) { + console.log('Setting up close handler'); + win.on('close', function(how) { + console.log('Got CLOSE event in close-handler.js with "' + how + '"'); + win.hide(); + var http = require('http'); + http.get("http://localhost:9000/close"); + setTimeout(function() { + console.log('Quitting from close handler'); + nwGui.App.quit(); + }, 5000); + }); +}; diff --git a/tests/manual_tests/quit/app/index-server.html b/tests/manual_tests/quit/app/index-server.html new file mode 100644 index 0000000000..a99f5bae1d --- /dev/null +++ b/tests/manual_tests/quit/app/index-server.html @@ -0,0 +1,23 @@ + + + NW Quit Test Server + + +

              This is a hidden window

              + + + diff --git a/tests/manual_tests/quit/app/index.html b/tests/manual_tests/quit/app/index.html new file mode 100644 index 0000000000..52f5e8fc52 --- /dev/null +++ b/tests/manual_tests/quit/app/index.html @@ -0,0 +1,125 @@ + + + NW Quit Test + + +

              + + + + diff --git a/tests/manual_tests/quit/app/package.json b/tests/manual_tests/quit/app/package.json new file mode 100644 index 0000000000..2247ae95c3 --- /dev/null +++ b/tests/manual_tests/quit/app/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw-quit", + "main": "index.html" +} diff --git a/tests/manual_tests/quit/app/splash.html b/tests/manual_tests/quit/app/splash.html new file mode 100644 index 0000000000..e70567e578 --- /dev/null +++ b/tests/manual_tests/quit/app/splash.html @@ -0,0 +1,5 @@ + + +

              Splash screen

              + + diff --git a/tests/manual_tests/quit/index.html b/tests/manual_tests/quit/index.html new file mode 100644 index 0000000000..bf68312caa --- /dev/null +++ b/tests/manual_tests/quit/index.html @@ -0,0 +1,173 @@ + + + + + test + + + +
              +
                +
              1. Click Quit Button(Automatical)
              2. +
              3. Click native window red dot.(left top or right top)
              4. +
              5. Press COMMAND-Q(MAC ONLY)
              6. +
              7. Press left top system menu node-webkit and click nw-quit.(MAC ONLY)
              8. +
              +
              + +
              + + + + + + + + + + + + +
              MethodcloseHandlerInNodeshowSplashScreenstartServerResult
              +
              + + + + \ No newline at end of file diff --git a/tests/manual_tests/quit/package.json b/tests/manual_tests/quit/package.json new file mode 100644 index 0000000000..113d160a54 --- /dev/null +++ b/tests/manual_tests/quit/package.json @@ -0,0 +1,5 @@ +{ + "name":"test", + "main":"index.html", + "dependencies":{} +} \ No newline at end of file diff --git a/tests/manual_tests/skip_taskbar/app/index.html b/tests/manual_tests/skip_taskbar/app/index.html new file mode 100644 index 0000000000..40468dff9c --- /dev/null +++ b/tests/manual_tests/skip_taskbar/app/index.html @@ -0,0 +1,16 @@ + + + + + test + + +

              + + + + \ No newline at end of file diff --git a/tests/manual_tests/skip_taskbar/index.html b/tests/manual_tests/skip_taskbar/index.html new file mode 100644 index 0000000000..15cf5f6a77 --- /dev/null +++ b/tests/manual_tests/skip_taskbar/index.html @@ -0,0 +1,178 @@ + + + + + + test + + + + + + + +
              +
              + +
              + +
              + +
              + +
              + +
              + +
              + +
              + +
              + +
              + +
              +
              +

              +
              +
              +
              + + + + diff --git a/tests/manual_tests/skip_taskbar/package.json b/tests/manual_tests/skip_taskbar/package.json new file mode 100644 index 0000000000..b3912d1507 --- /dev/null +++ b/tests/manual_tests/skip_taskbar/package.json @@ -0,0 +1,5 @@ +{ + "name":"nw_1403508585", + "main":"index.html", + "dependencies":{} +} \ No newline at end of file diff --git a/tests/manual_tests/test/index.html b/tests/manual_tests/test/index.html index 5d7edf9711..876d92757b 100644 --- a/tests/manual_tests/test/index.html +++ b/tests/manual_tests/test/index.html @@ -27,10 +27,22 @@

              web site

              file dialog

              - nwworkingdir:
              - nwworkingdir with nwdirectory:
              - nwworkingdir with webkitdirectory:
              - nwworkingdir with multiple:
              + +

              nwworkingdir(C:\Windows or /usr/):

              +
              +
              + +

              nwworkingdir(C:\Users or /home) with nwdirectory:

              +
              +
              + +

              nwworkingdir(C:\Users\Public or /sys) with webkitdirectory:

              +
              +
              + +

              nwworkingdir(C:\Windows or /usr/local) with multiple:

              +
              +
              + + diff --git a/tests/manual_tests/visible_on_all_workspaces/index2.html b/tests/manual_tests/visible_on_all_workspaces/index2.html new file mode 100644 index 0000000000..e7ff6d3748 --- /dev/null +++ b/tests/manual_tests/visible_on_all_workspaces/index2.html @@ -0,0 +1,41 @@ + + + Always on Visible Workspace Test - window #2 + + +

              Window #2

              +

              This window is visible only on this workspace

              +
              + Visible on all workspaces +

              Makes the window visible on all workspaces simultaneously.

              +
              +
              + Only on this workspace +

              Makes the window visible only on this workspace.

              +
              +

              + + + diff --git a/tests/manual_tests/visible_on_all_workspaces/package.json b/tests/manual_tests/visible_on_all_workspaces/package.json new file mode 100644 index 0000000000..8c6eadaecd --- /dev/null +++ b/tests/manual_tests/visible_on_all_workspaces/package.json @@ -0,0 +1,8 @@ +{ + "name": "nw-always-on-visible-workspace-test", + "main": "index.html", + "window": { + "visible-on-all-workspaces": true + }, + "dependencies": {} +} diff --git a/tests/manual_tests/webview/index.html b/tests/manual_tests/webview/index.html new file mode 100755 index 0000000000..5820bb835e --- /dev/null +++ b/tests/manual_tests/webview/index.html @@ -0,0 +1,28 @@ + + + + Webview + + + +

              + + + + + + diff --git a/tests/manual_tests/webview/package.json b/tests/manual_tests/webview/package.json new file mode 100755 index 0000000000..61360bc487 --- /dev/null +++ b/tests/manual_tests/webview/package.json @@ -0,0 +1,4 @@ +{ + "name": "WebView", + "main": "index.html" +} diff --git a/tests/nw_test_app/app_test.js b/tests/nw_test_app/app_test.js index 800dfe4aae..c7f2e4e886 100644 --- a/tests/nw_test_app/app_test.js +++ b/tests/nw_test_app/app_test.js @@ -85,7 +85,7 @@ childProcess.prototype.close = function() { * options: * execPath: (string)the path of nw. * appPath: (string)the path of app. - * + * args: (array)the args of the app. * end: (function)we should do the report here, after get child process's result. * data: JSON object * app: nodejs childProcess.spawn @@ -104,12 +104,16 @@ exports.createChildProcess = function(options) { var execPath = options.execPath, path = options.appPath, - exec_argv = [path, '--port', port, '--auto'], + exec_argv, app, cb, no_connect = options.no_connect || false, child = new childProcess(); - + if (!options.args) + exec_argv = [path, '--port', port, '--auto']; + else + exec_argv = [path].concat(options.args).concat(['--port', port, '--auto']); + if (!no_connect) { server.on('connection', cb = function(socket){ diff --git a/tests/package.json b/tests/package.json index 0e251eaa13..b148c1ed24 100644 --- a/tests/package.json +++ b/tests/package.json @@ -7,6 +7,8 @@ "show": false }, "dependencies": { + "http-auth":"2.1.8", + "http-proxy":"1.1.4", "tar": "*", "commander": "0.6.1", "growl": "1.6.x", @@ -20,9 +22,11 @@ "mocha": "1.7.4", "nw_test_loop": "git+https://github.com/owenc4a4/nw_test_loop_without_handle.git", "bignum": "git+https://github.com/owenc4a4/bignum.git", + "node-dtrace-provider": "git+https://github.com/chrisa/node-dtrace-provider.git", + "ref": "git+https://github.com/TooTallNate/ref.git", + "node-lame": "git+https://github.com/TooTallNate/node-lame.git", "fs-extra": "*" }, - "chromium-args": "--disable-javascript-i18n-api", "user-agent": "%name/%nwver/%ver/%webkit_ver/%osinfo", "scripts": { "install": "python ../tools/build_native_modules.py" diff --git a/tests/server/config.js b/tests/server/config.js new file mode 100644 index 0000000000..3f919bad24 --- /dev/null +++ b/tests/server/config.js @@ -0,0 +1,4 @@ +exports.Expires = { + fileMatch: /^(.gif|.png|.jpg|.js|.css)$/ig, + maxAge: 606024365 +}; diff --git a/tests/server/document_cookies.html b/tests/server/document_cookies.html new file mode 100644 index 0000000000..86bc8bebba --- /dev/null +++ b/tests/server/document_cookies.html @@ -0,0 +1,52 @@ + + + + + + +document.cookies test + + + diff --git a/tests/server/img.jpg b/tests/server/img.jpg new file mode 100644 index 0000000000..1bb8351d0f Binary files /dev/null and b/tests/server/img.jpg differ diff --git a/tests/server/index.html b/tests/server/index.html new file mode 100644 index 0000000000..a424b015c2 --- /dev/null +++ b/tests/server/index.html @@ -0,0 +1,21 @@ + + + + + remote file access test + + + +
              + + + \ No newline at end of file diff --git a/tests/automatic_tests/node-remote/node_remote_test.html b/tests/server/node_remote_test.html similarity index 100% rename from tests/automatic_tests/node-remote/node_remote_test.html rename to tests/server/node_remote_test.html diff --git a/tests/server/server.js b/tests/server/server.js new file mode 100644 index 0000000000..9b7d497b3d --- /dev/null +++ b/tests/server/server.js @@ -0,0 +1,87 @@ +var http = require("http"), +url = require("url"), +path = require("path"), +fs = require("fs"), +config = require('./config'); +ports = new Array(8123,8124), +servers = new Array(ports.length); + +var res_save = new Array(); + +types = { + ".css": "text/css", + ".gif": "image/gif", + ".html": "text/html", + ".ico": "image/x-icon", + ".jpeg": "image/jpeg", + ".jpg": "image/jpeg", + ".js": "text/javascript", + ".json": "application/json", + ".pdf": "application/pdf", + ".png": "image/png", + ".svg": "image/svg+xml", + ".swf": "application/x-shockwave-flash", + ".tiff": "image/tiff", + ".txt": "text/plain", + ".wav": "audio/x-wav", + ".wma": "audio/x-ms-wma", + ".wmv": "video/x-ms-wmv", + ".xml": "text/xml" +}; + +function request_listener(req, res) { + var pathname=__dirname+url.parse(req.url).pathname; + console.log("Receive request"); + if (path.extname(pathname)=="") { + pathname+="/"; + } + + if (pathname.charAt(pathname.length-1)=="/"){ + pathname+="index.html"; + } + + path.exists(pathname,function(exists){ + if(exists){ + var ext = path.extname(pathname); + var content_type = types[ext] || "text/plain"; + + res.setHeader("Content-Type", content_type); + + fs.stat(pathname, function (err, stat) { + var last_modified = stat.mtime.toUTCString(); + var if_modified_since = "If-Modified-Since".toLowerCase(); + res.setHeader("Last-Modified", last_modified); + + if (req.headers[if_modified_since] && last_modified == req.headers[if_modified_since]) { + res_save.push({"status": 304, 'pathname':req.url.slice(1)}); + res.writeHead(304, "Not Modified"); + res.end(); + } else { + res_save.push({"status": 200, 'pathname':req.url.slice(1)}); + fs.readFile(pathname,function (err,data){ + res.end(data); + }); + } + }); + } else { + res.writeHead(404, {"Content-Type": "text/html"}); + res.end("

              404 Not Found

              "); + } + }); +} + +function error_handler(e) { + console.log("error code: ", e.code); +} + + + + +for (var i = 0; i < ports.length; i++) { + servers[i] = http.createServer(request_listener); + servers[i].listen(ports[i], "127.0.0.1"); + servers[i].on('error', error_handler); + console.log("Server running at http://127.0.0.1:" + ports[i]); +} + +exports.res_save = res_save; diff --git a/tools/aws_uploader.py b/tools/aws_uploader.py new file mode 100755 index 0000000000..35b3b6fd26 --- /dev/null +++ b/tools/aws_uploader.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +import argparse +import boto +import datetime +import json +import os +import sys +import time + + +# Set timeout, for retry +#if not boto.config.has_section('Boto'): +# boto.config.add_section('Boto') +#boto.config.set('Boto','http_socket_timeout','30') + +################################ +# Parse command line args +parser = argparse.ArgumentParser(description='AWS uploader, please fill in your aws key and id in Boto config (~/.boto)') +parser.add_argument('-p','--path', help='Optional. Where to find the binaries, normally out/Release/dist', required=False) +parser.add_argument('-b','--buildername', help='Builder name, e.g. linux_32bit', required=True) +parser.add_argument('-r','--revision', help='Commit revision',required=True) +parser.add_argument('-n','--number', help='Build number', required=True) +parser.add_argument('-t','--bucket', help='AWS bucket name', required=True) +parser.add_argument('-d','--dlpath', help='AWS bucket path', required=True) + +args = parser.parse_args() + +################################ +# Check and init variables +dist_dir = args.path +builder_name = args.buildername +got_revision = args.revision +build_number = args.number +bucket_name = args.bucket +dlpath = args.dlpath +date = datetime.date.today().strftime('%m-%d-%Y') + +# If the binaries location is not given, calculate it from script related dir. +if dist_dir == None: + dist_dir = os.path.join(os.path.dirname(__file__), + os.pardir, os.pardir, os.pardir, 'out', 'Release') + +dist_dir = os.path.join(dist_dir, 'dist') + +if not os.path.isabs(dist_dir): + dist_dir = os.path.join(os.getcwd(), dist_dir) + +if not os.path.isdir(dist_dir): + print 'Invalid path: ' + dist_dir + exit(-1) +dist_dir = os.path.normpath(dist_dir) + +# it's for S3, so always use '/' here +#upload_path = ''.join(['/' + date, +# '/' + builder_name + '-build-' + build_number + '-' + got_revision]) +upload_path = '/' + dlpath; + +file_list = os.listdir(dist_dir) +if len(file_list) == 0: + print 'Cannot find packages!' + exit(-1) + +# move node-webkit- to the top of the list. +for i in range(len(file_list)): + fname = file_list[i] + if fname.startswith('node-webkit-v'): + del file_list[i] + file_list.insert(0,fname) + break + +def print_progress(transmitted, total): + print ' %d%% transferred of total: %d bytes.' % (transmitted*100/total, total) + sys.stdout.flush() + + +def aws_upload(upload_path, file_list): + conn = boto.connect_s3() + print 'Connecting to S3 ...' + sys.stdout.flush() + bucket = conn.get_bucket(bucket_name) + print 'Uploading to: ' + upload_path + for f in file_list: + print 'Uploading "' + f + '" ...' + sys.stdout.flush() + # use '/' for s3 + path_prefix = '' + if builder_name.startswith("win64") and (f == 'nw.lib' or f == 'nw.exp') : + path_prefix = 'x64' + key = bucket.new_key(upload_path + '/' + path_prefix + '/' + f) + key.set_contents_from_filename(filename=os.path.join(dist_dir, f), cb=print_progress, num_cb=50, replace=True) + +for retry in range(3): + try: + aws_upload(upload_path, file_list) + break + except Exception, e: + print e + sys.stdout.flush() + time.sleep(30) #wait for 30s and try again. + +print 'Done.' + +# vim: et:ts=4:sw=4 diff --git a/tools/build_native_modules.py b/tools/build_native_modules.py index 35f1b97704..f480f46cc6 100755 --- a/tools/build_native_modules.py +++ b/tools/build_native_modules.py @@ -4,6 +4,9 @@ native_modules = ['nw_test_loop_without_handle', 'bignum', + 'dtrace-provider', + 'ref', + 'lame', ]; script_dir = os.path.dirname(__file__) @@ -49,6 +52,22 @@ win = sys.platform in ('win32', 'cygwin') + +#We need to rebuild a submodule in http-auth +apache_crypt_path = os.path.join( + script_dir, + "..", + "tests", + "node_modules", + "http-auth", + "node_modules", + "htpasswd", + "node_modules", + "apache-crypt") +os.chdir(apache_crypt_path) +subprocess.call(exec_args) + + for dir in native_modules: if dir == 'bignum' and win: @@ -61,4 +80,5 @@ #os.execl(node_gyp_script, '', 'build') + os.chdir(cur_dir) diff --git a/tools/commit_id.py b/tools/commit_id.py new file mode 100644 index 0000000000..40aebce038 --- /dev/null +++ b/tools/commit_id.py @@ -0,0 +1,43 @@ +import subprocess as sp +import sys +import os + +# Usage: commit_id.py check (checks if git is present) +# Usage: commit_id.py gen (generates commit id) + +def grab_output(command, cwd): + return sp.Popen(command, stdout=sp.PIPE, shell=True, cwd=cwd).communicate()[0].strip() + +operation = sys.argv[1] +cwd = sys.argv[2] +repos = ['content/nw', '.', 'third_party/WebKit', 'v8', 'third_party/node', 'breakpad/src' ] + +if operation == 'check': + for repo in repos: + index_path = os.path.join(cwd, repo, '.git', 'index') + if not os.path.exists(index_path): + print("0") + sys.exit(0) + print("1") + sys.exit(0) + +output_file = sys.argv[3] +commit_id_size = 7 +final_hash = '' + +for repo in repos: + try: + repo_path = os.path.join(cwd, repo) + final_hash += grab_output('git rev-parse --short=%d HEAD' % commit_id_size, repo_path) + final_hash += '-' + except: + final_hash = 'invalid-hash' + break + +final_hash = final_hash.rstrip('-') +hfile = open(output_file, 'w') + +hfile.write('#define NW_COMMIT_HASH "%s"\n' % final_hash) +hfile.write('#define NW_COMMIT_HASH_SIZE %d\n' % len(final_hash)) + +hfile.close() diff --git a/tools/dump_app_syms b/tools/dump_app_syms new file mode 100755 index 0000000000..11bf0e62ed --- /dev/null +++ b/tools/dump_app_syms @@ -0,0 +1,43 @@ +#!/bin/sh + +# Copyright (c) 2010 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. +# +# Helper script to run dump_syms on Chrome Linux executables and strip +# them if needed. + +set -e + +usage() { + echo -n "$0 " >&2 + echo " " >&2 +} + + +if [ $# -ne 4 ]; then + usage + exit 1 +fi + +SCRIPTDIR="$(readlink -f "$(dirname "$0")")" +DUMPSYMS="$1" +STRIP_BINARY="$2" +INFILE="$3" +OUTFILE="$4" + +# Dump the symbols from the given binary. +if [ ! -e "$OUTFILE" -o "$INFILE" -nt "$OUTFILE" ]; then +echo "bb" + "$DUMPSYMS" -r "$INFILE" > "$OUTFILE" +fi + +if [ "$STRIP_BINARY" != "0" ]; then + strip "$INFILE" +# To avoid dumpping twice. +echo "aa" + touch "$OUTFILE" +fi + + + diff --git a/tools/dump_mac_syms b/tools/dump_mac_syms new file mode 100755 index 0000000000..dd567e1f13 --- /dev/null +++ b/tools/dump_mac_syms @@ -0,0 +1,162 @@ +#!/bin/bash + +# 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. + +# This script expects the following environment variables to be set. Xcode +# normally sets them: +# +# CONFIGURATION - Release or Debug; this script only operates when Release. +# SRCROOT - /path/to/chrome/src/chrome +# BUILT_PRODUTS_DIR - /path/to/chrome/src/xcodebuild/Release +# +# The script also takes a single argument defining the branding type. +# +# To test this script without running an entire build: +# +# cd /path/to/chrome/src/chrome +# CONFIGURATION=Release \ +# SRCROOT=$(pwd) \ +# BUILT_PRODUCTS_DIR=$(pwd)/../xcodebuild/Release \ +# tools/build/mac/dump_app_syms Chromium + +# Make sure we got the header to write into passed to us +#if [ $# -ne 1 ]; then + #echo "error: missing branding as an argument" >&2 + #exit 1 +#fi + +set -ex + +# Skip out if we're aren't in Release mode, no need for dump_syms on debug runs. +if [ "${CONFIGURATION}" != "Release" ] ; then + exit 0 +fi + +TOP="${SRCROOT}/.." +#BUILD_BRANDING=$1 + +#BRAND_SCRIPT="${TOP}/build/branding_value.sh" +#SRC_APP_NAME=$("${BRAND_SCRIPT}" "${BUILD_BRANDING}" PRODUCT_FULLNAME) +SRC_APP_NAME=nwjs +#. "${TOP}/chrome/VERSION" + +BREAKPAD_DUMP_SYMS="${BUILT_PRODUCTS_DIR}/dump_syms" +#FULL_VERSION="${MAJOR}.${MINOR}.${BUILD}.${PATCH}" +FULL_VERSION=$1 + +DSYM_TAR_PATH="${BUILT_PRODUCTS_DIR}/${SRC_APP_NAME}.breakpad.tar" + +# Starting with an already-dumped symbol file at ${original_sym_path}, +# transforms the MODULE line (which must be the first line) from referring to +# ${original_stem} to refer to ${variant_stem}. The transformed symbol file +# is written to a symbol file at the same location that a symbol file would +# be written to if ${variant_name} were in the SRC_NAMES array below. +# +# If the transformed symbol file already appears more recent than +# ${original_sym_path}, it is left alone. +redump_syms_variant() { + local original_sym_path="${1}" + local original_stem="${2}" + local variant_stem="${3}" + local variant_name="${4}" + local arch="${5}" + + local variant_sym_name="${variant_name}-${FULL_VERSION}-${arch}.breakpad" + local variant_sym_path="${BUILT_PRODUCTS_DIR}/${variant_sym_name}" + + if [[ "${original_sym_path}" -nt "${variant_sym_path}" ]]; then + local pattern="\ +1s/^(MODULE [^ ]+ [^ ]+ [0-9a-fA-F]{33}) ${original_stem}\$/\1 ${variant_stem}/" + sed -E -e "${pattern}" < "${original_sym_path}" > "${variant_sym_path}" + fi +} + +declare -a DSYMS + +# Everything in SRC_NAMES is required. It's an error for any of these files +# to be missing. +SRC_NAMES=( + "${SRC_APP_NAME}.app" + "${SRC_APP_NAME} Framework.framework" + "${SRC_APP_NAME} Helper.app" + "crash_inspector" + "crash_report_sender.app" + "ffmpegsumo.so" +) + +# PDF.plugin is optional. Only include it if present. +if [[ -e "${BUILT_PRODUCTS_DIR}/PDF.plugin" ]]; then + SRC_NAMES[${#SRC_NAMES[@]}]="PDF.plugin" +fi + +# libpeerconnection.so is optional. Only include it if present. +if [[ -e "${BUILT_PRODUCTS_DIR}/libpeerconnection.so" ]]; then + SRC_NAMES[${#SRC_NAMES[@]}]="libpeerconnection.so" +fi + +for SRC_NAME in "${SRC_NAMES[@]}"; do + # SRC_STEM is the name of the file within the DWARF directory of the .dSYM + # bundle, which comes from the on-disk name of an executable or dylib within + # its enclosing .app, .framework or .plugin bundle. This is the bundle name + # without .app, .framework or .plugin appended. For non-bundled types, the + # stem is just the name of the singular file on disk. + SRC_STEM=$(echo "${SRC_NAME}" | sed -Ee 's/\.(app|framework|plugin)$//') + DSYM_NAME="${SRC_NAME}.dSYM" + DSYM_PATH="${BUILT_PRODUCTS_DIR}/${DSYM_NAME}" + DWARF_PATH="${DSYM_PATH}/Contents/Resources/DWARF/${SRC_STEM}" + + ARCHS=$(file "${DWARF_PATH}" | sed -Ene 's/^.*(i386|x86_64)$/\1/p') + if [[ -z "${ARCHS}" ]]; then + echo "${0}: expected something dumpable in ${DWARF_PATH}" >& 2 + exit 1 + fi + + for ARCH in ${ARCHS}; do + BPAD_SYM_NAME="${SRC_NAME}-${FULL_VERSION}-${ARCH}.breakpad" + BPAD_SYM_PATH="${BUILT_PRODUCTS_DIR}/${BPAD_SYM_NAME}" + + # Only run dump_syms if the file has changed since the last dump. Use -c + # to avoid dumping CFI, because the Breakpad stackwalk is incompatible + # with CFI produced by clang. + # http://code.google.com/p/google-breakpad/issues/detail?id=443 + if [ "${DWARF_PATH}" -nt "${BPAD_SYM_PATH}" ] ; then + "${BREAKPAD_DUMP_SYMS}" -a "${ARCH}" -c "${DWARF_PATH}" > \ + "${BPAD_SYM_PATH}" + fi + + # Some executables will show up with variant names. The Breakpad symbol + # server looks up modules based on a combination of the module name and + # identifier (UUID). Produce symbol files for these variant names so that + # the Breakpad symbol server will have something to return for stacks that + # travel through these modules. + case "${SRC_NAME}" in + "${SRC_APP_NAME} Helper.app") + # Google Chrome Helper EH and Google Chrome Helper NP are produced by + # build/mac/make_more_helpers.sh. + redump_syms_variant "${BPAD_SYM_PATH}" "${SRC_STEM}" \ + "${SRC_STEM} EH" "${SRC_STEM} EH.app" "${ARCH}" + redump_syms_variant "${BPAD_SYM_PATH}" "${SRC_STEM}" \ + "${SRC_STEM} NP" "${SRC_STEM} NP.app" "${ARCH}" + ;; + esac + done + + # Remove the .dSYM archive if the file has changed since the archive was + # last generated. This will cause a new .dSYM archive to be created. + if [ "${DWARF_PATH}" -nt "${DSYM_TAR_PATH}" ] ; then + rm -f "${DSYM_TAR_PATH}" + fi + + # Push the .dSYM bundle onto the DSYMS array so that it will be included in + # the .dSYM archive if a new one is needed + DSYMS[${#DSYMS[@]}]="${BPAD_SYM_NAME}" +done + +# Create the archive of .dSYM bundles. +if [ ! -e "${DSYM_TAR_PATH}" ] ; then + # Change directory so that absolute paths aren't included in the archive. + (cd "${BUILT_PRODUCTS_DIR}" && + tar -cf "${DSYM_TAR_PATH}" "${DSYMS[@]}") +fi diff --git a/tools/dump_win_syms.py b/tools/dump_win_syms.py new file mode 100644 index 0000000000..29af27e326 --- /dev/null +++ b/tools/dump_win_syms.py @@ -0,0 +1,10 @@ +import sys, os +import subprocess + +nw_exe = os.path.normpath(sys.argv[1]) +sym_file = os.path.normpath(sys.argv[2]) +dump_exe = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', '..', '..', 'breakpad', 'src', 'tools', 'windows', 'binaries', 'dump_syms.exe') +subprocess.call([dump_exe, nw_exe], stdout=open(sym_file, 'w')) +lzma_exe = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', '..', '..', 'third_party', 'lzma_sdk', 'Executable', '7za.exe') +archive_file = sym_file + ".7z" +subprocess.call([lzma_exe, 'a', '-t7z', archive_file, sym_file]) diff --git a/tools/extract_l10n_id.sh b/tools/extract_l10n_id.sh new file mode 100644 index 0000000000..f2211eda39 --- /dev/null +++ b/tools/extract_l10n_id.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +for i in chrome/app/resources/generated_resources_*.xtb; do + fn=`basename $i` + lc=${fn#generated_resources_} + lc=${lc%.xtb} + outf=content/nw/src/resources/locale/nw_strings_$lc.xtb + echo output locale: $lc + cat > $outf < + + +EOF + + while read id; do + echo id: $id + grep $id $i >> $outf + done < content/nw/src/resources/locale/id_list + + cat >> $outf <<"EOF2" + +EOF2 + +done diff --git a/tools/make-nw-headers.py b/tools/make-nw-headers.py new file mode 100644 index 0000000000..27fbde92da --- /dev/null +++ b/tools/make-nw-headers.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +import os +import tarfile +import sys +import getnwisrelease +import getnwversion +import shutil +import distutils.core +import re + +def update_uvh(tmp_dir, header_files): + for file in header_files: + header_f = os.path.join(tmp_dir, 'node', 'src', file) + rfile = open(header_f, 'r') + old = rfile.read() + new = re.sub('third_party/node/deps/uv/include/uv.h', 'uv.h', old, 0) + wfile = open(header_f, 'w') + wfile.write(new) + wfile.close() + return + +script_dir = os.path.dirname(__file__) +nw_root = os.path.normpath(os.path.join(script_dir, os.pardir)) +project_root = os.path.normpath(os.path.join(nw_root, os.pardir, os.pardir)) +third_party_dir = os.path.normpath(os.path.join(project_root, 'third_party')) +tmp_dir = os.path.normpath(os.path.join(nw_root, 'tmp')) + +nw_version = getnwversion.nw_version +if getnwisrelease.release == 0: + nw_version += getnwisrelease.postfix + +#parse command line arguments +''' +-t, the version of nw-headers to download +''' +if '-t' in sys.argv: + nw_version = sys.argv[sys.argv.index('-t') + 1] +tarname = 'nw-headers-v' + nw_version + '.tar.gz' +tarpath = os.path.join(tmp_dir, tarname) + +#make tmpdir +if os.path.exists(tmp_dir): + pass +else: + os.mkdir(tmp_dir) + +# prepare the files to compress +print 'Begin copy file' +base = os.path.join(third_party_dir, 'node') +for dirpath, dirnames, filenames in os.walk(base): + relpath = dirpath.replace(third_party_dir + os.sep, '') + for dirs in dirnames: + if dirs =='gyp' or dirs == 'gyp_addon': + try: + shutil.copytree(os.path.join(dirpath, dirs), os.path.join(tmp_dir, relpath, dirs)) + except: + distutils.dir_util.copy_tree(os.path.join(dirpath, dirs), os.path.join(tmp_dir, relpath, dirs)) + for files in filenames: + if files.endswith('.h') or files.endswith('.gypi') or files == 'gyp' or files == 'gyp_addon': + if not os.path.exists(os.path.join(tmp_dir, relpath)): + os.makedirs(os.path.join(tmp_dir, relpath)) + shutil.copyfile(os.path.join(dirpath, files), os.path.join(tmp_dir, relpath, files)) +shutil.rmtree(os.path.join(tmp_dir, 'node', 'deps', 'v8')) +base = os.path.join(project_root, 'v8') +for dirpath, dirnames, filenames in os.walk(base): + relpath = dirpath.replace(project_root + os.sep, '') + relpath = os.path.join('node', 'deps', relpath) + for dirs in dirnames: + if dirs == 'gyp' or dirs == 'gyp_addon': + try: + shutil.copytree(os.path.join(dirpath, dirs), os.path.join(tmp_dir, relpath, dirs)) + except: + distutils.dir_util.copy_tree(os.path.join(dirpath, dirs), os.path.join(tmp_dir, relpath, dirs)) + for files in filenames: + if files.endswith('.h') or files.endswith('.gypi') or files == 'gyp' or files == 'gyp_addon': + if not os.path.exists(os.path.join(tmp_dir, relpath)): + os.makedirs(os.path.join(tmp_dir, relpath)) + shutil.copyfile(os.path.join(dirpath, files), os.path.join(tmp_dir, relpath, files)) +if os.path.exists(os.path.join(tmp_dir, 'node', 'deps', 'v8', 'build')): + shutil.rmtree(os.path.join(tmp_dir, 'node', 'deps', 'v8', 'build')) +if os.path.exists(os.path.join(tmp_dir, 'node', 'deps', 'v8', 'test')): + shutil.rmtree(os.path.join(tmp_dir, 'node', 'deps', 'v8', 'test')) +if os.path.exists(os.path.join(tmp_dir, 'node', 'deps', 'v8', 'out')): + shutil.rmtree(os.path.join(tmp_dir, 'node', 'deps', 'v8', 'out')) +if os.path.exists(os.path.join(tmp_dir, 'node', 'deps', 'npm', 'node_modules')): + shutil.rmtree(os.path.join(tmp_dir, 'node', 'deps', 'npm', 'node_modules')) + +header_files = ['node.h', 'env.h', 'env-inl.h'] +update_uvh(tmp_dir, header_files) + +print 'copy file end' +print 'Begin compress file' + +with tarfile.open(tarpath, 'w:gz') as tar: + tar.add(os.path.join(tmp_dir, 'node'), arcname='node') + +print 'compress end' diff --git a/tools/make-nw-headers.sh b/tools/make-nw-headers.sh new file mode 100644 index 0000000000..8d2a86ab12 --- /dev/null +++ b/tools/make-nw-headers.sh @@ -0,0 +1,19 @@ +mkdir -p tmp +tmpdir=$PWD/tmp +pushd third_party +find node -iname '*.h' -or -iname '*.gypi' -or -iname gyp -or -iname gyp_addon | xargs tar cf - | (cd $tmpdir; tar xf - ) +popd +rm -rf $tmpdir/node/deps/v8 +find v8 -iname '*.h' -or -iname '*.gypi' -or -iname gyp -or -iname gyp_addon | xargs tar cf - | (cd $tmpdir/node/deps; tar xf - ) +rm -rf $tmpdir/node/deps/v8/build +rm -rf $tmpdir/node/deps/v8/test +rm -rf $tmpdir/node/deps/v8/out +rm -rf $tmpdir/node/deps/npm/node_modules + +for h in env.h env-inl.h node.h; do +cat $tmpdir/node/src/$h | sed -e 's|third_party/node/deps/uv/include/uv.h|uv.h|' > tmp_$h && mv tmp_$h $tmpdir/node/src/$h +done + +pushd tmp +tar czf ../nw-headers-v0.11.0-rc1.tar.gz node +popd diff --git a/tools/package_binaries.py b/tools/package_binaries.py index 4887bc66dd..cd1b8540cb 100755 --- a/tools/package_binaries.py +++ b/tools/package_binaries.py @@ -1,183 +1,378 @@ #!/usr/bin/env python +import argparse +import getnwisrelease +import getnwversion +import gzip import os +import platform import shutil -import tarfile import sys +import tarfile +import zipfile + +from subprocess import call + +steps = ['nw', 'chromedriver', 'symbol', 'headers', 'others'] +################################ +# Parse command line args +parser = argparse.ArgumentParser(description='Package nw binaries.') +parser.add_argument('-p','--path', help='Where to find the binaries, like out/Release', required=False) +parser.add_argument('-a','--arch', help='target arch', required=False) +parser.add_argument('-m','--mode', help='package mode', required=False) +group = parser.add_mutually_exclusive_group() +group.add_argument('-s','--step', choices=steps, help='Execute specified step.', required=False) +group.add_argument('-n','--skip', choices=steps, help='Skip specified step.', required=False) +args = parser.parse_args() + +################################ +# Init variables. +binaries_location = None # .../out/Release +platform_name = None # win/linux/osx +arch = None # ia32/x64 +step = None # nw/chromedriver/symbol +skip = None +nw_ver = None # x.xx +dist_dir = None # .../out/Release/dist + +package_name = 'nwjs' + +if args.mode == 'mas': + package_name = 'nwjs-macappstore' + +step = args.step +skip = args.skip +binaries_location = args.path +# If the binaries location is not given, calculate it from script related dir. +if binaries_location == None: + binaries_location = os.path.join(os.path.dirname(__file__), + os.pardir, os.pardir, os.pardir, 'out', 'Release') + +if not os.path.isabs(binaries_location): + binaries_location = os.path.join(os.getcwd(), binaries_location) + +if not os.path.isdir(binaries_location): + print 'Invalid path: ' + binaries_location + exit(-1) +binaries_location = os.path.normpath(binaries_location) +dist_dir = os.path.join(binaries_location, 'dist') + +print 'Working on ' + binaries_location - -script_dir = os.path.dirname(__file__) -nw_root = os.path.normpath(os.path.join(script_dir, os.pardir)) -project_root = os.path.normpath(os.path.join(nw_root, os.pardir, os.pardir)); -#default project path -project_root = os.path.join(project_root, 'out', 'Release') - - -#parse command line arguments -""" --p , the absolute path of executable files -""" -if '-p' in sys.argv: - tmp = sys.argv[sys.argv.index('-p') + 1] - if not os.path.isabs(tmp): - print 'the path is not an absolute path.\n' - exit() - - if not os.path.exists(tmp): - print 'the directory does not exist.\n' - exit() - - project_root = tmp - - - -#get platform information if sys.platform.startswith('linux'): - platform_name = 'linux' - -if sys.platform in ('win32', 'cygwin'): - platform_name = 'win' - -if sys.platform == 'darwin': - platform_name = 'osx' - -#judge whether the target exist or not -if platform_name == 'linux' and not os.path.exists( - os.path.join(project_root, 'nw')): - print 'nw file does not exist.\n' - exit() - -if platform_name == 'win' and not os.path.exists( - os.path.join(project_root, 'nw.exe')): - print 'nw file does not exist.\n' - exit() - -if platform_name == 'osx' and not os.path.exists( - os.path.join(project_root, 'node-webkit.app')): - print 'nw file does not exist.\n' - exit() - -required_file_linux = ( - 'nw', - 'nw.pak', - 'libffmpegsumo.so', - 'nwsnapshot', -) - -required_file_win = ( - 'ffmpegsumo.dll', - 'icudt.dll', - 'libEGL.dll', - 'libGLESv2.dll', - 'nw.exe', - 'nw.pak', - 'nwsnapshot.exe', -) - -required_file_mac = ( - 'node-webkit.app', - 'nwsnapshot', -) - - -if (platform_name == 'linux'): - required_file = required_file_linux - -if (platform_name == 'win'): - required_file = required_file_win - -if (platform_name == 'osx'): - required_file = required_file_mac - -#generate binary tar name -import getnwisrelease -import getnwversion -import platform - -nw_version = 'v' + getnwversion.nw_version -is_release = getnwisrelease.release -if is_release == 0: - nw_version += getnwisrelease.postfix - -bits = platform.architecture()[0] - -if bits == '64bit': - arch = 'x64' + platform_name = 'linux' +elif sys.platform in ('win32', 'cygwin'): + platform_name = 'win' +elif sys.platform == 'darwin': + platform_name = 'osx' else: - arch = 'ia32' - - -tarname = 'node-webkit-' + nw_version - -binary_name = tarname + '-' + platform_name + '-' + arch -binary_tar = binary_name + '.tar.gz' - -#use zip in mac and windows -if platform_name in ('win', 'osx'): - binary_tar = binary_name + '.zip' - - -#make directory for binary_tar -binary_store_path = os.path.join(project_root, - 'node-webkit-binaries') - - -if not os.path.exists(binary_store_path): - os.mkdir(binary_store_path) - - -binary_full_path = os.path.join(binary_store_path, binary_name) -binary_tar_full_path = os.path.join(binary_store_path, binary_tar) - - -if os.path.exists(binary_full_path): - shutil.rmtree(binary_full_path) - -os.mkdir(binary_full_path) - - -#copy file to binary -print 'Begin copy file.' -for file in required_file: - shutil.copy(os.path.join(project_root, file), - os.path.join(binary_full_path, file)) - -print 'copy file end.\n' - -if (os.path.isfile(binary_tar_full_path)): - os.remove(binary_tar_full_path) - - -print 'Begin compress file' - - -if platform_name in ('win', 'osx'): - """ - If there are sub directors, this should be modified - """ - import zipfile - zip = zipfile.ZipFile(binary_tar_full_path, 'w', - compression=zipfile.ZIP_DEFLATED) - - for dirpath, dirnames, filenames in os.walk(binary_full_path): - for name in filenames: - path = os.path.normpath(os.path.join(dirpath, name)) - - if os.path.isfile(path): - zip.write(path, os.path.join(os.path.basename(binary_full_path), name)) - - - zip.close() - + print 'Unsupported platform: ' + sys.platform + exit(-1) + +_arch = platform.architecture()[0] +if _arch == '64bit': + arch = 'x64' +elif _arch == '32bit': + arch = 'ia32' else: - tar = tarfile.open(binary_tar_full_path, 'w:gz') - tar.add(binary_full_path, os.path.basename(binary_full_path)) - tar.close() - - -print 'compress file end.\n' - - -print 'the binaries files store in path:', os.path.normpath( - os.path.join(os.getcwd(), binary_store_path)) - - + print 'Unsupported arch: ' + _arch + exit(-1) + +if platform_name == 'win': + arch = 'ia32' + +if platform_name == 'osx': + # detect output arch + nw_bin = binaries_location + '/nwjs.app/Contents/MacOS/nwjs' + import subprocess + if 'i386' in subprocess.check_output(['file',nw_bin]): + arch = 'ia32' + else: # should be 'x86_64' + arch = 'x64' + +if args.arch != None: + arch = args.arch + +nw_ver = getnwversion.nw_version +if getnwisrelease.release == 0: + nw_ver += getnwisrelease.postfix + +################################ +# Generate targets +# +# target example: +# { +# 'input' : [ 'nw', 'nw.pak', ... ] +# 'output' : 'nwjs-v0.9.2-linux-x64' +# 'compress' : 'tar.gz' +# 'folder' : True # Optional. More than 2 files will be put into a seprate folder +# # normally, if you want do to this for only 1 file, set this flag. +# } +def generate_target_nw(platform_name, arch, version): + target = {} + # Output + target['output'] = ''.join([ + package_name, '-', + 'v', version, + '-', platform_name, + '-', arch]) + # Compress type + if platform_name == 'linux': + target['compress'] = 'tar.gz' + else: + target['compress'] = 'zip' + # Input + if platform_name == 'linux': + target['input'] = [ + 'credits.html', + 'libffmpegsumo.so', + 'nw.pak', + 'nwjc', + 'nw', + 'icudtl.dat', + 'locales', + ] + elif platform_name == 'win': + target['input'] = [ + 'd3dcompiler_47.dll', + 'ffmpegsumo.dll', + 'icudtl.dat', + 'libEGL.dll', + 'libGLESv2.dll', + 'pdf.dll', + 'nw.exe', + 'nw.pak', + 'locales', + 'nwjc.exe', + 'credits.html', + ] + elif platform_name == 'osx': + target['input'] = [ + 'nwjs.app', + 'nwjc', + 'credits.html', + ] + else: + print 'Unsupported platform: ' + platform_name + exit(-1) + return target + +def generate_target_chromedriver(platform_name, arch, version): + if args.mode == 'mas': + return generate_target_empty(platform_name, arch, version) + + target = {} + # Output + target['output'] = ''.join([ + 'chromedriver-nw-', + 'v', version, + '-', platform_name, + '-', arch]) + # Compress type + if platform_name == 'linux': + target['compress'] = 'tar.gz' + else: + target['compress'] = 'zip' + # Input + if platform_name == 'win': + target['input'] = ['chromedriver.exe'] + else: + target['input'] = ['chromedriver'] + target['folder'] = True # always create a folder + return target + +def generate_target_symbols(platform_name, arch, version): + target = {} + target['output'] = ''.join([package_name, '-symbol-', + 'v', version, + '-', platform_name, + '-', arch]) + if platform_name == 'linux': + target['compress'] = 'tar.gz' + target['input'] = ['nw.breakpad.' + arch] + target['folder'] = True + elif platform_name == 'win': + target['compress'] = None + target['input'] = ['nw.sym.7z'] + target['output'] = ''.join([package_name, '-symbol-', + 'v', version, + '-', platform_name, + '-', arch, '.7z']) + elif platform_name == 'osx': + target['compress'] = 'zip' + target['input'] = [ + 'nwjs.breakpad.tar' + ] + target['folder'] = True + else: + print 'Unsupported platform: ' + platform_name + exit(-1) + return target + +def generate_target_headers(platform_name, arch, version): + # here, call make_nw_header tool to generate headers + # then, move to binaries_location + target = {} + target['output'] = '' + target['compress'] = None + if platform_name == 'osx': + target['input'] = [] + # here , call make-nw-headers.py to generate nw headers + make_nw_header = os.path.join(os.path.dirname(__file__), \ + 'make-nw-headers.py') + print make_nw_header + res = call(['python', make_nw_header]) + if res == 0: + print 'nw-headers generated' + nw_headers_name = 'nw-headers-v' + version + '.tar.gz' + nw_headers_path = os.path.join(os.path.dirname(__file__), \ + os.pardir, 'tmp', nw_headers_name) + if os.path.isfile(os.path.join(binaries_location, nw_headers_name)): + os.remove(os.path.join(binaries_location, nw_headers_name)) + shutil.move(nw_headers_path, binaries_location) + target['input'].append(nw_headers_name) + else: + #TODO, handle err + print 'nw-headers generate failed' + elif platform_name == 'win': + target['input'] = [] + elif platform_name == 'linux': + target['input'] = [] + else: + print 'Unsupported platform: ' + platform_name + exit(-1) + return target + +def generate_target_empty(platform_name, arch, version): + target = {} + target['output'] = '' + target['compress'] = None + if platform_name == 'win': + target['input'] = [] + elif platform_name == 'linux' : + target['input'] = [] + else: + target['input'] = [] + return target + +def generate_target_others(platform_name, arch, version): + target = {} + target['output'] = '' + target['compress'] = None + if platform_name == 'win': + target['input'] = ['nw.exp', 'nw.lib'] + elif platform_name == 'linux' : + target['input'] = [] + else: + target['input'] = [] + return target + + +################################ +# Make packages +def compress(from_dir, to_dir, fname, compress): + from_dir = os.path.normpath(from_dir) + to_dir = os.path.normpath(to_dir) + _from = os.path.join(from_dir, fname) + _to = os.path.join(to_dir, fname) + if compress == 'zip': + z = zipfile.ZipFile(_to + '.zip', 'w', compression=zipfile.ZIP_DEFLATED) + if os.path.isdir(_from): + for root, dirs, files in os.walk(_from): + for f in files: + _path = os.path.join(root, f) + z.write(_path, _path.replace(from_dir+os.sep, '')) + else: + z.write(_from, fname) + z.close() + elif compress == 'tar.gz': # only for folders + if not os.path.isdir(_from): + print 'Will not create tar.gz for a single file: ' + _from + exit(-1) + with tarfile.open(_to + '.tar.gz', 'w:gz') as tar: + tar.add(_from, arcname=os.path.basename(_from)) + elif compress == 'gz': # only for single file + if os.path.isdir(_from): + print 'Will not create gz for a folder: ' + _from + exit(-1) + f_in = open(_from, 'rb') + f_out = gzip.open(_to + '.gz', 'wb') + f_out.writelines(f_in) + f_out.close() + f_in.close() + else: + print 'Unsupported compression format: ' + compress + exit(-1) + + +def make_packages(targets): + # check file existance + for t in targets: + for f in t['input']: + src = os.path.join(binaries_location, f) + if not os.path.exists(src): + print 'File does not exist: ', src + exit(-1) + + # clear the output folder + if os.path.exists(dist_dir): + if not os.path.isdir(dist_dir): + print 'Invalid path: ' + dist_dir + exit(-1) + else: + shutil.rmtree(dist_dir) + + # now let's do it + os.mkdir(dist_dir) + for t in targets: + if len(t['input']) == 0: + continue + if t['compress'] == None: + for f in t['input']: + src = os.path.join(binaries_location, f) + if t['output'] != '': + dest = os.path.join(dist_dir, t['output']) + else: + dest = os.path.join(dist_dir, f) + print "Copying " + f + shutil.copy(src, dest) + elif (t.has_key('folder') and t['folder'] == True) or len(t['input']) > 1: + print 'Making "' + t['output'] + '.' + t['compress'] + '"' + # copy files into a folder then pack + folder = os.path.join(dist_dir, t['output']) + os.mkdir(folder) + for f in t['input']: + src = os.path.join(binaries_location, f) + dest = os.path.join(folder, f) + if os.path.isdir(src): # like nw.app + shutil.copytree(src, dest) + else: + shutil.copy(src, dest) + compress(dist_dir, dist_dir, t['output'], t['compress']) + # remove temp folders + shutil.rmtree(folder) + else: + # single file + print 'Making "' + t['output'] + '.' + t['compress'] + '"' + compress(binaries_location, dist_dir, t['input'][0], t['compress']) + +# must be aligned with steps +generators = {} +generators['nw'] = generate_target_nw +generators['chromedriver'] = generate_target_chromedriver +generators['symbol'] = generate_target_symbols +generators['headers'] = generate_target_headers +generators['others'] = generate_target_others +################################ +# Process targets +targets = [] +for s in steps: + if (step != None) and (s != step): + continue + if (skip != None) and (s == skip): + continue + targets.append(generators[s](platform_name, arch, nw_ver)) + +print 'Creating packages...' +make_packages(targets) + +# vim: et:ts=4:sw=4