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