• 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'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