Upstream version 11.39.266.0
[platform/framework/web/crosswalk.git] / src / remoting / webapp / ui_mode.js
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /**
6  * @fileoverview
7  * Functions related to controlling the modal UI state of the app. UI states
8  * are expressed as HTML attributes with a dotted hierarchy. For example, the
9  * string 'host.shared' will match any elements with an associated attribute
10  * of 'host' or 'host.shared', showing those elements and hiding all others.
11  * Elements with no associated attribute are ignored.
12  */
13
14 'use strict';
15
16 /** @suppress {duplicate} */
17 var remoting = remoting || {};
18
19 /** @enum {string} */
20 // TODO(jamiewalch): Move 'in-session' to a separate web-page so that the
21 // 'home' state applies to all elements and can be removed.
22 remoting.AppMode = {
23   HOME: 'home',
24     TOKEN_REFRESH_FAILED: 'home.token-refresh-failed',
25     HOST_INSTALL: 'home.host-install',
26       HOST_INSTALL_PROMPT: 'home.host-install.prompt',
27       HOST_INSTALL_PENDING: 'home.host-install.pending',
28     HOST: 'home.host',
29       HOST_WAITING_FOR_CODE: 'home.host.waiting-for-code',
30       HOST_WAITING_FOR_CONNECTION: 'home.host.waiting-for-connection',
31       HOST_SHARED: 'home.host.shared',
32       HOST_SHARE_FAILED: 'home.host.share-failed',
33       HOST_SHARE_FINISHED: 'home.host.share-finished',
34     CLIENT: 'home.client',
35       CLIENT_UNCONNECTED: 'home.client.unconnected',
36       CLIENT_PIN_PROMPT: 'home.client.pin-prompt',
37       CLIENT_THIRD_PARTY_AUTH: 'home.client.third-party-auth',
38       CLIENT_CONNECTING: 'home.client.connecting',
39       CLIENT_CONNECT_FAILED_IT2ME: 'home.client.connect-failed.it2me',
40       CLIENT_CONNECT_FAILED_ME2ME: 'home.client.connect-failed.me2me',
41       CLIENT_SESSION_FINISHED_IT2ME: 'home.client.session-finished.it2me',
42       CLIENT_SESSION_FINISHED_ME2ME: 'home.client.session-finished.me2me',
43       CLIENT_HOST_NEEDS_UPGRADE: 'home.client.host-needs-upgrade',
44     HISTORY: 'home.history',
45     CONFIRM_HOST_DELETE: 'home.confirm-host-delete',
46     HOST_SETUP: 'home.host-setup',
47       HOST_SETUP_ASK_PIN: 'home.host-setup.ask-pin',
48       HOST_SETUP_PROCESSING: 'home.host-setup.processing',
49       HOST_SETUP_DONE: 'home.host-setup.done',
50       HOST_SETUP_ERROR: 'home.host-setup.error',
51     HOME_MANAGE_PAIRINGS: 'home.manage-pairings',
52   IN_SESSION: 'in-session'
53 };
54
55 /** @const */
56 remoting.kIT2MeVisitedStorageKey = 'it2me-visited';
57 /** @const */
58 remoting.kMe2MeVisitedStorageKey = 'me2me-visited';
59
60 /**
61  * @param {Element} element The element to check.
62  * @param {string} attrName The attribute on the element to check.
63  * @param {Array.<string>} modes The modes to check for.
64  * @return {boolean} True if any mode in |modes| is found within the attribute.
65  */
66 remoting.hasModeAttribute = function(element, attrName, modes) {
67   var attr = element.getAttribute(attrName);
68   for (var i = 0; i < modes.length; ++i) {
69     if (attr.match(new RegExp('(\\s|^)' + modes[i] + '(\\s|$)')) != null) {
70       return true;
71     }
72   }
73   return false;
74 };
75
76 /**
77  * Update the DOM by showing or hiding elements based on whether or not they
78  * have an attribute matching the specified name.
79  * @param {string} mode The value against which to match the attribute.
80  * @param {string} attr The attribute name to match.
81  * @return {void} Nothing.
82  */
83 remoting.updateModalUi = function(mode, attr) {
84   var modes = mode.split('.');
85   for (var i = 1; i < modes.length; ++i)
86     modes[i] = modes[i - 1] + '.' + modes[i];
87   var elements = document.querySelectorAll('[' + attr + ']');
88   // Hide elements first so that we don't end up trying to show two modal
89   // dialogs at once (which would break keyboard-navigation confinement).
90   for (var i = 0; i < elements.length; ++i) {
91     var element = /** @type {Element} */ elements[i];
92     if (!remoting.hasModeAttribute(element, attr, modes)) {
93       element.hidden = true;
94     }
95   }
96   for (var i = 0; i < elements.length; ++i) {
97     var element = /** @type {Element} */ elements[i];
98     if (remoting.hasModeAttribute(element, attr, modes)) {
99       element.hidden = false;
100       var autofocusNode = element.querySelector('[autofocus]');
101       if (autofocusNode) {
102         autofocusNode.focus();
103       }
104     }
105   }
106 };
107
108 /**
109  * @type {remoting.AppMode} The current app mode
110  */
111 remoting.currentMode = remoting.AppMode.HOME;
112
113 /**
114  * Change the app's modal state to |mode|, determined by the data-ui-mode
115  * attribute.
116  *
117  * @param {remoting.AppMode} mode The new modal state.
118  */
119 remoting.setMode = function(mode) {
120   remoting.updateModalUi(mode, 'data-ui-mode');
121   console.log('App mode: ' + mode);
122   remoting.currentMode = mode;
123   if (mode == remoting.AppMode.IN_SESSION) {
124     document.removeEventListener('keydown', remoting.ConnectionStats.onKeydown,
125                                  false);
126     if ('hidden' in document) {
127       document.addEventListener('visibilitychange',
128                                 remoting.onVisibilityChanged, false);
129     } else {
130       document.addEventListener('webkitvisibilitychange',
131                                 remoting.onVisibilityChanged, false);
132     }
133   } else {
134     document.addEventListener('keydown', remoting.ConnectionStats.onKeydown,
135                               false);
136     document.removeEventListener('visibilitychange',
137                                  remoting.onVisibilityChanged, false);
138     document.removeEventListener('webkitvisibilitychange',
139                                  remoting.onVisibilityChanged, false);
140     // TODO(jamiewalch): crbug.com/252796: Remove this once crbug.com/240772
141     // is fixed.
142     var scroller = document.getElementById('scroller');
143     scroller.classList.remove('no-horizontal-scroll');
144     scroller.classList.remove('no-vertical-scroll');
145   }
146
147   remoting.testEvents.raiseEvent(remoting.testEvents.Names.uiModeChanged, mode);
148 };
149
150 /**
151  * Get the major mode that the app is running in.
152  * @return {string} The app's current major mode.
153  */
154 remoting.getMajorMode = function() {
155   return remoting.currentMode.split('.')[0];
156 };
157
158 /**
159  * Helper function for showing or hiding the infographic UI based on
160  * whether or not the user has already dismissed it.
161  *
162  * @param {string} mode
163  * @param {!Object} items
164  */
165 remoting.showOrHideCallback = function(mode, items) {
166   // Get the first element of a dictionary or array, without needing to know
167   // the key.
168   /** @type {string} */
169   var key = Object.keys(items)[0];
170   var visited = !!items[key];
171   document.getElementById(mode + '-first-run').hidden = visited;
172   document.getElementById(mode + '-content').hidden = !visited;
173 };
174
175 remoting.showOrHideIT2MeUi = function() {
176   chrome.storage.local.get(remoting.kIT2MeVisitedStorageKey,
177                            remoting.showOrHideCallback.bind(null, 'it2me'));
178 };
179
180 remoting.showOrHideMe2MeUi = function() {
181   chrome.storage.local.get(remoting.kMe2MeVisitedStorageKey,
182                            remoting.showOrHideCallback.bind(null, 'me2me'));
183 };
184
185 remoting.showIT2MeUiAndSave = function() {
186   var items = {};
187   items[remoting.kIT2MeVisitedStorageKey] = true;
188   chrome.storage.local.set(items);
189   remoting.showOrHideCallback('it2me', [true]);
190 };
191
192 remoting.showMe2MeUiAndSave = function() {
193   var items = {};
194   items[remoting.kMe2MeVisitedStorageKey] = true;
195   chrome.storage.local.set(items);
196   remoting.showOrHideCallback('me2me', [true]);
197 };
198
199 remoting.resetInfographics = function() {
200   chrome.storage.local.remove(remoting.kIT2MeVisitedStorageKey);
201   chrome.storage.local.remove(remoting.kMe2MeVisitedStorageKey);
202   remoting.showOrHideCallback('it2me', [false]);
203   remoting.showOrHideCallback('me2me', [false]);
204 }
205
206
207 /**
208  * Initialize all modal dialogs (class kd-modaldialog), adding event handlers
209  * to confine keyboard navigation to child controls of the dialog when it is
210  * shown and restore keyboard navigation when it is hidden.
211  */
212 remoting.initModalDialogs = function() {
213   var dialogs = document.querySelectorAll('.kd-modaldialog');
214   var observer = new MutationObserver(confineOrRestoreFocus_);
215   var options = {
216     subtree: false,
217     attributes: true
218   };
219   for (var i = 0; i < dialogs.length; ++i) {
220     observer.observe(dialogs[i], options);
221   }
222 };
223
224 /**
225  * @param {Array.<MutationRecord>} mutations The set of mutations affecting
226  *     an observed node.
227  */
228 function confineOrRestoreFocus_(mutations) {
229   // The list of mutations can include duplicates, so reduce it to a canonical
230   // show/hide list.
231   /** @type {Array.<Element>} */
232   var shown = [];
233   /** @type {Array.<Element>} */
234   var hidden = [];
235   for (var i = 0; i < mutations.length; ++i) {
236     var mutation = mutations[i];
237     if (mutation.type == 'attributes' &&
238         mutation.attributeName == 'hidden') {
239       var node = mutation.target;
240       if (node.hidden && hidden.indexOf(node) == -1) {
241         hidden.push(node);
242       } else if (!node.hidden && shown.indexOf(node) == -1) {
243         shown.push(node);
244       }
245     }
246   }
247   var kSavedAttributeName = 'data-saved-tab-index';
248   // If any dialogs have been dismissed, restore all the tabIndex attributes.
249   if (hidden.length != 0) {
250     var elements = document.querySelectorAll('[' + kSavedAttributeName + ']');
251     for (var i = 0 ; i < elements.length; ++i) {
252       var element = /** @type {Element} */ elements[i];
253       element.tabIndex = element.getAttribute(kSavedAttributeName);
254       element.removeAttribute(kSavedAttributeName);
255     }
256   }
257   // If any dialogs have been shown, confine keyboard navigation to the first
258   // one. We don't have nested modal dialogs, so this will suffice for now.
259   if (shown.length != 0) {
260     var selector = '[tabIndex],a,area,button,input,select,textarea';
261     var disable = document.querySelectorAll(selector);
262     var except = shown[0].querySelectorAll(selector);
263     for (var i = 0; i < disable.length; ++i) {
264       var element = /** @type {Element} */ disable[i];
265       var removeFromKeyboardNavigation = true;
266       for (var j = 0; j < except.length; ++j) {  // No indexOf on NodeList
267         if (element == except[j]) {
268           removeFromKeyboardNavigation = false;
269           break;
270         }
271       }
272       if (removeFromKeyboardNavigation) {
273         element.setAttribute(kSavedAttributeName, element.tabIndex);
274         element.tabIndex = -1;
275       }
276     }
277   }
278 }
279
280 /**
281  * @param {string} tag
282  */
283 remoting.showSetupProcessingMessage = function(tag) {
284   var messageDiv = document.getElementById('host-setup-processing-message');
285   l10n.localizeElementFromTag(messageDiv, tag);
286   remoting.setMode(remoting.AppMode.HOST_SETUP_PROCESSING);
287 }