summaryrefslogtreecommitdiffstats
path: root/chrome/common
diff options
context:
space:
mode:
authorerikkay@chromium.org <erikkay@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-11-11 21:29:11 +0000
committererikkay@chromium.org <erikkay@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-11-11 21:29:11 +0000
commit5e85b6df11e8a98a7384a9b2a84471e3de85011f (patch)
tree8bfd4b28fde39723c8963dc6a9256bcf12caa366 /chrome/common
parent6c8dcc3cb5d7f6b0523fd44b6a72aa42b911a575 (diff)
downloadchromium_src-5e85b6df11e8a98a7384a9b2a84471e3de85011f.zip
chromium_src-5e85b6df11e8a98a7384a9b2a84471e3de85011f.tar.gz
chromium_src-5e85b6df11e8a98a7384a9b2a84471e3de85011f.tar.bz2
Moving samples into docs/examples.
Also did a little cleanup of a few of the samples. More cleanup still to go... BUG=26106 TEST=none Review URL: http://codereview.chromium.org/390013 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@31714 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/common')
-rw-r--r--chrome/common/extensions/docs/examples/README.txt6
-rw-r--r--chrome/common/extensions/docs/examples/api/browserAction/print/background.html12
-rw-r--r--chrome/common/extensions/docs/examples/api/browserAction/print/manifest.json13
-rw-r--r--chrome/common/extensions/docs/examples/api/browserAction/print/print_16x16.pngbin0 -> 647 bytes
-rwxr-xr-xchrome/common/extensions/docs/examples/api/browserAction/set_page_color/icon.pngbin0 -> 3622 bytes
-rwxr-xr-xchrome/common/extensions/docs/examples/api/browserAction/set_page_color/manifest.json12
-rw-r--r--chrome/common/extensions/docs/examples/api/browserAction/set_page_color/popup.html52
-rw-r--r--chrome/common/extensions/docs/examples/api/i18n/cld/background.html32
-rw-r--r--chrome/common/extensions/docs/examples/api/i18n/cld/manifest.json12
-rw-r--r--chrome/common/extensions/docs/examples/api/override/override_igoogle/manifest.json7
-rw-r--r--chrome/common/extensions/docs/examples/api/override/override_igoogle/redirect.html3
-rw-r--r--chrome/common/extensions/docs/examples/api/pageAction/set_icon/background.html69
-rwxr-xr-xchrome/common/extensions/docs/examples/api/pageAction/set_icon/icon1.pngbin0 -> 2809 bytes
-rwxr-xr-xchrome/common/extensions/docs/examples/api/pageAction/set_icon/icon2.pngbin0 -> 2809 bytes
-rw-r--r--chrome/common/extensions/docs/examples/api/pageAction/set_icon/manifest.json10
-rw-r--r--chrome/common/extensions/docs/examples/api/tabs/inspector/background.html5
-rw-r--r--chrome/common/extensions/docs/examples/api/tabs/inspector/jstemplate_compiled.js1182
-rw-r--r--chrome/common/extensions/docs/examples/api/tabs/inspector/manifest.json10
-rw-r--r--chrome/common/extensions/docs/examples/api/tabs/inspector/tabs_api.html388
-rw-r--r--chrome/common/extensions/docs/examples/api/tabs/screenshot/background.html10
-rwxr-xr-xchrome/common/extensions/docs/examples/api/tabs/screenshot/camera.pngbin0 -> 1257 bytes
-rwxr-xr-xchrome/common/extensions/docs/examples/api/tabs/screenshot/manifest.json11
-rw-r--r--chrome/common/extensions/docs/examples/api/tabs/screenshot/screenshot.html14
-rwxr-xr-xchrome/common/extensions/docs/examples/api/tabs/screenshot/screenshot.js47
-rw-r--r--chrome/common/extensions/docs/examples/api/tabs/screenshot/white.pngbin0 -> 1614 bytes
-rw-r--r--chrome/common/extensions/docs/examples/extensions/buildbot/bg.html58
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/buildbot/chromium.pngbin0 -> 1736 bytes
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/buildbot/icon.pngbin0 -> 17504 bytes
-rw-r--r--chrome/common/extensions/docs/examples/extensions/buildbot/manifest.json17
-rw-r--r--chrome/common/extensions/docs/examples/extensions/buildbot/popup.html218
-rw-r--r--chrome/common/extensions/docs/examples/extensions/gmail/background.html235
-rw-r--r--chrome/common/extensions/docs/examples/extensions/gmail/gmail_logged_in.pngbin0 -> 3484 bytes
-rw-r--r--chrome/common/extensions/docs/examples/extensions/gmail/gmail_not_logged_in.pngbin0 -> 3412 bytes
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/gmail/icon_128.pngbin0 -> 7911 bytes
-rw-r--r--chrome/common/extensions/docs/examples/extensions/gmail/manifest.json15
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/mappy/icon.pngbin0 -> 15702 bytes
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/mappy/manifest.json18
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/mappy/mappy_content_script.js55
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/mappy/marker.pngbin0 -> 2237 bytes
-rw-r--r--chrome/common/extensions/docs/examples/extensions/mappy/popup.html51
-rw-r--r--chrome/common/extensions/docs/examples/tutorials/debugging/hello_world.html45
-rw-r--r--chrome/common/extensions/docs/examples/tutorials/debugging/manifest.json11
-rw-r--r--chrome/common/extensions/docs/examples/tutorials/debugging/my_toolstrip.html3
-rw-r--r--chrome/common/extensions/docs/samples.html6
-rw-r--r--chrome/common/extensions/docs/static/samples.html6
45 files changed, 2627 insertions, 6 deletions
diff --git a/chrome/common/extensions/docs/examples/README.txt b/chrome/common/extensions/docs/examples/README.txt
new file mode 100644
index 0000000..834cae2
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/README.txt
@@ -0,0 +1,6 @@
+Chromium Extension Examples
+
+The directory structure is as follows:
+* api/ - trivial extensions focused on a single API package
+* extensions/ - full featured extensions spanning multiple API packages
+* tutorials/ - multi-step walkthroughs referenced inline in the docs
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/print/background.html b/chrome/common/extensions/docs/examples/api/browserAction/print/background.html
new file mode 100644
index 0000000..5b3f930
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/print/background.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<script>
+ // Called when the user clicks on the browser action.
+ chrome.browserAction.onClicked.addListener(function(tab) {
+ var action_url = "javascript:window.print();";
+ chrome.tabs.update(tab.id, {url: action_url});
+ });
+</script>
+</head>
+</html>
+
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/print/manifest.json b/chrome/common/extensions/docs/examples/api/browserAction/print/manifest.json
new file mode 100644
index 0000000..0949549
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/print/manifest.json
@@ -0,0 +1,13 @@
+{
+ "name": "Print this page",
+ "description": "Adds a print button to the browser.",
+ "version": "1.1",
+ "background_page": "background.html",
+ "permissions": [
+ "tabs", "http://*/*", "https://*/*"
+ ],
+ "browser_action": {
+ "default_title": "Print this page",
+ "default_icon": "print_16x16.png"
+ }
+} \ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/print/print_16x16.png b/chrome/common/extensions/docs/examples/api/browserAction/print/print_16x16.png
new file mode 100644
index 0000000..d145964
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/print/print_16x16.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/icon.png b/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/icon.png
new file mode 100755
index 0000000..1f1c906
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/manifest.json b/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/manifest.json
new file mode 100755
index 0000000..83e9052
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/manifest.json
@@ -0,0 +1,12 @@
+{
+ "name": "A browser action with a popup that changes the page color.",
+ "version": "1.0",
+ "permissions": [
+ "tabs", "http://*/*", "https://*/*"
+ ],
+ "browser_action": {
+ "default_title": "Set this page's color.",
+ "default_icon": "icon.png",
+ "popup": "popup.html"
+ }
+} \ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/popup.html b/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/popup.html
new file mode 100644
index 0000000..f82fded
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/popup.html
@@ -0,0 +1,52 @@
+<style>
+body {
+ overflow: hidden;
+ margin: 0px;
+ padding: 0px;
+ background: white;
+}
+
+div:first-child {
+ margin-top: 0px;
+}
+
+div {
+ cursor: pointer;
+ text-align: center;
+ padding: 1px 3px;
+ font: menu;
+ width: 100px;
+ margin-top: 1px;
+ background: #cccccc;
+}
+div:hover {
+ background: #aaaaaa;
+}
+#red {
+ border: 1px solid red;
+ color: red;
+}
+#blue {
+ border: 1px solid blue;
+ color: blue;
+}
+#green {
+ border: 1px solid green;
+ color: green;
+}
+#yellow {
+ border: 1px solid yellow;
+ color: yellow;
+}
+</style>
+<script>
+function click(color) {
+ chrome.tabs.executeScript(null,
+ {code:"document.body.style.backgroundColor='" + color.id + "'"});
+ window.close();
+}
+</script>
+<div onclick="click(this)" id="red">red</div>
+<div onclick="click(this)" id="blue">blue</div>
+<div onclick="click(this)" id="green">green</div>
+<div onclick="click(this)" id="yellow">yellow</div>
diff --git a/chrome/common/extensions/docs/examples/api/i18n/cld/background.html b/chrome/common/extensions/docs/examples/api/i18n/cld/background.html
new file mode 100644
index 0000000..cfb1fac
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/i18n/cld/background.html
@@ -0,0 +1,32 @@
+<!--
+Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+source code is governed by a BSD-style license that can be found in the
+LICENSE file.
+-->
+
+<script>
+var selectedId = -1;
+function refreshLanguage() {
+ chrome.tabs.detectLanguage(null, function(language) {
+ console.log(language);
+ if (language == " invalid_language_code")
+ language = "???";
+ chrome.browserAction.setBadgeText({"text": language, tabId: selectedId});
+ });
+}
+
+chrome.tabs.onUpdated.addListener(function(tabId, props) {
+ if (props.status == "complete" && tabId == selectedId)
+ refreshLanguage();
+});
+
+chrome.tabs.onSelectionChanged.addListener(function(tabId, props) {
+ selectedId = tabId;
+ refreshLanguage();
+});
+
+chrome.tabs.getSelected(null, function(tab) {
+ selectedId = tab.id;
+ refreshLanguage();
+});
+</script>
diff --git a/chrome/common/extensions/docs/examples/api/i18n/cld/manifest.json b/chrome/common/extensions/docs/examples/api/i18n/cld/manifest.json
new file mode 100644
index 0000000..f974838
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/i18n/cld/manifest.json
@@ -0,0 +1,12 @@
+{
+ "name": "CLD",
+ "description": "Displays the language of a tab",
+ "version": "0.1",
+ "background_page": "background.html",
+ "permissions": [
+ "tabs"
+ ],
+ "browser_action": {
+ "default_name": "Page Language"
+ }
+}
diff --git a/chrome/common/extensions/docs/examples/api/override/override_igoogle/manifest.json b/chrome/common/extensions/docs/examples/api/override/override_igoogle/manifest.json
new file mode 100644
index 0000000..461da24
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/override/override_igoogle/manifest.json
@@ -0,0 +1,7 @@
+{
+ "name": "iGoogle new tab page",
+ "version": "0.1",
+ "chrome_url_overrides": {
+ "newtab": "redirect.html"
+ }
+} \ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/override/override_igoogle/redirect.html b/chrome/common/extensions/docs/examples/api/override/override_igoogle/redirect.html
new file mode 100644
index 0000000..35117f4
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/override/override_igoogle/redirect.html
@@ -0,0 +1,3 @@
+<head>
+<meta http-equiv="refresh"content="0;URL=http://www.google.com/ig">
+</head>
diff --git a/chrome/common/extensions/docs/examples/api/pageAction/set_icon/background.html b/chrome/common/extensions/docs/examples/api/pageAction/set_icon/background.html
new file mode 100644
index 0000000..9986850
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/pageAction/set_icon/background.html
@@ -0,0 +1,69 @@
+<html>
+<head>
+<script>
+ var lastTabId = 0;
+ var tab_clicks = {};
+
+ chrome.tabs.onSelectionChanged.addListener(function(tabId) {
+ lastTabId = tabId;
+ chrome.pageAction.show(lastTabId);
+ });
+
+ chrome.tabs.getSelected(null, function(tab) {
+ lastTabId = tab.id;
+ chrome.pageAction.show(lastTabId);
+ });
+
+ // Called when the user clicks on the page action.
+ chrome.pageAction.onClicked.addListener(function(tab) {
+ var clicks = tab_clicks[tab.id] || 0;
+ chrome.pageAction.setIcon({path: "icon" + (clicks + 1) + ".png",
+ tabId: tab.id});
+ if (clicks % 2) {
+ chrome.pageAction.show(tab.id);
+ } else {
+ chrome.pageAction.hide(tab.id);
+ setTimeout(function() { chrome.pageAction.show(tab.id); }, 200);
+ }
+ chrome.pageAction.setTitle({title: "click:" + clicks, tabId: tab.id});
+
+ // We only have 2 icons, but cycle through 3 icons to test the
+ // out-of-bounds index bug.
+ clicks++;
+ if (clicks > 3)
+ clicks = 0;
+ tab_clicks[tab.id] = clicks;
+ });
+
+ var i = 0;
+ window.setInterval(function() {
+ var clicks = tab_clicks[lastTabId] || 0;
+
+ // Don't animate while in "click" mode.
+ if (clicks > 0) return;
+
+ // Don't do anything if we don't have a tab yet.
+ if (lastTabId == 0) return;
+
+ i++;
+ chrome.pageAction.setIcon({imageData: draw(i*2, i*4), tabId: lastTabId});
+ }, 50);
+
+ function draw(starty, startx) {
+ var canvas = document.getElementById('canvas');
+ var context = canvas.getContext('2d');
+ context.clearRect(0, 0, canvas.width, canvas.height);
+ context.fillStyle = "rgba(0,200,0,255)";
+ context.fillRect(startx % 19, starty % 19, 8, 8);
+ context.fillStyle = "rgba(0,0,200,255)";
+ context.fillRect((startx + 5) % 19, (starty + 5) % 19, 8, 8);
+ context.fillStyle = "rgba(200,0,0,255)";
+ context.fillRect((startx + 10) % 19, (starty + 10) % 19, 8, 8);
+ return context.getImageData(0, 0, 19, 19);
+ }
+</script>
+</head>
+<body>
+<canvas id="canvas" width="19" height="19"></canvas>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/pageAction/set_icon/icon1.png b/chrome/common/extensions/docs/examples/api/pageAction/set_icon/icon1.png
new file mode 100755
index 0000000..9a79a46
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/pageAction/set_icon/icon1.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/pageAction/set_icon/icon2.png b/chrome/common/extensions/docs/examples/api/pageAction/set_icon/icon2.png
new file mode 100755
index 0000000..8d3f710
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/pageAction/set_icon/icon2.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/pageAction/set_icon/manifest.json b/chrome/common/extensions/docs/examples/api/pageAction/set_icon/manifest.json
new file mode 100644
index 0000000..5a374e8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/pageAction/set_icon/manifest.json
@@ -0,0 +1,10 @@
+{
+ "name": "Animated Page Action",
+ "description": "This extension adds an animated browser action to the toolbar.",
+ "version": "1.0",
+ "permissions": ["tabs"],
+ "background_page": "background.html",
+ "page_action": {
+ "default_title": "First icon"
+ }
+}
diff --git a/chrome/common/extensions/docs/examples/api/tabs/inspector/background.html b/chrome/common/extensions/docs/examples/api/tabs/inspector/background.html
new file mode 100644
index 0000000..314dc3c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/inspector/background.html
@@ -0,0 +1,5 @@
+<script>
+chrome.browserAction.onClicked.addListener(function(tab) {
+ chrome.tabs.create({url:chrome.extension.getURL("tabs_api.html")});
+});
+</script> \ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/tabs/inspector/jstemplate_compiled.js b/chrome/common/extensions/docs/examples/api/tabs/inspector/jstemplate_compiled.js
new file mode 100644
index 0000000..2f62b31
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/inspector/jstemplate_compiled.js
@@ -0,0 +1,1182 @@
+/**
+ * @fileoverview This file contains miscellaneous basic functionality.
+ *
+ */
+
+/**
+ * Creates a DOM element with the given tag name in the document of the
+ * owner element.
+ *
+ * @param {String} tagName The name of the tag to create.
+ * @param {Element} owner The intended owner (i.e., parent element) of
+ * the created element.
+ * @param {Point} opt_position The top-left corner of the created element.
+ * @param {Size} opt_size The size of the created element.
+ * @param {Boolean} opt_noAppend Do not append the new element to the owner.
+ * @return {Element} The newly created element node.
+ */
+function createElement(tagName, owner, opt_position, opt_size, opt_noAppend) {
+ var element = ownerDocument(owner).createElement(tagName);
+ if (opt_position) {
+ setPosition(element, opt_position);
+ }
+ if (opt_size) {
+ setSize(element, opt_size);
+ }
+ if (owner && !opt_noAppend) {
+ appendChild(owner, element);
+ }
+
+ return element;
+}
+
+/**
+ * Creates a text node with the given value.
+ *
+ * @param {String} value The text to place in the new node.
+ * @param {Element} owner The owner (i.e., parent element) of the new
+ * text node.
+ * @return {Text} The newly created text node.
+ */
+function createTextNode(value, owner) {
+ var element = ownerDocument(owner).createTextNode(value);
+ if (owner) {
+ appendChild(owner, element);
+ }
+ return element;
+}
+
+/**
+ * Returns the document owner of the given element. In particular,
+ * returns window.document if node is null or the browser does not
+ * support ownerDocument.
+ *
+ * @param {Node} node The node whose ownerDocument is required.
+ * @returns {Document|Null} The owner document or null if unsupported.
+ */
+function ownerDocument(node) {
+ return (node ? node.ownerDocument : null) || document;
+}
+
+/**
+ * Wrapper function to create CSS units (pixels) string
+ *
+ * @param {Number} numPixels Number of pixels, may be floating point.
+ * @returns {String} Corresponding CSS units string.
+ */
+function px(numPixels) {
+ return round(numPixels) + "px";
+}
+
+/**
+ * Sets the left and top of the given element to the given point.
+ *
+ * @param {Element} element The dom element to manipulate.
+ * @param {Point} point The desired position.
+ */
+function setPosition(element, point) {
+ var style = element.style;
+ style.position = "absolute";
+ style.left = px(point.x);
+ style.top = px(point.y);
+}
+
+/**
+ * Sets the width and height style attributes to the given size.
+ *
+ * @param {Element} element The dom element to manipulate.
+ * @param {Size} size The desired size.
+ */
+function setSize(element, size) {
+ var style = element.style;
+ style.width = px(size.width);
+ style.height = px(size.height);
+}
+
+/**
+ * Sets display to none. Doing this as a function saves a few bytes for
+ * the 'style.display' property and the 'none' literal.
+ *
+ * @param {Element} node The dom element to manipulate.
+ */
+function displayNone(node) {
+ node.style.display = 'none';
+}
+
+/**
+ * Sets display to default.
+ *
+ * @param {Element} node The dom element to manipulate.
+ */
+function displayDefault(node) {
+ node.style.display = '';
+}
+
+/**
+ * Appends the given child to the given parent in the DOM
+ *
+ * @param {Element} parent The parent dom element.
+ * @param {Node} child The new child dom node.
+ */
+function appendChild(parent, child) {
+ parent.appendChild(child);
+}
+
+
+/**
+ * Wrapper for the eval() builtin function to evaluate expressions and
+ * obtain their value. It wraps the expression in parentheses such
+ * that object literals are really evaluated to objects. Without the
+ * wrapping, they are evaluated as block, and create syntax
+ * errors. Also protects against other syntax errors in the eval()ed
+ * code and returns null if the eval throws an exception.
+ *
+ * @param {String} expr
+ * @return {Object|Null}
+ */
+function jsEval(expr) {
+ try {
+ return eval('[' + expr + '][0]');
+ } catch (e) {
+ return null;
+ }
+}
+
+
+/**
+ * Wrapper for the eval() builtin function to execute statements. This
+ * guards against exceptions thrown, but doesn't return a
+ * value. Still, mostly for testability, it returns a boolean to
+ * indicate whether execution was successful. NOTE:
+ * javascript's eval semantics is murky in that it confounds
+ * expression evaluation and statement execution into a single
+ * construct. Cf. jsEval().
+ *
+ * @param {String} stmt
+ * @return {Boolean}
+ */
+function jsExec(stmt) {
+ try {
+ eval(stmt);
+ return true;
+ } catch (e) {
+ return false;
+ }
+}
+
+
+/**
+ * Wrapper for eval with a context. NOTE: The style guide
+ * deprecates eval, so this is the exception that proves the
+ * rule. Notice also that since the value of the expression is
+ * returned rather than assigned to a local variable, one major
+ * objection aganist the use of the with() statement, namely that
+ * properties of the with() target override local variables of the
+ * same name, is void here.
+ *
+ * @param {String} expr
+ * @param {Object} context
+ * @return {Object|Null}
+ */
+function jsEvalWith(expr, context) {
+ try {
+ with (context) {
+ return eval('[' + expr + '][0]');
+ }
+ } catch (e) {
+ return null;
+ }
+}
+
+
+var DOM_ELEMENT_NODE = 1;
+var DOM_ATTRIBUTE_NODE = 2;
+var DOM_TEXT_NODE = 3;
+var DOM_CDATA_SECTION_NODE = 4;
+var DOM_ENTITY_REFERENCE_NODE = 5;
+var DOM_ENTITY_NODE = 6;
+var DOM_PROCESSING_INSTRUCTION_NODE = 7;
+var DOM_COMMENT_NODE = 8;
+var DOM_DOCUMENT_NODE = 9;
+var DOM_DOCUMENT_TYPE_NODE = 10;
+var DOM_DOCUMENT_FRAGMENT_NODE = 11;
+var DOM_NOTATION_NODE = 12;
+
+/**
+ * Traverses the element nodes in the DOM tree underneath the given
+ * node and finds the first node with elemId, or null if there is no such
+ * element. Traversal is in depth-first order.
+ *
+ * NOTE: The reason this is not combined with the elem() function is
+ * that the implementations are different.
+ * elem() is a wrapper for the built-in document.getElementById() function,
+ * whereas this function performs the traversal itself.
+ * Modifying elem() to take an optional root node is a possibility,
+ * but the in-built function would perform better than using our own traversal.
+ *
+ * @param {Element} node Root element of subtree to traverse.
+ * @param {String} elemId The id of the element to search for.
+ * @return {Element|Null} The corresponding element, or null if not found.
+ */
+function nodeGetElementById(node, elemId) {
+ for (var c = node.firstChild; c; c = c.nextSibling) {
+ if (c.id == elemId) {
+ return c;
+ }
+ if (c.nodeType == DOM_ELEMENT_NODE) {
+ var n = arguments.callee.call(this, c, elemId);
+ if (n) {
+ return n;
+ }
+ }
+ }
+ return null;
+}
+
+
+/**
+ * Get an attribute from the DOM. Simple redirect, exists to compress code.
+ *
+ * @param {Element} node Element to interrogate.
+ * @param {String} name Name of parameter to extract.
+ * @return {String} Resulting attribute.
+ */
+function domGetAttribute(node, name) {
+ return node.getAttribute(name);
+}
+
+/**
+ * Set an attribute in the DOM. Simple redirect to compress code.
+ *
+ * @param {Element} node Element to interrogate.
+ * @param {String} name Name of parameter to set.
+ * @param {String} value Set attribute to this value.
+ */
+function domSetAttribute(node, name, value) {
+ node.setAttribute(name, value);
+}
+
+/**
+ * Remove an attribute from the DOM. Simple redirect to compress code.
+ *
+ * @param {Element} node Element to interrogate.
+ * @param {String} name Name of parameter to remove.
+ */
+function domRemoveAttribute(node, name) {
+ node.removeAttribute(name);
+}
+
+/**
+ * Clone a node in the DOM.
+ *
+ * @param {Node} node Node to clone.
+ * @return {Node} Cloned node.
+ */
+function domCloneNode(node) {
+ return node.cloneNode(true);
+}
+
+
+/**
+ * Return a safe string for the className of a node.
+ * If className is not a string, returns "".
+ *
+ * @param {Element} node DOM element to query.
+ * @return {String}
+ */
+function domClassName(node) {
+ return node.className ? "" + node.className : "";
+}
+
+/**
+ * Adds a class name to the class attribute of the given node.
+ *
+ * @param {Element} node DOM element to modify.
+ * @param {String} className Class name to add.
+ */
+function domAddClass(node, className) {
+ var name = domClassName(node);
+ if (name) {
+ var cn = name.split(/\s+/);
+ var found = false;
+ for (var i = 0; i < jsLength(cn); ++i) {
+ if (cn[i] == className) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ cn.push(className);
+ }
+
+ node.className = cn.join(' ');
+ } else {
+ node.className = className;
+ }
+}
+
+/**
+ * Removes a class name from the class attribute of the given node.
+ *
+ * @param {Element} node DOM element to modify.
+ * @param {String} className Class name to remove.
+ */
+function domRemoveClass(node, className) {
+ var c = domClassName(node);
+ if (!c || c.indexOf(className) == -1) {
+ return;
+ }
+ var cn = c.split(/\s+/);
+ for (var i = 0; i < jsLength(cn); ++i) {
+ if (cn[i] == className) {
+ cn.splice(i--, 1);
+ }
+ }
+ node.className = cn.join(' ');
+}
+
+/**
+ * Checks if a node belongs to a style class.
+ *
+ * @param {Element} node DOM element to test.
+ * @param {String} className Class name to check for.
+ * @return {Boolean} Node belongs to style class.
+ */
+function domTestClass(node, className) {
+ var cn = domClassName(node).split(/\s+/);
+ for (var i = 0; i < jsLength(cn); ++i) {
+ if (cn[i] == className) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Inserts a new child before a given sibling.
+ *
+ * @param {Node} newChild Node to insert.
+ * @param {Node} oldChild Sibling node.
+ * @return {Node} Reference to new child.
+ */
+function domInsertBefore(newChild, oldChild) {
+ return oldChild.parentNode.insertBefore(newChild, oldChild);
+}
+
+/**
+ * Appends a new child to the specified (parent) node.
+ *
+ * @param {Element} node Parent element.
+ * @param {Node} child Child node to append.
+ * @return {Node} Newly appended node.
+ */
+function domAppendChild(node, child) {
+ return node.appendChild(child);
+}
+
+/**
+ * Remove a new child from the specified (parent) node.
+ *
+ * @param {Element} node Parent element.
+ * @param {Node} child Child node to remove.
+ * @return {Node} Removed node.
+ */
+function domRemoveChild(node, child) {
+ return node.removeChild(child);
+}
+
+/**
+ * Replaces an old child node with a new child node.
+ *
+ * @param {Node} newChild New child to append.
+ * @param {Node} oldChild Old child to remove.
+ * @return {Node} Replaced node.
+ */
+function domReplaceChild(newChild, oldChild) {
+ return oldChild.parentNode.replaceChild(newChild, oldChild);
+}
+
+/**
+ * Removes a node from the DOM.
+ *
+ * @param {Node} node The node to remove.
+ * @return {Node} The removed node.
+ */
+function domRemoveNode(node) {
+ return domRemoveChild(node.parentNode, node);
+}
+
+/**
+ * Creates a new text node in the given document.
+ *
+ * @param {Document} doc Target document.
+ * @param {String} text Text composing new text node.
+ * @return {Text} Newly constructed text node.
+ */
+function domCreateTextNode(doc, text) {
+ return doc.createTextNode(text);
+}
+
+/**
+ * Creates a new node in the given document
+ *
+ * @param {Document} doc Target document.
+ * @param {String} name Name of new element (i.e. the tag name)..
+ * @return {Element} Newly constructed element.
+ */
+function domCreateElement(doc, name) {
+ return doc.createElement(name);
+}
+
+/**
+ * Creates a new attribute in the given document.
+ *
+ * @param {Document} doc Target document.
+ * @param {String} name Name of new attribute.
+ * @return {Attr} Newly constructed attribute.
+ */
+function domCreateAttribute(doc, name) {
+ return doc.createAttribute(name);
+}
+
+/**
+ * Creates a new comment in the given document.
+ *
+ * @param {Document} doc Target document.
+ * @param {String} text Comment text.
+ * @return {Comment} Newly constructed comment.
+ */
+function domCreateComment(doc, text) {
+ return doc.createComment(text);
+}
+
+/**
+ * Creates a document fragment.
+ *
+ * @param {Document} doc Target document.
+ * @return {DocumentFragment} Resulting document fragment node.
+ */
+function domCreateDocumentFragment(doc) {
+ return doc.createDocumentFragment();
+}
+
+/**
+ * Redirect to document.getElementById
+ *
+ * @param {Document} doc Target document.
+ * @param {String} id Id of requested node.
+ * @return {Element|Null} Resulting element.
+ */
+function domGetElementById(doc, id) {
+ return doc.getElementById(id);
+}
+
+/**
+ * Redirect to window.setInterval
+ *
+ * @param {Window} win Target window.
+ * @param {Function} fun Callback function.
+ * @param {Number} time Time in milliseconds.
+ * @return {Object} Contract id.
+ */
+function windowSetInterval(win, fun, time) {
+ return win.setInterval(fun, time);
+}
+
+/**
+ * Redirect to window.clearInterval
+ *
+ * @param {Window} win Target window.
+ * @param {object} id Contract id.
+ * @return {any} NOTE: Return type unknown?
+ */
+function windowClearInterval(win, id) {
+ return win.clearInterval(id);
+}
+
+/**
+ * Determines whether one node is recursively contained in another.
+ * @param parent The parent node.
+ * @param child The node to look for in parent.
+ * @return parent recursively contains child
+ */
+function containsNode(parent, child) {
+ while (parent != child && child.parentNode) {
+ child = child.parentNode;
+ }
+ return parent == child;
+};
+/**
+ * @fileoverview This file contains javascript utility functions that
+ * do not depend on anything defined elsewhere.
+ *
+ */
+
+/**
+ * Returns the value of the length property of the given object. Used
+ * to reduce compiled code size.
+ *
+ * @param {Array | String} a The string or array to interrogate.
+ * @return {Number} The value of the length property.
+ */
+function jsLength(a) {
+ return a.length;
+}
+
+var min = Math.min;
+var max = Math.max;
+var ceil = Math.ceil;
+var floor = Math.floor;
+var round = Math.round;
+var abs = Math.abs;
+
+/**
+ * Copies all properties from second object to the first. Modifies to.
+ *
+ * @param {Object} to The target object.
+ * @param {Object} from The source object.
+ */
+function copyProperties(to, from) {
+ foreachin(from, function(p) {
+ to[p] = from[p];
+ });
+}
+
+/**
+ * Iterates over the array, calling the given function for each
+ * element.
+ *
+ * @param {Array} array
+ * @param {Function} fn
+ */
+function foreach(array, fn) {
+ var I = jsLength(array);
+ for (var i = 0; i < I; ++i) {
+ fn(array[i], i);
+ }
+}
+
+/**
+ * Safely iterates over all properties of the given object, calling
+ * the given function for each property. If opt_all isn't true, uses
+ * hasOwnProperty() to assure the property is on the object, not on
+ * its prototype.
+ *
+ * @param {Object} object
+ * @param {Function} fn
+ * @param {Boolean} opt_all If true, also iterates over inherited properties.
+ */
+function foreachin(object, fn, opt_all) {
+ for (var i in object) {
+ if (opt_all || !object.hasOwnProperty || object.hasOwnProperty(i)) {
+ fn(i, object[i]);
+ }
+ }
+}
+
+/**
+ * Appends the second array to the first, copying its elements.
+ * Optionally only a slice of the second array is copied.
+ *
+ * @param {Array} a1 Target array (modified).
+ * @param {Array} a2 Source array.
+ * @param {Number} opt_begin Begin of slice of second array (optional).
+ * @param {Number} opt_end End (exclusive) of slice of second array (optional).
+ */
+function arrayAppend(a1, a2, opt_begin, opt_end) {
+ var i0 = opt_begin || 0;
+ var i1 = opt_end || jsLength(a2);
+ for (var i = i0; i < i1; ++i) {
+ a1.push(a2[i]);
+ }
+}
+
+/**
+ * Trim whitespace from begin and end of string.
+ *
+ * @see testStringTrim();
+ *
+ * @param {String} str Input string.
+ * @return {String} Trimmed string.
+ */
+function stringTrim(str) {
+ return stringTrimRight(stringTrimLeft(str));
+}
+
+/**
+ * Trim whitespace from beginning of string.
+ *
+ * @see testStringTrimLeft();
+ *
+ * @param {String} str Input string.
+ * @return {String} Trimmed string.
+ */
+function stringTrimLeft(str) {
+ return str.replace(/^\s+/, "");
+}
+
+/**
+ * Trim whitespace from end of string.
+ *
+ * @see testStringTrimRight();
+ *
+ * @param {String} str Input string.
+ * @return {String} Trimmed string.
+ */
+function stringTrimRight(str) {
+ return str.replace(/\s+$/, "");
+}
+
+/**
+ * Jscompiler wrapper for parseInt() with base 10.
+ *
+ * @param {String} s String repersentation of a number.
+ *
+ * @return {Number} The integer contained in s, converted on base 10.
+ */
+function parseInt10(s) {
+ return parseInt(s, 10);
+}
+/**
+ * @fileoverview A simple formatter to project JavaScript data into
+ * HTML templates. The template is edited in place. I.e. in order to
+ * instantiate a template, clone it from the DOM first, and then
+ * process the cloned template. This allows for updating of templates:
+ * If the templates is processed again, changed values are merely
+ * updated.
+ *
+ * NOTE: IE DOM doesn't have importNode().
+ *
+ * NOTE: The property name "length" must not be used in input
+ * data, see comment in jstSelect_().
+ */
+
+
+/**
+ * Names of jstemplate attributes. These attributes are attached to
+ * normal HTML elements and bind expression context data to the HTML
+ * fragment that is used as template.
+ */
+var ATT_select = 'jsselect';
+var ATT_instance = 'jsinstance';
+var ATT_display = 'jsdisplay';
+var ATT_values = 'jsvalues';
+var ATT_eval = 'jseval';
+var ATT_transclude = 'transclude';
+var ATT_content = 'jscontent';
+
+
+/**
+ * Names of special variables defined by the jstemplate evaluation
+ * context. These can be used in js expression in jstemplate
+ * attributes.
+ */
+var VAR_index = '$index';
+var VAR_this = '$this';
+
+
+/**
+ * Context for processing a jstemplate. The context contains a context
+ * object, whose properties can be referred to in jstemplate
+ * expressions, and it holds the locally defined variables.
+ *
+ * @param {Object} opt_data The context object. Null if no context.
+ *
+ * @param {Object} opt_parent The parent context, from which local
+ * variables are inherited. Normally the context object of the parent
+ * context is the object whose property the parent object is. Null for the
+ * context of the root object.
+ *
+ * @constructor
+ */
+function JsExprContext(opt_data, opt_parent) {
+ var me = this;
+
+ /**
+ * The local context of the input data in which the jstemplate
+ * expressions are evaluated. Notice that this is usually an Object,
+ * but it can also be a scalar value (and then still the expression
+ * $this can be used to refer to it). Notice this can be a scalar
+ * value, including undefined.
+ *
+ * @type {Object}
+ */
+ me.data_ = opt_data;
+
+ /**
+ * The context for variable definitions in which the jstemplate
+ * expressions are evaluated. Other than for the local context,
+ * which replaces the parent context, variable definitions of the
+ * parent are inherited. The special variable $this points to data_.
+ *
+ * @type {Object}
+ */
+ me.vars_ = {};
+ if (opt_parent) {
+ copyProperties(me.vars_, opt_parent.vars_);
+ }
+ this.vars_[VAR_this] = me.data_;
+}
+
+
+/**
+ * Evaluates the given expression in the context of the current
+ * context object and the current local variables.
+ *
+ * @param {String} expr A javascript expression.
+ *
+ * @param {Element} template DOM node of the template.
+ *
+ * @return The value of that expression.
+ */
+JsExprContext.prototype.jseval = function(expr, template) {
+ with (this.vars_) {
+ with (this.data_) {
+ try {
+ return (function() {
+ return eval('[' + expr + '][0]');
+ }).call(template);
+ } catch (e) {
+ return null;
+ }
+ }
+ }
+}
+
+
+/**
+ * Clones the current context for a new context object. The cloned
+ * context has the data object as its context object and the current
+ * context as its parent context. It also sets the $index variable to
+ * the given value. This value usually is the position of the data
+ * object in a list for which a template is instantiated multiply.
+ *
+ * @param {Object} data The new context object.
+ *
+ * @param {Number} index Position of the new context when multiply
+ * instantiated. (See implementation of jstSelect().)
+ *
+ * @return {JsExprContext}
+ */
+JsExprContext.prototype.clone = function(data, index) {
+ var ret = new JsExprContext(data, this);
+ ret.setVariable(VAR_index, index);
+ if (this.resolver_) {
+ ret.setSubTemplateResolver(this.resolver_);
+ }
+ return ret;
+}
+
+
+/**
+ * Binds a local variable to the given value. If set from jstemplate
+ * jsvalue expressions, variable names must start with $, but in the
+ * API they only have to be valid javascript identifier.
+ *
+ * @param {String} name
+ *
+ * @param {Object} value
+ */
+JsExprContext.prototype.setVariable = function(name, value) {
+ this.vars_[name] = value;
+}
+
+
+/**
+ * Sets the function used to resolve the values of the transclude
+ * attribute into DOM nodes. By default, this is jstGetTemplate(). The
+ * value set here is inherited by clones of this context.
+ *
+ * @param {Function} resolver The function used to resolve transclude
+ * ids into a DOM node of a subtemplate. The DOM node returned by this
+ * function will be inserted into the template instance being
+ * processed. Thus, the resolver function must instantiate the
+ * subtemplate as necessary.
+ */
+JsExprContext.prototype.setSubTemplateResolver = function(resolver) {
+ this.resolver_ = resolver;
+}
+
+
+/**
+ * Resolves a sub template from an id. Used to process the transclude
+ * attribute. If a resolver function was set using
+ * setSubTemplateResolver(), it will be used, otherwise
+ * jstGetTemplate().
+ *
+ * @param {String} id The id of the sub template.
+ *
+ * @return {Node} The root DOM node of the sub template, for direct
+ * insertion into the currently processed template instance.
+ */
+JsExprContext.prototype.getSubTemplate = function(id) {
+ return (this.resolver_ || jstGetTemplate).call(this, id);
+}
+
+
+/**
+ * HTML template processor. Data values are bound to HTML templates
+ * using the attributes transclude, jsselect, jsdisplay, jscontent,
+ * jsvalues. The template is modifed in place. The values of those
+ * attributes are JavaScript expressions that are evaluated in the
+ * context of the data object fragment.
+ *
+ * @param {JsExprContext} context Context created from the input data
+ * object.
+ *
+ * @param {Element} template DOM node of the template. This will be
+ * processed in place. After processing, it will still be a valid
+ * template that, if processed again with the same data, will remain
+ * unchanged.
+ */
+function jstProcess(context, template) {
+ var processor = new JstProcessor();
+ processor.run_([ processor, processor.jstProcess_, context, template ]);
+}
+
+
+/**
+ * Internal class used by jstemplates to maintain context.
+ * NOTE: This is necessary to process deep templates in Safari
+ * which has a relatively shallow stack.
+ * @class
+ */
+function JstProcessor() {
+}
+
+
+/**
+ * Runs the state machine, beginning with function "start".
+ *
+ * @param {Array} start The first function to run, in the form
+ * [object, method, args ...]
+ */
+JstProcessor.prototype.run_ = function(start) {
+ var me = this;
+
+ me.queue_ = [ start ];
+ while (jsLength(me.queue_)) {
+ var f = me.queue_.shift();
+ f[1].apply(f[0], f.slice(2));
+ }
+}
+
+
+/**
+ * Appends a function to be called later.
+ * Analogous to calling that function on a subsequent line, or a subsequent
+ * iteration of a loop.
+ *
+ * @param {Array} f A function in the form [object, method, args ...]
+ */
+JstProcessor.prototype.enqueue_ = function(f) {
+ this.queue_.push(f);
+}
+
+
+/**
+ * Implements internals of jstProcess.
+ *
+ * @param {JsExprContext} context
+ *
+ * @param {Element} template
+ */
+JstProcessor.prototype.jstProcess_ = function(context, template) {
+ var me = this;
+
+ var transclude = domGetAttribute(template, ATT_transclude);
+ if (transclude) {
+ var tr = context.getSubTemplate(transclude);
+ if (tr) {
+ domReplaceChild(tr, template);
+ me.enqueue_([ me, me.jstProcess_, context, tr ]);
+ } else {
+ domRemoveNode(template);
+ }
+ return;
+ }
+
+ var select = domGetAttribute(template, ATT_select);
+ if (select) {
+ me.jstSelect_(context, template, select);
+ return;
+ }
+
+ var display = domGetAttribute(template, ATT_display);
+ if (display) {
+ if (!context.jseval(display, template)) {
+ displayNone(template);
+ return;
+ }
+
+ displayDefault(template);
+ }
+
+
+ var values = domGetAttribute(template, ATT_values);
+ if (values) {
+ me.jstValues_(context, template, values);
+ }
+
+ var expressions = domGetAttribute(template, ATT_eval);
+ if (expressions) {
+ foreach(expressions.split(/\s*;\s*/), function(expression) {
+ expression = stringTrim(expression);
+ if (jsLength(expression)) {
+ context.jseval(expression, template);
+ }
+ });
+ }
+
+ var content = domGetAttribute(template, ATT_content);
+ if (content) {
+ me.jstContent_(context, template, content);
+
+ } else {
+ var childnodes = [];
+ for (var i = 0; i < jsLength(template.childNodes); ++i) {
+ if (template.childNodes[i].nodeType == DOM_ELEMENT_NODE) {
+ me.enqueue_(
+ [ me, me.jstProcess_, context, template.childNodes[i] ]);
+ }
+ }
+ }
+}
+
+
+/**
+ * Implements the jsselect attribute: evalutes the value of the
+ * jsselect attribute in the current context, with the current
+ * variable bindings (see JsExprContext.jseval()). If the value is an
+ * array, the current template node is multiplied once for every
+ * element in the array, with the array element being the context
+ * object. If the array is empty, or the value is undefined, then the
+ * current template node is dropped. If the value is not an array,
+ * then it is just made the context object.
+ *
+ * @param {JsExprContext} context The current evaluation context.
+ *
+ * @param {Element} template The currently processed node of the template.
+ *
+ * @param {String} select The javascript expression to evaluate.
+ *
+ * @param {Function} process The function to continue processing with.
+ */
+JstProcessor.prototype.jstSelect_ = function(context, template, select) {
+ var me = this;
+
+ var value = context.jseval(select, template);
+ domRemoveAttribute(template, ATT_select);
+
+ var instance = domGetAttribute(template, ATT_instance);
+ var instance_last = false;
+ if (instance) {
+ if (instance.charAt(0) == '*') {
+ instance = parseInt10(instance.substr(1));
+ instance_last = true;
+ } else {
+ instance = parseInt10(instance);
+ }
+ }
+
+ var multiple = (value !== null &&
+ typeof value == 'object' &&
+ typeof value.length == 'number');
+ var multiple_empty = (multiple && value.length == 0);
+
+ if (multiple) {
+ if (multiple_empty) {
+ if (!instance) {
+ domSetAttribute(template, ATT_select, select);
+ domSetAttribute(template, ATT_instance, '*0');
+ displayNone(template);
+ } else {
+ domRemoveNode(template);
+ }
+
+ } else {
+ displayDefault(template);
+ if (instance === null || instance === "" || instance === undefined ||
+ (instance_last && instance < jsLength(value) - 1)) {
+ var templatenodes = [];
+ var instances_start = instance || 0;
+ for (var i = instances_start + 1; i < jsLength(value); ++i) {
+ var node = domCloneNode(template);
+ templatenodes.push(node);
+ domInsertBefore(node, template);
+ }
+ templatenodes.push(template);
+
+ for (var i = 0; i < jsLength(templatenodes); ++i) {
+ var ii = i + instances_start;
+ var v = value[ii];
+ var t = templatenodes[i];
+
+ me.enqueue_([ me, me.jstProcess_, context.clone(v, ii), t ]);
+ var instanceStr = (ii == jsLength(value) - 1 ? '*' : '') + ii;
+ me.enqueue_(
+ [ null, postProcessMultiple_, t, select, instanceStr ]);
+ }
+
+ } else if (instance < jsLength(value)) {
+ var v = value[instance];
+
+ me.enqueue_(
+ [me, me.jstProcess_, context.clone(v, instance), template]);
+ var instanceStr = (instance == jsLength(value) - 1 ? '*' : '')
+ + instance;
+ me.enqueue_(
+ [ null, postProcessMultiple_, template, select, instanceStr ]);
+ } else {
+ domRemoveNode(template);
+ }
+ }
+ } else {
+ if (value == null) {
+ domSetAttribute(template, ATT_select, select);
+ displayNone(template);
+ } else {
+ me.enqueue_(
+ [ me, me.jstProcess_, context.clone(value, 0), template ]);
+ me.enqueue_(
+ [ null, postProcessSingle_, template, select ]);
+ }
+ }
+}
+
+
+/**
+ * Sets ATT_select and ATT_instance following recursion to jstProcess.
+ *
+ * @param {Element} template The template
+ *
+ * @param {String} select The jsselect string
+ *
+ * @param {String} instanceStr The new value for the jsinstance attribute
+ */
+function postProcessMultiple_(template, select, instanceStr) {
+ domSetAttribute(template, ATT_select, select);
+ domSetAttribute(template, ATT_instance, instanceStr);
+}
+
+
+/**
+ * Sets ATT_select and makes the element visible following recursion to
+ * jstProcess.
+ *
+ * @param {Element} template The template
+ *
+ * @param {String} select The jsselect string
+ */
+function postProcessSingle_(template, select) {
+ domSetAttribute(template, ATT_select, select);
+ displayDefault(template);
+}
+
+
+/**
+ * Implements the jsvalues attribute: evaluates each of the values and
+ * assigns them to variables in the current context (if the name
+ * starts with '$', javascript properties of the current template node
+ * (if the name starts with '.'), or DOM attributes of the current
+ * template node (otherwise). Since DOM attribute values are always
+ * strings, the value is coerced to string in the latter case,
+ * otherwise it's the uncoerced javascript value.
+ *
+ * @param {JsExprContext} context Current evaluation context.
+ *
+ * @param {Element} template Currently processed template node.
+ *
+ * @param {String} valuesStr Value of the jsvalues attribute to be
+ * processed.
+ */
+JstProcessor.prototype.jstValues_ = function(context, template, valuesStr) {
+ var values = valuesStr.split(/\s*;\s*/);
+ for (var i = 0; i < jsLength(values); ++i) {
+ var colon = values[i].indexOf(':');
+ if (colon < 0) {
+ continue;
+ }
+ var label = stringTrim(values[i].substr(0, colon));
+ var value = context.jseval(values[i].substr(colon + 1), template);
+
+ if (label.charAt(0) == '$') {
+ context.setVariable(label, value);
+
+ } else if (label.charAt(0) == '.') {
+ var nameSpaceLabel = label.substr(1).split('.');
+ var nameSpaceObject = template;
+ var nameSpaceDepth = jsLength(nameSpaceLabel);
+ for (var j = 0, J = nameSpaceDepth - 1; j < J; ++j) {
+ var jLabel = nameSpaceLabel[j];
+ if (!nameSpaceObject[jLabel]) {
+ nameSpaceObject[jLabel] = {};
+ }
+ nameSpaceObject = nameSpaceObject[jLabel];
+ }
+ nameSpaceObject[nameSpaceLabel[nameSpaceDepth - 1]] = value;
+ } else if (label) {
+ if (typeof value == 'boolean') {
+ if (value) {
+ domSetAttribute(template, label, label);
+ } else {
+ domRemoveAttribute(template, label);
+ }
+ } else {
+ domSetAttribute(template, label, '' + value);
+ }
+ }
+ }
+}
+
+
+/**
+ * Implements the jscontent attribute. Evalutes the expression in
+ * jscontent in the current context and with the current variables,
+ * and assigns its string value to the content of the current template
+ * node.
+ *
+ * @param {JsExprContext} context Current evaluation context.
+ *
+ * @param {Element} template Currently processed template node.
+ *
+ * @param {String} content Value of the jscontent attribute to be
+ * processed.
+ */
+JstProcessor.prototype.jstContent_ = function(context, template, content) {
+ var value = '' + context.jseval(content, template);
+ if (template.innerHTML == value) {
+ return;
+ }
+ while (template.firstChild) {
+ domRemoveNode(template.firstChild);
+ }
+ var t = domCreateTextNode(ownerDocument(template), value);
+ domAppendChild(template, t);
+}
+
+
+/**
+ * Helps to implement the transclude attribute, and is the initial
+ * call to get hold of a template from its ID.
+ *
+ * @param {String} name The ID of the HTML element used as template.
+ *
+ * @returns {Element} The DOM node of the template. (Only element
+ * nodes can be found by ID, hence it's a Element.)
+ */
+function jstGetTemplate(name) {
+ var section = domGetElementById(document, name);
+ if (section) {
+ var ret = domCloneNode(section);
+ domRemoveAttribute(ret, 'id');
+ return ret;
+ } else {
+ return null;
+ }
+}
+
+window['jstGetTemplate'] = jstGetTemplate;
+window['jstProcess'] = jstProcess;
+window['JsExprContext'] = JsExprContext;
diff --git a/chrome/common/extensions/docs/examples/api/tabs/inspector/manifest.json b/chrome/common/extensions/docs/examples/api/tabs/inspector/manifest.json
new file mode 100644
index 0000000..1cf3576
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/inspector/manifest.json
@@ -0,0 +1,10 @@
+{
+ "name": "Tab Inspector",
+ "description": "Utility for working with the extension tabs api",
+ "version": "0.1",
+ "permissions": ["tabs"],
+ "background_page": "background.html",
+ "browser_action": {
+ "default_title": "show tab inspector"
+ }
+}
diff --git a/chrome/common/extensions/docs/examples/api/tabs/inspector/tabs_api.html b/chrome/common/extensions/docs/examples/api/tabs/inspector/tabs_api.html
new file mode 100644
index 0000000..e8047ab
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/inspector/tabs_api.html
@@ -0,0 +1,388 @@
+<html>
+<head>
+<script src="jstemplate_compiled.js" type="text/javascript"></script>
+<script>
+
+tabs = {};
+tabIds = [];
+
+focusedWindowId = undefined;
+currentWindowId = undefined;
+
+function bootStrap() {
+ chrome.windows.getCurrent(function(currentWindow) {
+ currentWindowId = currentWindow.id;
+ chrome.windows.getLastFocused(function(focusedWindow) {
+ focusedWindowId = focusedWindow.id;
+ loadWindowList();
+ });
+ });
+}
+
+function isInt(i) {
+ return (typeof i == "number") && !(i % 1) && !isNaN(i);
+}
+
+function loadWindowList() {
+ chrome.windows.getAll({ populate: true }, function(windowList) {
+ tabs = {};
+ tabIds = [];
+ for (var i = 0; i < windowList.length; i++) {
+ windowList[i].current = (windowList[i].id == currentWindowId);
+ windowList[i].focused = (windowList[i].id == focusedWindowId);
+
+ for (var j = 0; j < windowList[i].tabs.length; j++) {
+ tabIds[tabIds.length] = windowList[i].tabs[j].id;
+ tabs[windowList[i].tabs[j].id] = windowList[i].tabs[j];
+ }
+ }
+
+ var input = new JsExprContext(windowList);
+ var output = document.getElementById('windowList');
+ jstProcess(input, output);
+ });
+}
+
+function updateTabData(id) {
+ var retval = {
+ url: document.getElementById('url_' + id).value,
+ selected: document.getElementById('selected_' + id).value ? true : false
+ }
+
+ return retval;
+}
+
+function updateTab(id){
+ try {
+ chrome.tabs.update(id, updateTabData(id));
+ } catch (e) {
+ alert(e);
+ }
+}
+
+function moveTabData(id) {
+ return {
+ 'index': parseInt(document.getElementById('index_' + id).value),
+ 'windowId': parseInt(document.getElementById('windowId_' + id).value)
+ }
+}
+function moveTab(id) {
+ try {
+ chrome.tabs.move(id, moveTabData(id));
+ } catch (e) {
+ alert(e);
+ }
+}
+
+function createTabData(id) {
+ return {
+ 'index': parseInt(document.getElementById('index_' + id).value),
+ 'windowId': parseInt(document.getElementById('windowId_' + id).value),
+ 'index': parseInt(document.getElementById('index_' + id).value),
+ 'url': document.getElementById('url_' + id).value,
+ 'selected': document.getElementById('selected_' + id).value ? true : false
+ }
+}
+
+function createTab() {
+ var args = createTabData('new')
+
+ if (!isInt(args.windowId))
+ delete args.windowId;
+ if (!isInt(args.index))
+ delete args.index;
+
+ try {
+ chrome.tabs.create(args);
+ } catch (e) {
+ alert(e);
+ }
+}
+
+function updateAll() {
+ try {
+ for (var i = 0; i < tabIds.length; i++) {
+ chrome.tabs.update(tabIds[i], updateTabData(tabIds[i]));
+ }
+ } catch(e) {
+ alert(e);
+ }
+}
+
+function moveAll() {
+ appendToLog('moving all');
+ try {
+ for (var i = 0; i < tabIds.length; i++) {
+ chrome.tabs.move(tabIds[i], moveTabData(tabIds[i]));
+ }
+ } catch(e) {
+ alert(e);
+ }
+}
+
+function removeTab(tabId) {
+ try {
+ chrome.tabs.remove(tabId, function() {
+ appendToLog('tab: ' + tabId + ' removed.');
+ });
+ } catch (e) {
+ alert(e);
+ }
+}
+
+function appendToLog(logLine) {
+ var log = document.getElementById('log');
+ log.innerHTML = '<div> &gt;&nbsp;' + logLine + '</div>' + log.innerHTML;
+}
+
+function clearLog() {
+ document.getElementById('log').innerHTML = '';
+}
+
+chrome.windows.onCreated.addListener(function(createInfo) {
+ appendToLog('windows.onCreated -- window: ' + createInfo.id);
+ loadWindowList();
+});
+
+chrome.windows.onFocusChanged.addListener(function(windowId) {
+ focusedWindowId = windowId;
+ appendToLog('windows.onFocusChanged -- window: ' + windowId);
+ loadWindowList();
+});
+
+chrome.windows.onRemoved.addListener(function(windowId) {
+ appendToLog('windows.onRemoved -- window: ' + windowId);
+ loadWindowList();
+});
+
+chrome.tabs.onCreated.addListener(function(tab) {
+ appendToLog('tabs.onCreated -- window: ' + tab.windowId + ' tab: ' + tab.id + ' title: ' + tab.title + ' index ' + tab.index + ' url ' + tab.url);
+ loadWindowList();
+});
+
+chrome.tabs.onAttached.addListener(function(tabId, props) {
+ appendToLog('tabs.onAttached -- window: ' + props.newWindowId + ' tab: ' + tabId + ' index ' + props.newPosition);
+ loadWindowList();
+});
+
+chrome.tabs.onMoved.addListener(function(tabId, props) {
+ appendToLog('tabs.onMoved -- window: ' + props.windowId + ' tab: ' + tabId + ' from ' + props.fromIndex + ' to ' + props.toIndex);
+ loadWindowList();
+});
+
+function refreshTab(tabId) {
+ chrome.tabs.get(tabId, function(tab) {
+ var input = new JsExprContext(tab);
+ var output = document.getElementById('tab_' + tab.id);
+ jstProcess(input, output);
+ appendToLog('tab refreshed -- tabId: ' + tab.id + ' url: ' + tab.url);
+ });
+}
+
+chrome.tabs.onUpdated.addListener(function(tabId, props) {
+ appendToLog('tabs.onUpdated -- tab: ' + tabId + ' status ' + props.status + ' url ' + props.url);
+ refreshTab(tabId);
+});
+
+chrome.tabs.onDetached.addListener(function(tabId, props) {
+ appendToLog('tabs.onDetached -- window: ' + props.oldWindowId + ' tab: ' + tabId + ' index ' + props.oldPosition);
+ loadWindowList();
+});
+
+chrome.tabs.onSelectionChanged.addListener(function(tabId, props) {
+ appendToLog('tabs.onSelectionChanged -- window: ' + props.windowId + ' tab: ' + tabId);
+ loadWindowList();
+});
+
+chrome.tabs.onRemoved.addListener(function(tabId) {
+ appendToLog('tabs.onRemoved -- tab: ' + tabId);
+ loadWindowList();
+});
+
+function createWindow() {
+ var args = {
+ 'left': parseInt(document.getElementById('new_window_left').value),
+ 'top': parseInt(document.getElementById('new_window_top').value),
+ 'width': parseInt(document.getElementById('new_window_width').value),
+ 'height': parseInt(document.getElementById('new_window_height').value),
+ 'url': document.getElementById('new_window_url').value
+ }
+
+ if (!isInt(args.left))
+ delete args.left;
+ if (!isInt(args.top))
+ delete args.top;
+ if (!isInt(args.width))
+ delete args.width;
+ if (!isInt(args.height))
+ delete args.height;
+ if (!args.url)
+ delete args.url;
+
+ try {
+ chrome.windows.create(args);
+ } catch(e) {
+ alert(e);
+ }
+}
+
+function refreshWindow(windowId) {
+ chrome.windows.get(windowId, function(window) {
+ chrome.tabs.getAllInWindow(window.id, function(tabList) {
+ window.tabs = tabList;
+ var input = new JsExprContext(window);
+ var output = document.getElementById('window_' + window.id);
+ jstProcess(input, output);
+ appendToLog('window refreshed -- windowId: ' + window.id + ' tab count:' + window.tabs.length);
+ });
+ });
+}
+
+function updateWindowData(id) {
+ var retval = {
+ left: parseInt(document.getElementById('left_' + id).value),
+ top: parseInt(document.getElementById('top_' + id).value),
+ width: parseInt(document.getElementById('width_' + id).value),
+ height: parseInt(document.getElementById('height_' + id).value)
+ }
+ if (!isInt(retval.left))
+ delete retval.left;
+ if (!isInt(retval.top))
+ delete retval.top;
+ if (!isInt(retval.width))
+ delete retval.width;
+ if (!isInt(retval.height))
+ delete retval.height;
+
+ return retval;
+}
+
+function updateWindow(id){
+ try {
+ chrome.windows.update(id, updateWindowData(id));
+ } catch (e) {
+ alert(e);
+ }
+}
+
+function removeWindow(windowId) {
+ try {
+ chrome.windows.remove(windowId, function() {
+ appendToLog('window: ' + windowId + ' removed.');
+ });
+ } catch (e) {
+ alert(e);
+ }
+}
+
+function refreshSelectedTab(windowId) {
+ chrome.tabs.getSelected(windowId, function(tab) {
+ var input = new JsExprContext(tab);
+ var output = document.getElementById('tab_' + tab.id);
+ jstProcess(input, output);
+ appendToLog('selected tab refreshed -- tabId: ' + tab.id + ' url:' + tab.url);
+ });
+}
+
+</script>
+</head>
+ <body onload="bootStrap();">
+ <div id="windowList">
+ <div style="background-color: #AAEEEE; margin: 4px; padding: 8px; margin: 20px" jsselect="$this"
+ jsvalues="id:'window_' + id">
+ <div style="font-style: italic; width: 80px; display: inline-block">
+ Window: <span jscontent="id"></span>
+ </div>
+ <div style="display: inline-block">
+ left: <input style="width: 60px" type="text" jsvalues="value:$this.left;id:'left_' + id" />
+ top: <input style="width: 60px" type="text" jsvalues="value:$this.top;id:'top_' + id" />
+ width: <input style="width: 60px" type="text" jsvalues="value:$this.width;id:'width_' + id" />
+ height: <input style="width: 60px" type="text" jsvalues="value:$this.height;id:'height_' + id" />
+ <input type="checkbox" jsvalues="checked:focused; id:'focused_' + id" /> Focused
+ <input type="checkbox" jsvalues="checked:current; id:'current_' + id" /> Current
+ <button onclick="refreshWindow(this.jstdata);" jsvalues=".jstdata:id">Refresh</button>
+ </div>
+ <div id="tabList">
+ <div jsselect="tabs">
+ <div style="background-color: #EEEEEE; margin: 8px; padding: 4px" jsvalues="id:'tab_' + id">
+ <div style="margin: 8px">
+ <div style="font-style: italic; width: 80px; display: inline-block" jscontent="'TabId: ' + id"></div>
+ <div style="width: 300px; display: inline-block">
+ index: <input style="width: 20px" type="text" jsvalues="value:$this.index;id:'index_' + id" />
+ windowId: <input style="width: 20px" type="text" jsvalues="value:windowId;id:'windowId_' + id" />
+ <button onclick="moveTab(this.jstdata);" jsvalues=".jstdata:id">Move</button>
+ <button onclick="refreshTab(this.jstdata);" jsvalues=".jstdata:id">Refresh</button>
+ </div>
+ </div>
+ <div style="margin: 8px">
+ <div>
+ <div style="width: 40px; display:inline-block">title:</div>
+ <input style="width: 90%" type="text" jsvalues="value:title;id:'title_' + id" />
+ </div>
+ <div>
+ <div style="width: 40px; display:inline-block">url:</div>
+ <input style="width: 90%" type="text" jsvalues="value:url;id:'url_' + id" />
+ </div>
+ <div><input type="checkbox" jsvalues="checked:selected; id:'selected_' + id" /> Selected</div>
+ </div>
+ <button onclick="updateTab(this.jstdata)" jsvalues=".jstdata:id">Update Tab</button>
+ <button onclick="removeTab(this.jstdata);" jsvalues=".jstdata:id">Close Tab</button>
+ </div>
+ </div>
+ </div>
+ <button onclick="updateWindow(this.jstdata);" jsvalues=".jstdata:id">Update Window</button>
+ <button onclick="removeWindow(this.jstdata);" jsvalues=".jstdata:id">Close Window</button>
+ <button onclick="refreshSelectedTab(this.jstdata);" jsvalues=".jstdata:id">Refresh Selected Tab</button>
+ </div>
+ </div>
+ <div style="background-color: #EEEEBB; margin: 20px; padding: 8px">
+ <h3 style="text-align: center; margin: 8px"> Create Window</h3>
+ <div style="margin: 8px">
+ <div style="width: 300px; display: inline-block">
+ left: <input style="width: 20px" type="text" id="new_window_left" />
+ top: <input style="width: 20px" type="text" id="new_window_top" />
+ width: <input style="width: 20px" type="text" id="new_window_width" />
+ height: <input style="width: 20px" type="text" id="new_window_height" />
+ </div>
+ </div>
+ <div style="margin: 8px">
+ <div>
+ <div style="width: 40px; display:inline-block">url:</div>
+ <input style="width: 90%" type="text" id="new_window_url" />
+ </div>
+ </div>
+ <button onclick="createWindow();">Create</button>
+ </div>
+ <div style="background-color: #EEEEAA; margin: 20px; padding: 8px">
+ <h3 style="text-align: center; margin: 8px"> Create Tab</h3>
+ <div style="margin: 8px">
+ <div style="width: 300px; display: inline-block">
+ index: <input style="width: 20px" type="text" id="index_new" />
+ windowId: <input style="width: 20px" type="text" id="windowId_new" />
+ <button onclick="moveTab(this.jstdata);" jsvalues=".jstdata:id">Move</button>
+ </div>
+ </div>
+ <div style="margin: 8px">
+ <div>
+ <div style="width: 40px; display:inline-block">title:</div>
+ <input style="width: 90%" type="text" id="title_new" />
+ </div>
+ <div>
+ <div style="width: 40px; display:inline-block">url:</div>
+ <input style="width: 90%" type="text" id="url_new" />
+ </div>
+ <div><input type="checkbox" id="selected_new" /> Selected</div>
+ </div>
+ <button onclick="createTab();">Create</button>
+ </div>
+ <div style="margin: 20px;">
+ <button onclick="loadWindowList();">Refresh</button>
+ <button onclick="updateAll();">Update All</button>
+ <button onclick="moveAll();">Move All</button>
+ <button onclick="clearLog();">-->Clear Log</button>
+ <button onclick="chrome.windows.create();">New Window</button>
+ </div>
+ <div id="log" style="background-color: #EEAAEE; margin: 20px; padding: 8px">
+ </div>
+ </body>
+</html> \ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/tabs/screenshot/background.html b/chrome/common/extensions/docs/examples/api/tabs/screenshot/background.html
new file mode 100644
index 0000000..2955688
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/screenshot/background.html
@@ -0,0 +1,10 @@
+
+<html>
+<head>
+<script src='screenshot.js' type='text/javascript' />
+<script>
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/tabs/screenshot/camera.png b/chrome/common/extensions/docs/examples/api/tabs/screenshot/camera.png
new file mode 100755
index 0000000..be26c39
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/screenshot/camera.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/tabs/screenshot/manifest.json b/chrome/common/extensions/docs/examples/api/tabs/screenshot/manifest.json
new file mode 100755
index 0000000..37fdbf0
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/screenshot/manifest.json
@@ -0,0 +1,11 @@
+{
+ "name": "Test Screenshot Extension",
+ "version": "1.0",
+ "description": "Demonstrate screenshot functionality in the chrome.tabs api.",
+ "background_page": "background.html",
+ "browser_action": {
+ "default_icon": "camera.png",
+ "default_title": "Take a screen shot!"
+ },
+ "permissions": ["tabs"]
+}
diff --git a/chrome/common/extensions/docs/examples/api/tabs/screenshot/screenshot.html b/chrome/common/extensions/docs/examples/api/tabs/screenshot/screenshot.html
new file mode 100644
index 0000000..408d13d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/screenshot/screenshot.html
@@ -0,0 +1,14 @@
+<html>
+<script>
+function setScreenshotUrl(url) {
+ document.getElementById('target').src = url;
+}
+</script>
+<body>
+ Image here:
+ <p>
+ <img id="target" src="white.png" width="640" height="480">
+ <p>
+ End image
+ </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/tabs/screenshot/screenshot.js b/chrome/common/extensions/docs/examples/api/tabs/screenshot/screenshot.js
new file mode 100755
index 0000000..5d47a99
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/screenshot/screenshot.js
@@ -0,0 +1,47 @@
+function takeScreenshot() {
+ chrome.tabs.captureVisibleTab(null, function(img) {
+ var screenshotUrl = img;
+ var viewTabUrl = chrome.extension.getURL('screenshot.html');
+
+ chrome.tabs.create({url: viewTabUrl}, function(tab) {
+ var targetId = tab.id;
+
+ var addSnapshotImageToTab = function(tabId, changedProps) {
+ // We are waiting for the tab we opened to finish loading.
+ // Check that the the tab's id matches the tab we opened,
+ // and that the tab is done loading.
+ if (tabId != targetId || changedProps.status != "complete")
+ return;
+
+ // Passing the above test means this is the event we were waiting for.
+ // There is nothing we need to do for future onUpdated events, so we
+ // use removeListner to stop geting called when onUpdated events fire.
+ chrome.tabs.onUpdated.removeListener(addSnapshotImageToTab);
+
+ // Look through all views to find the window which will display
+ // the screenshot.
+ var views = chrome.extension.getViews();
+ for (var i = 0; i < views.length; i++) {
+ var view = views[i];
+ // If more than one screen shot tab is opened, we need to
+ // ensure that we do not change an existing screen shot image.
+ // view.imageAlreadySet is set to true when an image is set for
+ // the first time. We never change an image in a window with
+ // this flag set.
+ if (view.location.href == viewTabUrl && !view.imageAlreadySet) {
+ view.setScreenshotUrl(screenshotUrl);
+ view.imageAlreadySet = true;
+ break;
+ }
+ }
+ };
+ chrome.tabs.onUpdated.addListener(addSnapshotImageToTab);
+
+ });
+ });
+}
+
+// Listen for a click on the camera icon. On that click, take a screenshot.
+chrome.browserAction.onClicked.addListener(function(tab) {
+ takeScreenshot();
+});
diff --git a/chrome/common/extensions/docs/examples/api/tabs/screenshot/white.png b/chrome/common/extensions/docs/examples/api/tabs/screenshot/white.png
new file mode 100644
index 0000000..06d8aca
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/screenshot/white.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/buildbot/bg.html b/chrome/common/extensions/docs/examples/extensions/buildbot/bg.html
new file mode 100644
index 0000000..1324fb1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/buildbot/bg.html
@@ -0,0 +1,58 @@
+<script>
+var statusURL = "http://chromium-status.appspot.com/current";
+
+function updateStatus(text) {
+ // Page outputs a line like this:
+ // <div class="Notice">Tree is open (issue -> person) </div>
+ // Although what's in the <div> is arbitrary and typed by a person.
+ var results = (new RegExp('"Notice"[^>]*>(.*)<', 'g')).exec(text);
+ if (!results || results.index < 0) {
+ throw new Error("couldn't find status div in " + text);
+ }
+ var status = results[1];
+ chrome.browserAction.setTitle({title:status});
+ var open = /open/i;
+ if (open.exec(status)) {
+ //chrome.browserAction.setBadgeText("\u263A");
+ chrome.browserAction.setBadgeText({text:"\u2022"});
+ chrome.browserAction.setBadgeBackgroundColor({color:[0,255,0,255]});
+ } else {
+ //chrome.browserAction.setBadgeText("\u2639");
+ chrome.browserAction.setBadgeText({text:"\u00D7"});
+ chrome.browserAction.setBadgeBackgroundColor({color:[255,0,0,255]});
+ }
+}
+
+function requestStatus() {
+ requestURL(statusURL, updateStatus);
+ setTimeout(requestStatus, 30000);
+}
+
+function requestURL(url, callback) {
+ console.log("requestURL: " + url);
+ var xhr = new XMLHttpRequest();
+ try {
+ xhr.onreadystatechange = function(state) {
+ if (xhr.readyState == 4) {
+ var text = xhr.responseText;
+ //console.log(text);
+ callback(text);
+ }
+ }
+
+ xhr.onerror = function(error) {
+ console.log("xhr error: " + JSON.stringify(error));
+ console.dir(error);
+ }
+
+ xhr.open("GET", url, true);
+ xhr.send({});
+ } catch(e) {
+ console.log("exception: " + e);
+ }
+}
+
+window.onload = function() {
+ window.setTimeout(requestStatus, 10);
+}
+</script>
diff --git a/chrome/common/extensions/docs/examples/extensions/buildbot/chromium.png b/chrome/common/extensions/docs/examples/extensions/buildbot/chromium.png
new file mode 100755
index 0000000..cc9fb60
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/buildbot/chromium.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/buildbot/icon.png b/chrome/common/extensions/docs/examples/extensions/buildbot/icon.png
new file mode 100755
index 0000000..b3c3474
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/buildbot/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/buildbot/manifest.json b/chrome/common/extensions/docs/examples/extensions/buildbot/manifest.json
new file mode 100644
index 0000000..5e0349c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/buildbot/manifest.json
@@ -0,0 +1,17 @@
+{
+ "name": "Chromium Buildbot Monitor",
+ "version": "0.7.1",
+ "description": "Displays the status of the Chromium buildbot in the toolbar. On hover, expands to give more detail about each failing bot.",
+ "icons": { "128": "icon.png" },
+ "background_page": "bg.html",
+ "permissions": [
+ "http://build.chromium.org/",
+ "http://chromium-status.appspot.com/",
+ "http://chrome-buildbot.corp.google.com/"
+ ],
+ "browser_action": {
+ "default_title": "",
+ "default_icon": "chromium.png",
+ "popup": "popup.html"
+ }
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/buildbot/popup.html b/chrome/common/extensions/docs/examples/extensions/buildbot/popup.html
new file mode 100644
index 0000000..d5b913d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/buildbot/popup.html
@@ -0,0 +1,218 @@
+<head>
+<script>
+var botRoot = "http://build.chromium.org/buildbot";
+//var botRoot = "http://chrome-buildbot.corp.google.com:8010";
+var waterfallURL = botRoot + "/waterfall/bot_status.json?json=1";
+var botList;
+var checkinResults;
+var bots;
+var failures;
+
+function updateBotList(text) {
+ var results = (new RegExp('(.*)<\/body>', 'g')).exec(text);
+ if (!results || results.index < 0) {
+ console.log("Error: couldn't find bot JSON");
+ console.log(text);
+ return;
+ }
+ var data;
+ try {
+ data = JSON.parse(results[1]);
+ } catch (e) {
+ console.dir(e);
+ console.log(text);
+ return;
+ }
+ if (!data) {
+ throw new Error("JSON parse fail: " + results[1]);
+ }
+ botList = data[0];
+ checkinResults = data[1];
+
+ failures = botList.filter(function(bot) {
+ return (bot.color != "success");
+ });
+ displayFailures();
+}
+
+function displayFailures() {
+ var html = "";
+ if (failures.length == 0) {
+ html = "<a href='' onclick='showConsole()' class='open'>" +
+ "The tree is completely green.</a> (no way!)";
+ } else {
+ html = "<div><a class='closed' href='' onclick='showFailures()'>" +
+ "failures:</a></div>";
+ failures.forEach(function(bot, i) {
+ html += "<div class='bot " + bot.color +
+ "' onclick='showBot(" + i + ")'>" +
+ bot.title + "</div>";
+ });
+ }
+ bots.innerHTML = html;
+}
+
+function showURL(url) {
+ window.open(url);
+ window.event.stopPropagation();
+}
+
+function showBot(botIndex) {
+ var bot = failures[botIndex];
+ var url = botRoot + "/waterfall/waterfall?builder=" + bot.name;
+ showURL(url);
+}
+
+function showConsole() {
+ var url = botRoot + "/waterfall/console";
+ showURL(url);
+}
+
+function showTry() {
+ var url = botRoot + "/try-server/waterfall";
+ showURL(url);
+}
+
+function showFyi() {
+ var url = botRoot + "/waterfall.fyi/console";
+ showURL(url);
+}
+
+function showFailures() {
+ var url = botRoot + "/waterfall/waterfall?show_events=true&failures_only=true";
+ showURL(url);
+}
+
+function requestURL(url, callback) {
+ console.log("requestURL: " + url);
+ var xhr = new XMLHttpRequest();
+ try {
+ xhr.onreadystatechange = function(state) {
+ if (xhr.readyState == 4) {
+ var text = xhr.responseText;
+ //console.log(text);
+ callback(text);
+ }
+ }
+
+ xhr.onerror = function(error) {
+ console.log("xhr error: " + JSON.stringify(error));
+ console.dir(error);
+ }
+
+ xhr.open("GET", url, true);
+ xhr.send({});
+ } catch(e) {
+ console.log("exception: " + e);
+ }
+}
+window.onload = function() {
+ bots = document.getElementById("bots");
+
+ // XHR from onload winds up blocking the load, so we put it in a setTimeout.
+ window.setTimeout(requestURL, 0, waterfallURL, updateBotList);
+}
+
+function toggle_size() {
+ if (document.body.className == "big") {
+ document.body.className = "small";
+ } else {
+ document.body.className = "big";
+ }
+}
+
+</script>
+<style>
+body {
+ font: menu;
+ overflow: hidden;
+}
+
+#links {
+ background-color: #efefef;
+ border: 1px solid #cccccc;
+ -webkit-border-radius: 5px;
+ margin-top: 1px;
+ padding: 3px;
+ white-space: nowrap;
+ text-align: center;
+}
+
+a {
+ text-decoration: underline;
+ color: #444;
+}
+
+a:hover {
+ color: black;
+ cursor: pointer;
+}
+
+body.big .bot {
+ -webkit-transition: all .5s ease-out;
+ margin: 20px;
+}
+
+body.small .bot {
+ -webkit-transition: all .5s ease-out;
+}
+
+.bot {
+ cursor: pointer;
+ -webkit-border-radius: 5px;
+ margin-top: 1px;
+ padding: 3px;
+ white-space: nowrap;
+}
+
+.bot:hover {
+ border: 2px solid black;
+ padding: 2px;
+}
+
+.open {
+ color: green;
+}
+
+.closed {
+ color: red;
+}
+
+.running {
+ background-color: rgb(255, 252, 108);
+ border: 1px solid rgb(197, 197, 109);
+}
+
+.notstarted {
+ border: 1px solid rgb(170, 170, 170);
+}
+
+.failure {
+ background-color: rgb(233, 128, 128);
+ border: 1px solid rgb(167, 114, 114);
+}
+
+.warnings {
+ background-color: rgb(255, 195, 67);
+ border: 1px solid rgb(194, 157, 70);
+}
+
+.success {
+ background-color: rgb(143, 223, 95);
+ border: 1px solid rgb(79, 133, 48);
+}
+
+.exception {
+ background-color: rgb(224, 176, 255);
+ border: 1px solid rgb(172, 160, 179);
+}
+</style>
+</head>
+<body onclick="toggle_size()">
+<div id="links">
+<a href="" onclick='showConsole()'>console</a> -
+<a href="" onclick='showTry()'>try</a> -
+<a href="" onclick='showFyi()'>fyi</a>
+</div>
+<div id="bots">Loading....</div>
+</body> \ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/background.html b/chrome/common/extensions/docs/examples/extensions/gmail/background.html
new file mode 100644
index 0000000..eea77b6
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/background.html
@@ -0,0 +1,235 @@
+<html>
+<head>
+<script>
+var animationFrames = 36;
+var animationSpeed = 10; // ms
+var canvas;
+var canvasContext;
+var gmail = "http://mail.google.com/";
+var gmailAtomRef = "http://mail.google.com/mail/feed/atom";
+var loggedInImage;
+var pollInterval = 1000 * 10; // 10 seconds
+var requestTimeout = 1000 * 2; // 5 seconds
+var rotation = 0;
+var unreadCount = -1;
+var loadingAnimation = new LoadingAnimation();
+
+
+// A "loading" animation displayed while we wait for the first response from
+// Gmail. This animates the badge text with a dot that cycles from left to
+// right.
+function LoadingAnimation() {
+ this.timerId_ = 0;
+ this.maxCount_ = 8; // Total number of states in animation
+ this.current_ = 0; // Current state
+ this.maxDot_ = 4; // Max number of dots in animation
+}
+
+LoadingAnimation.prototype.paintFrame = function() {
+ var text = "";
+ for (var i = 0; i < this.maxDot_; i++) {
+ text += (i == this.current_) ? "." : " ";
+ }
+ if (this.current_ >= this.maxDot_)
+ text += "";
+
+ chrome.browserAction.setBadgeText({text:text});
+ this.current_++;
+ if (this.current_ == this.maxCount_)
+ this.current_ = 0;
+}
+
+LoadingAnimation.prototype.start = function() {
+ if (this.timerId_)
+ return;
+
+ var self = this;
+ this.timerId_ = window.setInterval(function() {
+ self.paintFrame();
+ }, 100);
+}
+
+LoadingAnimation.prototype.stop = function() {
+ if (!this.timerId_)
+ return;
+
+ window.clearInterval(this.timerId_);
+ this.timerId_ = 0;
+}
+
+
+chrome.tabs.onUpdated.addListener(function(tabId, changeInfo) {
+ if (changeInfo.url && changeInfo.url.indexOf(gmail) == 0) {
+ console.log("saw gmail! updating...");
+ getInboxCount(function(count) {
+ updateUnreadCount(count);
+ });
+ }
+});
+
+
+function init() {
+ canvas = document.getElementById('canvas');
+ loggedInImage = document.getElementById('logged_in');
+ canvasContext = canvas.getContext('2d');
+
+ chrome.browserAction.setIcon({path: "gmail_logged_in.png"});
+ loadingAnimation.start();
+
+ startRequest();
+}
+
+function scheduleRequest() {
+ window.setTimeout(startRequest, pollInterval);
+}
+
+// ajax stuff
+function startRequest() {
+ getInboxCount(
+ function(count) {
+ loadingAnimation.stop();
+ updateUnreadCount(count);
+ scheduleRequest();
+ },
+ function() {
+ loadingAnimation.stop();
+ showLoggedOut();
+ scheduleRequest();
+ }
+ );
+}
+
+function getInboxCount(onSuccess, onError) {
+ var xhr = new XMLHttpRequest();
+ var abortTimerId = window.setTimeout(function() {
+ xhr.abort();
+ onError();
+ }, requestTimeout);
+
+ function handleSuccess(count) {
+ window.clearTimeout(abortTimerId);
+ if (onSuccess)
+ onSuccess(count);
+ }
+
+ function handleError() {
+ window.clearTimeout(abortTimerId);
+ if (onError)
+ onError();
+ }
+
+ try {
+ console.log("request..");
+ xhr.onreadystatechange = function(){
+ console.log("readystate: " + xhr.readyState);
+ if (xhr.readyState == 4) {
+ console.log("responseText: " + xhr.responseText.substring(0, 200) +
+ "...");
+ if (xhr.responseXML) {
+ var xmlDoc = xhr.responseXML;
+ var fullCountSet = xmlDoc.evaluate("/gmail:feed/gmail:fullcount",
+ xmlDoc, gmailNSResolver, XPathResult.ANY_TYPE, null);
+ var fullCountNode = fullCountSet.iterateNext();
+ if (fullCountNode) {
+ handleSuccess(fullCountNode.textContent);
+ } else {
+ console.log("fullcount not found!");
+ console.error("Error: feed retrieved, but no <fullcount> node " +
+ "found");
+ handleError();
+ }
+ } else {
+ console.log("No responseXML!");
+ }
+ }
+ }
+
+ xhr.onerror = function(error) {
+ console.log("error");
+ console.log(error);
+ handleError();
+ }
+
+ xhr.open("GET", gmailAtomRef, true);
+ xhr.send(null);
+ } catch(e) {
+ console.log("ex: " + e);
+ console.error("exception: " + e);
+ handleError();
+ }
+}
+
+function gmailNSResolver(prefix) {
+ if(prefix == 'gmail') {
+ return 'http://purl.org/atom/ns#';
+ }
+}
+
+function updateUnreadCount(count) {
+ if (unreadCount != count) {
+ unreadCount = count;
+ animateFlip();
+ }
+}
+
+
+function ease(x) {
+ return (1-Math.sin(Math.PI/2+x*Math.PI))/2;
+}
+
+function animateFlip() {
+ rotation += 1/animationFrames;
+ drawIconAtRotation();
+
+ if (rotation <= 1) {
+ setTimeout("animateFlip()", animationSpeed);
+ } else {
+ rotation = 0;
+ drawIconAtRotation();
+ chrome.browserAction.setBadgeText({
+ text: unreadCount != "0" ? unreadCount : ""
+ });
+ chrome.browserAction.setBadgeBackgroundColor({color:[208, 0, 24, 255]});
+ }
+}
+
+function showLoggedOut() {
+ unreadCount = -1;
+ chrome.browserAction.setIcon({path:"gmail_not_logged_in.png"});
+ chrome.browserAction.setBadgeBackgroundColor({color:[190, 190, 190, 230]});
+ chrome.browserAction.setBadgeText({text:"?"});
+}
+
+function drawIconAtRotation() {
+ canvasContext.save();
+ canvasContext.clearRect(0, 0, canvas.width, canvas.height);
+ canvasContext.translate(
+ Math.ceil(canvas.width/2),
+ Math.ceil(canvas.height/2));
+ canvasContext.rotate(2*Math.PI*ease(rotation));
+ canvasContext.drawImage(loggedInImage,
+ -Math.ceil(canvas.width/2),
+ -Math.ceil(canvas.height/2));
+ canvasContext.restore();
+
+ chrome.browserAction.setIcon({imageData:canvasContext.getImageData(0, 0,
+ canvas.width,canvas.height)});
+}
+
+function goToInbox() {
+ chrome.tabs.create({url: gmail});
+}
+
+// Called when the user clicks on the browser action.
+chrome.browserAction.onClicked.addListener(function(tab) {
+ goToInbox();
+});
+
+</script>
+</head>
+<body onload="init()">
+<img id="logged_in" src="gmail_logged_in.png">
+<canvas id="canvas" width="19" height="19">
+</body>
+</html>
+
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/gmail_logged_in.png b/chrome/common/extensions/docs/examples/extensions/gmail/gmail_logged_in.png
new file mode 100644
index 0000000..8d5e256
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/gmail_logged_in.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/gmail_not_logged_in.png b/chrome/common/extensions/docs/examples/extensions/gmail/gmail_not_logged_in.png
new file mode 100644
index 0000000..582d262
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/gmail_not_logged_in.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/icon_128.png b/chrome/common/extensions/docs/examples/extensions/gmail/icon_128.png
new file mode 100755
index 0000000..1ddc33b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/icon_128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/manifest.json b/chrome/common/extensions/docs/examples/extensions/gmail/manifest.json
new file mode 100644
index 0000000..3dc942f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/manifest.json
@@ -0,0 +1,15 @@
+{
+ "name": "Google Mail Checker",
+ "description": "This extension adds a Google Mail button to the toolbar which displays the number of unread messages in your inbox. You can also click the button to open your inbox.",
+ "version": "1.0",
+ "background_page": "background.html",
+ "permissions": [
+ "tabs", "http://*.google.com/", "https://*.google.com/"
+ ],
+ "browser_action": {
+ "default_title": ""
+ },
+ "icons": {
+ "128": "icon_128.png"
+ }
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/mappy/icon.png b/chrome/common/extensions/docs/examples/extensions/mappy/icon.png
new file mode 100755
index 0000000..40ebbff
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/mappy/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/mappy/manifest.json b/chrome/common/extensions/docs/examples/extensions/mappy/manifest.json
new file mode 100755
index 0000000..344920f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/mappy/manifest.json
@@ -0,0 +1,18 @@
+{
+ "name": "Mappy",
+ "version": "0.6",
+ "description": "Finds addresses in the web page you're on and pops up a map window.",
+ "icons": { "128": "icon.png" },
+ "content_scripts": [
+ { "matches": ["http://*/*"], "js": ["mappy_content_script.js"] }
+ ],
+ "permissions": [
+ "tabs",
+ "http://maps.google.com/*"
+ ],
+ "browser_action": {
+ "name": "Display Map",
+ "icons": ["marker.png"],
+ "popup": { "path": "popup.html", "height": 512 }
+ }
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/mappy/mappy_content_script.js b/chrome/common/extensions/docs/examples/extensions/mappy/mappy_content_script.js
new file mode 100755
index 0000000..7e17af7
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/mappy/mappy_content_script.js
@@ -0,0 +1,55 @@
+// find map on demand
+
+console.log("mappy_content_script.js loaded");
+
+var maps_key = "ABQIAAAATfHumDbW3OmRByfquHd3SRTRERdeAiwZ9EeJWta3L_JZVS0bOBRQeZgr4K0xyVKzUdnnuFl8X9PX0w";
+
+chrome.extension.onConnect.addListener(function(port) {
+ //console.log("extension connected");
+ port.onMessage.addListener(function(data) {
+ //console.log("extension sent message");
+ findAddress(port);
+ });
+});
+
+var findAddress = function(port) {
+ var found;
+ var re = /(\d+\s+[':.,\s\w]*,\s*[A-Za-z]+\s*\d{5}(-\d{4})?)/m;
+ var node = document.body;
+ var done = false;
+ while (!done) {
+ done = true;
+ for (var i = 0; i < node.childNodes.length; ++i) {
+ var child = node.childNodes[i];
+ if (child.textContent.match(re)) {
+ node = child;
+ found = node;
+ done = false;
+ break;
+ }
+ }
+ }
+ if (found) {
+ var text = "";
+ if (found.childNodes.length) {
+ for (var i = 0; i < found.childNodes.length; ++i) {
+ text += found.childNodes[i].textContent + " ";
+ }
+ } else {
+ text = found.textContent;
+ }
+ var match = re.exec(text);
+ if (match && match.length) {
+ console.log("found: " + match[0]);
+ var trim = /\s{2,}/g;
+ var map = match[0].replace(trim, " ");
+ port.postMessage({message:"map", values:[map]});
+ } else {
+ console.log("found bad " + found.textContent);
+ console.log("no match in: " + text);
+ }
+ } else {
+ console.log("no match in " + node.textContent);
+ }
+}
+
diff --git a/chrome/common/extensions/docs/examples/extensions/mappy/marker.png b/chrome/common/extensions/docs/examples/extensions/mappy/marker.png
new file mode 100755
index 0000000..aa45f4d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/mappy/marker.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/mappy/popup.html b/chrome/common/extensions/docs/examples/extensions/mappy/popup.html
new file mode 100644
index 0000000..409b216
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/mappy/popup.html
@@ -0,0 +1,51 @@
+<head>
+<style>
+body {
+ margin: 0px;
+ padding: 0px;
+}
+#map {
+ width: 512px;
+ height: 512px;
+}
+</style>
+<script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAATfHumDbW3OmRByfquHd3SRTRERdeAiwZ9EeJWta3L_JZVS0bOBRQeZgr4K0xyVKzUdnnuFl8X9PX0w&sensor=false"
+ type="text/javascript"></script>
+<script>
+var maps_key = "ABQIAAAATfHumDbW3OmRByfquHd3SRTRERdeAiwZ9EeJWta3L_JZVS0bOBRQeZgr4K0xyVKzUdnnuFl8X9PX0w";
+
+function gclient_geocode(address) {
+ var geocoder = new GClientGeocoder();
+ geocoder.getLatLng(address, function(point) {
+ if (!point) {
+ console.log(address + " not found");
+ } else {
+ var latlng = point.toUrlValue();
+ var url = "http://maps.google.com/staticmap?center=" + latlng +
+ "&markers=" + latlng + "&zoom=14" +
+ "&size=512x512&sensor=false&key=" + maps_key;
+ var map = document.getElementById("map");
+ map.src = url;
+ }
+ });
+}
+
+function map() {
+ chrome.tabs.getSelected(null, function(tab) {
+ var port = chrome.tabs.connect(tab.id);
+ if (!port) {
+ console.log("no port");
+ } else {
+ port.onMessage.addListener(function(data) {
+ var address = data.values[0];
+ gclient_geocode(address);
+ });
+ port.postMessage({message: "hello tab: " + tab.id});
+ }
+ });
+};
+</script>
+</head>
+<body onload="map()">
+<img id="map" onclick="window.close()">
+</body>
diff --git a/chrome/common/extensions/docs/examples/tutorials/debugging/hello_world.html b/chrome/common/extensions/docs/examples/tutorials/debugging/hello_world.html
new file mode 100644
index 0000000..5cf5fab
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/tutorials/debugging/hello_world.html
@@ -0,0 +1,45 @@
+<style>
+img {
+ margin:5px;
+ border:2px solid black;
+ vertical-align:middle;
+ width:75px;
+ height:75px;
+}
+</style>
+
+<script>
+var req = new XMLHttpRequest();
+req.open(
+ "GET",
+ "http://api.flickr.com/services/rest/?" +
+ "method=flickr.photos.search&" +
+ "api_key=90485e931f687a9b9c2a66bf58a3861a&" +
+ "text=hello%20world&" +
+ "safe_search=1&" + // 1 is "safe"
+ "content_type=1&" + // 1 is "photos only"
+ "sort=relevance&" + // another good one is "interestingness-desc"
+ "per_page=95",
+ true);
+req.onload = showPhotos;
+req.send(null);
+
+function showPhotos() {
+ var photos = req.responseXML.getElementsByTagName("photo");
+
+ for (var i = 0, photo; photo = photos[i]; i++) {
+ var img = document.createElement("image");
+ img.src = constructImageURL(photo);
+ document.body.appendChild(img);
+ }
+}
+
+// See: http://www.flickr.com/services/api/misc.urls.html
+function constructImageURL(photo) {
+ return "http://farm" + photo.getAttribute("farm") +
+ ".static.flickr.com/" + photo.getAttribute("server") +
+ "/" + photo.getAttribute("id") +
+ "_" + photo.getAttribute("secret") +
+ "_s.jpg";
+}
+</script>
diff --git a/chrome/common/extensions/docs/examples/tutorials/debugging/manifest.json b/chrome/common/extensions/docs/examples/tutorials/debugging/manifest.json
new file mode 100644
index 0000000..3170bf4
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/tutorials/debugging/manifest.json
@@ -0,0 +1,11 @@
+{
+ "name": "My First Extension",
+ "version": "1.0",
+ "description": "The first extension that I made.",
+ "toolstrips": [
+ "my_toolstrip.html"
+ ],
+ "permissions": [
+ "http://api.flickr.com/"
+ ]
+}
diff --git a/chrome/common/extensions/docs/examples/tutorials/debugging/my_toolstrip.html b/chrome/common/extensions/docs/examples/tutorials/debugging/my_toolstrip.html
new file mode 100644
index 0000000..a6b70d5
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/tutorials/debugging/my_toolstrip.html
@@ -0,0 +1,3 @@
+<div class="toolstrip-button" onclick="window.open('hello_world.html')">
+ <span>Hello, World!</span>
+</div>
diff --git a/chrome/common/extensions/docs/samples.html b/chrome/common/extensions/docs/samples.html
index ccb422c..4fdaebc 100644
--- a/chrome/common/extensions/docs/samples.html
+++ b/chrome/common/extensions/docs/samples.html
@@ -223,7 +223,7 @@
</p><p><a href="https://clients2.google.com/service/update2/crx?response=redirect&amp;x=id%3Dmihcahmgecmbnbcchbopgniflfhgnkff%26uc%26lang%3Den-US&amp;prod=chrome&amp;prodversion=4.0.238.0"><img src="images/google-mail-checker-capture.png" width="243" height="170" style="margin-bottom:0.5em"></a><br>
<b><a href="https://clients2.google.com/service/update2/crx?response=redirect&amp;x=id%3Dmihcahmgecmbnbcchbopgniflfhgnkff%26uc%26lang%3Den-US&amp;prod=chrome&amp;prodversion=4.0.238.0">Install</a>&nbsp;&nbsp;
-<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/test/data/extensions/samples/gmail_browser_action/">Source code</a></b>
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/gmail/">Source code</a></b>
<br><br>
</p><p>Features used:
@@ -244,7 +244,7 @@
</p><p><a href="https://clients2.google.com/service/update2/crx?response=redirect&amp;x=id%3Dafmppjmdopaajlhgcddfhfhfgincjeih%26uc%26lang%3Den-US&amp;prod=chrome&amp;prodversion=4.0.238.0"><img src="images/buildbot-capture.png" style="margin-bottom:0.5em" width="336" height="288"></a><br>
<b><a href="https://clients2.google.com/service/update2/crx?response=redirect&amp;x=id%3Dafmppjmdopaajlhgcddfhfhfgincjeih%26uc%26lang%3Den-US&amp;prod=chrome&amp;prodversion=4.0.238.0">Install</a>&nbsp;&nbsp;
-<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/test/data/extensions/samples/buildbot/">Source code</a></b>
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/buildbot/">Source code</a></b>
<br><br>
@@ -267,7 +267,7 @@
</p><p><a href="https://clients2.google.com/service/update2/crx?response=redirect&amp;x=id%3Dnlbjncdgjeocebhnmkbbbdekmmmcbfjd%26uc%26lang%3Den-US&amp;prod=chrome&amp;prodversion=4.0.238.0"><img src="images/subscribe-cap2.png" style="margin-bottom:0.5em" width="566" height="327"></a><br>
<b><a href="https://clients2.google.com/service/update2/crx?response=redirect&amp;x=id%3Dnlbjncdgjeocebhnmkbbbdekmmmcbfjd%26uc%26lang%3Den-US&amp;prod=chrome&amp;prodversion=4.0.238.0">Install</a>&nbsp;&nbsp;
-<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/test/data/extensions/samples/subscribe_page_action/">Source code</a></b>
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/test/data/extensions/subscribe_page_action/">Source code</a></b>
<br><br>
</p><p>Features used:
diff --git a/chrome/common/extensions/docs/static/samples.html b/chrome/common/extensions/docs/static/samples.html
index 3a48d5d..92a8faa 100644
--- a/chrome/common/extensions/docs/static/samples.html
+++ b/chrome/common/extensions/docs/static/samples.html
@@ -8,7 +8,7 @@
<p><a href="https://clients2.google.com/service/update2/crx?response=redirect&x=id%3Dmihcahmgecmbnbcchbopgniflfhgnkff%26uc%26lang%3Den-US&prod=chrome&prodversion=4.0.238.0"><img src="images/google-mail-checker-capture.png" width="243" height="170" style="margin-bottom:0.5em"></a><br>
<b><a href="https://clients2.google.com/service/update2/crx?response=redirect&x=id%3Dmihcahmgecmbnbcchbopgniflfhgnkff%26uc%26lang%3Den-US&prod=chrome&prodversion=4.0.238.0">Install</a>&nbsp;&nbsp;
-<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/test/data/extensions/samples/gmail_browser_action/">Source code</a></b>
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/gmail/">Source code</a></b>
<br><br>
<p>Features used:
@@ -29,7 +29,7 @@
<p><a href="https://clients2.google.com/service/update2/crx?response=redirect&x=id%3Dafmppjmdopaajlhgcddfhfhfgincjeih%26uc%26lang%3Den-US&prod=chrome&prodversion=4.0.238.0"><img src="images/buildbot-capture.png" style="margin-bottom:0.5em" width="336" height="288"></a><br>
<b><a href="https://clients2.google.com/service/update2/crx?response=redirect&x=id%3Dafmppjmdopaajlhgcddfhfhfgincjeih%26uc%26lang%3Den-US&prod=chrome&prodversion=4.0.238.0">Install</a>&nbsp;&nbsp;
-<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/test/data/extensions/samples/buildbot/">Source code</a></b>
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/buildbot/">Source code</a></b>
<br><br>
@@ -52,7 +52,7 @@
<p><a href="https://clients2.google.com/service/update2/crx?response=redirect&x=id%3Dnlbjncdgjeocebhnmkbbbdekmmmcbfjd%26uc%26lang%3Den-US&prod=chrome&prodversion=4.0.238.0"><img src="images/subscribe-cap2.png" style="margin-bottom:0.5em" width="566" height="327"></a><br>
<b><a href="https://clients2.google.com/service/update2/crx?response=redirect&x=id%3Dnlbjncdgjeocebhnmkbbbdekmmmcbfjd%26uc%26lang%3Den-US&prod=chrome&prodversion=4.0.238.0">Install</a>&nbsp;&nbsp;
-<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/test/data/extensions/samples/subscribe_page_action/">Source code</a></b>
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/test/data/extensions/subscribe_page_action/">Source code</a></b>
<br><br>
<p>Features used: