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.
6 * Background page for Chrome Sounds extension.
7 * This tracks various events from Chrome and plays sounds.
10 // Map of hostname suffixes or URLs without query params to sounds.
11 // Yeah OK, some of these are a little cliche...
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",
27 // Map of query parameter words to sounds.
28 // More easy cliches...
30 "scotland": "bagpipe.mp3",
31 "seattle": "rain.mp3",
34 // Map of tab numbers to notes on a scale.
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",
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).
49 "keypress": "typewriter-1.mp3",
50 "resize": "harp-transition-2.mp3",
51 "scroll": "shepard.mp3"
54 // Map of events to their default sounds
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"
72 var soundLists = [urlSounds, searchSounds, eventSounds, tabNoteSounds,
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
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
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,
108 function shouldPlay(id) {
109 // Ignore all events until the startup sound has finished.
110 if (id != "startup" && !started)
112 var val = localStorage.getItem(id);
113 if (val && val != "enabled") {
114 console.log(id + " disabled");
120 function didPlay(id) {
121 if (!localStorage.getItem(id))
122 localStorage.setItem(id, "enabled");
125 function playSound(id, loop) {
129 var sound = sounds[id];
130 console.log("playsound: " + id);
131 if (sound && sound.src) {
133 if (sound.currentTime < 0.2) {
134 console.log("ignoring fast replay: " + id + "/" + sound.currentTime);
138 sound.currentTime = 0;
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);
153 console.log("bad playSound: " + id);
157 function stopSound(id) {
158 console.log("stopSound: " + id);
159 var sound = sounds[id];
160 if (sound && sound.src && !sound.paused) {
162 sound.currentTime = 0;
166 var base_url = "http://dl.google.com/dl/chrome/extensions/audio/";
168 function soundLoadError(audio, id) {
169 console.log("failed to load sound: " + id + "-" + audio.src);
175 function soundLoaded(audio, id) {
176 console.log("loaded sound: " + id);
182 // Hack to keep a reference to the objects while we're waiting for them to load.
183 var notYetLoaded = {};
185 function loadSound(file, id) {
187 console.log("no sound for " + id);
190 var audio = new Audio();
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; });
198 audio.src = base_url + file;
200 notYetLoaded[id] = audio;
203 // Remember the last event so that we can avoid multiple events firing
204 // unnecessarily (e.g. selection changed due to close).
207 function eatEvent(name) {
208 if (eventsToEat > 0) {
209 console.log("ate event: " + name);
216 function soundEvent(event, name) {
218 var validator = eventValidator[name];
220 event.addListener(function() {
221 console.log("handling custom event: " + name);
223 // Check this first since the validator may bump the count for future
225 var canPlay = (eventsToEat == 0);
226 if (validator.apply(this, arguments)) {
228 console.log("ate event: " + name);
236 event.addListener(function() {
237 console.log("handling event: " + name);
238 if (eatEvent(name)) {
245 console.log("no event for " + name);
251 function stopNavSound() {
258 function playNavSound(id) {
264 function tabNavigated(tabId, changeInfo, tab) {
265 // Quick fix to catch the case where the content script doesn't have a chance
267 stopSound("keypress");
269 //console.log(JSON.stringify(changeInfo) + JSON.stringify(tab));
270 if (changeInfo.status != "complete") {
273 if (eatEvent("tabNavigated")) {
277 console.log(JSON.stringify(tab));
282 var re = /https?:\/\/([^\/:]*)[^\?]*\??(.*)/i;
283 match = re.exec(tab.url);
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(" ");
295 if (searchSounds[words[j]]) {
296 console.log("searchSound: " + words[j]);
297 playNavSound(words[j]);
305 if (match.length >= 2) {
306 var hostname = match[1];
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];
317 sound = urlSounds[tld3];
327 // Now try a direct URL match (without query string).
329 var query = url.indexOf("?");
331 url = tab.url.substring(0, query);
333 console.log(tab.url);
334 var sound = urlSounds[url];
343 var selectedTabId = -1;
345 function tabSelectionChanged(tabId) {
346 selectedTabId = tabId;
347 if (eatEvent("tabSelectionChanged"))
351 chrome.tabs.get(tabId, function(tab) {
352 var index = tab.index % count;
353 playSound("tab" + index);
358 function tabCreated(tab) {
359 if (eatEvent("tabCreated")) {
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.
368 function tabRemoved(tabId) {
369 if (eatEvent("tabRemoved")) {
372 if (tabId == selectedTabId) {
373 eventsToEat++; // tabSelectionChanged
379 function windowCreated(window) {
380 if (eatEvent("windowCreated")) {
383 eventsToEat += 3; // tabNavigated, tabSelectionChanged, windowFocusChanged
384 if (window.incognito) {
385 playSound("windowCreatedIncognito");
391 var selectedWindowId = -1;
393 function windowFocusChanged(windowId) {
394 if (windowId == selectedWindowId) {
397 selectedWindowId = windowId;
398 if (eatEvent("windowFocusChanged")) {
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);
411 playSound(request.eventName);
414 console.log("got message: " + JSON.stringify(request));
418 //////////////////////////////////////////////////////
420 // Listen for messages from content scripts.
421 chrome.extension.onRequest.addListener(contentScriptHandler);
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);
429 for (var name in events) {
430 soundEvent(events[name], name);