Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(525)

Side by Side Diff: chrome/test/data/extensions/api_test/notifications/galore/app/controller.js

Issue 315053006: Refactor Notifications Galore to simplify, amke more hackable and add 'recording'. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: cr feedback Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | chrome/test/data/extensions/api_test/notifications/galore/app/data/27.0.0.0.json » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 var Galore = Galore || {}; 5 var STOPPED = "Stopped";
6 6 var RECORDING = "Recording";
7 Galore.controller = { 7 var PAUSED_RECORDING = "Recording Paused";
8 /** @constructor */ 8 var PAUSED_PLAYING = "Playing Paused";
9 create: function() { 9 var PLAYING = "Playing";
10 var controller = Object.create(this); 10
11 controller.api = chrome; 11 var recordingState = STOPPED;
12 controller.counter = 0; 12
13 return controller; 13 // Timestamp when current segment started.
14 }, 14 var segmentStart;
15 15 // Segment duration accumulated before pause button was hit.
16 createWindow: function() { 16 var pausedDuration;
17 chrome.storage.sync.get('settings', this.onSettingsFetched_.bind(this)); 17 // The array of segments, with delay and action.
18 }, 18 var recordingList;
19 19 // When this timer fires, the next segment from recordingList should be played.
20 /** @private */ 20 var playingTimer;
21 onSettingsFetched_: function(items) { 21 var currentSegmentIndex;
22 var request = new XMLHttpRequest(); 22 // A set of web Notifications - used to delete them during playback by id.
23 var settings = items.settings || {}; 23 var webNotifications = {};
24 var source = settings.data || '/data/' + this.getDataVersion_(); 24
25 request.open('GET', source, true); 25 var recorderButtons = [ "play", "record", "pause", "stop"];
26 request.responseType = 'text'; 26 var recorderButtonStates = [
27 request.onload = this.onDataFetched_.bind(this, settings, request); 27 { state: STOPPED, enabled: "play record" },
28 request.send(); 28 { state: RECORDING, enabled: "pause stop" },
29 }, 29 { state: PAUSED_RECORDING, enabled: "record stop" },
30 30 { state: PAUSED_PLAYING, enabled: "play stop" },
31 /** @private */ 31 { state: PLAYING, enabled: "pause stop" }
32 onDataFetched_: function(settings, request) { 32 ];
33 var count = 0; 33
34 var data = JSON.parse(request.response); 34 // This function forms 2 selector lists - one that includes enabled buttons
35 // and one that includes disabled ones. Then it applies "disabled" attribute to
36 // corresponding sets of buttons.
37 function updateButtonsState() {
38 recorderButtonStates.map(function(entry) {
39 if (entry.state != recordingState)
40 return;
41 // Found entry with current recorder state. Now compute the sets
42 // of enabled/disabled buttons.
43 // Copy a list of all buttons.
44 var disabled = recorderButtons.slice(0);
45 // Get an array of enabled buttons for the state.
46 var enabled = entry.enabled.split(" ");
47 // Remove enabled buttons from disabled list, prefix them with "#" so they
48 // form proper id selectors.
49 for (var i = 0; i < enabled.length; i++) {
50 disabled.splice(disabled.indexOf(enabled[i]), 1);
51 enabled[i] = "#" + enabled[i];
52 }
53 // Prefix remaining disabled ids to form proper id selectors.
54 for (var i = 0; i < disabled.length; i++) {
55 disabled[i] = "#" + disabled[i];
56 }
57 getElements(disabled.join(", ")).forEach(function(element) {
58 element.setAttribute("disabled", "true")
59 })
60 getElements(enabled.join(", ")).forEach(function(element) {
61 element.removeAttribute("disabled")
62 })
63 })
64 }
65
66
67 function setRecordingState(newState) {
68 setRecorderStatusText(newState);
69 recordingState = newState;
70 updateButtonsState();
71 }
72
73 function updateRecordingStats(context) {
74 var length = 0;
75 var segmentCnt = 0;
76 recordingList.slice(currentSegmentIndex).forEach(function(segment) {
77 length += segment.delay || 0;
78 segmentCnt++;
79 })
80 updateRecordingStatsDisplay(context + ": " + (segmentCnt-1) + " segments, " +
81 Math.floor(length/1000) + " seconds.");
82 }
83
84 function loadRecording() {
85 chrome.storage.local.get("recording", function(items) {
86 recordingList = JSON.parse(items["recording"] || "[]");
87 setRecordingState(STOPPED);
88 updateRecordingStats("Loaded record");
89 })
90 }
91
92 function finalizeRecording() {
93 chrome.storage.local.set({"recording": JSON.stringify(recordingList)});
94 updateRecordingStats("Recorded");
95 }
96
97 function setPreviousSegmentDuration() {
98 var now = new Date().getTime();
99 var delay = now - segmentStart;
100 segmentStart = now;
101 recordingList[recordingList.length - 1].delay = delay;
102 }
103
104 function recordCreate(kind, id, options) {
105 if (recordingState != RECORDING)
106 return;
107 setPreviousSegmentDuration();
108 recordingList.push({ type: "create", kind: kind, id: id, options: options });
109 updateRecordingStats("Recording");
110 }
111
112 function recordDelete(kind, id) {
113 if (recordingState != RECORDING)
114 return;
115 setPreviousSegmentDuration();
116 recordingList.push({ type: "delete", kind: kind, id: id });
117 updateRecordingStats("Recording");
118 }
119
120 function startPlaying() {
121 if (recordingList.length < 2)
122 return false;
123
124 setRecordingState(PLAYING);
125
126 if (playingTimer)
127 clearTimeout(playingTimer);
128
129 webNotifications = {};
130 currentSegmentIndex = 0;
131 playingTimer = setTimeout(playNextSegment,
132 recordingList[currentSegmentIndex].delay);
133 updateRecordingStats("Playing");
134 }
135
136 function playNextSegment() {
137 currentSegmentIndex++;
138 var segment = recordingList[currentSegmentIndex];
139 if (!segment) {
140 stopPlaying();
141 return;
142 }
143
144 if (segment.type == "create") {
145 createNotificationForPlay(segment.kind, segment.id, segment.options);
146 } else { // type == "delete"
147 deleteNotificationForPlay(segment.kind, segment.id);
148 }
149 playingTimer = setTimeout(playNextSegment,
150 recordingList[currentSegmentIndex].delay);
151 segmentStart = new Date().getTime();
152 updateRecordingStats("Playing");
153 }
154
155 function deleteNotificationForPlay(kind, id) {
156 if (kind == 'web') {
157 webNotifications[id].close();
158 } else {
159 chrome.notifications.clear(id, function(wasClosed) {
160 // nothing to do
161 });
162 }
163 }
164
165 function createNotificationForPlay(kind, id, options) {
166 if (kind == 'web') {
167 webNotifications[id] = createWebNotification(id, options);
168 } else {
169 var type = options.type;
170 var priority = options.priority;
171 createRichNotification(id, type, priority, options);
172 }
173 }
174 function stopPlaying() {
175 currentSegmentIndex = 0;
176 clearTimeout(playingTimer);
177 updateRecordingStats("Record");
178 setRecordingState(STOPPED);
179 }
180
181 function pausePlaying() {
182 clearTimeout(playingTimer);
183 pausedDuration = new Date().getTime() - segmentStart;
184 setRecordingState(PAUSED_PLAYING);
185 }
186
187 function unpausePlaying() {
188 var remainingInSegment =
189 recordingList[currentSegmentIndex].delay - pausedDuration;
190 if (remainingInSegment < 0)
191 remainingInSegment = 0;
192 playingTimer = setTimeout(playNextSegment, remainingInSegment);
193 segmentStart = new Date().getTime() - pausedDuration;
194 }
195
196 function onRecord() {
197 if (recordingState == STOPPED) {
198 segmentStart = new Date().getTime();
199 pausedDuration = 0;
200 // This item is only needed to keep a duration of the delay between start
201 // and first action.
202 recordingList = [ { type:"start" } ];
203 } else if (recordingState == PAUSED_RECORDING) {
204 segmentStart = new Date().getTime() - pausedDuration;
205 pausedDuration = 0;
206 } else {
207 return;
208 }
209 updateRecordingStats("Recording");
210 setRecordingState(RECORDING);
211 }
212
213 function pauseRecording() {
214 pausedDuration = new Date().getTime() - segmentStart;
215 segmentStart = 0;
216 setRecordingState(PAUSED_RECORDING);
217 }
218
219 function onPause() {
220 if (recordingState == RECORDING) {
221 pauseRecording();
222 } else if (recordingState == PLAYING) {
223 pausePlaying();
224 } else {
225 return;
226 }
227 }
228
229 function onStop() {
230 switch (recordingState) {
231 case PAUSED_RECORDING:
232 segmentStart = new Date().getTime() - pausedDuration;
233 // fall through
234 case RECORDING:
235 finalizeRecording();
236 break;
237 case PLAYING:
238 case PAUSED_PLAYING:
239 stopPlaying();
240 break;
241 }
242 setRecordingState(STOPPED);
243 }
244
245 function onPlay() {
246 if (recordingState == STOPPED) {
247 if (!startPlaying())
248 return;
249 } else if (recordingState == PAUSED_PLAYING) {
250 unpausePlaying();
251 }
252 setRecordingState(PLAYING);
253 }
254
255 function createWindow() {
256 chrome.storage.local.get('settings', onSettingsFetched);
257 }
258
259 function onSettingsFetched(items) {
260 settings = items.settings || settings;
261 var request = new XMLHttpRequest();
262 var source = '/data/data.json';
263 request.open('GET', source, true);
264 request.responseType = 'text';
265 request.onload = onDataFetched;
266 request.send();
267 }
268
269 function onDataFetched() {
270 var data = JSON.parse(this.response);
271 createAppWindow(function() {
272 // Create notification buttons.
35 data.forEach(function(section) { 273 data.forEach(function(section) {
274 var type = section.notificationType;
36 (section.notificationOptions || []).forEach(function(options) { 275 (section.notificationOptions || []).forEach(function(options) {
37 ++count; 276 addNotificationButton(section.sectionName,
38 this.fetchImages_(options, function() { 277 options.title,
39 if (--count == 0) 278 options.iconUrl,
40 this.onImagesFetched_(settings, data); 279 function() { createNotification(type, options) });
41 }.bind(this)); 280 });
42 }, this);
43 }, this);
44 },
45
46 /** @private */
47 onImagesFetched_: function(settings, data) {
48 this.settings = settings;
49 this.view = Galore.view.create(this.settings, function() {
50 // Create buttons.
51 data.forEach(function(section) {
52 var defaults = section.globals || data[0].globals;
53 var type = section.notificationType;
54 (section.notificationOptions || []).forEach(function(options) {
55 var defaulted = this.getDefaultedOptions_(options, defaults);
56 var create = this.createNotification_.bind(this, type, defaulted);
57 this.view.addNotificationButton(section.sectionName,
58 defaulted.title,
59 defaulted.iconUrl,
60 create);
61 }, this);
62 }, this);
63 // Set the API entry point and use it to set event listeners.
64 this.api = this.getApi_(data);
65 if (this.api)
66 this.addListeners_(this.api, data[0].events);
67 // Display the completed and ready window.
68 this.view.showWindow();
69 }.bind(this), this.onSettingsChange_.bind(this));
70 },
71
72 /** @private */
73 fetchImages_: function(options, onFetched) {
74 var count = 0;
75 var replacements = {};
76 this.mapStrings_(options, function(string) {
77 if (string.indexOf("/images/") == 0 || string.search(/https?:\//) == 0) {
78 ++count;
79 this.fetchImage_(string, function(url) {
80 replacements[string] = url;
81 if (--count == 0) {
82 this.mapStrings_(options, function(string) {
83 return replacements[string] || string;
84 });
85 onFetched.call(this, options);
86 }
87 });
88 }
89 }); 281 });
90 }, 282 loadRecording();
91 283 addListeners();
92 /** @private */ 284 showWindow();
93 fetchImage_: function(url, onFetched) { 285 });
94 var request = new XMLHttpRequest(); 286 }
95 request.open('GET', url, true); 287
96 request.responseType = 'blob'; 288 function onSettingsChange(settings) {
97 request.onload = function() { 289 chrome.storage.local.set({settings: settings});
98 var url = window.URL.createObjectURL(request.response); 290 }
99 onFetched.call(this, url); 291
100 }.bind(this); 292 function createNotification(type, options) {
101 request.send(); 293 var id = getNextId();
102 }, 294 var priority = Number(settings.priority || 0);
103 295 if (type == 'web')
104 /** @private */ 296 createWebNotification(id, options);
105 onSettingsChange_: function(settings) { 297 else
106 this.settings = settings; 298 createRichNotification(id, type, priority, options);
107 chrome.storage.sync.set({settings: this.settings}); 299 }
108 }, 300
109 301 function createWebNotification(id, options) {
110 /** @private */ 302 var iconUrl = options.iconUrl;
111 createNotification_: function(type, options) { 303 var title = options.title;
112 var id = this.getNextId_(); 304 var message = options.message;
113 var priority = Number(this.settings.priority || 0); 305 var n = new Notification(title, {
114 var expanded = this.getExpandedOptions_(options, id, type, priority); 306 body: message,
115 if (type == 'webkit') 307 icon: iconUrl,
116 this.createWebKitNotification_(expanded); 308 tag: id
117 else 309 });
118 this.createRichNotification_(expanded, id, type, priority); 310 n.onshow = function() { logEvent('WebNotification #' + id + ': onshow'); }
119 }, 311 n.onclick = function() { logEvent('WebNotification #' + id + ': onclick'); }
120 312 n.onclose = function() {
121 /** @private */ 313 logEvent('WebNotification #' + id + ': onclose');
122 createWebKitNotification_: function(options) { 314 recordDelete('web', id);
123 var iconUrl = options.iconUrl; 315 }
124 var title = options.title; 316 logCreate('Web', id, 'title: "' + title + '"');
125 var message = options.message; 317 recordCreate('web', id, options);
126 new Notification(title, { 318 return n;
127 body: message, 319 }
128 icon: iconUrl 320
129 }); 321 function createRichNotification(id, type, priority, options) {
130 this.handleEvent_('create', '?', 'title: "' + title + '"'); 322 options["type"] = type;
131 }, 323 options["priority"] = priority;
132 324 chrome.notifications.create(id, options, function() {
133 /** @private */ 325 var argument1 = 'type: "' + type + '"';
134 createRichNotification_: function(options, id, type, priority) { 326 var argument2 = 'priority: ' + priority;
135 this.api.create(id, options, function() { 327 var argument3 = 'title: "' + options.title + '"';
136 var argument1 = 'type: "' + type + '"'; 328 logCreate('Rich', id, argument1, argument2, argument3);
137 var argument2 = 'priority: ' + priority; 329 });
138 var argument3 = 'title: "' + options.title + '"'; 330 recordCreate('rich', id, options);
139 this.handleEvent_('create', id, argument1, argument2, argument3); 331 }
140 }.bind(this)); 332
141 }, 333 var counter = 0;
142 334 function getNextId() {
143 /** @private */ 335 return String(counter++);
144 getNextId_: function() { 336 }
145 this.counter += 1; 337
146 return String(this.counter); 338 function addListeners() {
147 }, 339 chrome.notifications.onClosed.addListener(onClosed);
148 340 chrome.notifications.onClicked.addListener(onClicked);
149 /** @private */ 341 chrome.notifications.onButtonClicked.addListener(onButtonClicked);
150 getDefaultedOptions_: function(options, defaults) { 342 }
151 var defaulted = this.deepCopy_(options); 343
152 Object.keys(defaults || {}).forEach(function (key) { 344 function logCreate(kind, id, var_args) {
153 defaulted[key] = options[key] || defaults[key]; 345 logEvent(kind + ' Notification #' + id + ': created ' + '(' +
154 }); 346 Array.prototype.slice.call(arguments, 2).join(', ') + ')');
155 return defaulted; 347 }
156 }, 348
157 349 function onClosed(id) {
158 /** @private */ 350 logEvent('Notification #' + id + ': onClosed');
159 getExpandedOptions_: function(options, id, type, priority) { 351 recordDelete('rich', id);
160 var expanded = this.deepCopy_(options); 352 }
161 return this.mapStrings_(expanded, function(string) { 353
162 return this.getExpandedOption_(string, id, type, priority); 354 function onClicked(id) {
163 }, this); 355 logEvent('Notification #' + id + ': onClicked');
164 }, 356 }
165 357
166 /** @private */ 358 function onButtonClicked(id, index) {
167 getExpandedOption_: function(option, id, type, priority) { 359 logEvent('Notification #' + id + ': onButtonClicked, btn: ' + index);
168 if (option == '$!') { 360 }
169 option = priority; // Avoids making priorities into strings.
170 } else {
171 option = option.replace(/\$#/g, id);
172 option = option.replace(/\$\?/g, type);
173 option = option.replace(/\$\!/g, priority);
174 }
175 return option;
176 },
177
178 /** @private */
179 deepCopy_: function(value) {
180 var copy = value;
181 if (Array.isArray(value)) {
182 copy = value.map(this.deepCopy_, this);
183 } else if (value && typeof value === 'object') {
184 copy = {}
185 Object.keys(value).forEach(function (key) {
186 copy[key] = this.deepCopy_(value[key]);
187 }, this);
188 }
189 return copy;
190 },
191
192 /** @private */
193 mapStrings_: function(value, map) {
194 var mapped = value;
195 if (typeof value === 'string') {
196 mapped = map.call(this, value);
197 mapped = (typeof mapped !== 'undefined') ? mapped : value;
198 } else if (value && typeof value == 'object') {
199 Object.keys(value).forEach(function (key) {
200 mapped[key] = this.mapStrings_(value[key], map);
201 }, this);
202 }
203 return mapped;
204 },
205
206 /** @private */
207 addListeners_: function(api, events) {
208 (events || []).forEach(function(event) {
209 var listener = this.handleEvent_.bind(this, event);
210 if (api[event])
211 api[event].addListener(listener);
212 else
213 console.log('Event ' + event + ' not defined.');
214 }, this);
215 },
216
217 /** @private */
218 handleEvent_: function(event, id, var_args) {
219 this.view.logEvent('Notification #' + id + ': ' + event + '(' +
220 Array.prototype.slice.call(arguments, 2).join(', ') +
221 ')');
222 },
223
224 /** @private */
225 getDataVersion_: function() {
226 var version = navigator.appVersion.replace(/^.* Chrome\//, '');
227 return (version > '28.0.1500.70') ? '28.0.1500.70.json' :
228 (version > '27.0.1433.1') ? '27.0.1433.1.json' :
229 (version > '27.0.1432.2') ? '27.0.1432.2.json' :
230 '27.0.0.0.json';
231 },
232
233 /** @private */
234 getApi_: function(data) {
235 var path = data[0].api || 'notifications';
236 var api = chrome;
237 path.split('.').forEach(function(key) { api = api && api[key]; });
238 if (!api)
239 this.view.logError('No API found - chrome.' + path + ' is undefined');
240 return api;
241 }
242
243 };
OLDNEW
« no previous file with comments | « no previous file | chrome/test/data/extensions/api_test/notifications/galore/app/data/27.0.0.0.json » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698