- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / local_discovery / local_discovery.js
1 // Copyright 2013 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  * Javascript for local_discovery.html, served from chrome://devices/
7  * This is used to show discoverable devices near the user as well as
8  * cloud devices registered to them.
9  *
10  * The object defined in this javascript file listens for callbacks from the
11  * C++ code saying that a new device is available as well as manages the UI for
12  * registering a device on the local network.
13  */
14
15 cr.define('local_discovery', function() {
16   'use strict';
17
18   /**
19    * Prefix for printer management page URLs, relative to base cloud print URL.
20    * @type {string}
21    */
22   var PRINTER_MANAGEMENT_PAGE_PREFIX = '#printers/';
23
24   // Histogram buckets for UMA tracking.
25   /** @const */ var DEVICES_PAGE_EVENTS = {
26     OPENED: 0,
27     LOG_IN_STARTED_FROM_REGISTER_PROMO: 1,
28     LOG_IN_STARTED_FROM_DEVICE_LIST_PROMO: 2,
29     ADD_PRINTER_CLICKED: 3,
30     REGISTER_CLICKED: 4,
31     REGISTER_CONFIRMED: 5,
32     REGISTER_SUCCESS: 6,
33     REGISTER_CANCEL: 7,
34     REGISTER_FAILURE: 8,
35     MANAGE_CLICKED: 9,
36     REGISTER_CANCEL_ON_PRINTER: 10,
37     REGISTER_TIMEOUT: 11,
38     MAX_EVENT: 12,
39   };
40
41   /**
42    * Map of service names to corresponding service objects.
43    * @type {Object.<string,Service>}
44    */
45   var devices = {};
46
47   /**
48    * Whether or not the user is currently logged in.
49    * @type bool
50    */
51   var isUserLoggedIn = true;
52
53   /**
54    * Focus manager for page.
55    */
56   var focusManager = null;
57
58   /**
59    * Object that represents a device in the device list.
60    * @param {Object} info Information about the device.
61    * @constructor
62    */
63   function Device(info, registerEnabled) {
64     this.info = info;
65     this.domElement = null;
66     this.registerButton = null;
67     this.registerEnabled = registerEnabled;
68   }
69
70   Device.prototype = {
71     /**
72      * Update the device.
73      * @param {Object} info New information about the device.
74      */
75     updateDevice: function(info) {
76       this.info = info;
77       this.renderDevice();
78     },
79
80     /**
81      * Delete the device.
82      */
83     removeDevice: function() {
84       this.deviceContainer().removeChild(this.domElement);
85     },
86
87     /**
88      * Render the device to the device list.
89      */
90     renderDevice: function() {
91       if (this.domElement) {
92         clearElement(this.domElement);
93       } else {
94         this.domElement = document.createElement('div');
95         this.deviceContainer().appendChild(this.domElement);
96       }
97
98       this.registerButton = fillDeviceDescription(
99         this.domElement,
100         this.info.human_readable_name,
101         this.info.description,
102         loadTimeData.getString('serviceRegister'),
103         this.showRegister.bind(this));
104
105       this.setRegisterEnabled(this.registerEnabled);
106     },
107
108     /**
109      * Return the correct container for the device.
110      * @param {boolean} is_mine Whether or not the device is in the 'Registered'
111      *    section.
112      */
113     deviceContainer: function() {
114       return $('register-device-list');
115     },
116     /**
117      * Register the device.
118      */
119     register: function() {
120       recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_CONFIRMED);
121       chrome.send('registerDevice', [this.info.service_name]);
122       setRegisterPage('register-page-adding1');
123     },
124     /**
125      * Show registrtation UI for device.
126      */
127     showRegister: function() {
128       recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_CLICKED);
129       $('register-message').textContent = loadTimeData.getStringF(
130         'registerConfirmMessage',
131         this.info.human_readable_name);
132       $('register-continue-button').onclick = this.register.bind(this);
133       showRegisterOverlay();
134     },
135     /**
136      * Set registration button enabled/disabled
137      */
138     setRegisterEnabled: function(isEnabled) {
139       this.registerEnabled = isEnabled;
140       if (this.registerButton) {
141         this.registerButton.disabled = !isEnabled;
142       }
143     }
144   };
145
146   /**
147    * Manages focus for local devices page.
148    * @constructor
149    * @extends {cr.ui.FocusManager}
150    */
151   function LocalDiscoveryFocusManager() {
152     cr.ui.FocusManager.call(this);
153     this.focusParent_ = document.body;
154   }
155
156   LocalDiscoveryFocusManager.prototype = {
157     __proto__: cr.ui.FocusManager.prototype,
158     /** @override */
159     getFocusParent: function() {
160       return document.querySelector('#overlay .showing') ||
161         $('main-page');
162     }
163   };
164
165   /**
166    * Returns a textual representation of the number of printers on the network.
167    * @return {string} Number of printers on the network as localized string.
168    */
169   function generateNumberPrintersAvailableText(numberPrinters) {
170     if (numberPrinters == 0) {
171       return loadTimeData.getString('printersOnNetworkZero');
172     } else if (numberPrinters == 1) {
173       return loadTimeData.getString('printersOnNetworkOne');
174     } else {
175       return loadTimeData.getStringF('printersOnNetworkMultiple',
176                                      numberPrinters);
177     }
178   }
179
180   /**
181    * Fill device element with the description of a device.
182    * @param {HTMLElement} device_dom_element Element to be filled.
183    * @param {string} name Name of device.
184    * @param {string} description Description of device.
185    * @param {string} button_text Text to appear on button.
186    * @param {function()} button_action Action for button.
187    * @return {HTMLElement} The button (for enabling/disabling/rebinding)
188    */
189   function fillDeviceDescription(device_dom_element,
190                                 name,
191                                 description,
192                                 button_text,
193                                 button_action) {
194     device_dom_element.classList.add('device');
195     device_dom_element.classList.add('printer');
196
197     var deviceInfo = document.createElement('div');
198     deviceInfo.className = 'device-info';
199     device_dom_element.appendChild(deviceInfo);
200
201     var deviceName = document.createElement('h3');
202     deviceName.className = 'device-name';
203     deviceName.textContent = name;
204     deviceInfo.appendChild(deviceName);
205
206     var deviceDescription = document.createElement('div');
207     deviceDescription.className = 'device-subline';
208     deviceDescription.textContent = description;
209     deviceInfo.appendChild(deviceDescription);
210
211     var button = document.createElement('button');
212     button.textContent = button_text;
213     button.addEventListener('click', button_action);
214     device_dom_element.appendChild(button);
215
216     return button;
217   }
218
219   /**
220    * Show the register overlay.
221    */
222   function showRegisterOverlay() {
223     recordUmaEvent(DEVICES_PAGE_EVENTS.ADD_PRINTER_CLICKED);
224
225     var registerOverlay = $('register-overlay');
226     registerOverlay.classList.add('showing');
227     registerOverlay.focus();
228
229     $('overlay').hidden = false;
230     setRegisterPage('register-page-confirm');
231   }
232
233   /**
234    * Hide the register overlay.
235    */
236   function hideRegisterOverlay() {
237     $('register-overlay').classList.remove('showing');
238     $('overlay').hidden = true;
239   }
240
241   /**
242    * Clear a DOM element of all children.
243    * @param {HTMLElement} element DOM element to clear.
244    */
245   function clearElement(element) {
246     while (element.firstChild) {
247       element.removeChild(element.firstChild);
248     }
249   }
250
251   /**
252    * Announce that a registration failed.
253    */
254   function onRegistrationFailed() {
255     $('error-message').textContent =
256       loadTimeData.getString('addingErrorMessage');
257     setRegisterPage('register-page-error');
258     recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_FAILURE);
259   }
260
261   /**
262    * Announce that a registration has been canceled on the printer.
263    */
264   function onRegistrationCanceledPrinter() {
265     $('error-message').textContent =
266       loadTimeData.getString('addingCanceledMessage');
267     setRegisterPage('register-page-error');
268     recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_CANCEL_ON_PRINTER);
269   }
270
271   /**
272    * Announce that a registration has timed out.
273    */
274   function onRegistrationTimeout() {
275     $('error-message').textContent =
276       loadTimeData.getString('addingTimeoutMessage');
277     setRegisterPage('register-page-error');
278     recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_TIMEOUT);
279   }
280
281   /**
282    * Update UI to reflect that registration has been confirmed on the printer.
283    */
284   function onRegistrationConfirmedOnPrinter() {
285     setRegisterPage('register-page-adding2');
286   }
287
288   /**
289    * Update device unregistered device list, and update related strings to
290    * reflect the number of devices available to register.
291    * @param {string} name Name of the device.
292    * @param {string} info Additional info of the device or null if the device
293    *                          has been removed.
294    */
295   function onUnregisteredDeviceUpdate(name, info) {
296     if (info) {
297       if (devices.hasOwnProperty(name)) {
298         devices[name].updateDevice(info);
299       } else {
300         devices[name] = new Device(info, isUserLoggedIn);
301         devices[name].renderDevice();
302       }
303     } else {
304       if (devices.hasOwnProperty(name)) {
305         devices[name].removeDevice();
306         delete devices[name];
307       }
308     }
309
310     updateUIToReflectState();
311   }
312
313   /**
314    * Create the DOM for a cloud device described by the device section.
315    * @param {Array.<Object>} devices_list List of devices.
316    */
317   function createCloudDeviceDOM(device) {
318     var devicesDomElement = document.createElement('div');
319
320     var description;
321     if (device.description == '') {
322         description = loadTimeData.getString('noDescription');
323       } else {
324         description = device.description;
325       }
326
327     fillDeviceDescription(devicesDomElement, device.display_name,
328                           description, 'Manage' /*Localize*/,
329                           manageCloudDevice.bind(null, device.id));
330     return devicesDomElement;
331   }
332
333   /**
334    * Handle a list of cloud devices available to the user globally.
335    * @param {Array.<Object>} devices_list List of devices.
336    */
337   function onCloudDeviceListAvailable(devices_list) {
338     var devicesListLength = devices_list.length;
339     var devicesContainer = $('cloud-devices');
340
341     clearElement(devicesContainer);
342     $('cloud-devices-loading').hidden = true;
343
344     for (var i = 0; i < devicesListLength; i++) {
345       devicesContainer.appendChild(createCloudDeviceDOM(devices_list[i]));
346     }
347   }
348
349   /**
350    * Handle the case where the list of cloud devices is not available.
351    */
352   function onCloudDeviceListUnavailable() {
353     if (isUserLoggedIn) {
354       $('cloud-devices-loading').hidden = true;
355       $('cloud-devices-unavailable').hidden = false;
356     }
357   }
358
359   /**
360    * Handle the case where the cache for local devices has been flushed..
361    */
362   function onDeviceCacheFlushed() {
363     for (var deviceName in devices) {
364       devices[deviceName].removeDevice();
365       delete devices[deviceName];
366     }
367
368     updateUIToReflectState();
369   }
370
371   /**
372    * Update UI strings to reflect the number of local devices.
373    */
374   function updateUIToReflectState() {
375     var numberPrinters = $('register-device-list').children.length;
376     if (numberPrinters == 0) {
377       $('no-printers-message').hidden = false;
378
379       $('register-login-promo').hidden = true;
380     } else {
381       $('no-printers-message').hidden = true;
382       $('register-login-promo').hidden = isUserLoggedIn;
383     }
384   }
385
386   /**
387    * Announce that a registration succeeeded.
388    */
389   function onRegistrationSuccess(device_data) {
390     hideRegisterOverlay();
391     var deviceDOM = createCloudDeviceDOM(device_data);
392     $('cloud-devices').insertBefore(deviceDOM, $('cloud-devices').firstChild);
393     recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_SUCCESS);
394   }
395
396   /**
397    * Update visibility status for page.
398    */
399   function updateVisibility() {
400     chrome.send('isVisible', [!document.webkitHidden]);
401   }
402
403   /**
404    * Set the page that the register wizard is on.
405    * @param {string} page_id ID string for page.
406    */
407   function setRegisterPage(page_id) {
408     var pages = $('register-overlay').querySelectorAll('.register-page');
409     var pagesLength = pages.length;
410     for (var i = 0; i < pagesLength; i++) {
411       pages[i].hidden = true;
412     }
413
414     $(page_id).hidden = false;
415   }
416
417   /**
418    * Request the printer list.
419    */
420   function requestPrinterList() {
421     if (isUserLoggedIn) {
422       clearElement($('cloud-devices'));
423       $('cloud-devices-loading').hidden = false;
424       $('cloud-devices-unavailable').hidden = true;
425
426       chrome.send('requestPrinterList');
427     }
428   }
429
430   /**
431    * Go to management page for a cloud device.
432    * @param {string} device_id ID of device.
433    */
434   function manageCloudDevice(device_id) {
435     recordUmaEvent(DEVICES_PAGE_EVENTS.MANAGE_CLICKED);
436     chrome.send('openCloudPrintURL',
437                 [PRINTER_MANAGEMENT_PAGE_PREFIX + device_id]);
438   }
439
440   /**
441   * Record an event in the UMA histogram.
442   * @param {number} eventId The id of the event to be recorded.
443   * @private
444   */
445   function recordUmaEvent(eventId) {
446     chrome.send('metricsHandler:recordInHistogram',
447       ['LocalDiscovery.DevicesPage', eventId, DEVICES_PAGE_EVENTS.MAX_EVENT]);
448   }
449
450   /**
451    * Cancel the registration.
452    */
453   function cancelRegistration() {
454     hideRegisterOverlay();
455     chrome.send('cancelRegistration');
456     recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_CANCEL);
457   }
458
459   /**
460    * Retry loading the devices from Google Cloud Print.
461    */
462   function retryLoadCloudDevices() {
463     requestPrinterList();
464   }
465
466   /**
467    * User is not logged in.
468    */
469   function setUserLoggedIn(userLoggedIn) {
470     isUserLoggedIn = userLoggedIn;
471
472     $('cloud-devices-login-promo').hidden = isUserLoggedIn;
473
474
475     if (isUserLoggedIn) {
476       requestPrinterList();
477       $('register-login-promo').hidden = true;
478     } else {
479       $('cloud-devices-loading').hidden = true;
480       $('cloud-devices-unavailable').hidden = true;
481       clearElement($('cloud-devices'));
482     }
483
484     updateUIToReflectState();
485
486     for (var device in devices) {
487       devices[device].setRegisterEnabled(isUserLoggedIn);
488     }
489   }
490
491   function openSignInPage() {
492     chrome.send('showSyncUI');
493   }
494
495   function registerLoginButtonClicked() {
496     recordUmaEvent(DEVICES_PAGE_EVENTS.LOG_IN_STARTED_FROM_REGISTER_PROMO);
497     openSignInPage();
498   }
499
500   function cloudDevicesLoginButtonClicked() {
501     recordUmaEvent(DEVICES_PAGE_EVENTS.LOG_IN_STARTED_FROM_DEVICE_LIST_PROMO);
502     openSignInPage();
503   }
504
505   /**
506    * Set the Cloud Print proxy UI to enabled, disabled, or processing.
507    * @private
508    */
509   function setupCloudPrintConnectorSection(disabled, label, allowed) {
510     if (!cr.isChromeOS && !cr.isMac) {
511       $('cloudPrintConnectorLabel').textContent = label;
512       if (disabled || !allowed) {
513         $('cloudPrintConnectorSetupButton').textContent =
514           loadTimeData.getString('cloudPrintConnectorDisabledButton');
515       } else {
516         $('cloudPrintConnectorSetupButton').textContent =
517           loadTimeData.getString('cloudPrintConnectorEnabledButton');
518       }
519       $('cloudPrintConnectorSetupButton').disabled = !allowed;
520
521       if (disabled) {
522         $('cloudPrintConnectorSetupButton').onclick = function(event) {
523           // Disable the button, set its text to the intermediate state.
524           $('cloudPrintConnectorSetupButton').textContent =
525             loadTimeData.getString('cloudPrintConnectorEnablingButton');
526           $('cloudPrintConnectorSetupButton').disabled = true;
527           chrome.send('showCloudPrintSetupDialog');
528         };
529       } else {
530         $('cloudPrintConnectorSetupButton').onclick = function(event) {
531           chrome.send('disableCloudPrintConnector');
532           requestPrinterList();
533         };
534       }
535     }
536   }
537
538   function removeCloudPrintConnectorSection() {
539     if (!cr.isChromeOS && !cr.isMac) {
540        var connectorSectionElm = $('cloud-print-connector-section');
541        if (connectorSectionElm)
542           connectorSectionElm.parentNode.removeChild(connectorSectionElm);
543      }
544   }
545
546
547   document.addEventListener('DOMContentLoaded', function() {
548     cr.ui.overlay.setupOverlay($('overlay'));
549     cr.ui.overlay.globalInitialization();
550     $('overlay').addEventListener('cancelOverlay', cancelRegistration);
551
552     var cancelButtons = document.querySelectorAll('.register-cancel');
553     var cancelButtonsLength = cancelButtons.length;
554     for (var i = 0; i < cancelButtonsLength; i++) {
555       cancelButtons[i].addEventListener('click', cancelRegistration);
556     }
557
558     $('register-error-exit').addEventListener('click', cancelRegistration);
559
560
561     $('cloud-devices-retry-button').addEventListener('click',
562                                                      retryLoadCloudDevices);
563
564     $('cloud-devices-login-button').addEventListener(
565       'click',
566       cloudDevicesLoginButtonClicked);
567
568     $('register-login-button').addEventListener(
569       'click',
570       registerLoginButtonClicked);
571
572     updateVisibility();
573     document.addEventListener('webkitvisibilitychange', updateVisibility,
574                               false);
575
576
577     focusManager = new LocalDiscoveryFocusManager();
578     focusManager.initialize();
579
580     chrome.send('start');
581     recordUmaEvent(DEVICES_PAGE_EVENTS.OPENED);
582   });
583
584   return {
585     onRegistrationSuccess: onRegistrationSuccess,
586     onRegistrationFailed: onRegistrationFailed,
587     onUnregisteredDeviceUpdate: onUnregisteredDeviceUpdate,
588     onRegistrationConfirmedOnPrinter: onRegistrationConfirmedOnPrinter,
589     onCloudDeviceListAvailable: onCloudDeviceListAvailable,
590     onCloudDeviceListUnavailable: onCloudDeviceListUnavailable,
591     onDeviceCacheFlushed: onDeviceCacheFlushed,
592     onRegistrationCanceledPrinter: onRegistrationCanceledPrinter,
593     onRegistrationTimeout: onRegistrationTimeout,
594     setUserLoggedIn: setUserLoggedIn,
595     setupCloudPrintConnectorSection: setupCloudPrintConnectorSection,
596     removeCloudPrintConnectorSection: removeCloudPrintConnectorSection
597   };
598 });