Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / resources / local_discovery / local_discovery.js
blob9d437aa721738456611b4b4851e56fc8bb8b3b13
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   // Histogram buckets for UMA tracking.
19   /** @const */ var DEVICES_PAGE_EVENTS = {
20     OPENED: 0,
21     LOG_IN_STARTED_FROM_REGISTER_PROMO: 1,
22     LOG_IN_STARTED_FROM_DEVICE_LIST_PROMO: 2,
23     ADD_PRINTER_CLICKED: 3,
24     REGISTER_CLICKED: 4,
25     REGISTER_CONFIRMED: 5,
26     REGISTER_SUCCESS: 6,
27     REGISTER_CANCEL: 7,
28     REGISTER_FAILURE: 8,
29     MANAGE_CLICKED: 9,
30     REGISTER_CANCEL_ON_PRINTER: 10,
31     REGISTER_TIMEOUT: 11,
32     LOG_IN_STARTED_FROM_REGISTER_OVERLAY_PROMO: 12,
33     MAX_EVENT: 13,
34   };
36   /**
37    * Map of service names to corresponding service objects.
38    * @type {Object<string,Service>}
39    */
40   var devices = {};
42   /**
43    * Whether or not the user is currently logged in.
44    * @type bool
45    */
46   var isUserLoggedIn = true;
48   /**
49    * Whether or not the user is supervised or off the record.
50    * @type bool
51    */
52   var isUserSupervisedOrOffTheRecord = false;
54   /**
55    * Whether or not the path-based dialog has been shown.
56    * @type bool
57    */
58   var dialogFromPathHasBeenShown = false;
60   /**
61    * Focus manager for page.
62    */
63   var focusManager = null;
65   /**
66    * Object that represents a device in the device list.
67    * @param {Object} info Information about the device.
68    * @constructor
69    */
70   function Device(info, registerEnabled) {
71     this.info = info;
72     this.domElement = null;
73     this.registerButton = null;
74     this.registerEnabled = registerEnabled;
75   }
77   Device.prototype = {
78     /**
79      * Update the device.
80      * @param {Object} info New information about the device.
81      */
82     updateDevice: function(info) {
83       this.info = info;
84       this.renderDevice();
85     },
87     /**
88      * Delete the device.
89      */
90     removeDevice: function() {
91       this.deviceContainer().removeChild(this.domElement);
92     },
94     /**
95      * Render the device to the device list.
96      */
97     renderDevice: function() {
98       if (this.domElement) {
99         clearElement(this.domElement);
100       } else {
101         this.domElement = document.createElement('div');
102         this.deviceContainer().appendChild(this.domElement);
103       }
105       this.registerButton = fillDeviceDescription(
106         this.domElement,
107         this.info.display_name,
108         this.info.description,
109         this.info.type,
110         loadTimeData.getString('serviceRegister'),
111         this.showRegister.bind(this, this.info.type));
113       this.setRegisterEnabled(this.registerEnabled);
114     },
116     /**
117      * Return the correct container for the device.
118      * @param {boolean} is_mine Whether or not the device is in the 'Registered'
119      *    section.
120      */
121     deviceContainer: function() {
122       return $('register-device-list');
123     },
124     /**
125      * Register the device.
126      */
127     register: function() {
128       recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_CONFIRMED);
129       chrome.send('registerDevice', [this.info.service_name]);
130       setRegisterPage(isPrinter(this.info.type) ?
131           'register-printer-page-adding1' : 'register-device-page-adding1');
132     },
133     /**
134      * Show registrtation UI for device.
135      */
136     showRegister: function() {
137       recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_CLICKED);
138       $('register-message').textContent = loadTimeData.getStringF(
139         isPrinter(this.info.type) ? 'registerPrinterConfirmMessage' :
140                                     'registerDeviceConfirmMessage',
141         this.info.display_name);
142       $('register-continue-button').onclick = this.register.bind(this);
143       showRegisterOverlay();
144     },
145     /**
146      * Set registration button enabled/disabled
147      */
148     setRegisterEnabled: function(isEnabled) {
149       this.registerEnabled = isEnabled;
150       if (this.registerButton) {
151         this.registerButton.disabled = !isEnabled;
152       }
153     }
154   };
156   /**
157    * Manages focus for local devices page.
158    * @constructor
159    * @extends {cr.ui.FocusManager}
160    */
161   function LocalDiscoveryFocusManager() {
162     cr.ui.FocusManager.call(this);
163     this.focusParent_ = document.body;
164   }
166   LocalDiscoveryFocusManager.prototype = {
167     __proto__: cr.ui.FocusManager.prototype,
168     /** @override */
169     getFocusParent: function() {
170       return document.querySelector('#overlay .showing') ||
171         $('main-page');
172     }
173   };
175   /**
176    * Returns a textual representation of the number of printers on the network.
177    * @return {string} Number of printers on the network as localized string.
178    */
179   function generateNumberPrintersAvailableText(numberPrinters) {
180     if (numberPrinters == 0) {
181       return loadTimeData.getString('printersOnNetworkZero');
182     } else if (numberPrinters == 1) {
183       return loadTimeData.getString('printersOnNetworkOne');
184     } else {
185       return loadTimeData.getStringF('printersOnNetworkMultiple',
186                                      numberPrinters);
187     }
188   }
190   /**
191    * Fill device element with the description of a device.
192    * @param {HTMLElement} device_dom_element Element to be filled.
193    * @param {string} name Name of device.
194    * @param {string} description Description of device.
195    * @param {string} type Type of device.
196    * @param {string} button_text Text to appear on button.
197    * @param {function()?} button_action Action for button.
198    * @return {HTMLElement} The button (for enabling/disabling/rebinding)
199    */
200   function fillDeviceDescription(device_dom_element,
201                                  name,
202                                  description,
203                                  type,
204                                  button_text,
205                                  button_action) {
206     device_dom_element.classList.add('device');
207     if (isPrinter(type))
208       device_dom_element.classList.add('printer');
210     var deviceInfo = document.createElement('div');
211     deviceInfo.className = 'device-info';
212     device_dom_element.appendChild(deviceInfo);
214     var deviceName = document.createElement('h3');
215     deviceName.className = 'device-name';
216     deviceName.textContent = name;
217     deviceInfo.appendChild(deviceName);
219     var deviceDescription = document.createElement('div');
220     deviceDescription.className = 'device-subline';
221     deviceDescription.textContent = description;
222     deviceInfo.appendChild(deviceDescription);
224     if (button_action) {
225       var button = document.createElement('button');
226       button.textContent = button_text;
227       button.addEventListener('click', button_action);
228       device_dom_element.appendChild(button);
229     }
231     return button;
232   }
234   /**
235    * Show the register overlay.
236    */
237   function showRegisterOverlay() {
238     recordUmaEvent(DEVICES_PAGE_EVENTS.ADD_PRINTER_CLICKED);
240     var registerOverlay = $('register-overlay');
241     registerOverlay.classList.add('showing');
242     registerOverlay.focus();
244     $('overlay').hidden = false;
245     setRegisterPage('register-page-confirm');
246   }
248   /**
249    * Hide the register overlay.
250    */
251   function hideRegisterOverlay() {
252     $('register-overlay').classList.remove('showing');
253     $('overlay').hidden = true;
254   }
256   /**
257    * Clear a DOM element of all children.
258    * @param {HTMLElement} element DOM element to clear.
259    */
260   function clearElement(element) {
261     while (element.firstChild) {
262       element.removeChild(element.firstChild);
263     }
264   }
266   /**
267    * Announce that a registration failed.
268    */
269   function onRegistrationFailed() {
270     $('error-message').textContent =
271       loadTimeData.getString('addingErrorMessage');
272     setRegisterPage('register-page-error');
273     recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_FAILURE);
274   }
276   /**
277    * Announce that a registration has been canceled on the printer.
278    */
279   function onRegistrationCanceledPrinter() {
280     $('error-message').textContent =
281       loadTimeData.getString('addingCanceledMessage');
282     setRegisterPage('register-page-error');
283     recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_CANCEL_ON_PRINTER);
284   }
286   /**
287    * Announce that a registration has timed out.
288    */
289   function onRegistrationTimeout() {
290     $('error-message').textContent =
291       loadTimeData.getString('addingTimeoutMessage');
292     setRegisterPage('register-page-error');
293     recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_TIMEOUT);
294   }
296   /**
297    * Update UI to reflect that registration has been confirmed on the printer.
298    */
299   function onRegistrationConfirmedOnPrinter() {
300     setRegisterPage('register-printer-page-adding2');
301   }
303   /**
304    * Shows UI to confirm security code.
305    * @param {string} code The security code to confirm.
306    */
307   function onRegistrationConfirmDeviceCode(code) {
308     setRegisterPage('register-device-page-adding2');
309     $('register-device-page-code').textContent = code;
310   }
312   /**
313    * Update device unregistered device list, and update related strings to
314    * reflect the number of devices available to register.
315    * @param {string} name Name of the device.
316    * @param {string} info Additional info of the device or null if the device
317    *                          has been removed.
318    */
319   function onUnregisteredDeviceUpdate(name, info) {
320     if (info) {
321       if (devices.hasOwnProperty(name)) {
322         devices[name].updateDevice(info);
323       } else {
324         devices[name] = new Device(info, isUserLoggedIn);
325         devices[name].renderDevice();
326       }
328       if (name == getOverlayIDFromPath() && !dialogFromPathHasBeenShown) {
329         dialogFromPathHasBeenShown = true;
330         devices[name].showRegister();
331       }
332     } else {
333       if (devices.hasOwnProperty(name)) {
334         devices[name].removeDevice();
335         delete devices[name];
336       }
337     }
339     updateUIToReflectState();
340   }
342   /**
343    * Create the DOM for a cloud device described by the device section.
344    * @param {Array<Object>} devices_list List of devices.
345    */
346   function createCloudDeviceDOM(device) {
347     var devicesDomElement = document.createElement('div');
349     var description;
350     if (device.description == '') {
351       if (isPrinter(device.type))
352         description = loadTimeData.getString('noDescriptionPrinter');
353       else
354         description = loadTimeData.getString('noDescriptionDevice');
355     } else {
356       description = device.description;
357     }
359     fillDeviceDescription(devicesDomElement, device.display_name,
360                           description, device.type,
361                           loadTimeData.getString('manageDevice'),
362                           isPrinter(device.type) ?
363                               manageCloudDevice.bind(null, device.id) : null);
364     return devicesDomElement;
365   }
367   /**
368    * Handle a list of cloud devices available to the user globally.
369    * @param {Array<Object>} devices_list List of devices.
370    */
371   function onCloudDeviceListAvailable(devices_list) {
372     var devicesListLength = devices_list.length;
373     var devicesContainer = $('cloud-devices');
375     clearElement(devicesContainer);
376     $('cloud-devices-loading').hidden = true;
378     for (var i = 0; i < devicesListLength; i++) {
379       devicesContainer.appendChild(createCloudDeviceDOM(devices_list[i]));
380     }
381   }
383   /**
384    * Handle the case where the list of cloud devices is not available.
385    */
386   function onCloudDeviceListUnavailable() {
387     if (isUserLoggedIn) {
388       $('cloud-devices-loading').hidden = true;
389       $('cloud-devices-unavailable').hidden = false;
390     }
391   }
393   /**
394    * Handle the case where the cache for local devices has been flushed..
395    */
396   function onDeviceCacheFlushed() {
397     for (var deviceName in devices) {
398       devices[deviceName].removeDevice();
399       delete devices[deviceName];
400     }
402     updateUIToReflectState();
403   }
405   /**
406    * Update UI strings to reflect the number of local devices.
407    */
408   function updateUIToReflectState() {
409     var numberPrinters = $('register-device-list').children.length;
410     if (numberPrinters == 0) {
411       $('no-printers-message').hidden = false;
413       $('register-login-promo').hidden = true;
414     } else {
415       $('no-printers-message').hidden = true;
416       $('register-login-promo').hidden = isUserLoggedIn ||
417         isUserSupervisedOrOffTheRecord;
418     }
419   }
421   /**
422    * Announce that a registration succeeeded.
423    */
424   function onRegistrationSuccess(device_data) {
425     hideRegisterOverlay();
427     if (device_data.service_name == getOverlayIDFromPath()) {
428       window.close();
429     }
431     var deviceDOM = createCloudDeviceDOM(device_data);
432     $('cloud-devices').insertBefore(deviceDOM, $('cloud-devices').firstChild);
433     recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_SUCCESS);
434   }
436   /**
437    * Update visibility status for page.
438    */
439   function updateVisibility() {
440     chrome.send('isVisible', [!document.hidden]);
441   }
443   /**
444    * Set the page that the register wizard is on.
445    * @param {string} page_id ID string for page.
446    */
447   function setRegisterPage(page_id) {
448     var pages = $('register-overlay').querySelectorAll('.register-page');
449     var pagesLength = pages.length;
450     for (var i = 0; i < pagesLength; i++) {
451       pages[i].hidden = true;
452     }
454     $(page_id).hidden = false;
455   }
457   /**
458    * Request the device list.
459    */
460   function requestDeviceList() {
461     if (isUserLoggedIn) {
462       clearElement($('cloud-devices'));
463       $('cloud-devices-loading').hidden = false;
464       $('cloud-devices-unavailable').hidden = true;
466       chrome.send('requestDeviceList');
467     }
468   }
470   /**
471    * Go to management page for a cloud device.
472    * @param {string} device_id ID of device.
473    */
474   function manageCloudDevice(device_id) {
475     recordUmaEvent(DEVICES_PAGE_EVENTS.MANAGE_CLICKED);
476     chrome.send('openCloudPrintURL', [device_id]);
477   }
479   /**
480   * Record an event in the UMA histogram.
481   * @param {number} eventId The id of the event to be recorded.
482   * @private
483   */
484   function recordUmaEvent(eventId) {
485     chrome.send('metricsHandler:recordInHistogram',
486       ['LocalDiscovery.DevicesPage', eventId, DEVICES_PAGE_EVENTS.MAX_EVENT]);
487   }
489   /**
490    * Cancel the registration.
491    */
492   function cancelRegistration() {
493     hideRegisterOverlay();
494     chrome.send('cancelRegistration');
495     recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_CANCEL);
496   }
498   /**
499    * Confirms device code.
500    */
501   function confirmCode() {
502     chrome.send('confirmCode');
503     setRegisterPage('register-device-page-adding1');
504   }
506   /**
507    * Retry loading the devices from Google Cloud Print.
508    */
509   function retryLoadCloudDevices() {
510     requestDeviceList();
511   }
513   /**
514    * User is not logged in.
515    */
516   function setUserLoggedIn(userLoggedIn, userSupervisedOrOffTheRecord) {
517     isUserLoggedIn = userLoggedIn;
518     isUserSupervisedOrOffTheRecord = userSupervisedOrOffTheRecord;
520     $('cloud-devices-login-promo').hidden = isUserLoggedIn ||
521       isUserSupervisedOrOffTheRecord;
522     $('register-overlay-login-promo').hidden = isUserLoggedIn ||
523       isUserSupervisedOrOffTheRecord;
524     $('register-continue-button').disabled = !isUserLoggedIn ||
525       isUserSupervisedOrOffTheRecord;
527     $('my-devices-container').hidden = userSupervisedOrOffTheRecord;
529     if (isUserSupervisedOrOffTheRecord) {
530       $('cloud-print-connector-section').hidden = true;
531     }
533     if (isUserLoggedIn && !isUserSupervisedOrOffTheRecord) {
534       requestDeviceList();
535       $('register-login-promo').hidden = true;
536     } else {
537       $('cloud-devices-loading').hidden = true;
538       $('cloud-devices-unavailable').hidden = true;
539       clearElement($('cloud-devices'));
540       hideRegisterOverlay();
541     }
543     updateUIToReflectState();
545     for (var device in devices) {
546       devices[device].setRegisterEnabled(isUserLoggedIn);
547     }
548   }
550   function openSignInPage() {
551     chrome.send('showSyncUI');
552   }
554   function registerLoginButtonClicked() {
555     recordUmaEvent(DEVICES_PAGE_EVENTS.LOG_IN_STARTED_FROM_REGISTER_PROMO);
556     openSignInPage();
557   }
559   function registerOverlayLoginButtonClicked() {
560     recordUmaEvent(
561       DEVICES_PAGE_EVENTS.LOG_IN_STARTED_FROM_REGISTER_OVERLAY_PROMO);
562     openSignInPage();
563   }
565   function cloudDevicesLoginButtonClicked() {
566     recordUmaEvent(DEVICES_PAGE_EVENTS.LOG_IN_STARTED_FROM_DEVICE_LIST_PROMO);
567     openSignInPage();
568   }
570   /**
571    * Set the Cloud Print proxy UI to enabled, disabled, or processing.
572    * @private
573    */
574   function setupCloudPrintConnectorSection(disabled, label, allowed) {
575     if (!cr.isChromeOS) {
576       $('cloudPrintConnectorLabel').textContent = label;
577       if (disabled || !allowed) {
578         $('cloudPrintConnectorSetupButton').textContent =
579           loadTimeData.getString('cloudPrintConnectorDisabledButton');
580       } else {
581         $('cloudPrintConnectorSetupButton').textContent =
582           loadTimeData.getString('cloudPrintConnectorEnabledButton');
583       }
584       $('cloudPrintConnectorSetupButton').disabled = !allowed;
586       if (disabled) {
587         $('cloudPrintConnectorSetupButton').onclick = function(event) {
588           // Disable the button, set its text to the intermediate state.
589           $('cloudPrintConnectorSetupButton').textContent =
590             loadTimeData.getString('cloudPrintConnectorEnablingButton');
591           $('cloudPrintConnectorSetupButton').disabled = true;
592           chrome.send('showCloudPrintSetupDialog');
593         };
594       } else {
595         $('cloudPrintConnectorSetupButton').onclick = function(event) {
596           chrome.send('disableCloudPrintConnector');
597           requestDeviceList();
598         };
599       }
600     }
601   }
603   function removeCloudPrintConnectorSection() {
604     if (!cr.isChromeOS) {
605        var connectorSectionElm = $('cloud-print-connector-section');
606        if (connectorSectionElm)
607           connectorSectionElm.parentNode.removeChild(connectorSectionElm);
608      }
609   }
611   function getOverlayIDFromPath() {
612     if (document.location.pathname == '/register') {
613       var params = parseQueryParams(document.location);
614       return params['id'] || null;
615     }
616   }
618   /**
619    * Returns true of device is printer.
620    * @param {string} type Type of printer.
621    */
622   function isPrinter(type) {
623     return type == 'printer';
624   }
626   document.addEventListener('DOMContentLoaded', function() {
627     cr.ui.overlay.setupOverlay($('overlay'));
628     cr.ui.overlay.globalInitialization();
629     $('overlay').addEventListener('cancelOverlay', cancelRegistration);
631     [].forEach.call(
632         document.querySelectorAll('.register-cancel'), function(button) {
633       button.addEventListener('click', cancelRegistration);
634     });
636     [].forEach.call(
637         document.querySelectorAll('.confirm-code'), function(button) {
638       button.addEventListener('click', confirmCode);
639     });
641     $('register-error-exit').addEventListener('click', cancelRegistration);
644     $('cloud-devices-retry-link').addEventListener('click',
645                                                    retryLoadCloudDevices);
647     $('cloud-devices-login-link').addEventListener(
648       'click',
649       cloudDevicesLoginButtonClicked);
651     $('register-login-link').addEventListener(
652       'click',
653       registerLoginButtonClicked);
655     $('register-overlay-login-button').addEventListener(
656       'click',
657       registerOverlayLoginButtonClicked);
659     if (loadTimeData.valueExists('backButtonURL')) {
660       $('back-link').hidden = false;
661       $('back-link').addEventListener('click', function() {
662         window.location.href = loadTimeData.getString('backButtonURL');
663       });
664     }
666     updateVisibility();
667     document.addEventListener('visibilitychange', updateVisibility, false);
669     focusManager = new LocalDiscoveryFocusManager();
670     focusManager.initialize();
672     chrome.send('start');
673     recordUmaEvent(DEVICES_PAGE_EVENTS.OPENED);
674   });
676   return {
677     onRegistrationSuccess: onRegistrationSuccess,
678     onRegistrationFailed: onRegistrationFailed,
679     onUnregisteredDeviceUpdate: onUnregisteredDeviceUpdate,
680     onRegistrationConfirmedOnPrinter: onRegistrationConfirmedOnPrinter,
681     onRegistrationConfirmDeviceCode: onRegistrationConfirmDeviceCode,
682     onCloudDeviceListAvailable: onCloudDeviceListAvailable,
683     onCloudDeviceListUnavailable: onCloudDeviceListUnavailable,
684     onDeviceCacheFlushed: onDeviceCacheFlushed,
685     onRegistrationCanceledPrinter: onRegistrationCanceledPrinter,
686     onRegistrationTimeout: onRegistrationTimeout,
687     setUserLoggedIn: setUserLoggedIn,
688     setupCloudPrintConnectorSection: setupCloudPrintConnectorSection,
689     removeCloudPrintConnectorSection: removeCloudPrintConnectorSection
690   };