• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
15cr.define('local_discovery', function() {
16  'use strict';
17
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  };
35
36  /**
37   * Map of service names to corresponding service objects.
38   * @type {Object.<string,Service>}
39   */
40  var devices = {};
41
42  /**
43   * Whether or not the user is currently logged in.
44   * @type bool
45   */
46  var isUserLoggedIn = true;
47
48  /**
49   * Whether or not the user is supervised or off the record.
50   * @type bool
51   */
52  var isUserSupervisedOrOffTheRecord = false;
53
54  /**
55   * Whether or not the path-based dialog has been shown.
56   * @type bool
57   */
58  var dialogFromPathHasBeenShown = false;
59
60  /**
61   * Focus manager for page.
62   */
63  var focusManager = null;
64
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  }
76
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    },
86
87    /**
88     * Delete the device.
89     */
90    removeDevice: function() {
91      this.deviceContainer().removeChild(this.domElement);
92    },
93
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      }
104
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));
112
113      this.setRegisterEnabled(this.registerEnabled);
114    },
115
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  };
155
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  }
165
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  };
174
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  }
189
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');
209
210    var deviceInfo = document.createElement('div');
211    deviceInfo.className = 'device-info';
212    device_dom_element.appendChild(deviceInfo);
213
214    var deviceName = document.createElement('h3');
215    deviceName.className = 'device-name';
216    deviceName.textContent = name;
217    deviceInfo.appendChild(deviceName);
218
219    var deviceDescription = document.createElement('div');
220    deviceDescription.className = 'device-subline';
221    deviceDescription.textContent = description;
222    deviceInfo.appendChild(deviceDescription);
223
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    }
230
231    return button;
232  }
233
234  /**
235   * Show the register overlay.
236   */
237  function showRegisterOverlay() {
238    recordUmaEvent(DEVICES_PAGE_EVENTS.ADD_PRINTER_CLICKED);
239
240    var registerOverlay = $('register-overlay');
241    registerOverlay.classList.add('showing');
242    registerOverlay.focus();
243
244    $('overlay').hidden = false;
245    setRegisterPage('register-page-confirm');
246  }
247
248  /**
249   * Hide the register overlay.
250   */
251  function hideRegisterOverlay() {
252    $('register-overlay').classList.remove('showing');
253    $('overlay').hidden = true;
254  }
255
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  }
265
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  }
275
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  }
285
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  }
295
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  }
302
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  }
311
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      }
327
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    }
338
339    updateUIToReflectState();
340  }
341
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');
348
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    }
358
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  }
366
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');
374
375    clearElement(devicesContainer);
376    $('cloud-devices-loading').hidden = true;
377
378    for (var i = 0; i < devicesListLength; i++) {
379      devicesContainer.appendChild(createCloudDeviceDOM(devices_list[i]));
380    }
381  }
382
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  }
392
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    }
401
402    updateUIToReflectState();
403  }
404
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;
412
413      $('register-login-promo').hidden = true;
414    } else {
415      $('no-printers-message').hidden = true;
416      $('register-login-promo').hidden = isUserLoggedIn ||
417        isUserSupervisedOrOffTheRecord;
418    }
419  }
420
421  /**
422   * Announce that a registration succeeeded.
423   */
424  function onRegistrationSuccess(device_data) {
425    hideRegisterOverlay();
426
427    if (device_data.service_name == getOverlayIDFromPath()) {
428      window.close();
429    }
430
431    var deviceDOM = createCloudDeviceDOM(device_data);
432    $('cloud-devices').insertBefore(deviceDOM, $('cloud-devices').firstChild);
433    recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_SUCCESS);
434  }
435
436  /**
437   * Update visibility status for page.
438   */
439  function updateVisibility() {
440    chrome.send('isVisible', [!document.hidden]);
441  }
442
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    }
453
454    $(page_id).hidden = false;
455  }
456
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;
465
466      chrome.send('requestDeviceList');
467    }
468  }
469
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  }
478
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  }
488
489  /**
490   * Cancel the registration.
491   */
492  function cancelRegistration() {
493    hideRegisterOverlay();
494    chrome.send('cancelRegistration');
495    recordUmaEvent(DEVICES_PAGE_EVENTS.REGISTER_CANCEL);
496  }
497
498  /**
499   * Confirms device code.
500   */
501  function confirmCode() {
502    chrome.send('confirmCode');
503    setRegisterPage('register-device-page-adding1');
504  }
505
506  /**
507   * Retry loading the devices from Google Cloud Print.
508   */
509  function retryLoadCloudDevices() {
510    requestDeviceList();
511  }
512
513  /**
514   * User is not logged in.
515   */
516  function setUserLoggedIn(userLoggedIn, userSupervisedOrOffTheRecord) {
517    isUserLoggedIn = userLoggedIn;
518    isUserSupervisedOrOffTheRecord = userSupervisedOrOffTheRecord;
519
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;
526
527    $('my-devices-container').hidden = userSupervisedOrOffTheRecord;
528
529    if (isUserSupervisedOrOffTheRecord) {
530      $('cloud-print-connector-section').hidden = true;
531    }
532
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    }
542
543    updateUIToReflectState();
544
545    for (var device in devices) {
546      devices[device].setRegisterEnabled(isUserLoggedIn);
547    }
548  }
549
550  function openSignInPage() {
551    chrome.send('showSyncUI');
552  }
553
554  function registerLoginButtonClicked() {
555    recordUmaEvent(DEVICES_PAGE_EVENTS.LOG_IN_STARTED_FROM_REGISTER_PROMO);
556    openSignInPage();
557  }
558
559  function registerOverlayLoginButtonClicked() {
560    recordUmaEvent(
561      DEVICES_PAGE_EVENTS.LOG_IN_STARTED_FROM_REGISTER_OVERLAY_PROMO);
562    openSignInPage();
563  }
564
565  function cloudDevicesLoginButtonClicked() {
566    recordUmaEvent(DEVICES_PAGE_EVENTS.LOG_IN_STARTED_FROM_DEVICE_LIST_PROMO);
567    openSignInPage();
568  }
569
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;
585
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  }
602
603  function removeCloudPrintConnectorSection() {
604    if (!cr.isChromeOS) {
605       var connectorSectionElm = $('cloud-print-connector-section');
606       if (connectorSectionElm)
607          connectorSectionElm.parentNode.removeChild(connectorSectionElm);
608     }
609  }
610
611  function getOverlayIDFromPath() {
612    if (document.location.pathname == '/register') {
613      var params = parseQueryParams(document.location);
614      return params['id'] || null;
615    }
616  }
617
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  }
625
626  document.addEventListener('DOMContentLoaded', function() {
627    cr.ui.overlay.setupOverlay($('overlay'));
628    cr.ui.overlay.globalInitialization();
629    $('overlay').addEventListener('cancelOverlay', cancelRegistration);
630
631    [].forEach.call(
632        document.querySelectorAll('.register-cancel'), function(button) {
633      button.addEventListener('click', cancelRegistration);
634    });
635
636    [].forEach.call(
637        document.querySelectorAll('.confirm-code'), function(button) {
638      button.addEventListener('click', confirmCode);
639    });
640
641    $('register-error-exit').addEventListener('click', cancelRegistration);
642
643
644    $('cloud-devices-retry-button').addEventListener('click',
645                                                     retryLoadCloudDevices);
646
647    $('cloud-devices-login-button').addEventListener(
648      'click',
649      cloudDevicesLoginButtonClicked);
650
651    $('register-login-button').addEventListener(
652      'click',
653      registerLoginButtonClicked);
654
655    $('register-overlay-login-button').addEventListener(
656      'click',
657      registerOverlayLoginButtonClicked);
658
659    if (loadTimeData.valueExists('backButtonURL')) {
660      $('back-button').hidden = false;
661      $('back-button').addEventListener('click', function() {
662        window.location.href = loadTimeData.getString('backButtonURL');
663      });
664    }
665
666    updateVisibility();
667    document.addEventListener('visibilitychange', updateVisibility, false);
668
669    focusManager = new LocalDiscoveryFocusManager();
670    focusManager.initialize();
671
672    chrome.send('start');
673    recordUmaEvent(DEVICES_PAGE_EVENTS.OPENED);
674  });
675
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  };
691});
692