summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorerikkay@chromium.org <erikkay@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-02 16:05:07 +0000
committererikkay@chromium.org <erikkay@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-02 16:05:07 +0000
commitc3f0f8542bd06744f00d5c918ae042096b917df7 (patch)
tree98e3245f62257df87955b280c59293fc09ea1b26
parenta17eef828c8fbd19c646915c7b13b9b7f917f0ad (diff)
downloadchromium_src-c3f0f8542bd06744f00d5c918ae042096b917df7.zip
chromium_src-c3f0f8542bd06744f00d5c918ae042096b917df7.tar.gz
chromium_src-c3f0f8542bd06744f00d5c918ae042096b917df7.tar.bz2
Sample code for tracking extension and content events.
https://chrome.google.com/extensions/detail/kkfibincabhfblmkmhcabnlghmncdcaf Review URL: http://codereview.chromium.org/1541001 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@43488 0039d316-1c4b-4281-b951-d872f2087c98
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/fx/bg.html1
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/fx/bg.js427
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/fx/content.js62
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/fx/icon.pngbin0 -> 21948 bytes
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/fx/manifest.json19
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/fx/options.html85
6 files changed, 594 insertions, 0 deletions
diff --git a/chrome/common/extensions/docs/examples/extensions/fx/bg.html b/chrome/common/extensions/docs/examples/extensions/fx/bg.html
new file mode 100755
index 0000000..0753d5e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/fx/bg.html
@@ -0,0 +1 @@
+<script src="bg.js"></script>
diff --git a/chrome/common/extensions/docs/examples/extensions/fx/bg.js b/chrome/common/extensions/docs/examples/extensions/fx/bg.js
new file mode 100755
index 0000000..eab6a34
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/fx/bg.js
@@ -0,0 +1,427 @@
+/*
+ * Background page for Chrome Sounds extension.
+ * This tracks various events from Chrome and plays sounds.
+ */
+
+// Map of hostname suffixes or URLs without query params to sounds.
+// Yeah OK, some of these are a little cliche...
+var urlSounds = {
+ "http://www.google.ca/": "canadian-hello.mp3",
+ "about:histograms": "time-passing.mp3",
+ "about:memory": "transform!.mp3",
+ "about:crash": "sadtrombone.mp3",
+ "chrome://extensions/": "beepboop.mp3",
+ "http://www.google.com.au/": "didgeridoo.mp3",
+ "http://www.google.com.my/": "my_subway.mp3",
+ "http://www.google.com/appserve/fiberrfi/": "dialup.mp3",
+ "lively.com": "cricket.mp3",
+ "http://www.google.co.uk/": "mind_the_gap.mp3",
+ "http://news.google.com/": "news.mp3",
+ "http://www.bing.com/": "sonar.mp3",
+};
+
+// Map of query parameter words to sounds.
+// More easy cliches...
+var searchSounds = {
+ "scotland": "bagpipe.mp3",
+ "seattle": "rain.mp3",
+};
+
+// Map of tab numbers to notes on a scale.
+var tabNoteSounds = {
+ "tab0": "mando-1.mp3",
+ "tab1": "mando-2.mp3",
+ "tab2": "mando-3.mp3",
+ "tab3": "mando-4.mp3",
+ "tab4": "mando-5.mp3",
+ "tab5": "mando-6.mp3",
+ "tab6": "mando-7.mp3",
+};
+
+// Map of sounds that play in a continuous loop while an event is happening
+// in the content area (e.g. "keypress" while start and keep looping while
+// the user keeps typing).
+var contentSounds = {
+ "keypress": "typewriter-1.mp3",
+ "resize": "harp-transition-2.mp3",
+ "scroll": "shepard.mp3"
+};
+
+// Map of events to their default sounds
+var eventSounds = {
+ "tabCreated": "conga1.mp3",
+ "tabMoved": "bell-transition.mp3",
+ "tabRemoved": "smash-glass-1.mp3",
+ "tabSelectionChanged": "click.mp3",
+ "tabAttached": "whoosh-15.mp3",
+ "tabDetached": "sword-shrill.mp3",
+ "tabNavigated": "click.mp3",
+ "windowCreated": "bell-small.mp3",
+ "windowFocusChanged": "click.mp3",
+ "bookmarkCreated": "bubble-drop.mp3",
+ "bookmarkMoved": "thud.mp3",
+ "bookmarkRemoved": "explosion-6.mp3",
+ "windowCreatedIncognito": "weird-wind1.mp3",
+ "startup": "whoosh-19.mp3"
+};
+
+var soundLists = [urlSounds, searchSounds, eventSounds, tabNoteSounds,
+ contentSounds];
+
+var sounds = {};
+
+// Map of event names to extension events.
+// Events intentionally skipped:
+// chrome.windows.onRemoved - can't suppress the tab removed that comes first
+var events = {
+ "tabCreated": chrome.tabs.onCreated,
+ "tabMoved": chrome.tabs.onMoved,
+ "tabRemoved": chrome.tabs.onRemoved,
+ "tabSelectionChanged": chrome.tabs.onSelectionChanged,
+ "tabAttached": chrome.tabs.onAttached,
+ "tabDetached": chrome.tabs.onDetached,
+ "tabNavigated": chrome.tabs.onUpdated,
+ "windowCreated": chrome.windows.onCreated,
+ "windowFocusChanged": chrome.windows.onFocusChanged,
+ "bookmarkCreated": chrome.bookmarks.onCreated,
+ "bookmarkMoved": chrome.bookmarks.onMoved,
+ "bookmarkRemoved": chrome.bookmarks.onRemoved
+};
+
+// Map of event name to a validation function that is should return true if
+// the default sound should be played for this event.
+var eventValidator = {
+ "tabCreated": tabCreated,
+ "tabNavigated": tabNavigated,
+ "tabRemoved": tabRemoved,
+ "tabSelectionChanged": tabSelectionChanged,
+ "windowCreated": windowCreated,
+ "windowFocusChanged": windowFocusChanged,
+};
+
+var started = false;
+
+function shouldPlay(id) {
+ // Ignore all events until the startup sound has finished.
+ if (id != "startup" && !started)
+ return false;
+ var val = localStorage.getItem(id);
+ if (val && val != "enabled") {
+ console.log(id + " disabled");
+ return false;
+ }
+ return true;
+}
+
+function didPlay(id) {
+ if (!localStorage.getItem(id))
+ localStorage.setItem(id, "enabled");
+}
+
+function playSound(id, loop) {
+ if (!shouldPlay(id))
+ return;
+
+ var sound = sounds[id];
+ console.log("playsound: " + id);
+ if (sound && sound.src) {
+ if (!sound.paused) {
+ if (sound.currentTime < 0.2) {
+ console.log("ignoring fast replay: " + id + "/" + sound.currentTime);
+ return;
+ }
+ sound.pause();
+ sound.currentTime = 0;
+ }
+ if (loop)
+ sound.loop = loop;
+
+ // Sometimes, when playing multiple times, readyState is HAVE_METADATA.
+ if (sound.readyState == 0) { // HAVE_NOTHING
+ console.log("bad ready state: " + sound.readyState);
+ } else if (sound.error) {
+ console.log("media error: " + sound.error);
+ } else {
+ didPlay(id);
+ sound.play();
+ }
+ } else {
+ console.log("bad playSound: " + id);
+ }
+}
+
+function stopSound(id) {
+ console.log("stopSound: " + id);
+ var sound = sounds[id];
+ if (sound && sound.src && !sound.paused) {
+ sound.pause();
+ sound.currentTime = 0;
+ }
+}
+
+var base_url = "http://dl.google.com/dl/chrome/extensions/audio/";
+
+function soundLoadError(audio, id) {
+ console.log("failed to load sound: " + id + "-" + audio.src);
+ audio.src = "";
+ if (id == "startup")
+ started = true;
+}
+
+function soundLoaded(audio, id) {
+ console.log("loaded sound: " + id);
+ sounds[id] = audio;
+ if (id == "startup")
+ playSound(id);
+}
+
+// Hack to keep a reference to the objects while we're waiting for them to load.
+var notYetLoaded = {};
+
+function loadSound(file, id) {
+ if (!file.length) {
+ console.log("no sound for " + id);
+ return;
+ }
+ var audio = new Audio();
+ audio.id = id;
+ audio.onerror = function() { soundLoadError(audio, id); };
+ audio.addEventListener("canplaythrough",
+ function() { soundLoaded(audio, id); }, false);
+ if (id == "startup") {
+ audio.addEventListener("ended", function() { started = true; });
+ }
+ audio.src = base_url + file;
+ audio.load();
+ notYetLoaded[id] = audio;
+}
+
+// Remember the last event so that we can avoid multiple events firing
+// unnecessarily (e.g. selection changed due to close).
+var eventsToEat = 0;
+
+function eatEvent(name) {
+ if (eventsToEat > 0) {
+ console.log("ate event: " + name);
+ eventsToEat--;
+ return true;
+ }
+ return false;
+}
+
+function soundEvent(event, name) {
+ if (event) {
+ var validator = eventValidator[name];
+ if (validator) {
+ event.addListener(function() {
+ console.log("handling custom event: " + name);
+
+ // Check this first since the validator may bump the count for future
+ // events.
+ var canPlay = (eventsToEat == 0);
+ if (validator.apply(this, arguments)) {
+ if (!canPlay) {
+ console.log("ate event: " + name);
+ eventsToEat--;
+ return;
+ }
+ playSound(name);
+ }
+ });
+ } else {
+ event.addListener(function() {
+ console.log("handling event: " + name);
+ if (eatEvent(name)) {
+ return;
+ }
+ playSound(name);
+ });
+ }
+ } else {
+ console.log("no event for " + name);
+ }
+}
+
+var navSound;
+
+function stopNavSound() {
+ if (navSound) {
+ stopSound(navSound);
+ navSound = null;
+ }
+}
+
+function playNavSound(id) {
+ stopNavSound();
+ navSound = id;
+ playSound(id);
+}
+
+function tabNavigated(tabId, changeInfo, tab) {
+ // Quick fix to catch the case where the content script doesn't have a chance
+ // to stop itself.
+ stopSound("keypress");
+
+ //console.log(JSON.stringify(changeInfo) + JSON.stringify(tab));
+ if (changeInfo.status != "complete") {
+ return false;
+ }
+ if (eatEvent("tabNavigated")) {
+ return false;
+ }
+
+ console.log(JSON.stringify(tab));
+
+ if (navSound)
+ stopSound(navSound);
+
+ var re = /https?:\/\/([^\/:]*)[^\?]*\??(.*)/i;
+ match = re.exec(tab.url);
+ if (match) {
+ if (match.length == 3) {
+ var query = match[2];
+ var parts = query.split("&");
+ for (var i in parts) {
+ if (parts[i].indexOf("q=") == 0) {
+ var q = decodeURIComponent(parts[i].substring(2));
+ q = q.replace("+", " ");
+ console.log("query == " + q);
+ var words = q.split(" ");
+ for (j in words) {
+ if (searchSounds[words[j]]) {
+ console.log("searchSound: " + words[j]);
+ playNavSound(words[j]);
+ return false;
+ }
+ }
+ break;
+ }
+ }
+ }
+ if (match.length >= 2) {
+ var hostname = match[1];
+ if (hostname) {
+ var parts = hostname.split(".");
+ if (parts.length > 1) {
+ var tld2 = parts.slice(-2).join(".");
+ var tld3 = parts.slice(-3).join(".");
+ var sound = urlSounds[tld2];
+ if (sound) {
+ playNavSound(tld2);
+ return false;
+ }
+ sound = urlSounds[tld3];
+ if (sound) {
+ playNavSound(tld3);
+ return false;
+ }
+ }
+ }
+ }
+ }
+
+ // Now try a direct URL match (without query string).
+ var url = tab.url;
+ var query = url.indexOf("?");
+ if (query > 0) {
+ url = tab.url.substring(0, query);
+ }
+ console.log(tab.url);
+ var sound = urlSounds[url];
+ if (sound) {
+ playNavSound(url);
+ return false;
+ }
+
+ return true;
+}
+
+var selectedTabId = -1;
+
+function tabSelectionChanged(tabId) {
+ selectedTabId = tabId;
+ if (eatEvent("tabSelectionChanged"))
+ return false;
+
+ var count = 7;
+ chrome.tabs.get(tabId, function(tab) {
+ var index = tab.index % count;
+ playSound("tab" + index);
+ });
+ return false;
+}
+
+function tabCreated(tab) {
+ if (eatEvent("tabCreated")) {
+ return false;
+ }
+ eventsToEat++; // tabNavigated or tabSelectionChanged
+ // TODO - unfortunately, we can't detect whether this tab will get focus, so
+ // we can't decide whether or not to eat a second event.
+ return true;
+}
+
+function tabRemoved(tabId) {
+ if (eatEvent("tabRemoved")) {
+ return false;
+ }
+ if (tabId == selectedTabId) {
+ eventsToEat++; // tabSelectionChanged
+ stopNavSound();
+ }
+ return true;
+}
+
+function windowCreated(window) {
+ if (eatEvent("windowCreated")) {
+ return false;
+ }
+ eventsToEat += 3; // tabNavigated, tabSelectionChanged, windowFocusChanged
+ if (window.incognito) {
+ playSound("windowCreatedIncognito");
+ return false;
+ }
+ return true;
+}
+
+var selectedWindowId = -1;
+
+function windowFocusChanged(windowId) {
+ if (windowId == selectedWindowId) {
+ return false;
+ }
+ selectedWindowId = windowId;
+ if (eatEvent("windowFocusChanged")) {
+ return false;
+ }
+ return true;
+}
+
+function contentScriptHandler(request) {
+ if (contentSounds[request.eventName]) {
+ if (request.eventValue == "started") {
+ playSound(request.eventName, true);
+ } else if (request.eventValue == "stopped") {
+ stopSound(request.eventName);
+ } else {
+ playSound(request.eventName);
+ }
+ }
+ console.log("got message: " + JSON.stringify(request));
+}
+
+
+//////////////////////////////////////////////////////
+
+// Listen for messages from content scripts.
+chrome.extension.onRequest.addListener(contentScriptHandler);
+
+// Load the sounds and register event listeners.
+for (var list in soundLists) {
+ for (var id in soundLists[list]) {
+ loadSound(soundLists[list][id], id);
+ }
+}
+for (var name in events) {
+ soundEvent(events[name], name);
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/fx/content.js b/chrome/common/extensions/docs/examples/extensions/fx/content.js
new file mode 100755
index 0000000..9c1e7de
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/fx/content.js
@@ -0,0 +1,62 @@
+/*
+ * Content script for Chrome Sounds.
+ * Tracks in-page events and notifies the background page.
+ */
+
+function sendEvent(event, value) {
+ console.log("sendEvent: " + event + "," + value);
+ chrome.extension.sendRequest({eventName: event, eventValue: value});
+}
+
+// Timers to trigger "stopEvent" for coalescing events.
+var timers = {};
+
+function stopEvent(type) {
+ timers[type] = 0;
+ sendEvent(type, "stopped");
+}
+
+// Automatically coalesces repeating events into a start and a stop event.
+// |validator| is a function which should return true if the event is
+// considered to be a valid event of this type.
+function handleEvent(event, type, validator) {
+ if (validator) {
+ if (!validator(event)) {
+ return;
+ }
+ }
+ var timerId = timers[type];
+ var eventInProgress = (timerId > 0);
+ if (eventInProgress) {
+ clearTimeout(timerId);
+ timers[type] = 0;
+ } else {
+ sendEvent(type, "started");
+ }
+ timers[type] = setTimeout(stopEvent, 300, type);
+}
+
+function listenAndCoalesce(target, type, validator) {
+ target.addEventListener(type, function(event) {
+ handleEvent(event, type, validator);
+ }, true);
+}
+
+listenAndCoalesce(document, "scroll");
+
+// For some reason, "resize" doesn't seem to work with addEventListener.
+if ((window == window.top) && document.body && !document.body.onresize) {
+ document.body.onresize = function(event) {
+ sendEvent("resize", "");
+ };
+}
+
+listenAndCoalesce(document, "keypress", function(event) {
+ if (event.charCode == 13)
+ return false;
+
+ // TODO(erikkay) This doesn't work in gmail's rich text compose window.
+ return event.target.tagName == "TEXTAREA" ||
+ event.target.tagName == "INPUT" ||
+ event.target.isContentEditable;
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/fx/icon.png b/chrome/common/extensions/docs/examples/extensions/fx/icon.png
new file mode 100755
index 0000000..c8322c3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/fx/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/fx/manifest.json b/chrome/common/extensions/docs/examples/extensions/fx/manifest.json
new file mode 100755
index 0000000..0fa4af9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/fx/manifest.json
@@ -0,0 +1,19 @@
+{
+ "name": "Chrome Sounds",
+ "version": "1.1",
+ "description": "Enjoy a more magical and immersive experience when browsing the web using the power of sound.",
+ "background_page": "bg.html",
+ "options_page": "options.html",
+ "icons": { "128": "icon.png" },
+ "permissions": [
+ "tabs",
+ "bookmarks",
+ "http://*/*",
+ "https://*/*"
+ ],
+ "content_scripts": [ {
+ "matches": ["http://*/*", "https://*/*"],
+ "js": ["content.js"],
+ "all_frames": true
+ }]
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/fx/options.html b/chrome/common/extensions/docs/examples/extensions/fx/options.html
new file mode 100755
index 0000000..8f5171c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/fx/options.html
@@ -0,0 +1,85 @@
+<!doctype html>
+<html>
+<head>
+<style>
+body {
+ font-family: sans-serif;
+}
+#attributions {
+ margin-top: 20px;
+ color: #666666;
+ Xfont-size: 10px;
+}
+.sound {
+ cursor: pointer;
+}
+</style>
+<script>
+
+function playSound(id) {
+ console.log(id);
+ chrome.extension.getBackgroundPage().playSound(id, false);
+}
+
+function stopSound(id) {
+ chrome.extension.getBackgroundPage().stopSound(id);
+}
+
+function soundChanged(event) {
+ var key = event.target.name;
+ var checked = event.target.checked;
+ if (checked) {
+ localStorage.setItem(key, "enabled");
+ playSound(event.target.name);
+ } else {
+ localStorage.setItem(key, "disabled");
+ stopSound(event.target.name);
+ }
+}
+
+function showSounds() {
+ var sounds = document.getElementById("sounds");
+ if (!localStorage.length) {
+ sounds.innerText = "";
+ return;
+ }
+ sounds.innerText = "Discovered sounds: (uncheck to disable)";
+ var keys = new Array();
+ for (var key in localStorage) {
+ keys.push(key);
+ console.log(key);
+ }
+ keys.sort();
+ for (var index in keys) {
+ var key = keys[index];
+ var div = document.createElement("div");
+ var check = document.createElement("input");
+ check.type = "checkbox"
+ check.name = key;
+ check.checked = localStorage[key] == "enabled";
+ check.onchange = soundChanged;
+ div.appendChild(check);
+ var text = document.createElement("span");
+ text.id = key;
+ text.innerText = key;
+ text.className = "sound";
+ text.onclick = function(event) { playSound(event.target.id); };
+ div.appendChild(text);
+ sounds.appendChild(div);
+ }
+}
+</script>
+</head>
+<body onload="showSounds()" onFocus="showSounds()">
+<div id="sounds"></div>
+<div id="attributions">
+Sounds from:
+<ul>
+<li><a href="http://www.freesound.org">www.freesound.org</a></li>
+<li><a href="http://www.free-samples-n-loops.com/loops.html">www.free-samples-n-loops.com/loops.html</a></li>
+<li>Googlers with microphones.*</li>
+</ul>
+<span style="font-size:10px">* Canadian sound made by actual Canadian.</span>
+</div>
+</body>
+</html>