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'use strict'; 6 7/** 8 * Progress center at the background page. 9 * @constructor 10 */ 11var ProgressCenter = function() { 12 /** 13 * Current items managed by the progress center. 14 * @type {Array.<ProgressItem>} 15 * @private 16 */ 17 this.items_ = []; 18 19 /** 20 * Map of progress ID and notification ID. 21 * @type {Object.<string, string>} 22 * @private 23 */ 24 this.notifications_ = new ProgressCenter.Notifications_( 25 this.requestCancel.bind(this)); 26 27 /** 28 * List of panel UI managed by the progress center. 29 * @type {Array.<ProgressCenterPanel>} 30 * @private 31 */ 32 this.panels_ = []; 33 34 Object.seal(this); 35}; 36 37/** 38 * Notifications created by progress center. 39 * @param {function(string)} cancelCallback Callback to notify the progress 40 * center of cancel operation. 41 * @constructor 42 * @private 43 */ 44ProgressCenter.Notifications_ = function(cancelCallback) { 45 /** 46 * ID set of notifications that is progressing now. 47 * @type {Object.<string, ProgressCenter.Notifications_.NotificationState_>} 48 * @private 49 */ 50 this.ids_ = {}; 51 52 /** 53 * Async queue. 54 * @type {AsyncUtil.Queue} 55 * @private 56 */ 57 this.queue_ = new AsyncUtil.Queue(); 58 59 /** 60 * Callback to notify the progress center of cancel operation. 61 * @type {function(string)} 62 * @private 63 */ 64 this.cancelCallback_ = cancelCallback; 65 66 chrome.notifications.onButtonClicked.addListener( 67 this.onButtonClicked_.bind(this)); 68 chrome.notifications.onClosed.addListener(this.onClosed_.bind(this)); 69 70 Object.seal(this); 71}; 72 73/** 74 * State of notification. 75 * @enum {string} 76 * @const 77 * @private 78 */ 79ProgressCenter.Notifications_.NotificationState_ = Object.freeze({ 80 VISIBLE: 'visible', 81 DISMISSED: 'dismissed' 82}); 83 84/** 85 * Updates the notification according to the item. 86 * @param {ProgressCenterItem} item Item to contain new information. 87 * @param {boolean} newItemAcceptable Whether to accept new item or not. 88 */ 89ProgressCenter.Notifications_.prototype.updateItem = function( 90 item, newItemAcceptable) { 91 var NotificationState = ProgressCenter.Notifications_.NotificationState_; 92 var newlyAdded = !(item.id in this.ids_); 93 94 // If new item is not acceptable, just return. 95 if (newlyAdded && !newItemAcceptable) 96 return; 97 98 // Update the ID map and return if we does not show a notification for the 99 // item. 100 if (item.state === ProgressItemState.PROGRESSING) { 101 if (newlyAdded) 102 this.ids_[item.id] = NotificationState.VISIBLE; 103 else if (this.ids_[item.id] === NotificationState.DISMISSED) 104 return; 105 } else { 106 // This notification is no longer tracked. 107 var previousState = this.ids_[item.id]; 108 delete this.ids_[item.id]; 109 // Clear notifications for complete or canceled items. 110 if (item.state === ProgressItemState.CANCELED || 111 item.state === ProgressItemState.COMPLETED) { 112 if (previousState === NotificationState.VISIBLE) { 113 this.queue_.run(function(proceed) { 114 chrome.notifications.clear(item.id, proceed); 115 }); 116 } 117 return; 118 } 119 } 120 121 // Create/update the notification with the item. 122 this.queue_.run(function(proceed) { 123 var params = { 124 title: chrome.runtime.getManifest().name, 125 iconUrl: chrome.runtime.getURL('/common/images/icon96.png'), 126 type: item.state === ProgressItemState.PROGRESSING ? 'progress' : 'basic', 127 message: item.message, 128 buttons: item.cancelable ? [{title: str('CANCEL_LABEL')}] : undefined, 129 progress: item.state === ProgressItemState.PROGRESSING ? 130 item.progressRateInPercent : undefined, 131 priority: (item.state === ProgressItemState.ERROR || !item.quiet) ? 0 : -1 132 }; 133 if (newlyAdded) 134 chrome.notifications.create(item.id, params, proceed); 135 else 136 chrome.notifications.update(item.id, params, proceed); 137 }.bind(this)); 138}; 139 140/** 141 * Handles cancel button click. 142 * @param {string} id Item ID. 143 * @private 144 */ 145ProgressCenter.Notifications_.prototype.onButtonClicked_ = function(id) { 146 if (id in this.ids_) 147 this.cancelCallback_(id); 148}; 149 150/** 151 * Handles notification close. 152 * @param {string} id Item ID. 153 * @private 154 */ 155ProgressCenter.Notifications_.prototype.onClosed_ = function(id) { 156 if (id in this.ids_) 157 this.ids_[id] = ProgressCenter.Notifications_.NotificationState_.DISMISSED; 158}; 159 160/** 161 * Updates the item in the progress center. 162 * If the item has a new ID, the item is added to the item list. 163 * 164 * @param {ProgressCenterItem} item Updated item. 165 */ 166ProgressCenter.prototype.updateItem = function(item) { 167 // Update item. 168 var index = this.getItemIndex_(item.id); 169 if (item.state === ProgressItemState.PROGRESSING) { 170 if (index === -1) 171 this.items_.push(item); 172 else 173 this.items_[index] = item; 174 } else { 175 if (index !== -1) 176 this.items_.splice(index, 1); 177 } 178 179 // Update panels. 180 for (var i = 0; i < this.panels_.length; i++) { 181 this.panels_[i].updateItem(item); 182 } 183 184 // Update notifications. 185 this.notifications_.updateItem(item, !this.panels_.length); 186}; 187 188/** 189 * Requests to cancel the progress item. 190 * @param {string} id Progress ID to be requested to cancel. 191 */ 192ProgressCenter.prototype.requestCancel = function(id) { 193 var item = this.getItemById(id); 194 if (item && item.cancelCallback) 195 item.cancelCallback(); 196}; 197 198/** 199 * Adds a panel UI to the notification center. 200 * @param {ProgressCenterPanel} panel Panel UI. 201 */ 202ProgressCenter.prototype.addPanel = function(panel) { 203 if (this.panels_.indexOf(panel) !== -1) 204 return; 205 206 // Update the panel list. 207 this.panels_.push(panel); 208 209 // Set the current items. 210 for (var i = 0; i < this.items_.length; i++) 211 panel.updateItem(this.items_[i]); 212 213 // Register the cancel callback. 214 panel.cancelCallback = this.requestCancel.bind(this); 215}; 216 217/** 218 * Removes a panel UI from the notification center. 219 * @param {ProgressCenterPanel} panel Panel UI. 220 */ 221ProgressCenter.prototype.removePanel = function(panel) { 222 var index = this.panels_.indexOf(panel); 223 if (index === -1) 224 return; 225 226 this.panels_.splice(index, 1); 227 panel.cancelCallback = null; 228 229 // If there is no panel, show the notifications. 230 if (this.panels_.length) 231 return; 232 for (var i = 0; i < this.items_.length; i++) 233 this.notifications_.updateItem(this.items_[i], true); 234}; 235 236/** 237 * Obtains item by ID. 238 * @param {string} id ID of progress item. 239 * @return {ProgressCenterItem} Progress center item having the specified 240 * ID. Null if the item is not found. 241 */ 242ProgressCenter.prototype.getItemById = function(id) { 243 return this.items_[this.getItemIndex_(id)]; 244}; 245 246/** 247 * Obtains item index that have the specifying ID. 248 * @param {string} id Item ID. 249 * @return {number} Item index. Returns -1 If the item is not found. 250 * @private 251 */ 252ProgressCenter.prototype.getItemIndex_ = function(id) { 253 for (var i = 0; i < this.items_.length; i++) { 254 if (this.items_[i].id === id) 255 return i; 256 } 257 return -1; 258}; 259