Home | History | Annotate | Download | only in chromeos
      1 // Copyright (c) 2012 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 var browserBridge;
      6 
      7 /**
      8  * Class that keeps track of current burn process state.
      9  * @param {Object} strings Localized state strings.
     10  * @constructor
     11  */
     12 function State(strings) {
     13   this.setStrings(strings);
     14   this.changeState(State.StatesEnum.DEVICE_NONE);
     15 }
     16 
     17 /**
     18  * State Enum object.
     19  */
     20 State.StatesEnum = {
     21   DEVICE_NONE: {
     22     cssState: 'device-detected-none',
     23   },
     24   DEVICE_USB: {
     25     cssState: 'device-detected-usb warning',
     26   },
     27   DEVICE_SD: {
     28     cssState: 'device-detected-sd warning',
     29   },
     30   DEVICE_MUL: {
     31     cssState: 'device-detected-mul warning',
     32   },
     33   ERROR_NO_NETWORK: {
     34     cssState: 'warning-no-conf',
     35   },
     36   ERROR_DEVICE_TOO_SMALL: {
     37     cssState: 'warning-no-conf',
     38   },
     39   PROGRESS_DOWNLOAD: {
     40     cssState: 'progress progress-canceble',
     41   },
     42   PROGRESS_UNZIP: {
     43     cssState: 'progress progress-canceble',
     44   },
     45   PROGRESS_BURN: {
     46     cssState: 'progress',
     47   },
     48   FAIL: {
     49     cssState: 'error',
     50   },
     51   SUCCESS: {
     52     cssState: 'success',
     53   },
     54 };
     55 
     56 State.prototype = {
     57   /**
     58    * Sets the state strings.
     59    * @param {Object} strings Localized state strings.
     60    */
     61   setStrings: function(strings) {
     62     State.StatesEnum.DEVICE_NONE.statusText =
     63         strings.getString('statusDevicesNone');
     64     State.StatesEnum.DEVICE_NONE.warningText =
     65         strings.getString('warningDevicesNone');
     66     State.StatesEnum.DEVICE_USB.statusText =
     67       strings.getString('statusDeviceUSB');
     68     State.StatesEnum.DEVICE_SD.statusText = strings.getString('statusDeviceSD');
     69     State.StatesEnum.DEVICE_MUL.statusText =
     70         strings.getString('statusDevicesMultiple');
     71     State.StatesEnum.ERROR_NO_NETWORK.statusText =
     72         strings.getString('statusNoConnection');
     73     State.StatesEnum.ERROR_NO_NETWORK.warningText =
     74         strings.getString('warningNoConnection');
     75     State.StatesEnum.ERROR_DEVICE_TOO_SMALL.statusText =
     76         strings.getString('statusNoSpace');
     77     State.StatesEnum.PROGRESS_DOWNLOAD.statusText =
     78         strings.getString('statusDownloading');
     79     State.StatesEnum.PROGRESS_UNZIP.statusText =
     80         strings.getString('statusUnzip');
     81     State.StatesEnum.PROGRESS_BURN.statusText = strings.getString('statusBurn');
     82     State.StatesEnum.FAIL.statusText = strings.getString('statusError');
     83     State.StatesEnum.SUCCESS.statusText = strings.getString('statusSuccess');
     84     State.StatesEnum.SUCCESS.warningText = strings.getString('warningSuccess');
     85   },
     86 
     87   /**
     88    * Changes the current state to new state.
     89    * @param {Object} newState Specifies the new state object.
     90    */
     91   changeState: function(newState) {
     92     if (newState == this.state)
     93       return;
     94     this.state = newState;
     95 
     96     $('main-content').className = this.state.cssState;
     97 
     98     $('status-text').textContent = this.state.statusText;
     99 
    100     if (newState.warningText)
    101       $('warning-text').textContent = this.state.warningText;
    102 
    103     if (this.isInitialState() && this.state != State.StatesEnum.DEVICE_NONE) {
    104       $('warning-button').textContent = loadTimeData.getString('confirmButton');
    105     } else if (this.state == State.StatesEnum.FAIL) {
    106       $('warning-button').textContent =
    107           loadTimeData.getString('retryButton');
    108     }
    109   },
    110 
    111   /**
    112    * Reset to initial state.
    113    * @param {Array} devices Array of device information.
    114    */
    115   gotoInitialState: function(devices) {
    116     if (devices.length == 0) {
    117       this.changeState(State.StatesEnum.DEVICE_NONE);
    118     } else if (devices.length == 1) {
    119       // If a device type is not specified for some reason, we should
    120       // default to display a USB device.
    121       var initialState = State.StatesEnum.DEVICE_USB;
    122       if (devices[0].type == 'sd')
    123         initialState = State.StatesEnum.DEVICE_SD;
    124       this.changeState(initialState);
    125     } else {
    126       this.changeState(State.StatesEnum.DEVICE_MUL);
    127     }
    128   },
    129 
    130   /**
    131    * Returns true if the device is in initial state.
    132    * @return {boolean} True if the device is in initial state else false.
    133    */
    134   isInitialState: function() {
    135     return this.state == State.StatesEnum.DEVICE_NONE ||
    136            this.state == State.StatesEnum.DEVICE_USB ||
    137            this.state == State.StatesEnum.DEVICE_SD ||
    138            this.state == State.StatesEnum.DEVICE_MUL;
    139   },
    140 
    141   /**
    142    * Returns true if device state matches the given state name.
    143    * @param {string} stateName Given state name.
    144    * @return {boolean} True if the device state matches the given state name.
    145    */
    146   equals: function(stateName) {
    147     return this.state == stateName;
    148   }
    149 };
    150 
    151 /**
    152  * Class that keeps track of available devices.
    153  * @constructor
    154  */
    155 function DeviceSelection() {
    156   this.selectedDevice = undefined;
    157   this.devices = [];
    158 }
    159 
    160 DeviceSelection.prototype = {
    161   /**
    162    * Shows the currently selected device.
    163    */
    164   showDeviceSelection: function() {
    165     if (this.devices.length == 0) {
    166       this.selectedDevice = undefined;
    167     } else {
    168       this.selectDevice(this.devices[0].devicePath);
    169     }
    170   },
    171 
    172   /**
    173    * Handles device selected event.
    174    * @param {string} label Device label.
    175    * @param {string} filePath File path.
    176    * @param {string} devicePath Selected device path.
    177    */
    178   onDeviceSelected: function(label, filePath, devicePath) {
    179     $('warning-button').onclick =
    180         browserBridge.sendBurnImageMessage.bind(browserBridge, filePath,
    181             devicePath);
    182 
    183     this.selectedDevice = devicePath;
    184 
    185     $('warning-text').textContent =
    186         loadTimeData.getStringF('warningDevices', label);
    187   },
    188 
    189   /**
    190    * Selects the specified device based on the specified path.
    191    * @param {string} path Device path.
    192    */
    193   selectDevice: function(path) {
    194     var element = $('radio-' + path);
    195     element.checked = true;
    196     element.onclick.apply(element);
    197   },
    198 
    199   /**
    200    * Creates a new device element.
    201    * @param {Object} device Specifies new device information.
    202    * @return {HTMLLIElement} New device element.
    203    */
    204   createNewDeviceElement: function(device) {
    205     var element = document.createElement('li');
    206     var radioButton = document.createElement('input');
    207     radioButton.type = 'radio';
    208     radioButton.name = 'device';
    209     radioButton.value = device.label;
    210     radioButton.id = 'radio-' + device.devicePath;
    211     radioButton.className = 'float-start';
    212     var deviceLabelText = document.createElement('p');
    213     deviceLabelText.textContent = device.label;
    214     deviceLabelText.className = 'select-option float-start';
    215     var newLine = document.createElement('div');
    216     newLine.className = 'new-line';
    217     element.appendChild(radioButton);
    218     element.appendChild(deviceLabelText);
    219     element.appendChild(newLine);
    220     element.id = 'select-' + device.devicePath;
    221     element.className = 'selection-element';
    222     radioButton.onclick = this.onDeviceSelected.bind(this,
    223         device.label, device.filePath, device.devicePath);
    224     return element;
    225   },
    226 
    227   /**
    228    * Updates the list of selected devices.
    229    * @param {Array} devices List of devices.
    230    */
    231   devicesUpdated: function(newDevices) {
    232     this.devices = newDevices;
    233     var selectListDOM = $('device-selection');
    234     selectListDOM.innerHTML = '';
    235     if (this.devices.length > 0) {
    236       for (var i = 0; i < this.devices.length; i++) {
    237         var element = this.createNewDeviceElement(this.devices[i]);
    238         selectListDOM.appendChild(element);
    239       }
    240       this.selectDevice(this.devices[0].devicePath);
    241     } else {
    242       this.selectedDevice = undefined;
    243     }
    244   },
    245 
    246   /**
    247    * Handles device added event.
    248    * @param {Object} device Device information.
    249    * @param {boolean} allowSelect True to update the selected device info.
    250    */
    251   deviceAdded: function(device, allowSelect) {
    252     this.devices.push(device);
    253     var selectListDOM = $('device-selection');
    254     selectListDOM.appendChild(this.createNewDeviceElement(device));
    255     if (allowSelect && this.devices.length == 1)
    256       this.selectDevice(device.devicePath);
    257   },
    258 
    259   /**
    260    * Handles device removed event.
    261    * @param {string} devicePath Device path to be removed.
    262    * @param {boolean} allowSelect True to update the selected device info.
    263    */
    264   deviceRemoved: function(devicePath, allowSelect) {
    265     device = this.findDevice(devicePath);
    266     if (!device)
    267       return;
    268     this.devices.splice(this.devices.indexOf(device), 1);
    269 
    270     // Remove device selection element from DOM.
    271     var deviceSelectElement = $('select-' + devicePath);
    272     deviceSelectElement.parentNode.removeChild(deviceSelectElement);
    273 
    274     // Update selected device element.
    275     if (allowSelect) {
    276       if (this.devices.length > 0) {
    277         if (this.selectedDevice == devicePath)
    278           this.selectDevice(this.devices[0].devicePath);
    279       } else {
    280         this.selectedDevice = undefined;
    281       }
    282     }
    283   },
    284 
    285   /**
    286    * Finds device with given device path property.
    287    * @param {string} devicePath Device path of device to find.
    288    * @return {Object} Matching device information or undefined if not found.
    289    */
    290   findDevice: function(devicePath) {
    291     for (var i = 0; i < this.devices.length; ++i) {
    292       if (this.devices[i].devicePath == devicePath) {
    293         return this.devices[i];
    294       }
    295     }
    296     return undefined;
    297   }
    298 };
    299 
    300 /**
    301  * Class that handles communication with chrome.
    302  * @constructor
    303  */
    304 function BrowserBridge() {
    305   this.currentState = new State(loadTimeData);
    306   this.deviceSelection = new DeviceSelection();
    307   // We will use these often so it makes sence making them class members to
    308   // avoid frequent document.getElementById calls.
    309   this.progressElement = $('progress-div');
    310   this.progressText = $('progress-text');
    311   this.progressTimeLeftText = $('pending-time');
    312 }
    313 
    314 BrowserBridge.prototype = {
    315   sendCancelMessage: function() {
    316     chrome.send('cancelBurnImage');
    317   },
    318 
    319   sendGetDevicesMessage: function() {
    320     chrome.send('getDevices');
    321   },
    322 
    323   sendWebuiInitializedMessage: function() {
    324     chrome.send('webuiInitialized');
    325   },
    326 
    327   /**
    328    * Sends the burn image message to c++ code.
    329    * @param {string} filePath Specifies the file path.
    330    * @param {string} devicePath Specifies the device path.
    331    */
    332   sendBurnImageMessage: function(filePath, devicePath) {
    333     chrome.send('burnImage', [devicePath, filePath]);
    334   },
    335 
    336   reportSuccess: function() {
    337     this.currentState.changeState(State.StatesEnum.SUCCESS);
    338   },
    339 
    340   /**
    341    * Update the device state to report a failure and display an error message to
    342    * the user.
    343    * @param {string} errorMessage Specifies the warning text message.
    344    */
    345   reportFail: function(errorMessage) {
    346     this.currentState.changeState(State.StatesEnum.FAIL);
    347     $('warning-text').textContent = errorMessage;
    348     $('warning-button').onclick = this.onBurnRetry.bind(this);
    349   },
    350 
    351   /**
    352    * Handles device added event.
    353    * @param {Object} device Device information.
    354    */
    355   deviceAdded: function(device) {
    356     var inInitialState = this.currentState.isInitialState();
    357     this.deviceSelection.deviceAdded(device, inInitialState);
    358     if (inInitialState)
    359       this.currentState.gotoInitialState(this.deviceSelection.devices);
    360   },
    361 
    362   /**
    363    * Handles device removed event.
    364    * @param {string} devicePath Device path to be removed.
    365    */
    366   deviceRemoved: function(devicePath) {
    367     var inInitialState = this.currentState.isInitialState();
    368     this.deviceSelection.deviceRemoved(devicePath, inInitialState);
    369     if (inInitialState)
    370       this.currentState.gotoInitialState(this.deviceSelection.devices);
    371   },
    372 
    373   /**
    374    * Gets device callbacks and update the current state.
    375    * @param {Array} devices List of devices.
    376    */
    377   getDevicesCallback: function(devices) {
    378     this.deviceSelection.devicesUpdated(devices);
    379     this.currentState.gotoInitialState(this.deviceSelection.devices);
    380     this.sendWebuiInitializedMessage();
    381   },
    382 
    383   /**
    384    *  Updates the progress information based on the signal received.
    385    *  @param {Object} updateSignal Specifies the signal information.
    386    */
    387   updateProgress: function(updateSignal) {
    388     if (updateSignal.progressType == 'download' &&
    389         !this.currentState.equals(State.StatesEnum.PROGRESS_DOWNLOAD)) {
    390       this.currentState.changeState(State.StatesEnum.PROGRESS_DOWNLOAD);
    391     } else if (updateSignal.progressType == 'unzip' &&
    392         !this.currentState.equals(State.StatesEnum.PROGRESS_UNZIP)) {
    393       this.currentState.changeState(State.StatesEnum.PROGRESS_UNZIP);
    394     } else if (updateSignal.progressType == 'burn' &&
    395         !this.currentState.equals(State.StatesEnum.PROGRESS_BURN)) {
    396       this.currentState.changeState(State.StatesEnum.PROGRESS_BURN);
    397     }
    398 
    399     if (!(updateSignal.amountTotal > 0)) {
    400       this.progressElement.removeAttribute('value');
    401     } else {
    402       this.progressElement.value = updateSignal.amountFinished;
    403       this.progressElement.max = updateSignal.amountTotal;
    404     }
    405 
    406     this.progressText.textContent = updateSignal.progressText;
    407     this.progressTimeLeftText.textContent = updateSignal.timeLeftText;
    408   },
    409 
    410   reportNoNetwork: function() {
    411     this.currentState.changeState(State.StatesEnum.ERROR_NO_NETWORK);
    412   },
    413 
    414   reportNetworkDetected: function() {
    415     if (this.currentState.equals(State.StatesEnum.ERROR_NO_NETWORK)) {
    416       this.deviceSelection.showDeviceSelection();
    417       this.currentState.gotoInitialState(this.deviceSelection.devices);
    418     }
    419   },
    420 
    421   /**
    422    *  Updates the current state to report device too small error.
    423    *  @param {number} deviceSize Received device size.
    424    */
    425   reportDeviceTooSmall: function(deviceSize) {
    426     this.currentState.changeState(State.StatesEnum.ERROR_DEVICE_TOO_SMALL);
    427     $('warning-text').textContent =
    428         loadTimeData.getStringF('warningNoSpace', deviceSize);
    429   },
    430 
    431   /**
    432    * Processes click on 'Retry' button in FAIL state.
    433    */
    434   onBurnRetry: function() {
    435     this.deviceSelection.showDeviceSelection();
    436     this.currentState.gotoInitialState(this.deviceSelection.devices);
    437   }
    438 };
    439 
    440 document.addEventListener('DOMContentLoaded', function() {
    441   browserBridge = new BrowserBridge();
    442 
    443   $('cancel-button').onclick =
    444       browserBridge.sendCancelMessage.bind(browserBridge);
    445   browserBridge.sendGetDevicesMessage();
    446 });
    447