diff options
author | xiyuan@chromium.org <xiyuan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-02-16 18:15:32 +0000 |
---|---|---|
committer | xiyuan@chromium.org <xiyuan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-02-16 18:15:32 +0000 |
commit | 94c30ca27a105548898b46d4d9ff07c998b92282 (patch) | |
tree | 21fc151b8e9eb913e40791e66d7ec692d3595b2b | |
parent | bde6bc5d302dba589b8e9a20dea48e98e25c47e3 (diff) | |
download | chromium_src-94c30ca27a105548898b46d4d9ff07c998b92282.zip chromium_src-94c30ca27a105548898b46d4d9ff07c998b92282.tar.gz chromium_src-94c30ca27a105548898b46d4d9ff07c998b92282.tar.bz2 |
cros: Kiosk apps settings UI.
- Add a kiosk section in browser options when app mode is
enabled and current user is owner (or running on dev box);
- Click on the manage button brings up a kiosk app settings
overlay;
- The overlay allows user to add, remove and set app to be
auto launched. And a simple error handling by showing
bad app in an error banner for 5 seconds;
BUG=173749
TEST=Verify kiosk apps could be added, removed and set as
auto launch. Only one could be auto launched. Invalid
app should show up in an error banner.
R=zelidrag@chromium.org,dbeam@chromium.org
TBR=sky@chromium.org for gyp changes
Review URL: https://chromiumcodereview.appspot.com/12213033
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@182985 0039d316-1c4b-4281-b951-d872f2087c98
16 files changed, 858 insertions, 2 deletions
diff --git a/chrome/browser/resources/options/browser_options.html b/chrome/browser/resources/options/browser_options.html index d6286da..10adb0a 100644 --- a/chrome/browser/resources/options/browser_options.html +++ b/chrome/browser/resources/options/browser_options.html @@ -679,6 +679,14 @@ </button> </div> </section> + <section id="kiosk-section" hidden> + <h3 i18n-content="advancedSectionTitleKiosk"></h3> + <div> + <button id="manage-kiosk-apps-button" + i18n-content="manageKioskAppsButton"> + </button> + </div> + </section> </if> </if> <if expr="not is_macosx and not pp_ifdef('chromeos')"> diff --git a/chrome/browser/resources/options/browser_options.js b/chrome/browser/resources/options/browser_options.js index 391c792..0a42050 100644 --- a/chrome/browser/resources/options/browser_options.js +++ b/chrome/browser/resources/options/browser_options.js @@ -438,6 +438,17 @@ cr.define('options', function() { OptionsPage.navigateToPage('factoryResetData'); }; } + + // Kiosk section (CrOS only). + if (cr.isChromeOS) { + if (loadTimeData.getBoolean('enableKioskSection')) { + $('kiosk-section').hidden = false; + + $('manage-kiosk-apps-button').onclick = function(event) { + OptionsPage.navigateToPage('kioskAppsOverlay'); + }; + } + } }, /** @override */ @@ -1337,7 +1348,6 @@ cr.define('options', function() { $('bluetooth-paired-devices-list').deleteItemAtIndex(index); } } - }; //Forward public APIs to private implementations. diff --git a/chrome/browser/resources/options/chromeos/kiosk_app_list.js b/chrome/browser/resources/options/chromeos/kiosk_app_list.js new file mode 100644 index 0000000..8500106 --- /dev/null +++ b/chrome/browser/resources/options/chromeos/kiosk_app_list.js @@ -0,0 +1,146 @@ +// 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. + +cr.define('options', function() { + /** @const */ var List = cr.ui.List; + /** @const */ var ListItem = cr.ui.ListItem; + /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel; + + /** + * Creates a list for showing kiosk apps. + * @constructor + * @extends {cr.ui.List} + */ + var KioskAppList = cr.ui.define('list'); + + KioskAppList.prototype = { + __proto__: List.prototype, + + /** @override */ + createItem: function(app) { + var item = new KioskAppListItem(); + item.data = app; + return item; + }, + + /** + * Loads the given list of apps. + * @param {!Array.<!Object>} apps An array of app info objects. + */ + setApps: function(apps) { + this.dataModel = new ArrayDataModel(apps); + }, + + /** + * Updates the given app. + * @param {!Object} app An app info object. + */ + updateApp: function(app) { + for (var i = 0; i < this.items.length; ++i) { + if (this.items[i].data.id == app.id) { + this.items[i].data = app; + break; + } + } + } + }; + + /** + * Creates a list item for a kiosk app. + * @constructor + * @extends {cr.ui.ListItem} + */ + var KioskAppListItem = cr.ui.define(function() { + var el = $('kiosk-app-list-item-template').cloneNode(true); + el.removeAttribute('id'); + el.hidden = false; + return el; + }); + + KioskAppListItem.prototype = { + __proto__: ListItem.prototype, + + /** + * Data object to hold app info. + * @type {Object} + * @private + */ + data_: null, + get data() { + assert(this.data_); + return this.data_; + }, + set data(data) { + this.data_ = data; + this.redraw(); + }, + + /** + * Getter for the icon element. + * @type {Element} + */ + get icon() { + return this.querySelector('.kiosk-app-icon'); + }, + + /** + * Getter for the name element. + * @type {Element} + */ + get name() { + return this.querySelector('.kiosk-app-name'); + }, + + /** + * Getter for the status text element. + * @type {Element} + */ + get status() { + return this.querySelector('.kiosk-app-status'); + }, + + /** @override */ + decorate: function() { + ListItem.prototype.decorate.call(this); + + var sendMessageWithId = function(msg) { + return function() { + chrome.send(msg, [this.data.id]); + }.bind(this); + }.bind(this); + + this.querySelector('.enable-auto-launch-button').onclick = + sendMessageWithId('enableKioskAutoLaunch'); + this.querySelector('.disable-auto-launch-button').onclick = + sendMessageWithId('disableKioskAutoLaunch'); + this.querySelector('.row-delete-button').onclick = + sendMessageWithId('removeKioskApp'); + }, + + /** + * Updates UI from app info data. + */ + redraw: function() { + this.icon.classList.toggle('spinner', this.data.isLoading); + this.icon.style.backgroundImage = 'url(' + this.data.iconURL + ')'; + + this.name.textContent = this.data.name || this.data.id; + this.status.textContent = this.data.autoLaunch ? + loadTimeData.getString('autoLaunch') : ''; + + this.autoLaunch = this.data.autoLaunch; + } + }; + + /* + * True if the app represented by this item will auto launch. + * @type {boolean} + */ + cr.defineProperty(KioskAppListItem, 'autoLaunch', cr.PropertyKind.BOOL_ATTR); + + // Export + return { + KioskAppList: KioskAppList + }; +}); diff --git a/chrome/browser/resources/options/chromeos/kiosk_apps.css b/chrome/browser/resources/options/chromeos/kiosk_apps.css new file mode 100644 index 0000000..30608c5 --- /dev/null +++ b/chrome/browser/resources/options/chromeos/kiosk_apps.css @@ -0,0 +1,78 @@ +/* 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. */ + +#kiosk-app-list { + border: 1px solid lightgrey; + margin-bottom: 5px; +} + +#kiosk-app-id-edit { + width: 510px; +} + +#kiosk-apps-error-banner { + -webkit-transition: opacity 150ms; + background-color: rgb(223, 165, 165); + margin: 2px 0; + opacity: 0; + padding: 5px; + visibility: hidden; + white-space: nowrap; + width: 510px; +} + +#kiosk-apps-error-banner.visible { + opacity: 1; + visibility: visible; +} + +.kiosk-app-list-item { + white-space: nowrap; +} + +.kiosk-app-list-item .space-filler { + -webkit-box-flex: 1; +} + +.kiosk-app-icon, +.kiosk-app-name, +.kiosk-app-status { + display: inline-block; + vertical-align: middle; +} + +.kiosk-app-icon { + background-size: 100%; + height: 16px; + width: 16px; +} + +.kiosk-app-icon.spinner { + background-image: url('chrome://resources/images/spinner.svg') !important; +} + +.kiosk-app-name, +.kiosk-app-status { + overflow: hidden; + text-overflow: ellipsis; +} + +.kiosk-app-name { + max-width: 250px; +} + +.kiosk-app-status { + -webkit-margin-start: 8px; + max-width: 120px; +} + +.disable-auto-launch-button, +.enable-auto-launch-button { + display: none; +} + +.kiosk-app-list-item[auto-launch]:hover .disable-auto-launch-button, +.kiosk-app-list-item:not([auto-launch]):hover .enable-auto-launch-button { + display: inline-block; +} diff --git a/chrome/browser/resources/options/chromeos/kiosk_apps.html b/chrome/browser/resources/options/chromeos/kiosk_apps.html new file mode 100644 index 0000000..a97c92a --- /dev/null +++ b/chrome/browser/resources/options/chromeos/kiosk_apps.html @@ -0,0 +1,41 @@ +<div id="kiosk-apps-page" class="page" hidden> + <div class="close-button"></div> + <h1 i18n-content="kioskOverlayTitle"></h1> + <div class="content-area"> + <div class="option"> + <list id="kiosk-app-list"></list> + <div id="kiosk-apps-error-banner"> + <span class="kiosk-app-name"></span> + <span class="kiosk-app-status" i18n-content="invalidApp"></span> + </div> + <label> + <span i18n-content="addKioskApp"></span><br> + <input id="kiosk-app-id-edit" type="text" + i18n-values="placeholder:kioskAppIdEditHint"> + </label> + </div> + </div> + <div class="action-area"> + <div class="button-strip"> + <button id="kiosk-options-overlay-confirm" i18n-content="done"> + </button> + </div> + </div> + + <div id="kiosk-app-list-item-template" class="kiosk-app-list-item" hidden> + <div class="content"> + <span class="kiosk-app-icon"></span> + <span class="kiosk-app-name"></span> + <span class="kiosk-app-status"></span> + </div> + <div class="space-filler"></div> + <button class="enable-auto-launch-button" + i18n-content="enableAutoLaunchButton"> + </button> + <button class="disable-auto-launch-button" + i18n-content="disableAutoLaunchButton"> + </button> + <button class="raw-button custom-appearance row-delete-button"> + </button> + </div> +</div> diff --git a/chrome/browser/resources/options/chromeos/kiosk_apps.js b/chrome/browser/resources/options/chromeos/kiosk_apps.js new file mode 100644 index 0000000..24b47899 --- /dev/null +++ b/chrome/browser/resources/options/chromeos/kiosk_apps.js @@ -0,0 +1,112 @@ +// 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. + +cr.define('options', function() { + var OptionsPage = options.OptionsPage; + + /** + * Encapsulated handling of ChromeOS kiosk apps options page. + * @extends {options.OptionsPage} + * @constructor + */ + function KioskAppsOverlay() { + OptionsPage.call(this, + 'kioskAppsOverlay', + loadTimeData.getString('kioskOverlayTitle'), + 'kiosk-apps-page'); + } + + cr.addSingletonGetter(KioskAppsOverlay); + + KioskAppsOverlay.prototype = { + __proto__: OptionsPage.prototype, + + /** + * Clear error timer id. + * @type {?number} + */ + clearErrorTimer_: null, + + /** @override */ + initializePage: function() { + // Call base class implementation to starts preference initialization. + OptionsPage.prototype.initializePage.call(this); + + options.KioskAppList.decorate($('kiosk-app-list')); + + $('kiosk-options-overlay-confirm').onclick = + OptionsPage.closeOverlay.bind(OptionsPage); + $('kiosk-app-id-edit').addEventListener('keypress', + this.handleAppIdInputKeyPressed_); + }, + + /** @override */ + didShowPage: function() { + chrome.send('getKioskApps'); + $('kiosk-app-id-edit').focus(); + }, + + /** + * Shows error for given app name/id and schedules it to cleared. + * @param {!string} appName App name/id to show in error banner. + */ + showError: function(appName) { + var errorBanner = $('kiosk-apps-error-banner'); + var appNameElement = errorBanner.querySelector('.kiosk-app-name'); + appNameElement.textContent = appName; + errorBanner.classList.add('visible'); + + if (this.clearErrorTimer_) + window.clearTimeout(this.clearErrorTimer_); + + // Sets a timer to clear out error banner after 5 seconds. + this.clearErrorTimer_ = window.setTimeout(function() { + errorBanner.classList.remove('visible'); + this.clearErrorTimer_ = null; + }.bind(this), 5000); + }, + + /** + * Handles keypressed event in the app id input element. + * @private + */ + handleAppIdInputKeyPressed_: function(e) { + if (e.keyIdentifier == 'Enter' && e.target.value) { + chrome.send('addKioskApp', [e.target.value]); + e.target.value = ''; + } + } + }; + + /** + * Sets apps to be displayed in kiosk-app-list. + * @param {!Array.<!Object>} apps An array of app info objects. + */ + KioskAppsOverlay.setApps = function(apps) { + $('kiosk-app-list').setApps(apps); + }; + + /** + * Update an app in kiosk-app-list. + * @param {!Object} app App info to be updated. + */ + KioskAppsOverlay.updateApp = function(app) { + $('kiosk-app-list').updateApp(app); + }; + + /** + * Shows error for given app name/id. + * @param {!string} appName App name/id to show in error banner. + */ + KioskAppsOverlay.showError = function(appName) { + KioskAppsOverlay.getInstance().showError(appName); + }; + + // Export + return { + KioskAppsOverlay: KioskAppsOverlay + }; +}); + +<include src="kiosk_app_list.js"></include> diff --git a/chrome/browser/resources/options/options.html b/chrome/browser/resources/options/options.html index 685289b..2775970 100644 --- a/chrome/browser/resources/options/options.html +++ b/chrome/browser/resources/options/options.html @@ -48,6 +48,7 @@ <link rel="stylesheet" href="chromeos/display_options.css"> <link rel="stylesheet" href="chromeos/display_overscan.css"> <link rel="stylesheet" href="chromeos/internet_detail.css"> + <link rel="stylesheet" href="chromeos/kiosk_apps.css"> <link rel="stylesheet" href="chromeos/pointer_overlay.css"> <link rel="stylesheet" href="factory_reset_overlay.css"> </if> @@ -116,6 +117,7 @@ <include src="chromeos/change_picture_options.html"> <include src="chromeos/display_options.html"> <include src="chromeos/keyboard_overlay.html"> + <include src="chromeos/kiosk_apps.html"> <include src="chromeos/pointer_overlay.html"> <include src="factory_reset_overlay.html"> </if> diff --git a/chrome/browser/resources/options/options.js b/chrome/browser/resources/options/options.js index 8d4e56f..4f63e32 100644 --- a/chrome/browser/resources/options/options.js +++ b/chrome/browser/resources/options/options.js @@ -191,6 +191,9 @@ function load() { OptionsPage.registerOverlay(KeyboardOverlay.getInstance(), BrowserOptions.getInstance(), [$('keyboard-settings-button')]); + OptionsPage.registerOverlay(KioskAppsOverlay.getInstance(), + BrowserOptions.getInstance(), + [$('manage-kiosk-apps-button')]); OptionsPage.registerOverlay(PointerOverlay.getInstance(), BrowserOptions.getInstance(), [$('pointer-settings-button')]); diff --git a/chrome/browser/resources/options/options_bundle.js b/chrome/browser/resources/options/options_bundle.js index a41718e..c69dd8a 100644 --- a/chrome/browser/resources/options/options_bundle.js +++ b/chrome/browser/resources/options/options_bundle.js @@ -31,6 +31,7 @@ <include src="chromeos/display_options.js"></include> <include src="chromeos/display_overscan.js"></include> <include src="chromeos/keyboard_overlay.js"></include> + <include src="chromeos/kiosk_apps.js"></include> <include src="chromeos/pointer_overlay.js"></include> var AccountsOptions = options.AccountsOptions; var ChangePictureOptions = options.ChangePictureOptions; @@ -40,6 +41,7 @@ var BluetoothOptions = options.BluetoothOptions; var BluetoothPairing = options.BluetoothPairing; var KeyboardOverlay = options.KeyboardOverlay; + var KioskAppsOverlay = options.KioskAppsOverlay; var PointerOverlay = options.PointerOverlay; var UIAccountTweaks = uiAccountTweaks.UIAccountTweaks; </if> diff --git a/chrome/browser/ui/webui/options/browser_options_handler.cc b/chrome/browser/ui/webui/options/browser_options_handler.cc index 1a423cf..942de5e 100644 --- a/chrome/browser/ui/webui/options/browser_options_handler.cc +++ b/chrome/browser/ui/webui/options/browser_options_handler.cc @@ -88,6 +88,7 @@ #if defined(OS_CHROMEOS) #include "ash/magnifier/magnifier_constants.h" +#include "base/chromeos/chromeos_version.h" #include "chrome/browser/chromeos/accessibility/accessibility_util.h" #include "chrome/browser/chromeos/extensions/wallpaper_manager_util.h" #include "chrome/browser/chromeos/login/user_manager.h" @@ -306,6 +307,7 @@ void BrowserOptionsHandler::GetLocalizedValues(DictionaryValue* values) { IDS_OPTIONS_SETTINGS_ACCESSIBILITY_VIRTUAL_KEYBOARD_DESCRIPTION }, { "accessibilityAlwaysShowMenu", IDS_OPTIONS_SETTINGS_ACCESSIBILITY_SHOULD_ALWAYS_SHOW_MENU }, + { "advancedSectionTitleKiosk", IDS_OPTIONS_KIOSK }, { "factoryResetHeading", IDS_OPTIONS_FACTORY_RESET_HEADING }, { "factoryResetTitle", IDS_OPTIONS_FACTORY_RESET }, { "factoryResetRestart", IDS_OPTIONS_FACTORY_RESET_BUTTON }, @@ -323,6 +325,7 @@ void BrowserOptionsHandler::GetLocalizedValues(DictionaryValue* values) { { "keyboardSettingsButtonTitle", IDS_OPTIONS_DEVICE_GROUP_KEYBOARD_SETTINGS_BUTTON_TITLE }, { "manageAccountsButtonTitle", IDS_OPTIONS_ACCOUNTS_BUTTON_TITLE }, + { "manageKioskAppsButton", IDS_OPTIONS_KIOSK_MANAGE_BUTTON }, { "noPointingDevices", IDS_OPTIONS_NO_POINTING_DEVICES }, { "sectionTitleDevice", IDS_OPTIONS_DEVICE_GROUP_NAME }, { "sectionTitleInternet", IDS_OPTIONS_INTERNET_OPTIONS_GROUP_LABEL }, @@ -450,7 +453,14 @@ void BrowserOptionsHandler::GetLocalizedValues(DictionaryValue* values) { values->Set("magnifierList", magnifier_list.release()); + // Sets flag of whether kiosk section should be enabled. + values->SetBoolean( + "enableKioskSection", + CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableAppMode) && + (chromeos::UserManager::Get()->IsCurrentUserOwner() || + !base::chromeos::IsRunningOnChromeOS())); #endif + #if defined(OS_MACOSX) values->SetString("macPasswordsWarning", l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_MAC_WARNING)); @@ -688,7 +698,6 @@ void BrowserOptionsHandler::InitializePage() { web_ui()->CallJavascriptFunction( "BrowserOptions.enableFactoryResetSection"); } - #endif } diff --git a/chrome/browser/ui/webui/options/chromeos/kiosk_apps_browsertest.js b/chrome/browser/ui/webui/options/chromeos/kiosk_apps_browsertest.js new file mode 100644 index 0000000..99b659a --- /dev/null +++ b/chrome/browser/ui/webui/options/chromeos/kiosk_apps_browsertest.js @@ -0,0 +1,149 @@ +// 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. + +/** + * TestFixture for kiosk app settings WebUI testing. + * @extends {testing.Test} + * @constructor + **/ +function KioskAppSettingsWebUITest() {} + +KioskAppSettingsWebUITest.prototype = { + __proto__: testing.Test.prototype, + + /** + * Browse to the kiosk app settings page. + */ + browsePreload: 'chrome://settings-frame/kioskAppsOverlay', + + /** + * Mock apps data. + */ + apps_: [ + { + id: 'app_1', + name: 'App1 Name', + iconURL: '', + autoLaunch: false, + isLoading: false, + }, + { + id: 'app_2', + name: '', // no name + iconURL: '', + autoLaunch: false, + isLoading: true, + }, + ], + + /** + * Register a mock dictionary handler. + */ + preLoad: function() { + this.makeAndRegisterMockHandler( + ['getKioskApps', + 'addKioskApp', + 'removeKioskApp', + 'enableKioskAutoLaunch', + 'disableKioskAutoLaunch' + ]); + this.mockHandler.stubs().getKioskApps(). + will(callFunction(function() { + KioskAppsOverlay.setApps(this.apps_); + }.bind(this))); + this.mockHandler.stubs().addKioskApp(ANYTHING); + this.mockHandler.stubs().removeKioskApp(ANYTHING); + this.mockHandler.stubs().enableKioskAutoLaunch(ANYTHING); + this.mockHandler.stubs().disableKioskAutoLaunch(ANYTHING); + } +}; + +// Test opening kiosk app settings has correct location and app items have +// correct label. +TEST_F('KioskAppSettingsWebUITest', 'testOpenKioskAppSettings', function() { + assertEquals(this.browsePreload, document.location.href); + + var appItems = $('kiosk-app-list').items; + assertEquals(this.apps_.length, appItems.length); + assertEquals(this.apps_[0].name, appItems[0].name.textContent); + assertFalse(appItems[0].icon.classList.contains('spinner')); + assertEquals(this.apps_[1].id, appItems[1].name.textContent); + assertTrue(appItems[1].icon.classList.contains('spinner')); +}); + +// Verify that enter key on 'kiosk-app-id-edit' adds an app. +TEST_F('KioskAppSettingsWebUITest', 'testAddKioskApp', function() { + var testAppId = 'app_3'; + var appIdInput = $('kiosk-app-id-edit'); + + appIdInput.value = testAppId; + + this.mockHandler.expects(once()).addKioskApp([testAppId]); + var keypress = document.createEvent("KeyboardEvents"); + keypress.initKeyboardEvent('keypress', true, true, null, 'Enter', ''); + appIdInput.dispatchEvent(keypress); +}); + +// Test the row delete button. +TEST_F('KioskAppSettingsWebUITest', 'testRemoveKioskApp', function() { + var appItem = $('kiosk-app-list').items[0]; + var appId = appItem.data.id; + + this.mockHandler.expects(once()).removeKioskApp([appId]); + appItem.querySelector('.row-delete-button').click(); +}); + +// Test enable/disable auto launch buttons. +TEST_F('KioskAppSettingsWebUITest', 'testEnableDisableAutoLaunch', function() { + var appItem = $('kiosk-app-list').items[0]; + var appId = appItem.data.id; + + var enableAutoLaunchCalled = false; + this.mockHandler.expects(once()).enableKioskAutoLaunch([appId]). + will(callFunction(function() { + enableAutoLaunchCalled = true; + })); + appItem.querySelector('.enable-auto-launch-button').click(); + expectTrue(enableAutoLaunchCalled); + + var disableAutoLaunchCalled = false; + this.mockHandler.expects(once()).disableKioskAutoLaunch([appId]). + will(callFunction(function() { + disableAutoLaunchCalled = true; + })); + appItem.querySelector('.disable-auto-launch-button').click(); + expectTrue(disableAutoLaunchCalled); +}); + +// Verify that updateApp updates app info. +TEST_F('KioskAppSettingsWebUITest', 'testUpdateApp', function() { + var appItems = $('kiosk-app-list').items; + assertEquals(appItems[1].data.id, 'app_2'); + expectEquals(appItems[1].data.name, ''); + expectTrue(appItems[1].icon.classList.contains('spinner')); + expectFalse(appItems[1].autoLaunch); + + // New data changes name, autoLaunch and isLoading. + var newName = 'Name for App2'; + var newApp2 = { + id: 'app_2', + name: newName, + iconURL: '', + autoLaunch: true, + isLoading: false, + }; + KioskAppsOverlay.updateApp(newApp2); + + assertEquals('app_2', appItems[1].data.id); + expectEquals(newName, appItems[1].data.name, newName); + expectEquals(newName, appItems[1].name.textContent); + expectFalse(appItems[1].icon.classList.contains('spinner')); + expectTrue(appItems[1].autoLaunch); +}); + +// Verify that showError makes error banner visible. +TEST_F('KioskAppSettingsWebUITest', 'testShowError', function() { + KioskAppsOverlay.showError('A bad app'); + expectTrue($('kiosk-apps-error-banner').classList.contains('visible')); +}); diff --git a/chrome/browser/ui/webui/options/chromeos/kiosk_apps_handler.cc b/chrome/browser/ui/webui/options/chromeos/kiosk_apps_handler.cc new file mode 100644 index 0000000..11a0a3c --- /dev/null +++ b/chrome/browser/ui/webui/options/chromeos/kiosk_apps_handler.cc @@ -0,0 +1,236 @@ +// 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/browser/ui/webui/options/chromeos/kiosk_apps_handler.h" + +#include <algorithm> +#include <set> +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/lazy_instance.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h" +#include "chrome/common/extensions/extension.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/web_ui.h" +#include "googleurl/src/gurl.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/webui/web_ui_util.h" + +namespace chromeos { +namespace options { + +namespace { + +// Populates app info dictionary with |app_data|. +void PopulateAppDict(const KioskAppManager::App& app_data, + base::DictionaryValue* app_dict) { + std::string icon_url("chrome://theme/IDR_APP_DEFAULT_ICON"); + + // TODO(xiyuan): Replace data url with a URLDataSource. + if (!app_data.icon.isNull()) + icon_url = webui::GetBitmapDataUrl(*app_data.icon.bitmap()); + + app_dict->SetString("id", app_data.id); + app_dict->SetString("name", app_data.name); + app_dict->SetString("iconURL", icon_url); + app_dict->SetBoolean( + "autoLaunch", + KioskAppManager::Get()->GetAutoLaunchApp() == app_data.id); + app_dict->SetBoolean("isLoading", app_data.is_loading); +} + +// Sanitize app id input value and extracts app id out of it. +// Returns false if an app id could not be derived out of the input. +bool ExtractsAppIdFromInput(const std::string& input, + std::string* app_id) { + if (extensions::Extension::IdIsValid(input)) { + *app_id = input; + return true; + } + + GURL webstore_url = GURL(input); + if (!webstore_url.is_valid()) + return false; + + GURL webstore_base_url = + GURL(extension_urls::GetWebstoreItemDetailURLPrefix()); + + if (webstore_url.scheme() != webstore_base_url.scheme() || + webstore_url.host() != webstore_base_url.host() || + !StartsWithASCII( + webstore_url.path(), webstore_base_url.path(), true)) { + return false; + } + + const std::string path = webstore_url.path(); + const size_t last_slash = path.rfind('/'); + if (last_slash == std::string::npos) + return false; + + const std::string candidate_id = path.substr(last_slash + 1); + if (!extensions::Extension::IdIsValid(candidate_id)) + return false; + + *app_id = candidate_id; + return true; +} + +} // namespace + +KioskAppsHandler::KioskAppsHandler() + : kiosk_app_manager_(KioskAppManager::Get()), + initialized_(false) { + kiosk_app_manager_->AddObserver(this); +} + +KioskAppsHandler::~KioskAppsHandler() { + kiosk_app_manager_->RemoveObserver(this); +} + +void KioskAppsHandler::RegisterMessages() { + web_ui()->RegisterMessageCallback("getKioskApps", + base::Bind(&KioskAppsHandler::HandleGetKioskApps, + base::Unretained(this))); + web_ui()->RegisterMessageCallback("addKioskApp", + base::Bind(&KioskAppsHandler::HandleAddKioskApp, + base::Unretained(this))); + web_ui()->RegisterMessageCallback("removeKioskApp", + base::Bind(&KioskAppsHandler::HandleRemoveKioskApp, + base::Unretained(this))); + web_ui()->RegisterMessageCallback("enableKioskAutoLaunch", + base::Bind(&KioskAppsHandler::HandleEnableKioskAutoLaunch, + base::Unretained(this))); + web_ui()->RegisterMessageCallback("disableKioskAutoLaunch", + base::Bind(&KioskAppsHandler::HandleDisableKioskAutoLaunch, + base::Unretained(this))); +} + +void KioskAppsHandler::GetLocalizedValues( + base::DictionaryValue* localized_strings) { + DCHECK(localized_strings); + + RegisterTitle(localized_strings, + "kioskOverlayTitle", + IDS_OPTIONS_KIOSK_OVERLAY_TITLE); + + localized_strings->SetString( + "addKioskApp", + l10n_util::GetStringUTF16(IDS_OPTIONS_KIOSK_ADD_APP)); + localized_strings->SetString( + "kioskAppIdEditHint", + l10n_util::GetStringUTF16(IDS_OPTIONS_KIOSK_ADD_APP_HINT)); + localized_strings->SetString( + "enableAutoLaunchButton", + l10n_util::GetStringUTF16(IDS_OPTIONS_KIOSK_ENABLE_AUTO_LAUNCH)); + localized_strings->SetString( + "disableAutoLaunchButton", + l10n_util::GetStringUTF16(IDS_OPTIONS_KIOSK_DISABLE_AUTO_LAUNCH)); + localized_strings->SetString( + "autoLaunch", + l10n_util::GetStringUTF16(IDS_OPTIONS_KIOSK_AUTO_LAUNCH)); + localized_strings->SetString( + "invalidApp", + l10n_util::GetStringUTF16(IDS_OPTIONS_KIOSK_INVALID_APP)); +} + +void KioskAppsHandler::OnKioskAutoLaunchAppChanged() { + SendKioskApps(); +} + +void KioskAppsHandler::OnKioskAppsChanged() { + SendKioskApps(); +} + +void KioskAppsHandler::OnKioskAppDataChanged(const std::string& app_id) { + KioskAppManager::App app_data; + if (!kiosk_app_manager_->GetApp(app_id, &app_data)) + return; + + base::DictionaryValue app_dict; + PopulateAppDict(app_data, &app_dict); + + web_ui()->CallJavascriptFunction("options.KioskAppsOverlay.updateApp", + app_dict); +} + +void KioskAppsHandler::OnKioskAppDataLoadFailure(const std::string& app_id) { + base::StringValue app_id_value(app_id); + web_ui()->CallJavascriptFunction("options.KioskAppsOverlay.showError", + app_id_value); +} + +void KioskAppsHandler::SendKioskApps() { + if (!initialized_) + return; + + KioskAppManager::Apps apps; + kiosk_app_manager_->GetApps(&apps); + + base::ListValue apps_list; + for (size_t i = 0; i < apps.size(); ++i) { + const KioskAppManager::App& app_data = apps[i]; + + scoped_ptr<base::DictionaryValue> app_info(new base::DictionaryValue); + PopulateAppDict(app_data, app_info.get()); + apps_list.Append(app_info.release()); + } + + web_ui()->CallJavascriptFunction("options.KioskAppsOverlay.setApps", + apps_list); +} + +void KioskAppsHandler::HandleGetKioskApps(const base::ListValue* args) { + initialized_ = true; + SendKioskApps(); +} + +void KioskAppsHandler::HandleAddKioskApp(const base::ListValue* args) { + std::string input; + CHECK(args->GetString(0, &input)); + + std::string app_id; + if (!ExtractsAppIdFromInput(input, &app_id)) { + OnKioskAppDataLoadFailure(input); + return; + } + + kiosk_app_manager_->AddApp(app_id); +} + +void KioskAppsHandler::HandleRemoveKioskApp(const base::ListValue* args) { + std::string app_id; + CHECK(args->GetString(0, &app_id)); + + kiosk_app_manager_->RemoveApp(app_id); +} + +void KioskAppsHandler::HandleEnableKioskAutoLaunch( + const base::ListValue* args) { + std::string app_id; + CHECK(args->GetString(0, &app_id)); + + kiosk_app_manager_->SetAutoLaunchApp(app_id); +} + +void KioskAppsHandler::HandleDisableKioskAutoLaunch( + const base::ListValue* args) { + std::string app_id; + CHECK(args->GetString(0, &app_id)); + + std::string startup_app_id = kiosk_app_manager_->GetAutoLaunchApp(); + if (startup_app_id != app_id) + return; + + kiosk_app_manager_->SetAutoLaunchApp(""); +} + +} // namespace options +} // namespace chromeos diff --git a/chrome/browser/ui/webui/options/chromeos/kiosk_apps_handler.h b/chrome/browser/ui/webui/options/chromeos/kiosk_apps_handler.h new file mode 100644 index 0000000..8c2ae65 --- /dev/null +++ b/chrome/browser/ui/webui/options/chromeos/kiosk_apps_handler.h @@ -0,0 +1,54 @@ +// 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_UI_WEBUI_OPTIONS_CHROMEOS_KIOSK_APPS_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS_CHROMEOS_KIOSK_APPS_HANDLER_H_ + +#include "chrome/browser/chromeos/app_mode/kiosk_app_manager_observer.h" +#include "chrome/browser/ui/webui/options/options_ui.h" + +namespace chromeos { + +class KioskAppManager; + +namespace options { + +class KioskAppsHandler : public ::options::OptionsPageUIHandler, + public KioskAppManagerObserver { + public: + KioskAppsHandler(); + virtual ~KioskAppsHandler(); + + // options::OptionsPageUIHandler overrides: + virtual void RegisterMessages() OVERRIDE; + virtual void GetLocalizedValues( + base::DictionaryValue* localized_strings) OVERRIDE; + + // KioskAppPrefsObserver overrides: + virtual void OnKioskAutoLaunchAppChanged() OVERRIDE; + virtual void OnKioskAppsChanged() OVERRIDE; + virtual void OnKioskAppDataChanged(const std::string& app_id) OVERRIDE; + virtual void OnKioskAppDataLoadFailure(const std::string& app_id) OVERRIDE; + + private: + // Sends all kiosk apps to webui. + void SendKioskApps(); + + // JS callbacks. + void HandleGetKioskApps(const base::ListValue* args); + void HandleAddKioskApp(const base::ListValue* args); + void HandleRemoveKioskApp(const base::ListValue* args); + void HandleEnableKioskAutoLaunch(const base::ListValue* args); + void HandleDisableKioskAutoLaunch(const base::ListValue* args); + + KioskAppManager* kiosk_app_manager_; // not owned. + bool initialized_; + + DISALLOW_COPY_AND_ASSIGN(KioskAppsHandler); +}; + +} // namespace options +} // namespace chromeos + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS_CHROMEOS_KIOSK_APPS_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options/options_ui.cc b/chrome/browser/ui/webui/options/options_ui.cc index c9a6203..fcc1a00 100644 --- a/chrome/browser/ui/webui/options/options_ui.cc +++ b/chrome/browser/ui/webui/options/options_ui.cc @@ -73,6 +73,7 @@ #include "chrome/browser/ui/webui/options/chromeos/display_overscan_handler.h" #include "chrome/browser/ui/webui/options/chromeos/internet_options_handler.h" #include "chrome/browser/ui/webui/options/chromeos/keyboard_handler.h" +#include "chrome/browser/ui/webui/options/chromeos/kiosk_apps_handler.h" #include "chrome/browser/ui/webui/options/chromeos/language_chewing_handler.h" #include "chrome/browser/ui/webui/options/chromeos/language_hangul_handler.h" #include "chrome/browser/ui/webui/options/chromeos/language_mozc_handler.h" @@ -290,6 +291,8 @@ OptionsUI::OptionsUI(content::WebUI* web_ui) AddOptionsPageUIHandler(localized_strings, new chromeos::options::KeyboardHandler()); AddOptionsPageUIHandler(localized_strings, + new chromeos::options::KioskAppsHandler()); + AddOptionsPageUIHandler(localized_strings, new chromeos::options::LanguageHangulHandler()); AddOptionsPageUIHandler(localized_strings, new chromeos::options::LanguageMozcHandler()); diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi index 37ff1cf..9623381 100644 --- a/chrome/chrome_browser_ui.gypi +++ b/chrome/chrome_browser_ui.gypi @@ -2028,6 +2028,8 @@ 'browser/ui/webui/options/chromeos/internet_options_handler.h', 'browser/ui/webui/options/chromeos/keyboard_handler.cc', 'browser/ui/webui/options/chromeos/keyboard_handler.h', + 'browser/ui/webui/options/chromeos/kiosk_apps_handler.cc', + 'browser/ui/webui/options/chromeos/kiosk_apps_handler.h', 'browser/ui/webui/options/chromeos/language_chewing_handler.cc', 'browser/ui/webui/options/chromeos/language_chewing_handler.h', 'browser/ui/webui/options/chromeos/language_hangul_handler.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 80e02a9..1aae04d 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1390,6 +1390,7 @@ 'browser/ui/webui/options/browser_options_browsertest.js', 'browser/ui/webui/options/certificate_manager_browsertest.js', 'browser/ui/webui/options/chromeos/guest_mode_options_ui_browsertest.cc', + 'browser/ui/webui/options/chromeos/kiosk_apps_browsertest.js', 'browser/ui/webui/options/content_options_browsertest.js', 'browser/ui/webui/options/content_settings_exception_area_browsertest.js', 'browser/ui/webui/options/cookies_view_browsertest.js', |