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.
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.
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.
15 cr.define('local_discovery', function() {
19 * Prefix for printer management page URLs, relative to base cloud print URL.
22 var PRINTER_MANAGEMENT_PAGE_PREFIX = '#printers/';
24 // Histogram buckets for UMA tracking.
25 /** @const */ var DEVICES_PAGE_EVENTS = {
27 LOG_IN_STARTED_FROM_REGISTER_PROMO: 1,
28 LOG_IN_STARTED_FROM_DEVICE_LIST_PROMO: 2,
29 ADD_PRINTER_CLICKED: 3,
31 REGISTER_CONFIRMED: 5,
36 REGISTER_CANCEL_ON_PRINTER: 10,
38 LOG_IN_STARTED_FROM_REGISTER_OVERLAY_PROMO: 12,
43 * Map of service names to corresponding service objects.
44 * @type {Object.<string,Service>}
49 * Whether or not the user is currently logged in.
52 var isUserLoggedIn = true;
55 * Whether or not the path-based dialog has been shown.
58 var dialogFromPathHasBeenShown = false;
61 * Focus manager for page.
63 var focusManager = null;
66 * Object that represents a device in the device list.
67 * @param {Object} info Information about the device.
70 function Device(info, registerEnabled) {
72 this.domElement = null;
73 this.registerButton = null;
74 this.registerEnabled = registerEnabled;
80 * @param {Object} info New information about the device.
82 updateDevice: function(info) {
90 removeDevice: function() {
91 this.deviceContainer().removeChild(this.domElement);
95 * Render the device to the device list.
97 renderDevice: function() {
98 if (this.domElement) {
99 clearElement(this.domElement);
101 this.domElement = document.createElement('div');
102 this.deviceContainer().appendChild(this.domElement);
105 this.registerButton = fillDeviceDescription(
107 this.info.human_readable_name,
108 this.info.description,
109 loadTimeData.getString('serviceRegister'),
110 this.showRegister.bind(this));
112 this.setRegisterEnabled(this.registerEnabled);
116 * Return the correct container for the device.
117 * @param {boolean} is_mine Whether or not the device is in the 'Registered'
120 deviceContainer: function() {
121 return $('register-device-list');
124 * Register the device.
126 register: function() {
127 recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_CONFIRMED);
128 chrome.send('registerDevice', [this.info.service_name]);
129 setRegisterPage('register-page-adding1');
132 * Show registrtation UI for device.
134 showRegister: function() {
135 recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_CLICKED);
136 $('register-message').textContent = loadTimeData.getStringF(
137 'registerConfirmMessage',
138 this.info.human_readable_name);
139 $('register-continue-button').onclick = this.register.bind(this);
140 showRegisterOverlay();
143 * Set registration button enabled/disabled
145 setRegisterEnabled: function(isEnabled) {
146 this.registerEnabled = isEnabled;
147 if (this.registerButton) {
148 this.registerButton.disabled = !isEnabled;
154 * Manages focus for local devices page.
156 * @extends {cr.ui.FocusManager}
158 function LocalDiscoveryFocusManager() {
159 cr.ui.FocusManager.call(this);
160 this.focusParent_ = document.body;
163 LocalDiscoveryFocusManager.prototype = {
164 __proto__: cr.ui.FocusManager.prototype,
166 getFocusParent: function() {
167 return document.querySelector('#overlay .showing') ||
173 * Returns a textual representation of the number of printers on the network.
174 * @return {string} Number of printers on the network as localized string.
176 function generateNumberPrintersAvailableText(numberPrinters) {
177 if (numberPrinters == 0) {
178 return loadTimeData.getString('printersOnNetworkZero');
179 } else if (numberPrinters == 1) {
180 return loadTimeData.getString('printersOnNetworkOne');
182 return loadTimeData.getStringF('printersOnNetworkMultiple',
188 * Fill device element with the description of a device.
189 * @param {HTMLElement} device_dom_element Element to be filled.
190 * @param {string} name Name of device.
191 * @param {string} description Description of device.
192 * @param {string} button_text Text to appear on button.
193 * @param {function()} button_action Action for button.
194 * @return {HTMLElement} The button (for enabling/disabling/rebinding)
196 function fillDeviceDescription(device_dom_element,
201 device_dom_element.classList.add('device');
202 device_dom_element.classList.add('printer');
204 var deviceInfo = document.createElement('div');
205 deviceInfo.className = 'device-info';
206 device_dom_element.appendChild(deviceInfo);
208 var deviceName = document.createElement('h3');
209 deviceName.className = 'device-name';
210 deviceName.textContent = name;
211 deviceInfo.appendChild(deviceName);
213 var deviceDescription = document.createElement('div');
214 deviceDescription.className = 'device-subline';
215 deviceDescription.textContent = description;
216 deviceInfo.appendChild(deviceDescription);
218 var button = document.createElement('button');
219 button.textContent = button_text;
220 button.addEventListener('click', button_action);
221 device_dom_element.appendChild(button);
227 * Show the register overlay.
229 function showRegisterOverlay() {
230 recordUmaEvent(DEVICES_PAGE_EVENTS.ADD_PRINTER_CLICKED);
232 var registerOverlay = $('register-overlay');
233 registerOverlay.classList.add('showing');
234 registerOverlay.focus();
236 $('overlay').hidden = false;
237 setRegisterPage('register-page-confirm');
241 * Hide the register overlay.
243 function hideRegisterOverlay() {
244 $('register-overlay').classList.remove('showing');
245 $('overlay').hidden = true;
249 * Clear a DOM element of all children.
250 * @param {HTMLElement} element DOM element to clear.
252 function clearElement(element) {
253 while (element.firstChild) {
254 element.removeChild(element.firstChild);
259 * Announce that a registration failed.
261 function onRegistrationFailed() {
262 $('error-message').textContent =
263 loadTimeData.getString('addingErrorMessage');
264 setRegisterPage('register-page-error');
265 recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_FAILURE);
269 * Announce that a registration has been canceled on the printer.
271 function onRegistrationCanceledPrinter() {
272 $('error-message').textContent =
273 loadTimeData.getString('addingCanceledMessage');
274 setRegisterPage('register-page-error');
275 recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_CANCEL_ON_PRINTER);
279 * Announce that a registration has timed out.
281 function onRegistrationTimeout() {
282 $('error-message').textContent =
283 loadTimeData.getString('addingTimeoutMessage');
284 setRegisterPage('register-page-error');
285 recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_TIMEOUT);
289 * Update UI to reflect that registration has been confirmed on the printer.
291 function onRegistrationConfirmedOnPrinter() {
292 setRegisterPage('register-page-adding2');
296 * Update device unregistered device list, and update related strings to
297 * reflect the number of devices available to register.
298 * @param {string} name Name of the device.
299 * @param {string} info Additional info of the device or null if the device
302 function onUnregisteredDeviceUpdate(name, info) {
304 if (devices.hasOwnProperty(name)) {
305 devices[name].updateDevice(info);
307 devices[name] = new Device(info, isUserLoggedIn);
308 devices[name].renderDevice();
311 if (name == getOverlayIDFromPath() && !dialogFromPathHasBeenShown) {
312 dialogFromPathHasBeenShown = true;
313 devices[name].showRegister();
316 if (devices.hasOwnProperty(name)) {
317 devices[name].removeDevice();
318 delete devices[name];
322 updateUIToReflectState();
326 * Create the DOM for a cloud device described by the device section.
327 * @param {Array.<Object>} devices_list List of devices.
329 function createCloudDeviceDOM(device) {
330 var devicesDomElement = document.createElement('div');
333 if (device.description == '') {
334 description = loadTimeData.getString('noDescription');
336 description = device.description;
339 fillDeviceDescription(devicesDomElement, device.display_name,
340 description, 'Manage' /*Localize*/,
341 manageCloudDevice.bind(null, device.id));
342 return devicesDomElement;
346 * Handle a list of cloud devices available to the user globally.
347 * @param {Array.<Object>} devices_list List of devices.
349 function onCloudDeviceListAvailable(devices_list) {
350 var devicesListLength = devices_list.length;
351 var devicesContainer = $('cloud-devices');
353 clearElement(devicesContainer);
354 $('cloud-devices-loading').hidden = true;
356 for (var i = 0; i < devicesListLength; i++) {
357 devicesContainer.appendChild(createCloudDeviceDOM(devices_list[i]));
362 * Handle the case where the list of cloud devices is not available.
364 function onCloudDeviceListUnavailable() {
365 if (isUserLoggedIn) {
366 $('cloud-devices-loading').hidden = true;
367 $('cloud-devices-unavailable').hidden = false;
372 * Handle the case where the cache for local devices has been flushed..
374 function onDeviceCacheFlushed() {
375 for (var deviceName in devices) {
376 devices[deviceName].removeDevice();
377 delete devices[deviceName];
380 updateUIToReflectState();
384 * Update UI strings to reflect the number of local devices.
386 function updateUIToReflectState() {
387 var numberPrinters = $('register-device-list').children.length;
388 if (numberPrinters == 0) {
389 $('no-printers-message').hidden = false;
391 $('register-login-promo').hidden = true;
393 $('no-printers-message').hidden = true;
394 $('register-login-promo').hidden = isUserLoggedIn;
399 * Announce that a registration succeeeded.
401 function onRegistrationSuccess(device_data) {
402 hideRegisterOverlay();
404 if (device_data.service_name == getOverlayIDFromPath()) {
408 var deviceDOM = createCloudDeviceDOM(device_data);
409 $('cloud-devices').insertBefore(deviceDOM, $('cloud-devices').firstChild);
410 recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_SUCCESS);
414 * Update visibility status for page.
416 function updateVisibility() {
417 chrome.send('isVisible', [!document.hidden]);
421 * Set the page that the register wizard is on.
422 * @param {string} page_id ID string for page.
424 function setRegisterPage(page_id) {
425 var pages = $('register-overlay').querySelectorAll('.register-page');
426 var pagesLength = pages.length;
427 for (var i = 0; i < pagesLength; i++) {
428 pages[i].hidden = true;
431 $(page_id).hidden = false;
435 * Request the printer list.
437 function requestPrinterList() {
438 if (isUserLoggedIn) {
439 clearElement($('cloud-devices'));
440 $('cloud-devices-loading').hidden = false;
441 $('cloud-devices-unavailable').hidden = true;
443 chrome.send('requestPrinterList');
448 * Go to management page for a cloud device.
449 * @param {string} device_id ID of device.
451 function manageCloudDevice(device_id) {
452 recordUmaEvent(DEVICES_PAGE_EVENTS.MANAGE_CLICKED);
453 chrome.send('openCloudPrintURL',
454 [PRINTER_MANAGEMENT_PAGE_PREFIX + device_id]);
458 * Record an event in the UMA histogram.
459 * @param {number} eventId The id of the event to be recorded.
462 function recordUmaEvent(eventId) {
463 chrome.send('metricsHandler:recordInHistogram',
464 ['LocalDiscovery.DevicesPage', eventId, DEVICES_PAGE_EVENTS.MAX_EVENT]);
468 * Cancel the registration.
470 function cancelRegistration() {
471 hideRegisterOverlay();
472 chrome.send('cancelRegistration');
473 recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_CANCEL);
477 * Retry loading the devices from Google Cloud Print.
479 function retryLoadCloudDevices() {
480 requestPrinterList();
484 * User is not logged in.
486 function setUserLoggedIn(userLoggedIn) {
487 isUserLoggedIn = userLoggedIn;
489 $('cloud-devices-login-promo').hidden = isUserLoggedIn;
490 $('register-overlay-login-promo').hidden = isUserLoggedIn;
491 $('register-continue-button').disabled = !isUserLoggedIn;
493 if (isUserLoggedIn) {
494 requestPrinterList();
495 $('register-login-promo').hidden = true;
497 $('cloud-devices-loading').hidden = true;
498 $('cloud-devices-unavailable').hidden = true;
499 clearElement($('cloud-devices'));
500 hideRegisterOverlay();
503 updateUIToReflectState();
505 for (var device in devices) {
506 devices[device].setRegisterEnabled(isUserLoggedIn);
510 function openSignInPage() {
511 chrome.send('showSyncUI');
514 function registerLoginButtonClicked() {
515 recordUmaEvent(DEVICES_PAGE_EVENTS.LOG_IN_STARTED_FROM_REGISTER_PROMO);
519 function registerOverlayLoginButtonClicked() {
521 DEVICES_PAGE_EVENTS.LOG_IN_STARTED_FROM_REGISTER_OVERLAY_PROMO);
525 function cloudDevicesLoginButtonClicked() {
526 recordUmaEvent(DEVICES_PAGE_EVENTS.LOG_IN_STARTED_FROM_DEVICE_LIST_PROMO);
531 * Set the Cloud Print proxy UI to enabled, disabled, or processing.
534 function setupCloudPrintConnectorSection(disabled, label, allowed) {
535 if (!cr.isChromeOS && !cr.isMac) {
536 $('cloudPrintConnectorLabel').textContent = label;
537 if (disabled || !allowed) {
538 $('cloudPrintConnectorSetupButton').textContent =
539 loadTimeData.getString('cloudPrintConnectorDisabledButton');
541 $('cloudPrintConnectorSetupButton').textContent =
542 loadTimeData.getString('cloudPrintConnectorEnabledButton');
544 $('cloudPrintConnectorSetupButton').disabled = !allowed;
547 $('cloudPrintConnectorSetupButton').onclick = function(event) {
548 // Disable the button, set its text to the intermediate state.
549 $('cloudPrintConnectorSetupButton').textContent =
550 loadTimeData.getString('cloudPrintConnectorEnablingButton');
551 $('cloudPrintConnectorSetupButton').disabled = true;
552 chrome.send('showCloudPrintSetupDialog');
555 $('cloudPrintConnectorSetupButton').onclick = function(event) {
556 chrome.send('disableCloudPrintConnector');
557 requestPrinterList();
563 function removeCloudPrintConnectorSection() {
564 if (!cr.isChromeOS && !cr.isMac) {
565 var connectorSectionElm = $('cloud-print-connector-section');
566 if (connectorSectionElm)
567 connectorSectionElm.parentNode.removeChild(connectorSectionElm);
571 function getOverlayIDFromPath() {
572 if (document.location.pathname == '/register') {
573 var params = parseQueryParams(document.location);
574 return params['id'] || null;
578 document.addEventListener('DOMContentLoaded', function() {
579 cr.ui.overlay.setupOverlay($('overlay'));
580 cr.ui.overlay.globalInitialization();
581 $('overlay').addEventListener('cancelOverlay', cancelRegistration);
583 var cancelButtons = document.querySelectorAll('.register-cancel');
584 var cancelButtonsLength = cancelButtons.length;
585 for (var i = 0; i < cancelButtonsLength; i++) {
586 cancelButtons[i].addEventListener('click', cancelRegistration);
589 $('register-error-exit').addEventListener('click', cancelRegistration);
592 $('cloud-devices-retry-button').addEventListener('click',
593 retryLoadCloudDevices);
595 $('cloud-devices-login-button').addEventListener(
597 cloudDevicesLoginButtonClicked);
599 $('register-login-button').addEventListener(
601 registerLoginButtonClicked);
603 $('register-overlay-login-button').addEventListener(
605 registerOverlayLoginButtonClicked);
608 document.addEventListener('visibilitychange', updateVisibility, false);
610 focusManager = new LocalDiscoveryFocusManager();
611 focusManager.initialize();
613 chrome.send('start');
614 recordUmaEvent(DEVICES_PAGE_EVENTS.OPENED);
618 onRegistrationSuccess: onRegistrationSuccess,
619 onRegistrationFailed: onRegistrationFailed,
620 onUnregisteredDeviceUpdate: onUnregisteredDeviceUpdate,
621 onRegistrationConfirmedOnPrinter: onRegistrationConfirmedOnPrinter,
622 onCloudDeviceListAvailable: onCloudDeviceListAvailable,
623 onCloudDeviceListUnavailable: onCloudDeviceListUnavailable,
624 onDeviceCacheFlushed: onDeviceCacheFlushed,
625 onRegistrationCanceledPrinter: onRegistrationCanceledPrinter,
626 onRegistrationTimeout: onRegistrationTimeout,
627 setUserLoggedIn: setUserLoggedIn,
628 setupCloudPrintConnectorSection: setupCloudPrintConnectorSection,
629 removeCloudPrintConnectorSection: removeCloudPrintConnectorSection