diff --git a/docs/References/App.md b/docs/References/App.md index e21e65952e..16aa85c142 100644 --- a/docs/References/App.md +++ b/docs/References/App.md @@ -172,6 +172,21 @@ Unregisters a global keyboard shortcut. See [Shortcut](Shortcut.md) for more information. +## App.getDefaultAppUserModelID() (Windows) + +* Returns `{String}` the default application wide AUMID. + +Get the default application wide AUMID (App User Model ID). See also [`app_user_model_id`](Manifest Format.md#app_user_model_id-windows) field in Manifest Format. + +## App.setDefaultAppUserModelID(app_id) (Windows) + +* `app_id` `{String}` the default application wide AUMID. + +Overwrite the default application wide AUMID (App User Model ID) for newly opened windows. See also [`app_user_model_id`](Manifest Format.md#app_user_model_id-windows) field in Manifest Format. + +!!! warning "No Change for Previous Opened Windows" + This API will only affect the newly opened windows. It will **NOT** change the AUMID for previous opened windows. To change them, you need to iterate all windows and call [`win.setAppUserModelID(app_id)`](Window.md#winsetappusermodelid-app_id-windows) for each. + ## Event: open(args) * `args` `{String}` the full command line of the program. diff --git a/docs/References/Manifest Format.md b/docs/References/Manifest Format.md index c2ce9fb1ff..9ab85f9968 100644 --- a/docs/References/Manifest Format.md +++ b/docs/References/Manifest Format.md @@ -153,6 +153,16 @@ These certificates are used as additional root certificates for validation, to a * `{Boolean}` whether the default `Edit` menu should be disabled on Mac OS X. The default value is `false`. Only effective on Mac OS X. +### app_user_model_id (Windows) + +* `{String}` Default application wide AUMID ([App User Model ID](https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx)). Only effective on Windows. + +This property is a fallback when [window specific AUMID](#app_user_model_id-windows_1) is not set. + +If this property is not set, NW.js will use a auto generated AUMID. + +This property can be overwritten by [`nw.App.setDefaultAppUserModelID(app_id)`](App.md##appsetdefaultappusermodelidapp_id-windows) at runtime. + ## Window Subfields Most of window subfields are inherited by sub windows opened by `window.open()` or links (``) by default. The exception list of non inherited subfields are as following. They will be set to default value for opened window: @@ -259,6 +269,12 @@ Control the transparency with rgba background value in CSS. Use command line opt There is experimental support for "click-through" on the transparent region: add `--disable-gpu` option to the command line. +### app_user_model_id (Windows) + +* `{String}` window specific AUMID ([App User Model ID](https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx)). Only effective on Windows. + +The AUMID of the window can be changed at runtime by [`win.setAppUserModelID(app_id)`](Window.md#winsetappusermodelidapp_id-windows). + ## WebKit Subfields ### double_tap_to_zoom_enabled diff --git a/docs/References/Window.md b/docs/References/Window.md index eccc88c872..a850ea93fe 100644 --- a/docs/References/Window.md +++ b/docs/References/Window.md @@ -61,6 +61,7 @@ nw.Window.open('https://github.com/nwjs/nw.js', {}, function(new_win) { - `new_instance` `{Boolean}` _Optional_ whether to open a new window in a separate render process. - `inject_js_start` `{String}` _Optional_ the script to be injected before document loaded. See [Manifest format](Manifest Format.md#inject_js_start) - `inject_js_end` `{String}` _Optional_ the script to be injected before document unloaded. See [Manifest format](Manifest Format.md#inject_js_end) + - `app_user_model_id` `{String}` _Optional_ the AUMID (App User Model ID) for the window. See [Manifest format](Manifest Format.md#app_user_model_id-windows) - `id` `{String}` _Optional_ the `id` used to identify the window. This will be used to remember the size and position of the window and restore that geometry when a window with the same id is later opened. [See also the Chrome App documentation](https://developer.chrome.com/apps/app_window#type-CreateWindowOptions) * `callback(win)` `{Function}` _Optional_ callback when with the opened native `Window` object @@ -421,6 +422,16 @@ Execute a piece of JavaScript in the frame. Load and execute the compiled snapshot in the frame. See [Protect JavaScript Source Code with V8 Snapshot](../For Users/Advanced/Protect JavaScript Source Code.md). +## win.setAppUserModelID(app_id) (Windows) + +* `app_id` `{String}` Set AUMID ([App User Model ID](https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx)) for the window. Only effective on Windows 7 or later. + +This method will overwrite the AUMID set in [`package.json`](Manifest Format.md#app_user_model_id-windows_1). + +## win.getAppUserModelID() (Windows) + +* Returns `{String}` of AUMID ([App User Model ID](https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx)) for the window. Only effective on Windows. + ## Event: close The `close` event is a special event that will affect the result of the `Window.close()` function. If developer is listening to the `close` event of a window, the `Window.close()` emit the `close` event without closing the window. diff --git a/src/api/nw_app.idl b/src/api/nw_app.idl index be195a1aec..2264ed3f33 100644 --- a/src/api/nw_app.idl +++ b/src/api/nw_app.idl @@ -26,6 +26,8 @@ namespace nw.App { static DOMString[] getArgvSync(); static DOMString getDataPath(); static void crashBrowser(); + static DOMString getDefaultAppUserModelID(); + static void setDefaultAppUserModelID(DOMString app_id); }; interface Events { static void onOpen(DOMString cmdline); diff --git a/src/api/nw_app_api.cc b/src/api/nw_app_api.cc index b89580b14d..8ff6a96eab 100644 --- a/src/api/nw_app_api.cc +++ b/src/api/nw_app_api.cc @@ -2,12 +2,16 @@ #include "base/command_line.h" #include "base/memory/ptr_util.h" +#include "base/strings/utf_string_conversions.h" #include "chrome/browser/browsing_data/browsing_data_appcache_helper.h" #include "chrome/browser/browsing_data/browsing_data_helper.h" #include "chrome/browser/browsing_data/browsing_data_remover_factory.h" #include "chrome/browser/devtools/devtools_window.h" #include "chrome/browser/extensions/devtools_util.h" #include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/web_applications/web_app.h" +#include "content/nw/src/common/shell_switches.h" #include "content/nw/src/nw_base.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" @@ -24,6 +28,10 @@ #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_getter.h" +#if defined(OS_WIN) +#include "chrome/browser/shell_integration_win.h" +#endif + namespace { void SetProxyConfigCallback( base::WaitableEvent* done, @@ -182,5 +190,35 @@ bool NwAppCrashBrowserFunction::RunAsync() { return true; } +bool NwAppGetDefaultAppUserModelIDFunction::RunNWSync(base::ListValue* response, std::string* error) { +#if defined(OS_WIN) + base::string16 app_id; + nw::Package* package = nw::package(); + if (package->root()->GetString(::switches::kmAppUserModelID, &app_id)) { + response->AppendString(app_id); + } else { + std::string app_name = + web_app::GenerateApplicationNameFromExtensionId(extension_id()); + base::string16 app_name_wide = base::UTF8ToWide(app_name); + Profile* profile = + Profile::FromBrowserContext(browser_context()); + app_id = shell_integration::win::GetAppModelIdForProfile( + app_name_wide, profile->GetPath()); + response->AppendString(app_id); + } +#endif + return true; +} + +bool NwAppSetDefaultAppUserModelIDFunction::RunNWSync(base::ListValue* response, std::string* error) { +#if defined(OS_WIN) + std::string app_id; + EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &app_id)); + + nw::Package* package = nw::package(); + package->root()->SetString(::switches::kmAppUserModelID, app_id); +#endif + return true; +} } // namespace extensions diff --git a/src/api/nw_app_api.h b/src/api/nw_app_api.h index 7cdd4473fe..036b0ce2ff 100644 --- a/src/api/nw_app_api.h +++ b/src/api/nw_app_api.h @@ -117,5 +117,31 @@ class NwAppCrashBrowserFunction : public AsyncExtensionFunction { DECLARE_EXTENSION_FUNCTION("nw.App.crashBrowser", UNKNOWN) }; +class NwAppGetDefaultAppUserModelIDFunction : public NWSyncExtensionFunction { + public: + NwAppGetDefaultAppUserModelIDFunction(){} + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwAppGetDefaultAppUserModelIDFunction() override {} + + DECLARE_EXTENSION_FUNCTION("nw.App.getDefaultAppUserModelID", UNKNOWN) + private: + DISALLOW_COPY_AND_ASSIGN(NwAppGetDefaultAppUserModelIDFunction); +}; + +class NwAppSetDefaultAppUserModelIDFunction : public NWSyncExtensionFunction { + public: + NwAppSetDefaultAppUserModelIDFunction(){} + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwAppSetDefaultAppUserModelIDFunction() override {} + + DECLARE_EXTENSION_FUNCTION("nw.App.setDefaultAppUserModelID", UNKNOWN) + private: + DISALLOW_COPY_AND_ASSIGN(NwAppSetDefaultAppUserModelIDFunction); +}; + } // namespace extensions #endif diff --git a/src/api/nw_current_window_internal.idl b/src/api/nw_current_window_internal.idl index a1d1dfac56..17e04ed9a7 100644 --- a/src/api/nw_current_window_internal.idl +++ b/src/api/nw_current_window_internal.idl @@ -44,5 +44,7 @@ namespace nw.currentWindowInternal { static object getWinParamInternal(); static void getPrinters(GetPrintersCallback callback); static void setPrintSettingsInternal(optional object options); + static DOMString getAppUserModelID(); + static void setAppUserModelID(DOMString id); }; }; diff --git a/src/api/nw_window.idl b/src/api/nw_window.idl index 0076817dbe..78361a73f0 100644 --- a/src/api/nw_window.idl +++ b/src/api/nw_window.idl @@ -39,6 +39,7 @@ namespace nw.Window { [nodoc] DOMString? icon; [nodoc] DOMString? inject_js_start; [nodoc] DOMString? inject_js_end; + [nodoc] DOMString? app_user_model_id; }; [noinline_doc] dictionary NWWindow { @@ -72,6 +73,8 @@ namespace nw.Window { static void removeAllListeners(DOMString event); static void reload(); static void reloadIgnoringCache(); + static DOMString getAppUserModelID(); + static void setAppUserModelID(DOMString id); static void eval(object frame, DOMString script); static void evalNWBin(object frame, DOMString path); diff --git a/src/api/nw_window_api.cc b/src/api/nw_window_api.cc index f58a375344..0ca7261933 100644 --- a/src/api/nw_window_api.cc +++ b/src/api/nw_window_api.cc @@ -38,6 +38,7 @@ #include "ui/gfx/platform_font.h" #include "ui/display/win/dpi.h" #include "ui/views/win/hwnd_util.h" +#include "chrome/browser/ui/views/apps/chrome_native_app_window_views_win.h" #endif #if defined(OS_LINUX) @@ -746,5 +747,47 @@ bool NwCurrentWindowInternalSetShowInTaskbarFunction::RunAsync() { return true; } +bool NwCurrentWindowInternalGetAppUserModelIDFunction::RunNWSync(base::ListValue* response, std::string* error) { +#if defined(OS_WIN) + AppWindow* app_window = getAppWindow(this); + + if (!app_window) { + *error = "cannot get current window; are you in background page/node context?"; + return false; + } + + ChromeNativeAppWindowViewsWin* native_app_window = static_cast(app_window->GetBaseWindow()); + response->AppendString(native_app_window->app_model_id()); +#endif + return true; +} + +bool NwCurrentWindowInternalSetAppUserModelIDFunction::RunNWSync(base::ListValue* response, std::string* error) { +#if defined(OS_WIN) + EXTENSION_FUNCTION_VALIDATE(args_); + + if (!args_->GetSize()) + return false; + base::string16 app_id; + if (!args_->GetString(0, &app_id)) + return false; + if (app_id.empty()) { + *error = "AppUserModelID cannot set to empty string"; + return false; + } + + AppWindow* app_window = getAppWindow(this); + + if (!app_window) { + *error = "cannot get current window; are you in background page/node context?"; + return false; + } + + ChromeNativeAppWindowViewsWin* native_app_window = static_cast(app_window->GetBaseWindow()); + native_app_window->SetAppModelId(app_id); +#endif + return true; +} + } // namespace extensions diff --git a/src/api/nw_window_api.h b/src/api/nw_window_api.h index 4dd66a6079..5ba11bcb58 100644 --- a/src/api/nw_window_api.h +++ b/src/api/nw_window_api.h @@ -300,5 +300,25 @@ class NwCurrentWindowInternalSetPrintSettingsInternalFunction : public NWSyncExt ~NwCurrentWindowInternalSetPrintSettingsInternalFunction() override {} DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.setPrintSettingsInternal", UNKNOWN) }; + +class NwCurrentWindowInternalGetAppUserModelIDFunction : public NWSyncExtensionFunction { + public: + NwCurrentWindowInternalGetAppUserModelIDFunction() {} + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwCurrentWindowInternalGetAppUserModelIDFunction() override {} + DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.getAppUserModelID", UNKNOWN) +}; + +class NwCurrentWindowInternalSetAppUserModelIDFunction : public NWSyncExtensionFunction { + public: + NwCurrentWindowInternalSetAppUserModelIDFunction() {} + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwCurrentWindowInternalSetAppUserModelIDFunction() override {} + DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.setAppUserModelID", UNKNOWN) +}; } // namespace extensions #endif diff --git a/src/common/shell_switches.cc b/src/common/shell_switches.cc index 3ae10c959a..4e7d3e6eb6 100644 --- a/src/common/shell_switches.cc +++ b/src/common/shell_switches.cc @@ -112,6 +112,7 @@ const char kmNewInstance[] = "new-instance"; const char kmInjectJSDocStart[] = "inject_js_start"; const char kmInjectJSDocEnd[] = "inject_js_end"; const char kmInjectCSS[] = "inject-css"; +const char kmAppUserModelID[] = "app_user_model_id"; #if defined(OS_WIN) // Enable conversion from vector to raster for any page. diff --git a/src/common/shell_switches.h b/src/common/shell_switches.h index 097f550db5..2a22e2da1c 100644 --- a/src/common/shell_switches.h +++ b/src/common/shell_switches.h @@ -35,6 +35,7 @@ extern NW_EXPORT const char kmWebkit[]; extern NW_EXPORT const char kmWindow[]; extern NW_EXPORT const char kmChromiumArgs[]; extern NW_EXPORT const char kmJsFlags[]; +extern NW_EXPORT const char kmAppUserModelID[]; extern NW_EXPORT const char kmSingleInstance[]; diff --git a/src/resources/api_nw_app.js b/src/resources/api_nw_app.js index 8ce7517346..ae2bca685b 100644 --- a/src/resources/api_nw_app.js +++ b/src/resources/api_nw_app.js @@ -71,6 +71,12 @@ nw_binding.registerCustomHook(function(bindingsAPI) { apiFunctions.setHandleRequest('removeOriginAccessWhitelistEntry', function() { nwNatives.removeOriginAccessWhitelistEntry.apply(this, arguments); }); + apiFunctions.setHandleRequest('getDefaultAppUserModelID', function() { + return sendRequest.sendRequestSync('nw.App.getDefaultAppUserModelID', arguments, this.definition.parameters, {})[0]; + }); + apiFunctions.setHandleRequest('setDefaultAppUserModelID', function() { + sendRequest.sendRequestSync('nw.App.setDefaultAppUserModelID', arguments, this.definition.parameters, {}); + }); apiFunctions.setHandleRequest('once', function(event, listener) { //FIXME: unify with nw.Window if (typeof listener !== 'function') throw new TypeError('listener must be a function'); @@ -119,7 +125,6 @@ nw_binding.registerCustomHook(function(bindingsAPI) { bindingsAPI.compiledApi.unregisterGlobalHotKey = function() { return nw.Shortcut.unregisterGlobalHotKey.apply(nw.Shortcut, arguments); }; - }); exports.binding = nw_binding.generate(); diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index 12c8cc1cd5..5329bf73cc 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -164,6 +164,12 @@ nw_internal.registerCustomHook(function(bindingsAPI) { apiFunctions.setHandleRequest('setMenu', function() { return sendRequest.sendRequestSync('nw.currentWindowInternal.setMenu', arguments, this.definition.parameters, {})[0]; }); + apiFunctions.setHandleRequest('getAppUserModelID', function() { + return sendRequest.sendRequestSync('nw.currentWindowInternal.getAppUserModelID', arguments, this.definition.parameters, {})[0]; + }); + apiFunctions.setHandleRequest('setAppUserModelID', function() { + return sendRequest.sendRequestSync('nw.currentWindowInternal.setAppUserModelID', arguments, this.definition.parameters, {})[0]; + }); }); nw_binding.registerCustomHook(function(bindingsAPI) { @@ -409,6 +415,12 @@ nw_binding.registerCustomHook(function(bindingsAPI) { NWWindow.prototype.reloadIgnoringCache = function () { currentNWWindowInternal.reloadIgnoringCache(); }; + NWWindow.prototype.getAppUserModelID = function() { + return currentNWWindowInternal.getAppUserModelID(); + }; + NWWindow.prototype.setAppUserModelID = function(app_id) { + return currentNWWindowInternal.setAppUserModelID(app_id); + }; NWWindow.prototype.eval = function (frame, script) { return nwNatives.evalScript(frame, script); }; @@ -670,6 +682,8 @@ nw_binding.registerCustomHook(function(bindingsAPI) { options.inject_js_start = params['inject_js_start']; if (typeof params['inject_js_end'] == 'string') options.inject_js_end = params['inject_js_end']; + if (typeof params['app_user_model_id'] == 'string') + options.app_user_model_id = params['app_user_model_id']; if (params.transparent) options.alphaEnabled = true; if (params.kiosk === true) diff --git a/test/sanity/app-aumid-manifest/index.html b/test/sanity/app-aumid-manifest/index.html new file mode 100644 index 0000000000..592ab8012e --- /dev/null +++ b/test/sanity/app-aumid-manifest/index.html @@ -0,0 +1,54 @@ + + + + + + AUMID + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/test/sanity/app-aumid-manifest/test.py b/test/sanity/app-aumid-manifest/test.py new file mode 100644 index 0000000000..a8ad229bb8 --- /dev/null +++ b/test/sanity/app-aumid-manifest/test.py @@ -0,0 +1,223 @@ +import time +import os +import platform +import subprocess +import sys + +if platform.system() != 'Windows': + print 'Skipped for non Windows platform' + sys.exit(0) + +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +from nw_util import * + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options + +chrome_options = Options() +chrome_options.add_argument("nwapp=" + os.path.dirname(os.path.abspath(__file__))) + +testdir = os.path.dirname(os.path.abspath(__file__)) +os.chdir(testdir) + +def test_list(package_json, operations): + idx = [0] + + def click_expect(elem, expect): + elem.click() + i = idx[-1] = idx[-1] + 1 + result = driver.find_element_by_id('result-%s' % i).get_attribute('innerHTML') + print result + assert(expect in result) + + def open_and_switch(elem): + elem.click() + wait_window_handles(driver, 3) + switch_to_devtools(driver, driver.window_handles[1]) + driver.close() + wait_window_handles(driver, 2) + driver.switch_to_window(driver.window_handles[1]) + idx.append(0) + + def close_restore(): + driver.close() + driver.switch_to_window(driver.window_handles[0]) + idx.pop() + + def fill_input(elem, value): + elem.send_keys(value) + + def click(elem): + elem.click() + + with open('package.json', 'w') as manifest: + manifest.write(package_json) + + driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options, service_log_path="log", service_args=["--verbose"]) + driver.implicitly_wait(2) + time.sleep(1) + try: + print driver.current_url + for args in operations: + op = args.pop(0) + if op != 'close': + elem = driver.find_element_by_id(args[0]) + else: + elem = None + if op == 'click': + if len(args) == 1: + print 'click %s' % args[0] + click(elem) + elif len(args) == 2: + print 'click %s and expect %s' % (args[0], args[1]) + click_expect(elem, args[1]) + else: + raise Exception('len(args) > 2 is not supported') + elif op == 'open': + print 'click %s and switch to the window' % args[0] + open_and_switch(elem) + elif op == 'fill': + print 'fill %s with %s' % (args[0], args[1]) + fill_input(elem, args[1]) + elif op == 'close': + print 'close and restore to previous window' + close_restore() + else: + raise Exception('unsupported opcode: %s' % op) + finally: + driver.quit() + +test_list(''' +{ + "name": "app-aumid-manifest", + "main": "index.html" +} +''', [ + ['click', 'show', '_crx_'], + ['click', 'show-def', '_crx_'], + ['open', 'open'], + ['click', 'show', '_crx_'], + ['click', 'show-def', '_crx_'], + ['close'], +]) + +test_list(''' +{ + "name": "app-aumid-manifest-default", + "main": "index.html", + "app_user_model_id": "foo" +} +''', [ + ['click', 'show', 'foo'], + ['click', 'show-def', 'foo'], + ['open', 'open'], + ['click', 'show', 'foo'], + ['click', 'show-def', 'foo'], + ['close'], +]) + +test_list(''' +{ + "name": "app-aumid-manifest-win", + "main": "index.html", + "window": { + "app_user_model_id": "foo" + } +} +''', [ + ['click', 'show', 'foo'], + ['click', 'show-def', '_crx_'], + ['open', 'open'], + ['click', 'show', '_crx_'], + ['click', 'show-def', '_crx_'], + ['close'], +]) + +test_list(''' +{ + "name": "app-aumid-manifest-default-win", + "main": "index.html", + "app_user_model_id": "foo", + "window": { + "app_user_model_id": "bar" + } +} +''', [ + ['click', 'show', 'bar'], + ['click', 'show-def', 'foo'], + ['open', 'open'], + ['click', 'show', 'foo'], + ['click', 'show-def', 'foo'], + ['close'], +]) + +test_list(''' +{ + "name": "app-aumid-manifest-set-win", + "main": "index.html", + "app_user_model_id": "foo", + "window": { + "app_user_model_id": "bar" + } +} +''', [ + ['click', 'show', 'bar'], + ['click', 'show-def', 'foo'], + ['fill', 'aumid', 'baz'], + ['click', 'set-aumid'], + ['click', 'show', 'baz'], + ['click', 'show-def', 'foo'], + ['open', 'open'], + ['click', 'show', 'foo'], + ['click', 'show-def', 'foo'], + ['fill', 'aumid', 'baz'], + ['click', 'set-aumid'], + ['click', 'show', 'baz'], + ['click', 'show-def', 'foo'], + ['close'], +]) + +test_list(''' +{ + "name": "app-aumid-manifest-set-default", + "main": "index.html", + "app_user_model_id": "foo", + "window": { + "app_user_model_id": "bar" + } +} +''', [ + ['click', 'show', 'bar'], + ['click', 'show-def', 'foo'], + ['fill', 'aumid-def', 'far'], + ['click', 'set-def'], + ['click', 'show', 'bar'], + ['click', 'show-def', 'far'], + ['open', 'open'], + ['click', 'show', 'far'], + ['click', 'show-def', 'far'], + ['fill', 'aumid-def', 'foo'], + ['click', 'set-def'], + ['click', 'show', 'far'], + ['click', 'show-def', 'foo'], + ['close'], +]) + +test_list(''' +{ + "name": "app-aumid-manifest-open", + "main": "index.html", + "app_user_model_id": "foo", + "window": { + "app_user_model_id": "bar" + } +} +''', [ + ['click', 'show', 'bar'], + ['click', 'show-def', 'foo'], + ['fill', 'aumid-win', 'baz'], + ['open', 'open-aumid'], + ['click', 'show', 'baz'], + ['click', 'show-def', 'foo'], + ['close'], +]) \ No newline at end of file