15c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu// Copyright 2014 The Chromium Authors. All rights reserved.
25c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu// Use of this source code is governed by a BSD-style license that can be
35c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu// found in the LICENSE file.
45c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
55c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liucr.define('settime', function() {
65c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /**
75c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu   * TimeSetter handles a dialog to check and set system time. It can also
85c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu   * include a timezone dropdown if timezoneId is provided.
95c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu   *
105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu   * TimeSetter uses the system time to populate the controls initially and
115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu   * update them as the system time or timezone changes, and notifies Chrome
125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu   * when the user changes the time or timezone.
135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu   * @constructor
145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu   */
155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  function TimeSetter() {}
165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  cr.addSingletonGetter(TimeSetter);
185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @const */ var BODY_PADDING_PX = 20;
205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @const */ var LABEL_PADDING_PX = 5;
215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  TimeSetter.prototype = {
235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    /**
245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * Performs initial setup.
255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     */
265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    initialize: function() {
275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // Store values for reverting inputs when the user's date/time is invalid.
285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.prevValues_ = {};
295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // The build time doesn't include a timezone, so subtract 1 day to get a
315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // safe minimum date.
325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.minDate_ = new Date(loadTimeData.getValue('buildTime'));
335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.minDate_.setDate(this.minDate_.getDate() - 1);
345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // Set the max date to the min date plus 20 years.
365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.maxDate_ = new Date(this.minDate_);
375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.maxDate_.setYear(this.minDate_.getFullYear() + 20);
385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // Make sure the ostensible date is within this range.
405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      var now = new Date();
415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      if (now > this.maxDate_)
425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.maxDate_ = now;
435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      else if (now < this.minDate_)
445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.minDate_ = now;
455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      $('date').setAttribute('min', this.toHtmlValues_(this.minDate_).date);
475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      $('date').setAttribute('max', this.toHtmlValues_(this.maxDate_).date);
485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.updateTime_();
505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // Show the timezone select if we have a timezone ID.
525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      var currentTimezoneId = loadTimeData.getValue('currentTimezoneId');
535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      if (currentTimezoneId) {
545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.setTimezone_(currentTimezoneId);
555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        $('timezone-select').addEventListener(
565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu            'change', this.onTimezoneChange_.bind(this), false);
575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        $('timezone').hidden = false;
585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      }
595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.sizeToFit_();
615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      $('time').addEventListener('blur', this.onTimeBlur_.bind(this), false);
635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      $('date').addEventListener('blur', this.onTimeBlur_.bind(this), false);
645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      $('set-time').addEventListener(
665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          'submit', this.onSubmit_.bind(this), false);
675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    },
685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    /**
705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * Sets the current timezone.
715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * @param {string} timezoneId The timezone ID to select.
725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * @private
735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     */
745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    setTimezone_: function(timezoneId) {
755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      $('timezone-select').value = timezoneId;
765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.updateTime_();
775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    },
785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    /**
805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * Updates the date/time controls to the current local time.
815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * Called initially, then called again once a minute.
825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * @private
835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     */
845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    updateTime_: function() {
855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      var now = new Date();
865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // Only update time controls if neither is focused.
885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      if (document.activeElement.id != 'date' &&
895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          document.activeElement.id != 'time') {
905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        var htmlValues = this.toHtmlValues_(now);
915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.prevValues_.date = $('date').value = htmlValues.date;
925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.prevValues_.time = $('time').value = htmlValues.time;
935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      }
945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      window.clearTimeout(this.timeTimeout_);
965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // Start timer to update these inputs every minute.
985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      var secondsRemaining = 60 - now.getSeconds();
995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.timeTimeout_ = window.setTimeout(this.updateTime_.bind(this),
1005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu                                            secondsRemaining * 1000);
1015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    },
1025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    /**
1045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * Sets the system time from the UI.
1055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * @private
1065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     */
1075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    applyTime_: function() {
1085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      var date = $('date').valueAsDate;
1095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      date.setMilliseconds(date.getMilliseconds() + $('time').valueAsNumber);
1105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // Add timezone offset to get real time.
1125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      date.setMinutes(date.getMinutes() + date.getTimezoneOffset());
1135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      var seconds = Math.floor(date / 1000);
1155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      chrome.send('setTimeInSeconds', [seconds]);
1165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    },
1175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    /**
1195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * Called when focus is lost on date/time controls.
1205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * @param {Event} e The blur event.
1215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * @private
1225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     */
1235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    onTimeBlur_: function(e) {
1245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      if (e.target.validity.valid && e.target.value) {
1255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        // Make this the new fallback time in case of future invalid input.
1265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.prevValues_[e.target.id] = e.target.value;
1275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.applyTime_();
1285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      } else {
1295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        // Restore previous value.
1305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        e.target.value = this.prevValues_[e.target.id];
1315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      }
1325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    },
1335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    /**
1355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * @param {Event} e The change event.
1365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * @private
1375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     */
1385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    onTimezoneChange_: function(e) {
1395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      chrome.send('setTimezone', [e.currentTarget.value]);
1405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    },
1415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    /**
1435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * Closes the dialog window.
1445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * @param {Event} e The submit event.
1455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * @private
1465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     */
1475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    onSubmit_: function(e) {
1485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      e.preventDefault();
1495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      chrome.send('dialogClose');
1505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    },
1515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    /**
1535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * Resizes the window if necessary to show the entire contents.
1545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * @private
1555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     */
1565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    sizeToFit_: function() {
1575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // Because of l10n, we should check that the vertical content can fit
1585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // within the window.
1595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      if (window.innerHeight < document.body.scrollHeight) {
1605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        // Resize window to fit scrollHeight and the title bar.
1615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        var newHeight = document.body.scrollHeight +
1625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu                        window.outerHeight - window.innerHeight;
1635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        window.resizeTo(window.outerWidth, newHeight);
1645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      }
1655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    },
1665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    /**
1685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * Builds date and time strings suitable for the values of HTML date and
1695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * time elements.
1705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * @param {Date} date The date object to represent.
1715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * @return {{date: string, time: string}} Date is an RFC 3339 formatted date
1725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     *     and time is an HH:MM formatted time.
1735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     * @private
1745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu     */
1755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    toHtmlValues_: function(date) {
1765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // Get the current time and subtract the timezone offset, so the
1775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // JSON string is in local time.
1785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      var localDate = new Date(date);
1795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      localDate.setMinutes(date.getMinutes() - date.getTimezoneOffset());
1805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      return {date: localDate.toISOString().slice(0, 10),
1815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu              time: localDate.toISOString().slice(11, 16)};
1825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    },
1835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  };
1845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  TimeSetter.setTimezone = function(timezoneId) {
1865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    TimeSetter.getInstance().setTimezone_(timezoneId);
1875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  };
1885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  TimeSetter.updateTime = function() {
1905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    TimeSetter.getInstance().updateTime_();
1915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  };
1925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  return {
1945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    TimeSetter: TimeSetter
1955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  };
1965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu});
1975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liudocument.addEventListener('DOMContentLoaded', function() {
1995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  settime.TimeSetter.getInstance().initialize();
2005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu});
201