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

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: 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
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 NOT_RECORDING = "Stopped";
6 6 var RECORDING = "Recording";
7 Galore.controller = { 7 var PAUSED = "Paused";
8 /** @constructor */ 8 var PLAYING = "Playing";
9 create: function() { 9
10 var controller = Object.create(this); 10 var recordingState = NOT_RECORDING;
11 controller.api = chrome; 11
12 controller.counter = 0; 12 // Timestamp when current segment started.
13 return controller; 13 var segmentStart;
14 }, 14 // Segment duration accumulated before pause button was hit.
15 15 var pausedDuration;
16 createWindow: function() { 16 // The array of segments, with delay and action.
17 chrome.storage.sync.get('settings', this.onSettingsFetched_.bind(this)); 17 var recordingList;
18 }, 18 // When this timer fires, the next segment from recordingList should be played.
19 19 var playingTimer;
20 /** @private */ 20 var currentSegmentIndex;
21 onSettingsFetched_: function(items) { 21
22 var request = new XMLHttpRequest(); 22 function setRecordingState(newState) {
23 var settings = items.settings || {}; 23 controller.view.setRecorderStatusText(newState);
24 var source = settings.data || '/data/' + this.getDataVersion_(); 24 recordingState = newState;
25 request.open('GET', source, true); 25 }
26 request.responseType = 'text'; 26
27 request.onload = this.onDataFetched_.bind(this, settings, request); 27 function loadRecording() {
28 request.send(); 28 chrome.storage.local.get("recording", function(items) {
29 }, 29 recordingList = JSON.parse(items["recording"] || "[]");
30 30 })
31 /** @private */ 31 }
32 onDataFetched_: function(settings, request) { 32
33 var count = 0; 33 function finalizeRecording() {
34 var data = JSON.parse(request.response); 34 chrome.storage.local.set({"recording": JSON.stringify(recordingList)});
35 }
36
37 function setPreviousSegmentDuration() {
38 var now = new Date().getTime();
39 var delay = now - segmentStart;
40 segmentStart = now;
41 recordingList[currentSegmentIndex++].delay = delay;
42 }
43
44 function recordCreate(id, options) {
45 if (recordingState != RECORDING)
46 return;
47 setPreviousSegmentDuration();
48 recordingList.push({ type: "create", id: id, options: options });
49 }
50
51 function recordDelete(id) {
52 if (recordingState != RECORDING)
53 return;
54 setPreviousSegmentDuration();
55 recordingList.push({ type: "delete", id: id });
56 }
57
58 function startPlaying() {
59 if (recordingList.length < 2)
60 return false;
61
62 if (playingTimer)
63 clearTimeout(playingTimer);
64
65 currentSegmentIndex = 0;
66 playingTimer = setTimeout(playNextSegment,
67 recordingList[currentSegmentIndex].delay);
68 }
69
70 function playNextSegment() {
71 currentSegmentIndex++;
72 var segment = recordingList[currentSegmentIndex];
73 if (!segment)
74 stopPlaying();
75
76 if (segment.type == "create") {
77 chrome.notifications.create(segment.id, segment.options, function() {});
78 } else { // type == "delete"
79 chrome.notifications.clear(segment.id, function() {});
80 }
81 playingTimer = setTimeout(playNextSegment,
82 recordingList[currentSegmentIndex].delay);
83 segmentStart = new Date().getTime();
84 }
85
86 function stopPlaying() {
87 clearTimeout(playingTimer);
88 }
89
90 function pausePlaying() {
91 clearTimeout(playingTimer);
92 pausedDuration =
93 recordingList[currentSegmentIndex].delay - (now - segmentStart);
94 }
95
96 function unpausePlaying() {
97 playingTimer = setTimeout(playNextSegment, pausedDuration);
98 segmentStart = new Date().getTime();
99 }
100
101 function onRecord() {
102 if (recordingState == NOT_RECORDING) {
103 currentSegmentIndex = 0;
104 segmentStart = new Date().getTime();
105 pausedDuration = 0;
106 // This item is only needed to keep a duration of the delay between start
107 // and first action.
108 recordingList = [ { type:"start" } ];
109 } else if (recordingState == PAUSED) {
110 segmentStart = new Date().getTime() - pausedDuration;
111 pausedDuration = 0;
112 } else {
113 return;
114 }
115 setRecordingState(RECORDING);
116 }
117
118 function onPause() {
119 if (recordingState == RECORDING) {
120 pausedDuration = new Date().getTime() - segmentStart;
121 segmentStart = 0;
122 } else if (recordingState == PLAYING) {
123 pausePlaying();
124 } else {
125 return;
126 }
127 setRecordingState(PAUSED);
128 }
129
130 function onStop() {
131 if (recordingState == RECORDING) {
132 finalizeRecording();
133 } else if (recordingState == PAUSED) {
134 segmentStart = new Date().getTime() - pausedDuration;
135 finalizeRecording();
136 } else if (recordingState == PLAYING) {
137 stopPlaying();
138 } else {
139 return;
140 }
141 setRecordingState(NOT_RECORDING);
142 }
143
144 function onPlay() {
145 if (recordingState == NOT_RECORDING && startPlaying()) {
146 setRecordingState = PLAYING;
147 }
148 }
149
150 function createWindow() {
151 loadRecording();
152 chrome.storage.local.get('settings', onSettingsFetched);
153 }
154
155 function onSettingsFetched(items) {
156 settings = items.settings || settings;
157 var request = new XMLHttpRequest();
158 var source = '/data/data.json';
159 request.open('GET', source, true);
160 request.responseType = 'text';
161 request.onload = onDataFetched;
162 request.send();
163 }
164
165 function onDataFetched() {
166 var data = JSON.parse(this.response);
167 createAppWindow(function() {
168 // Create notification buttons.
35 data.forEach(function(section) { 169 data.forEach(function(section) {
170 var type = section.notificationType;
36 (section.notificationOptions || []).forEach(function(options) { 171 (section.notificationOptions || []).forEach(function(options) {
37 ++count; 172 addNotificationButton(section.sectionName,
38 this.fetchImages_(options, function() { 173 options.title,
39 if (--count == 0) 174 options.iconUrl,
40 this.onImagesFetched_(settings, data); 175 function() { createNotification(type, options) });
41 }.bind(this)); 176 });
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 }); 177 });
90 }, 178 addListeners();
91 179 showWindow();
92 /** @private */ 180 });
93 fetchImage_: function(url, onFetched) { 181 }
94 var request = new XMLHttpRequest(); 182
95 request.open('GET', url, true); 183 function onSettingsChange(settings) {
96 request.responseType = 'blob'; 184 chrome.storage.local.set({settings: settings});
97 request.onload = function() { 185 }
98 var url = window.URL.createObjectURL(request.response); 186
99 onFetched.call(this, url); 187 function createNotification(type, options) {
100 }.bind(this); 188 var id = getNextId();
101 request.send(); 189 var priority = Number(settings.priority || 0);
102 }, 190 if (type == 'webkit')
103 191 createWebKitNotification(options);
104 /** @private */ 192 else
105 onSettingsChange_: function(settings) { 193 createRichNotification(id, type, priority, options);
106 this.settings = settings; 194 }
107 chrome.storage.sync.set({settings: this.settings}); 195
108 }, 196 function createWebKitNotification(options) {
dewittj 2014/06/06 17:54:22 s/webkit/w3c/ or web
Dmitry Titov 2014/06/06 22:57:18 Done.
109 197 var iconUrl = options.iconUrl;
110 /** @private */ 198 var title = options.title;
111 createNotification_: function(type, options) { 199 var message = options.message;
112 var id = this.getNextId_(); 200 new Notification(title, {
113 var priority = Number(this.settings.priority || 0); 201 body: message,
114 var expanded = this.getExpandedOptions_(options, id, type, priority); 202 icon: iconUrl
dewittj 2014/06/06 17:54:22 add tag?
Dmitry Titov 2014/06/06 22:57:18 Done. We currently don't seem to catch/log any ev
115 if (type == 'webkit') 203 });
116 this.createWebKitNotification_(expanded); 204 logCreate('created WebKit', '', 'title: "' + title + '"');
117 else 205 }
118 this.createRichNotification_(expanded, id, type, priority); 206
119 }, 207 function createRichNotification(id, type, priority, options) {
120 208 options["type"] = type;
121 /** @private */ 209 options["priority"] = priority;
122 createWebKitNotification_: function(options) { 210 chrome.notifications.create(id, options, function() {
123 var iconUrl = options.iconUrl; 211 var argument1 = 'type: "' + type + '"';
124 var title = options.title; 212 var argument2 = 'priority: ' + priority;
125 var message = options.message; 213 var argument3 = 'title: "' + options.title + '"';
126 new Notification(title, { 214 logCreate('created Rich', id, argument1, argument2, argument3);
127 body: message, 215 });
128 icon: iconUrl 216 recordCreate(id, options);
129 }); 217 }
130 this.handleEvent_('create', '?', 'title: "' + title + '"'); 218
131 }, 219 var counter = 0;
132 220 function getNextId() {
133 /** @private */ 221 return String(counter++);
134 createRichNotification_: function(options, id, type, priority) { 222 }
135 this.api.create(id, options, function() { 223
136 var argument1 = 'type: "' + type + '"'; 224 function addListeners() {
137 var argument2 = 'priority: ' + priority; 225 chrome.notifications.onClosed.addListener(onClosed);
138 var argument3 = 'title: "' + options.title + '"'; 226 chrome.notifications.onClicked.addListener(onClicked);
139 this.handleEvent_('create', id, argument1, argument2, argument3); 227 chrome.notifications.onButtonClicked.addListener(onButtonClicked);
140 }.bind(this)); 228 }
141 }, 229
142 230 function logCreate(event, id, var_args) {
143 /** @private */ 231 logEvent('Notification #' + id + ': ' + event + '(' +
144 getNextId_: function() { 232 Array.prototype.slice.call(arguments, 2).join(', ') + ')');
145 this.counter += 1; 233 }
146 return String(this.counter); 234
147 }, 235 function onClosed(id) {
148 236 logEvent('Notification #' + id + ': onClosed');
149 /** @private */ 237 recordDelete(id);
150 getDefaultedOptions_: function(options, defaults) { 238 }
151 var defaulted = this.deepCopy_(options); 239
152 Object.keys(defaults || {}).forEach(function (key) { 240 function onClicked(id) {
153 defaulted[key] = options[key] || defaults[key]; 241 logEvent('Notification #' + id + ': onClicked');
154 }); 242 }
155 return defaulted; 243
156 }, 244 function onButtonClicked(id, index) {
157 245 logEvent('Notification #' + id + ': onButtonClicked, btn: ' + index);
158 /** @private */ 246 }
159 getExpandedOptions_: function(options, id, type, priority) {
160 var expanded = this.deepCopy_(options);
161 return this.mapStrings_(expanded, function(string) {
162 return this.getExpandedOption_(string, id, type, priority);
163 }, this);
164 },
165
166 /** @private */
167 getExpandedOption_: function(option, id, type, priority) {
168 if (option == '$!') {
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

Powered by Google App Engine
This is Rietveld 408576698