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
3 // found in the LICENSE file.
5 var STOPPED = "Stopped";
6 var RECORDING = "Recording";
7 var PAUSED_RECORDING = "Recording Paused";
8 var PAUSED_PLAYING = "Playing Paused";
9 var PLAYING = "Playing";
11 var recordingState = STOPPED;
13 // Timestamp when current segment started.
15 // Segment duration accumulated before pause button was hit.
17 // The array of segments, with delay and action.
19 // When this timer fires, the next segment from recordingList should be played.
21 var currentSegmentIndex;
22 // A set of web Notifications - used to delete them during playback by id.
23 var webNotifications = {};
25 var recorderButtons = [ "play", "record", "pause", "stop"];
26 var recorderButtonStates = [
27 { state: STOPPED, enabled: "play record" },
28 { state: RECORDING, enabled: "pause stop" },
29 { state: PAUSED_RECORDING, enabled: "record stop" },
30 { state: PAUSED_PLAYING, enabled: "play stop" },
31 { state: PLAYING, enabled: "pause stop" }
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)
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];
53 // Prefix remaining disabled ids to form proper id selectors.
54 for (var i = 0; i < disabled.length; i++) {
55 disabled[i] = "#" + disabled[i];
57 getElements(disabled.join(", ")).forEach(function(element) {
58 element.setAttribute("disabled", "true")
60 getElements(enabled.join(", ")).forEach(function(element) {
61 element.removeAttribute("disabled")
67 function setRecordingState(newState) {
68 setRecorderStatusText(newState);
69 recordingState = newState;
73 function updateRecordingStats(context) {
76 recordingList.slice(currentSegmentIndex).forEach(function(segment) {
77 length += segment.delay || 0;
80 updateRecordingStatsDisplay(context + ": " + (segmentCnt-1) + " segments, " +
81 Math.floor(length/1000) + " seconds.");
84 function loadRecording() {
85 chrome.storage.local.get("recording", function(items) {
86 recordingList = JSON.parse(items["recording"] || "[]");
87 setRecordingState(STOPPED);
88 updateRecordingStats("Loaded record");
92 function finalizeRecording() {
93 chrome.storage.local.set({"recording": JSON.stringify(recordingList)});
94 updateRecordingStats("Recorded");
97 function setPreviousSegmentDuration() {
98 var now = new Date().getTime();
99 var delay = now - segmentStart;
101 recordingList[recordingList.length - 1].delay = delay;
104 function cloneOptions(obj){
105 if(obj == null || typeof(obj) != 'object')
110 temp[key] = cloneOptions(obj[key]);
114 function recordCreate(kind, id, options) {
115 if (recordingState != RECORDING)
117 setPreviousSegmentDuration();
119 { type: "create", kind: kind, id: id, options: cloneOptions(options) });
120 updateRecordingStats("Recording");
123 function recordUpdate(kind, id, options) {
124 if (recordingState != RECORDING)
126 setPreviousSegmentDuration();
128 { type: "update", kind: kind, id: id, options: cloneOptions(options) });
129 updateRecordingStats("Recording");
132 function recordDelete(kind, id) {
133 if (recordingState != RECORDING)
135 setPreviousSegmentDuration();
136 recordingList.push({ type: "delete", kind: kind, id: id });
137 updateRecordingStats("Recording");
140 function startPlaying() {
141 if (recordingList.length < 2)
144 setRecordingState(PLAYING);
147 clearTimeout(playingTimer);
149 webNotifications = {};
150 currentSegmentIndex = 0;
151 playingTimer = setTimeout(playNextSegment,
152 recordingList[currentSegmentIndex].delay);
153 updateRecordingStats("Playing");
156 function playNextSegment() {
157 currentSegmentIndex++;
158 var segment = recordingList[currentSegmentIndex];
164 if (segment.type == "create") {
165 createNotificationForPlay(segment.kind, segment.id, segment.options);
166 } else if (segment.type == "update") {
167 updateNotificationForPlay(segment.kind, segment.id, segment.options);
168 } else { // type == "delete"
169 deleteNotificationForPlay(segment.kind, segment.id);
171 playingTimer = setTimeout(playNextSegment,
172 recordingList[currentSegmentIndex].delay);
173 segmentStart = new Date().getTime();
174 updateRecordingStats("Playing");
177 function deleteNotificationForPlay(kind, id) {
179 webNotifications[id].close();
181 chrome.notifications.clear(id, function(wasClosed) {
187 function createNotificationForPlay(kind, id, options) {
189 webNotifications[id] = createWebNotification(id, options);
191 var type = options.type;
192 var priority = options.priority;
193 createRichNotification(id, type, priority, options);
197 function updateNotificationForPlay(kind, id, options) {
199 // TODO: implement update.
201 var type = options.type;
202 var priority = options.priority;
203 updateRichNotification(id, type, priority, options);
207 function stopPlaying() {
208 currentSegmentIndex = 0;
209 clearTimeout(playingTimer);
210 updateRecordingStats("Record");
211 setRecordingState(STOPPED);
214 function pausePlaying() {
215 clearTimeout(playingTimer);
216 pausedDuration = new Date().getTime() - segmentStart;
217 setRecordingState(PAUSED_PLAYING);
220 function unpausePlaying() {
221 var remainingInSegment =
222 recordingList[currentSegmentIndex].delay - pausedDuration;
223 if (remainingInSegment < 0)
224 remainingInSegment = 0;
225 playingTimer = setTimeout(playNextSegment, remainingInSegment);
226 segmentStart = new Date().getTime() - pausedDuration;
229 function onRecord() {
230 if (recordingState == STOPPED) {
231 segmentStart = new Date().getTime();
233 // This item is only needed to keep a duration of the delay between start
235 recordingList = [ { type:"start" } ];
236 } else if (recordingState == PAUSED_RECORDING) {
237 segmentStart = new Date().getTime() - pausedDuration;
242 updateRecordingStats("Recording");
243 setRecordingState(RECORDING);
246 function pauseRecording() {
247 pausedDuration = new Date().getTime() - segmentStart;
249 setRecordingState(PAUSED_RECORDING);
253 if (recordingState == RECORDING) {
255 } else if (recordingState == PLAYING) {
263 switch (recordingState) {
264 case PAUSED_RECORDING:
265 segmentStart = new Date().getTime() - pausedDuration;
275 setRecordingState(STOPPED);
279 if (recordingState == STOPPED) {
282 } else if (recordingState == PAUSED_PLAYING) {
285 setRecordingState(PLAYING);
288 function createWindow() {
289 chrome.storage.local.get('settings', onSettingsFetched);
292 function onSettingsFetched(items) {
293 settings = items.settings || settings;
294 var request = new XMLHttpRequest();
295 var source = '/data/data.json';
296 request.open('GET', source, true);
297 request.responseType = 'text';
298 request.onload = onDataFetched;
302 function onDataFetched() {
303 var data = JSON.parse(this.response);
304 createAppWindow(function() {
305 // Create notification buttons.
306 data.forEach(function(section) {
307 var type = section.notificationType;
308 if (type == "progress") {
309 addProgressControl(section.sectionName);
311 (section.notificationOptions || []).forEach(function(options) {
312 addNotificationButton(section.sectionName,
315 function() { createNotification(type, options) });
324 function onSettingsChange(settings) {
325 chrome.storage.local.set({settings: settings});
328 function scheduleNextProgress(id, priority, options, progress, step, timeout) {
329 var new_progress = progress + step;
330 if (new_progress > 100)
332 setTimeout(nextProgress(id, priority, options, new_progress, step, timeout),
336 function nextProgress(id, priority, options, progress, step, timeout) {
338 options["progress"] = progress;
339 updateRichNotification(id, "progress", priority, options);
342 scheduleNextProgress(id, priority, options, progress, step, timeout)
346 function createNotification(type, options) {
347 var id = getNextId();
348 var priority = Number(settings.priority || 0);
350 createWebNotification(id, options);
352 if (type == "progress") {
353 if (getElement('#progress-oneshot').checked) {
354 options["progress"] = Number(getElement('#progress').value);
356 var step = Number(getElement('#progress-step').value);
357 options["progress"] = step;
358 if (options["progress"] < 100) {
359 scheduleNextProgress(id, priority, options, options["progress"], step,
360 Number(getElement('#progress-sec').value) * 1000);
364 createRichNotification(id, type, priority, options);
368 function createWebNotification(id, options) {
369 var iconUrl = options.iconUrl;
370 var title = options.title;
371 var message = options.message;
372 var n = new Notification(title, {
377 n.onshow = function() { logEvent('WebNotification #' + id + ': onshow'); }
378 n.onclick = function() { logEvent('WebNotification #' + id + ': onclick'); }
379 n.onclose = function() {
380 logEvent('WebNotification #' + id + ': onclose');
381 recordDelete('web', id);
383 logMethodCall('created', 'Web', id, 'title: "' + title + '"');
384 recordCreate('web', id, options);
388 function createRichNotification(id, type, priority, options) {
389 options["type"] = type;
390 options["priority"] = priority;
391 chrome.notifications.create(id, options, function() {
392 var argument1 = 'type: "' + type + '"';
393 var argument2 = 'priority: ' + priority;
394 var argument3 = 'title: "' + options.title + '"';
395 logMethodCall('created', 'Rich', id, argument1, argument2, argument3);
397 recordCreate('rich', id, options);
400 function updateRichNotification(id, type, priority, options) {
401 options["type"] = type;
402 options["priority"] = priority;
403 chrome.notifications.update(id, options, function() {
404 var argument1 = 'type: "' + type + '"';
405 var argument2 = 'priority: ' + priority;
406 var argument3 = 'title: "' + options.title + '"';
407 logMethodCall('updated', 'Rich', id, argument1, argument2, argument3);
409 recordUpdate('rich', id, options);
413 function getNextId() {
414 return String(counter++);
417 function addListeners() {
418 chrome.notifications.onClosed.addListener(onClosed);
419 chrome.notifications.onClicked.addListener(onClicked);
420 chrome.notifications.onButtonClicked.addListener(onButtonClicked);
423 function logMethodCall(method, kind, id, var_args) {
424 logEvent(kind + ' Notification #' + id + ': ' + method + ' (' +
425 Array.prototype.slice.call(arguments, 2).join(', ') + ')');
428 function onClosed(id) {
429 logEvent('Notification #' + id + ': onClosed');
430 recordDelete('rich', id);
433 function onClicked(id) {
434 logEvent('Notification #' + id + ': onClicked');
437 function onButtonClicked(id, index) {
438 logEvent('Notification #' + id + ': onButtonClicked, btn: ' + index);