- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / common / extensions / docs / examples / extensions / fx / bg.js
1 // Copyright (c) 2012 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 /*
6  * Background page for Chrome Sounds extension.
7  * This tracks various events from Chrome and plays sounds.
8  */
9
10 // Map of hostname suffixes or URLs without query params to sounds.
11 // Yeah OK, some of these are a little cliche...
12 var urlSounds = {
13   "http://www.google.ca/": "canadian-hello.mp3",
14   "about:histograms": "time-passing.mp3",
15   "about:memory": "transform!.mp3",
16   "about:crash": "sadtrombone.mp3",
17   "chrome://extensions/": "beepboop.mp3",
18   "http://www.google.com.au/": "didgeridoo.mp3",
19   "http://www.google.com.my/": "my_subway.mp3",
20   "http://www.google.com/appserve/fiberrfi/": "dialup.mp3",
21   "lively.com": "cricket.mp3",
22   "http://www.google.co.uk/": "mind_the_gap.mp3",
23   "http://news.google.com/": "news.mp3",
24   "http://www.bing.com/": "sonar.mp3",
25 };
26
27 // Map of query parameter words to sounds.
28 // More easy cliches...
29 var searchSounds = {
30   "scotland": "bagpipe.mp3",
31   "seattle": "rain.mp3",
32 };
33
34 // Map of tab numbers to notes on a scale.
35 var tabNoteSounds = {
36   "tab0": "mando-1.mp3",
37   "tab1": "mando-2.mp3",
38   "tab2": "mando-3.mp3",
39   "tab3": "mando-4.mp3",
40   "tab4": "mando-5.mp3",
41   "tab5": "mando-6.mp3",
42   "tab6": "mando-7.mp3",
43 };
44
45 // Map of sounds that play in a continuous loop while an event is happening
46 // in the content area (e.g. "keypress" while start and keep looping while
47 // the user keeps typing).
48 var contentSounds = {
49   "keypress": "typewriter-1.mp3",
50   "resize": "harp-transition-2.mp3",
51   "scroll": "shepard.mp3"
52 };
53
54 // Map of events to their default sounds
55 var eventSounds = {
56   "tabCreated": "conga1.mp3",
57   "tabMoved": "bell-transition.mp3",
58   "tabRemoved": "smash-glass-1.mp3",
59   "tabSelectionChanged": "click.mp3",
60   "tabAttached": "whoosh-15.mp3",
61   "tabDetached": "sword-shrill.mp3",
62   "tabNavigated": "click.mp3",
63   "windowCreated": "bell-small.mp3",
64   "windowFocusChanged": "click.mp3",
65   "bookmarkCreated": "bubble-drop.mp3",
66   "bookmarkMoved": "thud.mp3",
67   "bookmarkRemoved": "explosion-6.mp3",
68   "windowCreatedIncognito": "weird-wind1.mp3",
69   "startup": "whoosh-19.mp3"
70 };
71
72 var soundLists = [urlSounds, searchSounds, eventSounds, tabNoteSounds,
73     contentSounds];
74
75 var sounds = {};
76
77 // Map of event names to extension events.
78 // Events intentionally skipped:
79 // chrome.windows.onRemoved - can't suppress the tab removed that comes first
80 var events = {
81   "tabCreated": chrome.tabs.onCreated,
82   "tabMoved": chrome.tabs.onMoved,
83   "tabRemoved": chrome.tabs.onRemoved,
84   "tabSelectionChanged": chrome.tabs.onSelectionChanged,
85   "tabAttached": chrome.tabs.onAttached,
86   "tabDetached": chrome.tabs.onDetached,
87   "tabNavigated": chrome.tabs.onUpdated,
88   "windowCreated": chrome.windows.onCreated,
89   "windowFocusChanged": chrome.windows.onFocusChanged,
90   "bookmarkCreated": chrome.bookmarks.onCreated,
91   "bookmarkMoved": chrome.bookmarks.onMoved,
92   "bookmarkRemoved": chrome.bookmarks.onRemoved
93 };
94
95 // Map of event name to a validation function that is should return true if
96 // the default sound should be played for this event.
97 var eventValidator = {
98   "tabCreated": tabCreated,
99   "tabNavigated": tabNavigated,
100   "tabRemoved": tabRemoved,
101   "tabSelectionChanged": tabSelectionChanged,
102   "windowCreated": windowCreated,
103   "windowFocusChanged": windowFocusChanged,
104 };
105
106 var started = false;
107
108 function shouldPlay(id) {
109   // Ignore all events until the startup sound has finished.
110   if (id != "startup" && !started)
111     return false;
112   var val = localStorage.getItem(id);
113   if (val && val != "enabled") {
114     console.log(id + " disabled");
115     return false;
116   }
117   return true;
118 }
119
120 function didPlay(id) {
121   if (!localStorage.getItem(id))
122     localStorage.setItem(id, "enabled");
123 }
124
125 function playSound(id, loop) {
126   if (!shouldPlay(id))
127     return;
128
129   var sound = sounds[id];
130   console.log("playsound: " + id);
131   if (sound && sound.src) {
132     if (!sound.paused) {
133       if (sound.currentTime < 0.2) {
134         console.log("ignoring fast replay: " + id + "/" + sound.currentTime);
135         return;
136       }
137       sound.pause();
138       sound.currentTime = 0;
139     }
140     if (loop)
141       sound.loop = loop;
142
143     // Sometimes, when playing multiple times, readyState is HAVE_METADATA.
144     if (sound.readyState == 0) {  // HAVE_NOTHING
145       console.log("bad ready state: " + sound.readyState);
146     } else if (sound.error) {
147       console.log("media error: " + sound.error);
148     } else {
149       didPlay(id);
150       sound.play();
151     }
152   } else {
153     console.log("bad playSound: " + id);
154   }
155 }
156
157 function stopSound(id) {
158   console.log("stopSound: " + id);
159   var sound = sounds[id];
160   if (sound && sound.src && !sound.paused) {
161     sound.pause();
162     sound.currentTime = 0;
163   }
164 }
165
166 var base_url = "http://dl.google.com/dl/chrome/extensions/audio/";
167
168 function soundLoadError(audio, id) {
169   console.log("failed to load sound: " + id + "-" + audio.src);
170   audio.src = "";
171   if (id == "startup")
172     started = true;
173 }
174
175 function soundLoaded(audio, id) {
176   console.log("loaded sound: " + id);
177   sounds[id] = audio;
178   if (id == "startup")
179     playSound(id);
180 }
181
182 // Hack to keep a reference to the objects while we're waiting for them to load.
183 var notYetLoaded = {};
184
185 function loadSound(file, id) {
186   if (!file.length) {
187     console.log("no sound for " + id);
188     return;
189   }
190   var audio = new Audio();
191   audio.id = id;
192   audio.onerror = function() { soundLoadError(audio, id); };
193   audio.addEventListener("canplaythrough",
194       function() { soundLoaded(audio, id); }, false);
195   if (id == "startup") {
196     audio.addEventListener("ended", function() { started = true; });
197   }
198   audio.src = base_url + file;
199   audio.load();
200   notYetLoaded[id] = audio;
201 }
202
203 // Remember the last event so that we can avoid multiple events firing
204 // unnecessarily (e.g. selection changed due to close).
205 var eventsToEat = 0;
206
207 function eatEvent(name) {
208   if (eventsToEat > 0) {
209     console.log("ate event: " + name);
210     eventsToEat--;
211     return true;
212   }
213   return false;
214 }
215
216 function soundEvent(event, name) {
217   if (event) {
218     var validator = eventValidator[name];
219     if (validator) {
220       event.addListener(function() {
221         console.log("handling custom event: " + name);
222
223         // Check this first since the validator may bump the count for future
224         // events.
225         var canPlay = (eventsToEat == 0);
226         if (validator.apply(this, arguments)) {
227           if (!canPlay) {
228             console.log("ate event: " + name);
229             eventsToEat--;
230             return;
231           }
232           playSound(name);
233         }
234       });
235     } else {
236       event.addListener(function() {
237         console.log("handling event: " + name);
238         if (eatEvent(name)) {
239           return;
240         }
241         playSound(name);
242       });
243     }
244   } else {
245     console.log("no event for " + name);
246   }
247 }
248
249 var navSound;
250
251 function stopNavSound() {
252   if (navSound) {
253     stopSound(navSound);
254     navSound = null;
255   }
256 }
257
258 function playNavSound(id) {
259   stopNavSound();
260   navSound = id;
261   playSound(id);
262 }
263
264 function tabNavigated(tabId, changeInfo, tab) {
265   // Quick fix to catch the case where the content script doesn't have a chance
266   // to stop itself.
267   stopSound("keypress");
268
269   //console.log(JSON.stringify(changeInfo) + JSON.stringify(tab));
270   if (changeInfo.status != "complete") {
271     return false;
272   }
273   if (eatEvent("tabNavigated")) {
274     return false;
275   }
276
277   console.log(JSON.stringify(tab));
278
279   if (navSound)
280     stopSound(navSound);
281
282   var re = /https?:\/\/([^\/:]*)[^\?]*\??(.*)/i;
283   match = re.exec(tab.url);
284   if (match) {
285     if (match.length == 3) {
286       var query = match[2];
287       var parts = query.split("&");
288       for (var i in parts) {
289         if (parts[i].indexOf("q=") == 0) {
290           var q = decodeURIComponent(parts[i].substring(2));
291           q = q.replace("+", " ");
292           console.log("query == " + q);
293           var words = q.split(" ");
294           for (j in words) {
295             if (searchSounds[words[j]]) {
296               console.log("searchSound: " + words[j]);
297               playNavSound(words[j]);
298               return false;
299             }
300           }
301           break;
302         }
303       }
304     }
305     if (match.length >= 2) {
306       var hostname = match[1];
307       if (hostname) {
308         var parts = hostname.split(".");
309         if (parts.length > 1) {
310           var tld2 = parts.slice(-2).join(".");
311           var tld3 = parts.slice(-3).join(".");
312           var sound = urlSounds[tld2];
313           if (sound) {
314             playNavSound(tld2);
315             return false;
316           }
317           sound = urlSounds[tld3];
318           if (sound) {
319             playNavSound(tld3);
320             return false;
321           }
322         }
323       }
324     }
325   }
326
327   // Now try a direct URL match (without query string).
328   var url = tab.url;
329   var query = url.indexOf("?");
330   if (query > 0) {
331     url = tab.url.substring(0, query);
332   }
333   console.log(tab.url);
334   var sound = urlSounds[url];
335   if (sound) {
336     playNavSound(url);
337     return false;
338   }
339
340   return true;
341 }
342
343 var selectedTabId = -1;
344
345 function tabSelectionChanged(tabId) {
346   selectedTabId = tabId;
347   if (eatEvent("tabSelectionChanged"))
348     return false;
349
350   var count = 7;
351   chrome.tabs.get(tabId, function(tab) {
352     var index = tab.index % count;
353     playSound("tab" + index);
354   });
355   return false;
356 }
357
358 function tabCreated(tab) {
359   if (eatEvent("tabCreated")) {
360     return false;
361   }
362   eventsToEat++;  // tabNavigated or tabSelectionChanged
363   // TODO - unfortunately, we can't detect whether this tab will get focus, so
364   // we can't decide whether or not to eat a second event.
365   return true;
366 }
367
368 function tabRemoved(tabId) {
369   if (eatEvent("tabRemoved")) {
370     return false;
371   }
372   if (tabId == selectedTabId) {
373     eventsToEat++;  // tabSelectionChanged
374     stopNavSound();
375   }
376   return true;
377 }
378
379 function windowCreated(window) {
380   if (eatEvent("windowCreated")) {
381     return false;
382   }
383   eventsToEat += 3;  // tabNavigated, tabSelectionChanged, windowFocusChanged
384   if (window.incognito) {
385     playSound("windowCreatedIncognito");
386     return false;
387   }
388   return true;
389 }
390
391 var selectedWindowId = -1;
392
393 function windowFocusChanged(windowId) {
394   if (windowId == selectedWindowId) {
395     return false;
396   }
397   selectedWindowId = windowId;
398   if (eatEvent("windowFocusChanged")) {
399     return false;
400   }
401   return true;
402 }
403
404 function contentScriptHandler(request) {
405   if (contentSounds[request.eventName]) {
406     if (request.eventValue == "started") {
407       playSound(request.eventName, true);
408     } else if (request.eventValue == "stopped") {
409       stopSound(request.eventName);
410     } else {
411       playSound(request.eventName);
412     }
413   }
414   console.log("got message: " + JSON.stringify(request));
415 }
416
417
418 //////////////////////////////////////////////////////
419
420 // Listen for messages from content scripts.
421 chrome.extension.onRequest.addListener(contentScriptHandler);
422
423 // Load the sounds and register event listeners.
424 for (var list in soundLists) {
425   for (var id in soundLists[list]) {
426     loadSound(soundLists[list][id], id);
427   }
428 }
429 for (var name in events) {
430   soundEvent(events[name], name);
431 }