diff options
author | battre@chromium.org <battre@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-03-14 10:24:13 +0000 |
---|---|---|
committer | battre@chromium.org <battre@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-03-14 10:24:13 +0000 |
commit | 0583e2b917d85db0f301ac5994c64c4e30da678b (patch) | |
tree | a34bcfd795b2a6c449229f87f60b1a98daa1a39f /chrome | |
parent | c6b65bfd7201db65f8b5f188e5688d2bdf93cb81 (diff) | |
download | chromium_src-0583e2b917d85db0f301ac5994c64c4e30da678b.zip chromium_src-0583e2b917d85db0f301ac5994c64c4e30da678b.tar.gz chromium_src-0583e2b917d85db0f301ac5994c64c4e30da678b.tar.bz2 |
Sample extension demonstrating usage of the Proxy API.
BUG=62700
TEST=Install extension, ensure that nothing explodes, change proxy settings, profit!
Review URL: http://codereview.chromium.org/6538077
Patch from Mike West <mkwst@chromium.org>.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@78014 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
13 files changed, 3008 insertions, 1 deletions
diff --git a/chrome/common/extensions/docs/examples/extensions/proxy_configuration.zip b/chrome/common/extensions/docs/examples/extensions/proxy_configuration.zip Binary files differnew file mode 100644 index 0000000..07b7ad0 --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration.zip diff --git a/chrome/common/extensions/docs/examples/extensions/proxy_configuration/_locales/en/messages.json b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/_locales/en/messages.json new file mode 100644 index 0000000..b05ac21 --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/_locales/en/messages.json @@ -0,0 +1,50 @@ +{ + "extName": { + "message": "Proxy Settings", + "description": "The extension name." + }, + "extDescription": { + "message": "Set Chrome-specific proxies; a demonstration of Chrome's Proxy API", + "description": "The extension description." + }, + "headerDirectConnection": { + "message": "Direct Connection", + "description": "Header for 'Direct Connection' configuration `fieldset`." + }, + "errorNoExtensionAccess": { + "message": "Sorry. This browser's proxy settings cannot be controlled via extensions.", + "description": "Error message displayed when `levelOfControl` is 'NotControllable'." + }, + "errorOtherExtensionControls": { + "message": "Sorry. This browser's proxy settings are being controlled by another extension. Please visit chrome://extensions for details.", + "description": "Error message displayed when `levelOfControl` is 'ControlledByOtherExtensions'." + }, + "errorSettingRegularProxy": { + "message": "Setting regular proxy settings failed. Sorry!", + "description": "Error message, displayed when failing to set regular proxy settings." + }, + "errorSettingIncognitoProxy": { + "message": "Setting incognito proxy settings failed. Sorry!", + "description": "Error message, displayed when failing to set incognito proxy settings." + }, + "successfullySetProxy": { + "message": "Your proxy settings have been saved successfully; this window will close automagically. Have a nice day!", + "description": "Success message, displayed after proxy settings have been written." + }, + "errorPopupTitle": { + "message": "Error: $1", + "description": "Error message used as popup title." + }, + "errorProxyError": { + "message": "ProxyError: $1", + "description": "Error message displayed in popup when an error occurs." + }, + "errorIdNotFound": { + "message": "Element with ID `$1` doesn't exist in the document", + "description": "Error message thrown when the given `id` doesn't exist" + }, + "errorIdNotForm": { + "message": "Element with ID `$1` isn't a form element.", + "description": "Error message thrown when the given `id` isn't a form element." + } +} diff --git a/chrome/common/extensions/docs/examples/extensions/proxy_configuration/background.html b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/background.html new file mode 100644 index 0000000..4c4fccb --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/background.html @@ -0,0 +1,28 @@ +<!doctype html> +<html> + <head> + <title>Proxy Configuration Extension Background Page</title> + </head> + <body> + <!-- + Here, we'll hook into the proxy extension API's `onProxyError` + event, and use it to set a warning badge on the browser action's icon. + Additionally, we'll store proxy settings locally, and reset them when the + background page initializes. This is essential, as incognito settings + are wiped on restart. + --> + <script src="./proxy_form_controller.js"></script> + <script src="./proxy_error_handler.js"></script> + <script> + var errorHandler = new ProxyErrorHandler(); + + // If this extension has already set the proxy settings, then reset it + // once as the background page initializes. This is essential, as + // incognito settings are wiped on restart. + var persistedSettings = ProxyFormController.getPersistedSettings(); + if (persistedSettings !== null) { + chrome.experimental.proxy.settings.set({'value': persistedSettings}); + } + </script> + </body> +</html> diff --git a/chrome/common/extensions/docs/examples/extensions/proxy_configuration/manifest.json b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/manifest.json new file mode 100644 index 0000000..1870843 --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/manifest.json @@ -0,0 +1,16 @@ +{ + "name": "__MSG_extName__", + "version": "0.1", + "description": "__MSG_extDescription__", + "default_locale": "en", + "browser_action": { + "default_icon": "icon.png", + "popup": "popup.html" + }, + "background_page": "background.html", + "permissions": [ + "experimental", + "proxy", + "TTS" + ] +} diff --git a/chrome/common/extensions/docs/examples/extensions/proxy_configuration/popup.html b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/popup.html new file mode 100644 index 0000000..8f0c39f --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/popup.html @@ -0,0 +1,297 @@ +<!doctype html> +<html> +<head> + <title>Popup for Proxy API Test</title> + <style> + body { + margin: 5px 10px 10px; + } + + h1 { + color: #53637D; + font: 26px/1.2 Helvetica, sans-serif; + font-size: 200%; + margin: 0; + padding-bottom: 4px; + text-shadow: white 0 1px 2px; + } + + div[role='main'] { + border-radius: 5px; + background: #EAEEF3; + font: 14px/1 Arial,Sans Serif; + padding: 10px; + width: 563px; + box-shadow: inset 0px 2px 5px rgba(0,0,0,0.5); + -webkit-transition: background-color 0.5s ease-out; + overflow: hidden; + } + + div[role='main'].incognito { + background: #496281 url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAAhCAYAAABeD2IVAAAFz0lEQVRYw8VYC0xcRRQtUEprERQ1VkohSGNpwaCYYNBUjS2K+KEVTUkDiqY0hWD5xxhDINgQqIZviRAkSiiWNDRxWypNpaatEQk/CwvlWyks3+XPAssu7I7nbmabx+suPzfrTU7yljfz5sy95947w5YtpjcLwBLYBuzgsAGs+DuzGZGwBh4BHIDdwHPA8xxuwBOcnIWpPUAL7wQeB54CngYcAWdO4kXgDeBoWFhYSk5OTtmFCxeuxsbGpuBv3sCTwFZTkrLmXtgH+AJ+QAAQCBzz8/P7MiUl5aebN29Kh4eHFVNTU2xoaIgNDAywe/fusYyMjO858R2mJEUe2ldZWfljd3f3P+3t7bK2trZBuVyuYCJbXFxkXV1drLOzU4e7d++yzMzMasz3AexMSYq89CoRYuuwvr6+B6Q6OjpYUVFRG+Yf4iE0iZYoc5woVK2trUPrIUXh05Mir9XW1qow/33gmf8idj0ZypjHuIgj5ubm1OshpdFoVoTwzp07zMbGJsTb23s/16fFZlKcyDjY29u7+fr6HoJFV1dXt7EN2MjIyANSUqmUubq6Jtna2r7OvWW7kUy04IR2+fj4HDx+/HhUYmJiIQQuhZ5mN0JKqVSuEHtWVlbd6dOns+3s7N7h9YtKynbuhFWNQuYQGBh4ODs7u2B0dHRauNDMzIx2eXmZabXadRG7f/++jhSylV25ckXOk2D4yJEjSVjnNV7ntq9Fily6Jzg4+CTEOqf/OJFoampSFRQUjDc3Ny+QF9RqtU47hozGE3mUDIbxOtTX1ytQt5T6MQipFGu9zDPbYq0i+ey5c+dyxAvBSxrsXHXixIlmVOsOaGykp6dnYXZ2lt49BHhIWVdXN1VaWipLS0trCQ8P/xseUwrD6+HhEUJO4BFa3VMREREx4+PjCkNhunjx4iAWuI0x16KjowlVYkA7uncYczUyMpJ+3z579mzXwsLCA9fimcXHx6djvb3cGatmHrnTBxr4jSZShaYwCQnC89PQXFNUVNSvMTExv6C/XYqLi6sg0DMISRISEq7h+QY81dHY2DgpDq9CoWCYm8Fbl8FmLaxLROoA8OGpU6dKL1++LCMdGdIPEkF569atIYlE0oNNdGFsNz03NDTI0QOVhrRG31GpVCR4JcrEV1jnFWOZaMVrBx07vIDDQBiQil3XQENDMplseWlpiW3WBOLXovWoy8vLBxA+CTT6NdZ5gR9vtooFvis1NfWTioqKn6uqqv6ihius4NCYZn5+Xuf2zRAi4OSgQQIsT05OrnA71qrmR6BtQlL0wwVpL1nPIqQzCgFpzlB50HuFxlF2TkxMUKiXxBKgb4Dooqen5zHeX63FWUfl/22k/aix+qM3IkEeS05Obs7Pz6+vqakZgAcUY2Njur8T2cHBwSXobbSwsPCPgICAAny7QJgstAaVjry8vEquKwexpiz5WccrJCTkDO1yLWIILcP474DPgQ8oMahpA9+C0FxJSUk7nr/h2vwMSAfhJWFJQAKN8Xku4tCJveWP3d8gb6zWToh0aGjoJYz/FHgToGYbTMmBUM0RnJyccvE7GkiEVlv1cylhpqenGbI7l4v8UWNVfRsn5efs7Jxx/vz5fr0+VvGWuqysrMbNzS0R88KDgoLyEMo+/XvyDI4tcmHC6GsUSkgL5rxFCWbsxGDFY/oSdn+mt7d3gj5w/fp15VrENmokbvRANTbyBdbbz8/tBgsn1agD/v7+cUh7lfAj2KmGmdAoI9PT0//kOnQ01vesedF8t7+/X27oQ0hbrSmJoXBKucD3GAsd3VS8cDIoY2YykgM6RS4/Zj8kckt+sfTDeaeHmdFwHxzj98Y94qKpOz+5u7ufZGY28haOyOX89rzCW9SZ3YuLi39g/4Mhy4ex/kHejFeQ2tvS0vI79ShqE+YEmjxLSkoK5aQsheGjW6snv8EeBT42Az4C3uP/l3DhyWYhvuPZ84PWbt6tzQFH7pCdvCzoSP0LtBi6oflBr2wAAAAASUVORK5CYII=') no-repeat 533px bottom; + } + + form { + width: 563px; + -webkit-transition: -webkit-transform 0.25s ease; + } + + form.offscreen { + -webkit-transform: translateX(-600px); + } + + fieldset { + border: 0; + margin: 0; + padding: 0; + position: relative; + } + + legend { + position: absolute; + left: -999em; + } + + form > fieldset { + border-radius: 5px; + border: 1px solid transparent; + padding: 10px 10px 10px 30px; + margin: 5px 0; + -webkit-transition: all 0.5s ease; + } + + form > fieldset:hover { + background: rgba(255,255,255,0.1); + border-color: rgba(0,0,0,0.1); + } + + form > fieldset.active { + background: rgba(255,255,255,0.25); + border-color: rgba(0,0,0,0.25); + } + + form > fieldset > input { + margin-left: -20px; + } + + section { + margin: 5px 0 0; + } + + section fieldset:not(:first-child):not(:last-child) { + -webkit-transition: all 0.5s ease; + overflow: hidden; + max-height: 1.6em; + } + + section.single fieldset:not(:first-child):not(:last-child) { + max-height: 0px; + } + + section fieldset:last-child { + margin-top: 5px; + } + + section fieldset:last-child label { + display: block; + } + + section fieldset:last-child textarea { + width: 412px; + } + + section > fieldset { + position: relative; + padding-left: 60px; + } + + section > fieldset > legend { + left: 0; + top: 4px; + width: 53px; + text-align: right; + } + + input[type='url']:invalid:not(:active):not(:focus) { + border-color: rgba(255,0,0,0.5); + background: rgba(255,0,0,0.25); + } + + input:invalid:not(:active):not(:focus):after { + content: "This isn't a valid URL!"; + display:block; + } + + input[type="checkbox"] { + margin: 5px 0 5px 35px; + } + + input[type="text"] { + width: 200px; + margin: 0 10px 0 0; + } + + input[type="number"] { + width: 50px; + margin: 2px 10px 0 5px; + } + + section label, section legend { + color: #999; + -webkit-transition: color 0.5s ease; + } + + .incognito section label, .incognito section legend { + color: #BBB; + } + + .active section label, .active section legend, form > fieldset > label { + color: #000; + -webkit-transition: color 0.5s ease; + } + + .incognito .active section label, .incognito .active section legend, .incognito form > fieldset > label { + color: #FFF; + } + + input[type="submit"], button { + border-radius: 2px; + box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1); + -webkit-user-select: none; + background: -webkit-linear-gradient(#FAFAFA, #F4F4F4 40%, #E5E5E5); + border: 1px solid #AAA; + color: #444; + margin-bottom: 0; + min-width: 4em; + padding: 3px 12px; + margin-top: 0; + font-size: 1.1em; + } + + .overlay { + display: block; + text-align: center; + position: absolute; + left: 50%; + top: 50%; + width: 350px; + padding: 20px; + margin: -80px 0 0 -195px; + opacity: 0; + background: rgba(0, 0, 0, 0.75); + border-radius: 5px; + color: #FFF; + font: 1.5em/1.2 Helvetica Neue, sans-serif; + -webkit-transition: all 1.0s ease; + } + + .overlay.visible { + opacity: 1; + } + </style> +</head> +<body> + <h1>Proxy Configuration</h1> + <div role="main"> + <form id="proxyForm"> + <fieldset id="system"> + <legend>System Settings</legend> + <input type="radio" name="proxyType" id="proxyTypeSystem" value="system"> + <label for="proxyTypeSystem">Use the <em>system's proxy settings</em>.</label> + </fieldset> + <fieldset id="direct"> + <legend>Direct Connection</legend> + <input type="radio" name="proxyType" id="proxyTypeDirect" value="direct"> + <label for="proxyTypeDirect">Your computer is <em>directly connected</em> to the internet; no need for a proxy.</label> + </fieldset> + <fieldset id="pac_script"> + <legend>Automatic Configuration</legend> + <input type="radio" name="proxyType" id="proxyTypeAutoconfig" value="autoconfig"> + <label for="proxyTypeAutoconfig">Your proxy supports <em>automatic configuration</em>.</label> + + <section> + <label for="autoconfigURL">Autoconfiguration URL (PAC file)</label> + <input type="url" name="autoconfigURL" id="autoconfigURL"> + <input type="hidden" name="autoconfigData" id="autoconfigData"> + </section> + </fieldset> + <fieldset id="fixed_servers"> + <legend>Manual Proxy</legend> + <input type="radio" name="proxyType" id="proxyTypeManual" value="manual"> + <label for="proxyTypeManual">Configure your proxy settings <em>manually</em>.</label> + <section> + <fieldset> + <legend>HTTP</legend> + <label for="proxyHostHttp">Host</label> + <select id="proxySchemeHttp" name="proxySchemeHttp"> + <option selected value="http">http://</option> + <option value="https">https://</option> + <option value="socks4">socks4://</option> + <option value="socks5">socks5://</option> + </select> + <input type="text" name="proxyHostHttp" id="proxyHostHttp"> + + <label for="proxyPortHttp">Port</label> + <input type="number" min="1" step="1" name="proxyPortHttp" id="proxyPortHttp"> + + <input type="checkbox" name="singleProxyForEverything" id="singleProxyForEverything"> + <label for="singleProxyForEverything">Use the same proxy server for all protocols</label> + </fieldset> + <fieldset> + <legend>HTTPS</legend> + <label for="proxyHostHttps">Host</label> + <select id="proxySchemeHttps" name="proxySchemeHttps"> + <option selected value="http">http://</option> + <option value="https">https://</option> + <option value="socks4">socks4://</option> + <option value="socks5">socks5://</option> + </select> + <input type="text" name="proxyHostHttps" id="proxyHostHttps"> + + <label for="proxyPortHttps">Port</label> + <input type="number" min="1" step="1" name="proxyPortHttps" id="proxyPortHttps"> + </fieldset> + <fieldset> + <legend>FTP</legend> + <label for="proxyHostFtp">Host</label> + <select id="proxySchemeFtp" name="proxySchemeFtp"> + <option selected value="http">http://</option> + <option value="https">https://</option> + <option value="socks4">socks4://</option> + <option value="socks5">socks5://</option> + </select> + <input type="text" name="proxyHostFtp" id="proxyHostFtp"> + + <label for="proxyPortFtp">Port</label> + <input type="number" min="1" step="1" name="proxyPortFtp" id="proxyPortFtp"> + </fieldset> + <fieldset> + <legend>Fallback</legend> + <label for="proxyHostFallback">Host</label> + <select id="proxySchemeFallback" name="proxySchemeFallback"> + <option selected value="http">http://</option> + <option value="https">https://</option> + <option value="socks4">socks4://</option> + <option value="socks5">socks5://</option> + </select> + <input type="text" name="proxyHostFallback" id="proxyHostFallback"> + + <label for="proxyPortFallback">Port</label> + <input type="number" min="1" step="1" name="proxyPortFallback" id="proxyPortFallback"> + </fieldset> + <fieldset> + <label for="bypassList">Bypass proxy for these hosts:</label> + <textarea id="bypassList" name="bypassList" placeholder="<local>,192.168.1.1/16, .example.com"></textarea> + </fieldset> + </section> + </fieldset> + <input type="submit" value="Save proxy settings"> + <button value="incognito">Configure incognito window settings.</button> + </form> + </div> + <script src="./proxy_form_controller.js"></script> + <script> + var c = new ProxyFormController( 'proxyForm' ); + </script> +</body> +</html> diff --git a/chrome/common/extensions/docs/examples/extensions/proxy_configuration/proxy_error_handler.js b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/proxy_error_handler.js new file mode 100644 index 0000000..d5fb404 --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/proxy_error_handler.js @@ -0,0 +1,105 @@ +// Copyright (c) 2011 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. + +/** + * @fileoverview This file implements the ProxyErrorHandler class, which will + * flag proxy errors in a visual way for the extension's user. + * + * @author Mike West <mkwst@google.com> + */ + + +/** + * The proxy error handling object. Binds to the 'onProxyError' event, and + * changes the extensions badge to reflect the error state (yellow for + * non-fatal errors, red for fatal). + * + * @constructor + */ +function ProxyErrorHandler() { + // Handle proxy error events. + chrome.experimental.proxy.onProxyError.addListener( + this.handleError.bind(this)); + + // Handle message events from popup. + chrome.extension.onRequest.addListener( + this.handleOnRequest_.bind(this)); +}; + +/////////////////////////////////////////////////////////////////////////////// + +/** + * @typedef {{fatal: boolean, error: string, details: string}} + */ +ProxyErrorHandler.ErrorDetails; + +/////////////////////////////////////////////////////////////////////////////// + +ProxyErrorHandler.prototype = { + /** + * Details of the most recent error. + * @type {?ProxyErrorHandler.ErrorDetails} + * @private + */ + lastError_: null, + + /** + * Handle request messages from the popup. + * + * @param {!{type:string}} request The external request to answer. + * @param {!MessageSender} sender Info about the script context that sent + * the request. + * @param {!function} sendResponse Function to call to send a response. + * @private + */ + handleOnRequest_: function(request, sender, sendResponse) { + if (request.type === 'getError') { + sendResponse({result: this.getErrorDetails()}); + } else if (request.type === 'clearError') { + this.clearErrorDetails(); + sendResponse({result: true}); + } + }, + + /** + * Handles the error event, storing the error details for later use, and + * badges the browser action icon. + * + * @param {!ProxyErrorHandler.ErrorDetails} details The error details. + * @private + */ + handleError_: function(details) { + var RED = [255, 0, 0, 255]; + var YELLOW = [255, 205, 0, 255]; + + // Badge the popup icon. + var color = details.fatal ? RED : YELLOW; + chrome.browserAction.setBadgeBackgroundColor({color: color}); + chrome.browserAction.setBadgeText({text: 'X'}); + chrome.browserAction.setTitle({ + 'title': chrome.i18n.getMessage('errorPopupTitle', details.error) + }); + + // Store the error for display in the popup. + this.lastError_ = JSON.stringify(details); + }, + + + /** + * Returns details of the last error handled. + * + * @return {?ProxyErrorHandler.ErrorDetails} + */ + getErrorDetails: function() { + return this.lastError_; + }, + + + /** + * Clears last handled error. + */ + clearErrorDetails: function() { + this.lastError_ = null; + } +} diff --git a/chrome/common/extensions/docs/examples/extensions/proxy_configuration/proxy_form_controller.js b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/proxy_form_controller.js new file mode 100644 index 0000000..08223d4 --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/proxy_form_controller.js @@ -0,0 +1,744 @@ +// Copyright (c) 2011 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. + +/** + * @fileoverview This file implements the ProxyFormController class, which + * wraps a form element with logic that enables implementation of proxy + * settings. + * + * @author mkwst@google.com (Mike West) + */ + +/** + * Wraps the proxy configuration form, binding proper handlers to its various + * `change`, `click`, etc. events in order to take appropriate action in + * response to user events. + * + * @param {string} id The form's DOM ID. + * @constructor + */ +var ProxyFormController = function(id) { + /** + * The wrapped form element + * @type {Node} + * @private + */ + this.form_ = document.getElementById(id); + + // Throw an error if the element either doesn't exist, or isn't a form. + if (!this.form_) + throw chrome.i18n.getMessage('errorIdNotFound', id); + else if (this.form_.nodeName !== 'FORM') + throw chrome.i18n.getMessage('errorIdNotForm', id); + + /** + * Cached references to the `fieldset` groups that define the configuration + * options presented to the user. + * + * @type {NodeList} + * @private + */ + this.configGroups_ = document.querySelectorAll('#' + id + ' > fieldset'); + + this.bindEventHandlers_(); + this.readCurrentState_(); + + // Handle errors + this.handleProxyErrors_(); +}; + +/////////////////////////////////////////////////////////////////////////////// + +/** + * The proxy types we're capable of handling. + * @enum {string} + */ +ProxyFormController.ProxyTypes = { + AUTO: 'auto_detect', + PAC: 'pac_script', + DIRECT: 'direct', + FIXED: 'fixed_servers', + SYSTEM: 'system' +}; + +/** + * The window types we're capable of handling. + * @enum {int} + */ +ProxyFormController.WindowTypes = { + REGULAR: 1, + INCOGNITO: 2 +}; + +/** + * The extension's level of control of Chrome's roxy setting + * @enum {string} + */ +ProxyFormController.LevelOfControl = { + NOT_CONTROLLABLE: 'NotControllable', + OTHER_EXTENSION: 'ControlledByOtherExtension', + AVAILABLE: 'ControllableByThisExtension', + CONTROLLING: 'ControlledByThisExtension' +}; + +/** + * The response type from 'proxy.settings.get' + * + * @typedef {{value: ProxyConfig, + * levelOfControl: ProxyFormController.LevelOfControl}} + */ +ProxyFormController.WrappedProxyConfig; + +/////////////////////////////////////////////////////////////////////////////// + +/** + * Retrieves proxy settings that have been persisted across restarts. + * + * @return {?ProxyConfig} The persisted proxy configuration, or null if no + * value has been persisted. + * @static + */ +ProxyFormController.getPersistedSettings = function() { + var result = JSON.parse(window.localStorage['proxyConfig']); + return result ? result : null; +}; + + +/** + * Persists proxy settings across restarts. + * + * @param {!ProxyConfig} config The proxy config to persist. + * @static + */ +ProxyFormController.setPersistedSettings = function(config) { + window.localStorage['proxyConfig'] = JSON.stringify(config); +}; + +/////////////////////////////////////////////////////////////////////////////// + +ProxyFormController.prototype = { + /** + * The form's current state. + * @type {regular: ?ProxyConfig, incognito: ?ProxyConfig} + * @private + */ + config_: {regular: null, incognito: null}, + + + /** + * @return {string} The PAC file URL (or an empty string). + */ + get pacURL() { + return document.getElementById('autoconfigURL').value; + }, + + + /** + * @param {!string} value The PAC file URL. + */ + set pacURL(value) { + document.getElementById('autoconfigURL').value = value; + }, + + + /** + * @return {string} The PAC file data (or an empty string). + */ + get manualPac() { + return document.getElementById('autoconfigData').value; + }, + + + /** + * @param {!string} value The PAC file data. + */ + set manualPac(value) { + document.getElementById('autoconfigData').value = value; + }, + + + /** + * @return {Array.<string>} A list of hostnames that should bypass the proxy. + */ + get bypassList() { + return document.getElementById('bypassList').value.split(/\s*(?:,|^)\s*/m); + }, + + + /** + * @param {?Array.<string>} data A list of hostnames that should bypass + * the proxy. If empty, the bypass list is emptied. + */ + set bypassList(data) { + if (!data) + data = []; + document.getElementById('bypassList').value = data.join(', '); + }, + + + /** + * @see http://code.google.com/chrome/extensions/trunk/experimental.proxy.html + * @return {?ProxyServer} An object containing the proxy server host, port, + * and scheme. If null, there is no single proxy. + */ + get singleProxy() { + var checkbox = document.getElementById('singleProxyForEverything'); + return checkbox.checked ? this.httpProxy : null; + }, + + + /** + * @see http://code.google.com/chrome/extensions/trunk/experimental.proxy.html + * @param {?ProxyServer} data An object containing the proxy server host, + * port, and scheme. If null, the single proxy checkbox will be unchecked. + */ + set singleProxy(data) { + var checkbox = document.getElementById('singleProxyForEverything'); + checkbox.checked = !!data; + + if (data) + this.httpProxy = data; + + if (checkbox.checked) + checkbox.parentNode.parentNode.classList.add('single'); + else + checkbox.parentNode.parentNode.classList.remove('single'); + }, + + /** + * @return {?ProxyServer} An object containing the proxy server host, port + * and scheme. + */ + get httpProxy() { + return this.getProxyImpl_('Http'); + }, + + + /** + * @param {?ProxyServer} data An object containing the proxy server host, + * port, and scheme. If empty, empties the proxy setting. + */ + set httpProxy(data) { + this.setProxyImpl_('Http', data); + }, + + + /** + * @return {?ProxyServer} An object containing the proxy server host, port + * and scheme. + */ + get httpsProxy() { + return this.getProxyImpl_('Https'); + }, + + + /** + * @param {?ProxyServer} data An object containing the proxy server host, + * port, and scheme. If empty, empties the proxy setting. + */ + set httpsProxy(data) { + this.setProxyImpl_('Https', data); + }, + + + /** + * @return {?ProxyServer} An object containing the proxy server host, port + * and scheme. + */ + get ftpProxy() { + return this.getProxyImpl_('Ftp'); + }, + + + /** + * @param {?ProxyServer} data An object containing the proxy server host, + * port, and scheme. If empty, empties the proxy setting. + */ + set ftpProxy(data) { + this.setProxyImpl_('Ftp', data); + }, + + + /** + * @return {?ProxyServer} An object containing the proxy server host, port + * and scheme. + */ + get fallbackProxy() { + return this.getProxyImpl_('Fallback'); + }, + + + /** + * @param {?ProxyServer} data An object containing the proxy server host, + * port, and scheme. If empty, empties the proxy setting. + */ + set fallbackProxy(data) { + this.setProxyImpl_('Fallback', data); + }, + + + /** + * @param {string} type The type of proxy that's being set ("Http", + * "Https", etc.). + * @return {?ProxyServer} An object containing the proxy server host, + * port, and scheme. + * @private + */ + getProxyImpl_: function(type) { + var result = { + scheme: document.getElementById('proxyScheme' + type).value, + host: document.getElementById('proxyHost' + type).value, + port: parseInt(document.getElementById('proxyPort' + type).value, 10) + }; + return (result.scheme && result.host && result.port) ? result : undefined; + }, + + + /** + * A generic mechanism for setting proxy data. + * + * @see http://code.google.com/chrome/extensions/trunk/experimental.proxy.html + * @param {string} type The type of proxy that's being set ("Http", + * "Https", etc.). + * @param {?ProxyServer} data An object containing the proxy server host, + * port, and scheme. If empty, empties the proxy setting. + * @private + */ + setProxyImpl_: function(type, data) { + if (!data) + data = {scheme: 'http', host: '', port: ''}; + + document.getElementById('proxyScheme' + type).value = data.scheme; + document.getElementById('proxyHost' + type).value = data.host; + document.getElementById('proxyPort' + type).value = data.port; + }, + +/////////////////////////////////////////////////////////////////////////////// + + /** + * Calls the proxy API to read the current settings, and populates the form + * accordingly. + * + * @private + */ + readCurrentState_: function() { + chrome.experimental.proxy.settings.get({incognito: false}, + this.handleRegularState_.bind(this)); + chrome.experimental.proxy.settings.get({incognito: true}, + this.handleIncognitoState_.bind(this)); + }, + + /** + * Handles the response from 'proxy.settings.get' for regular settings. + * + * @param {ProxyFormController.WrappedProxyConfig} c The proxy data and + * extension's level of control thereof. + * @private + */ + handleRegularState_: function(c) { + if (c.levelOfControl === ProxyFormController.LevelOfControl.AVAILABLE || + c.levelOfControl === ProxyFormController.LevelOfControl.CONTROLLING) { + this.recalcFormValues_(c.value); + this.config_.regular = c.value; + } else { + this.handleLackOfControl_(c.levelOfControl); + } + }, + + /** + * Handles the response from 'proxy.settings.get' for incognito settings. + * + * @param {ProxyFormController.WrappedProxyConfig} c The proxy data and + * extension's level of control thereof. + * @private + */ + handleIncognitoState_: function(c) { + if (c.levelOfControl === ProxyFormController.LevelOfControl.AVAILABLE || + c.levelOfControl === ProxyFormController.LevelOfControl.CONTROLLING) { + if (this.isIncognitoMode_()) + this.recalcFormValues_(c.value); + + this.config_.incognito = c.value; + } else { + this.handleLackOfControl_(c.levelOfControl); + } + }, + + /** + * Binds event handlers for the various bits and pieces of the form that + * are interesting to the controller. + * + * @private + */ + bindEventHandlers_: function() { + this.form_.addEventListener('click', this.dispatchFormClick_.bind(this)); + }, + + + /** + * When a `click` event is triggered on the form, this function handles it by + * analyzing the context, and dispatching the click to the correct handler. + * + * @param {Event} e The event to be handled. + * @private + * @return {boolean} True if the event should bubble, false otherwise. + */ + dispatchFormClick_: function(e) { + var t = e.target; + + // Case 1: "Apply" + if (t.nodeName === 'INPUT' && t.getAttribute('type') === 'submit') { + return this.applyChanges_(e); + + // Case 2: "Use the same proxy for all protocols" in an active section + } else if (t.nodeName === 'INPUT' && + t.getAttribute('type') === 'checkbox' && + t.parentNode.parentNode.parentNode.classList.contains('active') + ) { + return this.toggleSingleProxyConfig_(e); + + // Case 3: "Flip to incognito mode." + } else if (t.nodeName === 'BUTTON') { + return this.toggleIncognitoMode_(e); + + // Case 4: Click on something random: maybe changing active config group? + } else { + // Walk up the tree until we hit `form > fieldset` or fall off the top + while (t && (t.nodeName !== 'FIELDSET' || + t.parentNode.nodeName !== 'FORM')) { + t = t.parentNode; + } + if (t) { + this.changeActive_(t); + return false; + } + } + return true; + }, + + + /** + * Sets the form's active config group. + * + * @param {DOMElement} fieldset The configuration group to activate. + * @private + */ + changeActive_: function(fieldset) { + for (var i = 0; i < this.configGroups_.length; i++) { + var el = this.configGroups_[i]; + var radio = el.querySelector("input[type='radio']"); + if (el === fieldset) { + el.classList.add('active'); + radio.checked = true; + } else { + el.classList.remove('active'); + } + } + this.recalcDisabledInputs_(); + }, + + + /** + * Recalculates the `disabled` state of the form's input elements, based + * on the currently active group, and that group's contents. + * + * @private + */ + recalcDisabledInputs_: function() { + var i, j; + for (i = 0; i < this.configGroups_.length; i++) { + var el = this.configGroups_[i]; + var inputs = el.querySelectorAll( + "input:not([type='radio']), select, textarea"); + if (el.classList.contains('active')) { + for (j = 0; j < inputs.length; j++) { + inputs[j].removeAttribute('disabled'); + } + } else { + for (j = 0; j < inputs.length; j++) { + inputs[j].setAttribute('disabled', 'disabled'); + } + } + } + }, + + + /** + * Handler called in response to click on form's submission button. Generates + * the proxy configuration and passes it to `useCustomProxySettings`, or + * handles errors in user input. + * + * @param {Event} e DOM event generated by the user's click. + * @private + */ + applyChanges_: function(e) { + e.preventDefault(); + e.stopPropagation(); + + if (this.isIncognitoMode_()) + this.config_.incognito = this.generateProxyConfig_(); + else + this.config_.regular = this.generateProxyConfig_(); + + chrome.experimental.proxy.settings.set( + {value: this.config_.regular, incognito: false}, + this.callbackForRegularSettings_.bind(this)); + }, + + /** + * Called in response to setting a regular window's proxy settings: checks + * for `lastError`, and then sets incognito settings (if they exist). + * + * @private + */ + callbackForRegularSettings_: function() { + if (!chrome.extension.lastError) { + this.generateAlert(chrome.i18n.getMessage('errorSettingRegularProxy')); + return; + } + if (this.config_.incognito) { + chrome.experimental.proxy.settings.set( + {value: this.config_.incognito, incognito: true}, + this.callbackForIncognitoSettings_.bind(this)); + } else { + ProxyFormController.setPersistedSettings(this.config_); + this.generateAlert_(chrome.i18n.getMessage('successfullySetProxy')); + } + }, + + /** + * Called in response to setting an incognito window's proxy settings: checks + * for `lastError` and sets a success message. + * + * @private + */ + callbackForIncognitoSettings_: function() { + if (!chrome.extension.lastError) { + this.generateAlert(chrome.i18n.getMessage('errorSettingIncognitoProxy')); + return; + } + ProxyFormController.setPersistedSettings(this.config_); + this.generateAlert_( + chrome.i18n.getMessage('successfullySetProxy')); + }, + + /** + * Generates an alert overlay inside the proxy's popup, then closes the popup + * after a short delay. + * + * @param {string} msg The message to be displayed in the overlay. + * @param {?boolean} close Should the window be closed? Defaults to true. + * @private + */ + generateAlert_: function(msg, close) { + var success = document.createElement('p'); + success.classList.add('overlay'); + success.setAttribute('role', 'alert'); + success.innerText = msg; + document.body.appendChild(success); + + setTimeout(function() { success.classList.add('visible'); }, 10); + setTimeout(function() { + if (close === false) + success.classList.remove('visible'); + else + window.close(); + }, 3000); + }, + + + /** + * Parses the proxy configuration form, and generates a ProxyConfig object + * that can be passed to `useCustomProxyConfig`. + * + * @see http://code.google.com/chrome/extensions/trunk/experimental.proxy.html + * @return {ProxyConfig} The proxy configuration represented by the form. + * @private + */ + generateProxyConfig_: function() { + var active = document.getElementsByClassName('active')[0]; + switch (active.id) { + case ProxyFormController.ProxyTypes.SYSTEM: + return {mode: 'system'}; + case ProxyFormController.ProxyTypes.DIRECT: + return {mode: 'direct'}; + case ProxyFormController.ProxyTypes.PAC: + var pacScriptURL = this.pacURL; + var pacManual = this.manualPac; + if (pacScriptURL) + return {mode: 'pac_script', pacScript: {url: pacScriptURL}}; + else if (pacManual) + return {mode: 'pac_script', pacScript: {data: pacManual}}; + else + return {mode: 'auto_detect'}; + case ProxyFormController.ProxyTypes.FIXED: + var config = {mode: 'fixed_servers'}; + if (this.singleProxy) { + config.rules = {singleProxy: this.singleProxy}; + } else { + config.rules = { + proxyForHttp: this.httpProxy, + proxyForHttps: this.httpsProxy, + proxyForFtp: this.ftpProxy, + fallbackProxy: this.fallbackProxy, + bypassList: this.bypassList + }; + } + return config; + } + }, + + + /** + * Sets the proper display classes based on the "Use the same proxy server + * for all protocols" checkbox. Expects to be called as an event handler + * when that field is clicked. + * + * @param {Event} e The `click` event to respond to. + * @private + */ + toggleSingleProxyConfig_: function(e) { + var checkbox = e.target; + if (checkbox.nodeName === 'INPUT' && + checkbox.getAttribute('type') === 'checkbox') { + if (checkbox.checked) + checkbox.parentNode.parentNode.classList.add('single'); + else + checkbox.parentNode.parentNode.classList.remove('single'); + } + }, + + + /** + * Returns the form's current incognito status. + * + * @return {boolean} True if the form is in incognito mode, false otherwise. + * @private + */ + isIncognitoMode_: function(e) { + return this.form_.parentNode.classList.contains('incognito'); + }, + + + /** + * Toggles the form's incognito mode. Saves the current state to an object + * property for later use, clears the form, and toggles the appropriate state. + * + * @param {Event} e The `click` event to respond to. + * @private + */ + toggleIncognitoMode_: function(e) { + var div = this.form_.parentNode; + var button = document.getElementsByTagName('button')[0]; + if (this.isIncognitoMode_()) { + // In incognito mode, switching to cognito. + this.config_.incognito = this.generateProxyConfig_(); + div.classList.remove('incognito'); + this.recalcFormValues_(this.config_.regular); + button.innerText = 'Configure incognito window settings.'; + } else { + // In cognito mode, switching to incognito. + this.config_.regular = this.generateProxyConfig_(); + div.classList.add('incognito'); + this.recalcFormValues_(this.config_.incognito); + button.innerText = 'Configure cognito window settings.'; + } + + // Cancel the button click. + e.preventDefault(); + e.stopPropagation(); + }, + + + /** + * Sets the form's values based on a ProxyConfig. + * + * @param {!ProxyConfig} c The ProxyConfig object. + * @private + */ + recalcFormValues_: function(c) { + // Normalize `auto_detect` + if (c.mode === 'auto_detect') + c.mode = 'pac_script'; + // Activate one of the groups, based on `mode`. + this.changeActive_(document.getElementById(c.mode)); + // Populate the PAC script + if (c.pacScript) { + if (c.pacScript.url) + this.pacURL = c.pacScript.url; + } else { + this.pacURL = ''; + } + // Evaluate the `rules` + if (c.rules) { + var rules = c.rules; + if (rules.singleProxy) { + this.singleProxy = rules.singleProxy; + } else { + this.singleProxy = null; + this.httpProxy = rules.proxyForHttp; + this.httpsProxy = rules.proxyForHttps; + this.ftpProxy = rules.proxyForFtp; + this.fallbackProxy = rules.fallbackProxy; + } + this.bypassList = rules.bypassList; + } else { + this.singleProxy = null; + this.httpProxy = null; + this.httpsProxy = null; + this.ftpProxy = null; + this.fallbackProxy = null; + this.bypassList = ''; + } + }, + + + /** + * Handles the case in which this extension doesn't have the ability to + * control the Proxy settings, either because of an overriding policy + * or an extension with higher priority. + * + * @param {ProxyFormController.LevelOfControl} l The level of control this + * extension has over the proxy settings. + * @private + */ + handleLackOfControl_: function(l) { + var msg; + if (l === ProxyFormController.LevelOfControl.NO_ACCESS) + msg = chrome.i18n.getMessage('errorNoExtensionAccess'); + else if (l === ProxyFormController.LevelOfControl.OTHER_EXTENSION) + msg = chrome.i18n.getMessage('errorOtherExtensionControls'); + this.generateAlert_(msg); + }, + + + /** + * Handle the case in which errors have been generated outside the context + * of this popup. + * + * @private + */ + handleProxyErrors_: function() { + chrome.extension.sendRequest( + {type: 'getError'}, + this.handleProxyErrorHandlerResponse_.bind(this)); + }, + + /** + * Handles response from ProxyErrorHandler + * + * @param {{result: !string}} response The message sent in response to this + * popup's request. + */ + handleProxyErrorHandlerResponse_: function(response) { + if (response.result !== null) { + var error = response.result; + console.error(error); // TODO(mkwst): Do something more interesting + this.generateAlert_( + chrome.i18n.getMessage('errorProxyError', error.error), + false); + } + chrome.extension.sendRequest({type: 'clearError'}); + } +}; diff --git a/chrome/common/extensions/docs/examples/extensions/proxy_configuration/test/jsunittest.js b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/test/jsunittest.js new file mode 100644 index 0000000..8e1eab0 --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/test/jsunittest.js @@ -0,0 +1,964 @@ +/* Jsunittest, version 0.6.0 + * (c) 2008 Dr Nic Williams + * + * Jsunittest is freely distributable under + * the terms of an MIT-style license. + * For details, see the web site: http://jsunittest.rubyforge.org + * + *--------------------------------------------------------------------------*/ + +var JsUnitTest = { + Version: '0.6.0', +}; + +var DrNicTest = { + Unit: {}, + inspect: function(object) { + try { + if (typeof object == "undefined") return 'undefined'; + if (object === null) return 'null'; + if (typeof object == "string") { + var useDoubleQuotes = arguments[1]; + var escapedString = this.gsub(object, /[\x00-\x1f\\]/, function(match) { + var character = String.specialChar[match[0]]; + return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); + }); + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + }; + return String(object); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + }, + $: function(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push(this.$(arguments[i])); + return elements; + } + if (typeof element == "string") + element = document.getElementById(element); + return element; + }, + gsub: function(source, pattern, replacement) { + var result = '', match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += DrNicTest.String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + scan: function(source, pattern, iterator) { + this.gsub(source, pattern, iterator); + return String(source); + }, + escapeHTML: function(data) { + return data.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + }, + arrayfromargs: function(args) { + var myarray = new Array(); + var i; + + for (i=0;i<args.length;i++) + myarray[i] = args[i]; + + return myarray; + }, + hashToSortedArray: function(hash) { + var results = []; + for (key in hash) { + results.push([key, hash[key]]); + } + return results.sort(); + }, + flattenArray: function(array) { + var results = arguments[1] || []; + for (var i=0; i < array.length; i++) { + var object = array[i]; + if (object != null && typeof object == "object" && + 'splice' in object && 'join' in object) { + this.flattenArray(object, results); + } else { + results.push(object); + } + }; + return results; + }, + selectorMatch: function(expression, element) { + var tokens = []; + var patterns = { + // combinators must be listed first + // (and descendant needs to be last combinator) + laterSibling: /^\s*~\s*/, + child: /^\s*>\s*/, + adjacent: /^\s*\+\s*/, + descendant: /^\s/, + + // selectors follow + tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, + id: /^#([\w\-\*]+)(\b|$)/, + className: /^\.([\w\-\*]+)(\b|$)/, + pseudo: + /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, + attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/, + attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ + }; + + var assertions = { + tagName: function(element, matches) { + return matches[1].toUpperCase() == element.tagName.toUpperCase(); + }, + + className: function(element, matches) { + return Element.hasClassName(element, matches[1]); + }, + + id: function(element, matches) { + return element.id === matches[1]; + }, + + attrPresence: function(element, matches) { + return Element.hasAttribute(element, matches[1]); + }, + + attr: function(element, matches) { + var nodeValue = Element.readAttribute(element, matches[1]); + return nodeValue && operators[matches[2]](nodeValue, matches[5] || matches[6]); + } + }; + var e = this.expression, ps = patterns, as = assertions; + var le, p, m; + + while (e && le !== e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + p = ps[i]; + if (m = e.match(p)) { + // use the Selector.assertions methods unless the selector + // is too complex. + if (as[i]) { + tokens.push([i, Object.clone(m)]); + e = e.replace(m[0], ''); + } + } + } + } + + var match = true, name, matches; + for (var i = 0, token; token = tokens[i]; i++) { + name = token[0], matches = token[1]; + if (!assertions[name](element, matches)) { + match = false; break; + } + } + + return match; + }, + toQueryParams: function(query, separator) { + var query = query || window.location.search; + var match = query.replace(/^\s+/, '').replace(/\s+$/, '').match(/([^?#]*)(#.*)?$/); + if (!match) return { }; + + var hash = {}; + var parts = match[1].split(separator || '&'); + for (var i=0; i < parts.length; i++) { + var pair = parts[i].split('='); + if (pair[0]) { + var key = decodeURIComponent(pair.shift()); + var value = pair.length > 1 ? pair.join('=') : pair[0]; + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + var object = hash[key]; + var isArray = object != null && typeof object == "object" && + 'splice' in object && 'join' in object + if (!isArray) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + }; + return hash; + }, + + String: { + interpret: function(value) { + return value == null ? '' : String(value); + } + } +}; + +DrNicTest.gsub.prepareReplacement = function(replacement) { + if (typeof replacement == "function") return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +}; + +DrNicTest.Template = function(template, pattern) { + this.template = template; //template.toString(); + this.pattern = pattern || DrNicTest.Template.Pattern; +}; + +DrNicTest.Template.prototype.evaluate = function(object) { + if (typeof object.toTemplateReplacements == "function") + object = object.toTemplateReplacements(); + + return DrNicTest.gsub(this.template, this.pattern, function(match) { + if (object == null) return ''; + + var before = match[1] || ''; + if (before == '\\') return match[2]; + + var ctx = object, expr = match[3]; + var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + match = pattern.exec(expr); + if (match == null) return before; + + while (match != null) { + var comp = (match[1].indexOf('[]') === 0) ? match[2].gsub('\\\\]', ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + DrNicTest.String.interpret(ctx); + }); +} + +DrNicTest.Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; +DrNicTest.Event = {}; +// written by Dean Edwards, 2005 +// with input from Tino Zijdel, Matthias Miller, Diego Perini +// namespaced by Dr Nic Williams 2008 + +// http://dean.edwards.name/weblog/2005/10/add-event/ +// http://dean.edwards.name/weblog/2005/10/add-event2/ +DrNicTest.Event.addEvent = function(element, type, handler) { + if (element.addEventListener) { + element.addEventListener(type, handler, false); + } else { + // assign each event handler a unique ID + if (!handler.$$guid) handler.$$guid = addEvent.guid++; + // create a hash table of event types for the element + if (!element.events) element.events = {}; + // create a hash table of event handlers for each element/event pair + var handlers = element.events[type]; + if (!handlers) { + handlers = element.events[type] = {}; + // store the existing event handler (if there is one) + if (element["on" + type]) { + handlers[0] = element["on" + type]; + } + } + // store the event handler in the hash table + handlers[handler.$$guid] = handler; + // assign a global event handler to do all the work + element["on" + type] = handleEvent; + } +}; +// a counter used to create unique IDs +DrNicTest.Event.addEvent.guid = 1; + +DrNicTest.Event.removeEvent = function(element, type, handler) { + if (element.removeEventListener) { + element.removeEventListener(type, handler, false); + } else { + // delete the event handler from the hash table + if (element.events && element.events[type]) { + delete element.events[type][handler.$$guid]; + } + } +}; + +DrNicTest.Event.handleEvent = function(event) { + var returnValue = true; + // grab the event object (IE uses a global event object) + event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event); + // get a reference to the hash table of event handlers + var handlers = this.events[event.type]; + // execute each event handler + for (var i in handlers) { + this.$$handleEvent = handlers[i]; + if (this.$$handleEvent(event) === false) { + returnValue = false; + } + } + return returnValue; +}; + +DrNicTest.Event.fixEvent = function(event) { + // add W3C standard event methods + event.preventDefault = fixEvent.preventDefault; + event.stopPropagation = fixEvent.stopPropagation; + return event; +}; +DrNicTest.Event.fixEvent.preventDefault = function() { + this.returnValue = false; +}; +DrNicTest.Event.fixEvent.stopPropagation = function() { + this.cancelBubble = true; +}; + +DrNicTest.Unit.Logger = function(element) { + this.element = DrNicTest.$(element); + if (this.element) this._createLogTable(); +}; + +DrNicTest.Unit.Logger.prototype.start = function(testName) { + if (!this.element) return; + var tbody = this.element.getElementsByTagName('tbody')[0]; + tbody.innerHTML = tbody.innerHTML + '<tr><td>' + testName + '</td><td></td><td></td></tr>'; +}; + +DrNicTest.Unit.Logger.prototype.setStatus = function(status) { + var logline = this.getLastLogLine(); + logline.className = status; + var statusCell = logline.getElementsByTagName('td')[1]; + statusCell.innerHTML = status; +}; + +DrNicTest.Unit.Logger.prototype.finish = function(status, summary) { + if (!this.element) return; + this.setStatus(status); + this.message(summary); +}; + +DrNicTest.Unit.Logger.prototype.message = function(message) { + if (!this.element) return; + var cell = this.getMessageCell(); + cell.innerHTML = this._toHTML(message); +}; + +DrNicTest.Unit.Logger.prototype.summary = function(summary) { + if (!this.element) return; + var div = this.element.getElementsByTagName('div')[0]; + div.innerHTML = this._toHTML(summary); +}; + +DrNicTest.Unit.Logger.prototype.getLastLogLine = function() { + var tbody = this.element.getElementsByTagName('tbody')[0]; + var loglines = tbody.getElementsByTagName('tr'); + return loglines[loglines.length - 1]; +}; + +DrNicTest.Unit.Logger.prototype.getMessageCell = function() { + var logline = this.getLastLogLine(); + return logline.getElementsByTagName('td')[2]; +}; + +DrNicTest.Unit.Logger.prototype._createLogTable = function() { + var html = '<div class="logsummary">running...</div>' + + '<table class="logtable">' + + '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' + + '<tbody class="loglines"></tbody>' + + '</table>'; + this.element.innerHTML = html; +}; + +DrNicTest.Unit.Logger.prototype.appendActionButtons = function(actions) { + // actions = $H(actions); + // if (!actions.any()) return; + // var div = new Element("div", {className: 'action_buttons'}); + // actions.inject(div, function(container, action) { + // var button = new Element("input").setValue(action.key).observe("click", action.value); + // button.type = "button"; + // return container.insert(button); + // }); + // this.getMessageCell().insert(div); +}; + +DrNicTest.Unit.Logger.prototype._toHTML = function(txt) { + return DrNicTest.escapeHTML(txt).replace(/\n/g,"<br/>"); +}; +DrNicTest.Unit.MessageTemplate = function(string) { + var parts = []; + var str = DrNicTest.scan((string || ''), /(?=[^\\])\?|(?:\\\?|[^\?])+/, function(part) { + parts.push(part[0]); + }); + this.parts = parts; +}; + +DrNicTest.Unit.MessageTemplate.prototype.evaluate = function(params) { + var results = []; + for (var i=0; i < this.parts.length; i++) { + var part = this.parts[i]; + var result = (part == '?') ? DrNicTest.inspect(params.shift()) : part.replace(/\\\?/, '?'); + results.push(result); + }; + return results.join(''); +}; +// A generic function for performming AJAX requests +// It takes one argument, which is an object that contains a set of options +// All of which are outline in the comments, below +// From John Resig's book Pro JavaScript Techniques +// published by Apress, 2006-8 +DrNicTest.ajax = function( options ) { + + // Load the options object with defaults, if no + // values were provided by the user + options = { + // The type of HTTP Request + type: options.type || "POST", + + // The URL the request will be made to + url: options.url || "", + + // How long to wait before considering the request to be a timeout + timeout: options.timeout || 5000, + + // Functions to call when the request fails, succeeds, + // or completes (either fail or succeed) + onComplete: options.onComplete || function(){}, + onError: options.onError || function(){}, + onSuccess: options.onSuccess || function(){}, + + // The data type that'll be returned from the server + // the default is simply to determine what data was returned from the + // and act accordingly. + data: options.data || "" + }; + + // Create the request object + var xml = new XMLHttpRequest(); + + // Open the asynchronous POST request + xml.open(options.type, options.url, true); + + // We're going to wait for a request for 5 seconds, before giving up + var timeoutLength = 5000; + + // Keep track of when the request has been succesfully completed + var requestDone = false; + + // Initalize a callback which will fire 5 seconds from now, cancelling + // the request (if it has not already occurred). + setTimeout(function(){ + requestDone = true; + }, timeoutLength); + + // Watch for when the state of the document gets updated + xml.onreadystatechange = function(){ + // Wait until the data is fully loaded, + // and make sure that the request hasn't already timed out + if ( xml.readyState == 4 && !requestDone ) { + + // Check to see if the request was successful + if ( httpSuccess( xml ) ) { + + // Execute the success callback with the data returned from the server + options.onSuccess( httpData( xml, options.type ) ); + + // Otherwise, an error occurred, so execute the error callback + } else { + options.onError(); + } + + // Call the completion callback + options.onComplete(); + + // Clean up after ourselves, to avoid memory leaks + xml = null; + } + }; + + // Establish the connection to the server + xml.send(); + + // Determine the success of the HTTP response + function httpSuccess(r) { + try { + // If no server status is provided, and we're actually + // requesting a local file, then it was successful + return !r.status && location.protocol == "file:" || + + // Any status in the 200 range is good + ( r.status >= 200 && r.status < 300 ) || + + // Successful if the document has not been modified + r.status == 304 || + + // Safari returns an empty status if the file has not been modified + navigator.userAgent.indexOf("Safari") >= 0 && typeof r.status == "undefined"; + } catch(e){} + + // If checking the status failed, then assume that the request failed too + return false; + } + + // Extract the correct data from the HTTP response + function httpData(r,type) { + // Get the content-type header + var ct = r.getResponseHeader("content-type"); + + // If no default type was provided, determine if some + // form of XML was returned from the server + var data = !type && ct && ct.indexOf("xml") >= 0; + + // Get the XML Document object if XML was returned from + // the server, otherwise return the text contents returned by the server + data = type == "xml" || data ? r.responseXML : r.responseText; + + // If the specified type is "script", execute the returned text + // response as if it was JavaScript + if ( type == "script" ) + eval.call( window, data ); + + // Return the response data (either an XML Document or a text string) + return data; + } + +} +DrNicTest.Unit.Assertions = { + buildMessage: function(message, template) { + var args = DrNicTest.arrayfromargs(arguments).slice(2); + return (message ? message + '\n' : '') + + new DrNicTest.Unit.MessageTemplate(template).evaluate(args); + }, + + flunk: function(message) { + this.assertBlock(message || 'Flunked', function() { return false }); + }, + + assertBlock: function(message, block) { + try { + block.call(this) ? this.pass() : this.fail(message); + } catch(e) { this.error(e) } + }, + + assert: function(expression, message) { + message = this.buildMessage(message || 'assert', 'got <?>', expression); + this.assertBlock(message, function() { return expression }); + }, + + assertEqual: function(expected, actual, message) { + message = this.buildMessage(message || 'assertEqual', 'expected <?>, actual: <?>', expected, actual); + this.assertBlock(message, function() { return expected == actual }); + }, + + assertNotEqual: function(expected, actual, message) { + message = this.buildMessage(message || 'assertNotEqual', 'expected <?>, actual: <?>', expected, actual); + this.assertBlock(message, function() { return expected != actual }); + }, + + assertEnumEqual: function(expected, actual, message) { + message = this.buildMessage(message || 'assertEnumEqual', 'expected <?>, actual: <?>', expected, actual); + var expected_array = DrNicTest.flattenArray(expected); + var actual_array = DrNicTest.flattenArray(actual); + this.assertBlock(message, function() { + if (expected_array.length == actual_array.length) { + for (var i=0; i < expected_array.length; i++) { + if (expected_array[i] != actual_array[i]) return false; + }; + return true; + } + return false; + }); + }, + + assertEnumNotEqual: function(expected, actual, message) { + message = this.buildMessage(message || 'assertEnumNotEqual', '<?> was the same as <?>', expected, actual); + var expected_array = DrNicTest.flattenArray(expected); + var actual_array = DrNicTest.flattenArray(actual); + this.assertBlock(message, function() { + if (expected_array.length == actual_array.length) { + for (var i=0; i < expected_array.length; i++) { + if (expected_array[i] != actual_array[i]) return true; + }; + return false; + } + return true; + }); + }, + + assertHashEqual: function(expected, actual, message) { + message = this.buildMessage(message || 'assertHashEqual', 'expected <?>, actual: <?>', expected, actual); + var expected_array = DrNicTest.flattenArray(DrNicTest.hashToSortedArray(expected)); + var actual_array = DrNicTest.flattenArray(DrNicTest.hashToSortedArray(actual)); + var block = function() { + if (expected_array.length == actual_array.length) { + for (var i=0; i < expected_array.length; i++) { + if (expected_array[i] != actual_array[i]) return false; + }; + return true; + } + return false; + }; + this.assertBlock(message, block); + }, + + assertHashNotEqual: function(expected, actual, message) { + message = this.buildMessage(message || 'assertHashNotEqual', '<?> was the same as <?>', expected, actual); + var expected_array = DrNicTest.flattenArray(DrNicTest.hashToSortedArray(expected)); + var actual_array = DrNicTest.flattenArray(DrNicTest.hashToSortedArray(actual)); + // from now we recursively zip & compare nested arrays + var block = function() { + if (expected_array.length == actual_array.length) { + for (var i=0; i < expected_array.length; i++) { + if (expected_array[i] != actual_array[i]) return true; + }; + return false; + } + return true; + }; + this.assertBlock(message, block); + }, + + assertIdentical: function(expected, actual, message) { + message = this.buildMessage(message || 'assertIdentical', 'expected <?>, actual: <?>', expected, actual); + this.assertBlock(message, function() { return expected === actual }); + }, + + assertNotIdentical: function(expected, actual, message) { + message = this.buildMessage(message || 'assertNotIdentical', 'expected <?>, actual: <?>', expected, actual); + this.assertBlock(message, function() { return expected !== actual }); + }, + + assertNull: function(obj, message) { + message = this.buildMessage(message || 'assertNull', 'got <?>', obj); + this.assertBlock(message, function() { return obj === null }); + }, + + assertNotNull: function(obj, message) { + message = this.buildMessage(message || 'assertNotNull', 'got <?>', obj); + this.assertBlock(message, function() { return obj !== null }); + }, + + assertUndefined: function(obj, message) { + message = this.buildMessage(message || 'assertUndefined', 'got <?>', obj); + this.assertBlock(message, function() { return typeof obj == "undefined" }); + }, + + assertNotUndefined: function(obj, message) { + message = this.buildMessage(message || 'assertNotUndefined', 'got <?>', obj); + this.assertBlock(message, function() { return typeof obj != "undefined" }); + }, + + assertNullOrUndefined: function(obj, message) { + message = this.buildMessage(message || 'assertNullOrUndefined', 'got <?>', obj); + this.assertBlock(message, function() { return obj == null }); + }, + + assertNotNullOrUndefined: function(obj, message) { + message = this.buildMessage(message || 'assertNotNullOrUndefined', 'got <?>', obj); + this.assertBlock(message, function() { return obj != null }); + }, + + assertMatch: function(expected, actual, message) { + message = this.buildMessage(message || 'assertMatch', 'regex <?> did not match <?>', expected, actual); + this.assertBlock(message, function() { return new RegExp(expected).exec(actual) }); + }, + + assertNoMatch: function(expected, actual, message) { + message = this.buildMessage(message || 'assertNoMatch', 'regex <?> matched <?>', expected, actual); + this.assertBlock(message, function() { return !(new RegExp(expected).exec(actual)) }); + }, + + assertHidden: function(element, message) { + message = this.buildMessage(message || 'assertHidden', '? isn\'t hidden.', element); + this.assertBlock(message, function() { return element.style.display == 'none' }); + }, + + assertInstanceOf: function(expected, actual, message) { + message = this.buildMessage(message || 'assertInstanceOf', '<?> was not an instance of the expected type', actual); + this.assertBlock(message, function() { return actual instanceof expected }); + }, + + assertNotInstanceOf: function(expected, actual, message) { + message = this.buildMessage(message || 'assertNotInstanceOf', '<?> was an instance of the expected type', actual); + this.assertBlock(message, function() { return !(actual instanceof expected) }); + }, + + assertRespondsTo: function(method, obj, message) { + message = this.buildMessage(message || 'assertRespondsTo', 'object doesn\'t respond to <?>', method); + this.assertBlock(message, function() { return (method in obj && typeof obj[method] == 'function') }); + }, + + assertRaise: function(exceptionName, method, message) { + message = this.buildMessage(message || 'assertRaise', '<?> exception expected but none was raised', exceptionName); + var block = function() { + try { + method(); + return false; + } catch(e) { + if (e.name == exceptionName) return true; + else throw e; + } + }; + this.assertBlock(message, block); + }, + + assertNothingRaised: function(method, message) { + try { + method(); + this.assert(true, "Expected nothing to be thrown"); + } catch(e) { + message = this.buildMessage(message || 'assertNothingRaised', '<?> was thrown when nothing was expected.', e); + this.flunk(message); + } + }, + + _isVisible: function(element) { + element = DrNicTest.$(element); + if(!element.parentNode) return true; + this.assertNotNull(element); + if(element.style && element.style.display == 'none') + return false; + + return arguments.callee.call(this, element.parentNode); + }, + + assertVisible: function(element, message) { + message = this.buildMessage(message, '? was not visible.', element); + this.assertBlock(message, function() { return this._isVisible(element) }); + }, + + assertNotVisible: function(element, message) { + message = this.buildMessage(message, '? was not hidden and didn\'t have a hidden parent either.', element); + this.assertBlock(message, function() { return !this._isVisible(element) }); + }, + + assertElementsMatch: function() { + var pass = true, expressions = DrNicTest.arrayfromargs(arguments); + var elements = expressions.shift(); + if (elements.length != expressions.length) { + message = this.buildMessage('assertElementsMatch', 'size mismatch: ? elements, ? expressions (?).', elements.length, expressions.length, expressions); + this.flunk(message); + pass = false; + } + for (var i=0; i < expressions.length; i++) { + var expression = expressions[i]; + var element = DrNicTest.$(elements[i]); + if (DrNicTest.selectorMatch(expression, element)) { + pass = true; + break; + } + message = this.buildMessage('assertElementsMatch', 'In index <?>: expected <?> but got ?', index, expression, element); + this.flunk(message); + pass = false; + }; + this.assert(pass, "Expected all elements to match."); + }, + + assertElementMatches: function(element, expression, message) { + this.assertElementsMatch([element], expression); + } +}; +DrNicTest.Unit.Runner = function(testcases) { + var argumentOptions = arguments[1] || {}; + var options = this.options = {}; + options.testLog = ('testLog' in argumentOptions) ? argumentOptions.testLog : 'testlog'; + options.resultsURL = this.queryParams.resultsURL; + options.testLog = DrNicTest.$(options.testLog); + + this.tests = this.getTests(testcases); + this.currentTest = 0; + this.logger = new DrNicTest.Unit.Logger(options.testLog); + + var self = this; + DrNicTest.Event.addEvent(window, "load", function() { + setTimeout(function() { + self.runTests(); + }, 0.1); + }); +}; + +DrNicTest.Unit.Runner.prototype.queryParams = DrNicTest.toQueryParams(); + +DrNicTest.Unit.Runner.prototype.portNumber = function() { + if (window.location.search.length > 0) { + var matches = window.location.search.match(/\:(\d{3,5})\//); + if (matches) { + return parseInt(matches[1]); + } + } + return null; +}; + +DrNicTest.Unit.Runner.prototype.getTests = function(testcases) { + var tests = [], options = this.options; + if (this.queryParams.tests) tests = this.queryParams.tests.split(','); + else if (options.tests) tests = options.tests; + else if (options.test) tests = [option.test]; + else { + for (testname in testcases) { + if (testname.match(/^test/)) tests.push(testname); + } + } + var results = []; + for (var i=0; i < tests.length; i++) { + var test = tests[i]; + if (testcases[test]) + results.push( + new DrNicTest.Unit.Testcase(test, testcases[test], testcases.setup, testcases.teardown) + ); + }; + return results; +}; + +DrNicTest.Unit.Runner.prototype.getResult = function() { + var results = { + tests: this.tests.length, + assertions: 0, + failures: 0, + errors: 0 + }; + + for (var i=0; i < this.tests.length; i++) { + var test = this.tests[i]; + results.assertions += test.assertions; + results.failures += test.failures; + results.errors += test.errors; + }; + return results; +}; + +DrNicTest.Unit.Runner.prototype.postResults = function() { + if (this.options.resultsURL) { + // new Ajax.Request(this.options.resultsURL, + // { method: 'get', parameters: this.getResult(), asynchronous: false }); + var results = this.getResult(); + var url = this.options.resultsURL + "?"; + url += "assertions="+ results.assertions + "&"; + url += "failures=" + results.failures + "&"; + url += "errors=" + results.errors; + DrNicTest.ajax({ + url: url, + type: 'GET' + }) + } +}; + +DrNicTest.Unit.Runner.prototype.runTests = function() { + var test = this.tests[this.currentTest], actions; + + if (!test) return this.finish(); + if (!test.isWaiting) this.logger.start(test.name); + test.run(); + var self = this; + if(test.isWaiting) { + this.logger.message("Waiting for " + test.timeToWait + "ms"); + // setTimeout(this.runTests.bind(this), test.timeToWait || 1000); + setTimeout(function() { + self.runTests(); + }, test.timeToWait || 1000); + return; + } + + this.logger.finish(test.status(), test.summary()); + if (actions = test.actions) this.logger.appendActionButtons(actions); + this.currentTest++; + // tail recursive, hopefully the browser will skip the stackframe + this.runTests(); +}; + +DrNicTest.Unit.Runner.prototype.finish = function() { + this.postResults(); + this.logger.summary(this.summary()); +}; + +DrNicTest.Unit.Runner.prototype.summary = function() { + return new DrNicTest.Template('#{tests} tests, #{assertions} assertions, #{failures} failures, #{errors} errors').evaluate(this.getResult()); +}; +DrNicTest.Unit.Testcase = function(name, test, setup, teardown) { + this.name = name; + this.test = test || function() {}; + this.setup = setup || function() {}; + this.teardown = teardown || function() {}; + this.messages = []; + this.actions = {}; +}; +// import DrNicTest.Unit.Assertions + +for (method in DrNicTest.Unit.Assertions) { + DrNicTest.Unit.Testcase.prototype[method] = DrNicTest.Unit.Assertions[method]; +} + +DrNicTest.Unit.Testcase.prototype.isWaiting = false; +DrNicTest.Unit.Testcase.prototype.timeToWait = 1000; +DrNicTest.Unit.Testcase.prototype.assertions = 0; +DrNicTest.Unit.Testcase.prototype.failures = 0; +DrNicTest.Unit.Testcase.prototype.errors = 0; +// DrNicTest.Unit.Testcase.prototype.isRunningFromRake = window.location.port == 4711; +DrNicTest.Unit.Testcase.prototype.isRunningFromRake = window.location.port; + +DrNicTest.Unit.Testcase.prototype.wait = function(time, nextPart) { + this.isWaiting = true; + this.test = nextPart; + this.timeToWait = time; +}; + +DrNicTest.Unit.Testcase.prototype.run = function(rethrow) { + try { + try { + if (!this.isWaiting) this.setup(); + this.isWaiting = false; + this.test(); + } finally { + if(!this.isWaiting) { + this.teardown(); + } + } + } + catch(e) { + if (rethrow) throw e; + this.error(e, this); + } +}; + +DrNicTest.Unit.Testcase.prototype.summary = function() { + var msg = '#{assertions} assertions, #{failures} failures, #{errors} errors\n'; + return new DrNicTest.Template(msg).evaluate(this) + + this.messages.join("\n"); +}; + +DrNicTest.Unit.Testcase.prototype.pass = function() { + this.assertions++; +}; + +DrNicTest.Unit.Testcase.prototype.fail = function(message) { + this.failures++; + var line = ""; + try { + throw new Error("stack"); + } catch(e){ + line = (/\.html:(\d+)/.exec(e.stack || '') || ['',''])[1]; + } + this.messages.push("Failure: " + message + (line ? " Line #" + line : "")); +}; + +DrNicTest.Unit.Testcase.prototype.info = function(message) { + this.messages.push("Info: " + message); +}; + +DrNicTest.Unit.Testcase.prototype.error = function(error, test) { + this.errors++; + this.actions['retry with throw'] = function() { test.run(true) }; + this.messages.push(error.name + ": "+ error.message + "(" + DrNicTest.inspect(error) + ")"); +}; + +DrNicTest.Unit.Testcase.prototype.status = function() { + if (this.failures > 0) return 'failed'; + if (this.errors > 0) return 'error'; + return 'passed'; +}; + +DrNicTest.Unit.Testcase.prototype.benchmark = function(operation, iterations) { + var startAt = new Date(); + (iterations || 1).times(operation); + var timeTaken = ((new Date())-startAt); + this.info((arguments[2] || 'Operation') + ' finished ' + + iterations + ' iterations in ' + (timeTaken/1000)+'s' ); + return timeTaken; +}; + +Test = DrNicTest diff --git a/chrome/common/extensions/docs/examples/extensions/proxy_configuration/test/proxy_form_controller_test.html b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/test/proxy_form_controller_test.html new file mode 100644 index 0000000..096e709 --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/test/proxy_form_controller_test.html @@ -0,0 +1,113 @@ +<!doctype html> +<html> +<head> + <title>Popup for Proxy API Test</title> + <link rel="stylesheet" type="text/css" href="./unittest.css"> + <script src="./jsunittest.js"></script> + <script src="../proxy_form_controller.js"></script> +</head> +<body> + <h1>Proxy Configuration Unit Tests</h1> + + <h2>ProxyFormController</h2> + <div id="proxyformcontrollerlog"></div> + + <div id="fixture"> + <form id="proxyForm"> + <fieldset id="system"> + <legend>System Settings</legend> + <input type="radio" name="proxyType" id="proxyTypeSystem" value="system"> + <label for="proxyTypeSystem">Use the <em>system's proxy settings</em>.</label> + </fieldset> + <fieldset id="direct"> + <legend>Direct Connection</legend> + <input type="radio" name="proxyType" id="proxyTypeDirect" value="direct"> + <label for="proxyTypeDirect">Your computer is <em>directly connected</em> to the internet; no need for a proxy.</label> + </fieldset> + <fieldset id="pac_script"> + <legend>Automatic Configuration</legend> + <input type="radio" name="proxyType" id="proxyTypeAutoconfig" value="autoconfig"> + <label for="proxyTypeAutoconfig">Your proxy supports <em>automatic configuration</em>.</label> + + <section> + <label for="autoconfigURL">Autoconfiguration URL (PAC file)</label> + <input type="url" name="autoconfigURL" id="autoconfigURL"> + <input type="hidden" name="autoconfigData" id="autoconfigData"> + </section> + </fieldset> + <fieldset id="fixed_servers"> + <legend>Manual Proxy</legend> + <input type="radio" name="proxyType" id="proxyTypeManual" value="manual"> + <label for="proxyTypeManual">Configure your proxy settings <em>manually</em>.</label> + <section> + <fieldset> + <legend>HTTP</legend> + <label for="proxyHostHttp">Host</label> + <select id="proxySchemeHttp" name="proxySchemeHttp"> + <option selected value="http">http://</option> + <option value="https">https://</option> + <option value="socks4">socks4://</option> + <option value="socks5">socks5://</option> + </select> + <input type="text" name="proxyHostHttp" id="proxyHostHttp"> + + <label for="proxyPortHttp">Port</label> + <input type="number" min="1" step="1" name="proxyPortHttp" id="proxyPortHttp"> + + <input type="checkbox" name="singleProxyForEverything" id="singleProxyForEverything"> + <label for="singleProxyForEverything">Use the same proxy server for all protocols</label> + </fieldset> + <fieldset> + <legend>HTTPS</legend> + <label for="proxyHostHttps">Host</label> + <select id="proxySchemeHttps" name="proxySchemeHttps"> + <option selected value="http">http://</option> + <option value="https">https://</option> + <option value="socks4">socks4://</option> + <option value="socks5">socks5://</option> + </select> + <input type="text" name="proxyHostHttps" id="proxyHostHttps"> + + <label for="proxyPortHttps">Port</label> + <input type="number" min="1" step="1" name="proxyPortHttps" id="proxyPortHttps"> + </fieldset> + <fieldset> + <legend>FTP</legend> + <label for="proxyHostFtp">Host</label> + <select id="proxySchemeFtp" name="proxySchemeFtp"> + <option selected value="http">http://</option> + <option value="https">https://</option> + <option value="socks4">socks4://</option> + <option value="socks5">socks5://</option> + </select> + <input type="text" name="proxyHostFtp" id="proxyHostFtp"> + + <label for="proxyPortFtp">Port</label> + <input type="number" min="1" step="1" name="proxyPortFtp" id="proxyPortFtp"> + </fieldset> + <fieldset> + <legend>Fallback</legend> + <label for="proxyHostFallback">Host</label> + <select id="proxySchemeFallback" name="proxySchemeFallback"> + <option selected value="http">http://</option> + <option value="https">https://</option> + <option value="socks4">socks4://</option> + <option value="socks5">socks5://</option> + </select> + <input type="text" name="proxyHostFallback" id="proxyHostFallback"> + + <label for="proxyPortFallback">Port</label> + <input type="number" min="1" step="1" name="proxyPortFallback" id="proxyPortFallback"> + </fieldset> + <fieldset> + <label for="bypassList">Bypass proxy for these hosts:</label> + <textarea id="bypassList" name="bypassList" placeholder="localhost,192.168.1.1/16, .example.com"></textarea> + </fieldset> + </section> + </fieldset> + <input type="submit" value="Save proxy settings"> + </form> + </div> + <script src="./proxy_form_controller_test.js"></script> +</body> +</html> diff --git a/chrome/common/extensions/docs/examples/extensions/proxy_configuration/test/proxy_form_controller_test.js b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/test/proxy_form_controller_test.js new file mode 100644 index 0000000..35fd424 --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/test/proxy_form_controller_test.js @@ -0,0 +1,516 @@ +// Copyright (c) 2011 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. + +// Stub out the `chrome.experimental.proxy` API +chrome.experimental = chrome.experimental || { + proxy: { + settings: { + get: function() {}, + clear: function() {}, + set: function() {} + } + } +}; +// Stub out i18n +chrome.i18n = chrome.i18n || { + getMessage: function(x) { return x; } +}; +// Stub out messaging +chrome.extension = chrome.extension || { + sendRequest: function() {} +}; +var fixture = document.getElementById('fixture'); +var baselineHTML = fixture.innerHTML; +var groupIDs = [ProxyFormController.ProxyTypes.DIRECT, + ProxyFormController.ProxyTypes.SYSTEM, + ProxyFormController.ProxyTypes.PAC, + ProxyFormController.ProxyTypes.FIXED]; + +var mockFunctionFactory = function(returnValue, logging) { + var called = []; + returnValue = returnValue || null; + + var funky = function() { + called.push(arguments); + if (arguments[1] && typeof(arguments[1]) === 'function') { + var funk = arguments[1]; + funk(returnValue); + } + return returnValue; + }; + funky.getCallList = function() { return called; }; + funky.getValue = function() { return returnValue; }; + return funky; +}; + +var chrome = chrome || {}; + +var proxyform = new Test.Unit.Runner({ + setup: function() { + fixture.innerHTML = baselineHTML; + this.controller_ = new ProxyFormController('proxyForm'); + this.clickEvent_ = document.createEvent('MouseEvents'); + this.clickEvent_.initMouseEvent('click', true, true, window, + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + // Reset mock functions. + chrome.experimental = { + proxy: { + settings: { + get: mockFunctionFactory({ + value: {mode: 'system' }, + levelOfControl: 'ControllableByThisExtension' }), + clear: mockFunctionFactory({ + value: {mode: 'system' }, + levelOfControl: 'ControllableByThisExtension' }), + set: mockFunctionFactory({ + value: {mode: 'system' }, + levelOfControl: 'ControllableByThisExtension' }) + } + } + }; + }, + + teardown: function() { + fixture.removeChild(fixture.childNodes[0]); + delete(this.controller_); + }, + + // Clicking on various bits of the interface should set correct classes, + // and select correct radio buttons. + testActivationClicks: function() { + var self = this; + var i; + groupIDs.forEach(function(id) { + var group = document.getElementById(id); + var all = group.querySelectorAll('*'); + for (i = 0; i < all.length; i++) { + group.classList.remove('active'); + all[i].dispatchEvent(self.clickEvent_); + self.assert(group.classList.contains('active')); + } + }); + }, + + // Elements inside an active group should not be disabled, and vice versa + testDisabledElements: function() { + var self = this; + var i, j; + groupIDs.forEach(function(id) { + var group = document.getElementById(id); + var all = group.querySelectorAll('*'); + // First, check that activating a group enables its form elements + for (i = 0; i < all.length; i++) { + group.classList.remove('active'); + var inputs = group.querySelectorAll('input:not([type="radio"]),select'); + for (j = 0; j < inputs.length; j++) { + inputs[j].setAttribute('disabled', 'disabled'); + } + all[i].dispatchEvent(self.clickEvent_); + for (j = 0; j < inputs.length; j++) { + self.assert(!inputs[j].hasAttribute('disabled')); + } + } + }); + }, + + // Clicking the "Use single proxy" checkbox should set the correct + // classes on the form. + testSingleProxyToggle: function() { + var group = document.getElementById( + ProxyFormController.ProxyTypes.FIXED); + var checkbox = document.getElementById('singleProxyForEverything'); + var section = checkbox.parentNode.parentNode; + // Checkbox only works in active group, `testActivationClicks` tests + // the inactive click behavior. + group.classList.add('active'); + + checkbox.checked = false; + checkbox.dispatchEvent(this.clickEvent_); + this.assert(section.classList.contains('single')); + checkbox.dispatchEvent(this.clickEvent_); + this.assert(!section.classList.contains('single')); + }, + + // On instantiation, ProxyFormController should read the current state + // from `chrome.experimental.getCurrentProxySettings`, and react + // accordingly. Let's see if that happens with the next four sets of + // assertsions. + testSetupFormSystem: function() { + chrome.experimental.proxy.settings.get = mockFunctionFactory({ + value: {mode: 'system'}, + levelOfControl: 'ControllableByThisExtension' + }); + + fixture.innerHTML = baselineHTML; + this.controller_ = new ProxyFormController('proxyForm'); + // Wait for async calls to fire + this.wait(100, function() { + this.assertEqual( + 2, + chrome.experimental.proxy.settings.get.getCallList().length); + this.assert( + document.getElementById(ProxyFormController.ProxyTypes.SYSTEM) + .classList.contains('active')); + }); + }, + + testSetupFormDirect: function() { + chrome.experimental.proxy.settings.get = + mockFunctionFactory({value: {mode: 'direct'}, + levelOfControl: 'ControllableByThisExtension'}, true); + + fixture.innerHTML = baselineHTML; + this.controller_ = new ProxyFormController('proxyForm'); + // Wait for async calls to fire + this.wait(100, function() { + this.assertEqual( + 2, + chrome.experimental.proxy.settings.get.getCallList().length); + this.assert( + document.getElementById(ProxyFormController.ProxyTypes.DIRECT) + .classList.contains('active')); + }); + }, + + testSetupFormPac: function() { + chrome.experimental.proxy.settings.get = + mockFunctionFactory({value: {mode: 'pac_script' }, + levelOfControl: 'ControllableByThisExtension'}); + + fixture.innerHTML = baselineHTML; + this.controller_ = new ProxyFormController('proxyForm'); + // Wait for async calls to fire + this.wait(100, function() { + this.assertEqual( + 2, + chrome.experimental.proxy.settings.get.getCallList().length); + this.assert( + document.getElementById(ProxyFormController.ProxyTypes.PAC) + .classList.contains('active')); + }); + }, + + testSetupFormFixed: function() { + chrome.experimental.proxy.settings.get = + mockFunctionFactory({value: {mode: 'fixed_servers' }, + levelOfControl: 'ControllableByThisExtension'}); + + fixture.innerHTML = baselineHTML; + this.controller_ = new ProxyFormController('proxyForm'); + // Wait for async calls to fire + this.wait(100, function() { + this.assertEqual( + 2, + chrome.experimental.proxy.settings.get.getCallList().length); + this.assert( + document.getElementById(ProxyFormController.ProxyTypes.FIXED) + .classList.contains('active')); + }); + }, + + // Test that `recalcFormValues_` correctly sets DOM field values when + // given a `ProxyConfig` structure + testRecalcFormValuesGroups: function() { + // Test `AUTO` normalization to `PAC` + this.controller_.recalcFormValues_({ + mode: ProxyFormController.ProxyTypes.AUTO, + rules: {}, + pacScript: '' + }); + this.assert( + document.getElementById(ProxyFormController.ProxyTypes.PAC) + .classList.contains('active')); + + // DIRECT + this.controller_.recalcFormValues_({ + mode: ProxyFormController.ProxyTypes.DIRECT, + rules: {}, + pacScript: '' + }); + this.assert( + document.getElementById(ProxyFormController.ProxyTypes.DIRECT) + .classList.contains('active')); + + // FIXED + this.controller_.recalcFormValues_({ + mode: ProxyFormController.ProxyTypes.FIXED, + rules: {}, + pacScript: '' + }); + this.assert( + document.getElementById(ProxyFormController.ProxyTypes.FIXED) + .classList.contains('active')); + + // PAC + this.controller_.recalcFormValues_({ + mode: ProxyFormController.ProxyTypes.PAC, + rules: {}, + pacScript: '' + }); + this.assert( + document.getElementById(ProxyFormController.ProxyTypes.PAC) + .classList.contains('active')); + + // SYSTEM + this.controller_.recalcFormValues_({ + mode: ProxyFormController.ProxyTypes.SYSTEM, + rules: {}, + pacScript: '' + }); + this.assert( + document.getElementById(ProxyFormController.ProxyTypes.SYSTEM) + .classList.contains('active')); + }, + + testRecalcFormValuesFixedSingle: function() { + this.controller_.recalcFormValues_({ + mode: ProxyFormController.ProxyTypes.FIXED, + rules: { + singleProxy: { + scheme: 'socks5', + host: 'singleproxy.example.com', + port: '1234' + } + } + }); + var single = this.controller_.singleProxy; + this.assertEqual('socks5', single.scheme); + this.assertEqual('singleproxy.example.com', single.host); + this.assertEqual(1234, single.port); + }, + + testRecalcFormValuesPacScript: function() { + this.controller_.recalcFormValues_({ + mode: ProxyFormController.ProxyTypes.PAC, + rules: {}, + pacScript: {url: 'http://example.com/this/is/a/pac.script'} + }); + this.assertEqual( + 'http://example.com/this/is/a/pac.script', + document.getElementById('autoconfigURL').value); + }, + + testRecalcFormValuesSingle: function() { + this.controller_.recalcFormValues_({ + mode: ProxyFormController.ProxyTypes.FIXED, + rules: { + singleProxy: { + scheme: 'https', + host: 'example.com', + port: 80 + } + } + }); + // Single! + this.assert( + document.querySelector('#' + ProxyFormController.ProxyTypes.FIXED + + ' > section').classList.contains('single')); + + var single = this.controller_.singleProxy; + this.assertEqual('https', single.scheme); + this.assertEqual('example.com', single.host); + this.assertEqual(80, single.port); + }, + + testRecalcFormValuesMultiple: function() { + this.controller_.recalcFormValues_({ + mode: ProxyFormController.ProxyTypes.FIXED, + rules: { + proxyForHttp: { + scheme: 'http', + host: 'http.example.com', + port: 1 + }, + proxyForHttps: { + scheme: 'https', + host: 'https.example.com', + port: 2 + }, + proxyForFtp: { + scheme: 'socks4', + host: 'socks4.example.com', + port: 3 + }, + fallbackProxy: { + scheme: 'socks5', + host: 'socks5.example.com', + port: 4 + } + } + }); + // Not Single! + this.assert( + !document.querySelector('#' + ProxyFormController.ProxyTypes.FIXED + + ' > section').classList.contains('single')); + var server = this.controller_.singleProxy; + this.assertNull(server); + + server = this.controller_.httpProxy; + this.assertEqual('http', server.scheme); + this.assertEqual('http.example.com', server.host); + this.assertEqual(1, server.port); + + server = this.controller_.httpsProxy; + this.assertEqual('https', server.scheme); + this.assertEqual('https.example.com', server.host); + this.assertEqual(2, server.port); + + server = this.controller_.ftpProxy; + this.assertEqual('socks4', server.scheme); + this.assertEqual('socks4.example.com', server.host); + this.assertEqual(3, server.port); + + server = this.controller_.fallbackProxy; + this.assertEqual('socks5', server.scheme); + this.assertEqual('socks5.example.com', server.host); + this.assertEqual(4, server.port); + }, + + testBypassList: function() { + this.controller_.bypassList = ['1.example.com', + '2.example.com', + '3.example.com']; + this.assertEnumEqual( + document.getElementById('bypassList').value, + '1.example.com, 2.example.com, 3.example.com'); + this.assertEnumEqual( + this.controller_.bypassList, + ['1.example.com', '2.example.com', '3.example.com']); + }, + + // Test that "system" rules are correctly generated + testProxyRulesGenerationSystem: function() { + this.controller_.changeActive_( + document.getElementById(ProxyFormController.ProxyTypes.SYSTEM)); + + this.assertHashEqual( + {mode: 'system'}, + this.controller_.generateProxyConfig_()); + }, + + // Test that "direct" rules are correctly generated + testProxyRulesGenerationDirect: function() { + this.controller_.changeActive_( + document.getElementById(ProxyFormController.ProxyTypes.DIRECT)); + + this.assertHashEqual( + {mode: 'direct'}, + this.controller_.generateProxyConfig_()); + }, + + // Test that auto detection rules are correctly generated when "automatic" + // is selected, and no PAC file URL is given + testProxyRulesGenerationAuto: function() { + this.controller_.changeActive_( + document.getElementById(ProxyFormController.ProxyTypes.PAC)); + + this.assertHashEqual( + {mode: 'auto_detect'}, + this.controller_.generateProxyConfig_()); + }, + + // Test that PAC URL rules are correctly generated when "automatic" + // is selected, and a PAC file URL is given + testProxyRulesGenerationPacURL: function() { + this.controller_.changeActive_( + document.getElementById(ProxyFormController.ProxyTypes.PAC)); + this.controller_.pacURL = 'http://example.com/pac.pac'; + var result = this.controller_.generateProxyConfig_(); + this.assertEqual('pac_script', result.mode); + this.assertEqual('http://example.com/pac.pac', result.pacScript.url); + }, + + // Manual PAC definitions + testProxyRulesGenerationPacData: function() { + var pacData = 'function FindProxyForURL(url,host) { return "DIRECT"; }'; + this.controller_.changeActive_( + document.getElementById(ProxyFormController.ProxyTypes.PAC)); + this.controller_.manualPac = pacData; + var result = this.controller_.generateProxyConfig_(); + this.assertEqual('pac_script', result.mode); + this.assertEqual(pacData, result.pacScript.data); + }, + + // PAC URLs override manual PAC definitions + testProxyRulesGenerationPacURLOverridesData: function() { + this.controller_.changeActive_( + document.getElementById(ProxyFormController.ProxyTypes.PAC)); + this.controller_.pacURL = 'http://example.com/pac.pac'; + this.controller_.manualPac = + 'function FindProxyForURL(url,host) { return "DIRECT"; }'; + var result = this.controller_.generateProxyConfig_(); + this.assertEqual('pac_script', result.mode); + this.assertEqual('http://example.com/pac.pac', result.pacScript.url); + }, + + // Test that fixed, manual servers are correctly transformed into a + // `ProxyRules` structure. + testProxyRulesGenerationSingle: function() { + this.controller_.changeActive_( + document.getElementById(ProxyFormController.ProxyTypes.FIXED)); + + this.controller_.singleProxy = { + scheme: 'http', + host: 'example.com', + port: '80' + }; + + var result = this.controller_.generateProxyConfig_(); + this.assertEqual('fixed_servers', result.mode); + this.assertEqual('http', result.rules.singleProxy.scheme); + this.assertEqual('example.com', result.rules.singleProxy.host); + this.assertEqual(80, result.rules.singleProxy.port); + this.assertEqual(undefined, result.rules.proxyForHttp); + this.assertEqual(undefined, result.rules.proxyForHttps); + this.assertEqual(undefined, result.rules.proxyForFtp); + this.assertEqual(undefined, result.rules.fallbackProxy); + }, + + // Test that proxy configuration rules are correctly generated + // for separate manually entered servers. + testProxyRulesGenerationSeparate: function() { + this.controller_.changeActive_( + document.getElementById(ProxyFormController.ProxyTypes.FIXED)); + + this.controller_.singleProxy = false; + this.controller_.httpProxy = { + scheme: 'http', + host: 'http.example.com', + port: 80 + }; + this.controller_.httpsProxy = { + scheme: 'https', + host: 'https.example.com', + port: 443 + }; + this.controller_.ftpProxy = { + scheme: 'socks4', + host: 'ftp.example.com', + port: 80 + }; + this.controller_.fallbackProxy = { + scheme: 'socks5', + host: 'fallback.example.com', + port: 80 + }; + + var result = this.controller_.generateProxyConfig_(); + this.assertEqual('fixed_servers', result.mode); + this.assertEqual(undefined, result.rules.singleProxy); + this.assertEqual('http', result.rules.proxyForHttp.scheme); + this.assertEqual('http.example.com', result.rules.proxyForHttp.host); + this.assertEqual('80', result.rules.proxyForHttp.port); + this.assertEqual('https', result.rules.proxyForHttps.scheme); + this.assertEqual('https.example.com', result.rules.proxyForHttps.host); + this.assertEqual('443', result.rules.proxyForHttps.port); + this.assertEqual('socks4', result.rules.proxyForFtp.scheme); + this.assertEqual('ftp.example.com', result.rules.proxyForFtp.host); + this.assertEqual('80', result.rules.proxyForFtp.port); + this.assertEqual('socks5', result.rules.fallbackProxy.scheme); + this.assertEqual('fallback.example.com', result.rules.fallbackProxy.host); + this.assertEqual('80', result.rules.fallbackProxy.port); + } +}, { testLog: 'proxyformcontrollerlog' }); + +var c = new ProxyFormController('proxyForm'); diff --git a/chrome/common/extensions/docs/examples/extensions/proxy_configuration/test/unittest.css b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/test/unittest.css new file mode 100644 index 0000000..d62b1c5 --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/test/unittest.css @@ -0,0 +1,58 @@ +body, div, p, h1, h2, h3, ul, ol, span, a, table, td, form, img, li { + font-family: sans-serif; +} + +body { + font-size:0.8em; +} + +#log { + padding-bottom: 1em; + border-bottom: 2px solid #000; + margin-bottom: 2em; +} + +.logsummary { + margin-top: 1em; + margin-bottom: 1em; + padding: 1ex; + border: 1px solid #000; + font-weight: bold; +} + +.logtable { + width:100%; + border-collapse: collapse; + border: 1px dotted #666; +} + +.logtable td, .logtable th { + text-align: left; + padding: 3px 8px; + border: 1px dotted #666; +} + +.logtable .passed { + background-color: #cfc; +} + +.logtable .failed, .logtable .error { + background-color: #fcc; +} + +.logtable .warning { + background-color: #FC6; +} + +.logtable td div.action_buttons { + display: inline; +} + +.logtable td div.action_buttons input { + margin: 0 5px; + font-size: 10px; +} + +#fixture { + display: none; +} diff --git a/chrome/common/extensions/docs/samples.html b/chrome/common/extensions/docs/samples.html index bda40f4..7efcbc4 100644 --- a/chrome/common/extensions/docs/samples.html +++ b/chrome/common/extensions/docs/samples.html @@ -313,7 +313,7 @@ <!-- STATIC CONTENT PLACEHOLDER --> <div id="static"><link rel="stylesheet" href="css/samples.css"> -<script>var search_data = {"0262260daf0c8f7b28feff2ef23b05e7abf9d1e0":"A BROWSER ACTION WHICH CHANGES ITS ICON WHEN CLICKED. BACKGROUND_PAGE BROWSER_ACTION TABS CHROME.BROWSERACTION.ONCLICKED CHROME.BROWSERACTION.SETICON","ea2894c41cb8e80a4433a3e6c5772dadce9be90d":"A BROWSER ACTION WITH A POPUP THAT CHANGES THE PAGE COLOR. BROWSER_ACTION POPUP TABS CHROME.TABS.EXECUTESCRIPT","ede3c47b7757245be42ec33fd5ca63df4b490066":"A BROWSER ACTION WITH NO ICON THAT MAKES THE PAGE RED BACKGROUND_PAGE BROWSER_ACTION TABS CHROME.BROWSERACTION.ONCLICKED CHROME.BROWSERACTION.SETBADGEBACKGROUNDCOLOR CHROME.BROWSERACTION.SETBADGETEXT CHROME.TABS.EXECUTESCRIPT","fbf0aa1a09a15ff8cc4fc7de4fd176d6c663d07a":"ACCEPTLANGUAGE RETURNS ACCEPT LANGUAGES OF THE BROWSER BROWSER_ACTION POPUP CHROME.I18N.GETACCEPTLANGUAGES CHROME.I18N.GETMESSAGE","9a6e4ec46997fb92b324974afa08a3d007e2537f":"ANIMATED PAGE ACTION THIS EXTENSION ADDS AN ANIMATED BROWSER ACTION TO THE TOOLBAR. BACKGROUND_PAGE PAGE_ACTION TABS CHROME.PAGEACTION.HIDE CHROME.PAGEACTION.ONCLICKED CHROME.PAGEACTION.SETICON CHROME.PAGEACTION.SETTITLE CHROME.PAGEACTION.SHOW CHROME.TABS.GET CHROME.TABS.GETSELECTED CHROME.TABS.ONSELECTIONCHANGED","a1f7cf79dd555b04fa8d603247a040e644996293":"APP LAUNCHER BROWSER_ACTION MANAGEMENT CHROME.EXTENSION.GETURL CHROME.MANAGEMENT.GET CHROME.MANAGEMENT.GETALL CHROME.MANAGEMENT.LAUNCHAPP CHROME.TABS.CREATE","9747e3d6a3eab39bc7c17f11a80573c62d44c7e5":"BLANK NEW TAB PAGE CHROME_URL_OVERRIDES","903e7277139e1e6caec123d3319cab295d8d1b3a":"CHROME SOUNDS ENJOY A MORE MAGICAL AND IMMERSIVE EXPERIENCE WHEN BROWSING THE WEB USING THE POWER OF SOUND. BACKGROUND_PAGE BOOKMARKS OPTIONS_PAGE TABS CHROME.BOOKMARKS.ONCREATED CHROME.BOOKMARKS.ONMOVED CHROME.BOOKMARKS.ONREMOVED CHROME.EXTENSION.GETBACKGROUNDPAGE CHROME.EXTENSION.ONREQUEST CHROME.EXTENSION.SENDREQUEST CHROME.TABS.GET CHROME.TABS.ONATTACHED CHROME.TABS.ONCREATED CHROME.TABS.ONDETACHED CHROME.TABS.ONMOVED CHROME.TABS.ONREMOVED CHROME.TABS.ONSELECTIONCHANGED CHROME.TABS.ONUPDATED CHROME.WINDOWS.ONCREATED CHROME.WINDOWS.ONFOCUSCHANGED CHROME.WINDOWS.ONREMOVED","0e790e035a4a00b6f1def5ef9a7d7be1bce95ab5":"CHROMIUM BUILDBOT MONITOR DISPLAYS THE STATUS OF THE CHROMIUM BUILDBOT IN THE TOOLBAR. CLICK TO SEE MORE DETAILED STATUS IN A POPUP. BACKGROUND_PAGE BROWSER_ACTION NOTIFICATIONS OPTIONS_PAGE POPUP CHROME.BROWSERACTION.SETBADGEBACKGROUNDCOLOR CHROME.BROWSERACTION.SETBADGETEXT CHROME.BROWSERACTION.SETTITLE CHROME.EXTENSION.GETURL","ac31228200b41a87982e386cc90d3a6eee4ad885":"CHROMIUM SEARCH ADD SUPPORT TO THE OMNIBOX TO SEARCH THE CHROMIUM SOURCE CODE. BACKGROUND_PAGE TABS CHROME.OMNIBOX.ONINPUTCANCELLED CHROME.OMNIBOX.ONINPUTCHANGED CHROME.OMNIBOX.ONINPUTENTERED CHROME.OMNIBOX.ONINPUTSTARTED CHROME.OMNIBOX.SETDEFAULTSUGGESTION CHROME.TABS.GET CHROME.TABS.GETSELECTED CHROME.TABS.UPDATE","7d5d6cf195bc25480256618e360aa38c6e6fba82":"CLD DISPLAYS THE LANGUAGE OF A TAB BACKGROUND_PAGE BROWSER_ACTION TABS CHROME.BROWSERACTION.SETBADGETEXT CHROME.TABS.DETECTLANGUAGE CHROME.TABS.GET CHROME.TABS.GETSELECTED CHROME.TABS.ONSELECTIONCHANGED CHROME.TABS.ONUPDATED","5d81304a17cf7ac2887484f730fbd2b01e51e166":"CONTEXT MENUS SAMPLE SHOWS SOME OF THE FEATURES OF THE CONTEXT MENUS API BACKGROUND_PAGE CONTEXTMENUS CHROME.CONTEXTMENUS.CREATE","4daa6becd0899a54776d9cf7f09613ed1a9f4d77":"COOKIE API TEST EXTENSION TESTING COOKIE API BACKGROUND_PAGE BROWSER_ACTION COOKIES TABS CHROME.BROWSERACTION.ONCLICKED CHROME.COOKIES.GET CHROME.COOKIES.GETALL CHROME.COOKIES.ONCHANGED CHROME.COOKIES.REMOVE CHROME.EXTENSION.GETURL CHROME.TABS.CREATE CHROME.TABS.UPDATE CHROME.WINDOWS.GET CHROME.WINDOWS.GETALL","6871d09f4a96bf9d4b6cc724d00e909cee0f3902":"CROSS-DOMAIN XMLHTTPREQUEST FROM A CONTENT SCRIPT DEMONSTRATES A METHOD TO MAKE A CROSS-DOMAIN XMLHTTPREQUEST FETCH FROM A CONTENT SCRIPT. THIS EXTENSION FETCHES THE CURRENT TRENDING TOPICS FROM TWITTER AND INSERTS THEM IN AN OVERLAY AT THE TOP OF GOOGLE NEWS. VISIT HTTP://NEWS.GOOGLE.COM TO TEST THIS EXTENSION. BACKGROUND_PAGE CHROME.EXTENSION.ONREQUEST CHROME.EXTENSION.SENDREQUEST","028eb5364924344029bcbe1d527f132fc72b34e5":"EMAIL THIS PAGE (BY GOOGLE) THIS EXTENSION ADDS AN EMAIL BUTTON TO THE TOOLBAR WHICH ALLOWS YOU TO EMAIL THE PAGE LINK USING YOUR DEFAULT MAIL CLIENT OR GMAIL. BACKGROUND_PAGE BROWSER_ACTION OPTIONS_PAGE TABS CHROME.BROWSERACTION.ONCLICKED CHROME.EXTENSION.CONNECT CHROME.EXTENSION.ONCONNECT CHROME.TABS.CREATE CHROME.TABS.EXECUTESCRIPT CHROME.TABS.UPDATE","763a08e9b06595d785568a8d392b95a2f3700258":"EVENT TRACKING WITH GOOGLE ANALYTICS A SAMPLE EXTENSION WHICH USES GOOGLE ANALYTICS TO TRACK USAGE. BACKGROUND_PAGE BROWSER_ACTION POPUP","e3df888a89e35bdeb9c8bc8d03be5e1851b97c68":"EXTENSION DOCS SEARCH SEARCH THE CHROME EXTENSIONS DOCUMENTATION. TO USE, TYPE CRDOC PLUS A SEARCH TERM INTO THE OMNIBOX. BACKGROUND_PAGE TABS CHROME.OMNIBOX.ONINPUTCHANGED CHROME.OMNIBOX.ONINPUTENTERED CHROME.TABS.CREATE CHROME.TABS.GET CHROME.TABS.ONREMOVED CHROME.TABS.UPDATE","8b0dd31216235941bdd8eb33fda915ef5cf79a82":"GOOGLE CALENDAR CHECKER (BY GOOGLE) QUICKLY SEE THE TIME UNTIL YOUR NEXT MEETING FROM ANY OF YOUR CALENDARS. CLICK ON THE BUTTON TO BE TAKEN TO YOUR CALENDAR. BACKGROUND_PAGE BROWSER_ACTION OPTIONS_PAGE TABS CHROME.BROWSERACTION.ONCLICKED CHROME.BROWSERACTION.SETBADGEBACKGROUNDCOLOR CHROME.BROWSERACTION.SETBADGETEXT CHROME.BROWSERACTION.SETICON CHROME.BROWSERACTION.SETTITLE CHROME.EXTENSION.GETBACKGROUNDPAGE CHROME.EXTENSION.ONREQUEST CHROME.EXTENSION.SENDREQUEST CHROME.I18N.GETMESSAGE CHROME.TABS.CREATE CHROME.TABS.GET CHROME.TABS.GETALLINWINDOW CHROME.TABS.ONUPDATED CHROME.TABS.UPDATE","4e35caa9742fb82dbd628892d23a781614f6eff6":"GOOGLE DOCUMENT LIST VIEWER DEMONSTRATES HOW TO USE OAUTH TO CONNECT THE GOOGLE DOCUMENTS LIST DATA API. BACKGROUND_PAGE BROWSER_ACTION OPTIONS_PAGE POPUP TABS CHROME.BROWSERACTION.SETBADGETEXT CHROME.EXTENSION.GETBACKGROUNDPAGE CHROME.EXTENSION.GETURL CHROME.TABS.CREATE CHROME.TABS.GET CHROME.TABS.GETSELECTED CHROME.TABS.ONUPDATED CHROME.TABS.REMOVE","bb57f7a0132cbeb36ad7e7bb0ab75c21704234ca":"GOOGLE MAIL CHECKER DISPLAYS THE NUMBER OF UNREAD MESSAGES IN YOUR GOOGLE MAIL INBOX. YOU CAN ALSO CLICK THE BUTTON TO OPEN YOUR INBOX. BACKGROUND_PAGE BROWSER_ACTION OPTIONS_PAGE TABS CHROME.BROWSERACTION.ONCLICKED CHROME.BROWSERACTION.SETBADGEBACKGROUNDCOLOR CHROME.BROWSERACTION.SETBADGETEXT CHROME.BROWSERACTION.SETICON CHROME.EXTENSION.GETBACKGROUNDPAGE CHROME.I18N.GETMESSAGE CHROME.TABS.CREATE CHROME.TABS.GET CHROME.TABS.GETALLINWINDOW CHROME.TABS.ONUPDATED CHROME.TABS.UPDATE","1682e05ea9a1bde985123b04f6f8ac50a8a64033":"GOOGLE WAVE NOTIFIER FIND OUT WHEN YOU HAVE NEW WAVES AND PREVIEW THEM FAST. BACKGROUND_PAGE BROWSER_ACTION OPTIONS_PAGE POPUP TABS CHROME.EXTENSION.GETBACKGROUNDPAGE CHROME.EXTENSION.GETURL CHROME.TABS.CREATE CHROME.TABS.GET CHROME.TABS.GETSELECTED CHROME.TABS.ONUPDATED CHROME.TABS.REMOVE","14b9651fda4e57b2a5914ba73a779812201b750a":"HELLO WORLD THE FIRST EXTENSION THAT I MADE. BROWSER_ACTION POPUP","2020d72f2577f53caf8e94e3dbac0fb849ceaa4d":"IDLE - SIMPLE EXAMPLE DEMONSTRATES THE IDLE API BACKGROUND_PAGE BROWSER_ACTION IDLE CHROME.BROWSERACTION.ONCLICKED CHROME.EXTENSION.GETBACKGROUNDPAGE CHROME.IDLE.ONSTATECHANGED CHROME.IDLE.QUERYSTATE","0ea1588bd07b20338fc21f725de1542a5fdf9726":"IGOOGLE NEW TAB PAGE CHROME_URL_OVERRIDES","646325c25f572a1d15edc73d057f821d847a4fbe":"IMAGEINFO GET IMAGE INFO FOR IMAGES, INCLUDING EXIF DATA BACKGROUND_PAGE CONTEXTMENUS TABS CHROME.CONTEXTMENUS.CREATE CHROME.TABS.GET CHROME.TABS.GETCURRENT CHROME.WINDOWS.CREATE CHROME.WINDOWS.UPDATE","ec97ec20ca2f095d081e39f1565fc12af09ef067":"MAPPY FINDS ADDRESSES IN THE WEB PAGE YOURE ON AND POPS UP A MAP WINDOW. BACKGROUND_PAGE PAGE_ACTION POPUP TABS CHROME.EXTENSION.GETBACKGROUNDPAGE CHROME.EXTENSION.ONREQUEST CHROME.PAGEACTION.HIDE CHROME.PAGEACTION.SETTITLE CHROME.PAGEACTION.SHOW CHROME.TABS.GET CHROME.TABS.GETSELECTED CHROME.TABS.ONSELECTIONCHANGED CHROME.TABS.ONUPDATED CHROME.TABS.SENDREQUEST","b2f5f8a790e16f091a7e4e0a39b2d0a6d32e3a6d":"MERGE WINDOWS MERGES ALL OF THE BROWSERS WINDOWS INTO THE CURRENT WINDOW BACKGROUND_PAGE BROWSER_ACTION TABS CHROME.BROWSERACTION.ONCLICKED CHROME.TABS.GET CHROME.TABS.GETALLINWINDOW CHROME.TABS.MOVE CHROME.WINDOWS.GET CHROME.WINDOWS.GETALL CHROME.WINDOWS.GETCURRENT","51a83d2ba3a32e3ff1bdb624d4e18ccec4c4038e":"MESSAGE TIMER TIMES HOW LONG IT TAKES TO SEND A MESSAGE TO A CONTENT SCRIPT AND BACK. BROWSER_ACTION POPUP TABS CHROME.EXTENSION.ONCONNECT CHROME.EXTENSION.ONREQUEST CHROME.TABS.CONNECT CHROME.TABS.GET CHROME.TABS.GETSELECTED CHROME.TABS.SENDREQUEST","4f6785ec4f937add6728615682dd37c9a42d9548":"MY BOOKMARKS A BROWSER ACTION WITH A POPUP DUMP OF ALL BOOKMARKS, INCLUDING SEARCH, ADD, EDIT AND DELETE. BOOKMARKS BROWSER_ACTION POPUP TABS CHROME.BOOKMARKS.CREATE CHROME.BOOKMARKS.GET CHROME.BOOKMARKS.GETTREE CHROME.BOOKMARKS.REMOVE CHROME.BOOKMARKS.UPDATE CHROME.TABS.CREATE","597015d3bcce3da693b02314afd607bec4f55291":"NEWS READER DISPLAYS THE FIRST 5 ITEMS FROM THE GOOGLE NEWS - TOP NEWS RSS FEED IN A POPUP. BROWSER_ACTION POPUP TABS CHROME.TABS.CREATE","6444e5c8ae112a6a433909c5e770669cd16e2e5f":"NEWS READER DISPLAYS THE FIRST 5 ITEMS FROM THE GOOGLE NEWS - TOP NEWS RSS FEED IN A POPUP. BROWSER_ACTION POPUP TABS CHROME.I18N.GETMESSAGE CHROME.TABS.CREATE","3aea027164cb9b732ba4a8c51cb93708891726ef":"NEWS READER (BY GOOGLE) DISPLAYS THE LATEST STORIES FROM GOOGLE NEWS IN A POPUP. BACKGROUND_PAGE BROWSER_ACTION OPTIONS_PAGE POPUP TABS CHROME.EXTENSION.GETURL CHROME.I18N.GETMESSAGE CHROME.TABS.CREATE","f799e26ceef2367cf836f24bcb47df4398b0df58":"NOTIFICATION DEMO SHOWS OFF DESKTOP NOTIFICATIONS, WHICH ARE TOAST WINDOWS THAT POP UP ON THE DESKTOP. BACKGROUND_PAGE NOTIFICATIONS OPTIONS_PAGE TABS CHROME.TABS.CREATE","e787b322bddbc6289bb31b7d7550b1bf6456a80b":"OMNIBOX EXAMPLE TO USE, TYPE OMNIX PLUS A SEARCH TERM INTO THE OMNIBOX. BACKGROUND_PAGE CHROME.OMNIBOX.ONINPUTCHANGED CHROME.OMNIBOX.ONINPUTENTERED","8d0a50b57c26bb498be592e871001ffed91541b4":"PAGE ACTION BY CONTENT SHOWS A PAGE ACTION FOR HTML PAGES CONTAINING THE WORD SANDWICH BACKGROUND_PAGE PAGE_ACTION CHROME.EXTENSION.ONREQUEST CHROME.EXTENSION.SENDREQUEST CHROME.PAGEACTION.SHOW","80b86ccc6e8520660fa591caa565826f0ed1b12c":"PAGE ACTION BY URL SHOWS A PAGE ACTION FOR URLS WHICH HAVE THE LETTER G IN THEM. BACKGROUND_PAGE PAGE_ACTION TABS CHROME.PAGEACTION.SHOW CHROME.TABS.ONUPDATED","d74c3c18a1c1dd18b035149105a306f837c8823e":"PAGE BENCHMARKER CHROMIUM PAGE BENCHMARKER. BACKGROUND_PAGE BROWSER_ACTION OPTIONS_PAGE TABS CHROME.BROWSERACTION.ONCLICKED CHROME.BROWSERACTION.SETBADGEBACKGROUNDCOLOR CHROME.BROWSERACTION.SETBADGETEXT CHROME.BROWSERACTION.SETTITLE CHROME.EXTENSION.CONNECT CHROME.EXTENSION.GETBACKGROUNDPAGE CHROME.EXTENSION.GETEXTENSIONTABS CHROME.EXTENSION.GETURL CHROME.EXTENSION.ONCONNECT CHROME.TABS.CREATE CHROME.TABS.EXECUTESCRIPT CHROME.TABS.GET CHROME.TABS.GETALLINWINDOW CHROME.TABS.GETSELECTED CHROME.TABS.REMOVE CHROME.TABS.UPDATE CHROME.WINDOWS.GET CHROME.WINDOWS.GETCURRENT","e6ae17ab4ccfd7e059c8c01f25760ca5d894c7fd":"PRINT THIS PAGE ADDS A PRINT BUTTON TO THE BROWSER. BACKGROUND_PAGE BROWSER_ACTION TABS CHROME.BROWSERACTION.ONCLICKED CHROME.TABS.UPDATE","beff6ecd9677dea0a7c648c5042165b48bb66f09":"PROCESS MONITOR ADDS A BROWSER ACTION THAT MONITORS RESOURCE USAGE OF ALL BROWSER PROCESSES. BROWSER_ACTION EXPERIMENTAL POPUP TABS CHROME.EXPERIMENTAL.PROCESSES.ONUPDATED","56a8d2ac24ca7bba78fd88ad57f43fc13c784497":"SAMPLE - OAUTH CONTACTS USES OAUTH TO CONNECT TO GOOGLES CONTACTS SERVICE AND DISPLAY A LIST OF YOUR CONTACTS. BACKGROUND_PAGE BROWSER_ACTION TABS CHROME.BROWSERACTION.ONCLICKED CHROME.BROWSERACTION.SETICON CHROME.EXTENSION.GETBACKGROUNDPAGE CHROME.EXTENSION.GETURL CHROME.TABS.CREATE CHROME.TABS.GET CHROME.TABS.GETSELECTED CHROME.TABS.ONUPDATED CHROME.TABS.REMOVE","38f6e1e17756ede38b1364c7114a738ca717dcbb":"SANDWICHBAR SHOWS AN INFOBAR ON PAGES WHICH CONTAIN THE WORD SANDWICH BACKGROUND_PAGE EXPERIMENTAL CHROME.EXPERIMENTAL.INFOBARS.SHOW CHROME.EXTENSION.ONREQUEST CHROME.EXTENSION.SENDREQUEST","fc89b35755483af30b66cd72cefa34a43a3e8312":"SHOW TABS IN PROCESS ADDS A BROWSER ACTION SHOWING WHICH TABS SHARE THE CURRENT TABS PROCESS. BROWSER_ACTION EXPERIMENTAL POPUP TABS CHROME.EXPERIMENTAL.PROCESSES.GETPROCESSIDFORTAB CHROME.TABS.GET CHROME.TABS.GETSELECTED CHROME.TABS.UPDATE CHROME.WINDOWS.GET CHROME.WINDOWS.GETALL CHROME.WINDOWS.GETCURRENT CHROME.WINDOWS.UPDATE","230463f2d5c3d4d0ca13c230e1f00f2aae0a8a64":"TAB INSPECTOR UTILITY FOR WORKING WITH THE EXTENSION TABS API BACKGROUND_PAGE BROWSER_ACTION TABS CHROME.BROWSERACTION.ONCLICKED CHROME.EXTENSION.GETURL CHROME.TABS.CREATE CHROME.TABS.GET CHROME.TABS.GETALLINWINDOW CHROME.TABS.GETSELECTED CHROME.TABS.MOVE CHROME.TABS.ONATTACHED CHROME.TABS.ONCREATED CHROME.TABS.ONDETACHED CHROME.TABS.ONMOVED CHROME.TABS.ONREMOVED CHROME.TABS.ONSELECTIONCHANGED CHROME.TABS.ONUPDATED CHROME.TABS.REMOVE CHROME.TABS.UPDATE CHROME.WINDOWS.CREATE CHROME.WINDOWS.GET CHROME.WINDOWS.GETALL CHROME.WINDOWS.GETCURRENT CHROME.WINDOWS.GETLASTFOCUSED CHROME.WINDOWS.ONCREATED CHROME.WINDOWS.ONFOCUSCHANGED CHROME.WINDOWS.ONREMOVED CHROME.WINDOWS.REMOVE CHROME.WINDOWS.UPDATE","e1697cacebad05218798bf3e8a0f724517f0e8c3":"TEST SCREENSHOT EXTENSION DEMONSTRATE SCREENSHOT FUNCTIONALITY IN THE CHROME.TABS API. BACKGROUND_PAGE BROWSER_ACTION TABS CHROME.BROWSERACTION.ONCLICKED CHROME.EXTENSION.GETURL CHROME.EXTENSION.GETVIEWS CHROME.TABS.CAPTUREVISIBLETAB CHROME.TABS.CREATE CHROME.TABS.ONUPDATED","b3de91ab04b7d7a2670ca7ee9d740eb42cead0b6":"TYPED URL HISTORY READS YOUR HISTORY, AND SHOWS THE TOP TEN PAGES YOU GO TO BY TYPING THE URL. BROWSER_ACTION HISTORY TABS CHROME.HISTORY.GETVISITS CHROME.HISTORY.SEARCH CHROME.TABS.CREATE"}</script> +<script>var search_data = {"0262260daf0c8f7b28feff2ef23b05e7abf9d1e0":"A BROWSER ACTION WHICH CHANGES ITS ICON WHEN CLICKED. BACKGROUND_PAGE BROWSER_ACTION TABS CHROME.BROWSERACTION.ONCLICKED CHROME.BROWSERACTION.SETICON","ea2894c41cb8e80a4433a3e6c5772dadce9be90d":"A BROWSER ACTION WITH A POPUP THAT CHANGES THE PAGE COLOR. BROWSER_ACTION POPUP TABS CHROME.TABS.EXECUTESCRIPT","ede3c47b7757245be42ec33fd5ca63df4b490066":"A BROWSER ACTION WITH NO ICON THAT MAKES THE PAGE RED BACKGROUND_PAGE BROWSER_ACTION TABS CHROME.BROWSERACTION.ONCLICKED CHROME.BROWSERACTION.SETBADGEBACKGROUNDCOLOR CHROME.BROWSERACTION.SETBADGETEXT CHROME.TABS.EXECUTESCRIPT","fbf0aa1a09a15ff8cc4fc7de4fd176d6c663d07a":"ACCEPTLANGUAGE RETURNS ACCEPT LANGUAGES OF THE BROWSER BROWSER_ACTION POPUP CHROME.I18N.GETACCEPTLANGUAGES CHROME.I18N.GETMESSAGE","9a6e4ec46997fb92b324974afa08a3d007e2537f":"ANIMATED PAGE ACTION THIS EXTENSION ADDS AN ANIMATED BROWSER ACTION TO THE TOOLBAR. BACKGROUND_PAGE PAGE_ACTION TABS CHROME.PAGEACTION.HIDE CHROME.PAGEACTION.ONCLICKED CHROME.PAGEACTION.SETICON CHROME.PAGEACTION.SETTITLE CHROME.PAGEACTION.SHOW CHROME.TABS.GET CHROME.TABS.GETSELECTED CHROME.TABS.ONSELECTIONCHANGED","a1f7cf79dd555b04fa8d603247a040e644996293":"APP LAUNCHER BROWSER_ACTION MANAGEMENT CHROME.EXTENSION.GETURL CHROME.MANAGEMENT.GET CHROME.MANAGEMENT.GETALL CHROME.MANAGEMENT.LAUNCHAPP CHROME.TABS.CREATE","9747e3d6a3eab39bc7c17f11a80573c62d44c7e5":"BLANK NEW TAB PAGE CHROME_URL_OVERRIDES","903e7277139e1e6caec123d3319cab295d8d1b3a":"CHROME SOUNDS ENJOY A MORE MAGICAL AND IMMERSIVE EXPERIENCE WHEN BROWSING THE WEB USING THE POWER OF SOUND. BACKGROUND_PAGE BOOKMARKS OPTIONS_PAGE TABS CHROME.BOOKMARKS.ONCREATED CHROME.BOOKMARKS.ONMOVED CHROME.BOOKMARKS.ONREMOVED CHROME.EXTENSION.GETBACKGROUNDPAGE CHROME.EXTENSION.ONREQUEST CHROME.EXTENSION.SENDREQUEST CHROME.TABS.GET CHROME.TABS.ONATTACHED CHROME.TABS.ONCREATED CHROME.TABS.ONDETACHED CHROME.TABS.ONMOVED CHROME.TABS.ONREMOVED CHROME.TABS.ONSELECTIONCHANGED CHROME.TABS.ONUPDATED CHROME.WINDOWS.ONCREATED CHROME.WINDOWS.ONFOCUSCHANGED CHROME.WINDOWS.ONREMOVED","0e790e035a4a00b6f1def5ef9a7d7be1bce95ab5":"CHROMIUM BUILDBOT MONITOR DISPLAYS THE STATUS OF THE CHROMIUM BUILDBOT IN THE TOOLBAR. CLICK TO SEE MORE DETAILED STATUS IN A POPUP. BACKGROUND_PAGE BROWSER_ACTION NOTIFICATIONS OPTIONS_PAGE POPUP CHROME.BROWSERACTION.SETBADGEBACKGROUNDCOLOR CHROME.BROWSERACTION.SETBADGETEXT CHROME.BROWSERACTION.SETTITLE CHROME.EXTENSION.GETURL","ac31228200b41a87982e386cc90d3a6eee4ad885":"CHROMIUM SEARCH ADD SUPPORT TO THE OMNIBOX TO SEARCH THE CHROMIUM SOURCE CODE. BACKGROUND_PAGE TABS CHROME.OMNIBOX.ONINPUTCANCELLED CHROME.OMNIBOX.ONINPUTCHANGED CHROME.OMNIBOX.ONINPUTENTERED CHROME.OMNIBOX.ONINPUTSTARTED CHROME.OMNIBOX.SETDEFAULTSUGGESTION CHROME.TABS.GET CHROME.TABS.GETSELECTED CHROME.TABS.UPDATE","7d5d6cf195bc25480256618e360aa38c6e6fba82":"CLD DISPLAYS THE LANGUAGE OF A TAB BACKGROUND_PAGE BROWSER_ACTION TABS CHROME.BROWSERACTION.SETBADGETEXT CHROME.TABS.DETECTLANGUAGE CHROME.TABS.GET CHROME.TABS.GETSELECTED CHROME.TABS.ONSELECTIONCHANGED CHROME.TABS.ONUPDATED","5d81304a17cf7ac2887484f730fbd2b01e51e166":"CONTEXT MENUS SAMPLE SHOWS SOME OF THE FEATURES OF THE CONTEXT MENUS API BACKGROUND_PAGE CONTEXTMENUS CHROME.CONTEXTMENUS.CREATE","4daa6becd0899a54776d9cf7f09613ed1a9f4d77":"COOKIE API TEST EXTENSION TESTING COOKIE API BACKGROUND_PAGE BROWSER_ACTION COOKIES TABS CHROME.BROWSERACTION.ONCLICKED CHROME.COOKIES.GET CHROME.COOKIES.GETALL CHROME.COOKIES.ONCHANGED CHROME.COOKIES.REMOVE CHROME.EXTENSION.GETURL CHROME.TABS.CREATE CHROME.TABS.UPDATE CHROME.WINDOWS.GET CHROME.WINDOWS.GETALL","6871d09f4a96bf9d4b6cc724d00e909cee0f3902":"CROSS-DOMAIN XMLHTTPREQUEST FROM A CONTENT SCRIPT DEMONSTRATES A METHOD TO MAKE A CROSS-DOMAIN XMLHTTPREQUEST FETCH FROM A CONTENT SCRIPT. THIS EXTENSION FETCHES THE CURRENT TRENDING TOPICS FROM TWITTER AND INSERTS THEM IN AN OVERLAY AT THE TOP OF GOOGLE NEWS. VISIT HTTP://NEWS.GOOGLE.COM TO TEST THIS EXTENSION. BACKGROUND_PAGE CHROME.EXTENSION.ONREQUEST CHROME.EXTENSION.SENDREQUEST","028eb5364924344029bcbe1d527f132fc72b34e5":"EMAIL THIS PAGE (BY GOOGLE) THIS EXTENSION ADDS AN EMAIL BUTTON TO THE TOOLBAR WHICH ALLOWS YOU TO EMAIL THE PAGE LINK USING YOUR DEFAULT MAIL CLIENT OR GMAIL. BACKGROUND_PAGE BROWSER_ACTION OPTIONS_PAGE TABS CHROME.BROWSERACTION.ONCLICKED CHROME.EXTENSION.CONNECT CHROME.EXTENSION.ONCONNECT CHROME.TABS.CREATE CHROME.TABS.EXECUTESCRIPT CHROME.TABS.UPDATE","763a08e9b06595d785568a8d392b95a2f3700258":"EVENT TRACKING WITH GOOGLE ANALYTICS A SAMPLE EXTENSION WHICH USES GOOGLE ANALYTICS TO TRACK USAGE. BACKGROUND_PAGE BROWSER_ACTION POPUP","e3df888a89e35bdeb9c8bc8d03be5e1851b97c68":"EXTENSION DOCS SEARCH SEARCH THE CHROME EXTENSIONS DOCUMENTATION. TO USE, TYPE CRDOC PLUS A SEARCH TERM INTO THE OMNIBOX. BACKGROUND_PAGE TABS CHROME.OMNIBOX.ONINPUTCHANGED CHROME.OMNIBOX.ONINPUTENTERED CHROME.TABS.CREATE CHROME.TABS.GET CHROME.TABS.ONREMOVED CHROME.TABS.UPDATE","8b0dd31216235941bdd8eb33fda915ef5cf79a82":"GOOGLE CALENDAR CHECKER (BY GOOGLE) QUICKLY SEE THE TIME UNTIL YOUR NEXT MEETING FROM ANY OF YOUR CALENDARS. CLICK ON THE BUTTON TO BE TAKEN TO YOUR CALENDAR. BACKGROUND_PAGE BROWSER_ACTION OPTIONS_PAGE TABS CHROME.BROWSERACTION.ONCLICKED CHROME.BROWSERACTION.SETBADGEBACKGROUNDCOLOR CHROME.BROWSERACTION.SETBADGETEXT CHROME.BROWSERACTION.SETICON CHROME.BROWSERACTION.SETTITLE CHROME.EXTENSION.GETBACKGROUNDPAGE CHROME.EXTENSION.ONREQUEST CHROME.EXTENSION.SENDREQUEST CHROME.I18N.GETMESSAGE CHROME.TABS.CREATE CHROME.TABS.GET CHROME.TABS.GETALLINWINDOW CHROME.TABS.ONUPDATED CHROME.TABS.UPDATE","4e35caa9742fb82dbd628892d23a781614f6eff6":"GOOGLE DOCUMENT LIST VIEWER DEMONSTRATES HOW TO USE OAUTH TO CONNECT THE GOOGLE DOCUMENTS LIST DATA API. BACKGROUND_PAGE BROWSER_ACTION OPTIONS_PAGE POPUP TABS CHROME.BROWSERACTION.SETBADGETEXT CHROME.EXTENSION.GETBACKGROUNDPAGE CHROME.EXTENSION.GETURL CHROME.TABS.CREATE CHROME.TABS.GET CHROME.TABS.GETSELECTED CHROME.TABS.ONUPDATED CHROME.TABS.REMOVE","bb57f7a0132cbeb36ad7e7bb0ab75c21704234ca":"GOOGLE MAIL CHECKER DISPLAYS THE NUMBER OF UNREAD MESSAGES IN YOUR GOOGLE MAIL INBOX. YOU CAN ALSO CLICK THE BUTTON TO OPEN YOUR INBOX. BACKGROUND_PAGE BROWSER_ACTION OPTIONS_PAGE TABS CHROME.BROWSERACTION.ONCLICKED CHROME.BROWSERACTION.SETBADGEBACKGROUNDCOLOR CHROME.BROWSERACTION.SETBADGETEXT CHROME.BROWSERACTION.SETICON CHROME.EXTENSION.GETBACKGROUNDPAGE CHROME.I18N.GETMESSAGE CHROME.TABS.CREATE CHROME.TABS.GET CHROME.TABS.GETALLINWINDOW CHROME.TABS.ONUPDATED CHROME.TABS.UPDATE","1682e05ea9a1bde985123b04f6f8ac50a8a64033":"GOOGLE WAVE NOTIFIER FIND OUT WHEN YOU HAVE NEW WAVES AND PREVIEW THEM FAST. BACKGROUND_PAGE BROWSER_ACTION OPTIONS_PAGE POPUP TABS CHROME.EXTENSION.GETBACKGROUNDPAGE CHROME.EXTENSION.GETURL CHROME.TABS.CREATE CHROME.TABS.GET CHROME.TABS.GETSELECTED CHROME.TABS.ONUPDATED CHROME.TABS.REMOVE","14b9651fda4e57b2a5914ba73a779812201b750a":"HELLO WORLD THE FIRST EXTENSION THAT I MADE. BROWSER_ACTION POPUP","2020d72f2577f53caf8e94e3dbac0fb849ceaa4d":"IDLE - SIMPLE EXAMPLE DEMONSTRATES THE IDLE API BACKGROUND_PAGE BROWSER_ACTION IDLE CHROME.BROWSERACTION.ONCLICKED CHROME.EXTENSION.GETBACKGROUNDPAGE CHROME.IDLE.ONSTATECHANGED CHROME.IDLE.QUERYSTATE","0ea1588bd07b20338fc21f725de1542a5fdf9726":"IGOOGLE NEW TAB PAGE CHROME_URL_OVERRIDES","646325c25f572a1d15edc73d057f821d847a4fbe":"IMAGEINFO GET IMAGE INFO FOR IMAGES, INCLUDING EXIF DATA BACKGROUND_PAGE CONTEXTMENUS TABS CHROME.CONTEXTMENUS.CREATE CHROME.TABS.GET CHROME.TABS.GETCURRENT CHROME.WINDOWS.CREATE CHROME.WINDOWS.UPDATE","ec97ec20ca2f095d081e39f1565fc12af09ef067":"MAPPY FINDS ADDRESSES IN THE WEB PAGE YOURE ON AND POPS UP A MAP WINDOW. BACKGROUND_PAGE PAGE_ACTION POPUP TABS CHROME.EXTENSION.GETBACKGROUNDPAGE CHROME.EXTENSION.ONREQUEST CHROME.PAGEACTION.HIDE CHROME.PAGEACTION.SETTITLE CHROME.PAGEACTION.SHOW CHROME.TABS.GET CHROME.TABS.GETSELECTED CHROME.TABS.ONSELECTIONCHANGED CHROME.TABS.ONUPDATED CHROME.TABS.SENDREQUEST","b2f5f8a790e16f091a7e4e0a39b2d0a6d32e3a6d":"MERGE WINDOWS MERGES ALL OF THE BROWSERS WINDOWS INTO THE CURRENT WINDOW BACKGROUND_PAGE BROWSER_ACTION TABS CHROME.BROWSERACTION.ONCLICKED CHROME.TABS.GET CHROME.TABS.GETALLINWINDOW CHROME.TABS.MOVE CHROME.WINDOWS.GET CHROME.WINDOWS.GETALL CHROME.WINDOWS.GETCURRENT","51a83d2ba3a32e3ff1bdb624d4e18ccec4c4038e":"MESSAGE TIMER TIMES HOW LONG IT TAKES TO SEND A MESSAGE TO A CONTENT SCRIPT AND BACK. BROWSER_ACTION POPUP TABS CHROME.EXTENSION.ONCONNECT CHROME.EXTENSION.ONREQUEST CHROME.TABS.CONNECT CHROME.TABS.GET CHROME.TABS.GETSELECTED CHROME.TABS.SENDREQUEST","4f6785ec4f937add6728615682dd37c9a42d9548":"MY BOOKMARKS A BROWSER ACTION WITH A POPUP DUMP OF ALL BOOKMARKS, INCLUDING SEARCH, ADD, EDIT AND DELETE. BOOKMARKS BROWSER_ACTION POPUP TABS CHROME.BOOKMARKS.CREATE CHROME.BOOKMARKS.GET CHROME.BOOKMARKS.GETTREE CHROME.BOOKMARKS.REMOVE CHROME.BOOKMARKS.UPDATE CHROME.TABS.CREATE","597015d3bcce3da693b02314afd607bec4f55291":"NEWS READER DISPLAYS THE FIRST 5 ITEMS FROM THE GOOGLE NEWS - TOP NEWS RSS FEED IN A POPUP. BROWSER_ACTION POPUP TABS CHROME.TABS.CREATE","6444e5c8ae112a6a433909c5e770669cd16e2e5f":"NEWS READER DISPLAYS THE FIRST 5 ITEMS FROM THE GOOGLE NEWS - TOP NEWS RSS FEED IN A POPUP. BROWSER_ACTION POPUP TABS CHROME.I18N.GETMESSAGE CHROME.TABS.CREATE","3aea027164cb9b732ba4a8c51cb93708891726ef":"NEWS READER (BY GOOGLE) DISPLAYS THE LATEST STORIES FROM GOOGLE NEWS IN A POPUP. BACKGROUND_PAGE BROWSER_ACTION OPTIONS_PAGE POPUP TABS CHROME.EXTENSION.GETURL CHROME.I18N.GETMESSAGE CHROME.TABS.CREATE","f799e26ceef2367cf836f24bcb47df4398b0df58":"NOTIFICATION DEMO SHOWS OFF DESKTOP NOTIFICATIONS, WHICH ARE TOAST WINDOWS THAT POP UP ON THE DESKTOP. BACKGROUND_PAGE NOTIFICATIONS OPTIONS_PAGE TABS CHROME.TABS.CREATE","e787b322bddbc6289bb31b7d7550b1bf6456a80b":"OMNIBOX EXAMPLE TO USE, TYPE OMNIX PLUS A SEARCH TERM INTO THE OMNIBOX. BACKGROUND_PAGE CHROME.OMNIBOX.ONINPUTCHANGED CHROME.OMNIBOX.ONINPUTENTERED","8d0a50b57c26bb498be592e871001ffed91541b4":"PAGE ACTION BY CONTENT SHOWS A PAGE ACTION FOR HTML PAGES CONTAINING THE WORD SANDWICH BACKGROUND_PAGE PAGE_ACTION CHROME.EXTENSION.ONREQUEST CHROME.EXTENSION.SENDREQUEST CHROME.PAGEACTION.SHOW","80b86ccc6e8520660fa591caa565826f0ed1b12c":"PAGE ACTION BY URL SHOWS A PAGE ACTION FOR URLS WHICH HAVE THE LETTER G IN THEM. BACKGROUND_PAGE PAGE_ACTION TABS CHROME.PAGEACTION.SHOW CHROME.TABS.ONUPDATED","d74c3c18a1c1dd18b035149105a306f837c8823e":"PAGE BENCHMARKER CHROMIUM PAGE BENCHMARKER. BACKGROUND_PAGE BROWSER_ACTION OPTIONS_PAGE TABS CHROME.BROWSERACTION.ONCLICKED CHROME.BROWSERACTION.SETBADGEBACKGROUNDCOLOR CHROME.BROWSERACTION.SETBADGETEXT CHROME.BROWSERACTION.SETTITLE CHROME.EXTENSION.CONNECT CHROME.EXTENSION.GETBACKGROUNDPAGE CHROME.EXTENSION.GETEXTENSIONTABS CHROME.EXTENSION.GETURL CHROME.EXTENSION.ONCONNECT CHROME.TABS.CREATE CHROME.TABS.EXECUTESCRIPT CHROME.TABS.GET CHROME.TABS.GETALLINWINDOW CHROME.TABS.GETSELECTED CHROME.TABS.REMOVE CHROME.TABS.UPDATE CHROME.WINDOWS.GET CHROME.WINDOWS.GETCURRENT","e6ae17ab4ccfd7e059c8c01f25760ca5d894c7fd":"PRINT THIS PAGE ADDS A PRINT BUTTON TO THE BROWSER. BACKGROUND_PAGE BROWSER_ACTION TABS CHROME.BROWSERACTION.ONCLICKED CHROME.TABS.UPDATE","beff6ecd9677dea0a7c648c5042165b48bb66f09":"PROCESS MONITOR ADDS A BROWSER ACTION THAT MONITORS RESOURCE USAGE OF ALL BROWSER PROCESSES. BROWSER_ACTION EXPERIMENTAL POPUP TABS CHROME.EXPERIMENTAL.PROCESSES.ONUPDATED","3e8e226d87e431296bb110b4f6eb7eec2ca7a826":"PROXY SETTINGS SET CHROME-SPECIFIC PROXIES; A DEMONSTRATION OF CHROMES PROXY API TTS BACKGROUND_PAGE BROWSER_ACTION EXPERIMENTAL POPUP PROXY CHROME.BROWSERACTION.SETBADGEBACKGROUNDCOLOR CHROME.BROWSERACTION.SETBADGETEXT CHROME.BROWSERACTION.SETTITLE CHROME.EXPERIMENTAL.PROXY.ONPROXYERROR CHROME.EXTENSION.ONREQUEST CHROME.EXTENSION.SENDREQUEST CHROME.I18N.GETMESSAGE","56a8d2ac24ca7bba78fd88ad57f43fc13c784497":"SAMPLE - OAUTH CONTACTS USES OAUTH TO CONNECT TO GOOGLES CONTACTS SERVICE AND DISPLAY A LIST OF YOUR CONTACTS. BACKGROUND_PAGE BROWSER_ACTION TABS CHROME.BROWSERACTION.ONCLICKED CHROME.BROWSERACTION.SETICON CHROME.EXTENSION.GETBACKGROUNDPAGE CHROME.EXTENSION.GETURL CHROME.TABS.CREATE CHROME.TABS.GET CHROME.TABS.GETSELECTED CHROME.TABS.ONUPDATED CHROME.TABS.REMOVE","38f6e1e17756ede38b1364c7114a738ca717dcbb":"SANDWICHBAR SHOWS AN INFOBAR ON PAGES WHICH CONTAIN THE WORD SANDWICH BACKGROUND_PAGE EXPERIMENTAL CHROME.EXPERIMENTAL.INFOBARS.SHOW CHROME.EXTENSION.ONREQUEST CHROME.EXTENSION.SENDREQUEST","fc89b35755483af30b66cd72cefa34a43a3e8312":"SHOW TABS IN PROCESS ADDS A BROWSER ACTION SHOWING WHICH TABS SHARE THE CURRENT TABS PROCESS. BROWSER_ACTION EXPERIMENTAL POPUP TABS CHROME.EXPERIMENTAL.PROCESSES.GETPROCESSIDFORTAB CHROME.TABS.GET CHROME.TABS.GETSELECTED CHROME.TABS.UPDATE CHROME.WINDOWS.GET CHROME.WINDOWS.GETALL CHROME.WINDOWS.GETCURRENT CHROME.WINDOWS.UPDATE","230463f2d5c3d4d0ca13c230e1f00f2aae0a8a64":"TAB INSPECTOR UTILITY FOR WORKING WITH THE EXTENSION TABS API BACKGROUND_PAGE BROWSER_ACTION TABS CHROME.BROWSERACTION.ONCLICKED CHROME.EXTENSION.GETURL CHROME.TABS.CREATE CHROME.TABS.GET CHROME.TABS.GETALLINWINDOW CHROME.TABS.GETSELECTED CHROME.TABS.MOVE CHROME.TABS.ONATTACHED CHROME.TABS.ONCREATED CHROME.TABS.ONDETACHED CHROME.TABS.ONMOVED CHROME.TABS.ONREMOVED CHROME.TABS.ONSELECTIONCHANGED CHROME.TABS.ONUPDATED CHROME.TABS.REMOVE CHROME.TABS.UPDATE CHROME.WINDOWS.CREATE CHROME.WINDOWS.GET CHROME.WINDOWS.GETALL CHROME.WINDOWS.GETCURRENT CHROME.WINDOWS.GETLASTFOCUSED CHROME.WINDOWS.ONCREATED CHROME.WINDOWS.ONFOCUSCHANGED CHROME.WINDOWS.ONREMOVED CHROME.WINDOWS.REMOVE CHROME.WINDOWS.UPDATE","e1697cacebad05218798bf3e8a0f724517f0e8c3":"TEST SCREENSHOT EXTENSION DEMONSTRATE SCREENSHOT FUNCTIONALITY IN THE CHROME.TABS API. BACKGROUND_PAGE BROWSER_ACTION TABS CHROME.BROWSERACTION.ONCLICKED CHROME.EXTENSION.GETURL CHROME.EXTENSION.GETVIEWS CHROME.TABS.CAPTUREVISIBLETAB CHROME.TABS.CREATE CHROME.TABS.ONUPDATED","b3de91ab04b7d7a2670ca7ee9d740eb42cead0b6":"TYPED URL HISTORY READS YOUR HISTORY, AND SHOWS THE TOP TEN PAGES YOU GO TO BY TYPING THE URL. BROWSER_ACTION HISTORY TABS CHROME.HISTORY.GETVISITS CHROME.HISTORY.SEARCH CHROME.TABS.CREATE"}</script> <script src="js/sample_search.js"></script> @@ -2441,6 +2441,82 @@ <a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/processes/process_monitor/">Browse source</a> - <a href="examples/api/processes/process_monitor.zip">Download source</a> </div> +</div><div class="sample" id="3e8e226d87e431296bb110b4f6eb7eec2ca7a826"> + <img class="icon" style="display: none; "> + <img class="icon" src="images/sample-default-icon.png"> + <h2 class="name"> + <a href="#3e8e226d87e431296bb110b4f6eb7eec2ca7a826">Proxy Settings</a> + </h2> + <p class="metadata features">Uses + <span> + <strong>TTS</strong><span>, </span> + <span style="display: none; "> and</span> + </span><span> + <strong>background_page</strong><span>, </span> + <span style="display: none; "> and</span> + </span><span> + <strong>browser_action</strong><span>, </span> + <span style="display: none; "> and</span> + </span><span> + <strong>experimental</strong><span>, </span> + <span style="display: none; "> and</span> + </span><span> + <strong>popup</strong><span style="display: none; ">, </span> + <span> and</span> + </span><span> + <strong>proxy</strong><span style="display: none; ">, </span> + <span style="display: none; "> and</span> + </span> + </p> + <p>Set Chrome-specific proxies; a demonstration of Chrome's Proxy API</p> + <div class="apicalls"><strong>Calls:</strong> + <ul> + <li> + <code><a href="browserAction.html#method-setBadgeBackgroundColor">chrome.browserAction.setBadgeBackgroundColor</a></code> + </li><li> + <code><a href="browserAction.html#method-setBadgeText">chrome.browserAction.setBadgeText</a></code> + </li><li> + <code><a href="browserAction.html#method-setTitle">chrome.browserAction.setTitle</a></code> + </li><li> + <code><a href="experimental.proxy.html#event-onProxyError">chrome.experimental.proxy.onProxyError</a></code> + </li><li> + <code><a href="extension.html#event-onRequest">chrome.extension.onRequest</a></code> + </li><li> + <code><a href="extension.html#method-sendRequest">chrome.extension.sendRequest</a></code> + </li><li> + <code><a href="i18n.html#method-getMessage">chrome.i18n.getMessage</a></code> + </li> + </ul> + </div> + <div class="sourcefiles"><strong>Source files:</strong> + <ul> + <li> + <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/proxy_configuration/_locales/en/messages.json?content-type=text/plain">_locales/en/messages.json</a></code> + </li><li> + <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/proxy_configuration/background.html?content-type=text/plain">background.html</a></code> + </li><li> + <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/proxy_configuration/manifest.json?content-type=text/plain">manifest.json</a></code> + </li><li> + <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/proxy_configuration/popup.html?content-type=text/plain">popup.html</a></code> + </li><li> + <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/proxy_configuration/proxy_error_handler.js?content-type=text/plain">proxy_error_handler.js</a></code> + </li><li> + <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/proxy_configuration/proxy_form_controller.js?content-type=text/plain">proxy_form_controller.js</a></code> + </li><li> + <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/proxy_configuration/test/jsunittest.js?content-type=text/plain">test/jsunittest.js</a></code> + </li><li> + <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/proxy_configuration/test/proxy_form_controller_test.html?content-type=text/plain">test/proxy_form_controller_test.html</a></code> + </li><li> + <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/proxy_configuration/test/proxy_form_controller_test.js?content-type=text/plain">test/proxy_form_controller_test.js</a></code> + </li><li> + <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/proxy_configuration/test/unittest.css?content-type=text/plain">test/unittest.css</a></code> + </li> + </ul> + </div> + <div> + <a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/proxy_configuration/">Browse source</a> + - <a href="examples/extensions/proxy_configuration.zip">Download source</a> + </div> </div><div class="sample" id="56a8d2ac24ca7bba78fd88ad57f43fc13c784497"> <img class="icon" src="examples/extensions/oauth_contacts/img/icon-128.png"> <img class="icon" src="images/sample-default-icon.png" style="display: none; "> diff --git a/chrome/common/extensions/docs/samples.json b/chrome/common/extensions/docs/samples.json index 2aad896..e293a2e 100644 --- a/chrome/common/extensions/docs/samples.json +++ b/chrome/common/extensions/docs/samples.json @@ -1378,6 +1378,46 @@ }, { "api_calls": [ + "chrome.browserAction.setBadgeBackgroundColor", + "chrome.browserAction.setBadgeText", + "chrome.browserAction.setTitle", + "chrome.experimental.proxy.onProxyError", + "chrome.extension.onRequest", + "chrome.extension.sendRequest", + "chrome.i18n.getMessage" + ], + "description": "Set Chrome-specific proxies; a demonstration of Chrome's Proxy API", + "features": [ + "TTS", + "background_page", + "browser_action", + "experimental", + "popup", + "proxy" + ], + "icon": null, + "id": "3e8e226d87e431296bb110b4f6eb7eec2ca7a826", + "name": "Proxy Settings", + "path": "examples/extensions/proxy_configuration/", + "protocols": [], + "search_string": "PROXY SETTINGS SET CHROME-SPECIFIC PROXIES; A DEMONSTRATION OF CHROMES PROXY API TTS BACKGROUND_PAGE BROWSER_ACTION EXPERIMENTAL POPUP PROXY CHROME.BROWSERACTION.SETBADGEBACKGROUNDCOLOR CHROME.BROWSERACTION.SETBADGETEXT CHROME.BROWSERACTION.SETTITLE CHROME.EXPERIMENTAL.PROXY.ONPROXYERROR CHROME.EXTENSION.ONREQUEST CHROME.EXTENSION.SENDREQUEST CHROME.I18N.GETMESSAGE", + "source_files": [ + "_locales/en/messages.json", + "background.html", + "manifest.json", + "popup.html", + "proxy_error_handler.js", + "proxy_form_controller.js", + "test/jsunittest.js", + "test/proxy_form_controller_test.html", + "test/proxy_form_controller_test.js", + "test/unittest.css" + ], + "source_hash": "866078e5f0611065ef9ef26225a7c38abaa32509", + "zip_path": "examples/extensions/proxy_configuration.zip" + }, + { + "api_calls": [ "chrome.browserAction.onClicked", "chrome.browserAction.setIcon", "chrome.extension.getBackgroundPage", |