diff --git a/AUTHORS b/AUTHORS index d334c270bd..0679037506 100644 --- a/AUTHORS +++ b/AUTHORS @@ -24,3 +24,16 @@ 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 index 23695737c9..55766cde31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,216 @@ +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 diff --git a/LICENSE b/LICENSE index 3a4009653d..ba52b7c91d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ -Copyright (c) 2012-2014 Intel Corp -Copyright (c) 2012-2014 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 1bef16f610..5f1c5ba13c 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,55 @@ +## 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 +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 the 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) [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) +[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.9.2 release notes](https://groups.google.com/d/msg/node-webkit/qpBhcWr-hSc/caGjhtl8cEgJ) - -Prebuilt binaries (v0.9.2 - Feb 20, 2014): +* **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) -* Linux: [32bit](http://dl.node-webkit.org/v0.9.2/node-webkit-v0.9.2-linux-ia32.tar.gz) / [64bit] (http://dl.node-webkit.org/v0.9.2/node-webkit-v0.9.2-linux-x64.tar.gz) -* Windows: [win32](http://dl.node-webkit.org/v0.9.2/node-webkit-v0.9.2-win-ia32.zip) -* Mac: [32bit, 10.7+](http://dl.node-webkit.org/v0.9.2/node-webkit-v0.9.2-osx-ia32.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) -**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/node-webkit/2OJ1cEMPLlA/09BvpTagSA0J)** -[v0.8.5 release notes](https://groups.google.com/d/msg/node-webkit/Izwu5icHFOQ/0A-3uEKfldkJ) +* **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.8.5 - Feb 26, 2014): + * 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](http://dl.node-webkit.org/v0.8.5/node-webkit-v0.8.5-linux-ia32.tar.gz) / [64bit] (http://dl.node-webkit.org/v0.8.5/node-webkit-v0.8.5-linux-x64.tar.gz) -* Windows: [win32](http://dl.node-webkit.org/v0.8.5/node-webkit-v0.8.5-win-ia32.zip) -* Mac: [32bit, 10.7+](http://dl.node-webkit.org/v0.8.5/node-webkit-v0.8.5-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 @@ -64,32 +73,20 @@ Create `package.json`: ```json { "name": "nw-demo", + "version": "0.0.1", "main": "index.html" } ``` -Compress `index.html` and `package.json` into a zip archive called `app.nw`: - -````bash -$ zip app.nw index.html package.json -```` - -This should create a structure like this: - -``` -app.nw -|-- package.json -`-- index.html +Run: +```bash +$ /path/to/nw . (suppose the current directory contains 'package.json') ``` -Download the prebuilt binary for your platform and use it to open the -`app.nw` file: +Note: on Windows, you can drag the folder containing `package.json` to `nw.exe` to open it. -````bash -$ ./nw app.nw -```` - -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 @@ -103,14 +100,19 @@ And our [Wiki](https://github.com/rogerwang/node-webkit/wiki) for much more. ## Community -We use the [node-webkit group](http://groups.google.com/group/node-webkit) as -our mailing list (use English only). Subscribe via [node-webkit+subscribe@googlegroups.com](mailto:node-webkit+subscribe@googlegroups.com). -Issues are being tracked here on GitHub. +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). -You can chat with us on IRC in the ##node-webkit channel on irc.freenode.net +*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 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) -[![Analytics](https://ga-beacon.appspot.com/UA-27805459-2/node-webkit/index)](https://github.com/igrigorik/ga-beacon) +## 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 11af9d460d..0b4bc0f863 100644 --- a/nw.gypi +++ b/nw.gypi @@ -4,14 +4,28 @@ { 'variables': { - 'nw_product_name': 'node-webkit', - 'conditions': [ - ['OS=="linux"', { - 'linux_dump_symbols%': 1, - }], - ], + '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%': '& rphs) { } } +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 @@ -76,23 +108,22 @@ void App::Call(const std::string& method, const base::ListValue& arguments) { if (method == "Quit") { Quit(); - return; } 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(Shell* shell, const std::string& method, const base::ListValue& arguments, - base::ListValue* result) { + base::ListValue* result, + DispatcherHost* dispatcher_host) { if (method == "GetDataPath") { ShellBrowserContext* browser_context = static_cast(shell->web_contents()->GetBrowserContext()); @@ -119,13 +150,65 @@ void App::Call(Shell* shell, } 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); - result->AppendBoolean(SetCrashDumpPath(path.c_str())); + //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; } @@ -216,4 +299,29 @@ void App::ClearCache(content::RenderProcessHost* render_process_host) { render_process_host->GetID()); } +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 9cee82ab93..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 @@ -44,7 +45,8 @@ 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(bool force = false, bool quit = false); @@ -60,6 +62,9 @@ class App { 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(); diff --git a/src/api/app/app.js b/src/api/app/app.js index bb61d736b5..7ff009ffe3 100644 --- a/src/api/app/app.js +++ b/src/api/app/app.js @@ -19,16 +19,17 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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() { @@ -52,14 +53,50 @@ App.prototype.setCrashDumpDir = function(dir) { 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; diff --git a/src/api/bindings_common.cc b/src/api/bindings_common.cc index be1e1c74c7..fbe444edc9 100644 --- a/src/api/bindings_common.cc +++ b/src/api/bindings_common.cc @@ -27,19 +27,20 @@ #include "content/public/renderer/render_thread.h" #include "content/public/renderer/v8_value_converter.h" #include "third_party/WebKit/public/web/WebView.h" -#include "third_party/WebKit/public/web/WebFrame.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; +using blink::WebFrame; +using blink::WebLocalFrame; +using blink::WebView; namespace { RenderView* GetRenderView(v8::Handle ctx) { - WebFrame* frame = WebFrame::frameForContext(ctx); - if (!frame) + WebLocalFrame* frame = WebLocalFrame::frameForContext(ctx); + if (!frame || !frame->isNodeJS()) return NULL; WebView* view = frame->view(); @@ -53,12 +54,14 @@ RenderView* GetRenderView(v8::Handle ctx) { } RenderView* GetCurrentRenderView() { - v8::Local ctx = v8::Context::GetCurrent(); + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Local ctx = isolate->GetCurrentContext(); return GetRenderView(ctx); } RenderView* GetEnteredRenderView() { - v8::Local ctx = v8::Context::GetEntered(); + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Local ctx = isolate->GetEnteredContext(); return GetRenderView(ctx); } @@ -69,29 +72,31 @@ base::StringPiece GetStringResource(int resource_id) { namespace remote { v8::Handle AllocateId(int routing_id) { - v8::HandleScope scope; + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::EscapableHandleScope scope(isolate); int result = 0; RenderThread::Get()->Send(new ShellViewHostMsg_AllocateId( routing_id, &result)); - return scope.Close(v8::Integer::New(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 << ")"; @@ -101,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, @@ -116,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( @@ -131,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, @@ -139,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; @@ -156,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/clipboard/clipboard.cc b/src/api/clipboard/clipboard.cc index 03ccd2d070..399d13616c 100644 --- a/src/api/clipboard/clipboard.cc +++ b/src/api/clipboard/clipboard.cc @@ -73,9 +73,9 @@ void Clipboard::SetText(std::string& text) { std::string Clipboard::GetText() { ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); - string16 text; + base::string16 text; clipboard->ReadText(ui::CLIPBOARD_TYPE_COPY_PASTE, &text); - return UTF16ToUTF8(text); + return base::UTF16ToUTF8(text); } void Clipboard::Clear() { diff --git a/src/api/clipboard/clipboard.h b/src/api/clipboard/clipboard.h index e0ed72f622..3d8451387f 100644 --- a/src/api/clipboard/clipboard.h +++ b/src/api/clipboard/clipboard.h @@ -31,13 +31,13 @@ class Clipboard : public Base { Clipboard(int id, 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); 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 1e411a00b2..edfc4965ae 100644 --- a/src/api/dispatcher.cc +++ b/src/api/dispatcher.cc @@ -29,25 +29,28 @@ #undef CHECK #include "third_party/node/src/req_wrap.h" #include "third_party/WebKit/public/web/WebDocument.h" -#include "third_party/WebKit/public/web/WebFrame.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" #include "third_party/WebKit/public/web/WebView.h" #include "v8/include/v8.h" #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/WebFrameImpl.h" +#include "third_party/WebKit/Source/web/WebLocalFrameImpl.h" #include "V8HTMLElement.h" namespace nwapi { static inline v8::Local v8_str(const char* x) { - return v8::String::New(x); + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + return v8::String::NewFromUtf8(isolate, x); } Dispatcher::Dispatcher(content::RenderView* render_view) @@ -67,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) { @@ -83,8 +86,9 @@ 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; @@ -92,48 +96,56 @@ void Dispatcher::OnEvent(int object_id, content::V8ValueConverterImpl converter; v8::Local context = - v8::Local::New(node::g_context->GetIsolate(), node::g_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_str(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_str("__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 - node::g_context->Enter(); + context->Enter(); v8::Handle registry = - node::g_context->Global()->Get(v8_str("__nwObjectsRegistry")); - node::g_context->Exit(); + context->Global()->Get(v8_str("__nwObjectsRegistry")); + context->Exit(); + ASSERT(!(registry->IsNull() || registry->IsUndefined())); // if (registry->IsNull() || registry->IsUndefined()) // return v8::Undefined(); return registry->ToObject(); } -v8::Handle Dispatcher::GetWindowId(WebKit::WebFrame* frame) { +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() { - WebKit::WebView* web_view = render_view()->GetWebView(); +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 val = GetWindowId(web_view->mainFrame()); + if (val.IsEmpty()) + return; if (val->IsNull() || val->IsUndefined()) return; @@ -141,29 +153,33 @@ void Dispatcher::ZoomLevelChanged() { if (objects_registry->IsUndefined()) return; - v8::Local args = v8::Array::New(); - args->Set(0, v8::Number::New(zoom_level)); + 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(objects_registry, "handleEvent", 3, argv); + node::MakeCallback(isolate, objects_registry, "handleEvent", 3, argv); } -void Dispatcher::DidCreateDocumentElement(WebKit::WebFrame* frame) { +void Dispatcher::DidCreateDocumentElement(blink::WebLocalFrame* frame) { documentCallback("document-start", frame); } -void Dispatcher::DidFinishDocumentLoad(WebKit::WebFrame* frame) { +void Dispatcher::DidFinishDocumentLoad(blink::WebLocalFrame* frame) { documentCallback("document-end", frame); } -void Dispatcher::documentCallback(const char* ev, WebKit::WebFrame* frame) { - WebKit::WebView* web_view = render_view()->GetWebView(); +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; if (val->IsNull() || val->IsUndefined()) return; @@ -171,48 +187,58 @@ void Dispatcher::documentCallback(const char* ev, WebKit::WebFrame* frame) { if (objects_registry->IsUndefined()) return; - v8::Local args = v8::Array::New(); - v8::Handle element = v8::Null(); - WebCore::Frame* core_frame = WebKit::toWebFrameImpl(frame)->frame(); - if (core_frame->ownerElement()) { - element = WebCore::toV8((WebCore::HTMLElement*)core_frame->ownerElement(), + 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(objects_registry, "handleEvent", 3, argv); + node::MakeCallback(isolate, objects_registry, "handleEvent", 3, argv); } void Dispatcher::willHandleNavigationPolicy( content::RenderView* rv, - WebKit::WebFrame* frame, - const WebKit::WebURLRequest& request, - WebKit::WebNavigationPolicy* policy) { + blink::WebFrame* frame, + const blink::WebURLRequest& request, + blink::WebNavigationPolicy* policy, + blink::WebString* manifest) { - WebKit::WebView* web_view = rv->GetWebView(); + blink::WebView* web_view = rv->GetWebView(); if (!web_view) return; - v8::Context::Scope cscope (web_view->mainFrame()->mainWorldScriptContext()); + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope handleScope(isolate); - v8::Handle id_val = nwapi::Dispatcher::GetWindowId(web_view->mainFrame()); - if (id_val->IsNull() || id_val->IsUndefined()) + 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::Local args = v8::Array::New(); - v8::Handle element = v8::Null(); - v8::Handle policy_obj = v8::Object::New(); + 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); - WebCore::Frame* core_frame = WebKit::toWebFrameImpl(frame)->frame(); - if (core_frame->ownerElement()) { - element = WebCore::toV8((WebCore::HTMLElement*)core_frame->ownerElement(), + 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()); } @@ -222,21 +248,30 @@ void Dispatcher::willHandleNavigationPolicy( 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 = WebKit::WebNavigationPolicyIgnore; + *policy = blink::WebNavigationPolicyIgnore; else if (!strcmp(*policy_str, "download")) - *policy = WebKit::WebNavigationPolicyDownload; + *policy = blink::WebNavigationPolicyDownload; else if (!strcmp(*policy_str, "current")) - *policy = WebKit::WebNavigationPolicyCurrentTab; + *policy = blink::WebNavigationPolicyCurrentTab; else if (!strcmp(*policy_str, "new-window")) - *policy = WebKit::WebNavigationPolicyNewWindow; + *policy = blink::WebNavigationPolicyNewWindow; else if (!strcmp(*policy_str, "new-popup")) - *policy = WebKit::WebNavigationPolicyNewPopup; + *policy = blink::WebNavigationPolicyNewPopup; } } // namespace nwapi diff --git a/src/api/dispatcher.h b/src/api/dispatcher.h index 4a59932d57..0193f55d66 100644 --- a/src/api/dispatcher.h +++ b/src/api/dispatcher.h @@ -25,6 +25,7 @@ #include "content/public/renderer/render_view_observer.h" #include "third_party/WebKit/public/web/WebNavigationPolicy.h" #include +#include namespace base { class ListValue; @@ -34,9 +35,10 @@ namespace content { class RenderView; } -namespace WebKit { +namespace blink { class WebFrame; class WebURLRequest; +class WebView; } namespace nwapi { @@ -44,25 +46,26 @@ 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(WebKit::WebFrame* frame); + static v8::Handle GetWindowId(blink::WebFrame* frame); + static void ZoomLevelChanged(blink::WebView* web_view); static void willHandleNavigationPolicy( content::RenderView* rv, - WebKit::WebFrame* frame, - const WebKit::WebURLRequest& request, - WebKit::WebNavigationPolicy* policy); + 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; - virtual void DidFinishDocumentLoad(WebKit::WebFrame* frame) OVERRIDE; - virtual void DidCreateDocumentElement(WebKit::WebFrame* frame) 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, WebKit::WebFrame* frame); + void documentCallback(const char* ev, blink::WebLocalFrame* frame); void OnEvent(int object_id, std::string event, diff --git a/src/api/dispatcher_bindings.cc b/src/api/dispatcher_bindings.cc index 84697ce400..44f7c58b3b 100644 --- a/src/api/dispatcher_bindings.cc +++ b/src/api/dispatcher_bindings.cc @@ -21,18 +21,24 @@ #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; @@ -42,38 +48,50 @@ 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()) @@ -99,129 +117,161 @@ 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); - else if (name->Equals(v8::String::New("CrashRenderer"))) - return v8::FunctionTemplate::New(CrashRenderer); - else if (name->Equals(v8::String::New("SetCrashDumpDir"))) - return v8::FunctionTemplate::New(SetCrashDumpDir); - else if (name->Equals(v8::String::New("AllocateId"))) - return v8::FunctionTemplate::New(AllocateId); - +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 void DispatcherBindings::RequireNwGui(const v8::FunctionCallbackInfo& args) { - v8::HandleScope scope; + 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()) { - args.GetReturnValue().Set(scope.Close(NwGuiHidden)); + args.GetReturnValue().Set(handle_scope.Escape(NwGuiHidden)); return; } - v8::Local NwGui = v8::Object::New(); + v8::Local context = isolate->GetEnteredContext(); + v8::Local global = context->Global(); + ASSERT(!global->IsUndefined()); + v8::Local g_context = + v8::Local::New(isolate, node::g_context); + + 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, global, v8::String::NewFromUtf8(isolate, "menuitem.js"), IDR_NW_API_MENUITEM_JS); RequireFromResource(args.This(), - NwGui, v8::String::New("menuitem.js"), IDR_NW_API_MENUITEM_JS); + NwGui, global, v8::String::NewFromUtf8(isolate, "menu.js"), IDR_NW_API_MENU_JS); RequireFromResource(args.This(), - NwGui, v8::String::New("menu.js"), IDR_NW_API_MENU_JS); + NwGui, global, v8::String::NewFromUtf8(isolate, "tray.js"), IDR_NW_API_TRAY_JS); RequireFromResource(args.This(), - NwGui, v8::String::New("tray.js"), IDR_NW_API_TRAY_JS); + NwGui, global, v8::String::NewFromUtf8(isolate, "clipboard.js"), IDR_NW_API_CLIPBOARD_JS); RequireFromResource(args.This(), - NwGui, v8::String::New("clipboard.js"), IDR_NW_API_CLIPBOARD_JS); + NwGui, global, v8::String::NewFromUtf8(isolate, "window.js"), IDR_NW_API_WINDOW_JS); RequireFromResource(args.This(), - NwGui, v8::String::New("window.js"), IDR_NW_API_WINDOW_JS); + NwGui, global, v8::String::NewFromUtf8(isolate, "shell.js"), IDR_NW_API_SHELL_JS); RequireFromResource(args.This(), - NwGui, v8::String::New("shell.js"), IDR_NW_API_SHELL_JS); + NwGui, global, v8::String::NewFromUtf8(isolate, "app.js"), IDR_NW_API_APP_JS); RequireFromResource(args.This(), - NwGui, v8::String::New("app.js"), IDR_NW_API_APP_JS); + 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); - args.GetReturnValue().Set(scope.Close(NwGui)); + g_context->Exit(); + args.GetReturnValue().Set(handle_scope.Escape(NwGui)); } // static 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) - args.GetReturnValue().Set(v8::String::New(path.value().c_str())); + args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, path.value().c_str())); #else - args.GetReturnValue().Set(v8::String::New(path.AsUTF8Unsafe().c_str())); + args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, path.AsUTF8Unsafe().c_str())); #endif } // static void DispatcherBindings::GetShellIdForCurrentContext(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); RenderView* render_view = GetCurrentRenderView(); if (!render_view) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + 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)); - args.GetReturnValue().Set(v8::Integer::New(id)); + args.GetReturnValue().Set(v8::Integer::New(isolate, id)); } // static void DispatcherBindings::GetRoutingIDForCurrentContext(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); RenderView* render_view = GetCurrentRenderView(); if (!render_view) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "Unable to get render view in GetRoutingIDForCurrentContext")))); return; } - args.GetReturnValue().Set(v8::Integer::New(render_view->GetRoutingID())); + args.GetReturnValue().Set(v8::Integer::New(isolate, render_view->GetRoutingID())); } // static void DispatcherBindings::CreateShell(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); if (args.Length() < 2) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "CreateShell requries 2 arguments")))); return; } @@ -231,17 +281,17 @@ DispatcherBindings::CreateShell(const v8::FunctionCallbackInfo& args) 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)) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + 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) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "Unable to get render view in CreateShell")))); return; } @@ -253,15 +303,16 @@ DispatcherBindings::CreateShell(const v8::FunctionCallbackInfo& args) *static_cast(value_manifest.get()), &routing_id)); - args.GetReturnValue().Set(v8::Integer::New(routing_id)); + args.GetReturnValue().Set(v8::Integer::New(isolate, routing_id)); } // static void DispatcherBindings::AllocateId(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); RenderView* render_view = GetCurrentRenderView(); if (!render_view) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "Unable to get render view in AllocateId")))); return; } @@ -271,8 +322,9 @@ DispatcherBindings::AllocateId(const v8::FunctionCallbackInfo& args) void DispatcherBindings::AllocateObject(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); if (args.Length() < 3) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "AllocateObject requries 3 arguments")))); return; } @@ -282,7 +334,7 @@ DispatcherBindings::AllocateObject(const v8::FunctionCallbackInfo& ar RenderView* render_view = GetCurrentRenderView(); if (!render_view) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "Unable to get render view in AllocateObject")))); return; } @@ -293,15 +345,16 @@ DispatcherBindings::AllocateObject(const v8::FunctionCallbackInfo& ar // static void DispatcherBindings::DeallocateObject(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); RenderView* render_view = GetCurrentRenderView(); if (!render_view) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "Unable to get render view in DeallocateObject")))); return; } if (args.Length() < 1) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "DeallocateObject requries 1 arguments")))); return; } @@ -311,8 +364,9 @@ DispatcherBindings::DeallocateObject(const v8::FunctionCallbackInfo& // static void DispatcherBindings::CallObjectMethod(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); if (args.Length() < 4) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "CallObjectMethod requries 4 arguments")))); return; } @@ -325,7 +379,7 @@ DispatcherBindings::CallObjectMethod(const v8::FunctionCallbackInfo& if (!render_view) render_view = GetEnteredRenderView(); if (!render_view) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "Unable to get render view in CallObjectMethod")))); return; } @@ -336,8 +390,9 @@ DispatcherBindings::CallObjectMethod(const v8::FunctionCallbackInfo& // static void DispatcherBindings::CallObjectMethodSync( const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); if (args.Length() < 4) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "CallObjectMethodSync requries 4 arguments")))); return; } @@ -348,7 +403,7 @@ void DispatcherBindings::CallObjectMethodSync( RenderView* render_view = GetCurrentRenderView(); if (!render_view) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "Unable to get render view in CallObjectMethod")))); return; } @@ -359,8 +414,9 @@ void DispatcherBindings::CallObjectMethodSync( // static void DispatcherBindings::CallStaticMethod( const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); if (args.Length() < 3) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "CallStaticMethod requries 3 arguments")))); return; } @@ -371,17 +427,17 @@ void 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)) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + 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) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "Unable to get render view in CallStaticMethod")))); return; } @@ -391,12 +447,19 @@ void DispatcherBindings::CallStaticMethod( type, method, *static_cast(value_args.get()))); - args.GetReturnValue().Set(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; } @@ -405,16 +468,17 @@ void DispatcherBindings::CrashRenderer( void DispatcherBindings::SetCrashDumpDir( const v8::FunctionCallbackInfo& args) { #if defined(OS_WIN) || defined(OS_MACOSX) - std::string path = *v8::String::Utf8Value(args[0]); - SetCrashDumpPath(path.c_str()); + //std::string path = *v8::String::Utf8Value(args[0]); + //FIXME: SetCrashDumpPath(path.c_str()); #endif } // static void DispatcherBindings::CallStaticMethodSync( const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); if (args.Length() < 3) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "CallStaticMethodSync requries 3 arguments")))); return; } @@ -426,7 +490,7 @@ void DispatcherBindings::CallStaticMethodSync( RenderView* render_view = GetEnteredRenderView(); if (!render_view) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "Unable to get render view in CallStaticMethodSync")))); return; } @@ -435,25 +499,51 @@ void DispatcherBindings::CallStaticMethodSync( std::string url = *v8::String::Utf8Value(args[2]); GURL gurl(url); if (!gurl.is_valid()) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + 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()); + args.GetReturnValue().Set(v8::Undefined(isolate)); return; } - args.GetReturnValue().Set(v8::String::New(proxy.c_str())); + 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)) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New( + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "Unable to convert 'args' passed to CallStaticMethodSync")))); return; } @@ -465,7 +555,7 @@ void DispatcherBindings::CallStaticMethodSync( method, *static_cast(value_args.get()), &result)); - args.GetReturnValue().Set(converter->ToV8Value(&result, v8::Context::GetCurrent())); + args.GetReturnValue().Set(converter->ToV8Value(&result, isolate->GetCurrentContext())); } } // namespace nwapi diff --git a/src/api/dispatcher_bindings.h b/src/api/dispatcher_bindings.h index f20d600f09..28cf641d51 100644 --- a/src/api/dispatcher_bindings.h +++ b/src/api/dispatcher_bindings.h @@ -30,11 +30,13 @@ 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. @@ -62,6 +64,11 @@ class DispatcherBindings : public v8::Extension { 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); }; diff --git a/src/api/dispatcher_bindings.js b/src/api/dispatcher_bindings.js index 1dab320210..6c1939f505 100644 --- a/src/api/dispatcher_bindings.js +++ b/src/api/dispatcher_bindings.js @@ -38,6 +38,9 @@ var nwDispatcher = nwDispatcher || {}; native function CrashRenderer(); native function SetCrashDumpDir(); + native function GetNSStringWithFixup(); + native function GetNSStringFWithFixup(); + nwDispatcher.requireNwGui = RequireNwGui; // Request a new object from browser @@ -97,4 +100,8 @@ var nwDispatcher = nwDispatcher || {}; 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 d9fa56b49f..dbbcd7b86c 100644 --- a/src/api/dispatcher_host.cc +++ b/src/api/dispatcher_host.cc @@ -21,6 +21,7 @@ #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" @@ -29,15 +30,20 @@ #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; @@ -52,7 +58,8 @@ 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) { + weak_ptr_factory_(this), + run_loop_(NULL) { g_dispatcher_host_map[render_view_host_] = this; } @@ -78,10 +85,12 @@ 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_++; } @@ -94,10 +103,21 @@ void DispatcherHost::SendEvent(Base* object, } bool DispatcherHost::Send(IPC::Message* message) { - return content::WebContentsObserver::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; @@ -115,6 +135,7 @@ bool DispatcherHost::OnMessageReceived(const IPC::Message& message) { 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() @@ -147,6 +168,12 @@ void DispatcherHost::OnAllocateObject(int object_id, new Clipboard(object_id, weak_ptr_factory_.GetWeakPtr(), option), object_id); } else if (type == "Window") { 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, weak_ptr_factory_.GetWeakPtr(), option), object_id); @@ -235,7 +262,10 @@ void DispatcherHost::OnCallStaticMethodSync( if (type == "App") { content::Shell* shell = content::Shell::FromRenderViewHost(render_view_host()); - nwapi::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; } @@ -257,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()); @@ -281,17 +310,14 @@ void DispatcherHost::OnCreateShell(const std::string& url, WebContents* web_contents = content::WebContentsImpl::CreateWithOpener( create_params, static_cast(base_web_contents)); - content::Shell* new_shell = - content::Shell::Create(base_web_contents, + + content::Shell::Create(base_web_contents, GURL(url), new_manifest.get(), web_contents); if (new_renderer) { browser_context->set_pinning_renderer(true); - // since the new-instance shell is always bound - // there would be 'Close' event cannot reach dest - new_shell->set_force_close(true); } *routing_id = web_contents->GetRoutingID(); @@ -307,4 +333,11 @@ 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 nwapi diff --git a/src/api/dispatcher_host.h b/src/api/dispatcher_host.h index f15115e1f1..cdd8326875 100644 --- a/src/api/dispatcher_host.h +++ b/src/api/dispatcher_host.h @@ -32,6 +32,7 @@ namespace base { class DictionaryValue; class ListValue; +class RunLoop; } namespace WebKit { @@ -49,15 +50,16 @@ class Base; 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)); } @@ -68,13 +70,17 @@ class DispatcherHost : public content::WebContentsObserver { const std::string& event, const base::ListValue& arguments); - virtual bool Send(IPC::Message* message) OVERRIDE; - virtual void RenderViewHostChanged(content::RenderViewHost* old_host, - content::RenderViewHost* new_host) 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 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: content::RenderViewHost* render_view_host_; friend class content::Shell; @@ -87,9 +93,14 @@ class DispatcherHost : public content::WebContentsObserver { // Factory to generate weak pointer base::WeakPtrFactory weak_ptr_factory_; + base::RunLoop* run_loop_; + // RenderViewHostObserver implementation. // WebContentsObserver implementation: - virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + bool OnMessageReceived( + content::RenderViewHost* render_view_host, + const IPC::Message& message) override; + void OnAllocateObject(int object_id, const std::string& type, @@ -117,6 +128,8 @@ class DispatcherHost : public content::WebContentsObserver { const base::DictionaryValue& manifest, int* routing_id); void OnAllocateId(int* ret); + void OnSetForceClose(bool force, int* ret); + DISALLOW_COPY_AND_ASSIGN(DispatcherHost); }; 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 2a66f8b0dc..688603fc98 100644 --- a/src/api/menu/menu.cc +++ b/src/api/menu/menu.cc @@ -30,7 +30,7 @@ namespace nwapi { Menu::Menu(int id, 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,6 +63,8 @@ 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; diff --git a/src/api/menu/menu.h b/src/api/menu/menu.h index d066ceb060..0b9148d260 100644 --- a/src/api/menu/menu.h +++ b/src/api/menu/menu.h @@ -28,29 +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" -#include "chrome/browser/status_icons/status_icon_menu_model.h" -namespace nw { -class NativeWindowWin; +namespace nwapi { +class Menu; } namespace ui { @@ -64,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 @@ -84,10 +91,19 @@ class Menu : public Base { Menu(int id, const base::WeakPtr& dispatcher_host, const base::DictionaryValue& option); - virtual ~Menu(); + ~Menu() override; + + 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 - virtual void Call(const std::string& method, - const base::ListValue& arguments) OVERRIDE; + bool enable_show_event() { return enable_show_event_; } + protected: + bool enable_show_event_; private: friend class MenuItem; @@ -101,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_; 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 92% rename from src/api/menu/menu_delegate_win.cc rename to src/api/menu/menu_delegate.cc index 7718deb93a..a094211020 100644 --- a/src/api/menu/menu_delegate_win.cc +++ b/src/api/menu/menu_delegate.cc @@ -1,102 +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/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_; -} - -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) - 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 +// 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 70% rename from src/api/menu/menu_delegate_win.h rename to src/api/menu/menu_delegate.h index 80ac9d03bf..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 nwapi { - -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, int event_flags) OVERRIDE; - - virtual 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_ +// 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 17502e68c2..893c0ab45d 100644 --- a/src/api/menu/menu_gtk.cc +++ b/src/api/menu/menu_gtk.cc @@ -27,6 +27,8 @@ #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 nwapi { @@ -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,20 +105,30 @@ 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; if (!event) { @@ -131,4 +144,21 @@ void Menu::Popup(int x, int y, content::Shell* shell) { 3, triggering_event_time); } +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 538b0f7bda..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 @@ -25,7 +25,8 @@ #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" @@ -35,17 +36,13 @@ 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 diff --git a/src/api/menu/menu_win.cc b/src/api/menu/menu_views.cc similarity index 70% rename from src/api/menu/menu_win.cc rename to src/api/menu/menu_views.cc index b943de7912..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 @@ -73,26 +85,39 @@ bool NwMenuModel::HasIcons() const { 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((HWND)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 nwapi diff --git a/src/api/menuitem/menuitem.cc b/src/api/menuitem/menuitem.cc index 2765e4ac0c..e706617f66 100644 --- a/src/api/menuitem/menuitem.cc +++ b/src/api/menuitem/menuitem.cc @@ -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,6 +70,16 @@ 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; diff --git a/src/api/menuitem/menuitem.h b/src/api/menuitem/menuitem.h index 9a6ec986b5..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) +#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 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, 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,9 +111,14 @@ 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); 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.mm b/src/api/menuitem/menuitem_delegate_mac.mm index a3ccd08a9b..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 diff --git a/src/api/menuitem/menuitem_gtk.cc b/src/api/menuitem/menuitem_gtk.cc index fbd06733d2..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 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); } + +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 d907adb33f..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 @@ -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()]]; } 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 73cbb05bf6..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/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" - -namespace nwapi { - -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 nwapi 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 b393f7b1f0..ff00722667 100644 --- a/src/api/shell/shell.cc +++ b/src/api/shell/shell.cc @@ -36,15 +36,15 @@ 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"; } 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 2371b10cc6..0259984c6b 100644 --- a/src/api/tray/tray.cc +++ b/src/api/tray/tray.cc @@ -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); diff --git a/src/api/tray/tray.h b/src/api/tray/tray.h index a0303edb14..095d9e7d34 100644 --- a/src/api/tray/tray.h +++ b/src/api/tray/tray.h @@ -34,10 +34,10 @@ 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) @@ -52,10 +52,10 @@ class Tray : public Base { Tray(int id, 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 @@ -64,16 +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_; MacTrayObserver* status_observer_; -#elif defined(TOOLKIT_GTK) + bool iconsAreTemplates; +#elif 0 GtkStatusIcon* status_item_; // Reference to the associated menu. @@ -83,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_; diff --git a/src/api/tray/tray.js b/src/api/tray/tray.js index 4f55cbfd94..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 = ''; @@ -42,12 +42,17 @@ function Tray(option) { 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 String("'click' must be a valid Function"); + throw new TypeError("'click' must be a valid Function"); } else { this.click = option.click; } @@ -55,7 +60,7 @@ function Tray(option) { 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); @@ -100,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() { @@ -117,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 ]); diff --git a/src/api/tray/tray_win.cc b/src/api/tray/tray_aura.cc similarity index 86% rename from src/api/tray/tray_win.cc rename to src/api/tray/tray_aura.cc index 7fb61b6b46..7bd9623fe2 100644 --- a/src/api/tray/tray_win.cc +++ b/src/api/tray/tray_aura.cc @@ -30,6 +30,7 @@ #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 nwapi { @@ -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,7 +63,7 @@ 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(StatusTray::NOTIFICATION_TRAY_ICON, gfx::ImageSkia(), base::string16()); @@ -87,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) { @@ -107,4 +114,7 @@ void Tray::Remove() { void Tray::SetAltIcon(const std::string& alticon_path) { } +void Tray::SetIconsAreTemplates(bool areTemplates) { +} + } // namespace nwapi diff --git a/src/api/tray/tray_gtk.cc b/src/api/tray/tray_gtk.cc index cc86c9f86b..12b5990ef2 100644 --- a/src/api/tray/tray_gtk.cc +++ b/src/api/tray/tray_gtk.cc @@ -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) { } +void Tray::SetIconsAreTemplates(bool areTemplates) { +} + } // namespace nwapi diff --git a/src/api/tray/tray_mac.mm b/src/api/tray/tray_mac.mm index c983063883..8c8575306d 100644 --- a/src/api/tray/tray_mac.mm +++ b/src/api/tray/tray_mac.mm @@ -22,6 +22,7 @@ #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" @@ -40,6 +41,15 @@ - (void)setBacking:(nwapi::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 @@ -65,6 +75,11 @@ - (void)onClick:(id)sender { } 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()]]; } @@ -72,6 +87,7 @@ - (void)onClick:(id)sender { if (!icon.empty()) { NSImage* image = [[NSImage alloc] initWithContentsOfFile:[NSString stringWithUTF8String:icon.c_str()]]; + [image setTemplate:iconsAreTemplates]; [status_item_ setImage:image]; [image release]; } else { @@ -83,6 +99,7 @@ - (void)onClick:(id)sender { if (!alticon.empty()) { NSImage* image = [[NSImage alloc] initWithContentsOfFile:[NSString stringWithUTF8String:alticon.c_str()]]; + [image setTemplate:iconsAreTemplates]; [status_item_ setAlternateImage:image]; [image release]; } else { @@ -90,6 +107,16 @@ - (void)onClick:(id)sender { } } +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()]]; } 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 0679c04e5a..cb6f3d9fd1 100644 --- a/src/api/window/window.cc +++ b/src/api/window/window.cc @@ -47,10 +47,10 @@ namespace { const char kCauseKey[] = "cause"; const char kCookieKey[] = "cookie"; -const char kDomainKey[] = "domain"; -const char kIdKey[] = "id"; +//const char kDomainKey[] = "domain"; +//const char kIdKey[] = "id"; const char kRemovedKey[] = "removed"; -const char kTabIdsKey[] = "tabIds"; +//const char kTabIdsKey[] = "tabIds"; // Cause Constants const char kEvictedChangeCause[] = "evicted"; @@ -65,7 +65,7 @@ GURL GetURLFromCanonicalCookie(const net::CanonicalCookie& cookie) { cookie.IsSecure() ? "https" : "http"; const std::string host = domain_key.find('.') != 0 ? domain_key : domain_key.substr(1); - return GURL(scheme + content::kStandardSchemeSeparator + host + "/"); + return GURL(scheme + url::kStandardSchemeSeparator + host + "/"); } void GetCookieListFromStore( @@ -139,13 +139,13 @@ 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", UTF16ToUTF8(UTF8ToUTF16(canonical_cookie.Name()))); - result->SetString("value", UTF16ToUTF8(UTF8ToUTF16(canonical_cookie.Value()))); + 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", IsStringUTF8(canonical_cookie.Path()) ? canonical_cookie.Path() + 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()); @@ -161,6 +161,8 @@ PopulateCookieObject(const net::CanonicalCookie& canonical_cookie) { namespace nwapi { +CookieAPIContext::~CookieAPIContext() {} + Window::Window(int id, const base::WeakPtr& dispatcher_host, const base::DictionaryValue& option) @@ -221,7 +223,12 @@ void Window::Call(const std::string& method, shell_->window()->SetKiosk(!shell_->window()->IsKiosk()); } else if (method == "CloseDevTools") { shell_->CloseDevTools(); - }else if (method == "ResizeTo") { + } 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) && arguments.GetInteger(1, &height)) @@ -248,23 +255,37 @@ void Window::Call(const std::string& method, 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)) @@ -302,6 +323,9 @@ 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") { diff --git a/src/api/window/window.h b/src/api/window/window.h index fa9295e834..3e5e9f8c25 100644 --- a/src/api/window/window.h +++ b/src/api/window/window.h @@ -51,6 +51,9 @@ class CookieAPIContext : public base::RefCountedThreadSafe { GURL url_; int req_id_; bool success_; +private: + friend class base::RefCountedThreadSafe; + ~CookieAPIContext(); }; @@ -59,13 +62,13 @@ class Window : public Base, public content::NotificationObserver { Window(int id, 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*); @@ -84,9 +87,9 @@ class Window : public Base, public content::NotificationObserver { private: // content::NotificationObserver implementation. - virtual void Observe(int type, + void Observe(int type, const content::NotificationSource& source, - const content::NotificationDetails& details) OVERRIDE; + 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. diff --git a/src/api/window/window.js b/src/api/window/window.js index 73d050a316..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. @@ -48,7 +51,7 @@ exports.Window = { // 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; } @@ -66,5 +69,8 @@ exports.Window = { var routing_id = nw.createShell(url, options); 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 466a4eeca5..00625340ef 100644 --- a/src/api/window_bindings.cc +++ b/src/api/window_bindings.cc @@ -18,33 +18,50 @@ // 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/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" + #undef LOG -using namespace WebCore; +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/WebFrameImpl.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" -using WebKit::WebScriptSource; -using WebKit::WebFrame; +extern void FixSourceNWBin(v8::Isolate* v8_isolate, v8::Handle script); + +using blink::WebScriptSource; +using blink::WebFrame; +//using blink::InstrumentingAgents; +//using blink::InspectorResourceAgent; namespace nwapi { @@ -62,30 +79,33 @@ 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); - else if (name->Equals(v8::String::New("AllocateId"))) - return v8::FunctionTemplate::New(AllocateId); - - 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 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()); + args.GetReturnValue().Set(v8::Undefined(isolate)); } void @@ -99,9 +119,12 @@ WindowBindings::AllocateId(const v8::FunctionCallbackInfo& args) { // static 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)); @@ -110,7 +133,7 @@ WindowBindings::CallObjectMethod(const v8::FunctionCallbackInfo& args if (!render_view) { std::string msg = "Unable to get render view in " + method; - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New(msg.c_str())))); + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, msg.c_str())))); return; } @@ -122,8 +145,8 @@ WindowBindings::CallObjectMethod(const v8::FunctionCallbackInfo& args if (frm->IsNull()) { web_frame = main_frame; }else{ - WebCore::HTMLIFrameElement* iframe = WebCore::V8HTMLIFrameElement::toNative(frm); - web_frame = WebKit::WebFrameImpl::fromFrame(iframe->contentFrame()); + 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])); @@ -135,16 +158,70 @@ WindowBindings::CallObjectMethod(const v8::FunctionCallbackInfo& args } 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{ - WebCore::HTMLIFrameElement* iframe = WebCore::V8HTMLIFrameElement::toNative(frm); - main_frame->setDevtoolsJail(WebKit::WebFrameImpl::fromFrame(iframe->contentFrame())); + blink::HTMLIFrameElement* iframe = blink::V8HTMLIFrameElement::toImpl(frm); + main_frame->setDevtoolsJail(blink::WebFrame::fromFrame(iframe->contentFrame())); } - args.GetReturnValue().Set(v8::Undefined()); + 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(), @@ -155,31 +232,33 @@ WindowBindings::CallObjectMethod(const v8::FunctionCallbackInfo& args // static void WindowBindings::CallObjectMethodSync(const v8::FunctionCallbackInfo& args) { - v8::HandleScope scope; + 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(v8::ThrowException(v8::Exception::Error(v8::String::New(msg.c_str())))); + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, msg.c_str())))); return; } if (method == "GetZoomLevel") { float zoom_level = render_view->GetWebView()->zoomLevel(); - v8::Local array = v8::Array::New(); - array->Set(0, v8::Number::New(zoom_level)); - args.GetReturnValue().Set(scope.Close(array)); + 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); - args.GetReturnValue().Set(v8::Undefined()); + render_view->GetWebView()->setZoomLevel(zoom_level); + nwapi::Dispatcher::ZoomLevelChanged(render_view->GetWebView()); + args.GetReturnValue().Set(v8::Undefined(isolate)); return; } args.GetReturnValue().Set(remote::CallObjectMethodSync(routing_id, object_id, "Window", method, args[2])); @@ -188,13 +267,14 @@ WindowBindings::CallObjectMethodSync(const v8::FunctionCallbackInfo& // static 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::RenderViewImpl::FromRoutingID(routing_id)); if (!render_view) { - args.GetReturnValue().Set(v8::ThrowException(v8::Exception::Error(v8::String::New("Unable to get render view in GetWindowObject")))); + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, "Unable to get render view in GetWindowObject")))); return; } // Return the window object. diff --git a/src/api/window_bindings.h b/src/api/window_bindings.h index d141363944..4bb99d6d87 100644 --- a/src/api/window_bindings.h +++ b/src/api/window_bindings.h @@ -30,11 +30,13 @@ 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); diff --git a/src/api/window_bindings.js b/src/api/window_bindings.js index 4ab3ba6248..c5e987b523 100644 --- a/src/api/window_bindings.js +++ b/src/api/window_bindings.js @@ -117,6 +117,7 @@ Window.prototype.handleEvent = function(ev) { 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). @@ -130,9 +131,11 @@ 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]); } @@ -143,6 +146,7 @@ Window.prototype.handleEvent = function(ev) { 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); @@ -212,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 ]); @@ -242,6 +250,11 @@ 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) @@ -400,8 +413,15 @@ Window.prototype.setShowInTaskbar = function(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 ]); } @@ -410,9 +430,19 @@ Window.prototype.setBadgeLabel = function(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 ]); } @@ -475,8 +505,17 @@ Window.prototype.capturePage = function(callback, image_format_options) { CallObjectMethod(this, 'CapturePage', [options.format]); }; - Window.prototype.eval = function(frame, script) { - return CallObjectMethod(this, 'EvaluateScript', frame, script); - }; +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 index 0d3d40d3d9..2f1d8fcb49 100644 --- a/src/breakpad_linux.cc +++ b/src/breakpad_linux.cc @@ -25,10 +25,10 @@ #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/platform_file.h" #include "base/posix/eintr_wrapper.h" #include "base/posix/global_descriptors.h" #include "base/process/memory.h" @@ -196,27 +196,11 @@ size_t LengthWithoutTrailingSpaces(const char* str, size_t len) { return len; } -// Populates the passed in allocated string and its size with the distro of -// the crashing process. -// The passed string is expected to be at least kDistroSize bytes long. -void PopulateDistro(char* distro, size_t* distro_len_param) { - size_t distro_len = std::min(my_strlen(base::g_linux_distro), kDistroSize); - memcpy(distro, base::g_linux_distro, distro_len); - if (distro_len_param) - *distro_len_param = distro_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); - size_t separator = switch_value.find(","); - if (separator != std::string::npos) { - GetBreakpadClient()->SetClientID(switch_value.substr(0, separator)); - base::SetLinuxDistro(switch_value.substr(separator + 1)); - } else { - GetBreakpadClient()->SetClientID(switch_value); - } + GetBreakpadClient()->SetBreakpadClientIdFromGUID(switch_value); } // MIME substrings. @@ -689,11 +673,8 @@ bool CrashDoneInProcessNoUpload( // Start constructing the message to send to the browser. char guid[kGuidSize + 1] = {0}; char crash_url[kMaxActiveURLSize + 1] = {0}; - char distro[kDistroSize + 1] = {0}; size_t guid_length = 0; size_t crash_url_length = 0; - size_t distro_length = 0; - PopulateDistro(distro, &distro_length); BreakpadInfo info = {0}; info.filename = NULL; info.fd = descriptor.fd(); @@ -701,8 +682,8 @@ bool CrashDoneInProcessNoUpload( info.process_type_length = my_strlen(g_process_type); info.crash_url = crash_url; info.crash_url_length = crash_url_length; - info.distro = distro; - info.distro_length = distro_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); @@ -1384,7 +1365,7 @@ void InitCrashReporter() { SetProcessStartTime(); - breakpad::GetBreakpadClient()->SetDumpWithoutCrashingFunction(&DumpProcess); + base::debug::SetDumpWithoutCrashingFunction(&DumpProcess); #if defined(ADDRESS_SANITIZER) // Register the callback for AddressSanitizer error reporting. __asan_set_error_report_callback(AsanLinuxBreakpadCallback); diff --git a/src/breakpad_mac.mm b/src/breakpad_mac.mm index 9b60ea12f9..25b0282831 100644 --- a/src/breakpad_mac.mm +++ b/src/breakpad_mac.mm @@ -12,6 +12,7 @@ #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" @@ -29,6 +30,7 @@ #include "content/nw/src/paths_mac.h" //#include "policy/policy_constants.h" + namespace { BreakpadRef gBreakpadRef = NULL; @@ -147,6 +149,8 @@ bool IsCrashReporterEnabled() { return gBreakpadRef != NULL; } +namespace breakpad { + // Only called for a branded build of Chrome.app. void InitCrashReporter() { DCHECK(!gBreakpadRef); @@ -191,7 +195,7 @@ void InitCrashReporter() { [resource_path stringByAppendingPathComponent:@"crash_report_sender.app"]; NSString *reporter_location = [[NSBundle bundleWithPath:reporter_bundle_location] executablePath]; -#endif +#endif VLOG(1) << "resource_path: " << [resource_path UTF8String]; VLOG(1) << "inspector_location: " << [inspector_location UTF8String]; @@ -231,7 +235,7 @@ void InitCrashReporter() { // Initialize Breakpad. gBreakpadRef = BreakpadCreate(breakpad_config); if (!gBreakpadRef) { - LOG_IF(ERROR, base::mac::AmIBundled()) << "Breakpad initializaiton failed"; + LOG_IF(ERROR, base::mac::AmIBundled()) << "Breakpad initialization failed"; return; } @@ -254,7 +258,7 @@ void InitCrashReporter() { } logging::SetLogMessageHandler(&FatalMessageHandler); - breakpad::GetBreakpadClient()->SetDumpWithoutCrashingFunction( + base::debug::SetDumpWithoutCrashingFunction( &DumpHelper::DumpWithoutCrashing); // abort() sends SIGABRT, which breakpad does not intercept. @@ -266,6 +270,8 @@ void InitCrashReporter() { CHECK(0 == sigaction(SIGABRT, &sigact, NULL)); } +} //namespace breakpad + void InitCrashProcessInfo() { if (gBreakpadRef == NULL) { return; @@ -292,3 +298,5 @@ bool SetCrashDumpPath(const char* path) { BreakpadSetKeyValue(gBreakpadRef, @BREAKPAD_DUMP_DIRECTORY, base::SysUTF8ToNSString(path)); return true; } + + diff --git a/src/breakpad_win.cc b/src/breakpad_win.cc index 9405704764..65ddf4016b 100644 --- a/src/breakpad_win.cc +++ b/src/breakpad_win.cc @@ -269,9 +269,9 @@ google_breakpad::CustomClientInfo* GetCustomInfo(const std::wstring& exe_path, // Common g_custom_entries. g_custom_entries->push_back( - google_breakpad::CustomInfoEntry(L"ver", UTF16ToWide(version).c_str())); + google_breakpad::CustomInfoEntry(L"ver", base::UTF16ToWide(version).c_str())); g_custom_entries->push_back( - google_breakpad::CustomInfoEntry(L"prod", UTF16ToWide(product).c_str())); + 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( @@ -287,7 +287,7 @@ google_breakpad::CustomClientInfo* GetCustomInfo(const std::wstring& exe_path, if (!special_build.empty()) g_custom_entries->push_back(google_breakpad::CustomInfoEntry( - L"special", UTF16ToWide(special_build).c_str())); + L"special", base::UTF16ToWide(special_build).c_str())); if (type == L"plugin" || type == L"ppapi") { std::wstring plugin_path = @@ -659,7 +659,7 @@ static void InitPipeNameEnvVar(bool is_per_user_install) { pipe_name = kGoogleUpdatePipeName; pipe_name += user_sid; } - env->SetVar(kPipeNameVar, WideToASCII(pipe_name)); + env->SetVar(kPipeNameVar, base::UTF16ToASCII(pipe_name)); } void InitDefaultCrashCallback(LPTOP_LEVEL_EXCEPTION_FILTER filter) { @@ -717,7 +717,7 @@ void InitCrashReporter() { InitDefaultCrashCallback(default_filter); return; } - std::wstring pipe_name = ASCIIToWide(pipe_name_ascii); + std::wstring pipe_name = base::ASCIIToWide(pipe_name_ascii); #ifdef _WIN64 // The protocol for connecting to the out-of-process Breakpad crash @@ -739,9 +739,11 @@ void InitCrashReporter() { 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, - google_breakpad::ExceptionHandler::HANDLER_ALL, + handler_types, dump_type, pipe_name.c_str(), custom_info); // Now initialize the non crash dump handler. 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 2c329a7c89..7de1d69375 100644 --- a/src/browser/app_controller_mac.mm +++ b/src/browser/app_controller_mac.mm @@ -31,11 +31,13 @@ @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; } @@ -52,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 = @@ -66,12 +77,16 @@ - (void) applicationDidFinishLaunching: (NSNotification *) note { [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(); if (!no_edit_menu) standard_menus.BuildEditMenu(); standard_menus.BuildWindowMenu(); +#endif } - (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication @@ -80,4 +95,30 @@ - (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication 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_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 3771d1200c..97943a84d8 100644 --- a/src/browser/capture_page_helper.cc +++ b/src/browser/capture_page_helper.cc @@ -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 { @@ -88,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; diff --git a/src/browser/capture_page_helper.h b/src/browser/capture_page_helper.h index 0c76d9e2bc..7b82b225c0 100644 --- a/src/browser/capture_page_helper.h +++ b/src/browser/capture_page_helper.h @@ -24,6 +24,7 @@ #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; @@ -64,20 +65,18 @@ class CapturePageHelper : public base::RefCountedThreadSafe, private: CapturePageHelper(const base::WeakPtr& shell); - virtual ~CapturePageHelper(); + ~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; base::WeakPtr shell_; 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/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 index 5697a8be4c..3f27e3da93 100644 --- a/src/browser/color_chooser_dialog.cc +++ b/src/browser/color_chooser_dialog.cc @@ -32,8 +32,8 @@ ColorChooserDialog::ColorChooserDialog(views::ColorChooserListener* listener, : listener_(listener) { DCHECK(listener_); CopyCustomColors(g_custom_colors, custom_colors_); - ExecuteOpenParams execute_params(initial_color, BeginRun(owning_window), - owning_window); + 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)); } @@ -41,8 +41,8 @@ ColorChooserDialog::ColorChooserDialog(views::ColorChooserListener* listener, ColorChooserDialog::~ColorChooserDialog() { } -bool ColorChooserDialog::IsRunning(HWND owning_hwnd) const { - return listener_ && IsRunningDialogForOwner(owning_hwnd); +bool ColorChooserDialog::IsRunning(gfx::NativeWindow owning_hwnd) const { + return listener_ && IsRunningDialogForOwner((HWND)owning_hwnd); } void ColorChooserDialog::ListenerDestroyed() { diff --git a/src/browser/color_chooser_dialog.h b/src/browser/color_chooser_dialog.h index 79a7fa09a8..530c9a3331 100644 --- a/src/browser/color_chooser_dialog.h +++ b/src/browser/color_chooser_dialog.h @@ -26,8 +26,8 @@ class ColorChooserDialog virtual ~ColorChooserDialog(); // BaseShellDialog: - virtual bool IsRunning(gfx::NativeWindow owning_window) const OVERRIDE; - virtual void ListenerDestroyed() OVERRIDE; + virtual bool IsRunning(gfx::NativeWindow owning_window) const override; + virtual void ListenerDestroyed() override; private: struct ExecuteOpenParams { diff --git a/src/browser/color_chooser_gtk.cc b/src/browser/color_chooser_gtk.cc deleted file mode 100644 index 9a33769d1e..0000000000 --- a/src/browser/color_chooser_gtk.cc +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include "content/public/browser/color_chooser.h" -#include "content/public/browser/web_contents.h" -#include "grit/nw_resources.h" -#include "ui/base/gtk/gtk_signal.h" -#include "ui/base/l10n/l10n_util.h" -#include "ui/gfx/skia_utils_gtk.h" - -class ColorChooserGtk : public content::ColorChooser { - public: - static ColorChooserGtk* Open(content::WebContents* web_contents, - SkColor initial_color); - - ColorChooserGtk(content::WebContents* web_contents, SkColor initial_color); - virtual ~ColorChooserGtk(); - - virtual void End() OVERRIDE; - virtual void SetSelectedColor(SkColor color) OVERRIDE; - - private: - static ColorChooserGtk* current_color_chooser_; - - CHROMEGTK_CALLBACK_0(ColorChooserGtk, void, OnColorChooserOk); - CHROMEGTK_CALLBACK_0(ColorChooserGtk, void, OnColorChooserCancel); - CHROMEGTK_CALLBACK_0(ColorChooserGtk, void, OnColorChooserDestroy); - - // The web contents invoking the color chooser. No ownership because it will - // outlive this class. - content::WebContents* web_contents_; - GtkWidget* color_selection_dialog_; -}; - -ColorChooserGtk* ColorChooserGtk::current_color_chooser_ = NULL; - -ColorChooserGtk* ColorChooserGtk::Open(content::WebContents* web_contents, - SkColor initial_color) { - if (current_color_chooser_) - current_color_chooser_->End(); - DCHECK(!current_color_chooser_); - current_color_chooser_ = new ColorChooserGtk(web_contents, initial_color); - return current_color_chooser_; -} - -ColorChooserGtk::ColorChooserGtk(content::WebContents* web_contents, - SkColor initial_color) - : web_contents_(web_contents) { - color_selection_dialog_ = gtk_color_selection_dialog_new( - l10n_util::GetStringUTF8(IDS_SELECT_COLOR_DIALOG_TITLE).c_str()); - GtkWidget* cancel_button; - GtkColorSelection* color_selection; - GtkWidget* ok_button; - g_object_get(color_selection_dialog_, - "cancel-button", &cancel_button, - "color-selection", &color_selection, - "ok-button", &ok_button, - NULL); - gtk_color_selection_set_has_opacity_control(color_selection, FALSE); - g_signal_connect(ok_button, "clicked", - G_CALLBACK(OnColorChooserOkThunk), this); - g_signal_connect(cancel_button, "clicked", - G_CALLBACK(OnColorChooserCancelThunk), this); - g_signal_connect(color_selection_dialog_, "destroy", - G_CALLBACK(OnColorChooserDestroyThunk), this); - GdkColor gdk_color = gfx::SkColorToGdkColor(initial_color); - gtk_color_selection_set_previous_color(color_selection, &gdk_color); - gtk_color_selection_set_current_color(color_selection, &gdk_color); - gtk_window_present(GTK_WINDOW(color_selection_dialog_)); - g_object_unref(cancel_button); - g_object_unref(color_selection); - g_object_unref(ok_button); -} - -ColorChooserGtk::~ColorChooserGtk() { - // Always call End() before destroying. - DCHECK(!color_selection_dialog_); -} - -void ColorChooserGtk::OnColorChooserOk(GtkWidget* widget) { - GdkColor color; - GtkColorSelection* color_selection; - g_object_get(color_selection_dialog_, - "color-selection", &color_selection, NULL); - gtk_color_selection_get_current_color(color_selection, &color); - if (web_contents_) - web_contents_->DidChooseColorInColorChooser(gfx::GdkColorToSkColor(color)); - g_object_unref(color_selection); - gtk_widget_destroy(color_selection_dialog_); -} - -void ColorChooserGtk::OnColorChooserCancel(GtkWidget* widget) { - gtk_widget_destroy(color_selection_dialog_); -} - -void ColorChooserGtk::OnColorChooserDestroy(GtkWidget* widget) { - color_selection_dialog_ = NULL; - DCHECK(current_color_chooser_ == this); - current_color_chooser_ = NULL; - if (web_contents_) - web_contents_->DidEndColorChooser(); -} - -void ColorChooserGtk::End() { - if (!color_selection_dialog_) - return; - - gtk_widget_destroy(color_selection_dialog_); -} - -void ColorChooserGtk::SetSelectedColor(SkColor color) { - if (!color_selection_dialog_) - return; - - GdkColor gdk_color = gfx::SkColorToGdkColor(color); - GtkColorSelection* color_selection; - g_object_get(color_selection_dialog_, - "color-selection", &color_selection, NULL); - gtk_color_selection_set_previous_color(color_selection, &gdk_color); - gtk_color_selection_set_current_color(color_selection, &gdk_color); - g_object_unref(color_selection); -} - -namespace nw { - -content::ColorChooser* ShowColorChooser(content::WebContents* web_contents, - SkColor initial_color) { - return ColorChooserGtk::Open(web_contents, initial_color); -} - -} // namespace chrome diff --git a/src/browser/color_chooser_mac.mm b/src/browser/color_chooser_mac.mm index 6ec46f1e2b..38b6b2101e 100644 --- a/src/browser/color_chooser_mac.mm +++ b/src/browser/color_chooser_mac.mm @@ -45,8 +45,8 @@ - (void)setColor:(NSColor*)color; void DidChooseColorInColorPanel(SkColor color); void DidCloseColorPabel(); - virtual void End() OVERRIDE; - virtual void SetSelectedColor(SkColor color) OVERRIDE; + virtual void End() override; + virtual void SetSelectedColor(SkColor color) override; private: static ColorChooserMac* current_color_chooser_; diff --git a/src/browser/color_chooser_win.cc b/src/browser/color_chooser_win.cc index fc426679a7..6ebc3be0a0 100644 --- a/src/browser/color_chooser_win.cc +++ b/src/browser/color_chooser_win.cc @@ -24,8 +24,8 @@ class ColorChooserWin : public content::ColorChooser, ~ColorChooserWin(); // content::ColorChooser overrides: - virtual void End() OVERRIDE {} - virtual void SetSelectedColor(SkColor color) OVERRIDE {} + virtual void End() override {} + virtual void SetSelectedColor(SkColor color) override {} // views::ColorChooserListener overrides: virtual void OnColorChosen(SkColor color); diff --git a/src/browser/file_select_helper.cc b/src/browser/file_select_helper.cc index 7b6edfa2f9..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/strings/string_util.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,15 +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, - FileChooserParams::Mode dialog_mode) { - render_view_host->FilesSelectedInChooser(files, dialog_mode); -} - // 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( @@ -74,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 { @@ -82,17 +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_mode_(FileChooserParams::Open) -{ + dialog_mode_(FileChooserParams::Open) { } FileSelectHelper::~FileSelectHelper() { @@ -121,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); } @@ -130,10 +124,13 @@ 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; + const base::FilePath& path = file.local_path; if (dialog_type_ == ui::SelectFileDialog::SELECT_UPLOAD_FOLDER && extract_directory_) { StartNewEnumeration(path, kFileSelectEnumerationId, render_view_host_); @@ -142,14 +139,20 @@ void FileSelectHelper::FileSelectedWithExtraInfo( std::vector files; files.push_back(file); - NotifyRenderViewHost(render_view_host_, files, dialog_mode_); - // 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); @@ -159,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_mode_); - - // 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_mode_); - - // 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); @@ -208,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. + // Directory upload only cares about files. if (data.info.IsDirectory()) - entry->results_.push_back(data.path.Append(FILE_PATH_LITERAL("."))); - else - entry->results_.push_back(data.path); + return; + + entry->results_.push_back(data.path); } void FileSelectHelper::OnListDone(int id, int error) { @@ -231,17 +224,71 @@ void FileSelectHelper::OnListDone(int id, int error) { std::vector selected_files = FilePathListToSelectedFileInfoList(entry->results_); - if (id == kFileSelectEnumerationId) - NotifyRenderViewHost(entry->rvh_, selected_files, dialog_mode_); - 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); +} - EnumerateDirectoryEnd(); +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(); +} + +void FileSelectHelper::DeleteTemporaryFiles() { + BrowserThread::PostTask(BrowserThread::FILE, + FROM_HERE, + base::Bind(&DeleteFiles, temporary_files_)); + temporary_files_.clear(); } 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()) @@ -252,13 +299,14 @@ FileSelectHelper::GetFileTypesFromAcceptType( 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; 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; @@ -295,7 +343,7 @@ 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_id == 0 && extensions->size() > 1)) + (valid_type_count == 1 && description_id == 0 && extensions->size() > 1)) description_id = IDS_CUSTOM_FILES; if (description_id) { @@ -319,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()); @@ -335,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, @@ -356,8 +408,8 @@ void FileSelectHelper::RunFileChooser(RenderViewHost* render_view_host, void FileSelectHelper::RunFileChooserOnFileThread( const FileChooserParams& params) { - select_file_types_ = - 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, @@ -373,7 +425,10 @@ 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) { @@ -395,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(); } @@ -419,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(); @@ -426,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 @@ -458,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(); } @@ -473,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 6f1c977be2..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 @@ -32,7 +16,10 @@ #include "net/base/directory_lister.h" #include "ui/shell_dialogs/select_file_dialog.h" +class Profile; + namespace content { +struct FileChooserFileInfo; class RenderViewHost; class WebContents; } @@ -41,9 +28,6 @@ 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 (.). - scoped_ptr 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. @@ -176,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 51f4ae5e8d..997d513c3b 100644 --- a/src/browser/native_window.cc +++ b/src/browser/native_window.cc @@ -22,22 +22,28 @@ #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 { @@ -52,12 +58,12 @@ NativeWindow* NativeWindow::Create(const base::WeakPtr& shell, // Create window. NativeWindow* window = -#if defined(TOOLKIT_GTK) - new NativeWindowGtk(shell, manifest); +#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."; @@ -69,10 +75,21 @@ NativeWindow* NativeWindow::Create(const base::WeakPtr& 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); } @@ -85,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)) { @@ -122,6 +139,11 @@ void NativeWindow::InitFromManifest(base::DictionaryValue* manifest) { !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 79c76dca7c..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 @@ -31,8 +31,12 @@ #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" + +#if defined(OS_WIN) || defined(OS_LINUX) +#include "components/web_modal/web_contents_modal_dialog_host.h" +#endif namespace nwapi { class Menu; @@ -60,9 +64,15 @@ 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(const base::WeakPtr& shell, base::DictionaryValue* manifest); @@ -80,6 +90,7 @@ 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; @@ -87,15 +98,18 @@ class NativeWindow { 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(nwapi::Menu* menu) = 0; + virtual void ClearMenu() {} virtual void SetInitialFocus(bool accept_focus) = 0; virtual bool InitialFocus() = 0; @@ -118,11 +132,19 @@ class NativeWindow { virtual void HandleKeyboardEvent( const content::NativeWebKeyboardEvent& event) = 0; +#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: void OnNativeWindowDestory() { @@ -134,6 +156,8 @@ class NativeWindow { // Weak reference to parent. base::WeakPtr shell_; + + bool transparent_; bool has_frame_; 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 2b1a3fc094..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 { @@ -60,14 +69,19 @@ NativeWindowGtk::NativeWindowGtk(const base::WeakPtr& shell, { 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(); @@ -79,7 +93,7 @@ NativeWindowGtk::NativeWindowGtk(const base::WeakPtr& 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); @@ -291,12 +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) { - // TODO + 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) { @@ -308,6 +330,7 @@ bool NativeWindowGtk::IsKiosk() { } 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); } diff --git a/src/browser/native_window_gtk.h b/src/browser/native_window_gtk.h index 1dd1b6b0d3..c0a9111519 100644 --- a/src/browser/native_window_gtk.h +++ b/src/browser/native_window_gtk.h @@ -26,7 +26,7 @@ #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 { @@ -48,6 +48,7 @@ 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; @@ -59,8 +60,9 @@ class NativeWindowGtk : public NativeWindow { 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(nwapi::Menu* menu) OVERRIDE; @@ -89,6 +91,9 @@ 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(); diff --git a/src/browser/native_window_mac.h b/src/browser/native_window_mac.h index 99151269c0..f91240d7ff 100644 --- a/src/browser/native_window_mac.h +++ b/src/browser/native_window_mac.h @@ -30,6 +30,7 @@ @class ShellNSWindow; @class ShellToolbarDelegate; +@class NWMenuDelegate; class SkRegion; namespace nw { @@ -41,39 +42,43 @@ class NativeWindowCocoa : public NativeWindow { 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 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 SetBadgeLabel(const std::string& badge) OVERRIDE; - virtual void SetKiosk(bool kiosk) OVERRIDE; - virtual bool IsKiosk() OVERRIDE; - virtual void SetMenu(nwapi::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; - virtual void SetInitialFocus(bool accept_focus) OVERRIDE; - virtual bool InitialFocus() 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); @@ -87,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); @@ -109,6 +114,9 @@ class NativeWindowCocoa : public NativeWindow { // Delegate to the toolbar. base::scoped_nsobject toolbar_delegate_; + + // Data for transparency + NSColor *opaque_color_; bool is_fullscreen_; bool is_kiosk_; diff --git a/src/browser/native_window_mac.mm b/src/browser/native_window_mac.mm index 02552df38c..44ca8ac355 100644 --- a/src/browser/native_window_mac.mm +++ b/src/browser/native_window_mac.mm @@ -24,7 +24,9 @@ #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,13 +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/public/browser/render_widget_host_view.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 @@ -119,7 +126,7 @@ - (void)windowWillExitFullScreen:(NSNotification*)notification { - (void)windowDidBecomeKey:(NSNotification *)notification { if (shell_) { - shell_->web_contents()->GetView()->Focus(); + shell_->web_contents()->Focus(); shell_->SendEvent("focus"); } } @@ -282,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); } @@ -306,6 +317,41 @@ - (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( @@ -350,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) && @@ -371,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. @@ -388,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]; @@ -413,7 +463,7 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { } void NativeWindowCocoa::UninstallView() { - NSView* view = web_contents()->GetView()->GetNativeView(); + NSView* view = web_contents()->GetNativeView(); [view removeFromSuperview]; } @@ -434,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 @@ -442,9 +494,7 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { void NativeWindowCocoa::Show() { NSApplication *myApp = [NSApplication sharedApplication]; - [myApp activateIgnoringOtherApps:YES]; - content::RenderWidgetHostView* rwhv = - shell_->web_contents()->GetRenderWidgetHostView(); + [myApp activateIgnoringOtherApps:NO]; if (first_show_ && initial_focus_) { [window() makeKeyAndOrderFront:nil]; @@ -455,11 +505,12 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { // Chrome assumption is that becoming the first responder = you have focus // so we use this trick to refuse to become first responder during orderFrontRegardless - if (rwhv) - rwhv->SetTakesFocusOnlyOnMouseDown(true); + // TODO(roger) + // if (rwhv) + // rwhv->SetTakesFocusOnlyOnMouseDown(true); [window() orderFrontRegardless]; - if (rwhv) - rwhv->SetTakesFocusOnlyOnMouseDown(false); + // if (rwhv) + // rwhv->SetTakesFocusOnlyOnMouseDown(false); } first_show_ = false; } @@ -503,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; @@ -589,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]; } } @@ -597,6 +693,16 @@ - (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) { @@ -634,9 +740,9 @@ - (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; @@ -647,11 +753,65 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { [[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 + @@ -672,15 +832,17 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { } void NativeWindowCocoa::SetMenu(nwapi::Menu* menu) { - bool no_edit_menu = false; - shell_->GetPackage()->root()->GetBoolean("no-edit-menu", &no_edit_menu); + if(menu == nil) { + NSMenu *menu = [[NSMenu alloc] initWithTitle:@""]; + [NSApp setMainMenu:menu]; + } else { + [NSApp setMainMenu:menu->menu_]; + } +} - StandardMenusMac standard_menus(shell_->GetPackage()->GetName()); - [NSApp setMainMenu:menu->menu_]; - standard_menus.BuildAppleMenu(); - if (!no_edit_menu) - standard_menus.BuildEditMenu(); - standard_menus.BuildWindowMenu(); +void NativeWindowCocoa::ClearMenu() { + NSMenu *menu = [[NSMenu alloc] initWithTitle:@""]; + [NSApp setMainMenu:menu]; } void NativeWindowCocoa::SetInitialFocus(bool accept_focus) { @@ -738,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]; @@ -792,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 @@ -808,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]); @@ -878,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(); @@ -908,7 +1070,7 @@ - (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. @@ -932,6 +1094,7 @@ - (NSRect)contentRectForFrameRect:(NSRect)frameRect { webViewHeight - iter->bottom(), iter->width(), iter->height())]; + [controlRegion setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; [webView addSubview:controlRegion]; } } 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 71% rename from src/browser/native_window_toolbar_win.h rename to src/browser/native_window_toolbar_aura.h index 81cc82bf9a..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,22 +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( - const ViewHierarchyChangedDetails& details) 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(); @@ -81,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 1c125f5233..270f4ef466 100644 --- a/src/browser/native_window_toolbar_win.cc +++ b/src/browser/native_window_toolbar_win.cc @@ -18,7 +18,7 @@ // 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/strings/string16.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,21 +87,21 @@ void NativeWindowToolbarWin::Layout() { 24); } -void NativeWindowToolbarWin::ViewHierarchyChanged( +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()) @@ -113,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); @@ -129,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( @@ -165,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); @@ -192,7 +192,7 @@ void NativeWindowToolbarWin::InitToolbar() { AddChildView(dev_reload_button_); } -void NativeWindowToolbarWin::SetButtonEnabled( +void NativeWindowToolbarAura::SetButtonEnabled( NativeWindow::TOOLBAR_BUTTON button, bool enabled) { switch (button) { @@ -214,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 d4faaf4c7b..0000000000 --- a/src/browser/native_window_win.cc +++ /dev/null @@ -1,769 +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 - -#include "base/strings/utf_string_conversions.h" -#include "base/values.h" -#include "base/win/scoped_comptr.h" -#include "base/win/windows_version.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/gfx/native_widget_types.h" -#include "ui/gfx/win/hwnd_util.h" -#include "ui/gfx/path.h" -#include "ui/gfx/image/image_skia_operations.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, - const base::WeakPtr& shell) - : views::ClientView(widget, contents_view), - shell_(shell) { - } - virtual ~NativeWindowClientView() {} - - virtual 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(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 const char* 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) { -} - -const char* 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(const base::WeakPtr& shell, - base::DictionaryValue* manifest) - : NativeWindow(shell, manifest), - web_view_(NULL), - toolbar_(NULL), - is_fullscreen_(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_); - - 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)); - last_width_ = width; - last_height_ = height; - window_->AddObserver(this); - 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() { - VLOG(1) << "NativeWindowWin::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 - window_->native_widget_private()->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 (shell()) { - 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((HWND)window_->GetNativeView(), GWL_STYLE); - if (resizable) - style |= WS_MAXIMIZEBOX; - else - style &= ~WS_MAXIMIZEBOX; - ::SetWindowLong((HWND)window_->GetNativeView(), GWL_STYLE, style); -} - -void NativeWindowWin::SetShowInTaskbar(bool show) { - if (show == false && base::win::GetVersion() < base::win::VERSION_VISTA) { - // Change the owner of native window. Only needed on Windows XP. - ::SetWindowLong(window_->GetNativeView(), - GWL_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(window_->GetNativeWindow()); - else - result = taskbar->DeleteTab(window_->GetNativeWindow()); - - if (FAILED(result)) { - LOG(ERROR) << "Failed to change the show in taskbar attribute"; - return; - } -} - -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::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 NativeWindowWin::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") { - 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); - } - } -} - -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::SetBadgeLabel(const std::string& badge) { - // TODO -} - -void NativeWindowWin::SetKiosk(bool kiosk) { - SetFullscreen(kiosk); -} - -bool NativeWindowWin::IsKiosk() { - return IsFullscreen(); -} - -void NativeWindowWin::SetMenu(nwapi::Menu* menu) { - window_->set_has_menu_bar(true); - menu_ = menu; - - // The menu is lazily built. - menu->Rebuild(); - - // menu is nwapi::Menu, menu->menu_ is NativeMenuWin, - ::SetMenu((HWND)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; -} - -void NativeWindowWin::OnWidgetMove() { - gfx::Point origin = GetPosition(); - if (shell()) { - base::ListValue args; - args.AppendInteger(origin.x()); - args.AppendInteger(origin.y()); - shell()->SendEvent("move", args); - } -} - -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() { - OnNativeWindowDestory(); -} - -bool NativeWindowWin::ShouldShowWindowTitle() const { - return has_frame(); -} - -bool NativeWindowWin::ShouldHandleOnSize() const { - return true; -} - -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()) - 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 NativeWindowWin::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 NativeWindowWin::GetWindowIcon() { - gfx::Image icon = app_icon(); - if (icon.IsEmpty()) - return gfx::ImageSkia(); - - return *icon.ToImageSkia(); -} - -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( - const ViewHierarchyChangedDetails& details) { - if (details.is_add && details.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(); -} - -void NativeWindowWin::SetInitialFocus(bool initial_focus) { - initial_focus_ = initial_focus; -} - -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; - 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"); - } - return false; -} - -bool NativeWindowWin::HandleSize(unsigned int param, const gfx::Size& size) { - 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"); - } - return false; -} - -bool NativeWindowWin::ExecuteAppCommand(int command_id) { - if (menu_) { - menu_->menu_delegate_->ExecuteCommand(command_id, 0); - 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((HWND)web_contents()->GetView()->GetNativeView(), - 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 (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 9fb0591895..0000000000 --- a/src/browser/native_window_win.h +++ /dev/null @@ -1,173 +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/base/win/hidden_window.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" -#include "ui/views/widget/widget_observer.h" - -namespace views { -class WebView; -} - -namespace nw { - -class NativeWindowToolbarWin; - -class NativeWindowWin : public NativeWindow, - public views::WidgetFocusChangeListener, - public views::WidgetDelegateView , - public views::WidgetObserver { - public: - explicit NativeWindowWin(const base::WeakPtr& 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 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 SetKiosk(bool kiosk) OVERRIDE; - virtual void SetBadgeLabel(const std::string& badge) OVERRIDE; - virtual bool IsKiosk() OVERRIDE; - virtual void SetMenu(nwapi::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; - virtual void SetInitialFocus(bool initial_focus) OVERRIDE; - virtual bool InitialFocus() OVERRIDE { return initial_focus_; } - - // WidgetDelegate implementation. - virtual void OnWidgetMove() OVERRIDE; - 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; - virtual bool ShouldHandleOnSize() const OVERRIDE; - - // WidgetFocusChangeListener implementation. - virtual void OnNativeFocusChange(gfx::NativeView focused_before, - gfx::NativeView focused_now) OVERRIDE; - - // WidgetObserver implementation - virtual void OnWidgetBoundsChanged(views::Widget* widget, const gfx::Rect& new_bounds) 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( - const ViewHierarchyChangedDetails& details) 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 HandleSize(unsigned int param, const gfx::Size& size) OVERRIDE; - virtual bool ExecuteAppCommand(int command_id) OVERRIDE; - virtual void SaveWindowPlacement(const gfx::Rect& bounds, - ui::WindowShowState show_state) OVERRIDE; - - private: - friend class content::Shell; - 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_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_; - - DISALLOW_COPY_AND_ASSIGN(NativeWindowWin); -}; - -} // namespace nw - -#endif // CONTENT_NW_SRC_BROWSER_NATIVE_WINDOW_WIN_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 11ca278c76..2d6cdf7c25 100644 --- a/src/browser/printing/print_dialog_gtk.cc +++ b/src/browser/printing/print_dialog_gtk.cc @@ -122,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), @@ -232,7 +232,7 @@ bool PrintDialogGtk::UpdateSettings(printing::PrintSettings* settings) { 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)); @@ -266,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)); @@ -275,7 +275,7 @@ 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; } @@ -365,13 +365,13 @@ void PrintDialogGtk::OnResponse(GtkWidget* dialog, int response_id) { printing::PrintSettingsInitializerGtk::InitPrintSettings( 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; } @@ -382,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 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 d8e5cb79bd..7485983945 100644 --- a/src/browser/printing/print_job.cc +++ b/src/browser/printing/print_job.cc @@ -10,6 +10,7 @@ #include "base/threading/thread_restrictions.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_types.h" #include "content/public/browser/notification_service.h" @@ -38,10 +39,7 @@ PrintJob::PrintJob() settings_(), is_job_pending_(false), is_canceling_(false), - is_stopping_(false), - is_stopped_(false), - quit_factory_(this), - weak_ptr_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. @@ -73,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); @@ -164,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() { @@ -225,14 +220,6 @@ bool PrintJob::is_job_pending() const { return is_job_pending_; } -bool PrintJob::is_stopping() const { - return is_stopping_; -} - -bool PrintJob::is_stopped() const { - return is_stopped_; -} - PrintedDocument* PrintJob::document() const { return document_.get(); } @@ -326,53 +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().platform_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 // Now make sure the thread object is cleaned up. Do this on a worker // thread because it may block. - is_stopping_ = true; - base::WorkerPool::PostTaskAndReply( FROM_HERE, - base::Bind(&PrintJobWorker::Stop, - base::Unretained(worker_.get())), - base::Bind(&PrintJob::HoldUntilStopIsCalled, - weak_ptr_factory_.GetWeakPtr(), - scoped_refptr(this)), + 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(const scoped_refptr&) { - is_stopped_ = true; - is_stopping_ = false; +void PrintJob::HoldUntilStopIsCalled() { } void PrintJob::Quit() { @@ -391,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 35c3f7bc35..6b20bf40ba 100644 --- a/src/browser/printing/print_job.h +++ b/src/browser/printing/print_job.h @@ -88,12 +88,6 @@ class PrintJob : public PrintJobWorkerOwner, // and the end of the spooling. bool is_job_pending() const; - // Returns true if the worker thread is in the process of stopping. - bool is_stopping() const; - - // Returns true if the worker thread has stopped. - bool is_stopped() const; - // Access the current printed document. Warning: may be NULL. PrintedDocument* document() const; @@ -118,7 +112,7 @@ class PrintJob : public PrintJobWorkerOwner, // Called at shutdown when running a nested message loop. void Quit(); - void HoldUntilStopIsCalled(const scoped_refptr& job); + void HoldUntilStopIsCalled(); content::NotificationRegistrar registrar_; @@ -148,17 +142,9 @@ class PrintJob : public PrintJobWorkerOwner, // the notified calls Cancel() again. bool is_canceling_; - // Is the worker thread stopping. - bool is_stopping_; - - // Is the worker thread stopped. - bool is_stopped_; - // Used at shutdown so that we can quit a nested message loop. base::WeakPtrFactory quit_factory_; - base::WeakPtrFactory weak_ptr_factory_; - DISALLOW_COPY_AND_ASSIGN(PrintJob); }; diff --git a/src/browser/printing/print_job_worker.cc b/src/browser/printing/print_job_worker.cc index 8f86529e68..7da21ca269 100644 --- a/src/browser/printing/print_job_worker.cc +++ b/src/browser/printing/print_job_worker.cc @@ -22,6 +22,7 @@ #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; @@ -117,7 +118,7 @@ void PrintJobWorker::GetSettings( } } -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( @@ -128,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); } @@ -216,10 +194,10 @@ void PrintJobWorker::StartPrinting(PrintedDocument* 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 = @@ -273,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_owner.h b/src/browser/printing/print_job_worker_owner.h index 8f9f58509f..00a561a39b 100644 --- a/src/browser/printing/print_job_worker_owner.h +++ b/src/browser/printing/print_job_worker_owner.h @@ -10,8 +10,12 @@ namespace base { class MessageLoop; +class SequencedTaskRunner; } +namespace tracked_objects { +class Location; +} namespace printing { @@ -21,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. @@ -30,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 base::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 3d71ec9513..60a7c451fb 100644 --- a/src/browser/printing/print_view_manager.cc +++ b/src/browser/printing/print_view_manager.cc @@ -35,7 +35,6 @@ #include "content/public/browser/notification_source.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" @@ -52,8 +51,10 @@ DEFINE_WEB_CONTENTS_USER_DATA_KEY(printing::PrintViewManager); namespace { +#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 @@ -136,8 +137,8 @@ void PrintViewManager::RenderProcessGone(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; @@ -196,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()) { @@ -219,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); diff --git a/src/browser/printing/print_view_manager.h b/src/browser/printing/print_view_manager.h index 1670573ba9..f4640bcb95 100644 --- a/src/browser/printing/print_view_manager.h +++ b/src/browser/printing/print_view_manager.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 RenderProcessGone(base::TerminationStatus status) OVERRIDE; + virtual void RenderProcessGone(base::TerminationStatus status) override; // Cancels the print job. - virtual void NavigationStopped() OVERRIDE; + virtual void NavigationStopped() override; private: explicit PrintViewManager(content::WebContents* web_contents); diff --git a/src/browser/printing/printer_query.cc b/src/browser/printing/printer_query.cc index 425baaf29e..3bcfb8eb84 100644 --- a/src/browser/printing/printer_query.cc +++ b/src/browser/printing/printer_query.cc @@ -95,7 +95,7 @@ void PrinterQuery::GetSettings( 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/printing_message_filter.cc b/src/browser/printing/printing_message_filter.cc index 7a1a88f721..fae9f34a7f 100644 --- a/src/browser/printing/printing_message_filter.cc +++ b/src/browser/printing/printing_message_filter.cc @@ -2,33 +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 "content/nw/src/browser/printing/printer_query.h" -#include "content/nw/src/browser/printing/print_job_manager.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/content_client.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) @@ -44,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( @@ -77,12 +90,13 @@ void RenderParamsFromPrintSettings(const printing::PrintSettings& settings, } // namespace PrintingMessageFilter::PrintingMessageFilter(int render_process_id) - : print_job_manager_(NULL), - render_process_id_(render_process_id) { - + : 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() { @@ -95,17 +109,21 @@ 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, @@ -117,7 +135,9 @@ bool PrintingMessageFilter::OnMessageReceived(const IPC::Message& message, 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; @@ -134,10 +154,14 @@ void PrintingMessageFilter::OnDuplicateSection( } #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; @@ -145,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); @@ -159,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()) { @@ -178,107 +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); -} - -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); - if (wc) { - scoped_ptr wc_observer( - new PrintingUIWebContentsObserver(wc)); - BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - base::Bind(&printing::PrinterQuery::GetSettings, printer_query, - params.ask_user_for_settings, base::Passed(&wc_observer), - params.expected_page_count, params.has_selection, - params.margin_type, callback)); - } else { - BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - base::Bind(&PrintingMessageFilter::OnGetPrintSettingsFailed, this, - callback, printer_query)); - } + return view ? content::WebContents::FromRenderViewHost(view) : NULL; } void PrintingMessageFilter::OnIsPrintingEnabled(bool* is_enabled) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK_CURRENTLY_ON(BrowserThread::IO); *is_enabled = true; } -void PrintingMessageFilter::OnGetPrintSettingsFailed( - const base::Closure& callback, - scoped_refptr printer_query) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - printer_query->GetSettingsDone(printing::PrintSettings(), - printing::PrintingContext::FAILED); - callback.Run(); -} - void PrintingMessageFilter::OnGetDefaultPrintSettings(IPC::Message* reply_msg) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - scoped_refptr printer_query; - 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); @@ -290,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(); } @@ -300,90 +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(); } } +#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 + void PrintingMessageFilter::OnUpdatePrintSettings( - int document_cookie, const DictionaryValue& job_settings, + int document_cookie, const base::DictionaryValue& job_settings, IPC::Message* reply_msg) { - scoped_refptr printer_query; + scoped_ptr new_settings(job_settings.DeepCopy()); - print_job_manager_->PopPrinterQuery(document_cookie, &printer_query); + 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 b74f7cdcab..0a12d59a07 100644 --- a/src/browser/printing/printing_message_filter.h +++ b/src/browser/printing/printing_message_filter.h @@ -2,12 +2,13 @@ // 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) @@ -15,6 +16,8 @@ #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,35 +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); - - void OnGetPrintSettingsFailed( - 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 @@ -107,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 index 6353c5dccb..23be554307 100644 --- a/src/browser/printing/printing_ui_web_contents_observer.cc +++ b/src/browser/printing/printing_ui_web_contents_observer.cc @@ -6,7 +6,6 @@ #include "content/public/browser/browser_thread.h" #include "content/public/browser/web_contents.h" -#include "content/public/browser/web_contents_view.h" PrintingUIWebContentsObserver::PrintingUIWebContentsObserver( content::WebContents* web_contents) @@ -16,5 +15,5 @@ PrintingUIWebContentsObserver::PrintingUIWebContentsObserver( gfx::NativeView PrintingUIWebContentsObserver::GetParentView() { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); - return web_contents() ? web_contents()->GetView()->GetNativeView() : NULL; + return web_contents() ? web_contents()->GetNativeView() : NULL; } 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_component_extension_resource_manager.cc b/src/browser/shell_component_extension_resource_manager.cc new file mode 100644 index 0000000000..e700fbc529 --- /dev/null +++ b/src/browser/shell_component_extension_resource_manager.cc @@ -0,0 +1,61 @@ +// 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_component_extension_resource_manager.h" + +#include "base/logging.h" +#include "base/path_service.h" +#include "grit/nw_component_resources_map.h" + +namespace extensions { + +ShellComponentExtensionResourceManager:: +ShellComponentExtensionResourceManager() { + + AddComponentResourceEntries( + kNwComponentResources, kNwComponentResourcesSize); +} + +ShellComponentExtensionResourceManager:: +~ShellComponentExtensionResourceManager() {} + +bool ShellComponentExtensionResourceManager::IsComponentExtensionResource( + const base::FilePath& extension_path, + const base::FilePath& resource_path, + int* resource_id) const { + base::FilePath directory_path = extension_path; + base::FilePath resources_dir; + base::FilePath relative_path; +#if 0 + if (!PathService::Get(chrome::DIR_RESOURCES, &resources_dir) || + !resources_dir.AppendRelativePath(directory_path, &relative_path)) { + return false; + } +#endif + relative_path = relative_path.Append(resource_path); + relative_path = relative_path.NormalizePathSeparators(); + + std::map::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 b0caa9d698..4f08cc67b6 100644 --- a/src/browser/shell_devtools_delegate.cc +++ b/src/browser/shell_devtools_delegate.cc @@ -40,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() { @@ -82,11 +82,12 @@ class Target : public content::DevToolsTarget { 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 GURL GetURL() const OVERRIDE { return url_; } + virtual GURL GetFaviconURL() const OVERRIDE { return GURL(); } virtual base::TimeTicks GetLastActivityTime() const OVERRIDE { return last_activity_time_; } @@ -110,18 +111,15 @@ class Target : public content::DevToolsTarget { Target::Target(WebContents* web_contents) { agent_host_ = - DevToolsAgentHost::GetOrCreateFor(web_contents->GetRenderViewHost()); + DevToolsAgentHost::GetOrCreateFor(web_contents); id_ = agent_host_->GetId(); - title_ = UTF16ToUTF8(web_contents->GetTitle()); + title_ = base::UTF16ToUTF8(web_contents->GetTitle()); url_ = web_contents->GetURL(); - last_activity_time_ = web_contents->GetLastSelectedTime(); + last_activity_time_ = web_contents->GetLastActiveTime(); } bool Target::Activate() const { - RenderViewHost* rvh = agent_host_->GetRenderViewHost(); - if (!rvh) - return false; - WebContents* web_contents = WebContents::FromRenderViewHost(rvh); + WebContents* web_contents = agent_host_->GetWebContents(); if (!web_contents) return false; web_contents->GetDelegate()->ActivateContents(web_contents); @@ -129,22 +127,21 @@ bool Target::Activate() const { } bool Target::Close() const { - RenderViewHost* rvh = agent_host_->GetRenderViewHost(); - if (!rvh) + WebContents* web_contents = agent_host_->GetWebContents(); + if (!web_contents) return false; - rvh->ClosePage(); + web_contents->GetRenderViewHost()->ClosePage(); return true; } void ShellDevToolsDelegate::EnumerateTargets(TargetCallback callback) { TargetList targets; - std::vector rvh_list = - content::DevToolsAgentHost::GetValidRenderViewHosts(); - for (std::vector::iterator it = rvh_list.begin(); - it != rvh_list.end(); ++it) { - WebContents* web_contents = WebContents::FromRenderViewHost(*it); - if (web_contents) - targets.push_back(new Target(web_contents)); + 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); } diff --git a/src/browser/shell_devtools_delegate.h b/src/browser/shell_devtools_delegate.h index 111e28e55d..cdde5b7f7f 100644 --- a/src/browser/shell_devtools_delegate.h +++ b/src/browser/shell_devtools_delegate.h @@ -48,15 +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 scoped_ptr CreateNewTarget(const GURL& url) OVERRIDE; - virtual void EnumerateTargets(TargetCallback callback) 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; + 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 1b2910a31f..f5dd1b73d0 100644 --- a/src/browser/shell_download_manager_delegate.cc +++ b/src/browser/shell_download_manager_delegate.cc @@ -30,7 +30,7 @@ #endif #include "base/bind.h" -#include "base/file_util.h" +#include "base/files/file_util.h" #include "base/logging.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" @@ -38,7 +38,7 @@ #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,6 +102,12 @@ bool ShellDownloadManagerDelegate::DetermineDownloadTarget( return true; } +bool ShellDownloadManagerDelegate::ShouldOpenDownload( + DownloadItem* item, + const DownloadOpenDelayedCallback& callback) { + return true; +} + void ShellDownloadManagerDelegate::GenerateFilename( int32 download_id, const DownloadTargetCallback& callback, @@ -109,7 +115,7 @@ void ShellDownloadManagerDelegate::GenerateFilename( const FilePath& suggested_directory) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); if (!base::PathExists(suggested_directory)) - file_util::CreateDirectory(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 0e8d4fe50e..b782e30546 100644 --- a/src/browser/shell_download_manager_delegate_gtk.cc +++ b/src/browser/shell_download_manager_delegate_gtk.cc @@ -20,12 +20,12 @@ #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/strings/string_util.h" #include "base/strings/utf_string_conversions.h" @@ -33,7 +33,6 @@ #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 195b568fbe..18d21a258a 100644 --- a/src/browser/shell_download_manager_delegate_mac.mm +++ b/src/browser/shell_download_manager_delegate_mac.mm @@ -24,7 +24,7 @@ #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/strings/string_util.h" @@ -33,7 +33,6 @@ #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 38aab6be6a..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/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 fe1235b42b..8b4b8a649c 100644 --- a/src/browser/shell_javascript_dialog_creator.cc +++ b/src/browser/shell_javascript_dialog_creator.cc @@ -24,7 +24,6 @@ #include "base/logging.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,26 +92,26 @@ 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 } diff --git a/src/browser/shell_javascript_dialog_creator.h b/src/browser/shell_javascript_dialog_creator.h index e22cb31ad9..81c32e5a8c 100644 --- a/src/browser/shell_javascript_dialog_creator.h +++ b/src/browser/shell_javascript_dialog_creator.h @@ -17,31 +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 CancelActiveAndPendingDialogs( - WebContents* web_contents) OVERRIDE; + void CancelActiveAndPendingDialogs( + WebContents* web_contents) override; // Called by the ShellJavaScriptDialog when it closes. void DialogClosed(ShellJavaScriptDialog* dialog); - virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE; + 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 cff5458515..133fc98e0d 100644 --- a/src/browser/shell_javascript_dialog_gtk.cc +++ b/src/browser/shell_javascript_dialog_gtk.cc @@ -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 ca03d0db25..a35c621b5e 100644 --- a/src/browser/shell_javascript_dialog_mac.mm +++ b/src/browser/shell_javascript_dialog_mac.mm @@ -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) { diff --git a/src/browser/shell_javascript_dialog_win.cc b/src/browser/shell_javascript_dialog_win.cc index 7e192d3b3c..636bf3028e 100644 --- a/src/browser/shell_javascript_dialog_win.cc +++ b/src/browser/shell_javascript_dialog_win.cc @@ -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 b103c5efd0..1dfb826d1b 100644 --- a/src/browser/shell_login_dialog.cc +++ b/src/browser/shell_login_dialog.cc @@ -1,79 +1,43 @@ -// 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/strings/utf_string_conversions.h" -#include "chrome/browser/chrome_notification_types.h" #include "content/public/browser/browser_thread.h" -#include "content/public/browser/notification_registrar.h" -#include "content/public/browser/notification_service.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/gfx/text_elider.h" -using content::BrowserThread; -using content::ResourceDispatcherHost; - -namespace { -// Helper to remove the ref from an net::URLRequest to the ShellLoginDialog. -// Should only be called from the IO thread, since it accesses an -// net::URLRequest. -void ResetShellLoginDialogForRequest(net::URLRequest* request) { - ResourceDispatcherHost::Get()->ClearLoginDelegateForRequest(request); -} - -} // namespace - namespace content { -ShellLoginDialog::ShellLoginDialogList ShellLoginDialog::dialog_queue_; - ShellLoginDialog::ShellLoginDialog( net::AuthChallengeInfo* auth_info, - net::URLRequest* request) - : render_process_id_(0), - render_view_id_(0), - auth_info_(auth_info), - request_(request), - handled_auth_(false) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + net::URLRequest* request) : auth_info_(auth_info), + request_(request) { +#if !defined(OS_MACOSX) + AddRef(); +#endif + +#if defined(OS_WIN) + dialog_ = NULL; +#endif - if (!ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderView( - &render_process_id_, &render_view_id_)) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + if (!ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderFrame( + &render_process_id_, &render_frame_id_)) { NOTREACHED(); } - - // prevent object destruction on wrong (IO) thread - // paired with ReleaseSoon() - AddRef(); - 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() { @@ -83,24 +47,9 @@ 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)); - - if (TestAndSetAuthHandled()) - return; - - // Calling NotifyAuthSupplied() directly instead of posting a task - // allows other ShellLoginDialog instances to queue their - // CloseContentsDeferred() before ours. Closing dialogs in the - // opposite order as they were created avoids races where remaining - // dialogs in the same tab may be briefly displayed to the user - // before they are removed. - NotifyAuthSupplied(username, password); - - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(&ShellLoginDialog::CloseContentsDeferred, this)); BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&ShellLoginDialog::SendAuthToRequester, this, @@ -109,25 +58,13 @@ void ShellLoginDialog::UserAcceptedAuth(const string16& username, void ShellLoginDialog::UserCancelledAuth() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - if (TestAndSetAuthHandled()) - return; - - // Similar to how we deal with notifications above in SetAuth() - if (BrowserThread::CurrentlyOn(BrowserThread::UI)) { - NotifyAuthCancelled(); - } else { - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(&ShellLoginDialog::NotifyAuthCancelled, this)); - } - 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::CloseContentsDeferred, this)); + base::Bind(&ShellLoginDialog::PlatformCleanUp, this)); } ShellLoginDialog::~ShellLoginDialog() { @@ -135,203 +72,44 @@ 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; + 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("."); } - AddObservers(); - dialog_queue_.push_back(this); PlatformCreateDialog(explanation); - if (dialog_queue_.size() == 1) - PlatformShowDialog(); -} - -void ShellLoginDialog::HandleQueueOnClose() -{ - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - ShellLoginDialogList::iterator i( - std::find(dialog_queue_.begin(), dialog_queue_.end(), this)); - - // Ignore the second invocation. - if (i == dialog_queue_.end()) - return; - - bool removed_topmost_dialog = i == dialog_queue_.begin(); - dialog_queue_.erase(i); - if (!dialog_queue_.empty() && removed_topmost_dialog) { - ShellLoginDialog* next_dlg = dialog_queue_.front(); - next_dlg->PlatformShowDialog(); - } } 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) { + if (success) request_->SetAuth(net::AuthCredentials(username, password)); - } else + else request_->CancelAuth(); ResourceDispatcherHost::Get()->ClearLoginDelegateForRequest(request_); -} - -void ShellLoginDialog::NotifyAuthSupplied(const string16& username, - const string16& password) { - - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - content::NotificationService* service = - content::NotificationService::current(); - - AuthSuppliedLoginNotificationDetails details(this, username, password); - - service->Notify(chrome::NOTIFICATION_AUTH_SUPPLIED, - content::Source(this), - content::Details(&details)); -} - -void ShellLoginDialog::NotifyAuthCancelled() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(WasAuthHandled()); - - content::NotificationService* service = - content::NotificationService::current(); - - AuthSuppliedLoginNotificationDetails details(this, string16(), string16()); - - service->Notify(chrome::NOTIFICATION_AUTH_CANCELLED, - content::Source(this), - content::Details(&details)); -} - -void ShellLoginDialog::Observe(int type, - const content::NotificationSource& source, - const content::NotificationDetails& details) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(type == chrome::NOTIFICATION_AUTH_SUPPLIED || - type == chrome::NOTIFICATION_AUTH_CANCELLED); - - // Break out early if we aren't interested in the notification. - if (WasAuthHandled()) - return; - - LoginNotificationDetails* login_details = - content::Details(details).ptr(); - - // WasAuthHandled() should always test positive before we publish - // AUTH_SUPPLIED or AUTH_CANCELLED notifications. - DCHECK(login_details->handler() != this); - - // Only handle notification for the identical auth info. - if (!login_details->handler()->auth_info()->Equals(*auth_info())) - return; - - // Set or cancel the auth in this handler. - if (type == chrome::NOTIFICATION_AUTH_SUPPLIED) { - AuthSuppliedLoginNotificationDetails* supplied_details = - content::Details(details).ptr(); - UserAcceptedAuth(supplied_details->username(), supplied_details->password()); - } else { - DCHECK(type == chrome::NOTIFICATION_AUTH_CANCELLED); - UserCancelledAuth(); - } -} - -// Returns whether authentication had been handled (SetAuth or CancelAuth). -bool ShellLoginDialog::WasAuthHandled() const { - base::AutoLock lock(handled_auth_lock_); - bool was_handled = handled_auth_; - return was_handled; -} - -// Marks authentication as handled and returns the previous handled state. -bool ShellLoginDialog::TestAndSetAuthHandled() { - base::AutoLock lock(handled_auth_lock_); - bool was_handled = handled_auth_; - handled_auth_ = true; - return was_handled; -} - -// Closes the view_contents from the UI loop. -void ShellLoginDialog::CloseContentsDeferred() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - HandleQueueOnClose(); - PlatformCleanUp(); -} - -void ShellLoginDialog::AddObservers() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - // This is probably OK; we need to listen to everything and we break out of - // the Observe() if we aren't handling the same auth_info(). - registrar_.reset(new content::NotificationRegistrar); - registrar_->Add(this, chrome::NOTIFICATION_AUTH_SUPPLIED, - content::NotificationService::AllBrowserContextsAndSources()); - registrar_->Add(this, chrome::NOTIFICATION_AUTH_CANCELLED, - content::NotificationService::AllBrowserContextsAndSources()); -} - -void ShellLoginDialog::RemoveObservers() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - registrar_.reset(); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&ShellLoginDialog::PlatformCleanUp, this)); } void ShellLoginDialog::ReleaseSoon() { - if (!TestAndSetAuthHandled()) { - BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - base::Bind(&ShellLoginDialog::CancelAuthDeferred, this)); - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(&ShellLoginDialog::NotifyAuthCancelled, this)); - } - - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(&ShellLoginDialog::RemoveObservers, this)); - - // Delete this object once all InvokeLaters have been called. BrowserThread::ReleaseSoon(BrowserThread::IO, FROM_HERE, this); } -// Calls SetAuth from the IO loop. -void ShellLoginDialog::SetAuthDeferred(const string16& username, - const string16& password) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - - if (request_) { - request_->SetAuth(net::AuthCredentials(username, password)); - ResetShellLoginDialogForRequest(request_); - } -} - -// Calls CancelAuth from the IO loop. -void ShellLoginDialog::CancelAuthDeferred() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - - if (request_) { - request_->CancelAuth(); - // Verify that CancelAuth doesn't destroy the request via our delegate. - DCHECK(request_ != NULL); - ResetShellLoginDialogForRequest(request_); - } -} - } // namespace content diff --git a/src/browser/shell_login_dialog.h b/src/browser/shell_login_dialog.h index 48f0e617b3..b4c8d1a3db 100644 --- a/src/browser/shell_login_dialog.h +++ b/src/browser/shell_login_dialog.h @@ -1,39 +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_ - -#include -#include "base/basictypes.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/memory/scoped_ptr.h" #include "base/strings/string16.h" -#include "base/synchronization/lock.h" -#include "content/public/browser/notification_observer.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; @@ -42,11 +26,6 @@ class ShellLoginDialogHelper; #endif // __OBJC__ #endif // defined(OS_MACOSX) -namespace content { -class RenderViewHostDelegate; -class NotificationRegistrar; -} // namespace content - namespace net { class AuthChallengeInfo; class URLRequest; @@ -56,79 +35,56 @@ namespace content { // This class provides a dialog box to ask the user for credentials. Useful in // ResourceDispatcherHostDelegate::CreateLoginDelegate. -class ShellLoginDialog : public ResourceDispatcherHostLoginDelegate, - public content::NotificationObserver -{ +#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(); - // Implements the content::NotificationObserver interface. - // Listens for AUTH_SUPPLIED and AUTH_CANCELLED notifications from other - // LoginHandlers so that this LoginHandler has the chance to dismiss itself - // if it was waiting for the same authentication. - virtual void Observe(int type, - const content::NotificationSource& source, - const content::NotificationDetails& details) OVERRIDE; - - // Who/where/what asked for the authentication. - const net::AuthChallengeInfo* auth_info() const { return auth_info_.get(); } - - // Returns whether authentication had been handled (SetAuth or CancelAuth). - bool WasAuthHandled() const; - - // Performs necessary cleanup before deletion. - void ReleaseSoon(); +#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_view_id_; + int render_frame_id_; private: - - // popup next dialog in queue on closing of this dialog - void HandleQueueOnClose(); - - // Calls SetAuth from the IO loop. - void SetAuthDeferred(const string16& username, - const string16& password); - - // Calls CancelAuth from the IO loop. - void CancelAuthDeferred(); - - // Closes the view_contents from the UI loop. - void CloseContentsDeferred(); - - // Marks authentication as handled and returns the previous handled - // state. - bool TestAndSetAuthHandled(); - - // Notify observers that authentication is supplied. - void NotifyAuthSupplied(const string16& username, - const string16& password); - - void NotifyAuthCancelled(); // 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 PlatformShowDialog(); + void PlatformCreateDialog(const base::string16& message); // Called from the destructor to let each platform do any necessary cleanup. // Threading: UI thread. void PlatformCleanUp(); @@ -138,19 +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); - - // Starts observing notifications from other LoginHandlers. - void AddObservers(); - - // Stops observing notifications from other LoginHandlers. - void RemoveObservers(); + const base::string16& username, + const base::string16& password); // Who/where/what asked for the authentication. // Threading: IO thread. @@ -160,77 +110,22 @@ class ShellLoginDialog : public ResourceDispatcherHostLoginDelegate, // Threading: IO thread. net::URLRequest* request_; - // True if we've handled auth (SetAuth or CancelAuth has been called). - bool handled_auth_; - mutable base::Lock handled_auth_lock_; - - // Observes other login handlers so this login handler can respond. - // This is only accessed on the UI thread. - scoped_ptr registrar_; - - typedef std::deque ShellLoginDialogList; - - static ShellLoginDialogList dialog_queue_; - #if defined(OS_MACOSX) // Threading: UI thread. ShellLoginDialogHelper* helper_; // owned - base::string16 message_; -#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); -#endif -}; +#elif defined(OS_WIN) || defined(OS_LINUX) + LoginView* login_view_; -// Details to provide the content::NotificationObserver. Used by the automation -// proxy for testing. -class LoginNotificationDetails { - public: - explicit LoginNotificationDetails(ShellLoginDialog* handler) - : handler_(handler) {} - ShellLoginDialog* handler() const { return handler_; } - - private: - LoginNotificationDetails() {} - - ShellLoginDialog* handler_; // Where to send the response. - - DISALLOW_COPY_AND_ASSIGN(LoginNotificationDetails); -}; - - -// Details to provide the NotificationObserver. Used by the automation proxy -// for testing and by other LoginHandlers to dismiss themselves when an -// identical auth is supplied. -class AuthSuppliedLoginNotificationDetails : public LoginNotificationDetails { - public: - AuthSuppliedLoginNotificationDetails(ShellLoginDialog* handler, - const string16& username, - const string16& password) - : LoginNotificationDetails(handler), - username_(username), - password_(password) {} - const string16& username() const { return username_; } - const string16& password() const { return password_; } - - private: - // The username that was used for the authentication. - const string16 username_; - - // The password that was used for the authentication. - const string16 password_; - - DISALLOW_COPY_AND_ASSIGN(AuthSuppliedLoginNotificationDetails); + 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 e2a6447966..d8239351ff 100644 --- a/src/browser/shell_login_dialog_gtk.cc +++ b/src/browser/shell_login_dialog_gtk.cc @@ -1,22 +1,6 @@ -// 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" @@ -26,30 +10,21 @@ #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,34 +69,26 @@ 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); + g_signal_connect(root_, "destroy", G_CALLBACK(OnDestroyThunk), this); gtk_widget_grab_focus(username_entry_); + gtk_widget_show_all(GTK_WIDGET(root_)); } void ShellLoginDialog::PlatformCleanUp() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - if (root_) { - gtk_widget_destroy(root_); - root_ = NULL; - } } void ShellLoginDialog::PlatformRequestCancelled() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); } -void ShellLoginDialog::PlatformShowDialog() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - gtk_widget_show_all(GTK_WIDGET(root_)); -} - 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: @@ -131,12 +98,12 @@ void ShellLoginDialog::OnResponse(GtkWidget* sender, int response_id) { NOTREACHED(); } + gtk_widget_destroy(root_); } void ShellLoginDialog::OnDestroy(GtkWidget* widget) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - // The web contents modal dialog is going to delete itself; clear our pointer. root_ = NULL; ReleaseSoon(); diff --git a/src/browser/shell_login_dialog_mac.mm b/src/browser/shell_login_dialog_mac.mm index eb4ae0ec0d..3e014e8791 100644 --- a/src/browser/shell_login_dialog_mac.mm +++ b/src/browser/shell_login_dialog_mac.mm @@ -1,24 +1,8 @@ -// 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 @@ -80,14 +64,11 @@ - (void)focus { - (void)alertDidEnd:(NSAlert*)alert returnCode:(int)returnCode contextInfo:(void*)contextInfo { + if (returnCode == NSRunStoppedResponse) + return; content::ShellLoginDialog* this_dialog = reinterpret_cast(contextInfo); - if (returnCode == NSRunStoppedResponse) { - this_dialog->ReleaseSoon(); - return; - } - if (returnCode == NSAlertFirstButtonReturn) { this_dialog->UserAcceptedAuth( base::SysNSStringToUTF16([usernameField_ stringValue]), @@ -106,17 +87,14 @@ - (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]; - message_ = message; -} -void ShellLoginDialog::PlatformShowDialog() { // Show the modal dialog. NSAlert* alert = [helper_ alert]; [alert setDelegate:helper_]; - [alert setInformativeText:base::SysUTF16ToNSString(message_)]; + [alert setInformativeText:base::SysUTF16ToNSString(message)]; [alert setMessageText:@"Please log in."]; [alert addButtonWithTitle:@"OK"]; NSButton* other = [alert addButtonWithTitle:@"Cancel"]; @@ -134,7 +112,6 @@ - (void)cancel { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); [helper_ release]; helper_ = nil; - ReleaseSoon(); } void ShellLoginDialog::PlatformRequestCancelled() { 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 358cedf035..0000000000 --- a/src/browser/shell_login_dialog_win.cc +++ /dev/null @@ -1,129 +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/strings/string16.h" -#include "base/strings/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_); - break; - } - case WM_DESTROY: { - ShellLoginDialog* owner = reinterpret_cast( - GetWindowLongPtr(dialog, DWL_USER)); - owner->dialog_win_ = NULL; - owner->ReleaseSoon(); - 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 { - DLOG(INFO) << "wparam is " << LOWORD(wparam); - // NOTREACHED(); - } - - break; - } - default: - return DefWindowProc(dialog, message, wparam, lparam); - } - - return 0; -} - -void ShellLoginDialog::PlatformCreateDialog(const string16& message) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - 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)); -} - -void ShellLoginDialog::PlatformShowDialog() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - ShowWindow(dialog_win_, SW_SHOWNORMAL); -} - -void ShellLoginDialog::PlatformCleanUp() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - if (dialog_win_) { - DestroyWindow(dialog_win_); - dialog_win_ = NULL; - } -} - -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_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 649cdd70ac..80ac54c770 100644 --- a/src/browser/standard_menus_mac.mm +++ b/src/browser/standard_menus_mac.mm @@ -47,7 +47,7 @@ - (void)setAppleMenu:(NSMenu *)menu; void StandardMenusMac::BuildAppleMenu() { NSMenu* appleMenu = [[NSMenu alloc] initWithTitle:@""]; - string16 name = base::UTF8ToUTF16(app_name_); + base::string16 name = base::UTF8ToUTF16(app_name_); [appleMenu addItemWithTitle:l10n_util::GetNSStringFWithFixup(IDS_ABOUT_MAC, name) action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; diff --git a/src/chrome_breakpad_client.cc b/src/chrome_breakpad_client.cc index f73a28203b..d20083d32f 100644 --- a/src/chrome_breakpad_client.cc +++ b/src/chrome_breakpad_client.cc @@ -21,6 +21,8 @@ #include "chrome/common/crash_keys.h" #include "chrome/common/env_vars.h" +#include "id/commit.h" + #if defined(OS_WIN) #include @@ -37,7 +39,7 @@ #endif #if defined(OS_POSIX) -#include "chrome/common/dump_without_crashing.h" +#include "base/debug/dump_without_crashing.h" #endif #if defined(OS_WIN) || defined(OS_MACOSX) @@ -77,10 +79,10 @@ ChromeBreakpadClient::ChromeBreakpadClient() {} ChromeBreakpadClient::~ChromeBreakpadClient() {} -void ChromeBreakpadClient::SetClientID(const std::string& client_id) { - crash_keys::SetClientID(client_id); +void ChromeBreakpadClient::SetBreakpadClientIdFromGUID( + const std::string& client_guid) { + crash_keys::SetCrashClientIdFromGUID(client_guid); } - #if defined(OS_WIN) bool ChromeBreakpadClient::GetAlternativeCrashDumpLocation( base::FilePath* crash_dir) { @@ -116,13 +118,7 @@ void ChromeBreakpadClient::GetProductNameAndVersion( if (!version_info->is_official_build()) version->append(base::ASCIIToUTF16("-devel")); - const CommandLine& command = *CommandLine::ForCurrentProcess(); - if (command.HasSwitch(switches::kChromeFrame)) { - *product_name = base::ASCIIToUTF16("ChromeFrame"); - } else { - *product_name = version_info->product_short_name(); - } - + *product_name = version_info->product_short_name(); *special_build = version_info->special_build(); } else { // No version info found. Make up the values. @@ -272,19 +268,10 @@ void ChromeBreakpadClient::GetProductNameAndVersion(std::string* product_name, std::string* version) { DCHECK(product_name); DCHECK(version); -#if defined(OS_ANDROID) - *product_name = "Chrome_Android"; -#elif defined(OS_CHROMEOS) - *product_name = "Chrome_ChromeOS"; -#else // OS_LINUX -#if !defined(ADDRESS_SANITIZER) - *product_name = "Chrome_Linux"; -#else - *product_name = "Chrome_Linux_ASan"; -#endif -#endif - *version = NW_VERSION_STRING; + *product_name = "node-webkit"; + + *version = NW_VERSION_STRING " " NW_COMMIT_HASH; } base::FilePath ChromeBreakpadClient::GetReporterLogFilename() { @@ -306,12 +293,6 @@ bool ChromeBreakpadClient::GetCrashDumpLocation(base::FilePath* crash_dir) { return PathService::Get(chrome::DIR_CRASH_DUMPS, crash_dir); } -#if defined(OS_POSIX) -void ChromeBreakpadClient::SetDumpWithoutCrashingFunction(void (*function)()) { - logging::SetDumpWithoutCrashingFunction(function); -} -#endif - size_t ChromeBreakpadClient::RegisterCrashKeys() { return crash_keys::RegisterChromeCrashKeys(); } @@ -332,4 +313,13 @@ int ChromeBreakpadClient::GetAndroidMinidumpDescriptor() { } #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 index 10d09805df..d056f23f95 100644 --- a/src/chrome_breakpad_client.h +++ b/src/chrome_breakpad_client.h @@ -17,7 +17,6 @@ class ChromeBreakpadClient : public breakpad::BreakpadClient { virtual ~ChromeBreakpadClient(); // breakpad::BreakpadClient implementation. - virtual void SetClientID(const std::string& client_id) OVERRIDE; #if defined(OS_WIN) virtual bool GetAlternativeCrashDumpLocation(base::FilePath* crash_dir) OVERRIDE; @@ -46,10 +45,6 @@ class ChromeBreakpadClient : public breakpad::BreakpadClient { virtual bool GetCrashDumpLocation(base::FilePath* crash_dir) OVERRIDE; -#if defined(OS_POSIX) - virtual void SetDumpWithoutCrashingFunction(void (*function)()) OVERRIDE; -#endif - virtual size_t RegisterCrashKeys() OVERRIDE; virtual bool IsRunningUnattended() OVERRIDE; @@ -68,6 +63,9 @@ class ChromeBreakpadClient : public breakpad::BreakpadClient { virtual void InstallAdditionalFilters(BreakpadRef breakpad) OVERRIDE; #endif + virtual bool EnableBreakpadForProcess( + const std::string& process_type) OVERRIDE; + private: DISALLOW_COPY_AND_ASSIGN(ChromeBreakpadClient); }; 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 d829fb54cd..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/strings/string16.h" -#include "ui/gfx/size.h" +#include "ui/gfx/geometry/size.h" PrintMsg_Print_Params::PrintMsg_Print_Params() : page_size(), @@ -24,7 +24,7 @@ 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), title(), @@ -50,11 +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; - title = string16(); - url = string16(); + title = base::string16(); + url = base::string16(); should_print_backgrounds = false; } @@ -79,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 d154bff752..bffa44c4fa 100644 --- a/src/common/print_messages.h +++ b/src/common/print_messages.h @@ -11,14 +11,16 @@ #include "base/memory/shared_memory.h" #include "base/values.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/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,11 +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 title; - string16 url; + base::string16 title; + base::string16 url; bool should_print_backgrounds; }; @@ -70,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) @@ -163,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) @@ -183,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) @@ -255,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) @@ -267,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() @@ -279,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 */) @@ -305,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. @@ -323,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, @@ -348,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 @@ -362,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 */) @@ -401,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. @@ -427,13 +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) -// Notify the browser that the PDF in the initiator renderer has disabled print -// scaling option. -IPC_MESSAGE_ROUTED0(PrintHostMsg_PrintPreviewScalingDisabled) +// 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 */) -// Check if printing is enabled. -IPC_SYNC_MESSAGE_ROUTED0_1(PrintHostMsg_IsPrintingEnabled, - bool /* is_enabled */) +// 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 2d6065b224..9ffbdef8e8 100644 --- a/src/common/shell_switches.cc +++ b/src/common/shell_switches.cc @@ -72,6 +72,8 @@ 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"; @@ -83,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"; @@ -110,4 +115,5 @@ const char kmInjectCSS[] = "inject-css"; 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 8b145858e1..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[]; @@ -48,7 +53,10 @@ 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[]; @@ -64,6 +72,9 @@ 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/geolocation/shell_access_token_store.cc b/src/geolocation/shell_access_token_store.cc index 35e39e1a03..d1c53dc8b7 100644 --- a/src/geolocation/shell_access_token_store.cc +++ b/src/geolocation/shell_access_token_store.cc @@ -44,12 +44,12 @@ void ShellAccessTokenStore::RespondOnOriginatingThread( // 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"); + 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 82ee0f888e..7cc5d33fcb 100644 --- a/src/geolocation/shell_access_token_store.h +++ b/src/geolocation/shell_access_token_store.h @@ -17,18 +17,18 @@ class ShellAccessTokenStore : public content::AccessTokenStore { content::ShellBrowserContext* shell_browser_context); private: - virtual ~ShellAccessTokenStore(); + ~ShellAccessTokenStore() final; void GetRequestContextOnUIThread( content::ShellBrowserContext* shell_browser_context); void RespondOnOriginatingThread(const LoadAccessTokensCallbackType& callback); // AccessTokenStore - virtual void LoadAccessTokens( - const LoadAccessTokensCallbackType& callback) OVERRIDE; + void LoadAccessTokens( + const LoadAccessTokensCallbackType& callback) override; - virtual void SaveAccessToken( - const GURL& server_url, const string16& access_token) OVERRIDE; + void SaveAccessToken( + const GURL& server_url, const base::string16& access_token) override; content::ShellBrowserContext* shell_browser_context_; net::URLRequestContextGetter* system_request_context_; diff --git a/src/hard_error_handler_win.cc b/src/hard_error_handler_win.cc index 0b07edc66c..0cd46d5461 100644 --- a/src/hard_error_handler_win.cc +++ b/src/hard_error_handler_win.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "components/breakpad/app/hard_error_handler_win.h" +#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 @@ -57,7 +57,7 @@ void RaiseHardErrorMsg(long nt_status, const std::string& p1, if (!count) return; count += p1.size() + p2.size() + 1; - string16 message; + 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 diff --git a/src/mac/app-Info.plist b/src/mac/app-Info.plist index be7fbba31c..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.9.2 + 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 0a4d02ae7e..5e697f6732 100644 --- a/src/media/media_capture_devices_dispatcher.cc +++ b/src/media/media_capture_devices_dispatcher.cc @@ -4,14 +4,16 @@ #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 { @@ -31,28 +33,26 @@ const content::MediaStreamDevice* FindDeviceWithId( } 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) { @@ -69,25 +69,13 @@ 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::OnCreatingAudioStream( @@ -101,22 +89,16 @@ void MediaCaptureDevicesDispatcher::OnCreatingAudioStream( base::Unretained(this), render_process_id, render_view_id)); } -void MediaCaptureDevicesDispatcher::UpdateAudioDevicesOnUIThread( - const content::MediaStreamDevices& devices) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - devices_enumerated_ = true; - audio_devices_ = devices; +void MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread() { + MediaStreamDevices devices = GetAudioCaptureDevices(); FOR_EACH_OBSERVER(Observer, observers_, - OnUpdateAudioDevices(audio_devices_)); + OnUpdateAudioDevices(devices)); } -void MediaCaptureDevicesDispatcher::UpdateVideoDevicesOnUIThread( - const content::MediaStreamDevices& devices){ - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - devices_enumerated_ = true; - video_devices_ = devices; +void MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread() { + MediaStreamDevices devices = GetVideoCaptureDevices(); FOR_EACH_OBSERVER(Observer, observers_, - OnUpdateVideoDevices(video_devices_)); + OnUpdateVideoDevices(devices)); } void MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread( @@ -165,3 +147,9 @@ MediaCaptureDevicesDispatcher::GetFirstAvailableVideoDevice() { 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 18c6fd0d03..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,6 +31,13 @@ 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) {} @@ -36,15 +47,29 @@ class MediaCaptureDevicesDispatcher 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 OnCreatingAudioStream(int render_process_id, - int render_view_id); - - + 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. @@ -60,6 +85,15 @@ class MediaCaptureDevicesDispatcher 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(); diff --git a/src/media/media_internals.cc b/src/media/media_internals.cc index 56ed21b09d..22ddf30191 100644 --- a/src/media/media_internals.cc +++ b/src/media/media_internals.cc @@ -89,28 +89,23 @@ MediaInternals* MediaInternals::GetInstance() { MediaInternals::~MediaInternals() {} -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, - int page_request_id, - const content::MediaStreamDevice& device, - content::MediaRequestState state) { -} - -void MediaInternals::OnAudioStreamPlayingChanged( - int render_process_id, int render_view_id, int stream_id, bool playing, float power_dbfs, bool clipped) { + 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::OnCreatingAudioStream( @@ -119,7 +114,7 @@ void MediaInternals::OnCreatingAudioStream( media_devices_dispatcher_->OnCreatingAudioStream(render_process_id, render_view_id); } -scoped_refptr +MediaCaptureDevicesDispatcher* MediaInternals::GetMediaCaptureDevicesDispatcher() { return media_devices_dispatcher_; } diff --git a/src/media/media_internals.h b/src/media/media_internals.h index b4e7af4ffb..cef21cb575 100644 --- a/src/media/media_internals.h +++ b/src/media/media_internals.h @@ -42,26 +42,28 @@ class MediaInternals : public content::MediaObserver { static MediaInternals* GetInstance(); // Overridden from content::MediaObserver: - 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, + int render_frame_id, int page_request_id, - const content::MediaStreamDevice& device, - content::MediaRequestState state) OVERRIDE; - virtual void OnAudioStreamPlayingChanged( - int render_process_id, - int render_view_id, - int stream_id, - bool playing, - float power_dbfs, - bool clipped) OVERRIDE; + const GURL& security_origin, + content::MediaStreamType stream_type, + content::MediaRequestState state) override; + virtual void OnCreatingAudioStream(int render_process_id, - int render_view_id) OVERRIDE; + int render_view_id) 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. // Observers should add themselves on construction and remove themselves // on destruction. @@ -70,8 +72,7 @@ class MediaInternals : public content::MediaObserver { void SendEverything(); scoped_refptr GetMediaStreamCaptureIndicator(); - scoped_refptr - GetMediaCaptureDevicesDispatcher(); + MediaCaptureDevicesDispatcher* GetMediaCaptureDevicesDispatcher(); private: friend class MediaInternalsTest; @@ -83,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); @@ -91,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 866c080876..1522f034df 100644 --- a/src/media/media_stream_devices_controller.cc +++ b/src/media/media_stream_devices_controller.cc @@ -4,13 +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/common/desktop_media_id.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 { @@ -26,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 @@ -37,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()) { @@ -185,11 +189,14 @@ void MediaStreamDevicesController::Accept(bool update_content_setting) { break; } - callback_.Run(devices, scoped_ptr()); + 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(), scoped_ptr()); + callback_.Run(content::MediaStreamDevices(), content::MEDIA_DEVICE_NO_HARDWARE, scoped_ptr()); } bool MediaStreamDevicesController::IsAudioDeviceBlockedByPolicy() const { @@ -227,13 +234,32 @@ void MediaStreamDevicesController::HandleTapMediaRequest() { content::MEDIA_TAB_AUDIO_CAPTURE, "", "")); } if (request_.video_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE) { - 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")); + 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, scoped_ptr()); + 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 index da867b8644..b3060f2b4d 100644 --- a/src/net/app_protocol_handler.cc +++ b/src/net/app_protocol_handler.cc @@ -5,7 +5,7 @@ #include "content/nw/src/net/app_protocol_handler.h" #include "base/base64.h" -#include "base/file_util.h" +#include "base/files/file_util.h" #include "base/files/file_path.h" #include "base/format_macros.h" #include "base/logging.h" @@ -13,6 +13,7 @@ #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" @@ -51,45 +52,29 @@ net::HttpResponseHeaders* BuildHttpHeaders( last_modified_time.ToInternalValue()); hash = base::SHA1HashString(hash); std::string etag; - if (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"); - } + 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); } -void ReadMimeTypeFromFile(const base::FilePath& filename, - std::string* mime_type, - bool* result) { - *result = net::GetMimeTypeFromFile(filename, mime_type); -} - base::Time GetFileLastModifiedTime(const base::FilePath& filename) { if (base::PathExists(filename)) { - base::PlatformFileInfo info; - if (file_util::GetFileInfo(filename, &info)) + base::File::Info info; + if (base::GetFileInfo(filename, &info)) return info.last_modified; } return base::Time(); } -base::Time GetFileCreationTime(const base::FilePath& filename) { - if (base::PathExists(filename)) { - base::PlatformFileInfo info; - if (file_util::GetFileInfo(filename, &info)) - return info.creation_time; - } - return base::Time(); -} - void ReadResourceFilePathAndLastModifiedTime( const base::FilePath& file_path, base::Time* last_modified_time) { @@ -117,11 +102,11 @@ class URLRequestNWAppJob : public net::URLRequestFileJob { // base::Time()); } - virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE { + void GetResponseInfo(net::HttpResponseInfo* info) override { *info = response_info_; } - virtual void Start() OVERRIDE { + void Start() override { base::Time* last_modified_time = new base::Time(); bool posted = content::BrowserThread::PostBlockingPoolTaskAndReply( FROM_HERE, @@ -135,7 +120,7 @@ class URLRequestNWAppJob : public net::URLRequestFileJob { } private: - virtual ~URLRequestNWAppJob() {} + ~URLRequestNWAppJob() override {} void OnFilePathAndLastModifiedTimeRead(base::Time* last_modified_time) { response_info_.headers = BuildHttpHeaders( @@ -162,12 +147,12 @@ URLRequestJob* AppProtocolHandler::MaybeCreateJob( URLRequest* request, NetworkDelegate* network_delegate) const { base::FilePath file_path; GURL url(request->url()); - url_canon::Replacements replacements; - replacements.SetScheme("file", url_parse::Component(0, 4)); + url::Replacements replacements; + replacements.SetScheme("file", url::Component(0, 4)); replacements.ClearHost(); url = url.ReplaceComponents(replacements); - const bool is_file = FileURLToFilePath(url, &file_path); + const bool is_file = net::FileURLToFilePath(url, &file_path); file_path = root_path_.Append(file_path); // Check file access permissions. diff --git a/src/net/app_protocol_handler.h b/src/net/app_protocol_handler.h index ed8222bee0..76b7ed9006 100644 --- a/src/net/app_protocol_handler.h +++ b/src/net/app_protocol_handler.h @@ -23,9 +23,9 @@ class AppProtocolHandler : public URLRequestJobFactory::ProtocolHandler { public: AppProtocolHandler(const base::FilePath& root); - virtual URLRequestJob* MaybeCreateJob( - URLRequest* request, NetworkDelegate* network_delegate) const OVERRIDE; - virtual bool IsSafeRedirectTarget(const GURL& location) const OVERRIDE; + URLRequestJob* MaybeCreateJob( + URLRequest* request, NetworkDelegate* network_delegate) const override; + bool IsSafeRedirectTarget(const GURL& location) const override; private: base::FilePath root_path_; diff --git a/src/net/clear_on_exit_policy.cc b/src/net/clear_on_exit_policy.cc index 667f47da3e..0457b4ab22 100644 --- a/src/net/clear_on_exit_policy.cc +++ b/src/net/clear_on_exit_policy.cc @@ -27,9 +27,9 @@ bool ClearOnExitPolicy::ShouldClearOriginOnExit(const std::string& domain, return false; std::string scheme = - scheme_is_secure ? content::kHttpsScheme : content::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 191a5ffe53..20ce91a4c6 100644 --- a/src/net/resource_request_job.cc +++ b/src/net/resource_request_job.cc @@ -90,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 5020b71771..9c9ea85eb4 100644 --- a/src/net/shell_url_request_context_getter.cc +++ b/src/net/shell_url_request_context_getter.cc @@ -36,11 +36,12 @@ #include "content/nw/src/nw_protocol_handler.h" #include "content/nw/src/nw_shell.h" #include "content/nw/src/shell_content_browser_client.h" +#include "extensions/browser/info_map.h" #include "net/cert/cert_verifier.h" -#include "net/ssl/default_server_bound_cert_store.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/server_bound_cert_service.h" #include "net/ssl/ssl_config_service_defaults.h" #include "net/cookies/cookie_monster.h" #include "net/http/http_auth_filter.h" @@ -53,11 +54,13 @@ #include "net/proxy/proxy_script_fetcher_impl.h" #include "net/proxy/proxy_service.h" #include "net/proxy/proxy_service_v8.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/protocol_intercept_job_factory.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" @@ -90,18 +93,20 @@ class NWCookieMonsterDelegate : public net::CookieMonster::Delegate { } // net::CookieMonster::Delegate implementation. - virtual void OnCookieChanged( + void OnCookieChanged( const net::CanonicalCookie& cookie, bool removed, - net::CookieMonster::Delegate::ChangeCause cause) OVERRIDE { + net::CookieMonster::Delegate::ChangeCause cause) override { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&NWCookieMonsterDelegate::OnCookieChangedAsyncHelper, this, cookie, removed, cause)); } + void OnLoaded() override { + } private: - virtual ~NWCookieMonsterDelegate() {} + ~NWCookieMonsterDelegate() final {} void OnCookieChangedAsyncHelper( const net::CanonicalCookie& cookie, @@ -128,10 +133,12 @@ ShellURLRequestContextGetter::ShellURLRequestContextGetter( MessageLoop* file_loop, 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) + const std::string& gssapi_library_name, + extensions::InfoMap* extension_info_map) : ignore_certificate_errors_(ignore_certificate_errors), data_path_(data_path), root_path_(root_path), @@ -143,7 +150,9 @@ ShellURLRequestContextGetter::ShellURLRequestContextGetter( gssapi_library_name_(gssapi_library_name), io_loop_(io_loop), file_loop_(file_loop), - browser_context_(browser_context) { + 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)); @@ -154,7 +163,7 @@ ShellURLRequestContextGetter::ShellURLRequestContextGetter( // 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() { @@ -169,26 +178,26 @@ net::URLRequestContext* ShellURLRequestContextGetter::GetURLRequestContext() { GetContentClient()->browser()); url_request_context_.reset(new net::URLRequestContext()); - network_delegate_.reset(new ShellNetworkDelegate); - url_request_context_->set_network_delegate(network_delegate_.get()); - storage_.reset( + 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 = data_path_.Append(FILE_PATH_LITERAL("cookies")); scoped_refptr cookie_store = NULL; - cookie_store = content::CreatePersistentCookieStore( - cookie_path, - false, - NULL, - new NWCookieMonsterDelegate(browser_context_)); + + 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", "file", "app"}; - cookie_store->GetCookieMonster()->SetCookieableSchemes(schemes, 4); + 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(); @@ -199,12 +208,19 @@ net::URLRequestContext* ShellURLRequestContextGetter::GetURLRequestContext() { storage_->set_http_user_agent_settings( new net::StaticHttpUserAgentSettings( net::HttpUtil::GenerateAcceptLanguageHeader(accept_lang), - EmptyString())); + 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; @@ -231,7 +247,7 @@ net::URLRequestContext* ShellURLRequestContextGetter::GetURLRequestContext() { net::HttpCache::DefaultBackend* main_backend = new net::HttpCache::DefaultBackend( net::DISK_CACHE, - net::CACHE_BACKEND_SIMPLE, + net::CACHE_BACKEND_BLOCKFILE, cache_path, 10 * 1024 * 1024, // 10M BrowserThread::GetMessageLoopProxyForThread( @@ -242,8 +258,8 @@ net::URLRequestContext* ShellURLRequestContextGetter::GetURLRequestContext() { url_request_context_->cert_verifier(); network_session_params.transport_security_state = url_request_context_->transport_security_state(); - network_session_params.server_bound_cert_service = - url_request_context_->server_bound_cert_service(); + 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 = @@ -270,7 +286,7 @@ net::URLRequestContext* ShellURLRequestContextGetter::GetURLRequestContext() { new net::URLRequestJobFactoryImpl()); InstallProtocolHandlers(job_factory.get(), &protocol_handlers_); job_factory->SetProtocolHandler( - chrome::kFileScheme, + url::kFileScheme, new net::FileProtocolHandler( content::BrowserThread::GetBlockingPool()-> GetTaskRunnerWithShutdownBehavior( @@ -279,8 +295,19 @@ net::URLRequestContext* ShellURLRequestContextGetter::GetURLRequestContext() { new net::AppProtocolHandler(root_path_)); job_factory->SetProtocolHandler("nw", new nw::NwProtocolHandler()); - storage_->set_job_factory(job_factory.release()); - + // 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(); @@ -295,6 +322,17 @@ 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; diff --git a/src/net/shell_url_request_context_getter.h b/src/net/shell_url_request_context_getter.h index 9b92ab13f6..130000da3f 100644 --- a/src/net/shell_url_request_context_getter.h +++ b/src/net/shell_url_request_context_getter.h @@ -26,6 +26,7 @@ #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" @@ -43,11 +44,15 @@ namespace base{ class MessageLoop; } +namespace extensions { + class InfoMap; +} + namespace content { class ShellBrowserContext; - class ShellURLRequestContextGetter : public net::URLRequestContextGetter { + class ShellURLRequestContextGetter : public net::URLRequestContextGetter, public net::CertTrustAnchorProvider { public: ShellURLRequestContextGetter( bool ignore_certificate_errors, @@ -57,20 +62,27 @@ class ShellBrowserContext; 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); + 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: @@ -85,6 +97,7 @@ class ShellBrowserContext; 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_; @@ -96,6 +109,8 @@ class ShellBrowserContext; 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/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 6131cfca21..aad9c948d7 100644 --- a/src/nw_package.cc +++ b/src/nw_package.cc @@ -23,13 +23,14 @@ #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/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" #include "base/threading/thread_restrictions.h" #include "base/values.h" #include "third_party/zlib/google/zip.h" @@ -45,9 +46,11 @@ #include "ui/gfx/image/image_skia_rep.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 { @@ -64,7 +67,7 @@ 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()) @@ -83,7 +86,7 @@ bool MakePathAbsolute(FilePath* file_path) { } FilePath GetSelfPath() { - CommandLine* command_line = CommandLine::ForCurrentProcess(); + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); FilePath path; @@ -117,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 @@ -146,8 +151,8 @@ Package::Package() 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]); @@ -199,14 +204,14 @@ bool Package::GetImage(const FilePath& icon_path, gfx::Image* image) { 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; // 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); @@ -240,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); @@ -266,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() ? @@ -274,14 +285,14 @@ 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_, @@ -292,7 +303,6 @@ bool Package::InitFromPath() { // Check fields const char* required_fields[] = { - switches::kmMain, switches::kmName }; for (unsigned i = 0; i < arraysize(required_fields); i++) @@ -314,7 +324,7 @@ bool Package::InitFromPath() { if (root_->GetString(switches::kAudioBufferSize, &bufsz_str)) { int buffer_size = 0; if (base::StringToInt(bufsz_str, &buffer_size) && buffer_size > 0) { - CommandLine* command_line = CommandLine::ForCurrentProcess(); + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); command_line->AppendSwitchASCII(switches::kAudioBufferSize, bufsz_str); } } @@ -337,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. @@ -355,7 +365,7 @@ 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 @@ -378,9 +388,9 @@ bool Package::ExtractPackage(const FilePath& zip_file, FilePath* where) { 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."); @@ -411,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 @@ -443,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 d82de1b09b..e211572467 100644 --- a/src/nw_package.h +++ b/src/nw_package.h @@ -66,6 +66,8 @@ class Package { // Return if we enable node.js. bool GetUseNode(); + bool GetUseExtension(); + // Root path of package. FilePath path() const { return path_; } 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 09b614c538..fc2cc0c904 100644 --- a/src/nw_shell.cc +++ b/src/nw_shell.cc @@ -21,6 +21,7 @@ #include "content/nw/src/nw_shell.h" #include "base/command_line.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" @@ -28,9 +29,9 @@ #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" @@ -49,8 +50,8 @@ #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" @@ -58,22 +59,59 @@ #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 "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" -#if defined(OS_WIN) -#include "content/nw/src/browser/native_window_win.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::NativeWindowWin; +using nw::NativeWindowAura; #endif +#include "content/public/browser/media_capture_devices.h" +#include "media/audio/audio_manager_base.h" -#include "content/nw/src/browser/printing/print_view_manager.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 { std::vector Shell::windows_; @@ -105,7 +143,7 @@ Shell* Shell::Create(BrowserContext* browser_context, Shell* shell = new Shell(web_contents, GetPackage()->window()); NavigationController::LoadURLParams params(url); - params.transition_type = PageTransitionFromInt(PAGE_TRANSITION_TYPED); + params.transition_type = PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED); params.override_user_agent = NavigationController::UA_OVERRIDE_TRUE; params.frame_name = std::string(); @@ -123,7 +161,7 @@ Shell* Shell::Create(WebContents* source_contents, if (!target_url.is_empty()) { NavigationController::LoadURLParams params(target_url); - params.transition_type = PageTransitionFromInt(PAGE_TRANSITION_TYPED); + params.transition_type = PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED); params.override_user_agent = NavigationController::UA_OVERRIDE_TRUE; params.frame_name = std::string(); @@ -152,7 +190,7 @@ Shell* Shell::FromRenderViewHost(RenderViewHost* rvh) { return windows_[i]; }else{ WebContentsImpl* impl = static_cast(web_contents); - RenderViewHostManager* rvhm = impl->GetRenderManagerForTesting(); + RenderFrameHostManager* rvhm = impl->GetRenderManagerForTesting(); if (rvhm && static_cast(rvhm->pending_render_view_host()) == rvh) return windows_[i]; } @@ -179,6 +217,8 @@ Shell::Shell(WebContents* web_contents, base::DictionaryValue* manifest) 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); @@ -188,12 +228,30 @@ Shell::Shell(WebContents* web_contents, base::DictionaryValue* 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() { @@ -275,32 +333,7 @@ bool Shell::ShouldCloseWindow(bool quit) { 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() { @@ -310,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) { @@ -347,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() { @@ -393,31 +434,36 @@ void Shell::ShowDevTools(const char* jail_id, bool headless) { return; } - RenderViewHost* inspected_rvh = web_contents()->GetRenderViewHost(); + // RenderViewHost* inspected_rvh = web_contents()->GetRenderViewHost(); if (nodejs()) { std::string jscript = std::string("require('nw.gui').Window.get().__setDevToolsJail('") + (jail_id ? jail_id : "(null)") + "');"; - inspected_rvh->ExecuteJavascriptInWebFrame(string16(), UTF8ToUTF16(jscript.c_str())); + web_contents()->GetMainFrame()->ExecuteJavaScript(base::UTF8ToUTF16(jscript.c_str())); } - scoped_refptr agent(DevToolsAgentHost::GetOrCreateFor(inspected_rvh)); - DevToolsManager* manager = DevToolsManager::GetInstance(); + scoped_refptr agent(DevToolsAgentHost::GetOrCreateFor(web_contents())); if (agent->IsAttached()) { // Break remote debugging debugging session. - manager->CloseAllClientHosts(); + content::DevToolsAgentHost::DetachAllClients(); } - ShellDevToolsDelegate* delegate = - browser_client->shell_browser_main_parts()->devtools_delegate(); - GURL url = delegate->devtools_http_handler()->GetFrontendURL(); + DevToolsHttpHandler* http_handler = + browser_client->shell_browser_main_parts()->devtools_handler(); + GURL url = http_handler->GetFrontendURL("/devtools/devtools.html"); + http_handler->EnumerateTargets(); - SendEvent("devtools-opened", url.spec()); +#if 0 if (headless) { - // FIXME: DevToolsFrontendHost + 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); @@ -438,10 +484,10 @@ void Shell::ShowDevTools(const char* jail_id, bool headless) { new ShellDevToolsFrontend( shell, - DevToolsAgentHost::GetOrCreateFor(inspected_rvh).get()); + 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(); @@ -465,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; @@ -479,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; @@ -498,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); } @@ -531,16 +584,23 @@ bool Shell::IsPopupOrPanel(const WebContents* source) const { // Window opened by window.open void Shell::WebContentsCreated(WebContents* source_contents, - int64 source_frame_id, - const string16& frame_name, + 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) @@ -560,17 +620,37 @@ void Shell::WebContentsCreated(WebContents* source_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) +#if defined(OS_WIN) || defined(OS_LINUX) void Shell::WebContentsFocused(content::WebContents* web_contents) { - NativeWindowWin* win = static_cast(window_.get()); - win->web_view_->OnWebContentsFocused(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) { +Shell::OpenColorChooser(content::WebContents* web_contents, SkColor color, + const std::vector& suggestions) { return nw::ShowColorChooser(web_contents, color); } @@ -589,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; } @@ -618,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, @@ -632,18 +749,23 @@ 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(); + } } } @@ -656,6 +778,56 @@ GURL Shell::OverrideDOMStorageOrigin(const GURL& origin) { 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 45b8636de7..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; @@ -61,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 { @@ -74,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, @@ -91,6 +102,7 @@ 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); @@ -124,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_; } @@ -133,68 +145,96 @@ class Shell : public WebContentsDelegate, void set_id(int id) { id_ = id; } int id() const { return id_; } - virtual void RenderViewCreated(RenderViewHost* render_view_host) OVERRIDE; - + 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, - const string16& frame_name, + 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; -#if defined(OS_WIN) - virtual void WebContentsFocused(WebContents* contents) OVERRIDE; + 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 - virtual content::ColorChooser* OpenColorChooser( - content::WebContents* web_contents, SkColor color) OVERRIDE; - virtual void RunFileChooser( + content::ColorChooser* OpenColorChooser( content::WebContents* web_contents, - const content::FileChooserParams& params) OVERRIDE; - virtual void EnumerateDirectory(content::WebContents* web_contents, + SkColor color, + const std::vector& suggestions) override; + void RunFileChooser( + 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_; diff --git a/src/nw_version.h b/src/nw_version.h index 036c443eaf..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 9 -#define NW_PATCH_VERSION 2 +#define NW_MINOR_VERSION 12 +#define NW_PATCH_VERSION 3 #define NW_VERSION_IS_RELEASE 1 #ifndef NW_STRINGIFY @@ -38,12 +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 "32.0.1700.107" +#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.mm b/src/paths_mac.mm index d099c8aa74..354e8a36a2 100644 --- a/src/paths_mac.mm +++ b/src/paths_mac.mm @@ -51,16 +51,16 @@ FilePath GetFrameworksPath() { 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); } 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 68b438d079..50c0b66877 100644 --- a/src/renderer/nw_render_view_observer.cc +++ b/src/renderer/nw_render_view_observer.cc @@ -22,7 +22,7 @@ #include -#include "base/file_util.h" +#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" @@ -30,18 +30,17 @@ #include "skia/ext/platform_canvas.h" #include "third_party/WebKit/public/platform/WebRect.h" #include "third_party/WebKit/public/platform/WebSize.h" -#include "third_party/WebKit/public/web/WebFrame.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" -#include "webkit/glue/webkit_glue.h" using content::RenderView; using content::RenderViewImpl; -using WebKit::WebFrame; -using WebKit::WebRect; -using WebKit::WebScriptSource; -using WebKit::WebSize; +using blink::WebFrame; +using blink::WebRect; +using blink::WebScriptSource; +using blink::WebSize; namespace nw { @@ -80,7 +79,7 @@ void NwRenderViewObserver::OnCaptureSnapshot() { Send(new NwViewHostMsg_Snapshot(routing_id(), snapshot)); } -void NwRenderViewObserver::DidFinishDocumentLoad(WebKit::WebFrame* frame) { +void NwRenderViewObserver::DidFinishDocumentLoad(blink::WebLocalFrame* frame) { RenderViewImpl* rv = RenderViewImpl::FromWebView(frame->view()); if (!rv) return; @@ -88,7 +87,7 @@ void NwRenderViewObserver::DidFinishDocumentLoad(WebKit::WebFrame* frame) { OnDocumentCallback(rv, js_fn, frame); } -void NwRenderViewObserver::DidCreateDocumentElement(WebKit::WebFrame* frame) { +void NwRenderViewObserver::DidCreateDocumentElement(blink::WebLocalFrame* frame) { RenderViewImpl* rv = RenderViewImpl::FromWebView(frame->view()); if (!rv) return; @@ -98,7 +97,7 @@ void NwRenderViewObserver::DidCreateDocumentElement(WebKit::WebFrame* frame) { void NwRenderViewObserver::OnDocumentCallback(RenderViewImpl* rv, const std::string& js_fn, - WebKit::WebFrame* frame) { + blink::WebFrame* frame) { if (js_fn.empty()) return; std::string content; @@ -113,7 +112,7 @@ void NwRenderViewObserver::OnDocumentCallback(RenderViewImpl* rv, frame->executeScriptAndReturnValue(WebScriptSource(jscript)); } -bool NwRenderViewObserver::CaptureSnapshot(WebKit::WebView* view, +bool NwRenderViewObserver::CaptureSnapshot(blink::WebView* view, SkBitmap* snapshot) { view->layout(); const WebSize& size = view->size(); @@ -132,7 +131,7 @@ bool NwRenderViewObserver::CaptureSnapshot(WebKit::WebView* view, 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 d697ff9c4c..157b726b41 100644 --- a/src/renderer/nw_render_view_observer.h +++ b/src/renderer/nw_render_view_observer.h @@ -23,13 +23,15 @@ #include "content/public/renderer/render_view_observer.h" +#include + class SkBitmap; namespace content { class RenderViewImpl; } -namespace WebKit { +namespace blink { class WebView; } @@ -38,12 +40,12 @@ namespace nw { class NwRenderViewObserver : public content::RenderViewObserver { public: NwRenderViewObserver(content::RenderView* render_view); - virtual ~NwRenderViewObserver(); + ~NwRenderViewObserver() final; // RenderViewObserver implementation. - virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; - virtual void DidCreateDocumentElement(WebKit::WebFrame* frame) OVERRIDE; - virtual void DidFinishDocumentLoad(WebKit::WebFrame* frame) OVERRIDE; + bool OnMessageReceived(const IPC::Message& message) override; + void DidCreateDocumentElement(blink::WebLocalFrame* frame) override; + void DidFinishDocumentLoad(blink::WebLocalFrame* frame) override; private: @@ -51,11 +53,11 @@ class NwRenderViewObserver : public content::RenderViewObserver { // 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, - WebKit::WebFrame* frame); + 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 cbe5f08d47..22d4d1bd5b 100644 --- a/src/renderer/prerenderer/prerenderer_client.cc +++ b/src/renderer/prerenderer/prerenderer_client.cc @@ -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 8bcea1422f..ef9b98e99c 100644 --- a/src/renderer/prerenderer/prerenderer_client.h +++ b/src/renderer/prerenderer/prerenderer_client.h @@ -28,15 +28,15 @@ 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 395f9969cf..8e7d48769f 100644 --- a/src/renderer/printing/print_web_view_helper.cc +++ b/src/renderer/printing/print_web_view_helper.cc @@ -2,72 +2,81 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "content/nw/src/renderer/printing/print_web_view_helper.h" +#include "chrome/renderer/printing/print_web_view_helper.h" #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/message_loop.h" #include "base/metrics/histogram.h" -#include "base/strings/stringprintf.h" +#include "base/process/process_handle.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" -#include "content/public/renderer/web_preferences.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 "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/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/WebFrame.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/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" -#include "webkit/common/webpreferences.h" -using base::MessageLoop; +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 = base::StringPrintf(script_format, json.c_str()); - frame->executeScript(WebKit::WebString(UTF8ToUTF16(script))); + frame->executeScript(blink::WebString(base::UTF8ToUTF16(script))); } +#endif // !defined(ENABLE_PRINT_PREVIEW) int GetDPI(const PrintMsg_Print_Params* print_params) { #if defined(OS_MACOSX) @@ -83,18 +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); + 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 = @@ -111,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, @@ -238,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; @@ -271,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; @@ -296,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(); @@ -312,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, @@ -355,52 +401,76 @@ 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); @@ -408,9 +478,15 @@ void PrintWebViewHelper::PrintHeaderAndFooter( options->SetString("pageNumber", 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); @@ -418,17 +494,17 @@ void PrintWebViewHelper::PrintHeaderAndFooter( 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, @@ -448,12 +524,12 @@ 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(); @@ -465,11 +541,11 @@ class PrepareFrameAndViewForPrint : public WebKit::WebViewClient, // 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_; } @@ -477,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 WebPreferences& preferences); - base::WeakPtrFactory weak_ptr_factory_; - - 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_; @@ -511,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_); } @@ -556,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(); @@ -572,11 +655,10 @@ 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_); + expected_pages_count_ = + frame()->printBegin(web_print_params_, node_to_print_); is_printing_started_ = true; } @@ -584,17 +666,20 @@ void PrepareFrameAndViewForPrint::CopySelectionIfNeeded( 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 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 @@ -603,131 +688,165 @@ void PrepareFrameAndViewForPrint::CopySelection( 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; - content::ApplyWebPreferences(prefs, 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), + 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) @@ -736,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/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/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/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/start_app/script.js b/tests/automatic_tests/start_app/script.js index 64d9a6a3fa..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' 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/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/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/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/visible_on_all_workspaces/index.html b/tests/manual_tests/visible_on_all_workspaces/index.html new file mode 100644 index 0000000000..8114b47d52 --- /dev/null +++ b/tests/manual_tests/visible_on_all_workspaces/index.html @@ -0,0 +1,65 @@ + + + Always on Visible Workspace Test - window #1 + + +
              +

              Window #1

              +

              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/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/package.json b/tests/package.json index 2b014f8161..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", 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 8b83060806..f480f46cc6 100755 --- a/tools/build_native_modules.py +++ b/tools/build_native_modules.py @@ -52,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: @@ -64,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/make-nw-headers.py b/tools/make-nw-headers.py index d7c995ff5a..27fbde92da 100644 --- a/tools/make-nw-headers.py +++ b/tools/make-nw-headers.py @@ -2,18 +2,32 @@ 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 = tmp_dir = os.path.normpath(os.path.join(nw_root, 'tmp')) +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 ''' @@ -21,7 +35,7 @@ ''' if '-t' in sys.argv: nw_version = sys.argv[sys.argv.index('-t') + 1] -tarname = 'node-v' + nw_version + '.tar.gz' +tarname = 'nw-headers-v' + nw_version + '.tar.gz' tarpath = os.path.join(tmp_dir, tarname) #make tmpdir @@ -71,21 +85,13 @@ 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')) -rfile = open(os.path.join(tmp_dir, 'node', 'src', 'node.h'), 'r') -filer = rfile.read() -sub = re.sub('third_party/node/deps/uv/include/uv.h', 'uv.h', filer, 0) -rfile.close() -wfile = open(os.path.join(tmp_dir, 'node', 'src', 'node.h'), 'w') -wfile.write(sub) -wfile.close() +header_files = ['node.h', 'env.h', 'env-inl.h'] +update_uvh(tmp_dir, header_files) print 'copy file end' print 'Begin compress file' -tar = tarfile.open(tarpath, 'w:gz') -for dirpath, dirnames, filenames in os.walk(tmp_dir): - for name in filenames: - path = os.path.normpath(os.path.join(dirpath, name)) - tar.add(path, path.replace(tmp_dir + os.sep, '')) -tar.close() -print 'compress end' \ No newline at end of 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 index 48944b3100..8d2a86ab12 100644 --- a/tools/make-nw-headers.sh +++ b/tools/make-nw-headers.sh @@ -10,8 +10,10 @@ rm -rf $tmpdir/node/deps/v8/test rm -rf $tmpdir/node/deps/v8/out rm -rf $tmpdir/node/deps/npm/node_modules -cat $tmpdir/node/src/node.h | sed -e 's|third_party/node/deps/uv/include/uv.h|uv.h|' > tmp_node.h && mv tmp_node.h $tmpdir/node/src/node.h +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.6.1.tar.gz node +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 6122aac774..cd1b8540cb 100755 --- a/tools/package_binaries.py +++ b/tools/package_binaries.py @@ -1,224 +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') -required_file = [] -tarname = [] -binary_name = [] -binary_tar = [] -binary_store_path = [] -binary_full_path = [] -binary_tar_full_path = [] - -#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() - -if platform_name != 'win' and not os.path.exists( - os.path.join(project_root, 'chromedriver2_server')): - print 'chromedriver2_server file does not exist.\n' - exit() - -if platform_name == 'win' and not os.path.exists( - os.path.join(project_root, 'chromedriver2_server.exe')): - print 'chromedriver2_server file does not exist.\n' - exit() - - -required_file_linux = ( - 'nw', - 'nw.pak', - 'libffmpegsumo.so', - 'nwsnapshot', - 'credits.html', -) - -required_file_win = ( - 'ffmpegsumo.dll', - 'icudt.dll', - 'libEGL.dll', - 'libGLESv2.dll', - 'nw.exe', - 'nw.pak', - 'nwsnapshot.exe', - 'credits.html', -) - -required_file_mac = ( - 'node-webkit.app', - 'nwsnapshot', - 'credits.html', -) - -required_chromedriver2_file_win = ( - 'chromedriver2_server.exe', -) - -required_chromedriver2_file_others = ( - 'chromedriver2_server', -) - -if (platform_name == 'linux'): - required_file.append(required_file_linux) - required_file.append(required_chromedriver2_file_others) - -if (platform_name == 'win'): - required_file.append(required_file_win) - required_file.append(required_chromedriver2_file_win); - -if (platform_name == 'osx'): - required_file.append(required_file_mac) - required_file.append(required_chromedriver2_file_others) - - -#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' - -if (platform_name == 'win' or platform_name == 'osx'): - arch = 'ia32' - -tarname.append('node-webkit-' + nw_version) -tarname.append('chromedriver2-nw-' + nw_version) -for index in range(len(tarname)): - binary_name.append(tarname[index] + '-' + platform_name + '-' + arch); - binary_tar.append(binary_name[index] + '.tar.gz') - -#use zip in mac and windows -if platform_name in ('win', 'osx'): - for index in range(len(binary_name)): - binary_tar[index] = binary_name[index] + '.zip' - - -#make directory for binary_tar -binary_store_path.append(os.path.join(project_root, - 'node-webkit-binaries')) -binary_store_path.append(os.path.join(project_root, - 'chromedriver2-binaries')) - -for index in range(len(binary_store_path)): - if not os.path.exists(binary_store_path[index]): - os.mkdir(binary_store_path[index]) - -for index in range(len(binary_store_path)): - binary_full_path.append(os.path.join(binary_store_path[index], binary_name[index])) - binary_tar_full_path.append(os.path.join(binary_store_path[index], binary_tar[index])) - -for index in range(len(binary_full_path)): - if os.path.exists(binary_full_path[index]): - shutil.rmtree(binary_full_path[index]) - -for index in range(len(binary_full_path)): - os.mkdir(binary_full_path[index]) - - -#copy file to binary -print 'Begin copy file.' -for index in range(len(required_file)): - for file in required_file[index]: - try: - shutil.copy(os.path.join(project_root, file), - os.path.join(binary_full_path[index], file)) - except: - shutil.copytree(os.path.join(project_root, file), - os.path.join(binary_full_path[index], file)) - -print 'copy file end.\n' - -for index in range(len(binary_tar_full_path)): - if (os.path.isfile(binary_tar_full_path[index])): - os.remove(binary_tar_full_path[index]) - -print 'Begin compress file' - -if platform_name in ('win', 'osx'): - """ - If there are sub directors, this should be modified - """ - import zipfile - for index in range(len(binary_tar_full_path)): - zip = zipfile.ZipFile(binary_tar_full_path[index], 'w', - compression=zipfile.ZIP_DEFLATED) - - for dirpath, dirnames, filenames in os.walk(binary_full_path[index]): - for name in filenames: - path = os.path.normpath(os.path.join(dirpath, name)) - zip.write(path, path.replace(binary_full_path[index] + os.sep, '')) - - zip.close() - + print 'Unsupported platform: ' + sys.platform + exit(-1) + +_arch = platform.architecture()[0] +if _arch == '64bit': + arch = 'x64' +elif _arch == '32bit': + arch = 'ia32' else: - for index in range(len(binary_tar_full_path)): - tar = tarfile.open(binary_tar_full_path[index], 'w:gz') - tar.add(binary_full_path[index], os.path.basename(binary_full_path[index])) - tar.close() - - -print 'compress file end.\n' - -for index in range(len(binary_store_path)): - print 'the binaries files store in path:', os.path.normpath( - os.path.join(os.getcwd(), binary_store_path[index])) - - + 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